xref: /openbmc/linux/drivers/video/fbdev/metronomefb.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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