1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun * Driver for AT91 LCD Controller
3*4882a593Smuzhiyun *
4*4882a593Smuzhiyun * Copyright (C) 2007 Atmel Corporation
5*4882a593Smuzhiyun *
6*4882a593Smuzhiyun * This file is subject to the terms and conditions of the GNU General Public
7*4882a593Smuzhiyun * License. See the file COPYING in the main directory of this archive for
8*4882a593Smuzhiyun * more details.
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #include <linux/kernel.h>
12*4882a593Smuzhiyun #include <linux/platform_device.h>
13*4882a593Smuzhiyun #include <linux/dma-mapping.h>
14*4882a593Smuzhiyun #include <linux/interrupt.h>
15*4882a593Smuzhiyun #include <linux/clk.h>
16*4882a593Smuzhiyun #include <linux/fb.h>
17*4882a593Smuzhiyun #include <linux/init.h>
18*4882a593Smuzhiyun #include <linux/delay.h>
19*4882a593Smuzhiyun #include <linux/backlight.h>
20*4882a593Smuzhiyun #include <linux/gfp.h>
21*4882a593Smuzhiyun #include <linux/gpio/consumer.h>
22*4882a593Smuzhiyun #include <linux/module.h>
23*4882a593Smuzhiyun #include <linux/of.h>
24*4882a593Smuzhiyun #include <linux/of_device.h>
25*4882a593Smuzhiyun #include <video/of_videomode.h>
26*4882a593Smuzhiyun #include <video/of_display_timing.h>
27*4882a593Smuzhiyun #include <linux/regulator/consumer.h>
28*4882a593Smuzhiyun #include <video/videomode.h>
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun #include <video/atmel_lcdc.h>
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun struct atmel_lcdfb_config {
33*4882a593Smuzhiyun bool have_alt_pixclock;
34*4882a593Smuzhiyun bool have_hozval;
35*4882a593Smuzhiyun bool have_intensity_bit;
36*4882a593Smuzhiyun };
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun /* LCD Controller info data structure, stored in device platform_data */
39*4882a593Smuzhiyun struct atmel_lcdfb_info {
40*4882a593Smuzhiyun spinlock_t lock;
41*4882a593Smuzhiyun struct fb_info *info;
42*4882a593Smuzhiyun void __iomem *mmio;
43*4882a593Smuzhiyun int irq_base;
44*4882a593Smuzhiyun struct work_struct task;
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun unsigned int smem_len;
47*4882a593Smuzhiyun struct platform_device *pdev;
48*4882a593Smuzhiyun struct clk *bus_clk;
49*4882a593Smuzhiyun struct clk *lcdc_clk;
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun struct backlight_device *backlight;
52*4882a593Smuzhiyun u8 bl_power;
53*4882a593Smuzhiyun u8 saved_lcdcon;
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun u32 pseudo_palette[16];
56*4882a593Smuzhiyun bool have_intensity_bit;
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun struct atmel_lcdfb_pdata pdata;
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun struct atmel_lcdfb_config *config;
61*4882a593Smuzhiyun struct regulator *reg_lcd;
62*4882a593Smuzhiyun };
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun struct atmel_lcdfb_power_ctrl_gpio {
65*4882a593Smuzhiyun struct gpio_desc *gpiod;
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun struct list_head list;
68*4882a593Smuzhiyun };
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun #define lcdc_readl(sinfo, reg) __raw_readl((sinfo)->mmio+(reg))
71*4882a593Smuzhiyun #define lcdc_writel(sinfo, reg, val) __raw_writel((val), (sinfo)->mmio+(reg))
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun /* configurable parameters */
74*4882a593Smuzhiyun #define ATMEL_LCDC_CVAL_DEFAULT 0xc8
75*4882a593Smuzhiyun #define ATMEL_LCDC_DMA_BURST_LEN 8 /* words */
76*4882a593Smuzhiyun #define ATMEL_LCDC_FIFO_SIZE 512 /* words */
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun static struct atmel_lcdfb_config at91sam9261_config = {
79*4882a593Smuzhiyun .have_hozval = true,
80*4882a593Smuzhiyun .have_intensity_bit = true,
81*4882a593Smuzhiyun };
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun static struct atmel_lcdfb_config at91sam9263_config = {
84*4882a593Smuzhiyun .have_intensity_bit = true,
85*4882a593Smuzhiyun };
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun static struct atmel_lcdfb_config at91sam9g10_config = {
88*4882a593Smuzhiyun .have_hozval = true,
89*4882a593Smuzhiyun };
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun static struct atmel_lcdfb_config at91sam9g45_config = {
92*4882a593Smuzhiyun .have_alt_pixclock = true,
93*4882a593Smuzhiyun };
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun static struct atmel_lcdfb_config at91sam9g45es_config = {
96*4882a593Smuzhiyun };
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun static struct atmel_lcdfb_config at91sam9rl_config = {
99*4882a593Smuzhiyun .have_intensity_bit = true,
100*4882a593Smuzhiyun };
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun static u32 contrast_ctr = ATMEL_LCDC_PS_DIV8
103*4882a593Smuzhiyun | ATMEL_LCDC_POL_POSITIVE
104*4882a593Smuzhiyun | ATMEL_LCDC_ENA_PWMENABLE;
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun #ifdef CONFIG_BACKLIGHT_ATMEL_LCDC
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun /* some bl->props field just changed */
atmel_bl_update_status(struct backlight_device * bl)109*4882a593Smuzhiyun static int atmel_bl_update_status(struct backlight_device *bl)
110*4882a593Smuzhiyun {
111*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = bl_get_data(bl);
112*4882a593Smuzhiyun int power = sinfo->bl_power;
113*4882a593Smuzhiyun int brightness = bl->props.brightness;
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun /* REVISIT there may be a meaningful difference between
116*4882a593Smuzhiyun * fb_blank and power ... there seem to be some cases
117*4882a593Smuzhiyun * this doesn't handle correctly.
118*4882a593Smuzhiyun */
119*4882a593Smuzhiyun if (bl->props.fb_blank != sinfo->bl_power)
120*4882a593Smuzhiyun power = bl->props.fb_blank;
121*4882a593Smuzhiyun else if (bl->props.power != sinfo->bl_power)
122*4882a593Smuzhiyun power = bl->props.power;
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun if (brightness < 0 && power == FB_BLANK_UNBLANK)
125*4882a593Smuzhiyun brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL);
126*4882a593Smuzhiyun else if (power != FB_BLANK_UNBLANK)
127*4882a593Smuzhiyun brightness = 0;
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness);
130*4882a593Smuzhiyun if (contrast_ctr & ATMEL_LCDC_POL_POSITIVE)
131*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR,
132*4882a593Smuzhiyun brightness ? contrast_ctr : 0);
133*4882a593Smuzhiyun else
134*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr);
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun bl->props.fb_blank = bl->props.power = sinfo->bl_power = power;
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun return 0;
139*4882a593Smuzhiyun }
140*4882a593Smuzhiyun
atmel_bl_get_brightness(struct backlight_device * bl)141*4882a593Smuzhiyun static int atmel_bl_get_brightness(struct backlight_device *bl)
142*4882a593Smuzhiyun {
143*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = bl_get_data(bl);
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL);
146*4882a593Smuzhiyun }
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun static const struct backlight_ops atmel_lcdc_bl_ops = {
149*4882a593Smuzhiyun .update_status = atmel_bl_update_status,
150*4882a593Smuzhiyun .get_brightness = atmel_bl_get_brightness,
151*4882a593Smuzhiyun };
152*4882a593Smuzhiyun
init_backlight(struct atmel_lcdfb_info * sinfo)153*4882a593Smuzhiyun static void init_backlight(struct atmel_lcdfb_info *sinfo)
154*4882a593Smuzhiyun {
155*4882a593Smuzhiyun struct backlight_properties props;
156*4882a593Smuzhiyun struct backlight_device *bl;
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun sinfo->bl_power = FB_BLANK_UNBLANK;
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun if (sinfo->backlight)
161*4882a593Smuzhiyun return;
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun memset(&props, 0, sizeof(struct backlight_properties));
164*4882a593Smuzhiyun props.type = BACKLIGHT_RAW;
165*4882a593Smuzhiyun props.max_brightness = 0xff;
166*4882a593Smuzhiyun bl = backlight_device_register("backlight", &sinfo->pdev->dev, sinfo,
167*4882a593Smuzhiyun &atmel_lcdc_bl_ops, &props);
168*4882a593Smuzhiyun if (IS_ERR(bl)) {
169*4882a593Smuzhiyun dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n",
170*4882a593Smuzhiyun PTR_ERR(bl));
171*4882a593Smuzhiyun return;
172*4882a593Smuzhiyun }
173*4882a593Smuzhiyun sinfo->backlight = bl;
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun bl->props.power = FB_BLANK_UNBLANK;
176*4882a593Smuzhiyun bl->props.fb_blank = FB_BLANK_UNBLANK;
177*4882a593Smuzhiyun bl->props.brightness = atmel_bl_get_brightness(bl);
178*4882a593Smuzhiyun }
179*4882a593Smuzhiyun
exit_backlight(struct atmel_lcdfb_info * sinfo)180*4882a593Smuzhiyun static void exit_backlight(struct atmel_lcdfb_info *sinfo)
181*4882a593Smuzhiyun {
182*4882a593Smuzhiyun if (!sinfo->backlight)
183*4882a593Smuzhiyun return;
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun if (sinfo->backlight->ops) {
186*4882a593Smuzhiyun sinfo->backlight->props.power = FB_BLANK_POWERDOWN;
187*4882a593Smuzhiyun sinfo->backlight->ops->update_status(sinfo->backlight);
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun backlight_device_unregister(sinfo->backlight);
190*4882a593Smuzhiyun }
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun #else
193*4882a593Smuzhiyun
init_backlight(struct atmel_lcdfb_info * sinfo)194*4882a593Smuzhiyun static void init_backlight(struct atmel_lcdfb_info *sinfo)
195*4882a593Smuzhiyun {
196*4882a593Smuzhiyun dev_warn(&sinfo->pdev->dev, "backlight control is not available\n");
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun
exit_backlight(struct atmel_lcdfb_info * sinfo)199*4882a593Smuzhiyun static void exit_backlight(struct atmel_lcdfb_info *sinfo)
200*4882a593Smuzhiyun {
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun #endif
204*4882a593Smuzhiyun
init_contrast(struct atmel_lcdfb_info * sinfo)205*4882a593Smuzhiyun static void init_contrast(struct atmel_lcdfb_info *sinfo)
206*4882a593Smuzhiyun {
207*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun /* contrast pwm can be 'inverted' */
210*4882a593Smuzhiyun if (pdata->lcdcon_pol_negative)
211*4882a593Smuzhiyun contrast_ctr &= ~(ATMEL_LCDC_POL_POSITIVE);
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun /* have some default contrast/backlight settings */
214*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr);
215*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT);
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun if (pdata->lcdcon_is_backlight)
218*4882a593Smuzhiyun init_backlight(sinfo);
219*4882a593Smuzhiyun }
220*4882a593Smuzhiyun
atmel_lcdfb_power_control(struct atmel_lcdfb_info * sinfo,int on)221*4882a593Smuzhiyun static inline void atmel_lcdfb_power_control(struct atmel_lcdfb_info *sinfo, int on)
222*4882a593Smuzhiyun {
223*4882a593Smuzhiyun int ret;
224*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun if (pdata->atmel_lcdfb_power_control)
227*4882a593Smuzhiyun pdata->atmel_lcdfb_power_control(pdata, on);
228*4882a593Smuzhiyun else if (sinfo->reg_lcd) {
229*4882a593Smuzhiyun if (on) {
230*4882a593Smuzhiyun ret = regulator_enable(sinfo->reg_lcd);
231*4882a593Smuzhiyun if (ret)
232*4882a593Smuzhiyun dev_err(&sinfo->pdev->dev,
233*4882a593Smuzhiyun "lcd regulator enable failed: %d\n", ret);
234*4882a593Smuzhiyun } else {
235*4882a593Smuzhiyun ret = regulator_disable(sinfo->reg_lcd);
236*4882a593Smuzhiyun if (ret)
237*4882a593Smuzhiyun dev_err(&sinfo->pdev->dev,
238*4882a593Smuzhiyun "lcd regulator disable failed: %d\n", ret);
239*4882a593Smuzhiyun }
240*4882a593Smuzhiyun }
241*4882a593Smuzhiyun }
242*4882a593Smuzhiyun
243*4882a593Smuzhiyun static const struct fb_fix_screeninfo atmel_lcdfb_fix __initconst = {
244*4882a593Smuzhiyun .type = FB_TYPE_PACKED_PIXELS,
245*4882a593Smuzhiyun .visual = FB_VISUAL_TRUECOLOR,
246*4882a593Smuzhiyun .xpanstep = 0,
247*4882a593Smuzhiyun .ypanstep = 1,
248*4882a593Smuzhiyun .ywrapstep = 0,
249*4882a593Smuzhiyun .accel = FB_ACCEL_NONE,
250*4882a593Smuzhiyun };
251*4882a593Smuzhiyun
compute_hozval(struct atmel_lcdfb_info * sinfo,unsigned long xres)252*4882a593Smuzhiyun static unsigned long compute_hozval(struct atmel_lcdfb_info *sinfo,
253*4882a593Smuzhiyun unsigned long xres)
254*4882a593Smuzhiyun {
255*4882a593Smuzhiyun unsigned long lcdcon2;
256*4882a593Smuzhiyun unsigned long value;
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun if (!sinfo->config->have_hozval)
259*4882a593Smuzhiyun return xres;
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun lcdcon2 = lcdc_readl(sinfo, ATMEL_LCDC_LCDCON2);
262*4882a593Smuzhiyun value = xres;
263*4882a593Smuzhiyun if ((lcdcon2 & ATMEL_LCDC_DISTYPE) != ATMEL_LCDC_DISTYPE_TFT) {
264*4882a593Smuzhiyun /* STN display */
265*4882a593Smuzhiyun if ((lcdcon2 & ATMEL_LCDC_DISTYPE) == ATMEL_LCDC_DISTYPE_STNCOLOR) {
266*4882a593Smuzhiyun value *= 3;
267*4882a593Smuzhiyun }
268*4882a593Smuzhiyun if ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_4
269*4882a593Smuzhiyun || ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_8
270*4882a593Smuzhiyun && (lcdcon2 & ATMEL_LCDC_SCANMOD) == ATMEL_LCDC_SCANMOD_DUAL ))
271*4882a593Smuzhiyun value = DIV_ROUND_UP(value, 4);
272*4882a593Smuzhiyun else
273*4882a593Smuzhiyun value = DIV_ROUND_UP(value, 8);
274*4882a593Smuzhiyun }
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun return value;
277*4882a593Smuzhiyun }
278*4882a593Smuzhiyun
atmel_lcdfb_stop_nowait(struct atmel_lcdfb_info * sinfo)279*4882a593Smuzhiyun static void atmel_lcdfb_stop_nowait(struct atmel_lcdfb_info *sinfo)
280*4882a593Smuzhiyun {
281*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun /* Turn off the LCD controller and the DMA controller */
284*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_PWRCON,
285*4882a593Smuzhiyun pdata->guard_time << ATMEL_LCDC_GUARDT_OFFSET);
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun /* Wait for the LCDC core to become idle */
288*4882a593Smuzhiyun while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY)
289*4882a593Smuzhiyun msleep(10);
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0);
292*4882a593Smuzhiyun }
293*4882a593Smuzhiyun
atmel_lcdfb_stop(struct atmel_lcdfb_info * sinfo)294*4882a593Smuzhiyun static void atmel_lcdfb_stop(struct atmel_lcdfb_info *sinfo)
295*4882a593Smuzhiyun {
296*4882a593Smuzhiyun atmel_lcdfb_stop_nowait(sinfo);
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun /* Wait for DMA engine to become idle... */
299*4882a593Smuzhiyun while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY)
300*4882a593Smuzhiyun msleep(10);
301*4882a593Smuzhiyun }
302*4882a593Smuzhiyun
atmel_lcdfb_start(struct atmel_lcdfb_info * sinfo)303*4882a593Smuzhiyun static void atmel_lcdfb_start(struct atmel_lcdfb_info *sinfo)
304*4882a593Smuzhiyun {
305*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_DMACON, pdata->default_dmacon);
308*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_PWRCON,
309*4882a593Smuzhiyun (pdata->guard_time << ATMEL_LCDC_GUARDT_OFFSET)
310*4882a593Smuzhiyun | ATMEL_LCDC_PWR);
311*4882a593Smuzhiyun }
312*4882a593Smuzhiyun
atmel_lcdfb_update_dma(struct fb_info * info,struct fb_var_screeninfo * var)313*4882a593Smuzhiyun static void atmel_lcdfb_update_dma(struct fb_info *info,
314*4882a593Smuzhiyun struct fb_var_screeninfo *var)
315*4882a593Smuzhiyun {
316*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
317*4882a593Smuzhiyun struct fb_fix_screeninfo *fix = &info->fix;
318*4882a593Smuzhiyun unsigned long dma_addr;
319*4882a593Smuzhiyun
320*4882a593Smuzhiyun dma_addr = (fix->smem_start + var->yoffset * fix->line_length
321*4882a593Smuzhiyun + var->xoffset * info->var.bits_per_pixel / 8);
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun dma_addr &= ~3UL;
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun /* Set framebuffer DMA base address and pixel offset */
326*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr);
327*4882a593Smuzhiyun }
328*4882a593Smuzhiyun
atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info * sinfo)329*4882a593Smuzhiyun static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo)
330*4882a593Smuzhiyun {
331*4882a593Smuzhiyun struct fb_info *info = sinfo->info;
332*4882a593Smuzhiyun
333*4882a593Smuzhiyun dma_free_wc(info->device, info->fix.smem_len, info->screen_base,
334*4882a593Smuzhiyun info->fix.smem_start);
335*4882a593Smuzhiyun }
336*4882a593Smuzhiyun
337*4882a593Smuzhiyun /**
338*4882a593Smuzhiyun * atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory
339*4882a593Smuzhiyun * @sinfo: the frame buffer to allocate memory for
340*4882a593Smuzhiyun *
341*4882a593Smuzhiyun * This function is called only from the atmel_lcdfb_probe()
342*4882a593Smuzhiyun * so no locking by fb_info->mm_lock around smem_len setting is needed.
343*4882a593Smuzhiyun */
atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info * sinfo)344*4882a593Smuzhiyun static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo)
345*4882a593Smuzhiyun {
346*4882a593Smuzhiyun struct fb_info *info = sinfo->info;
347*4882a593Smuzhiyun struct fb_var_screeninfo *var = &info->var;
348*4882a593Smuzhiyun unsigned int smem_len;
349*4882a593Smuzhiyun
350*4882a593Smuzhiyun smem_len = (var->xres_virtual * var->yres_virtual
351*4882a593Smuzhiyun * ((var->bits_per_pixel + 7) / 8));
352*4882a593Smuzhiyun info->fix.smem_len = max(smem_len, sinfo->smem_len);
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun info->screen_base = dma_alloc_wc(info->device, info->fix.smem_len,
355*4882a593Smuzhiyun (dma_addr_t *)&info->fix.smem_start,
356*4882a593Smuzhiyun GFP_KERNEL);
357*4882a593Smuzhiyun
358*4882a593Smuzhiyun if (!info->screen_base) {
359*4882a593Smuzhiyun return -ENOMEM;
360*4882a593Smuzhiyun }
361*4882a593Smuzhiyun
362*4882a593Smuzhiyun memset(info->screen_base, 0, info->fix.smem_len);
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun return 0;
365*4882a593Smuzhiyun }
366*4882a593Smuzhiyun
atmel_lcdfb_choose_mode(struct fb_var_screeninfo * var,struct fb_info * info)367*4882a593Smuzhiyun static const struct fb_videomode *atmel_lcdfb_choose_mode(struct fb_var_screeninfo *var,
368*4882a593Smuzhiyun struct fb_info *info)
369*4882a593Smuzhiyun {
370*4882a593Smuzhiyun struct fb_videomode varfbmode;
371*4882a593Smuzhiyun const struct fb_videomode *fbmode = NULL;
372*4882a593Smuzhiyun
373*4882a593Smuzhiyun fb_var_to_videomode(&varfbmode, var);
374*4882a593Smuzhiyun fbmode = fb_find_nearest_mode(&varfbmode, &info->modelist);
375*4882a593Smuzhiyun if (fbmode)
376*4882a593Smuzhiyun fb_videomode_to_var(var, fbmode);
377*4882a593Smuzhiyun return fbmode;
378*4882a593Smuzhiyun }
379*4882a593Smuzhiyun
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun /**
382*4882a593Smuzhiyun * atmel_lcdfb_check_var - Validates a var passed in.
383*4882a593Smuzhiyun * @var: frame buffer variable screen structure
384*4882a593Smuzhiyun * @info: frame buffer structure that represents a single frame buffer
385*4882a593Smuzhiyun *
386*4882a593Smuzhiyun * Checks to see if the hardware supports the state requested by
387*4882a593Smuzhiyun * var passed in. This function does not alter the hardware
388*4882a593Smuzhiyun * state!!! This means the data stored in struct fb_info and
389*4882a593Smuzhiyun * struct atmel_lcdfb_info do not change. This includes the var
390*4882a593Smuzhiyun * inside of struct fb_info. Do NOT change these. This function
391*4882a593Smuzhiyun * can be called on its own if we intent to only test a mode and
392*4882a593Smuzhiyun * not actually set it. The stuff in modedb.c is a example of
393*4882a593Smuzhiyun * this. If the var passed in is slightly off by what the
394*4882a593Smuzhiyun * hardware can support then we alter the var PASSED in to what
395*4882a593Smuzhiyun * we can do. If the hardware doesn't support mode change a
396*4882a593Smuzhiyun * -EINVAL will be returned by the upper layers. You don't need
397*4882a593Smuzhiyun * to implement this function then. If you hardware doesn't
398*4882a593Smuzhiyun * support changing the resolution then this function is not
399*4882a593Smuzhiyun * needed. In this case the driver would just provide a var that
400*4882a593Smuzhiyun * represents the static state the screen is in.
401*4882a593Smuzhiyun *
402*4882a593Smuzhiyun * Returns negative errno on error, or zero on success.
403*4882a593Smuzhiyun */
atmel_lcdfb_check_var(struct fb_var_screeninfo * var,struct fb_info * info)404*4882a593Smuzhiyun static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var,
405*4882a593Smuzhiyun struct fb_info *info)
406*4882a593Smuzhiyun {
407*4882a593Smuzhiyun struct device *dev = info->device;
408*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
409*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
410*4882a593Smuzhiyun unsigned long clk_value_khz;
411*4882a593Smuzhiyun
412*4882a593Smuzhiyun clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000;
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun dev_dbg(dev, "%s:\n", __func__);
415*4882a593Smuzhiyun
416*4882a593Smuzhiyun if (!(var->pixclock && var->bits_per_pixel)) {
417*4882a593Smuzhiyun /* choose a suitable mode if possible */
418*4882a593Smuzhiyun if (!atmel_lcdfb_choose_mode(var, info)) {
419*4882a593Smuzhiyun dev_err(dev, "needed value not specified\n");
420*4882a593Smuzhiyun return -EINVAL;
421*4882a593Smuzhiyun }
422*4882a593Smuzhiyun }
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun dev_dbg(dev, " resolution: %ux%u\n", var->xres, var->yres);
425*4882a593Smuzhiyun dev_dbg(dev, " pixclk: %lu KHz\n", PICOS2KHZ(var->pixclock));
426*4882a593Smuzhiyun dev_dbg(dev, " bpp: %u\n", var->bits_per_pixel);
427*4882a593Smuzhiyun dev_dbg(dev, " clk: %lu KHz\n", clk_value_khz);
428*4882a593Smuzhiyun
429*4882a593Smuzhiyun if (PICOS2KHZ(var->pixclock) > clk_value_khz) {
430*4882a593Smuzhiyun dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock));
431*4882a593Smuzhiyun return -EINVAL;
432*4882a593Smuzhiyun }
433*4882a593Smuzhiyun
434*4882a593Smuzhiyun /* Do not allow to have real resoulution larger than virtual */
435*4882a593Smuzhiyun if (var->xres > var->xres_virtual)
436*4882a593Smuzhiyun var->xres_virtual = var->xres;
437*4882a593Smuzhiyun
438*4882a593Smuzhiyun if (var->yres > var->yres_virtual)
439*4882a593Smuzhiyun var->yres_virtual = var->yres;
440*4882a593Smuzhiyun
441*4882a593Smuzhiyun /* Force same alignment for each line */
442*4882a593Smuzhiyun var->xres = (var->xres + 3) & ~3UL;
443*4882a593Smuzhiyun var->xres_virtual = (var->xres_virtual + 3) & ~3UL;
444*4882a593Smuzhiyun
445*4882a593Smuzhiyun var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0;
446*4882a593Smuzhiyun var->transp.msb_right = 0;
447*4882a593Smuzhiyun var->transp.offset = var->transp.length = 0;
448*4882a593Smuzhiyun var->xoffset = var->yoffset = 0;
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun if (info->fix.smem_len) {
451*4882a593Smuzhiyun unsigned int smem_len = (var->xres_virtual * var->yres_virtual
452*4882a593Smuzhiyun * ((var->bits_per_pixel + 7) / 8));
453*4882a593Smuzhiyun if (smem_len > info->fix.smem_len) {
454*4882a593Smuzhiyun dev_err(dev, "Frame buffer is too small (%u) for screen size (need at least %u)\n",
455*4882a593Smuzhiyun info->fix.smem_len, smem_len);
456*4882a593Smuzhiyun return -EINVAL;
457*4882a593Smuzhiyun }
458*4882a593Smuzhiyun }
459*4882a593Smuzhiyun
460*4882a593Smuzhiyun /* Saturate vertical and horizontal timings at maximum values */
461*4882a593Smuzhiyun var->vsync_len = min_t(u32, var->vsync_len,
462*4882a593Smuzhiyun (ATMEL_LCDC_VPW >> ATMEL_LCDC_VPW_OFFSET) + 1);
463*4882a593Smuzhiyun var->upper_margin = min_t(u32, var->upper_margin,
464*4882a593Smuzhiyun ATMEL_LCDC_VBP >> ATMEL_LCDC_VBP_OFFSET);
465*4882a593Smuzhiyun var->lower_margin = min_t(u32, var->lower_margin,
466*4882a593Smuzhiyun ATMEL_LCDC_VFP);
467*4882a593Smuzhiyun var->right_margin = min_t(u32, var->right_margin,
468*4882a593Smuzhiyun (ATMEL_LCDC_HFP >> ATMEL_LCDC_HFP_OFFSET) + 1);
469*4882a593Smuzhiyun var->hsync_len = min_t(u32, var->hsync_len,
470*4882a593Smuzhiyun (ATMEL_LCDC_HPW >> ATMEL_LCDC_HPW_OFFSET) + 1);
471*4882a593Smuzhiyun var->left_margin = min_t(u32, var->left_margin,
472*4882a593Smuzhiyun ATMEL_LCDC_HBP + 1);
473*4882a593Smuzhiyun
474*4882a593Smuzhiyun /* Some parameters can't be zero */
475*4882a593Smuzhiyun var->vsync_len = max_t(u32, var->vsync_len, 1);
476*4882a593Smuzhiyun var->right_margin = max_t(u32, var->right_margin, 1);
477*4882a593Smuzhiyun var->hsync_len = max_t(u32, var->hsync_len, 1);
478*4882a593Smuzhiyun var->left_margin = max_t(u32, var->left_margin, 1);
479*4882a593Smuzhiyun
480*4882a593Smuzhiyun switch (var->bits_per_pixel) {
481*4882a593Smuzhiyun case 1:
482*4882a593Smuzhiyun case 2:
483*4882a593Smuzhiyun case 4:
484*4882a593Smuzhiyun case 8:
485*4882a593Smuzhiyun var->red.offset = var->green.offset = var->blue.offset = 0;
486*4882a593Smuzhiyun var->red.length = var->green.length = var->blue.length
487*4882a593Smuzhiyun = var->bits_per_pixel;
488*4882a593Smuzhiyun break;
489*4882a593Smuzhiyun case 16:
490*4882a593Smuzhiyun /* Older SOCs use IBGR:555 rather than BGR:565. */
491*4882a593Smuzhiyun if (sinfo->config->have_intensity_bit)
492*4882a593Smuzhiyun var->green.length = 5;
493*4882a593Smuzhiyun else
494*4882a593Smuzhiyun var->green.length = 6;
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) {
497*4882a593Smuzhiyun /* RGB:5X5 mode */
498*4882a593Smuzhiyun var->red.offset = var->green.length + 5;
499*4882a593Smuzhiyun var->blue.offset = 0;
500*4882a593Smuzhiyun } else {
501*4882a593Smuzhiyun /* BGR:5X5 mode */
502*4882a593Smuzhiyun var->red.offset = 0;
503*4882a593Smuzhiyun var->blue.offset = var->green.length + 5;
504*4882a593Smuzhiyun }
505*4882a593Smuzhiyun var->green.offset = 5;
506*4882a593Smuzhiyun var->red.length = var->blue.length = 5;
507*4882a593Smuzhiyun break;
508*4882a593Smuzhiyun case 32:
509*4882a593Smuzhiyun var->transp.offset = 24;
510*4882a593Smuzhiyun var->transp.length = 8;
511*4882a593Smuzhiyun fallthrough;
512*4882a593Smuzhiyun case 24:
513*4882a593Smuzhiyun if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) {
514*4882a593Smuzhiyun /* RGB:888 mode */
515*4882a593Smuzhiyun var->red.offset = 16;
516*4882a593Smuzhiyun var->blue.offset = 0;
517*4882a593Smuzhiyun } else {
518*4882a593Smuzhiyun /* BGR:888 mode */
519*4882a593Smuzhiyun var->red.offset = 0;
520*4882a593Smuzhiyun var->blue.offset = 16;
521*4882a593Smuzhiyun }
522*4882a593Smuzhiyun var->green.offset = 8;
523*4882a593Smuzhiyun var->red.length = var->green.length = var->blue.length = 8;
524*4882a593Smuzhiyun break;
525*4882a593Smuzhiyun default:
526*4882a593Smuzhiyun dev_err(dev, "color depth %d not supported\n",
527*4882a593Smuzhiyun var->bits_per_pixel);
528*4882a593Smuzhiyun return -EINVAL;
529*4882a593Smuzhiyun }
530*4882a593Smuzhiyun
531*4882a593Smuzhiyun return 0;
532*4882a593Smuzhiyun }
533*4882a593Smuzhiyun
534*4882a593Smuzhiyun /*
535*4882a593Smuzhiyun * LCD reset sequence
536*4882a593Smuzhiyun */
atmel_lcdfb_reset(struct atmel_lcdfb_info * sinfo)537*4882a593Smuzhiyun static void atmel_lcdfb_reset(struct atmel_lcdfb_info *sinfo)
538*4882a593Smuzhiyun {
539*4882a593Smuzhiyun might_sleep();
540*4882a593Smuzhiyun
541*4882a593Smuzhiyun atmel_lcdfb_stop(sinfo);
542*4882a593Smuzhiyun atmel_lcdfb_start(sinfo);
543*4882a593Smuzhiyun }
544*4882a593Smuzhiyun
545*4882a593Smuzhiyun /**
546*4882a593Smuzhiyun * atmel_lcdfb_set_par - Alters the hardware state.
547*4882a593Smuzhiyun * @info: frame buffer structure that represents a single frame buffer
548*4882a593Smuzhiyun *
549*4882a593Smuzhiyun * Using the fb_var_screeninfo in fb_info we set the resolution
550*4882a593Smuzhiyun * of the this particular framebuffer. This function alters the
551*4882a593Smuzhiyun * par AND the fb_fix_screeninfo stored in fb_info. It doesn't
552*4882a593Smuzhiyun * not alter var in fb_info since we are using that data. This
553*4882a593Smuzhiyun * means we depend on the data in var inside fb_info to be
554*4882a593Smuzhiyun * supported by the hardware. atmel_lcdfb_check_var is always called
555*4882a593Smuzhiyun * before atmel_lcdfb_set_par to ensure this. Again if you can't
556*4882a593Smuzhiyun * change the resolution you don't need this function.
557*4882a593Smuzhiyun *
558*4882a593Smuzhiyun */
atmel_lcdfb_set_par(struct fb_info * info)559*4882a593Smuzhiyun static int atmel_lcdfb_set_par(struct fb_info *info)
560*4882a593Smuzhiyun {
561*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
562*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
563*4882a593Smuzhiyun unsigned long hozval_linesz;
564*4882a593Smuzhiyun unsigned long value;
565*4882a593Smuzhiyun unsigned long clk_value_khz;
566*4882a593Smuzhiyun unsigned long bits_per_line;
567*4882a593Smuzhiyun unsigned long pix_factor = 2;
568*4882a593Smuzhiyun
569*4882a593Smuzhiyun might_sleep();
570*4882a593Smuzhiyun
571*4882a593Smuzhiyun dev_dbg(info->device, "%s:\n", __func__);
572*4882a593Smuzhiyun dev_dbg(info->device, " * resolution: %ux%u (%ux%u virtual)\n",
573*4882a593Smuzhiyun info->var.xres, info->var.yres,
574*4882a593Smuzhiyun info->var.xres_virtual, info->var.yres_virtual);
575*4882a593Smuzhiyun
576*4882a593Smuzhiyun atmel_lcdfb_stop_nowait(sinfo);
577*4882a593Smuzhiyun
578*4882a593Smuzhiyun if (info->var.bits_per_pixel == 1)
579*4882a593Smuzhiyun info->fix.visual = FB_VISUAL_MONO01;
580*4882a593Smuzhiyun else if (info->var.bits_per_pixel <= 8)
581*4882a593Smuzhiyun info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
582*4882a593Smuzhiyun else
583*4882a593Smuzhiyun info->fix.visual = FB_VISUAL_TRUECOLOR;
584*4882a593Smuzhiyun
585*4882a593Smuzhiyun bits_per_line = info->var.xres_virtual * info->var.bits_per_pixel;
586*4882a593Smuzhiyun info->fix.line_length = DIV_ROUND_UP(bits_per_line, 8);
587*4882a593Smuzhiyun
588*4882a593Smuzhiyun /* Re-initialize the DMA engine... */
589*4882a593Smuzhiyun dev_dbg(info->device, " * update DMA engine\n");
590*4882a593Smuzhiyun atmel_lcdfb_update_dma(info, &info->var);
591*4882a593Smuzhiyun
592*4882a593Smuzhiyun /* ...set frame size and burst length = 8 words (?) */
593*4882a593Smuzhiyun value = (info->var.yres * info->var.xres * info->var.bits_per_pixel) / 32;
594*4882a593Smuzhiyun value |= ((ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET);
595*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_DMAFRMCFG, value);
596*4882a593Smuzhiyun
597*4882a593Smuzhiyun /* Now, the LCDC core... */
598*4882a593Smuzhiyun
599*4882a593Smuzhiyun /* Set pixel clock */
600*4882a593Smuzhiyun if (sinfo->config->have_alt_pixclock)
601*4882a593Smuzhiyun pix_factor = 1;
602*4882a593Smuzhiyun
603*4882a593Smuzhiyun clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000;
604*4882a593Smuzhiyun
605*4882a593Smuzhiyun value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock));
606*4882a593Smuzhiyun
607*4882a593Smuzhiyun if (value < pix_factor) {
608*4882a593Smuzhiyun dev_notice(info->device, "Bypassing pixel clock divider\n");
609*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS);
610*4882a593Smuzhiyun } else {
611*4882a593Smuzhiyun value = (value / pix_factor) - 1;
612*4882a593Smuzhiyun dev_dbg(info->device, " * programming CLKVAL = 0x%08lx\n",
613*4882a593Smuzhiyun value);
614*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1,
615*4882a593Smuzhiyun value << ATMEL_LCDC_CLKVAL_OFFSET);
616*4882a593Smuzhiyun info->var.pixclock =
617*4882a593Smuzhiyun KHZ2PICOS(clk_value_khz / (pix_factor * (value + 1)));
618*4882a593Smuzhiyun dev_dbg(info->device, " updated pixclk: %lu KHz\n",
619*4882a593Smuzhiyun PICOS2KHZ(info->var.pixclock));
620*4882a593Smuzhiyun }
621*4882a593Smuzhiyun
622*4882a593Smuzhiyun
623*4882a593Smuzhiyun /* Initialize control register 2 */
624*4882a593Smuzhiyun value = pdata->default_lcdcon2;
625*4882a593Smuzhiyun
626*4882a593Smuzhiyun if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
627*4882a593Smuzhiyun value |= ATMEL_LCDC_INVLINE_INVERTED;
628*4882a593Smuzhiyun if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
629*4882a593Smuzhiyun value |= ATMEL_LCDC_INVFRAME_INVERTED;
630*4882a593Smuzhiyun
631*4882a593Smuzhiyun switch (info->var.bits_per_pixel) {
632*4882a593Smuzhiyun case 1: value |= ATMEL_LCDC_PIXELSIZE_1; break;
633*4882a593Smuzhiyun case 2: value |= ATMEL_LCDC_PIXELSIZE_2; break;
634*4882a593Smuzhiyun case 4: value |= ATMEL_LCDC_PIXELSIZE_4; break;
635*4882a593Smuzhiyun case 8: value |= ATMEL_LCDC_PIXELSIZE_8; break;
636*4882a593Smuzhiyun case 15: fallthrough;
637*4882a593Smuzhiyun case 16: value |= ATMEL_LCDC_PIXELSIZE_16; break;
638*4882a593Smuzhiyun case 24: value |= ATMEL_LCDC_PIXELSIZE_24; break;
639*4882a593Smuzhiyun case 32: value |= ATMEL_LCDC_PIXELSIZE_32; break;
640*4882a593Smuzhiyun default: BUG(); break;
641*4882a593Smuzhiyun }
642*4882a593Smuzhiyun dev_dbg(info->device, " * LCDCON2 = %08lx\n", value);
643*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_LCDCON2, value);
644*4882a593Smuzhiyun
645*4882a593Smuzhiyun /* Vertical timing */
646*4882a593Smuzhiyun value = (info->var.vsync_len - 1) << ATMEL_LCDC_VPW_OFFSET;
647*4882a593Smuzhiyun value |= info->var.upper_margin << ATMEL_LCDC_VBP_OFFSET;
648*4882a593Smuzhiyun value |= info->var.lower_margin;
649*4882a593Smuzhiyun dev_dbg(info->device, " * LCDTIM1 = %08lx\n", value);
650*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_TIM1, value);
651*4882a593Smuzhiyun
652*4882a593Smuzhiyun /* Horizontal timing */
653*4882a593Smuzhiyun value = (info->var.right_margin - 1) << ATMEL_LCDC_HFP_OFFSET;
654*4882a593Smuzhiyun value |= (info->var.hsync_len - 1) << ATMEL_LCDC_HPW_OFFSET;
655*4882a593Smuzhiyun value |= (info->var.left_margin - 1);
656*4882a593Smuzhiyun dev_dbg(info->device, " * LCDTIM2 = %08lx\n", value);
657*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_TIM2, value);
658*4882a593Smuzhiyun
659*4882a593Smuzhiyun /* Horizontal value (aka line size) */
660*4882a593Smuzhiyun hozval_linesz = compute_hozval(sinfo, info->var.xres);
661*4882a593Smuzhiyun
662*4882a593Smuzhiyun /* Display size */
663*4882a593Smuzhiyun value = (hozval_linesz - 1) << ATMEL_LCDC_HOZVAL_OFFSET;
664*4882a593Smuzhiyun value |= info->var.yres - 1;
665*4882a593Smuzhiyun dev_dbg(info->device, " * LCDFRMCFG = %08lx\n", value);
666*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_LCDFRMCFG, value);
667*4882a593Smuzhiyun
668*4882a593Smuzhiyun /* FIFO Threshold: Use formula from data sheet */
669*4882a593Smuzhiyun value = ATMEL_LCDC_FIFO_SIZE - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3);
670*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_FIFO, value);
671*4882a593Smuzhiyun
672*4882a593Smuzhiyun /* Toggle LCD_MODE every frame */
673*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_MVAL, 0);
674*4882a593Smuzhiyun
675*4882a593Smuzhiyun /* Disable all interrupts */
676*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0U);
677*4882a593Smuzhiyun /* Enable FIFO & DMA errors */
678*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI);
679*4882a593Smuzhiyun
680*4882a593Smuzhiyun /* ...wait for DMA engine to become idle... */
681*4882a593Smuzhiyun while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY)
682*4882a593Smuzhiyun msleep(10);
683*4882a593Smuzhiyun
684*4882a593Smuzhiyun atmel_lcdfb_start(sinfo);
685*4882a593Smuzhiyun
686*4882a593Smuzhiyun dev_dbg(info->device, " * DONE\n");
687*4882a593Smuzhiyun
688*4882a593Smuzhiyun return 0;
689*4882a593Smuzhiyun }
690*4882a593Smuzhiyun
chan_to_field(unsigned int chan,const struct fb_bitfield * bf)691*4882a593Smuzhiyun static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
692*4882a593Smuzhiyun {
693*4882a593Smuzhiyun chan &= 0xffff;
694*4882a593Smuzhiyun chan >>= 16 - bf->length;
695*4882a593Smuzhiyun return chan << bf->offset;
696*4882a593Smuzhiyun }
697*4882a593Smuzhiyun
698*4882a593Smuzhiyun /**
699*4882a593Smuzhiyun * atmel_lcdfb_setcolreg - Optional function. Sets a color register.
700*4882a593Smuzhiyun * @regno: Which register in the CLUT we are programming
701*4882a593Smuzhiyun * @red: The red value which can be up to 16 bits wide
702*4882a593Smuzhiyun * @green: The green value which can be up to 16 bits wide
703*4882a593Smuzhiyun * @blue: The blue value which can be up to 16 bits wide.
704*4882a593Smuzhiyun * @transp: If supported the alpha value which can be up to 16 bits wide.
705*4882a593Smuzhiyun * @info: frame buffer info structure
706*4882a593Smuzhiyun *
707*4882a593Smuzhiyun * Set a single color register. The values supplied have a 16 bit
708*4882a593Smuzhiyun * magnitude which needs to be scaled in this function for the hardware.
709*4882a593Smuzhiyun * Things to take into consideration are how many color registers, if
710*4882a593Smuzhiyun * any, are supported with the current color visual. With truecolor mode
711*4882a593Smuzhiyun * no color palettes are supported. Here a pseudo palette is created
712*4882a593Smuzhiyun * which we store the value in pseudo_palette in struct fb_info. For
713*4882a593Smuzhiyun * pseudocolor mode we have a limited color palette. To deal with this
714*4882a593Smuzhiyun * we can program what color is displayed for a particular pixel value.
715*4882a593Smuzhiyun * DirectColor is similar in that we can program each color field. If
716*4882a593Smuzhiyun * we have a static colormap we don't need to implement this function.
717*4882a593Smuzhiyun *
718*4882a593Smuzhiyun * Returns negative errno on error, or zero on success. In an
719*4882a593Smuzhiyun * ideal world, this would have been the case, but as it turns
720*4882a593Smuzhiyun * out, the other drivers return 1 on failure, so that's what
721*4882a593Smuzhiyun * we're going to do.
722*4882a593Smuzhiyun */
atmel_lcdfb_setcolreg(unsigned int regno,unsigned int red,unsigned int green,unsigned int blue,unsigned int transp,struct fb_info * info)723*4882a593Smuzhiyun static int atmel_lcdfb_setcolreg(unsigned int regno, unsigned int red,
724*4882a593Smuzhiyun unsigned int green, unsigned int blue,
725*4882a593Smuzhiyun unsigned int transp, struct fb_info *info)
726*4882a593Smuzhiyun {
727*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
728*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
729*4882a593Smuzhiyun unsigned int val;
730*4882a593Smuzhiyun u32 *pal;
731*4882a593Smuzhiyun int ret = 1;
732*4882a593Smuzhiyun
733*4882a593Smuzhiyun if (info->var.grayscale)
734*4882a593Smuzhiyun red = green = blue = (19595 * red + 38470 * green
735*4882a593Smuzhiyun + 7471 * blue) >> 16;
736*4882a593Smuzhiyun
737*4882a593Smuzhiyun switch (info->fix.visual) {
738*4882a593Smuzhiyun case FB_VISUAL_TRUECOLOR:
739*4882a593Smuzhiyun if (regno < 16) {
740*4882a593Smuzhiyun pal = info->pseudo_palette;
741*4882a593Smuzhiyun
742*4882a593Smuzhiyun val = chan_to_field(red, &info->var.red);
743*4882a593Smuzhiyun val |= chan_to_field(green, &info->var.green);
744*4882a593Smuzhiyun val |= chan_to_field(blue, &info->var.blue);
745*4882a593Smuzhiyun
746*4882a593Smuzhiyun pal[regno] = val;
747*4882a593Smuzhiyun ret = 0;
748*4882a593Smuzhiyun }
749*4882a593Smuzhiyun break;
750*4882a593Smuzhiyun
751*4882a593Smuzhiyun case FB_VISUAL_PSEUDOCOLOR:
752*4882a593Smuzhiyun if (regno < 256) {
753*4882a593Smuzhiyun if (sinfo->config->have_intensity_bit) {
754*4882a593Smuzhiyun /* old style I+BGR:555 */
755*4882a593Smuzhiyun val = ((red >> 11) & 0x001f);
756*4882a593Smuzhiyun val |= ((green >> 6) & 0x03e0);
757*4882a593Smuzhiyun val |= ((blue >> 1) & 0x7c00);
758*4882a593Smuzhiyun
759*4882a593Smuzhiyun /*
760*4882a593Smuzhiyun * TODO: intensity bit. Maybe something like
761*4882a593Smuzhiyun * ~(red[10] ^ green[10] ^ blue[10]) & 1
762*4882a593Smuzhiyun */
763*4882a593Smuzhiyun } else {
764*4882a593Smuzhiyun /* new style BGR:565 / RGB:565 */
765*4882a593Smuzhiyun if (pdata->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) {
766*4882a593Smuzhiyun val = ((blue >> 11) & 0x001f);
767*4882a593Smuzhiyun val |= ((red >> 0) & 0xf800);
768*4882a593Smuzhiyun } else {
769*4882a593Smuzhiyun val = ((red >> 11) & 0x001f);
770*4882a593Smuzhiyun val |= ((blue >> 0) & 0xf800);
771*4882a593Smuzhiyun }
772*4882a593Smuzhiyun
773*4882a593Smuzhiyun val |= ((green >> 5) & 0x07e0);
774*4882a593Smuzhiyun }
775*4882a593Smuzhiyun
776*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val);
777*4882a593Smuzhiyun ret = 0;
778*4882a593Smuzhiyun }
779*4882a593Smuzhiyun break;
780*4882a593Smuzhiyun
781*4882a593Smuzhiyun case FB_VISUAL_MONO01:
782*4882a593Smuzhiyun if (regno < 2) {
783*4882a593Smuzhiyun val = (regno == 0) ? 0x00 : 0x1F;
784*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_LUT(regno), val);
785*4882a593Smuzhiyun ret = 0;
786*4882a593Smuzhiyun }
787*4882a593Smuzhiyun break;
788*4882a593Smuzhiyun
789*4882a593Smuzhiyun }
790*4882a593Smuzhiyun
791*4882a593Smuzhiyun return ret;
792*4882a593Smuzhiyun }
793*4882a593Smuzhiyun
atmel_lcdfb_pan_display(struct fb_var_screeninfo * var,struct fb_info * info)794*4882a593Smuzhiyun static int atmel_lcdfb_pan_display(struct fb_var_screeninfo *var,
795*4882a593Smuzhiyun struct fb_info *info)
796*4882a593Smuzhiyun {
797*4882a593Smuzhiyun dev_dbg(info->device, "%s\n", __func__);
798*4882a593Smuzhiyun
799*4882a593Smuzhiyun atmel_lcdfb_update_dma(info, var);
800*4882a593Smuzhiyun
801*4882a593Smuzhiyun return 0;
802*4882a593Smuzhiyun }
803*4882a593Smuzhiyun
atmel_lcdfb_blank(int blank_mode,struct fb_info * info)804*4882a593Smuzhiyun static int atmel_lcdfb_blank(int blank_mode, struct fb_info *info)
805*4882a593Smuzhiyun {
806*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
807*4882a593Smuzhiyun
808*4882a593Smuzhiyun switch (blank_mode) {
809*4882a593Smuzhiyun case FB_BLANK_UNBLANK:
810*4882a593Smuzhiyun case FB_BLANK_NORMAL:
811*4882a593Smuzhiyun atmel_lcdfb_start(sinfo);
812*4882a593Smuzhiyun break;
813*4882a593Smuzhiyun case FB_BLANK_VSYNC_SUSPEND:
814*4882a593Smuzhiyun case FB_BLANK_HSYNC_SUSPEND:
815*4882a593Smuzhiyun break;
816*4882a593Smuzhiyun case FB_BLANK_POWERDOWN:
817*4882a593Smuzhiyun atmel_lcdfb_stop(sinfo);
818*4882a593Smuzhiyun break;
819*4882a593Smuzhiyun default:
820*4882a593Smuzhiyun return -EINVAL;
821*4882a593Smuzhiyun }
822*4882a593Smuzhiyun
823*4882a593Smuzhiyun /* let fbcon do a soft blank for us */
824*4882a593Smuzhiyun return ((blank_mode == FB_BLANK_NORMAL) ? 1 : 0);
825*4882a593Smuzhiyun }
826*4882a593Smuzhiyun
827*4882a593Smuzhiyun static const struct fb_ops atmel_lcdfb_ops = {
828*4882a593Smuzhiyun .owner = THIS_MODULE,
829*4882a593Smuzhiyun .fb_check_var = atmel_lcdfb_check_var,
830*4882a593Smuzhiyun .fb_set_par = atmel_lcdfb_set_par,
831*4882a593Smuzhiyun .fb_setcolreg = atmel_lcdfb_setcolreg,
832*4882a593Smuzhiyun .fb_blank = atmel_lcdfb_blank,
833*4882a593Smuzhiyun .fb_pan_display = atmel_lcdfb_pan_display,
834*4882a593Smuzhiyun .fb_fillrect = cfb_fillrect,
835*4882a593Smuzhiyun .fb_copyarea = cfb_copyarea,
836*4882a593Smuzhiyun .fb_imageblit = cfb_imageblit,
837*4882a593Smuzhiyun };
838*4882a593Smuzhiyun
atmel_lcdfb_interrupt(int irq,void * dev_id)839*4882a593Smuzhiyun static irqreturn_t atmel_lcdfb_interrupt(int irq, void *dev_id)
840*4882a593Smuzhiyun {
841*4882a593Smuzhiyun struct fb_info *info = dev_id;
842*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
843*4882a593Smuzhiyun u32 status;
844*4882a593Smuzhiyun
845*4882a593Smuzhiyun status = lcdc_readl(sinfo, ATMEL_LCDC_ISR);
846*4882a593Smuzhiyun if (status & ATMEL_LCDC_UFLWI) {
847*4882a593Smuzhiyun dev_warn(info->device, "FIFO underflow %#x\n", status);
848*4882a593Smuzhiyun /* reset DMA and FIFO to avoid screen shifting */
849*4882a593Smuzhiyun schedule_work(&sinfo->task);
850*4882a593Smuzhiyun }
851*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_ICR, status);
852*4882a593Smuzhiyun return IRQ_HANDLED;
853*4882a593Smuzhiyun }
854*4882a593Smuzhiyun
855*4882a593Smuzhiyun /*
856*4882a593Smuzhiyun * LCD controller task (to reset the LCD)
857*4882a593Smuzhiyun */
atmel_lcdfb_task(struct work_struct * work)858*4882a593Smuzhiyun static void atmel_lcdfb_task(struct work_struct *work)
859*4882a593Smuzhiyun {
860*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo =
861*4882a593Smuzhiyun container_of(work, struct atmel_lcdfb_info, task);
862*4882a593Smuzhiyun
863*4882a593Smuzhiyun atmel_lcdfb_reset(sinfo);
864*4882a593Smuzhiyun }
865*4882a593Smuzhiyun
atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info * sinfo)866*4882a593Smuzhiyun static int __init atmel_lcdfb_init_fbinfo(struct atmel_lcdfb_info *sinfo)
867*4882a593Smuzhiyun {
868*4882a593Smuzhiyun struct fb_info *info = sinfo->info;
869*4882a593Smuzhiyun int ret = 0;
870*4882a593Smuzhiyun
871*4882a593Smuzhiyun info->var.activate |= FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW;
872*4882a593Smuzhiyun
873*4882a593Smuzhiyun dev_info(info->device,
874*4882a593Smuzhiyun "%luKiB frame buffer at %08lx (mapped at %p)\n",
875*4882a593Smuzhiyun (unsigned long)info->fix.smem_len / 1024,
876*4882a593Smuzhiyun (unsigned long)info->fix.smem_start,
877*4882a593Smuzhiyun info->screen_base);
878*4882a593Smuzhiyun
879*4882a593Smuzhiyun /* Allocate colormap */
880*4882a593Smuzhiyun ret = fb_alloc_cmap(&info->cmap, 256, 0);
881*4882a593Smuzhiyun if (ret < 0)
882*4882a593Smuzhiyun dev_err(info->device, "Alloc color map failed\n");
883*4882a593Smuzhiyun
884*4882a593Smuzhiyun return ret;
885*4882a593Smuzhiyun }
886*4882a593Smuzhiyun
atmel_lcdfb_start_clock(struct atmel_lcdfb_info * sinfo)887*4882a593Smuzhiyun static void atmel_lcdfb_start_clock(struct atmel_lcdfb_info *sinfo)
888*4882a593Smuzhiyun {
889*4882a593Smuzhiyun clk_prepare_enable(sinfo->bus_clk);
890*4882a593Smuzhiyun clk_prepare_enable(sinfo->lcdc_clk);
891*4882a593Smuzhiyun }
892*4882a593Smuzhiyun
atmel_lcdfb_stop_clock(struct atmel_lcdfb_info * sinfo)893*4882a593Smuzhiyun static void atmel_lcdfb_stop_clock(struct atmel_lcdfb_info *sinfo)
894*4882a593Smuzhiyun {
895*4882a593Smuzhiyun clk_disable_unprepare(sinfo->bus_clk);
896*4882a593Smuzhiyun clk_disable_unprepare(sinfo->lcdc_clk);
897*4882a593Smuzhiyun }
898*4882a593Smuzhiyun
899*4882a593Smuzhiyun static const struct of_device_id atmel_lcdfb_dt_ids[] = {
900*4882a593Smuzhiyun { .compatible = "atmel,at91sam9261-lcdc" , .data = &at91sam9261_config, },
901*4882a593Smuzhiyun { .compatible = "atmel,at91sam9263-lcdc" , .data = &at91sam9263_config, },
902*4882a593Smuzhiyun { .compatible = "atmel,at91sam9g10-lcdc" , .data = &at91sam9g10_config, },
903*4882a593Smuzhiyun { .compatible = "atmel,at91sam9g45-lcdc" , .data = &at91sam9g45_config, },
904*4882a593Smuzhiyun { .compatible = "atmel,at91sam9g45es-lcdc" , .data = &at91sam9g45es_config, },
905*4882a593Smuzhiyun { .compatible = "atmel,at91sam9rl-lcdc" , .data = &at91sam9rl_config, },
906*4882a593Smuzhiyun { /* sentinel */ }
907*4882a593Smuzhiyun };
908*4882a593Smuzhiyun
909*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, atmel_lcdfb_dt_ids);
910*4882a593Smuzhiyun
911*4882a593Smuzhiyun static const char *atmel_lcdfb_wiring_modes[] = {
912*4882a593Smuzhiyun [ATMEL_LCDC_WIRING_BGR] = "BRG",
913*4882a593Smuzhiyun [ATMEL_LCDC_WIRING_RGB] = "RGB",
914*4882a593Smuzhiyun };
915*4882a593Smuzhiyun
atmel_lcdfb_get_of_wiring_modes(struct device_node * np)916*4882a593Smuzhiyun static int atmel_lcdfb_get_of_wiring_modes(struct device_node *np)
917*4882a593Smuzhiyun {
918*4882a593Smuzhiyun const char *mode;
919*4882a593Smuzhiyun int err, i;
920*4882a593Smuzhiyun
921*4882a593Smuzhiyun err = of_property_read_string(np, "atmel,lcd-wiring-mode", &mode);
922*4882a593Smuzhiyun if (err < 0)
923*4882a593Smuzhiyun return ATMEL_LCDC_WIRING_BGR;
924*4882a593Smuzhiyun
925*4882a593Smuzhiyun for (i = 0; i < ARRAY_SIZE(atmel_lcdfb_wiring_modes); i++)
926*4882a593Smuzhiyun if (!strcasecmp(mode, atmel_lcdfb_wiring_modes[i]))
927*4882a593Smuzhiyun return i;
928*4882a593Smuzhiyun
929*4882a593Smuzhiyun return -ENODEV;
930*4882a593Smuzhiyun }
931*4882a593Smuzhiyun
atmel_lcdfb_power_control_gpio(struct atmel_lcdfb_pdata * pdata,int on)932*4882a593Smuzhiyun static void atmel_lcdfb_power_control_gpio(struct atmel_lcdfb_pdata *pdata, int on)
933*4882a593Smuzhiyun {
934*4882a593Smuzhiyun struct atmel_lcdfb_power_ctrl_gpio *og;
935*4882a593Smuzhiyun
936*4882a593Smuzhiyun list_for_each_entry(og, &pdata->pwr_gpios, list)
937*4882a593Smuzhiyun gpiod_set_value(og->gpiod, on);
938*4882a593Smuzhiyun }
939*4882a593Smuzhiyun
atmel_lcdfb_of_init(struct atmel_lcdfb_info * sinfo)940*4882a593Smuzhiyun static int atmel_lcdfb_of_init(struct atmel_lcdfb_info *sinfo)
941*4882a593Smuzhiyun {
942*4882a593Smuzhiyun struct fb_info *info = sinfo->info;
943*4882a593Smuzhiyun struct atmel_lcdfb_pdata *pdata = &sinfo->pdata;
944*4882a593Smuzhiyun struct fb_var_screeninfo *var = &info->var;
945*4882a593Smuzhiyun struct device *dev = &sinfo->pdev->dev;
946*4882a593Smuzhiyun struct device_node *np =dev->of_node;
947*4882a593Smuzhiyun struct device_node *display_np;
948*4882a593Smuzhiyun struct atmel_lcdfb_power_ctrl_gpio *og;
949*4882a593Smuzhiyun bool is_gpio_power = false;
950*4882a593Smuzhiyun struct fb_videomode fb_vm;
951*4882a593Smuzhiyun struct gpio_desc *gpiod;
952*4882a593Smuzhiyun struct videomode vm;
953*4882a593Smuzhiyun int ret;
954*4882a593Smuzhiyun int i;
955*4882a593Smuzhiyun
956*4882a593Smuzhiyun sinfo->config = (struct atmel_lcdfb_config*)
957*4882a593Smuzhiyun of_match_device(atmel_lcdfb_dt_ids, dev)->data;
958*4882a593Smuzhiyun
959*4882a593Smuzhiyun display_np = of_parse_phandle(np, "display", 0);
960*4882a593Smuzhiyun if (!display_np) {
961*4882a593Smuzhiyun dev_err(dev, "failed to find display phandle\n");
962*4882a593Smuzhiyun return -ENOENT;
963*4882a593Smuzhiyun }
964*4882a593Smuzhiyun
965*4882a593Smuzhiyun ret = of_property_read_u32(display_np, "bits-per-pixel", &var->bits_per_pixel);
966*4882a593Smuzhiyun if (ret < 0) {
967*4882a593Smuzhiyun dev_err(dev, "failed to get property bits-per-pixel\n");
968*4882a593Smuzhiyun goto put_display_node;
969*4882a593Smuzhiyun }
970*4882a593Smuzhiyun
971*4882a593Smuzhiyun ret = of_property_read_u32(display_np, "atmel,guard-time", &pdata->guard_time);
972*4882a593Smuzhiyun if (ret < 0) {
973*4882a593Smuzhiyun dev_err(dev, "failed to get property atmel,guard-time\n");
974*4882a593Smuzhiyun goto put_display_node;
975*4882a593Smuzhiyun }
976*4882a593Smuzhiyun
977*4882a593Smuzhiyun ret = of_property_read_u32(display_np, "atmel,lcdcon2", &pdata->default_lcdcon2);
978*4882a593Smuzhiyun if (ret < 0) {
979*4882a593Smuzhiyun dev_err(dev, "failed to get property atmel,lcdcon2\n");
980*4882a593Smuzhiyun goto put_display_node;
981*4882a593Smuzhiyun }
982*4882a593Smuzhiyun
983*4882a593Smuzhiyun ret = of_property_read_u32(display_np, "atmel,dmacon", &pdata->default_dmacon);
984*4882a593Smuzhiyun if (ret < 0) {
985*4882a593Smuzhiyun dev_err(dev, "failed to get property bits-per-pixel\n");
986*4882a593Smuzhiyun goto put_display_node;
987*4882a593Smuzhiyun }
988*4882a593Smuzhiyun
989*4882a593Smuzhiyun INIT_LIST_HEAD(&pdata->pwr_gpios);
990*4882a593Smuzhiyun for (i = 0; i < gpiod_count(dev, "atmel,power-control"); i++) {
991*4882a593Smuzhiyun ret = -ENOMEM;
992*4882a593Smuzhiyun gpiod = devm_gpiod_get_index(dev, "atmel,power-control",
993*4882a593Smuzhiyun i, GPIOD_ASIS);
994*4882a593Smuzhiyun if (IS_ERR(gpiod))
995*4882a593Smuzhiyun continue;
996*4882a593Smuzhiyun
997*4882a593Smuzhiyun og = devm_kzalloc(dev, sizeof(*og), GFP_KERNEL);
998*4882a593Smuzhiyun if (!og)
999*4882a593Smuzhiyun goto put_display_node;
1000*4882a593Smuzhiyun
1001*4882a593Smuzhiyun og->gpiod = gpiod;
1002*4882a593Smuzhiyun is_gpio_power = true;
1003*4882a593Smuzhiyun
1004*4882a593Smuzhiyun ret = gpiod_direction_output(gpiod, gpiod_is_active_low(gpiod));
1005*4882a593Smuzhiyun if (ret) {
1006*4882a593Smuzhiyun dev_err(dev, "set direction output gpio atmel,power-control[%d] failed\n", i);
1007*4882a593Smuzhiyun goto put_display_node;
1008*4882a593Smuzhiyun }
1009*4882a593Smuzhiyun list_add(&og->list, &pdata->pwr_gpios);
1010*4882a593Smuzhiyun }
1011*4882a593Smuzhiyun
1012*4882a593Smuzhiyun if (is_gpio_power)
1013*4882a593Smuzhiyun pdata->atmel_lcdfb_power_control = atmel_lcdfb_power_control_gpio;
1014*4882a593Smuzhiyun
1015*4882a593Smuzhiyun ret = atmel_lcdfb_get_of_wiring_modes(display_np);
1016*4882a593Smuzhiyun if (ret < 0) {
1017*4882a593Smuzhiyun dev_err(dev, "invalid atmel,lcd-wiring-mode\n");
1018*4882a593Smuzhiyun goto put_display_node;
1019*4882a593Smuzhiyun }
1020*4882a593Smuzhiyun pdata->lcd_wiring_mode = ret;
1021*4882a593Smuzhiyun
1022*4882a593Smuzhiyun pdata->lcdcon_is_backlight = of_property_read_bool(display_np, "atmel,lcdcon-backlight");
1023*4882a593Smuzhiyun pdata->lcdcon_pol_negative = of_property_read_bool(display_np, "atmel,lcdcon-backlight-inverted");
1024*4882a593Smuzhiyun
1025*4882a593Smuzhiyun ret = of_get_videomode(display_np, &vm, OF_USE_NATIVE_MODE);
1026*4882a593Smuzhiyun if (ret) {
1027*4882a593Smuzhiyun dev_err(dev, "failed to get videomode from DT\n");
1028*4882a593Smuzhiyun goto put_display_node;
1029*4882a593Smuzhiyun }
1030*4882a593Smuzhiyun
1031*4882a593Smuzhiyun ret = fb_videomode_from_videomode(&vm, &fb_vm);
1032*4882a593Smuzhiyun if (ret < 0)
1033*4882a593Smuzhiyun goto put_display_node;
1034*4882a593Smuzhiyun
1035*4882a593Smuzhiyun fb_add_videomode(&fb_vm, &info->modelist);
1036*4882a593Smuzhiyun
1037*4882a593Smuzhiyun put_display_node:
1038*4882a593Smuzhiyun of_node_put(display_np);
1039*4882a593Smuzhiyun return ret;
1040*4882a593Smuzhiyun }
1041*4882a593Smuzhiyun
atmel_lcdfb_probe(struct platform_device * pdev)1042*4882a593Smuzhiyun static int __init atmel_lcdfb_probe(struct platform_device *pdev)
1043*4882a593Smuzhiyun {
1044*4882a593Smuzhiyun struct device *dev = &pdev->dev;
1045*4882a593Smuzhiyun struct fb_info *info;
1046*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo;
1047*4882a593Smuzhiyun struct resource *regs = NULL;
1048*4882a593Smuzhiyun struct resource *map = NULL;
1049*4882a593Smuzhiyun struct fb_modelist *modelist;
1050*4882a593Smuzhiyun int ret;
1051*4882a593Smuzhiyun
1052*4882a593Smuzhiyun dev_dbg(dev, "%s BEGIN\n", __func__);
1053*4882a593Smuzhiyun
1054*4882a593Smuzhiyun ret = -ENOMEM;
1055*4882a593Smuzhiyun info = framebuffer_alloc(sizeof(struct atmel_lcdfb_info), dev);
1056*4882a593Smuzhiyun if (!info)
1057*4882a593Smuzhiyun goto out;
1058*4882a593Smuzhiyun
1059*4882a593Smuzhiyun sinfo = info->par;
1060*4882a593Smuzhiyun sinfo->pdev = pdev;
1061*4882a593Smuzhiyun sinfo->info = info;
1062*4882a593Smuzhiyun
1063*4882a593Smuzhiyun INIT_LIST_HEAD(&info->modelist);
1064*4882a593Smuzhiyun
1065*4882a593Smuzhiyun if (!pdev->dev.of_node) {
1066*4882a593Smuzhiyun dev_err(dev, "cannot get default configuration\n");
1067*4882a593Smuzhiyun goto free_info;
1068*4882a593Smuzhiyun }
1069*4882a593Smuzhiyun
1070*4882a593Smuzhiyun ret = atmel_lcdfb_of_init(sinfo);
1071*4882a593Smuzhiyun if (ret)
1072*4882a593Smuzhiyun goto free_info;
1073*4882a593Smuzhiyun
1074*4882a593Smuzhiyun ret = -ENODEV;
1075*4882a593Smuzhiyun if (!sinfo->config)
1076*4882a593Smuzhiyun goto free_info;
1077*4882a593Smuzhiyun
1078*4882a593Smuzhiyun sinfo->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");
1079*4882a593Smuzhiyun if (IS_ERR(sinfo->reg_lcd))
1080*4882a593Smuzhiyun sinfo->reg_lcd = NULL;
1081*4882a593Smuzhiyun
1082*4882a593Smuzhiyun info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK |
1083*4882a593Smuzhiyun FBINFO_HWACCEL_YPAN;
1084*4882a593Smuzhiyun info->pseudo_palette = sinfo->pseudo_palette;
1085*4882a593Smuzhiyun info->fbops = &atmel_lcdfb_ops;
1086*4882a593Smuzhiyun
1087*4882a593Smuzhiyun info->fix = atmel_lcdfb_fix;
1088*4882a593Smuzhiyun strcpy(info->fix.id, sinfo->pdev->name);
1089*4882a593Smuzhiyun
1090*4882a593Smuzhiyun /* Enable LCDC Clocks */
1091*4882a593Smuzhiyun sinfo->bus_clk = clk_get(dev, "hclk");
1092*4882a593Smuzhiyun if (IS_ERR(sinfo->bus_clk)) {
1093*4882a593Smuzhiyun ret = PTR_ERR(sinfo->bus_clk);
1094*4882a593Smuzhiyun goto free_info;
1095*4882a593Smuzhiyun }
1096*4882a593Smuzhiyun sinfo->lcdc_clk = clk_get(dev, "lcdc_clk");
1097*4882a593Smuzhiyun if (IS_ERR(sinfo->lcdc_clk)) {
1098*4882a593Smuzhiyun ret = PTR_ERR(sinfo->lcdc_clk);
1099*4882a593Smuzhiyun goto put_bus_clk;
1100*4882a593Smuzhiyun }
1101*4882a593Smuzhiyun atmel_lcdfb_start_clock(sinfo);
1102*4882a593Smuzhiyun
1103*4882a593Smuzhiyun modelist = list_first_entry(&info->modelist,
1104*4882a593Smuzhiyun struct fb_modelist, list);
1105*4882a593Smuzhiyun fb_videomode_to_var(&info->var, &modelist->mode);
1106*4882a593Smuzhiyun
1107*4882a593Smuzhiyun atmel_lcdfb_check_var(&info->var, info);
1108*4882a593Smuzhiyun
1109*4882a593Smuzhiyun regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1110*4882a593Smuzhiyun if (!regs) {
1111*4882a593Smuzhiyun dev_err(dev, "resources unusable\n");
1112*4882a593Smuzhiyun ret = -ENXIO;
1113*4882a593Smuzhiyun goto stop_clk;
1114*4882a593Smuzhiyun }
1115*4882a593Smuzhiyun
1116*4882a593Smuzhiyun sinfo->irq_base = platform_get_irq(pdev, 0);
1117*4882a593Smuzhiyun if (sinfo->irq_base < 0) {
1118*4882a593Smuzhiyun ret = sinfo->irq_base;
1119*4882a593Smuzhiyun goto stop_clk;
1120*4882a593Smuzhiyun }
1121*4882a593Smuzhiyun
1122*4882a593Smuzhiyun /* Initialize video memory */
1123*4882a593Smuzhiyun map = platform_get_resource(pdev, IORESOURCE_MEM, 1);
1124*4882a593Smuzhiyun if (map) {
1125*4882a593Smuzhiyun /* use a pre-allocated memory buffer */
1126*4882a593Smuzhiyun info->fix.smem_start = map->start;
1127*4882a593Smuzhiyun info->fix.smem_len = resource_size(map);
1128*4882a593Smuzhiyun if (!request_mem_region(info->fix.smem_start,
1129*4882a593Smuzhiyun info->fix.smem_len, pdev->name)) {
1130*4882a593Smuzhiyun ret = -EBUSY;
1131*4882a593Smuzhiyun goto stop_clk;
1132*4882a593Smuzhiyun }
1133*4882a593Smuzhiyun
1134*4882a593Smuzhiyun info->screen_base = ioremap_wc(info->fix.smem_start,
1135*4882a593Smuzhiyun info->fix.smem_len);
1136*4882a593Smuzhiyun if (!info->screen_base) {
1137*4882a593Smuzhiyun ret = -ENOMEM;
1138*4882a593Smuzhiyun goto release_intmem;
1139*4882a593Smuzhiyun }
1140*4882a593Smuzhiyun
1141*4882a593Smuzhiyun /*
1142*4882a593Smuzhiyun * Don't clear the framebuffer -- someone may have set
1143*4882a593Smuzhiyun * up a splash image.
1144*4882a593Smuzhiyun */
1145*4882a593Smuzhiyun } else {
1146*4882a593Smuzhiyun /* allocate memory buffer */
1147*4882a593Smuzhiyun ret = atmel_lcdfb_alloc_video_memory(sinfo);
1148*4882a593Smuzhiyun if (ret < 0) {
1149*4882a593Smuzhiyun dev_err(dev, "cannot allocate framebuffer: %d\n", ret);
1150*4882a593Smuzhiyun goto stop_clk;
1151*4882a593Smuzhiyun }
1152*4882a593Smuzhiyun }
1153*4882a593Smuzhiyun
1154*4882a593Smuzhiyun /* LCDC registers */
1155*4882a593Smuzhiyun info->fix.mmio_start = regs->start;
1156*4882a593Smuzhiyun info->fix.mmio_len = resource_size(regs);
1157*4882a593Smuzhiyun
1158*4882a593Smuzhiyun if (!request_mem_region(info->fix.mmio_start,
1159*4882a593Smuzhiyun info->fix.mmio_len, pdev->name)) {
1160*4882a593Smuzhiyun ret = -EBUSY;
1161*4882a593Smuzhiyun goto free_fb;
1162*4882a593Smuzhiyun }
1163*4882a593Smuzhiyun
1164*4882a593Smuzhiyun sinfo->mmio = ioremap(info->fix.mmio_start, info->fix.mmio_len);
1165*4882a593Smuzhiyun if (!sinfo->mmio) {
1166*4882a593Smuzhiyun dev_err(dev, "cannot map LCDC registers\n");
1167*4882a593Smuzhiyun ret = -ENOMEM;
1168*4882a593Smuzhiyun goto release_mem;
1169*4882a593Smuzhiyun }
1170*4882a593Smuzhiyun
1171*4882a593Smuzhiyun /* Initialize PWM for contrast or backlight ("off") */
1172*4882a593Smuzhiyun init_contrast(sinfo);
1173*4882a593Smuzhiyun
1174*4882a593Smuzhiyun /* interrupt */
1175*4882a593Smuzhiyun ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info);
1176*4882a593Smuzhiyun if (ret) {
1177*4882a593Smuzhiyun dev_err(dev, "request_irq failed: %d\n", ret);
1178*4882a593Smuzhiyun goto unmap_mmio;
1179*4882a593Smuzhiyun }
1180*4882a593Smuzhiyun
1181*4882a593Smuzhiyun /* Some operations on the LCDC might sleep and
1182*4882a593Smuzhiyun * require a preemptible task context */
1183*4882a593Smuzhiyun INIT_WORK(&sinfo->task, atmel_lcdfb_task);
1184*4882a593Smuzhiyun
1185*4882a593Smuzhiyun ret = atmel_lcdfb_init_fbinfo(sinfo);
1186*4882a593Smuzhiyun if (ret < 0) {
1187*4882a593Smuzhiyun dev_err(dev, "init fbinfo failed: %d\n", ret);
1188*4882a593Smuzhiyun goto unregister_irqs;
1189*4882a593Smuzhiyun }
1190*4882a593Smuzhiyun
1191*4882a593Smuzhiyun ret = atmel_lcdfb_set_par(info);
1192*4882a593Smuzhiyun if (ret < 0) {
1193*4882a593Smuzhiyun dev_err(dev, "set par failed: %d\n", ret);
1194*4882a593Smuzhiyun goto unregister_irqs;
1195*4882a593Smuzhiyun }
1196*4882a593Smuzhiyun
1197*4882a593Smuzhiyun dev_set_drvdata(dev, info);
1198*4882a593Smuzhiyun
1199*4882a593Smuzhiyun /*
1200*4882a593Smuzhiyun * Tell the world that we're ready to go
1201*4882a593Smuzhiyun */
1202*4882a593Smuzhiyun ret = register_framebuffer(info);
1203*4882a593Smuzhiyun if (ret < 0) {
1204*4882a593Smuzhiyun dev_err(dev, "failed to register framebuffer device: %d\n", ret);
1205*4882a593Smuzhiyun goto reset_drvdata;
1206*4882a593Smuzhiyun }
1207*4882a593Smuzhiyun
1208*4882a593Smuzhiyun /* Power up the LCDC screen */
1209*4882a593Smuzhiyun atmel_lcdfb_power_control(sinfo, 1);
1210*4882a593Smuzhiyun
1211*4882a593Smuzhiyun dev_info(dev, "fb%d: Atmel LCDC at 0x%08lx (mapped at %p), irq %d\n",
1212*4882a593Smuzhiyun info->node, info->fix.mmio_start, sinfo->mmio, sinfo->irq_base);
1213*4882a593Smuzhiyun
1214*4882a593Smuzhiyun return 0;
1215*4882a593Smuzhiyun
1216*4882a593Smuzhiyun reset_drvdata:
1217*4882a593Smuzhiyun dev_set_drvdata(dev, NULL);
1218*4882a593Smuzhiyun fb_dealloc_cmap(&info->cmap);
1219*4882a593Smuzhiyun unregister_irqs:
1220*4882a593Smuzhiyun cancel_work_sync(&sinfo->task);
1221*4882a593Smuzhiyun free_irq(sinfo->irq_base, info);
1222*4882a593Smuzhiyun unmap_mmio:
1223*4882a593Smuzhiyun exit_backlight(sinfo);
1224*4882a593Smuzhiyun iounmap(sinfo->mmio);
1225*4882a593Smuzhiyun release_mem:
1226*4882a593Smuzhiyun release_mem_region(info->fix.mmio_start, info->fix.mmio_len);
1227*4882a593Smuzhiyun free_fb:
1228*4882a593Smuzhiyun if (map)
1229*4882a593Smuzhiyun iounmap(info->screen_base);
1230*4882a593Smuzhiyun else
1231*4882a593Smuzhiyun atmel_lcdfb_free_video_memory(sinfo);
1232*4882a593Smuzhiyun
1233*4882a593Smuzhiyun release_intmem:
1234*4882a593Smuzhiyun if (map)
1235*4882a593Smuzhiyun release_mem_region(info->fix.smem_start, info->fix.smem_len);
1236*4882a593Smuzhiyun stop_clk:
1237*4882a593Smuzhiyun atmel_lcdfb_stop_clock(sinfo);
1238*4882a593Smuzhiyun clk_put(sinfo->lcdc_clk);
1239*4882a593Smuzhiyun put_bus_clk:
1240*4882a593Smuzhiyun clk_put(sinfo->bus_clk);
1241*4882a593Smuzhiyun free_info:
1242*4882a593Smuzhiyun framebuffer_release(info);
1243*4882a593Smuzhiyun out:
1244*4882a593Smuzhiyun dev_dbg(dev, "%s FAILED\n", __func__);
1245*4882a593Smuzhiyun return ret;
1246*4882a593Smuzhiyun }
1247*4882a593Smuzhiyun
atmel_lcdfb_remove(struct platform_device * pdev)1248*4882a593Smuzhiyun static int __exit atmel_lcdfb_remove(struct platform_device *pdev)
1249*4882a593Smuzhiyun {
1250*4882a593Smuzhiyun struct device *dev = &pdev->dev;
1251*4882a593Smuzhiyun struct fb_info *info = dev_get_drvdata(dev);
1252*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo;
1253*4882a593Smuzhiyun
1254*4882a593Smuzhiyun if (!info || !info->par)
1255*4882a593Smuzhiyun return 0;
1256*4882a593Smuzhiyun sinfo = info->par;
1257*4882a593Smuzhiyun
1258*4882a593Smuzhiyun cancel_work_sync(&sinfo->task);
1259*4882a593Smuzhiyun exit_backlight(sinfo);
1260*4882a593Smuzhiyun atmel_lcdfb_power_control(sinfo, 0);
1261*4882a593Smuzhiyun unregister_framebuffer(info);
1262*4882a593Smuzhiyun atmel_lcdfb_stop_clock(sinfo);
1263*4882a593Smuzhiyun clk_put(sinfo->lcdc_clk);
1264*4882a593Smuzhiyun clk_put(sinfo->bus_clk);
1265*4882a593Smuzhiyun fb_dealloc_cmap(&info->cmap);
1266*4882a593Smuzhiyun free_irq(sinfo->irq_base, info);
1267*4882a593Smuzhiyun iounmap(sinfo->mmio);
1268*4882a593Smuzhiyun release_mem_region(info->fix.mmio_start, info->fix.mmio_len);
1269*4882a593Smuzhiyun if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) {
1270*4882a593Smuzhiyun iounmap(info->screen_base);
1271*4882a593Smuzhiyun release_mem_region(info->fix.smem_start, info->fix.smem_len);
1272*4882a593Smuzhiyun } else {
1273*4882a593Smuzhiyun atmel_lcdfb_free_video_memory(sinfo);
1274*4882a593Smuzhiyun }
1275*4882a593Smuzhiyun
1276*4882a593Smuzhiyun framebuffer_release(info);
1277*4882a593Smuzhiyun
1278*4882a593Smuzhiyun return 0;
1279*4882a593Smuzhiyun }
1280*4882a593Smuzhiyun
1281*4882a593Smuzhiyun #ifdef CONFIG_PM
1282*4882a593Smuzhiyun
atmel_lcdfb_suspend(struct platform_device * pdev,pm_message_t mesg)1283*4882a593Smuzhiyun static int atmel_lcdfb_suspend(struct platform_device *pdev, pm_message_t mesg)
1284*4882a593Smuzhiyun {
1285*4882a593Smuzhiyun struct fb_info *info = platform_get_drvdata(pdev);
1286*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
1287*4882a593Smuzhiyun
1288*4882a593Smuzhiyun /*
1289*4882a593Smuzhiyun * We don't want to handle interrupts while the clock is
1290*4882a593Smuzhiyun * stopped. It may take forever.
1291*4882a593Smuzhiyun */
1292*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0U);
1293*4882a593Smuzhiyun
1294*4882a593Smuzhiyun sinfo->saved_lcdcon = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_CTR);
1295*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 0);
1296*4882a593Smuzhiyun atmel_lcdfb_power_control(sinfo, 0);
1297*4882a593Smuzhiyun atmel_lcdfb_stop(sinfo);
1298*4882a593Smuzhiyun atmel_lcdfb_stop_clock(sinfo);
1299*4882a593Smuzhiyun
1300*4882a593Smuzhiyun return 0;
1301*4882a593Smuzhiyun }
1302*4882a593Smuzhiyun
atmel_lcdfb_resume(struct platform_device * pdev)1303*4882a593Smuzhiyun static int atmel_lcdfb_resume(struct platform_device *pdev)
1304*4882a593Smuzhiyun {
1305*4882a593Smuzhiyun struct fb_info *info = platform_get_drvdata(pdev);
1306*4882a593Smuzhiyun struct atmel_lcdfb_info *sinfo = info->par;
1307*4882a593Smuzhiyun
1308*4882a593Smuzhiyun atmel_lcdfb_start_clock(sinfo);
1309*4882a593Smuzhiyun atmel_lcdfb_start(sinfo);
1310*4882a593Smuzhiyun atmel_lcdfb_power_control(sinfo, 1);
1311*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, sinfo->saved_lcdcon);
1312*4882a593Smuzhiyun
1313*4882a593Smuzhiyun /* Enable FIFO & DMA errors */
1314*4882a593Smuzhiyun lcdc_writel(sinfo, ATMEL_LCDC_IER, ATMEL_LCDC_UFLWI
1315*4882a593Smuzhiyun | ATMEL_LCDC_OWRI | ATMEL_LCDC_MERI);
1316*4882a593Smuzhiyun
1317*4882a593Smuzhiyun return 0;
1318*4882a593Smuzhiyun }
1319*4882a593Smuzhiyun
1320*4882a593Smuzhiyun #else
1321*4882a593Smuzhiyun #define atmel_lcdfb_suspend NULL
1322*4882a593Smuzhiyun #define atmel_lcdfb_resume NULL
1323*4882a593Smuzhiyun #endif
1324*4882a593Smuzhiyun
1325*4882a593Smuzhiyun static struct platform_driver atmel_lcdfb_driver = {
1326*4882a593Smuzhiyun .remove = __exit_p(atmel_lcdfb_remove),
1327*4882a593Smuzhiyun .suspend = atmel_lcdfb_suspend,
1328*4882a593Smuzhiyun .resume = atmel_lcdfb_resume,
1329*4882a593Smuzhiyun .driver = {
1330*4882a593Smuzhiyun .name = "atmel_lcdfb",
1331*4882a593Smuzhiyun .of_match_table = of_match_ptr(atmel_lcdfb_dt_ids),
1332*4882a593Smuzhiyun },
1333*4882a593Smuzhiyun };
1334*4882a593Smuzhiyun
1335*4882a593Smuzhiyun module_platform_driver_probe(atmel_lcdfb_driver, atmel_lcdfb_probe);
1336*4882a593Smuzhiyun
1337*4882a593Smuzhiyun MODULE_DESCRIPTION("AT91 LCD Controller framebuffer driver");
1338*4882a593Smuzhiyun MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
1339*4882a593Smuzhiyun MODULE_LICENSE("GPL");
1340