xref: /OK3568_Linux_fs/kernel/drivers/video/fbdev/metronomefb.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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