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