1f7018c21STomi Valkeinen /*
2f7018c21STomi Valkeinen * linux/drivers/video/metronomefb.c -- FB driver for Metronome controller
3f7018c21STomi Valkeinen *
4f7018c21STomi Valkeinen * Copyright (C) 2008, Jaya Kumar
5f7018c21STomi Valkeinen *
6f7018c21STomi Valkeinen * This file is subject to the terms and conditions of the GNU General Public
7f7018c21STomi Valkeinen * License. See the file COPYING in the main directory of this archive for
8f7018c21STomi Valkeinen * more details.
9f7018c21STomi Valkeinen *
10f7018c21STomi Valkeinen * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.
11f7018c21STomi Valkeinen *
12f7018c21STomi Valkeinen * This work was made possible by help and equipment support from E-Ink
137c7b2a35SAlexander A. Klimov * Corporation. https://www.eink.com/
14f7018c21STomi Valkeinen *
15f7018c21STomi Valkeinen * This driver is written to be used with the Metronome display controller.
16f7018c21STomi Valkeinen * It is intended to be architecture independent. A board specific driver
17f7018c21STomi Valkeinen * must be used to perform all the physical IO interactions. An example
18f7018c21STomi Valkeinen * is provided as am200epd.c
19f7018c21STomi Valkeinen *
20f7018c21STomi Valkeinen */
21f7018c21STomi Valkeinen #include <linux/module.h>
22f7018c21STomi Valkeinen #include <linux/kernel.h>
23f7018c21STomi Valkeinen #include <linux/errno.h>
24f7018c21STomi Valkeinen #include <linux/string.h>
25f7018c21STomi Valkeinen #include <linux/mm.h>
26f7018c21STomi Valkeinen #include <linux/vmalloc.h>
27f7018c21STomi Valkeinen #include <linux/delay.h>
28f7018c21STomi Valkeinen #include <linux/interrupt.h>
29f7018c21STomi Valkeinen #include <linux/fb.h>
30f7018c21STomi Valkeinen #include <linux/init.h>
31f7018c21STomi Valkeinen #include <linux/platform_device.h>
32f7018c21STomi Valkeinen #include <linux/list.h>
33f7018c21STomi Valkeinen #include <linux/firmware.h>
34f7018c21STomi Valkeinen #include <linux/dma-mapping.h>
35f7018c21STomi Valkeinen #include <linux/uaccess.h>
36f7018c21STomi Valkeinen #include <linux/irq.h>
37f7018c21STomi Valkeinen
38f7018c21STomi Valkeinen #include <video/metronomefb.h>
39f7018c21STomi Valkeinen
40f7018c21STomi Valkeinen #include <asm/unaligned.h>
41f7018c21STomi Valkeinen
42f7018c21STomi Valkeinen /* Display specific information */
43f7018c21STomi Valkeinen #define DPY_W 832
44f7018c21STomi Valkeinen #define DPY_H 622
45f7018c21STomi Valkeinen
46f7018c21STomi Valkeinen static int user_wfm_size;
47f7018c21STomi Valkeinen
48f7018c21STomi Valkeinen /* frame differs from image. frame includes non-visible pixels */
49f7018c21STomi Valkeinen struct epd_frame {
50f7018c21STomi Valkeinen int fw; /* frame width */
51f7018c21STomi Valkeinen int fh; /* frame height */
52f7018c21STomi Valkeinen u16 config[4];
53f7018c21STomi Valkeinen int wfm_size;
54f7018c21STomi Valkeinen };
55f7018c21STomi Valkeinen
56f7018c21STomi Valkeinen static struct epd_frame epd_frame_table[] = {
57f7018c21STomi Valkeinen {
58f7018c21STomi Valkeinen .fw = 832,
59f7018c21STomi Valkeinen .fh = 622,
60f7018c21STomi Valkeinen .config = {
61f7018c21STomi Valkeinen 15 /* sdlew */
62f7018c21STomi Valkeinen | 2 << 8 /* sdosz */
63f7018c21STomi Valkeinen | 0 << 11 /* sdor */
64f7018c21STomi Valkeinen | 0 << 12 /* sdces */
65f7018c21STomi Valkeinen | 0 << 15, /* sdcer */
66f7018c21STomi Valkeinen 42 /* gdspl */
67f7018c21STomi Valkeinen | 1 << 8 /* gdr1 */
68f7018c21STomi Valkeinen | 1 << 9 /* sdshr */
69f7018c21STomi Valkeinen | 0 << 15, /* gdspp */
70f7018c21STomi Valkeinen 18 /* gdspw */
71f7018c21STomi Valkeinen | 0 << 15, /* dispc */
72f7018c21STomi Valkeinen 599 /* vdlc */
73f7018c21STomi Valkeinen | 0 << 11 /* dsi */
74f7018c21STomi Valkeinen | 0 << 12, /* dsic */
75f7018c21STomi Valkeinen },
76f7018c21STomi Valkeinen .wfm_size = 47001,
77f7018c21STomi Valkeinen },
78f7018c21STomi Valkeinen {
79f7018c21STomi Valkeinen .fw = 1088,
80f7018c21STomi Valkeinen .fh = 791,
81f7018c21STomi Valkeinen .config = {
82f7018c21STomi Valkeinen 0x0104,
83f7018c21STomi Valkeinen 0x031f,
84f7018c21STomi Valkeinen 0x0088,
85f7018c21STomi Valkeinen 0x02ff,
86f7018c21STomi Valkeinen },
87f7018c21STomi Valkeinen .wfm_size = 46770,
88f7018c21STomi Valkeinen },
89f7018c21STomi Valkeinen {
90f7018c21STomi Valkeinen .fw = 1200,
91f7018c21STomi Valkeinen .fh = 842,
92f7018c21STomi Valkeinen .config = {
93f7018c21STomi Valkeinen 0x0101,
94f7018c21STomi Valkeinen 0x030e,
95f7018c21STomi Valkeinen 0x0012,
96f7018c21STomi Valkeinen 0x0280,
97f7018c21STomi Valkeinen },
98f7018c21STomi Valkeinen .wfm_size = 46770,
99f7018c21STomi Valkeinen },
100f7018c21STomi Valkeinen };
101f7018c21STomi Valkeinen
102f7018c21STomi Valkeinen static struct fb_fix_screeninfo metronomefb_fix = {
103f7018c21STomi Valkeinen .id = "metronomefb",
104f7018c21STomi Valkeinen .type = FB_TYPE_PACKED_PIXELS,
105f7018c21STomi Valkeinen .visual = FB_VISUAL_STATIC_PSEUDOCOLOR,
106f7018c21STomi Valkeinen .xpanstep = 0,
107f7018c21STomi Valkeinen .ypanstep = 0,
108f7018c21STomi Valkeinen .ywrapstep = 0,
109f7018c21STomi Valkeinen .line_length = DPY_W,
110f7018c21STomi Valkeinen .accel = FB_ACCEL_NONE,
111f7018c21STomi Valkeinen };
112f7018c21STomi Valkeinen
113f7018c21STomi Valkeinen static struct fb_var_screeninfo metronomefb_var = {
114f7018c21STomi Valkeinen .xres = DPY_W,
115f7018c21STomi Valkeinen .yres = DPY_H,
116f7018c21STomi Valkeinen .xres_virtual = DPY_W,
117f7018c21STomi Valkeinen .yres_virtual = DPY_H,
118f7018c21STomi Valkeinen .bits_per_pixel = 8,
119f7018c21STomi Valkeinen .grayscale = 1,
120f7018c21STomi Valkeinen .nonstd = 1,
121f7018c21STomi Valkeinen .red = { 4, 3, 0 },
122f7018c21STomi Valkeinen .green = { 0, 0, 0 },
123f7018c21STomi Valkeinen .blue = { 0, 0, 0 },
124f7018c21STomi Valkeinen .transp = { 0, 0, 0 },
125f7018c21STomi Valkeinen };
126f7018c21STomi Valkeinen
127f7018c21STomi Valkeinen /* the waveform structure that is coming from userspace firmware */
128f7018c21STomi Valkeinen struct waveform_hdr {
129f7018c21STomi Valkeinen u8 stuff[32];
130f7018c21STomi Valkeinen
131f7018c21STomi Valkeinen u8 wmta[3];
132f7018c21STomi Valkeinen u8 fvsn;
133f7018c21STomi Valkeinen
134f7018c21STomi Valkeinen u8 luts;
135f7018c21STomi Valkeinen u8 mc;
136f7018c21STomi Valkeinen u8 trc;
137f7018c21STomi Valkeinen u8 stuff3;
138f7018c21STomi Valkeinen
139f7018c21STomi Valkeinen u8 endb;
140f7018c21STomi Valkeinen u8 swtb;
141f7018c21STomi Valkeinen u8 stuff2a[2];
142f7018c21STomi Valkeinen
143f7018c21STomi Valkeinen u8 stuff2b[3];
144f7018c21STomi Valkeinen u8 wfm_cs;
145f7018c21STomi Valkeinen } __attribute__ ((packed));
146f7018c21STomi Valkeinen
147f7018c21STomi Valkeinen /* main metronomefb functions */
calc_cksum(int start,int end,u8 * mem)148f7018c21STomi Valkeinen static u8 calc_cksum(int start, int end, u8 *mem)
149f7018c21STomi Valkeinen {
150f7018c21STomi Valkeinen u8 tmp = 0;
151f7018c21STomi Valkeinen int i;
152f7018c21STomi Valkeinen
153f7018c21STomi Valkeinen for (i = start; i < end; i++)
154f7018c21STomi Valkeinen tmp += mem[i];
155f7018c21STomi Valkeinen
156f7018c21STomi Valkeinen return tmp;
157f7018c21STomi Valkeinen }
158f7018c21STomi Valkeinen
calc_img_cksum(u16 * start,int length)159f7018c21STomi Valkeinen static u16 calc_img_cksum(u16 *start, int length)
160f7018c21STomi Valkeinen {
161f7018c21STomi Valkeinen u16 tmp = 0;
162f7018c21STomi Valkeinen
163f7018c21STomi Valkeinen while (length--)
164f7018c21STomi Valkeinen tmp += *start++;
165f7018c21STomi Valkeinen
166f7018c21STomi Valkeinen return tmp;
167f7018c21STomi Valkeinen }
168f7018c21STomi Valkeinen
169f7018c21STomi Valkeinen /* here we decode the incoming waveform file and populate metromem */
load_waveform(u8 * mem,size_t size,int m,int t,struct metronomefb_par * par)170f7018c21STomi Valkeinen static int load_waveform(u8 *mem, size_t size, int m, int t,
171f7018c21STomi Valkeinen struct metronomefb_par *par)
172f7018c21STomi Valkeinen {
173f7018c21STomi Valkeinen int tta;
174f7018c21STomi Valkeinen int wmta;
175f7018c21STomi Valkeinen int trn = 0;
176f7018c21STomi Valkeinen int i;
177f7018c21STomi Valkeinen unsigned char v;
178f7018c21STomi Valkeinen u8 cksum;
179f7018c21STomi Valkeinen int cksum_idx;
180f7018c21STomi Valkeinen int wfm_idx, owfm_idx;
181f7018c21STomi Valkeinen int mem_idx = 0;
182f7018c21STomi Valkeinen struct waveform_hdr *wfm_hdr;
183f7018c21STomi Valkeinen u8 *metromem = par->metromem_wfm;
1848ad76089SThomas Zimmermann struct device *dev = par->info->device;
185f7018c21STomi Valkeinen
186f7018c21STomi Valkeinen if (user_wfm_size)
187f7018c21STomi Valkeinen epd_frame_table[par->dt].wfm_size = user_wfm_size;
188f7018c21STomi Valkeinen
189f7018c21STomi Valkeinen if (size != epd_frame_table[par->dt].wfm_size) {
1905b5e0928SAlexey Dobriyan dev_err(dev, "Error: unexpected size %zd != %d\n", size,
191f7018c21STomi Valkeinen epd_frame_table[par->dt].wfm_size);
192f7018c21STomi Valkeinen return -EINVAL;
193f7018c21STomi Valkeinen }
194f7018c21STomi Valkeinen
195f7018c21STomi Valkeinen wfm_hdr = (struct waveform_hdr *) mem;
196f7018c21STomi Valkeinen
197f7018c21STomi Valkeinen if (wfm_hdr->fvsn != 1) {
198f7018c21STomi Valkeinen dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn);
199f7018c21STomi Valkeinen return -EINVAL;
200f7018c21STomi Valkeinen }
201f7018c21STomi Valkeinen if (wfm_hdr->luts != 0) {
202f7018c21STomi Valkeinen dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts);
203f7018c21STomi Valkeinen return -EINVAL;
204f7018c21STomi Valkeinen }
205f7018c21STomi Valkeinen cksum = calc_cksum(32, 47, mem);
206f7018c21STomi Valkeinen if (cksum != wfm_hdr->wfm_cs) {
207f7018c21STomi Valkeinen dev_err(dev, "Error: bad cksum %x != %x\n", cksum,
208f7018c21STomi Valkeinen wfm_hdr->wfm_cs);
209f7018c21STomi Valkeinen return -EINVAL;
210f7018c21STomi Valkeinen }
211f7018c21STomi Valkeinen wfm_hdr->mc += 1;
212f7018c21STomi Valkeinen wfm_hdr->trc += 1;
213f7018c21STomi Valkeinen for (i = 0; i < 5; i++) {
214f7018c21STomi Valkeinen if (*(wfm_hdr->stuff2a + i) != 0) {
215f7018c21STomi Valkeinen dev_err(dev, "Error: unexpected value in padding\n");
216f7018c21STomi Valkeinen return -EINVAL;
217f7018c21STomi Valkeinen }
218f7018c21STomi Valkeinen }
219f7018c21STomi Valkeinen
220f7018c21STomi Valkeinen /* calculating trn. trn is something used to index into
221f7018c21STomi Valkeinen the waveform. presumably selecting the right one for the
222f7018c21STomi Valkeinen desired temperature. it works out the offset of the first
223f7018c21STomi Valkeinen v that exceeds the specified temperature */
224f7018c21STomi Valkeinen if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size)
225f7018c21STomi Valkeinen return -EINVAL;
226f7018c21STomi Valkeinen
227f7018c21STomi Valkeinen for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) {
228f7018c21STomi Valkeinen if (mem[i] > t) {
229f7018c21STomi Valkeinen trn = i - sizeof(*wfm_hdr) - 1;
230f7018c21STomi Valkeinen break;
231f7018c21STomi Valkeinen }
232f7018c21STomi Valkeinen }
233f7018c21STomi Valkeinen
234f7018c21STomi Valkeinen /* check temperature range table checksum */
235f7018c21STomi Valkeinen cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1;
2365c820b80SDan Carpenter if (cksum_idx >= size)
237f7018c21STomi Valkeinen return -EINVAL;
238f7018c21STomi Valkeinen cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem);
239f7018c21STomi Valkeinen if (cksum != mem[cksum_idx]) {
240f7018c21STomi Valkeinen dev_err(dev, "Error: bad temperature range table cksum"
241f7018c21STomi Valkeinen " %x != %x\n", cksum, mem[cksum_idx]);
242f7018c21STomi Valkeinen return -EINVAL;
243f7018c21STomi Valkeinen }
244f7018c21STomi Valkeinen
245f7018c21STomi Valkeinen /* check waveform mode table address checksum */
246f7018c21STomi Valkeinen wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF;
247f7018c21STomi Valkeinen cksum_idx = wmta + m*4 + 3;
2485c820b80SDan Carpenter if (cksum_idx >= size)
249f7018c21STomi Valkeinen return -EINVAL;
250f7018c21STomi Valkeinen cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
251f7018c21STomi Valkeinen if (cksum != mem[cksum_idx]) {
252f7018c21STomi Valkeinen dev_err(dev, "Error: bad mode table address cksum"
253f7018c21STomi Valkeinen " %x != %x\n", cksum, mem[cksum_idx]);
254f7018c21STomi Valkeinen return -EINVAL;
255f7018c21STomi Valkeinen }
256f7018c21STomi Valkeinen
257f7018c21STomi Valkeinen /* check waveform temperature table address checksum */
258f7018c21STomi Valkeinen tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF;
259f7018c21STomi Valkeinen cksum_idx = tta + trn*4 + 3;
2605c820b80SDan Carpenter if (cksum_idx >= size)
261f7018c21STomi Valkeinen return -EINVAL;
262f7018c21STomi Valkeinen cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
263f7018c21STomi Valkeinen if (cksum != mem[cksum_idx]) {
264f7018c21STomi Valkeinen dev_err(dev, "Error: bad temperature table address cksum"
265f7018c21STomi Valkeinen " %x != %x\n", cksum, mem[cksum_idx]);
266f7018c21STomi Valkeinen return -EINVAL;
267f7018c21STomi Valkeinen }
268f7018c21STomi Valkeinen
269f7018c21STomi Valkeinen /* here we do the real work of putting the waveform into the
270f7018c21STomi Valkeinen metromem buffer. this does runlength decoding of the waveform */
271f7018c21STomi Valkeinen wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF;
272f7018c21STomi Valkeinen owfm_idx = wfm_idx;
2735c820b80SDan Carpenter if (wfm_idx >= size)
274f7018c21STomi Valkeinen return -EINVAL;
275f7018c21STomi Valkeinen while (wfm_idx < size) {
276f7018c21STomi Valkeinen unsigned char rl;
277f7018c21STomi Valkeinen v = mem[wfm_idx++];
278f7018c21STomi Valkeinen if (v == wfm_hdr->swtb) {
279f7018c21STomi Valkeinen while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) &&
280f7018c21STomi Valkeinen wfm_idx < size)
281f7018c21STomi Valkeinen metromem[mem_idx++] = v;
282f7018c21STomi Valkeinen
283f7018c21STomi Valkeinen continue;
284f7018c21STomi Valkeinen }
285f7018c21STomi Valkeinen
286f7018c21STomi Valkeinen if (v == wfm_hdr->endb)
287f7018c21STomi Valkeinen break;
288f7018c21STomi Valkeinen
289f7018c21STomi Valkeinen rl = mem[wfm_idx++];
290f7018c21STomi Valkeinen for (i = 0; i <= rl; i++)
291f7018c21STomi Valkeinen metromem[mem_idx++] = v;
292f7018c21STomi Valkeinen }
293f7018c21STomi Valkeinen
294f7018c21STomi Valkeinen cksum_idx = wfm_idx;
2955c820b80SDan Carpenter if (cksum_idx >= size)
296f7018c21STomi Valkeinen return -EINVAL;
297f7018c21STomi Valkeinen cksum = calc_cksum(owfm_idx, cksum_idx, mem);
298f7018c21STomi Valkeinen if (cksum != mem[cksum_idx]) {
299f7018c21STomi Valkeinen dev_err(dev, "Error: bad waveform data cksum"
300f7018c21STomi Valkeinen " %x != %x\n", cksum, mem[cksum_idx]);
301f7018c21STomi Valkeinen return -EINVAL;
302f7018c21STomi Valkeinen }
303f7018c21STomi Valkeinen par->frame_count = (mem_idx/64);
304f7018c21STomi Valkeinen
305f7018c21STomi Valkeinen return 0;
306f7018c21STomi Valkeinen }
307f7018c21STomi Valkeinen
metronome_display_cmd(struct metronomefb_par * par)308f7018c21STomi Valkeinen static int metronome_display_cmd(struct metronomefb_par *par)
309f7018c21STomi Valkeinen {
310f7018c21STomi Valkeinen int i;
311f7018c21STomi Valkeinen u16 cs;
312f7018c21STomi Valkeinen u16 opcode;
313f7018c21STomi Valkeinen static u8 borderval;
314f7018c21STomi Valkeinen
315f7018c21STomi Valkeinen /* setup display command
316f7018c21STomi Valkeinen we can't immediately set the opcode since the controller
317f7018c21STomi Valkeinen will try parse the command before we've set it all up
318f7018c21STomi Valkeinen so we just set cs here and set the opcode at the end */
319f7018c21STomi Valkeinen
320f7018c21STomi Valkeinen if (par->metromem_cmd->opcode == 0xCC40)
321f7018c21STomi Valkeinen opcode = cs = 0xCC41;
322f7018c21STomi Valkeinen else
323f7018c21STomi Valkeinen opcode = cs = 0xCC40;
324f7018c21STomi Valkeinen
325f7018c21STomi Valkeinen /* set the args ( 2 bytes ) for display */
326f7018c21STomi Valkeinen i = 0;
327f7018c21STomi Valkeinen par->metromem_cmd->args[i] = 1 << 3 /* border update */
328f7018c21STomi Valkeinen | ((borderval++ % 4) & 0x0F) << 4
329f7018c21STomi Valkeinen | (par->frame_count - 1) << 8;
330f7018c21STomi Valkeinen cs += par->metromem_cmd->args[i++];
331f7018c21STomi Valkeinen
332f7018c21STomi Valkeinen /* the rest are 0 */
333f7018c21STomi Valkeinen memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
334f7018c21STomi Valkeinen
335f7018c21STomi Valkeinen par->metromem_cmd->csum = cs;
336f7018c21STomi Valkeinen par->metromem_cmd->opcode = opcode; /* display cmd */
337f7018c21STomi Valkeinen
338f7018c21STomi Valkeinen return par->board->met_wait_event_intr(par);
339f7018c21STomi Valkeinen }
340f7018c21STomi Valkeinen
metronome_powerup_cmd(struct metronomefb_par * par)341f7018c21STomi Valkeinen static int metronome_powerup_cmd(struct metronomefb_par *par)
342f7018c21STomi Valkeinen {
343f7018c21STomi Valkeinen int i;
344f7018c21STomi Valkeinen u16 cs;
345f7018c21STomi Valkeinen
346f7018c21STomi Valkeinen /* setup power up command */
347f7018c21STomi Valkeinen par->metromem_cmd->opcode = 0x1234; /* pwr up pseudo cmd */
348f7018c21STomi Valkeinen cs = par->metromem_cmd->opcode;
349f7018c21STomi Valkeinen
350f7018c21STomi Valkeinen /* set pwr1,2,3 to 1024 */
351f7018c21STomi Valkeinen for (i = 0; i < 3; i++) {
352f7018c21STomi Valkeinen par->metromem_cmd->args[i] = 1024;
353f7018c21STomi Valkeinen cs += par->metromem_cmd->args[i];
354f7018c21STomi Valkeinen }
355f7018c21STomi Valkeinen
356f7018c21STomi Valkeinen /* the rest are 0 */
35717f2e8e1SDan Carpenter memset(&par->metromem_cmd->args[i], 0,
35817f2e8e1SDan Carpenter (ARRAY_SIZE(par->metromem_cmd->args) - i) * 2);
359f7018c21STomi Valkeinen
360f7018c21STomi Valkeinen par->metromem_cmd->csum = cs;
361f7018c21STomi Valkeinen
362f7018c21STomi Valkeinen msleep(1);
363f7018c21STomi Valkeinen par->board->set_rst(par, 1);
364f7018c21STomi Valkeinen
365f7018c21STomi Valkeinen msleep(1);
366f7018c21STomi Valkeinen par->board->set_stdby(par, 1);
367f7018c21STomi Valkeinen
368f7018c21STomi Valkeinen return par->board->met_wait_event(par);
369f7018c21STomi Valkeinen }
370f7018c21STomi Valkeinen
metronome_config_cmd(struct metronomefb_par * par)371f7018c21STomi Valkeinen static int metronome_config_cmd(struct metronomefb_par *par)
372f7018c21STomi Valkeinen {
373f7018c21STomi Valkeinen /* setup config command
374f7018c21STomi Valkeinen we can't immediately set the opcode since the controller
375f7018c21STomi Valkeinen will try parse the command before we've set it all up */
376f7018c21STomi Valkeinen
377f7018c21STomi Valkeinen memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config,
378f7018c21STomi Valkeinen sizeof(epd_frame_table[par->dt].config));
379f7018c21STomi Valkeinen /* the rest are 0 */
38017f2e8e1SDan Carpenter memset(&par->metromem_cmd->args[4], 0,
38117f2e8e1SDan Carpenter (ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2);
382f7018c21STomi Valkeinen
383f7018c21STomi Valkeinen par->metromem_cmd->csum = 0xCC10;
384f7018c21STomi Valkeinen par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4);
385f7018c21STomi Valkeinen par->metromem_cmd->opcode = 0xCC10; /* config cmd */
386f7018c21STomi Valkeinen
387f7018c21STomi Valkeinen return par->board->met_wait_event(par);
388f7018c21STomi Valkeinen }
389f7018c21STomi Valkeinen
metronome_init_cmd(struct metronomefb_par * par)390f7018c21STomi Valkeinen static int metronome_init_cmd(struct metronomefb_par *par)
391f7018c21STomi Valkeinen {
392f7018c21STomi Valkeinen int i;
393f7018c21STomi Valkeinen u16 cs;
394f7018c21STomi Valkeinen
395f7018c21STomi Valkeinen /* setup init command
396f7018c21STomi Valkeinen we can't immediately set the opcode since the controller
397f7018c21STomi Valkeinen will try parse the command before we've set it all up
398f7018c21STomi Valkeinen so we just set cs here and set the opcode at the end */
399f7018c21STomi Valkeinen
400f7018c21STomi Valkeinen cs = 0xCC20;
401f7018c21STomi Valkeinen
402f7018c21STomi Valkeinen /* set the args ( 2 bytes ) for init */
403f7018c21STomi Valkeinen i = 0;
404f7018c21STomi Valkeinen par->metromem_cmd->args[i] = 0;
405f7018c21STomi Valkeinen cs += par->metromem_cmd->args[i++];
406f7018c21STomi Valkeinen
407f7018c21STomi Valkeinen /* the rest are 0 */
408f7018c21STomi Valkeinen memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
409f7018c21STomi Valkeinen
410f7018c21STomi Valkeinen par->metromem_cmd->csum = cs;
411f7018c21STomi Valkeinen par->metromem_cmd->opcode = 0xCC20; /* init cmd */
412f7018c21STomi Valkeinen
413f7018c21STomi Valkeinen return par->board->met_wait_event(par);
414f7018c21STomi Valkeinen }
415f7018c21STomi Valkeinen
metronome_init_regs(struct metronomefb_par * par)416f7018c21STomi Valkeinen static int metronome_init_regs(struct metronomefb_par *par)
417f7018c21STomi Valkeinen {
418f7018c21STomi Valkeinen int res;
419f7018c21STomi Valkeinen
420f7018c21STomi Valkeinen res = par->board->setup_io(par);
421f7018c21STomi Valkeinen if (res)
422f7018c21STomi Valkeinen return res;
423f7018c21STomi Valkeinen
424f7018c21STomi Valkeinen res = metronome_powerup_cmd(par);
425f7018c21STomi Valkeinen if (res)
426f7018c21STomi Valkeinen return res;
427f7018c21STomi Valkeinen
428f7018c21STomi Valkeinen res = metronome_config_cmd(par);
429f7018c21STomi Valkeinen if (res)
430f7018c21STomi Valkeinen return res;
431f7018c21STomi Valkeinen
432f7018c21STomi Valkeinen res = metronome_init_cmd(par);
433f7018c21STomi Valkeinen
434f7018c21STomi Valkeinen return res;
435f7018c21STomi Valkeinen }
436f7018c21STomi Valkeinen
metronomefb_dpy_update(struct metronomefb_par * par)437f7018c21STomi Valkeinen static void metronomefb_dpy_update(struct metronomefb_par *par)
438f7018c21STomi Valkeinen {
439f7018c21STomi Valkeinen int fbsize;
440f7018c21STomi Valkeinen u16 cksum;
44186718782SThomas Zimmermann unsigned char *buf = par->info->screen_buffer;
442f7018c21STomi Valkeinen
443f7018c21STomi Valkeinen fbsize = par->info->fix.smem_len;
444f7018c21STomi Valkeinen /* copy from vm to metromem */
445f7018c21STomi Valkeinen memcpy(par->metromem_img, buf, fbsize);
446f7018c21STomi Valkeinen
447f7018c21STomi Valkeinen cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2);
448f7018c21STomi Valkeinen *((u16 *)(par->metromem_img) + fbsize/2) = cksum;
449f7018c21STomi Valkeinen metronome_display_cmd(par);
450f7018c21STomi Valkeinen }
451f7018c21STomi Valkeinen
metronomefb_dpy_update_page(struct metronomefb_par * par,int index)452f7018c21STomi Valkeinen static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)
453f7018c21STomi Valkeinen {
454f7018c21STomi Valkeinen int i;
455f7018c21STomi Valkeinen u16 csum = 0;
45686718782SThomas Zimmermann u16 *buf = (u16 *)(par->info->screen_buffer + index);
457f7018c21STomi Valkeinen u16 *img = (u16 *)(par->metromem_img + index);
458f7018c21STomi Valkeinen
459f7018c21STomi Valkeinen /* swizzle from vm to metromem and recalc cksum at the same time*/
460f7018c21STomi Valkeinen for (i = 0; i < PAGE_SIZE/2; i++) {
461f7018c21STomi Valkeinen *(img + i) = (buf[i] << 5) & 0xE0E0;
462f7018c21STomi Valkeinen csum += *(img + i);
463f7018c21STomi Valkeinen }
464f7018c21STomi Valkeinen return csum;
465f7018c21STomi Valkeinen }
466f7018c21STomi Valkeinen
467f7018c21STomi Valkeinen /* this is called back from the deferred io workqueue */
metronomefb_dpy_deferred_io(struct fb_info * info,struct list_head * pagereflist)468e80eec1bSThomas Zimmermann static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
469f7018c21STomi Valkeinen {
470f7018c21STomi Valkeinen u16 cksum;
47156c134f7SThomas Zimmermann struct fb_deferred_io_pageref *pageref;
472f7018c21STomi Valkeinen struct metronomefb_par *par = info->par;
473f7018c21STomi Valkeinen
474f7018c21STomi Valkeinen /* walk the written page list and swizzle the data */
475e80eec1bSThomas Zimmermann list_for_each_entry(pageref, pagereflist, list) {
476e2d8b428SThomas Zimmermann unsigned long pgoffset = pageref->offset >> PAGE_SHIFT;
477e2d8b428SThomas Zimmermann cksum = metronomefb_dpy_update_page(par, pageref->offset);
478e2d8b428SThomas Zimmermann par->metromem_img_csum -= par->csum_table[pgoffset];
479e2d8b428SThomas Zimmermann par->csum_table[pgoffset] = cksum;
480f7018c21STomi Valkeinen par->metromem_img_csum += cksum;
481f7018c21STomi Valkeinen }
482f7018c21STomi Valkeinen
483f7018c21STomi Valkeinen metronome_display_cmd(par);
484f7018c21STomi Valkeinen }
485f7018c21STomi Valkeinen
metronomefb_defio_damage_range(struct fb_info * info,off_t off,size_t len)486692411e5SThomas Zimmermann static void metronomefb_defio_damage_range(struct fb_info *info, off_t off, size_t len)
487f7018c21STomi Valkeinen {
488f7018c21STomi Valkeinen struct metronomefb_par *par = info->par;
489f7018c21STomi Valkeinen
490f7018c21STomi Valkeinen metronomefb_dpy_update(par);
491f7018c21STomi Valkeinen }
492f7018c21STomi Valkeinen
metronomefb_defio_damage_area(struct fb_info * info,u32 x,u32 y,u32 width,u32 height)493692411e5SThomas Zimmermann static void metronomefb_defio_damage_area(struct fb_info *info, u32 x, u32 y,
494692411e5SThomas Zimmermann u32 width, u32 height)
495f7018c21STomi Valkeinen {
496f7018c21STomi Valkeinen struct metronomefb_par *par = info->par;
497f7018c21STomi Valkeinen
498f7018c21STomi Valkeinen metronomefb_dpy_update(par);
499f7018c21STomi Valkeinen }
500f7018c21STomi Valkeinen
501*744d35d3SThomas Zimmermann FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(metronomefb,
502692411e5SThomas Zimmermann metronomefb_defio_damage_range,
503692411e5SThomas Zimmermann metronomefb_defio_damage_area)
504f7018c21STomi Valkeinen
5058a48ac33SJani Nikula static const struct fb_ops metronomefb_ops = {
506f7018c21STomi Valkeinen .owner = THIS_MODULE,
507692411e5SThomas Zimmermann FB_DEFAULT_DEFERRED_OPS(metronomefb),
508f7018c21STomi Valkeinen };
509f7018c21STomi Valkeinen
510f7018c21STomi Valkeinen static struct fb_deferred_io metronomefb_defio = {
511f7018c21STomi Valkeinen .delay = HZ,
512e80eec1bSThomas Zimmermann .sort_pagereflist = true,
513f7018c21STomi Valkeinen .deferred_io = metronomefb_dpy_deferred_io,
514f7018c21STomi Valkeinen };
515f7018c21STomi Valkeinen
metronomefb_probe(struct platform_device * dev)516f7018c21STomi Valkeinen static int metronomefb_probe(struct platform_device *dev)
517f7018c21STomi Valkeinen {
518f7018c21STomi Valkeinen struct fb_info *info;
519f7018c21STomi Valkeinen struct metronome_board *board;
520f7018c21STomi Valkeinen int retval = -ENOMEM;
521f7018c21STomi Valkeinen int videomemorysize;
522f7018c21STomi Valkeinen unsigned char *videomemory;
523f7018c21STomi Valkeinen struct metronomefb_par *par;
524f7018c21STomi Valkeinen const struct firmware *fw_entry;
525f7018c21STomi Valkeinen int i;
526f7018c21STomi Valkeinen int panel_type;
527f7018c21STomi Valkeinen int fw, fh;
528f7018c21STomi Valkeinen int epd_dt_index;
529f7018c21STomi Valkeinen
530f7018c21STomi Valkeinen /* pick up board specific routines */
531f7018c21STomi Valkeinen board = dev->dev.platform_data;
532f7018c21STomi Valkeinen if (!board)
533f7018c21STomi Valkeinen return -EINVAL;
534f7018c21STomi Valkeinen
535f7018c21STomi Valkeinen /* try to count device specific driver, if can't, platform recalls */
536f7018c21STomi Valkeinen if (!try_module_get(board->owner))
537f7018c21STomi Valkeinen return -ENODEV;
538f7018c21STomi Valkeinen
539f7018c21STomi Valkeinen info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev);
540f7018c21STomi Valkeinen if (!info)
541f7018c21STomi Valkeinen goto err;
542f7018c21STomi Valkeinen
543f7018c21STomi Valkeinen /* we have two blocks of memory.
54486718782SThomas Zimmermann info->screen_buffer which is vm, and is the fb used by apps.
545f7018c21STomi Valkeinen par->metromem which is physically contiguous memory and
546f7018c21STomi Valkeinen contains the display controller commands, waveform,
547f7018c21STomi Valkeinen processed image data and padding. this is the data pulled
548f7018c21STomi Valkeinen by the device's LCD controller and pushed to Metronome.
549f7018c21STomi Valkeinen the metromem memory is allocated by the board driver and
550f7018c21STomi Valkeinen is provided to us */
551f7018c21STomi Valkeinen
552f7018c21STomi Valkeinen panel_type = board->get_panel_type();
553f7018c21STomi Valkeinen switch (panel_type) {
554f7018c21STomi Valkeinen case 6:
555f7018c21STomi Valkeinen epd_dt_index = 0;
556f7018c21STomi Valkeinen break;
557f7018c21STomi Valkeinen case 8:
558f7018c21STomi Valkeinen epd_dt_index = 1;
559f7018c21STomi Valkeinen break;
560f7018c21STomi Valkeinen case 97:
561f7018c21STomi Valkeinen epd_dt_index = 2;
562f7018c21STomi Valkeinen break;
563f7018c21STomi Valkeinen default:
564f7018c21STomi Valkeinen dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n");
565f7018c21STomi Valkeinen epd_dt_index = 0;
566f7018c21STomi Valkeinen break;
567f7018c21STomi Valkeinen }
568f7018c21STomi Valkeinen
569f7018c21STomi Valkeinen fw = epd_frame_table[epd_dt_index].fw;
570f7018c21STomi Valkeinen fh = epd_frame_table[epd_dt_index].fh;
571f7018c21STomi Valkeinen
572f7018c21STomi Valkeinen /* we need to add a spare page because our csum caching scheme walks
573f7018c21STomi Valkeinen * to the end of the page */
574f7018c21STomi Valkeinen videomemorysize = PAGE_SIZE + (fw * fh);
575f7018c21STomi Valkeinen videomemory = vzalloc(videomemorysize);
576f7018c21STomi Valkeinen if (!videomemory)
577f7018c21STomi Valkeinen goto err_fb_rel;
578f7018c21STomi Valkeinen
57986718782SThomas Zimmermann info->screen_buffer = videomemory;
580f7018c21STomi Valkeinen info->fbops = &metronomefb_ops;
581f7018c21STomi Valkeinen
582f7018c21STomi Valkeinen metronomefb_fix.line_length = fw;
583f7018c21STomi Valkeinen metronomefb_var.xres = fw;
584f7018c21STomi Valkeinen metronomefb_var.yres = fh;
585f7018c21STomi Valkeinen metronomefb_var.xres_virtual = fw;
586f7018c21STomi Valkeinen metronomefb_var.yres_virtual = fh;
587f7018c21STomi Valkeinen info->var = metronomefb_var;
588f7018c21STomi Valkeinen info->fix = metronomefb_fix;
589f7018c21STomi Valkeinen info->fix.smem_len = videomemorysize;
590f7018c21STomi Valkeinen par = info->par;
591f7018c21STomi Valkeinen par->info = info;
592f7018c21STomi Valkeinen par->board = board;
593f7018c21STomi Valkeinen par->dt = epd_dt_index;
594f7018c21STomi Valkeinen init_waitqueue_head(&par->waitq);
595f7018c21STomi Valkeinen
596f7018c21STomi Valkeinen /* this table caches per page csum values. */
597f7018c21STomi Valkeinen par->csum_table = vmalloc(videomemorysize/PAGE_SIZE);
598f7018c21STomi Valkeinen if (!par->csum_table)
599f7018c21STomi Valkeinen goto err_vfree;
600f7018c21STomi Valkeinen
601f7018c21STomi Valkeinen /* the physical framebuffer that we use is setup by
602f7018c21STomi Valkeinen * the platform device driver. It will provide us
603f7018c21STomi Valkeinen * with cmd, wfm and image memory in a contiguous area. */
604f7018c21STomi Valkeinen retval = board->setup_fb(par);
605f7018c21STomi Valkeinen if (retval) {
606f7018c21STomi Valkeinen dev_err(&dev->dev, "Failed to setup fb\n");
607f7018c21STomi Valkeinen goto err_csum_table;
608f7018c21STomi Valkeinen }
609f7018c21STomi Valkeinen
610f7018c21STomi Valkeinen /* after this point we should have a framebuffer */
611f7018c21STomi Valkeinen if ((!par->metromem_wfm) || (!par->metromem_img) ||
612f7018c21STomi Valkeinen (!par->metromem_dma)) {
613f7018c21STomi Valkeinen dev_err(&dev->dev, "fb access failure\n");
614f7018c21STomi Valkeinen retval = -EINVAL;
615f7018c21STomi Valkeinen goto err_csum_table;
616f7018c21STomi Valkeinen }
617f7018c21STomi Valkeinen
618f7018c21STomi Valkeinen info->fix.smem_start = par->metromem_dma;
619f7018c21STomi Valkeinen
620f7018c21STomi Valkeinen /* load the waveform in. assume mode 3, temp 31 for now
621f7018c21STomi Valkeinen a) request the waveform file from userspace
622f7018c21STomi Valkeinen b) process waveform and decode into metromem */
623f7018c21STomi Valkeinen retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev);
624f7018c21STomi Valkeinen if (retval < 0) {
625f7018c21STomi Valkeinen dev_err(&dev->dev, "Failed to get waveform\n");
626f7018c21STomi Valkeinen goto err_csum_table;
627f7018c21STomi Valkeinen }
628f7018c21STomi Valkeinen
629f7018c21STomi Valkeinen retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31,
630f7018c21STomi Valkeinen par);
631f7018c21STomi Valkeinen release_firmware(fw_entry);
632f7018c21STomi Valkeinen if (retval < 0) {
633f7018c21STomi Valkeinen dev_err(&dev->dev, "Failed processing waveform\n");
634f7018c21STomi Valkeinen goto err_csum_table;
635f7018c21STomi Valkeinen }
636f7018c21STomi Valkeinen
637f7018c21STomi Valkeinen retval = board->setup_irq(info);
638f7018c21STomi Valkeinen if (retval)
639f7018c21STomi Valkeinen goto err_csum_table;
640f7018c21STomi Valkeinen
641f7018c21STomi Valkeinen retval = metronome_init_regs(par);
642f7018c21STomi Valkeinen if (retval < 0)
643f7018c21STomi Valkeinen goto err_free_irq;
644f7018c21STomi Valkeinen
6458a4675ebSThomas Zimmermann info->flags = FBINFO_VIRTFB;
646f7018c21STomi Valkeinen
647f7018c21STomi Valkeinen info->fbdefio = &metronomefb_defio;
648f7018c21STomi Valkeinen fb_deferred_io_init(info);
649f7018c21STomi Valkeinen
650f7018c21STomi Valkeinen retval = fb_alloc_cmap(&info->cmap, 8, 0);
651f7018c21STomi Valkeinen if (retval < 0) {
652f7018c21STomi Valkeinen dev_err(&dev->dev, "Failed to allocate colormap\n");
653f7018c21STomi Valkeinen goto err_free_irq;
654f7018c21STomi Valkeinen }
655f7018c21STomi Valkeinen
656f7018c21STomi Valkeinen /* set cmap */
657f7018c21STomi Valkeinen for (i = 0; i < 8; i++)
658f7018c21STomi Valkeinen info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16;
659f7018c21STomi Valkeinen memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8);
660f7018c21STomi Valkeinen memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8);
661f7018c21STomi Valkeinen
662f7018c21STomi Valkeinen retval = register_framebuffer(info);
663f7018c21STomi Valkeinen if (retval < 0)
664f7018c21STomi Valkeinen goto err_cmap;
665f7018c21STomi Valkeinen
666f7018c21STomi Valkeinen platform_set_drvdata(dev, info);
667f7018c21STomi Valkeinen
668f7018c21STomi Valkeinen dev_dbg(&dev->dev,
669f7018c21STomi Valkeinen "fb%d: Metronome frame buffer device, using %dK of video"
670f7018c21STomi Valkeinen " memory\n", info->node, videomemorysize >> 10);
671f7018c21STomi Valkeinen
672f7018c21STomi Valkeinen return 0;
673f7018c21STomi Valkeinen
674f7018c21STomi Valkeinen err_cmap:
675f7018c21STomi Valkeinen fb_dealloc_cmap(&info->cmap);
676f7018c21STomi Valkeinen err_free_irq:
677f7018c21STomi Valkeinen board->cleanup(par);
678f7018c21STomi Valkeinen err_csum_table:
679f7018c21STomi Valkeinen vfree(par->csum_table);
680f7018c21STomi Valkeinen err_vfree:
681f7018c21STomi Valkeinen vfree(videomemory);
682f7018c21STomi Valkeinen err_fb_rel:
683f7018c21STomi Valkeinen framebuffer_release(info);
684f7018c21STomi Valkeinen err:
685f7018c21STomi Valkeinen module_put(board->owner);
686f7018c21STomi Valkeinen return retval;
687f7018c21STomi Valkeinen }
688f7018c21STomi Valkeinen
metronomefb_remove(struct platform_device * dev)689d0513776SUwe Kleine-König static void metronomefb_remove(struct platform_device *dev)
690f7018c21STomi Valkeinen {
691f7018c21STomi Valkeinen struct fb_info *info = platform_get_drvdata(dev);
692f7018c21STomi Valkeinen
693f7018c21STomi Valkeinen if (info) {
694f7018c21STomi Valkeinen struct metronomefb_par *par = info->par;
695f7018c21STomi Valkeinen
696f7018c21STomi Valkeinen unregister_framebuffer(info);
697f7018c21STomi Valkeinen fb_deferred_io_cleanup(info);
698f7018c21STomi Valkeinen fb_dealloc_cmap(&info->cmap);
699f7018c21STomi Valkeinen par->board->cleanup(par);
700f7018c21STomi Valkeinen vfree(par->csum_table);
70186718782SThomas Zimmermann vfree(info->screen_buffer);
702f7018c21STomi Valkeinen module_put(par->board->owner);
703f7018c21STomi Valkeinen dev_dbg(&dev->dev, "calling release\n");
704f7018c21STomi Valkeinen framebuffer_release(info);
705f7018c21STomi Valkeinen }
706f7018c21STomi Valkeinen }
707f7018c21STomi Valkeinen
708f7018c21STomi Valkeinen static struct platform_driver metronomefb_driver = {
709f7018c21STomi Valkeinen .probe = metronomefb_probe,
710d0513776SUwe Kleine-König .remove_new = metronomefb_remove,
711f7018c21STomi Valkeinen .driver = {
712f7018c21STomi Valkeinen .name = "metronomefb",
713f7018c21STomi Valkeinen },
714f7018c21STomi Valkeinen };
715f7018c21STomi Valkeinen module_platform_driver(metronomefb_driver);
716f7018c21STomi Valkeinen
717f7018c21STomi Valkeinen module_param(user_wfm_size, uint, 0);
718f7018c21STomi Valkeinen MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size");
719f7018c21STomi Valkeinen
720f7018c21STomi Valkeinen MODULE_DESCRIPTION("fbdev driver for Metronome controller");
721f7018c21STomi Valkeinen MODULE_AUTHOR("Jaya Kumar");
722f7018c21STomi Valkeinen MODULE_LICENSE("GPL");
723f338beb6SJuerg Haefliger
724f338beb6SJuerg Haefliger MODULE_FIRMWARE("metronome.wbf");
725