diff options
author | Michael Hennerich <michael.hennerich@analog.com> | 2009-12-15 19:46:21 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-16 10:20:01 -0500 |
commit | 9cfe4a93e50bb36392c8d74432e4607f297cdbfc (patch) | |
tree | 77fa42b5cd0cd0a1af2668f3ee728cd67ee8c2ad /drivers | |
parent | 0769746183caff9d4334be48c7b0e7d2ec8716c4 (diff) |
fbdev: bfin-lq035q1-fb: new Blackfin Landscape LCD EZ-Extender driver
Framebuffer driver for the Landscape LCD EZ-Extender (ADZS-BFLLCD-EZEXT)
http://docs.blackfin.uclinux.org/doku.php?id=hw:cards:landscape_lcd_ez-extender
Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/video/Kconfig | 15 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/bfin-lq035q1-fb.c | 826 |
3 files changed, 842 insertions, 0 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 99c0df1c7ebf..5a5c303a6373 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig | |||
@@ -614,6 +614,21 @@ config FB_BFIN_T350MCQB | |||
614 | This display is a QVGA 320x240 24-bit RGB display interfaced by an 8-bit wide PPI | 614 | This display is a QVGA 320x240 24-bit RGB display interfaced by an 8-bit wide PPI |
615 | It uses PPI[0..7] PPI_FS1, PPI_FS2 and PPI_CLK. | 615 | It uses PPI[0..7] PPI_FS1, PPI_FS2 and PPI_CLK. |
616 | 616 | ||
617 | config FB_BFIN_LQ035Q1 | ||
618 | tristate "SHARP LQ035Q1DH02 TFT LCD" | ||
619 | depends on FB && BLACKFIN && SPI | ||
620 | select FB_CFB_FILLRECT | ||
621 | select FB_CFB_COPYAREA | ||
622 | select FB_CFB_IMAGEBLIT | ||
623 | select BFIN_GPTIMERS | ||
624 | help | ||
625 | This is the framebuffer device driver for a SHARP LQ035Q1DH02 TFT display found on | ||
626 | the Blackfin Landscape LCD EZ-Extender Card. | ||
627 | This display is a QVGA 320x240 18-bit RGB display interfaced by an 16-bit wide PPI | ||
628 | It uses PPI[0..15] PPI_FS1, PPI_FS2 and PPI_CLK. | ||
629 | |||
630 | To compile this driver as a module, choose M here: the | ||
631 | module will be called bfin-lq035q1-fb. | ||
617 | 632 | ||
618 | config FB_STI | 633 | config FB_STI |
619 | tristate "HP STI frame buffer device support" | 634 | tristate "HP STI frame buffer device support" |
diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 0f8da331ba0f..4ecb30c4f3f2 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile | |||
@@ -137,6 +137,7 @@ obj-$(CONFIG_FB_EFI) += efifb.o | |||
137 | obj-$(CONFIG_FB_VGA16) += vga16fb.o | 137 | obj-$(CONFIG_FB_VGA16) += vga16fb.o |
138 | obj-$(CONFIG_FB_OF) += offb.o | 138 | obj-$(CONFIG_FB_OF) += offb.o |
139 | obj-$(CONFIG_FB_BF54X_LQ043) += bf54x-lq043fb.o | 139 | obj-$(CONFIG_FB_BF54X_LQ043) += bf54x-lq043fb.o |
140 | obj-$(CONFIG_FB_BFIN_LQ035Q1) += bfin-lq035q1-fb.o | ||
140 | obj-$(CONFIG_FB_BFIN_T350MCQB) += bfin-t350mcqb-fb.o | 141 | obj-$(CONFIG_FB_BFIN_T350MCQB) += bfin-t350mcqb-fb.o |
141 | obj-$(CONFIG_FB_MX3) += mx3fb.o | 142 | obj-$(CONFIG_FB_MX3) += mx3fb.o |
142 | obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o | 143 | obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o |
diff --git a/drivers/video/bfin-lq035q1-fb.c b/drivers/video/bfin-lq035q1-fb.c new file mode 100644 index 000000000000..b690c269784a --- /dev/null +++ b/drivers/video/bfin-lq035q1-fb.c | |||
@@ -0,0 +1,826 @@ | |||
1 | /* | ||
2 | * Blackfin LCD Framebuffer driver SHARP LQ035Q1DH02 | ||
3 | * | ||
4 | * Copyright 2008-2009 Analog Devices Inc. | ||
5 | * Licensed under the GPL-2 or later. | ||
6 | */ | ||
7 | |||
8 | #define DRIVER_NAME "bfin-lq035q1" | ||
9 | #define pr_fmt(fmt) DRIVER_NAME ": " fmt | ||
10 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/errno.h> | ||
14 | #include <linux/string.h> | ||
15 | #include <linux/fb.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/types.h> | ||
18 | #include <linux/interrupt.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/backlight.h> | ||
21 | #include <linux/lcd.h> | ||
22 | #include <linux/dma-mapping.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/spi/spi.h> | ||
25 | #include <linux/dma-mapping.h> | ||
26 | |||
27 | #include <asm/blackfin.h> | ||
28 | #include <asm/irq.h> | ||
29 | #include <asm/dma.h> | ||
30 | #include <asm/portmux.h> | ||
31 | #include <asm/gptimers.h> | ||
32 | |||
33 | #include <asm/bfin-lq035q1.h> | ||
34 | |||
35 | #if defined(BF533_FAMILY) || defined(BF538_FAMILY) | ||
36 | #define TIMER_HSYNC_id TIMER1_id | ||
37 | #define TIMER_HSYNCbit TIMER1bit | ||
38 | #define TIMER_HSYNC_STATUS_TRUN TIMER_STATUS_TRUN1 | ||
39 | #define TIMER_HSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL1 | ||
40 | #define TIMER_HSYNC_STATUS_TOVF TIMER_STATUS_TOVF1 | ||
41 | |||
42 | #define TIMER_VSYNC_id TIMER2_id | ||
43 | #define TIMER_VSYNCbit TIMER2bit | ||
44 | #define TIMER_VSYNC_STATUS_TRUN TIMER_STATUS_TRUN2 | ||
45 | #define TIMER_VSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL2 | ||
46 | #define TIMER_VSYNC_STATUS_TOVF TIMER_STATUS_TOVF2 | ||
47 | #else | ||
48 | #define TIMER_HSYNC_id TIMER0_id | ||
49 | #define TIMER_HSYNCbit TIMER0bit | ||
50 | #define TIMER_HSYNC_STATUS_TRUN TIMER_STATUS_TRUN0 | ||
51 | #define TIMER_HSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL0 | ||
52 | #define TIMER_HSYNC_STATUS_TOVF TIMER_STATUS_TOVF0 | ||
53 | |||
54 | #define TIMER_VSYNC_id TIMER1_id | ||
55 | #define TIMER_VSYNCbit TIMER1bit | ||
56 | #define TIMER_VSYNC_STATUS_TRUN TIMER_STATUS_TRUN1 | ||
57 | #define TIMER_VSYNC_STATUS_TIMIL TIMER_STATUS_TIMIL1 | ||
58 | #define TIMER_VSYNC_STATUS_TOVF TIMER_STATUS_TOVF1 | ||
59 | #endif | ||
60 | |||
61 | #define LCD_X_RES 320 /* Horizontal Resolution */ | ||
62 | #define LCD_Y_RES 240 /* Vertical Resolution */ | ||
63 | #define DMA_BUS_SIZE 16 | ||
64 | |||
65 | #define USE_RGB565_16_BIT_PPI | ||
66 | |||
67 | #ifdef USE_RGB565_16_BIT_PPI | ||
68 | #define LCD_BPP 16 /* Bit Per Pixel */ | ||
69 | #define CLOCKS_PER_PIX 1 | ||
70 | #define CPLD_PIPELINE_DELAY_COR 0 /* NO CPLB */ | ||
71 | #endif | ||
72 | |||
73 | /* Interface 16/18-bit TFT over an 8-bit wide PPI using a small Programmable Logic Device (CPLD) | ||
74 | * http://blackfin.uclinux.org/gf/project/stamp/frs/?action=FrsReleaseBrowse&frs_package_id=165 | ||
75 | */ | ||
76 | |||
77 | #ifdef USE_RGB565_8_BIT_PPI | ||
78 | #define LCD_BPP 16 /* Bit Per Pixel */ | ||
79 | #define CLOCKS_PER_PIX 2 | ||
80 | #define CPLD_PIPELINE_DELAY_COR 3 /* RGB565 */ | ||
81 | #endif | ||
82 | |||
83 | #ifdef USE_RGB888_8_BIT_PPI | ||
84 | #define LCD_BPP 24 /* Bit Per Pixel */ | ||
85 | #define CLOCKS_PER_PIX 3 | ||
86 | #define CPLD_PIPELINE_DELAY_COR 5 /* RGB888 */ | ||
87 | #endif | ||
88 | |||
89 | /* | ||
90 | * HS and VS timing parameters (all in number of PPI clk ticks) | ||
91 | */ | ||
92 | |||
93 | #define U_LINE 4 /* Blanking Lines */ | ||
94 | |||
95 | #define H_ACTPIX (LCD_X_RES * CLOCKS_PER_PIX) /* active horizontal pixel */ | ||
96 | #define H_PERIOD (336 * CLOCKS_PER_PIX) /* HS period */ | ||
97 | #define H_PULSE (2 * CLOCKS_PER_PIX) /* HS pulse width */ | ||
98 | #define H_START (7 * CLOCKS_PER_PIX + CPLD_PIPELINE_DELAY_COR) /* first valid pixel */ | ||
99 | |||
100 | #define V_LINES (LCD_Y_RES + U_LINE) /* total vertical lines */ | ||
101 | #define V_PULSE (2 * CLOCKS_PER_PIX) /* VS pulse width (1-5 H_PERIODs) */ | ||
102 | #define V_PERIOD (H_PERIOD * V_LINES) /* VS period */ | ||
103 | |||
104 | #define ACTIVE_VIDEO_MEM_OFFSET ((U_LINE / 2) * LCD_X_RES * (LCD_BPP / 8)) | ||
105 | |||
106 | #define BFIN_LCD_NBR_PALETTE_ENTRIES 256 | ||
107 | |||
108 | #define PPI_TX_MODE 0x2 | ||
109 | #define PPI_XFER_TYPE_11 0xC | ||
110 | #define PPI_PORT_CFG_01 0x10 | ||
111 | #define PPI_POLS_1 0x8000 | ||
112 | |||
113 | #if (CLOCKS_PER_PIX > 1) | ||
114 | #define PPI_PMODE (DLEN_8 | PACK_EN) | ||
115 | #else | ||
116 | #define PPI_PMODE (DLEN_16) | ||
117 | #endif | ||
118 | |||
119 | #define LQ035_INDEX 0x74 | ||
120 | #define LQ035_DATA 0x76 | ||
121 | |||
122 | #define LQ035_DRIVER_OUTPUT_CTL 0x1 | ||
123 | #define LQ035_SHUT_CTL 0x11 | ||
124 | |||
125 | #define LQ035_DRIVER_OUTPUT_MASK (LQ035_LR | LQ035_TB | LQ035_BGR | LQ035_REV) | ||
126 | #define LQ035_DRIVER_OUTPUT_DEFAULT (0x2AEF & ~LQ035_DRIVER_OUTPUT_MASK) | ||
127 | |||
128 | #define LQ035_SHUT (1 << 0) /* Shutdown */ | ||
129 | #define LQ035_ON (0 << 0) /* Shutdown */ | ||
130 | |||
131 | struct bfin_lq035q1fb_info { | ||
132 | struct fb_info *fb; | ||
133 | struct device *dev; | ||
134 | struct spi_driver spidrv; | ||
135 | struct bfin_lq035q1fb_disp_info *disp_info; | ||
136 | unsigned char *fb_buffer; /* RGB Buffer */ | ||
137 | dma_addr_t dma_handle; | ||
138 | int lq035_open_cnt; | ||
139 | int irq; | ||
140 | spinlock_t lock; /* lock */ | ||
141 | u32 pseudo_pal[16]; | ||
142 | }; | ||
143 | |||
144 | static int nocursor; | ||
145 | module_param(nocursor, int, 0644); | ||
146 | MODULE_PARM_DESC(nocursor, "cursor enable/disable"); | ||
147 | |||
148 | struct spi_control { | ||
149 | unsigned short mode; | ||
150 | }; | ||
151 | |||
152 | static int lq035q1_control(struct spi_device *spi, unsigned char reg, unsigned short value) | ||
153 | { | ||
154 | int ret; | ||
155 | u8 regs[3] = { LQ035_INDEX, 0, 0 }; | ||
156 | u8 dat[3] = { LQ035_DATA, 0, 0 }; | ||
157 | |||
158 | if (!spi) | ||
159 | return -ENODEV; | ||
160 | |||
161 | regs[2] = reg; | ||
162 | dat[1] = value >> 8; | ||
163 | dat[2] = value & 0xFF; | ||
164 | |||
165 | ret = spi_write(spi, regs, ARRAY_SIZE(regs)); | ||
166 | ret |= spi_write(spi, dat, ARRAY_SIZE(dat)); | ||
167 | return ret; | ||
168 | } | ||
169 | |||
170 | static int __devinit lq035q1_spidev_probe(struct spi_device *spi) | ||
171 | { | ||
172 | int ret; | ||
173 | struct spi_control *ctl; | ||
174 | struct bfin_lq035q1fb_info *info = container_of(spi->dev.driver, | ||
175 | struct bfin_lq035q1fb_info, | ||
176 | spidrv.driver); | ||
177 | |||
178 | ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); | ||
179 | |||
180 | if (!ctl) | ||
181 | return -ENOMEM; | ||
182 | |||
183 | ctl->mode = (info->disp_info->mode & | ||
184 | LQ035_DRIVER_OUTPUT_MASK) | LQ035_DRIVER_OUTPUT_DEFAULT; | ||
185 | |||
186 | ret = lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_ON); | ||
187 | ret |= lq035q1_control(spi, LQ035_DRIVER_OUTPUT_CTL, ctl->mode); | ||
188 | if (ret) | ||
189 | return ret; | ||
190 | |||
191 | spi_set_drvdata(spi, ctl); | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static int lq035q1_spidev_remove(struct spi_device *spi) | ||
197 | { | ||
198 | return lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_SHUT); | ||
199 | } | ||
200 | |||
201 | #ifdef CONFIG_PM | ||
202 | static int lq035q1_spidev_suspend(struct spi_device *spi, pm_message_t state) | ||
203 | { | ||
204 | return lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_SHUT); | ||
205 | } | ||
206 | |||
207 | static int lq035q1_spidev_resume(struct spi_device *spi) | ||
208 | { | ||
209 | int ret; | ||
210 | struct spi_control *ctl = spi_get_drvdata(spi); | ||
211 | |||
212 | ret = lq035q1_control(spi, LQ035_DRIVER_OUTPUT_CTL, ctl->mode); | ||
213 | if (ret) | ||
214 | return ret; | ||
215 | |||
216 | return lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_ON); | ||
217 | } | ||
218 | #else | ||
219 | # define lq035q1_spidev_suspend NULL | ||
220 | # define lq035q1_spidev_resume NULL | ||
221 | #endif | ||
222 | |||
223 | /* Power down all displays on reboot, poweroff or halt */ | ||
224 | static void lq035q1_spidev_shutdown(struct spi_device *spi) | ||
225 | { | ||
226 | lq035q1_control(spi, LQ035_SHUT_CTL, LQ035_SHUT); | ||
227 | } | ||
228 | |||
229 | static int lq035q1_backlight(struct bfin_lq035q1fb_info *info, unsigned arg) | ||
230 | { | ||
231 | if (info->disp_info->use_bl) | ||
232 | gpio_set_value(info->disp_info->gpio_bl, arg); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static void bfin_lq035q1_config_ppi(struct bfin_lq035q1fb_info *fbi) | ||
238 | { | ||
239 | bfin_write_PPI_DELAY(H_START); | ||
240 | bfin_write_PPI_COUNT(H_ACTPIX - 1); | ||
241 | bfin_write_PPI_FRAME(V_LINES); | ||
242 | |||
243 | bfin_write_PPI_CONTROL(PPI_TX_MODE | /* output mode , PORT_DIR */ | ||
244 | PPI_XFER_TYPE_11 | /* sync mode XFR_TYPE */ | ||
245 | PPI_PORT_CFG_01 | /* two frame sync PORT_CFG */ | ||
246 | PPI_PMODE | /* 8/16 bit data length / PACK_EN? */ | ||
247 | PPI_POLS_1); /* faling edge syncs POLS */ | ||
248 | } | ||
249 | |||
250 | static inline void bfin_lq035q1_disable_ppi(void) | ||
251 | { | ||
252 | bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() & ~PORT_EN); | ||
253 | } | ||
254 | |||
255 | static inline void bfin_lq035q1_enable_ppi(void) | ||
256 | { | ||
257 | bfin_write_PPI_CONTROL(bfin_read_PPI_CONTROL() | PORT_EN); | ||
258 | } | ||
259 | |||
260 | static void bfin_lq035q1_start_timers(void) | ||
261 | { | ||
262 | enable_gptimers(TIMER_VSYNCbit | TIMER_HSYNCbit); | ||
263 | } | ||
264 | |||
265 | static void bfin_lq035q1_stop_timers(void) | ||
266 | { | ||
267 | disable_gptimers(TIMER_HSYNCbit | TIMER_VSYNCbit); | ||
268 | |||
269 | set_gptimer_status(0, TIMER_HSYNC_STATUS_TRUN | TIMER_VSYNC_STATUS_TRUN | | ||
270 | TIMER_HSYNC_STATUS_TIMIL | TIMER_VSYNC_STATUS_TIMIL | | ||
271 | TIMER_HSYNC_STATUS_TOVF | TIMER_VSYNC_STATUS_TOVF); | ||
272 | |||
273 | } | ||
274 | |||
275 | static void bfin_lq035q1_init_timers(void) | ||
276 | { | ||
277 | |||
278 | bfin_lq035q1_stop_timers(); | ||
279 | |||
280 | set_gptimer_period(TIMER_HSYNC_id, H_PERIOD); | ||
281 | set_gptimer_pwidth(TIMER_HSYNC_id, H_PULSE); | ||
282 | set_gptimer_config(TIMER_HSYNC_id, TIMER_MODE_PWM | TIMER_PERIOD_CNT | | ||
283 | TIMER_TIN_SEL | TIMER_CLK_SEL| | ||
284 | TIMER_EMU_RUN); | ||
285 | |||
286 | set_gptimer_period(TIMER_VSYNC_id, V_PERIOD); | ||
287 | set_gptimer_pwidth(TIMER_VSYNC_id, V_PULSE); | ||
288 | set_gptimer_config(TIMER_VSYNC_id, TIMER_MODE_PWM | TIMER_PERIOD_CNT | | ||
289 | TIMER_TIN_SEL | TIMER_CLK_SEL | | ||
290 | TIMER_EMU_RUN); | ||
291 | |||
292 | } | ||
293 | |||
294 | static void bfin_lq035q1_config_dma(struct bfin_lq035q1fb_info *fbi) | ||
295 | { | ||
296 | |||
297 | set_dma_config(CH_PPI, | ||
298 | set_bfin_dma_config(DIR_READ, DMA_FLOW_AUTO, | ||
299 | INTR_DISABLE, DIMENSION_2D, | ||
300 | DATA_SIZE_16, | ||
301 | DMA_NOSYNC_KEEP_DMA_BUF)); | ||
302 | set_dma_x_count(CH_PPI, (LCD_X_RES * LCD_BPP) / DMA_BUS_SIZE); | ||
303 | set_dma_x_modify(CH_PPI, DMA_BUS_SIZE / 8); | ||
304 | set_dma_y_count(CH_PPI, V_LINES); | ||
305 | |||
306 | set_dma_y_modify(CH_PPI, DMA_BUS_SIZE / 8); | ||
307 | set_dma_start_addr(CH_PPI, (unsigned long)fbi->fb_buffer); | ||
308 | |||
309 | } | ||
310 | |||
311 | #if (CLOCKS_PER_PIX == 1) | ||
312 | static const u16 ppi0_req_16[] = {P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, | ||
313 | P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, | ||
314 | P_PPI0_D3, P_PPI0_D4, P_PPI0_D5, | ||
315 | P_PPI0_D6, P_PPI0_D7, P_PPI0_D8, | ||
316 | P_PPI0_D9, P_PPI0_D10, P_PPI0_D11, | ||
317 | P_PPI0_D12, P_PPI0_D13, P_PPI0_D14, | ||
318 | P_PPI0_D15, 0}; | ||
319 | #else | ||
320 | static const u16 ppi0_req_16[] = {P_PPI0_CLK, P_PPI0_FS1, P_PPI0_FS2, | ||
321 | P_PPI0_D0, P_PPI0_D1, P_PPI0_D2, | ||
322 | P_PPI0_D3, P_PPI0_D4, P_PPI0_D5, | ||
323 | P_PPI0_D6, P_PPI0_D7, 0}; | ||
324 | #endif | ||
325 | |||
326 | static inline void bfin_lq035q1_free_ports(void) | ||
327 | { | ||
328 | peripheral_free_list(ppi0_req_16); | ||
329 | if (ANOMALY_05000400) | ||
330 | gpio_free(P_IDENT(P_PPI0_FS3)); | ||
331 | } | ||
332 | |||
333 | static int __devinit bfin_lq035q1_request_ports(struct platform_device *pdev) | ||
334 | { | ||
335 | /* ANOMALY_05000400 - PPI Does Not Start Properly In Specific Mode: | ||
336 | * Drive PPI_FS3 Low | ||
337 | */ | ||
338 | if (ANOMALY_05000400) { | ||
339 | int ret = gpio_request(P_IDENT(P_PPI0_FS3), "PPI_FS3"); | ||
340 | if (ret) | ||
341 | return ret; | ||
342 | gpio_direction_output(P_IDENT(P_PPI0_FS3), 0); | ||
343 | } | ||
344 | |||
345 | if (peripheral_request_list(ppi0_req_16, DRIVER_NAME)) { | ||
346 | dev_err(&pdev->dev, "requesting peripherals failed\n"); | ||
347 | return -EFAULT; | ||
348 | } | ||
349 | |||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | static int bfin_lq035q1_fb_open(struct fb_info *info, int user) | ||
354 | { | ||
355 | struct bfin_lq035q1fb_info *fbi = info->par; | ||
356 | |||
357 | spin_lock(&fbi->lock); | ||
358 | fbi->lq035_open_cnt++; | ||
359 | |||
360 | if (fbi->lq035_open_cnt <= 1) { | ||
361 | |||
362 | bfin_lq035q1_disable_ppi(); | ||
363 | SSYNC(); | ||
364 | |||
365 | bfin_lq035q1_config_dma(fbi); | ||
366 | bfin_lq035q1_config_ppi(fbi); | ||
367 | bfin_lq035q1_init_timers(); | ||
368 | |||
369 | /* start dma */ | ||
370 | enable_dma(CH_PPI); | ||
371 | bfin_lq035q1_enable_ppi(); | ||
372 | bfin_lq035q1_start_timers(); | ||
373 | lq035q1_backlight(fbi, 1); | ||
374 | } | ||
375 | |||
376 | spin_unlock(&fbi->lock); | ||
377 | |||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | static int bfin_lq035q1_fb_release(struct fb_info *info, int user) | ||
382 | { | ||
383 | struct bfin_lq035q1fb_info *fbi = info->par; | ||
384 | |||
385 | spin_lock(&fbi->lock); | ||
386 | |||
387 | fbi->lq035_open_cnt--; | ||
388 | |||
389 | if (fbi->lq035_open_cnt <= 0) { | ||
390 | lq035q1_backlight(fbi, 0); | ||
391 | bfin_lq035q1_disable_ppi(); | ||
392 | SSYNC(); | ||
393 | disable_dma(CH_PPI); | ||
394 | bfin_lq035q1_stop_timers(); | ||
395 | } | ||
396 | |||
397 | spin_unlock(&fbi->lock); | ||
398 | |||
399 | return 0; | ||
400 | } | ||
401 | |||
402 | static int bfin_lq035q1_fb_check_var(struct fb_var_screeninfo *var, | ||
403 | struct fb_info *info) | ||
404 | { | ||
405 | switch (var->bits_per_pixel) { | ||
406 | #if (LCD_BPP == 24) | ||
407 | case 24:/* TRUECOLOUR, 16m */ | ||
408 | #else | ||
409 | case 16:/* DIRECTCOLOUR, 64k */ | ||
410 | #endif | ||
411 | var->red.offset = info->var.red.offset; | ||
412 | var->green.offset = info->var.green.offset; | ||
413 | var->blue.offset = info->var.blue.offset; | ||
414 | var->red.length = info->var.red.length; | ||
415 | var->green.length = info->var.green.length; | ||
416 | var->blue.length = info->var.blue.length; | ||
417 | var->transp.offset = 0; | ||
418 | var->transp.length = 0; | ||
419 | var->transp.msb_right = 0; | ||
420 | var->red.msb_right = 0; | ||
421 | var->green.msb_right = 0; | ||
422 | var->blue.msb_right = 0; | ||
423 | break; | ||
424 | default: | ||
425 | pr_debug("%s: depth not supported: %u BPP\n", __func__, | ||
426 | var->bits_per_pixel); | ||
427 | return -EINVAL; | ||
428 | } | ||
429 | |||
430 | if (info->var.xres != var->xres || info->var.yres != var->yres || | ||
431 | info->var.xres_virtual != var->xres_virtual || | ||
432 | info->var.yres_virtual != var->yres_virtual) { | ||
433 | pr_debug("%s: Resolution not supported: X%u x Y%u \n", | ||
434 | __func__, var->xres, var->yres); | ||
435 | return -EINVAL; | ||
436 | } | ||
437 | |||
438 | /* | ||
439 | * Memory limit | ||
440 | */ | ||
441 | |||
442 | if ((info->fix.line_length * var->yres_virtual) > info->fix.smem_len) { | ||
443 | pr_debug("%s: Memory Limit requested yres_virtual = %u\n", | ||
444 | __func__, var->yres_virtual); | ||
445 | return -ENOMEM; | ||
446 | } | ||
447 | |||
448 | |||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | int bfin_lq035q1_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) | ||
453 | { | ||
454 | if (nocursor) | ||
455 | return 0; | ||
456 | else | ||
457 | return -EINVAL; /* just to force soft_cursor() call */ | ||
458 | } | ||
459 | |||
460 | static int bfin_lq035q1_fb_setcolreg(u_int regno, u_int red, u_int green, | ||
461 | u_int blue, u_int transp, | ||
462 | struct fb_info *info) | ||
463 | { | ||
464 | if (regno >= BFIN_LCD_NBR_PALETTE_ENTRIES) | ||
465 | return -EINVAL; | ||
466 | |||
467 | if (info->var.grayscale) { | ||
468 | /* grayscale = 0.30*R + 0.59*G + 0.11*B */ | ||
469 | red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; | ||
470 | } | ||
471 | |||
472 | if (info->fix.visual == FB_VISUAL_TRUECOLOR) { | ||
473 | |||
474 | u32 value; | ||
475 | /* Place color in the pseudopalette */ | ||
476 | if (regno > 16) | ||
477 | return -EINVAL; | ||
478 | |||
479 | red >>= (16 - info->var.red.length); | ||
480 | green >>= (16 - info->var.green.length); | ||
481 | blue >>= (16 - info->var.blue.length); | ||
482 | |||
483 | value = (red << info->var.red.offset) | | ||
484 | (green << info->var.green.offset) | | ||
485 | (blue << info->var.blue.offset); | ||
486 | value &= 0xFFFFFF; | ||
487 | |||
488 | ((u32 *) (info->pseudo_palette))[regno] = value; | ||
489 | |||
490 | } | ||
491 | |||
492 | return 0; | ||
493 | } | ||
494 | |||
495 | static struct fb_ops bfin_lq035q1_fb_ops = { | ||
496 | .owner = THIS_MODULE, | ||
497 | .fb_open = bfin_lq035q1_fb_open, | ||
498 | .fb_release = bfin_lq035q1_fb_release, | ||
499 | .fb_check_var = bfin_lq035q1_fb_check_var, | ||
500 | .fb_fillrect = cfb_fillrect, | ||
501 | .fb_copyarea = cfb_copyarea, | ||
502 | .fb_imageblit = cfb_imageblit, | ||
503 | .fb_cursor = bfin_lq035q1_fb_cursor, | ||
504 | .fb_setcolreg = bfin_lq035q1_fb_setcolreg, | ||
505 | }; | ||
506 | |||
507 | static irqreturn_t bfin_lq035q1_irq_error(int irq, void *dev_id) | ||
508 | { | ||
509 | /*struct bfin_lq035q1fb_info *info = (struct bfin_lq035q1fb_info *)dev_id;*/ | ||
510 | |||
511 | u16 status = bfin_read_PPI_STATUS(); | ||
512 | bfin_write_PPI_STATUS(-1); | ||
513 | |||
514 | if (status) { | ||
515 | bfin_lq035q1_disable_ppi(); | ||
516 | disable_dma(CH_PPI); | ||
517 | |||
518 | /* start dma */ | ||
519 | enable_dma(CH_PPI); | ||
520 | bfin_lq035q1_enable_ppi(); | ||
521 | bfin_write_PPI_STATUS(-1); | ||
522 | } | ||
523 | |||
524 | return IRQ_HANDLED; | ||
525 | } | ||
526 | |||
527 | static int __devinit bfin_lq035q1_probe(struct platform_device *pdev) | ||
528 | { | ||
529 | struct bfin_lq035q1fb_info *info; | ||
530 | struct fb_info *fbinfo; | ||
531 | int ret; | ||
532 | |||
533 | ret = request_dma(CH_PPI, DRIVER_NAME"_CH_PPI"); | ||
534 | if (ret < 0) { | ||
535 | dev_err(&pdev->dev, "PPI DMA unavailable\n"); | ||
536 | goto out1; | ||
537 | } | ||
538 | |||
539 | fbinfo = framebuffer_alloc(sizeof(*info), &pdev->dev); | ||
540 | if (!fbinfo) { | ||
541 | ret = -ENOMEM; | ||
542 | goto out2; | ||
543 | } | ||
544 | |||
545 | info = fbinfo->par; | ||
546 | info->fb = fbinfo; | ||
547 | info->dev = &pdev->dev; | ||
548 | |||
549 | info->disp_info = pdev->dev.platform_data; | ||
550 | |||
551 | platform_set_drvdata(pdev, fbinfo); | ||
552 | |||
553 | strcpy(fbinfo->fix.id, DRIVER_NAME); | ||
554 | |||
555 | fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; | ||
556 | fbinfo->fix.type_aux = 0; | ||
557 | fbinfo->fix.xpanstep = 0; | ||
558 | fbinfo->fix.ypanstep = 0; | ||
559 | fbinfo->fix.ywrapstep = 0; | ||
560 | fbinfo->fix.accel = FB_ACCEL_NONE; | ||
561 | fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; | ||
562 | |||
563 | fbinfo->var.nonstd = 0; | ||
564 | fbinfo->var.activate = FB_ACTIVATE_NOW; | ||
565 | fbinfo->var.height = -1; | ||
566 | fbinfo->var.width = -1; | ||
567 | fbinfo->var.accel_flags = 0; | ||
568 | fbinfo->var.vmode = FB_VMODE_NONINTERLACED; | ||
569 | |||
570 | fbinfo->var.xres = LCD_X_RES; | ||
571 | fbinfo->var.xres_virtual = LCD_X_RES; | ||
572 | fbinfo->var.yres = LCD_Y_RES; | ||
573 | fbinfo->var.yres_virtual = LCD_Y_RES; | ||
574 | fbinfo->var.bits_per_pixel = LCD_BPP; | ||
575 | |||
576 | if (info->disp_info->mode & LQ035_BGR) { | ||
577 | #if (LCD_BPP == 24) | ||
578 | fbinfo->var.red.offset = 0; | ||
579 | fbinfo->var.green.offset = 8; | ||
580 | fbinfo->var.blue.offset = 16; | ||
581 | #else | ||
582 | fbinfo->var.red.offset = 0; | ||
583 | fbinfo->var.green.offset = 5; | ||
584 | fbinfo->var.blue.offset = 11; | ||
585 | #endif | ||
586 | } else { | ||
587 | #if (LCD_BPP == 24) | ||
588 | fbinfo->var.red.offset = 16; | ||
589 | fbinfo->var.green.offset = 8; | ||
590 | fbinfo->var.blue.offset = 0; | ||
591 | #else | ||
592 | fbinfo->var.red.offset = 11; | ||
593 | fbinfo->var.green.offset = 5; | ||
594 | fbinfo->var.blue.offset = 0; | ||
595 | #endif | ||
596 | } | ||
597 | |||
598 | fbinfo->var.transp.offset = 0; | ||
599 | |||
600 | #if (LCD_BPP == 24) | ||
601 | fbinfo->var.red.length = 8; | ||
602 | fbinfo->var.green.length = 8; | ||
603 | fbinfo->var.blue.length = 8; | ||
604 | #else | ||
605 | fbinfo->var.red.length = 5; | ||
606 | fbinfo->var.green.length = 6; | ||
607 | fbinfo->var.blue.length = 5; | ||
608 | #endif | ||
609 | |||
610 | fbinfo->var.transp.length = 0; | ||
611 | |||
612 | fbinfo->fix.smem_len = LCD_X_RES * LCD_Y_RES * LCD_BPP / 8 | ||
613 | + ACTIVE_VIDEO_MEM_OFFSET; | ||
614 | |||
615 | fbinfo->fix.line_length = fbinfo->var.xres_virtual * | ||
616 | fbinfo->var.bits_per_pixel / 8; | ||
617 | |||
618 | |||
619 | fbinfo->fbops = &bfin_lq035q1_fb_ops; | ||
620 | fbinfo->flags = FBINFO_FLAG_DEFAULT; | ||
621 | |||
622 | info->fb_buffer = | ||
623 | dma_alloc_coherent(NULL, fbinfo->fix.smem_len, &info->dma_handle, | ||
624 | GFP_KERNEL); | ||
625 | |||
626 | if (NULL == info->fb_buffer) { | ||
627 | dev_err(&pdev->dev, "couldn't allocate dma buffer\n"); | ||
628 | ret = -ENOMEM; | ||
629 | goto out3; | ||
630 | } | ||
631 | |||
632 | fbinfo->screen_base = (void *)info->fb_buffer + ACTIVE_VIDEO_MEM_OFFSET; | ||
633 | fbinfo->fix.smem_start = (int)info->fb_buffer + ACTIVE_VIDEO_MEM_OFFSET; | ||
634 | |||
635 | fbinfo->fbops = &bfin_lq035q1_fb_ops; | ||
636 | |||
637 | fbinfo->pseudo_palette = &info->pseudo_pal; | ||
638 | |||
639 | ret = fb_alloc_cmap(&fbinfo->cmap, BFIN_LCD_NBR_PALETTE_ENTRIES, 0); | ||
640 | if (ret < 0) { | ||
641 | dev_err(&pdev->dev, "failed to allocate colormap (%d entries)\n", | ||
642 | BFIN_LCD_NBR_PALETTE_ENTRIES); | ||
643 | goto out4; | ||
644 | } | ||
645 | |||
646 | ret = bfin_lq035q1_request_ports(pdev); | ||
647 | if (ret) { | ||
648 | dev_err(&pdev->dev, "couldn't request gpio port\n"); | ||
649 | goto out6; | ||
650 | } | ||
651 | |||
652 | info->irq = platform_get_irq(pdev, 0); | ||
653 | if (info->irq < 0) { | ||
654 | ret = -EINVAL; | ||
655 | goto out7; | ||
656 | } | ||
657 | |||
658 | ret = request_irq(info->irq, bfin_lq035q1_irq_error, IRQF_DISABLED, | ||
659 | DRIVER_NAME" PPI ERROR", info); | ||
660 | if (ret < 0) { | ||
661 | dev_err(&pdev->dev, "unable to request PPI ERROR IRQ\n"); | ||
662 | goto out7; | ||
663 | } | ||
664 | |||
665 | info->spidrv.driver.name = DRIVER_NAME"-spi"; | ||
666 | info->spidrv.probe = lq035q1_spidev_probe; | ||
667 | info->spidrv.remove = __devexit_p(lq035q1_spidev_remove); | ||
668 | info->spidrv.shutdown = lq035q1_spidev_shutdown; | ||
669 | info->spidrv.suspend = lq035q1_spidev_suspend; | ||
670 | info->spidrv.resume = lq035q1_spidev_resume; | ||
671 | |||
672 | ret = spi_register_driver(&info->spidrv); | ||
673 | if (ret < 0) { | ||
674 | dev_err(&pdev->dev, "couldn't register SPI Interface\n"); | ||
675 | goto out8; | ||
676 | } | ||
677 | |||
678 | if (info->disp_info->use_bl) { | ||
679 | ret = gpio_request(info->disp_info->gpio_bl, "LQ035 Backlight"); | ||
680 | |||
681 | if (ret) { | ||
682 | dev_err(&pdev->dev, "failed to request GPIO %d\n", | ||
683 | info->disp_info->gpio_bl); | ||
684 | goto out9; | ||
685 | } | ||
686 | gpio_direction_output(info->disp_info->gpio_bl, 0); | ||
687 | } | ||
688 | |||
689 | ret = register_framebuffer(fbinfo); | ||
690 | if (ret < 0) { | ||
691 | dev_err(&pdev->dev, "unable to register framebuffer\n"); | ||
692 | goto out10; | ||
693 | } | ||
694 | |||
695 | dev_info(&pdev->dev, "%dx%d %d-bit RGB FrameBuffer initialized\n", | ||
696 | LCD_X_RES, LCD_Y_RES, LCD_BPP); | ||
697 | |||
698 | return 0; | ||
699 | |||
700 | out10: | ||
701 | if (info->disp_info->use_bl) | ||
702 | gpio_free(info->disp_info->gpio_bl); | ||
703 | out9: | ||
704 | spi_unregister_driver(&info->spidrv); | ||
705 | out8: | ||
706 | free_irq(info->irq, info); | ||
707 | out7: | ||
708 | bfin_lq035q1_free_ports(); | ||
709 | out6: | ||
710 | fb_dealloc_cmap(&fbinfo->cmap); | ||
711 | out4: | ||
712 | dma_free_coherent(NULL, fbinfo->fix.smem_len, info->fb_buffer, | ||
713 | info->dma_handle); | ||
714 | out3: | ||
715 | framebuffer_release(fbinfo); | ||
716 | out2: | ||
717 | free_dma(CH_PPI); | ||
718 | out1: | ||
719 | platform_set_drvdata(pdev, NULL); | ||
720 | |||
721 | return ret; | ||
722 | } | ||
723 | |||
724 | static int __devexit bfin_lq035q1_remove(struct platform_device *pdev) | ||
725 | { | ||
726 | struct fb_info *fbinfo = platform_get_drvdata(pdev); | ||
727 | struct bfin_lq035q1fb_info *info = fbinfo->par; | ||
728 | |||
729 | if (info->disp_info->use_bl) | ||
730 | gpio_free(info->disp_info->gpio_bl); | ||
731 | |||
732 | spi_unregister_driver(&info->spidrv); | ||
733 | |||
734 | unregister_framebuffer(fbinfo); | ||
735 | |||
736 | free_dma(CH_PPI); | ||
737 | free_irq(info->irq, info); | ||
738 | |||
739 | if (info->fb_buffer != NULL) | ||
740 | dma_free_coherent(NULL, fbinfo->fix.smem_len, info->fb_buffer, | ||
741 | info->dma_handle); | ||
742 | |||
743 | fb_dealloc_cmap(&fbinfo->cmap); | ||
744 | |||
745 | bfin_lq035q1_free_ports(); | ||
746 | |||
747 | platform_set_drvdata(pdev, NULL); | ||
748 | framebuffer_release(fbinfo); | ||
749 | |||
750 | dev_info(&pdev->dev, "unregistered LCD driver\n"); | ||
751 | |||
752 | return 0; | ||
753 | } | ||
754 | |||
755 | #ifdef CONFIG_PM | ||
756 | static int bfin_lq035q1_suspend(struct device *dev) | ||
757 | { | ||
758 | struct fb_info *fbinfo = dev_get_drvdata(dev); | ||
759 | struct bfin_lq035q1fb_info *info = fbinfo->par; | ||
760 | |||
761 | if (info->lq035_open_cnt) { | ||
762 | lq035q1_backlight(info, 0); | ||
763 | bfin_lq035q1_disable_ppi(); | ||
764 | SSYNC(); | ||
765 | disable_dma(CH_PPI); | ||
766 | bfin_lq035q1_stop_timers(); | ||
767 | bfin_write_PPI_STATUS(-1); | ||
768 | } | ||
769 | |||
770 | return 0; | ||
771 | } | ||
772 | |||
773 | static int bfin_lq035q1_resume(struct device *dev) | ||
774 | { | ||
775 | struct fb_info *fbinfo = dev_get_drvdata(dev); | ||
776 | struct bfin_lq035q1fb_info *info = fbinfo->par; | ||
777 | |||
778 | if (info->lq035_open_cnt) { | ||
779 | bfin_lq035q1_disable_ppi(); | ||
780 | SSYNC(); | ||
781 | |||
782 | bfin_lq035q1_config_dma(info); | ||
783 | bfin_lq035q1_config_ppi(info); | ||
784 | bfin_lq035q1_init_timers(); | ||
785 | |||
786 | /* start dma */ | ||
787 | enable_dma(CH_PPI); | ||
788 | bfin_lq035q1_enable_ppi(); | ||
789 | bfin_lq035q1_start_timers(); | ||
790 | lq035q1_backlight(info, 1); | ||
791 | } | ||
792 | |||
793 | return 0; | ||
794 | } | ||
795 | |||
796 | static struct dev_pm_ops bfin_lq035q1_dev_pm_ops = { | ||
797 | .suspend = bfin_lq035q1_suspend, | ||
798 | .resume = bfin_lq035q1_resume, | ||
799 | }; | ||
800 | #endif | ||
801 | |||
802 | static struct platform_driver bfin_lq035q1_driver = { | ||
803 | .probe = bfin_lq035q1_probe, | ||
804 | .remove = __devexit_p(bfin_lq035q1_remove), | ||
805 | .driver = { | ||
806 | .name = DRIVER_NAME, | ||
807 | #ifdef CONFIG_PM | ||
808 | .pm = &bfin_lq035q1_dev_pm_ops, | ||
809 | #endif | ||
810 | }, | ||
811 | }; | ||
812 | |||
813 | static int __init bfin_lq035q1_driver_init(void) | ||
814 | { | ||
815 | return platform_driver_register(&bfin_lq035q1_driver); | ||
816 | } | ||
817 | module_init(bfin_lq035q1_driver_init); | ||
818 | |||
819 | static void __exit bfin_lq035q1_driver_cleanup(void) | ||
820 | { | ||
821 | platform_driver_unregister(&bfin_lq035q1_driver); | ||
822 | } | ||
823 | module_exit(bfin_lq035q1_driver_cleanup); | ||
824 | |||
825 | MODULE_DESCRIPTION("Blackfin TFT LCD Driver"); | ||
826 | MODULE_LICENSE("GPL"); | ||