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_DEFAULT_IOMEM_OPS
,
159 .fb_setcolreg
= clps711x_fb_setcolreg
,
160 .fb_check_var
= clps711x_fb_check_var
,
161 .fb_set_par
= clps711x_fb_set_par
,
162 .fb_blank
= clps711x_fb_blank
,
165 static int clps711x_lcd_get_power(struct lcd_device
*lcddev
)
167 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
169 if (!IS_ERR_OR_NULL(cfb
->lcd_pwr
))
170 if (!regulator_is_enabled(cfb
->lcd_pwr
))
171 return LCD_POWER_REDUCED
;
176 static int clps711x_lcd_set_power(struct lcd_device
*lcddev
, int blank
)
178 struct clps711x_fb_info
*cfb
= dev_get_drvdata(&lcddev
->dev
);
180 if (!IS_ERR_OR_NULL(cfb
->lcd_pwr
)) {
181 if (blank
== LCD_POWER_ON
) {
182 if (!regulator_is_enabled(cfb
->lcd_pwr
))
183 return regulator_enable(cfb
->lcd_pwr
);
185 if (regulator_is_enabled(cfb
->lcd_pwr
))
186 return regulator_disable(cfb
->lcd_pwr
);
193 static const struct lcd_ops clps711x_lcd_ops
= {
194 .get_power
= clps711x_lcd_get_power
,
195 .set_power
= clps711x_lcd_set_power
,
198 static int clps711x_fb_probe(struct platform_device
*pdev
)
200 struct device
*dev
= &pdev
->dev
;
201 struct device_node
*disp
, *np
= dev
->of_node
;
202 struct clps711x_fb_info
*cfb
;
203 struct lcd_device
*lcd
;
204 struct fb_info
*info
;
205 struct resource
*res
;
209 if (fb_get_options(CLPS711X_FB_NAME
, NULL
))
212 info
= framebuffer_alloc(sizeof(*cfb
), dev
);
217 platform_set_drvdata(pdev
, info
);
219 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
222 cfb
->base
= devm_ioremap(dev
, res
->start
, resource_size(res
));
228 info
->fix
.mmio_start
= res
->start
;
229 info
->fix
.mmio_len
= resource_size(res
);
231 info
->screen_base
= devm_platform_get_and_ioremap_resource(pdev
, 1, &res
);
232 if (IS_ERR(info
->screen_base
)) {
233 ret
= PTR_ERR(info
->screen_base
);
237 /* Physical address should be aligned to 256 MiB */
238 if (res
->start
& 0x0fffffff) {
243 cfb
->buffsize
= resource_size(res
);
244 info
->fix
.smem_start
= res
->start
;
246 cfb
->clk
= devm_clk_get(dev
, NULL
);
247 if (IS_ERR(cfb
->clk
)) {
248 ret
= PTR_ERR(cfb
->clk
);
252 cfb
->syscon
= syscon_regmap_lookup_by_phandle(np
, "syscon");
253 if (IS_ERR(cfb
->syscon
)) {
254 ret
= PTR_ERR(cfb
->syscon
);
258 disp
= of_parse_phandle(np
, "display", 0);
260 dev_err(&pdev
->dev
, "No display defined\n");
265 ret
= of_get_fb_videomode(disp
, &cfb
->mode
, OF_USE_NATIVE_MODE
);
271 of_property_read_u32(disp
, "ac-prescale", &cfb
->ac_prescale
);
272 cfb
->cmap_invert
= of_property_read_bool(disp
, "cmap-invert");
274 ret
= of_property_read_u32(disp
, "bits-per-pixel",
275 &info
->var
.bits_per_pixel
);
280 /* Force disable LCD on any mismatch */
281 if (info
->fix
.smem_start
!= (readb(cfb
->base
+ CLPS711X_FBADDR
) << 28))
282 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
,
285 ret
= regmap_read(cfb
->syscon
, SYSCON_OFFSET
, &val
);
289 if (!(val
& SYSCON1_LCDEN
)) {
290 /* Setup start FB address */
291 writeb(info
->fix
.smem_start
>> 28, cfb
->base
+ CLPS711X_FBADDR
);
292 /* Clean FB memory */
293 memset_io(info
->screen_base
, 0, cfb
->buffsize
);
296 cfb
->lcd_pwr
= devm_regulator_get(dev
, "lcd");
297 if (PTR_ERR(cfb
->lcd_pwr
) == -EPROBE_DEFER
) {
302 info
->fbops
= &clps711x_fb_ops
;
303 info
->var
.activate
= FB_ACTIVATE_FORCE
| FB_ACTIVATE_NOW
;
304 info
->var
.height
= -1;
305 info
->var
.width
= -1;
306 info
->var
.vmode
= FB_VMODE_NONINTERLACED
;
307 info
->fix
.type
= FB_TYPE_PACKED_PIXELS
;
308 info
->fix
.accel
= FB_ACCEL_NONE
;
309 strscpy(info
->fix
.id
, CLPS711X_FB_NAME
, sizeof(info
->fix
.id
));
310 fb_videomode_to_var(&info
->var
, &cfb
->mode
);
312 ret
= fb_alloc_cmap(&info
->cmap
, BIT(CLPS711X_FB_BPP_MAX
), 0);
316 ret
= fb_set_var(info
, &info
->var
);
318 goto out_fb_dealloc_cmap
;
320 lcd
= devm_lcd_device_register(dev
, "clps711x-lcd", dev
, cfb
,
324 goto out_fb_dealloc_cmap
;
329 ret
= register_framebuffer(info
);
331 goto out_fb_dealloc_cmap
;
335 unregister_framebuffer(info
);
338 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
339 fb_dealloc_cmap(&info
->cmap
);
342 framebuffer_release(info
);
347 static void clps711x_fb_remove(struct platform_device
*pdev
)
349 struct fb_info
*info
= platform_get_drvdata(pdev
);
350 struct clps711x_fb_info
*cfb
= info
->par
;
352 regmap_update_bits(cfb
->syscon
, SYSCON_OFFSET
, SYSCON1_LCDEN
, 0);
354 unregister_framebuffer(info
);
355 fb_dealloc_cmap(&info
->cmap
);
356 framebuffer_release(info
);
359 static const struct of_device_id clps711x_fb_dt_ids
[] = {
360 { .compatible
= "cirrus,ep7209-fb", },
363 MODULE_DEVICE_TABLE(of
, clps711x_fb_dt_ids
);
365 static struct platform_driver clps711x_fb_driver
= {
367 .name
= CLPS711X_FB_NAME
,
368 .of_match_table
= clps711x_fb_dt_ids
,
370 .probe
= clps711x_fb_probe
,
371 .remove
= clps711x_fb_remove
,
373 module_platform_driver(clps711x_fb_driver
);
375 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
376 MODULE_DESCRIPTION("Cirrus Logic CLPS711X FB driver");
377 MODULE_LICENSE("GPL");