1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Cirrus Logic CLPS711X FB driver
5 * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
6 * Based on driver by Russell King <rmk@arm.linux.org.uk>
12 #include <linux/lcd.h>
13 #include <linux/module.h>
15 #include <linux/platform_device.h>
16 #include <linux/regmap.h>
17 #include <linux/mfd/syscon.h>
18 #include <linux/mfd/syscon/clps711x.h>
19 #include <linux/regulator/consumer.h>
20 #include <video/of_display_timing.h>
22 #define CLPS711X_FB_NAME "clps711x-fb"
23 #define CLPS711X_FB_BPP_MAX (4)
25 /* Registers relative to LCDCON */
26 #define CLPS711X_LCDCON (0x0000)
27 # define LCDCON_GSEN BIT(30)
28 # define LCDCON_GSMD BIT(31)
29 #define CLPS711X_PALLSW (0x0280)
30 #define CLPS711X_PALMSW (0x02c0)
31 #define CLPS711X_FBADDR (0x0d40)
33 struct clps711x_fb_info
{
36 struct regmap
*syscon
;
37 resource_size_t buffsize
;
38 struct fb_videomode mode
;
39 struct regulator
*lcd_pwr
;
44 static int clps711x_fb_setcolreg(u_int regno
, u_int red
, u_int green
,
45 u_int blue
, u_int transp
, struct fb_info
*info
)
47 struct clps711x_fb_info
*cfb
= info
->par
;
48 u32 level
, mask
, shift
;
50 if (regno
>= BIT(info
->var
.bits_per_pixel
))
53 shift
= 4 * (regno
& 7);
55 /* gray = 0.30*R + 0.58*G + 0.11*B */
56 level
= (((red
* 77 + green
* 151 + blue
* 28) >> 20) << shift
) & mask
;
60 regno
= (regno
< 8) ? CLPS711X_PALLSW
: CLPS711X_PALMSW
;
62 writel((readl(cfb
->base
+ regno
) & ~mask
) | level
, cfb
->base
+ regno
);
67 static int clps711x_fb_check_var(struct fb_var_screeninfo
*var
,
72 if (var
->bits_per_pixel
< 1 ||
73 var
->bits_per_pixel
> CLPS711X_FB_BPP_MAX
)
79 val
= DIV_ROUND_UP(var
->xres
, 16) - 1;
80 if (val
< 0x01 || val
> 0x3f)
83 val
= DIV_ROUND_UP(var
->yres
* var
->xres
* var
->bits_per_pixel
, 128);
85 if (val
< 0x001 || val
> 0x1fff)
88 var
->transp
.msb_right
= 0;
89 var
->transp
.offset
= 0;
90 var
->transp
.length
= 0;
91 var
->red
.msb_right
= 0;
93 var
->red
.length
= var
->bits_per_pixel
;
94 var
->green
= var
->red
;
96 var
->grayscale
= var
->bits_per_pixel
> 1;
101 static int clps711x_fb_set_par(struct fb_info
*info
)
103 struct clps711x_fb_info
*cfb
= info
->par
;
104 resource_size_t size
;
107 size
= (info
->var
.xres
* info
->var
.yres
* info
->var
.bits_per_pixel
) / 8;
108 if (size
> cfb
->buffsize
)
111 switch (info
->var
.bits_per_pixel
) {
113 info
->fix
.visual
= FB_VISUAL_MONO01
;
117 info
->fix
.visual
= FB_VISUAL_PSEUDOCOLOR
;
123 info
->fix
.line_length
= info
->var
.xres
* info
->var
.bits_per_pixel
/ 8;
124 info
->fix
.smem_len
= size
;
126 lcdcon
= (info
->var
.xres
* info
->var
.yres
*
127 info
->var
.bits_per_pixel
) / 128 - 1;
128 lcdcon
|= ((info
->var
.xres
/ 16) - 1) << 13;
129 lcdcon
|= (cfb
->ac_prescale
& 0x1f) << 25;
131 pps
= clk_get_rate(cfb
->clk
) / (PICOS2KHZ(info
->var
.pixclock
) * 1000);
134 lcdcon
|= (pps
& 0x3f) << 19;
136 if (info
->var
.bits_per_pixel
== 4)
137 lcdcon
|= LCDCON_GSMD
;
138 if (info
->var
.bits_per_pixel
>= 2)
139 lcdcon
|= LCDCON_GSEN
;
141 /* LCDCON must only be changed while the LCD is disabled */
142 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
143 writel(lcdcon
, cfb
->base
+ CLPS711X_LCDCON
);
144 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
,
145 SYSCON1_LCDEN
, SYSCON1_LCDEN
);
150 static int clps711x_fb_blank(int blank
, struct fb_info
*info
)
156 static const struct fb_ops clps711x_fb_ops
= {
157 .owner
= THIS_MODULE
,
158 .fb_setcolreg
= clps711x_fb_setcolreg
,
159 .fb_check_var
= clps711x_fb_check_var
,
160 .fb_set_par
= clps711x_fb_set_par
,
161 .fb_blank
= clps711x_fb_blank
,
162 .fb_fillrect
= sys_fillrect
,
163 .fb_copyarea
= sys_copyarea
,
164 .fb_imageblit
= sys_imageblit
,
167 static int clps711x_lcd_check_fb(struct lcd_device
*lcddev
, struct fb_info
*fi
)
169 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
171 return (!fi
|| fi
->par
== cfb
) ? 1 : 0;
174 static int clps711x_lcd_get_power(struct lcd_device
*lcddev
)
176 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
178 if (!IS_ERR_OR_NULL(cfb
->lcd_pwr
))
179 if (!regulator_is_enabled(cfb
->lcd_pwr
))
180 return FB_BLANK_NORMAL
;
182 return FB_BLANK_UNBLANK
;
185 static int clps711x_lcd_set_power(struct lcd_device
*lcddev
, int blank
)
187 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
189 if (!IS_ERR_OR_NULL(cfb
->lcd_pwr
)) {
190 if (blank
== FB_BLANK_UNBLANK
) {
191 if (!regulator_is_enabled(cfb
->lcd_pwr
))
192 return regulator_enable(cfb
->lcd_pwr
);
194 if (regulator_is_enabled(cfb
->lcd_pwr
))
195 return regulator_disable(cfb
->lcd_pwr
);
202 static struct lcd_ops clps711x_lcd_ops
= {
203 .check_fb
= clps711x_lcd_check_fb
,
204 .get_power
= clps711x_lcd_get_power
,
205 .set_power
= clps711x_lcd_set_power
,
208 static int clps711x_fb_probe(struct platform_device
*pdev
)
210 struct device
*dev
= &pdev
->dev
;
211 struct device_node
*disp
, *np
= dev
->of_node
;
212 struct clps711x_fb_info
*cfb
;
213 struct lcd_device
*lcd
;
214 struct fb_info
*info
;
215 struct resource
*res
;
219 if (fb_get_options(CLPS711X_FB_NAME
, NULL
))
222 info
= framebuffer_alloc(sizeof(*cfb
), dev
);
227 platform_set_drvdata(pdev
, info
);
229 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
232 cfb
->base
= devm_ioremap(dev
, res
->start
, resource_size(res
));
238 info
->fix
.mmio_start
= res
->start
;
239 info
->fix
.mmio_len
= resource_size(res
);
241 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 1);
242 info
->screen_base
= devm_ioremap_resource(dev
, res
);
243 if (IS_ERR(info
->screen_base
)) {
244 ret
= PTR_ERR(info
->screen_base
);
248 /* Physical address should be aligned to 256 MiB */
249 if (res
->start
& 0x0fffffff) {
254 info
->apertures
= alloc_apertures(1);
255 if (!info
->apertures
) {
260 cfb
->buffsize
= resource_size(res
);
261 info
->fix
.smem_start
= res
->start
;
262 info
->apertures
->ranges
[0].base
= info
->fix
.smem_start
;
263 info
->apertures
->ranges
[0].size
= cfb
->buffsize
;
265 cfb
->clk
= devm_clk_get(dev
, NULL
);
266 if (IS_ERR(cfb
->clk
)) {
267 ret
= PTR_ERR(cfb
->clk
);
272 syscon_regmap_lookup_by_compatible("cirrus,ep7209-syscon1");
273 if (IS_ERR(cfb
->syscon
)) {
274 ret
= PTR_ERR(cfb
->syscon
);
278 disp
= of_parse_phandle(np
, "display", 0);
280 dev_err(&pdev
->dev
, "No display defined\n");
285 ret
= of_get_fb_videomode(disp
, &cfb
->mode
, OF_USE_NATIVE_MODE
);
291 of_property_read_u32(disp
, "ac-prescale", &cfb
->ac_prescale
);
292 cfb
->cmap_invert
= of_property_read_bool(disp
, "cmap-invert");
294 ret
= of_property_read_u32(disp
, "bits-per-pixel",
295 &info
->var
.bits_per_pixel
);
300 /* Force disable LCD on any mismatch */
301 if (info
->fix
.smem_start
!= (readb(cfb
->base
+ CLPS711X_FBADDR
) << 28))
302 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
,
305 ret
= regmap_read(cfb
->syscon
, SYSCON_OFFSET
, &val
);
309 if (!(val
& SYSCON1_LCDEN
)) {
310 /* Setup start FB address */
311 writeb(info
->fix
.smem_start
>> 28, cfb
->base
+ CLPS711X_FBADDR
);
312 /* Clean FB memory */
313 memset_io(info
->screen_base
, 0, cfb
->buffsize
);
316 cfb
->lcd_pwr
= devm_regulator_get(dev
, "lcd");
317 if (PTR_ERR(cfb
->lcd_pwr
) == -EPROBE_DEFER
) {
322 info
->fbops
= &clps711x_fb_ops
;
323 info
->flags
= FBINFO_DEFAULT
;
324 info
->var
.activate
= FB_ACTIVATE_FORCE
| FB_ACTIVATE_NOW
;
325 info
->var
.height
= -1;
326 info
->var
.width
= -1;
327 info
->var
.vmode
= FB_VMODE_NONINTERLACED
;
328 info
->fix
.type
= FB_TYPE_PACKED_PIXELS
;
329 info
->fix
.accel
= FB_ACCEL_NONE
;
330 strlcpy(info
->fix
.id
, CLPS711X_FB_NAME
, sizeof(info
->fix
.id
));
331 fb_videomode_to_var(&info
->var
, &cfb
->mode
);
333 ret
= fb_alloc_cmap(&info
->cmap
, BIT(CLPS711X_FB_BPP_MAX
), 0);
337 ret
= fb_set_var(info
, &info
->var
);
339 goto out_fb_dealloc_cmap
;
341 ret
= register_framebuffer(info
);
343 goto out_fb_dealloc_cmap
;
345 lcd
= devm_lcd_device_register(dev
, "clps711x-lcd", dev
, cfb
,
351 unregister_framebuffer(info
);
354 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
355 fb_dealloc_cmap(&info
->cmap
);
358 framebuffer_release(info
);
363 static int clps711x_fb_remove(struct platform_device
*pdev
)
365 struct fb_info
*info
= platform_get_drvdata(pdev
);
366 struct clps711x_fb_info
*cfb
= info
->par
;
368 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
370 unregister_framebuffer(info
);
371 fb_dealloc_cmap(&info
->cmap
);
372 framebuffer_release(info
);
377 static const struct of_device_id clps711x_fb_dt_ids
[] = {
378 { .compatible
= "cirrus,ep7209-fb", },
381 MODULE_DEVICE_TABLE(of
, clps711x_fb_dt_ids
);
383 static struct platform_driver clps711x_fb_driver
= {
385 .name
= CLPS711X_FB_NAME
,
386 .of_match_table
= clps711x_fb_dt_ids
,
388 .probe
= clps711x_fb_probe
,
389 .remove
= clps711x_fb_remove
,
391 module_platform_driver(clps711x_fb_driver
);
393 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
394 MODULE_DESCRIPTION("Cirrus Logic CLPS711X FB driver");
395 MODULE_LICENSE("GPL");