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