2 * Cirrus Logic CLPS711X FB driver
4 * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
5 * Based on driver by Russell King <rmk@arm.linux.org.uk>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
13 #include <linux/clk.h>
16 #include <linux/lcd.h>
17 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/regmap.h>
21 #include <linux/mfd/syscon.h>
22 #include <linux/mfd/syscon/clps711x.h>
23 #include <linux/regulator/consumer.h>
24 #include <video/of_display_timing.h>
26 #define CLPS711X_FB_NAME "clps711x-fb"
27 #define CLPS711X_FB_BPP_MAX (4)
29 /* Registers relative to LCDCON */
30 #define CLPS711X_LCDCON (0x0000)
31 # define LCDCON_GSEN BIT(30)
32 # define LCDCON_GSMD BIT(31)
33 #define CLPS711X_PALLSW (0x0280)
34 #define CLPS711X_PALMSW (0x02c0)
35 #define CLPS711X_FBADDR (0x0d40)
37 struct clps711x_fb_info
{
40 struct regmap
*syscon
;
41 resource_size_t buffsize
;
42 struct fb_videomode mode
;
43 struct regulator
*lcd_pwr
;
48 static int clps711x_fb_setcolreg(u_int regno
, u_int red
, u_int green
,
49 u_int blue
, u_int transp
, struct fb_info
*info
)
51 struct clps711x_fb_info
*cfb
= info
->par
;
52 u32 level
, mask
, shift
;
54 if (regno
>= BIT(info
->var
.bits_per_pixel
))
57 shift
= 4 * (regno
& 7);
59 /* gray = 0.30*R + 0.58*G + 0.11*B */
60 level
= (((red
* 77 + green
* 151 + blue
* 28) >> 20) << shift
) & mask
;
64 regno
= (regno
< 8) ? CLPS711X_PALLSW
: CLPS711X_PALMSW
;
66 writel((readl(cfb
->base
+ regno
) & ~mask
) | level
, cfb
->base
+ regno
);
71 static int clps711x_fb_check_var(struct fb_var_screeninfo
*var
,
76 if (var
->bits_per_pixel
< 1 ||
77 var
->bits_per_pixel
> CLPS711X_FB_BPP_MAX
)
83 val
= DIV_ROUND_UP(var
->xres
, 16) - 1;
84 if (val
< 0x01 || val
> 0x3f)
87 val
= DIV_ROUND_UP(var
->yres
* var
->xres
* var
->bits_per_pixel
, 128);
89 if (val
< 0x001 || val
> 0x1fff)
92 var
->transp
.msb_right
= 0;
93 var
->transp
.offset
= 0;
94 var
->transp
.length
= 0;
95 var
->red
.msb_right
= 0;
97 var
->red
.length
= var
->bits_per_pixel
;
98 var
->green
= var
->red
;
100 var
->grayscale
= var
->bits_per_pixel
> 1;
105 static int clps711x_fb_set_par(struct fb_info
*info
)
107 struct clps711x_fb_info
*cfb
= info
->par
;
108 resource_size_t size
;
111 size
= (info
->var
.xres
* info
->var
.yres
* info
->var
.bits_per_pixel
) / 8;
112 if (size
> cfb
->buffsize
)
115 switch (info
->var
.bits_per_pixel
) {
117 info
->fix
.visual
= FB_VISUAL_MONO01
;
121 info
->fix
.visual
= FB_VISUAL_PSEUDOCOLOR
;
127 info
->fix
.line_length
= info
->var
.xres
* info
->var
.bits_per_pixel
/ 8;
128 info
->fix
.smem_len
= size
;
130 lcdcon
= (info
->var
.xres
* info
->var
.yres
*
131 info
->var
.bits_per_pixel
) / 128 - 1;
132 lcdcon
|= ((info
->var
.xres
/ 16) - 1) << 13;
133 lcdcon
|= (cfb
->ac_prescale
& 0x1f) << 25;
135 pps
= clk_get_rate(cfb
->clk
) / (PICOS2KHZ(info
->var
.pixclock
) * 1000);
138 lcdcon
|= (pps
& 0x3f) << 19;
140 if (info
->var
.bits_per_pixel
== 4)
141 lcdcon
|= LCDCON_GSMD
;
142 if (info
->var
.bits_per_pixel
>= 2)
143 lcdcon
|= LCDCON_GSEN
;
145 /* LCDCON must only be changed while the LCD is disabled */
146 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
147 writel(lcdcon
, cfb
->base
+ CLPS711X_LCDCON
);
148 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
,
149 SYSCON1_LCDEN
, SYSCON1_LCDEN
);
154 static int clps711x_fb_blank(int blank
, struct fb_info
*info
)
160 static struct fb_ops clps711x_fb_ops
= {
161 .owner
= THIS_MODULE
,
162 .fb_setcolreg
= clps711x_fb_setcolreg
,
163 .fb_check_var
= clps711x_fb_check_var
,
164 .fb_set_par
= clps711x_fb_set_par
,
165 .fb_blank
= clps711x_fb_blank
,
166 .fb_fillrect
= sys_fillrect
,
167 .fb_copyarea
= sys_copyarea
,
168 .fb_imageblit
= sys_imageblit
,
171 static int clps711x_lcd_check_fb(struct lcd_device
*lcddev
, struct fb_info
*fi
)
173 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
175 return (!fi
|| fi
->par
== cfb
) ? 1 : 0;
178 static int clps711x_lcd_get_power(struct lcd_device
*lcddev
)
180 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
182 if (!IS_ERR_OR_NULL(cfb
->lcd_pwr
))
183 if (!regulator_is_enabled(cfb
->lcd_pwr
))
184 return FB_BLANK_NORMAL
;
186 return FB_BLANK_UNBLANK
;
189 static int clps711x_lcd_set_power(struct lcd_device
*lcddev
, int blank
)
191 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
193 if (!IS_ERR_OR_NULL(cfb
->lcd_pwr
)) {
194 if (blank
== FB_BLANK_UNBLANK
) {
195 if (!regulator_is_enabled(cfb
->lcd_pwr
))
196 return regulator_enable(cfb
->lcd_pwr
);
198 if (regulator_is_enabled(cfb
->lcd_pwr
))
199 return regulator_disable(cfb
->lcd_pwr
);
206 static struct lcd_ops clps711x_lcd_ops
= {
207 .check_fb
= clps711x_lcd_check_fb
,
208 .get_power
= clps711x_lcd_get_power
,
209 .set_power
= clps711x_lcd_set_power
,
212 static int clps711x_fb_probe(struct platform_device
*pdev
)
214 struct device
*dev
= &pdev
->dev
;
215 struct device_node
*disp
, *np
= dev
->of_node
;
216 struct clps711x_fb_info
*cfb
;
217 struct lcd_device
*lcd
;
218 struct fb_info
*info
;
219 struct resource
*res
;
223 if (fb_get_options(CLPS711X_FB_NAME
, NULL
))
226 info
= framebuffer_alloc(sizeof(*cfb
), dev
);
231 platform_set_drvdata(pdev
, info
);
233 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
236 cfb
->base
= devm_ioremap(dev
, res
->start
, resource_size(res
));
242 info
->fix
.mmio_start
= res
->start
;
243 info
->fix
.mmio_len
= resource_size(res
);
245 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 1);
246 info
->screen_base
= devm_ioremap_resource(dev
, res
);
247 if (IS_ERR(info
->screen_base
)) {
248 ret
= PTR_ERR(info
->screen_base
);
252 /* Physical address should be aligned to 256 MiB */
253 if (res
->start
& 0x0fffffff) {
258 info
->apertures
= alloc_apertures(1);
259 if (!info
->apertures
) {
264 cfb
->buffsize
= resource_size(res
);
265 info
->fix
.smem_start
= res
->start
;
266 info
->apertures
->ranges
[0].base
= info
->fix
.smem_start
;
267 info
->apertures
->ranges
[0].size
= cfb
->buffsize
;
269 cfb
->clk
= devm_clk_get(dev
, NULL
);
270 if (IS_ERR(cfb
->clk
)) {
271 ret
= PTR_ERR(cfb
->clk
);
276 syscon_regmap_lookup_by_compatible("cirrus,ep7209-syscon1");
277 if (IS_ERR(cfb
->syscon
)) {
278 ret
= PTR_ERR(cfb
->syscon
);
282 disp
= of_parse_phandle(np
, "display", 0);
284 dev_err(&pdev
->dev
, "No display defined\n");
289 ret
= of_get_fb_videomode(disp
, &cfb
->mode
, OF_USE_NATIVE_MODE
);
295 of_property_read_u32(disp
, "ac-prescale", &cfb
->ac_prescale
);
296 cfb
->cmap_invert
= of_property_read_bool(disp
, "cmap-invert");
298 ret
= of_property_read_u32(disp
, "bits-per-pixel",
299 &info
->var
.bits_per_pixel
);
304 /* Force disable LCD on any mismatch */
305 if (info
->fix
.smem_start
!= (readb(cfb
->base
+ CLPS711X_FBADDR
) << 28))
306 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
,
309 ret
= regmap_read(cfb
->syscon
, SYSCON_OFFSET
, &val
);
313 if (!(val
& SYSCON1_LCDEN
)) {
314 /* Setup start FB address */
315 writeb(info
->fix
.smem_start
>> 28, cfb
->base
+ CLPS711X_FBADDR
);
316 /* Clean FB memory */
317 memset_io(info
->screen_base
, 0, cfb
->buffsize
);
320 cfb
->lcd_pwr
= devm_regulator_get(dev
, "lcd");
321 if (PTR_ERR(cfb
->lcd_pwr
) == -EPROBE_DEFER
) {
326 info
->fbops
= &clps711x_fb_ops
;
327 info
->flags
= FBINFO_DEFAULT
;
328 info
->var
.activate
= FB_ACTIVATE_FORCE
| FB_ACTIVATE_NOW
;
329 info
->var
.height
= -1;
330 info
->var
.width
= -1;
331 info
->var
.vmode
= FB_VMODE_NONINTERLACED
;
332 info
->fix
.type
= FB_TYPE_PACKED_PIXELS
;
333 info
->fix
.accel
= FB_ACCEL_NONE
;
334 strlcpy(info
->fix
.id
, CLPS711X_FB_NAME
, sizeof(info
->fix
.id
));
335 fb_videomode_to_var(&info
->var
, &cfb
->mode
);
337 ret
= fb_alloc_cmap(&info
->cmap
, BIT(CLPS711X_FB_BPP_MAX
), 0);
341 ret
= fb_set_var(info
, &info
->var
);
343 goto out_fb_dealloc_cmap
;
345 ret
= register_framebuffer(info
);
347 goto out_fb_dealloc_cmap
;
349 lcd
= devm_lcd_device_register(dev
, "clps711x-lcd", dev
, cfb
,
355 unregister_framebuffer(info
);
358 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
359 fb_dealloc_cmap(&info
->cmap
);
362 framebuffer_release(info
);
367 static int clps711x_fb_remove(struct platform_device
*pdev
)
369 struct fb_info
*info
= platform_get_drvdata(pdev
);
370 struct clps711x_fb_info
*cfb
= info
->par
;
372 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
374 unregister_framebuffer(info
);
375 fb_dealloc_cmap(&info
->cmap
);
376 framebuffer_release(info
);
381 static const struct of_device_id clps711x_fb_dt_ids
[] = {
382 { .compatible
= "cirrus,ep7209-fb", },
385 MODULE_DEVICE_TABLE(of
, clps711x_fb_dt_ids
);
387 static struct platform_driver clps711x_fb_driver
= {
389 .name
= CLPS711X_FB_NAME
,
390 .of_match_table
= clps711x_fb_dt_ids
,
392 .probe
= clps711x_fb_probe
,
393 .remove
= clps711x_fb_remove
,
395 module_platform_driver(clps711x_fb_driver
);
397 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
398 MODULE_DESCRIPTION("Cirrus Logic CLPS711X FB driver");
399 MODULE_LICENSE("GPL");