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. https://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 <linux/unaligned.h>
41
42 /* Display specific information */
43 #define DPY_W 832
44 #define DPY_H 622
45
46 static int user_wfm_size;
47
48 /* frame differs from image. frame includes non-visible pixels */
49 struct epd_frame {
50 int fw; /* frame width */
51 int fh; /* frame height */
52 u16 config[4];
53 int wfm_size;
54 };
55
56 static 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
102 static 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
113 static 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 */
128 struct 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 */
calc_cksum(int start,int end,u8 * mem)148 static 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
calc_img_cksum(u16 * start,int length)159 static 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 */
load_waveform(u8 * mem,size_t size,int m,int t,struct metronomefb_par * par)170 static 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->device;
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
metronome_display_cmd(struct metronomefb_par * par)308 static 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
metronome_powerup_cmd(struct metronomefb_par * par)341 static 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(&par->metromem_cmd->args[i], 0,
358 (ARRAY_SIZE(par->metromem_cmd->args) - i) * 2);
359
360 par->metromem_cmd->csum = cs;
361
362 msleep(1);
363 par->board->set_rst(par, 1);
364
365 msleep(1);
366 par->board->set_stdby(par, 1);
367
368 return par->board->met_wait_event(par);
369 }
370
metronome_config_cmd(struct metronomefb_par * par)371 static int metronome_config_cmd(struct metronomefb_par *par)
372 {
373 /* setup config command
374 we can't immediately set the opcode since the controller
375 will try parse the command before we've set it all up */
376
377 memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config,
378 sizeof(epd_frame_table[par->dt].config));
379 /* the rest are 0 */
380 memset(&par->metromem_cmd->args[4], 0,
381 (ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2);
382
383 par->metromem_cmd->csum = 0xCC10;
384 par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4);
385 par->metromem_cmd->opcode = 0xCC10; /* config cmd */
386
387 return par->board->met_wait_event(par);
388 }
389
metronome_init_cmd(struct metronomefb_par * par)390 static int metronome_init_cmd(struct metronomefb_par *par)
391 {
392 int i;
393 u16 cs;
394
395 /* setup init command
396 we can't immediately set the opcode since the controller
397 will try parse the command before we've set it all up
398 so we just set cs here and set the opcode at the end */
399
400 cs = 0xCC20;
401
402 /* set the args ( 2 bytes ) for init */
403 i = 0;
404 par->metromem_cmd->args[i] = 0;
405 cs += par->metromem_cmd->args[i++];
406
407 /* the rest are 0 */
408 memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
409
410 par->metromem_cmd->csum = cs;
411 par->metromem_cmd->opcode = 0xCC20; /* init cmd */
412
413 return par->board->met_wait_event(par);
414 }
415
metronome_init_regs(struct metronomefb_par * par)416 static int metronome_init_regs(struct metronomefb_par *par)
417 {
418 int res;
419
420 res = par->board->setup_io(par);
421 if (res)
422 return res;
423
424 res = metronome_powerup_cmd(par);
425 if (res)
426 return res;
427
428 res = metronome_config_cmd(par);
429 if (res)
430 return res;
431
432 res = metronome_init_cmd(par);
433
434 return res;
435 }
436
metronomefb_dpy_update(struct metronomefb_par * par)437 static void metronomefb_dpy_update(struct metronomefb_par *par)
438 {
439 int fbsize;
440 u16 cksum;
441 unsigned char *buf = par->info->screen_buffer;
442
443 fbsize = par->info->fix.smem_len;
444 /* copy from vm to metromem */
445 memcpy(par->metromem_img, buf, fbsize);
446
447 cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2);
448 *((u16 *)(par->metromem_img) + fbsize/2) = cksum;
449 metronome_display_cmd(par);
450 }
451
metronomefb_dpy_update_page(struct metronomefb_par * par,int index)452 static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)
453 {
454 int i;
455 u16 csum = 0;
456 u16 *buf = (u16 *)(par->info->screen_buffer + index);
457 u16 *img = (u16 *)(par->metromem_img + index);
458
459 /* swizzle from vm to metromem and recalc cksum at the same time*/
460 for (i = 0; i < PAGE_SIZE/2; i++) {
461 *(img + i) = (buf[i] << 5) & 0xE0E0;
462 csum += *(img + i);
463 }
464 return csum;
465 }
466
467 /* this is called back from the deferred io workqueue */
metronomefb_dpy_deferred_io(struct fb_info * info,struct list_head * pagereflist)468 static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
469 {
470 u16 cksum;
471 struct fb_deferred_io_pageref *pageref;
472 struct metronomefb_par *par = info->par;
473
474 /* walk the written page list and swizzle the data */
475 list_for_each_entry(pageref, pagereflist, list) {
476 unsigned long pgoffset = pageref->offset >> PAGE_SHIFT;
477 cksum = metronomefb_dpy_update_page(par, pageref->offset);
478 par->metromem_img_csum -= par->csum_table[pgoffset];
479 par->csum_table[pgoffset] = cksum;
480 par->metromem_img_csum += cksum;
481 }
482
483 metronome_display_cmd(par);
484 }
485
metronomefb_defio_damage_range(struct fb_info * info,off_t off,size_t len)486 static void metronomefb_defio_damage_range(struct fb_info *info, off_t off, size_t len)
487 {
488 struct metronomefb_par *par = info->par;
489
490 metronomefb_dpy_update(par);
491 }
492
metronomefb_defio_damage_area(struct fb_info * info,u32 x,u32 y,u32 width,u32 height)493 static void metronomefb_defio_damage_area(struct fb_info *info, u32 x, u32 y,
494 u32 width, u32 height)
495 {
496 struct metronomefb_par *par = info->par;
497
498 metronomefb_dpy_update(par);
499 }
500
501 FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(metronomefb,
502 metronomefb_defio_damage_range,
503 metronomefb_defio_damage_area)
504
505 static const struct fb_ops metronomefb_ops = {
506 .owner = THIS_MODULE,
507 FB_DEFAULT_DEFERRED_OPS(metronomefb),
508 };
509
510 static struct fb_deferred_io metronomefb_defio = {
511 .delay = HZ,
512 .sort_pagereflist = true,
513 .deferred_io = metronomefb_dpy_deferred_io,
514 };
515
metronomefb_probe(struct platform_device * dev)516 static int metronomefb_probe(struct platform_device *dev)
517 {
518 struct fb_info *info;
519 struct metronome_board *board;
520 int retval = -ENOMEM;
521 int videomemorysize;
522 unsigned char *videomemory;
523 struct metronomefb_par *par;
524 const struct firmware *fw_entry;
525 int i;
526 int panel_type;
527 int fw, fh;
528 int epd_dt_index;
529
530 /* pick up board specific routines */
531 board = dev->dev.platform_data;
532 if (!board)
533 return -EINVAL;
534
535 /* try to count device specific driver, if can't, platform recalls */
536 if (!try_module_get(board->owner))
537 return -ENODEV;
538
539 info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev);
540 if (!info)
541 goto err;
542
543 /* we have two blocks of memory.
544 info->screen_buffer which is vm, and is the fb used by apps.
545 par->metromem which is physically contiguous memory and
546 contains the display controller commands, waveform,
547 processed image data and padding. this is the data pulled
548 by the device's LCD controller and pushed to Metronome.
549 the metromem memory is allocated by the board driver and
550 is provided to us */
551
552 panel_type = board->get_panel_type();
553 switch (panel_type) {
554 case 6:
555 epd_dt_index = 0;
556 break;
557 case 8:
558 epd_dt_index = 1;
559 break;
560 case 97:
561 epd_dt_index = 2;
562 break;
563 default:
564 dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n");
565 epd_dt_index = 0;
566 break;
567 }
568
569 fw = epd_frame_table[epd_dt_index].fw;
570 fh = epd_frame_table[epd_dt_index].fh;
571
572 /* we need to add a spare page because our csum caching scheme walks
573 * to the end of the page */
574 videomemorysize = PAGE_SIZE + (fw * fh);
575 videomemory = vzalloc(videomemorysize);
576 if (!videomemory)
577 goto err_fb_rel;
578
579 info->screen_buffer = videomemory;
580 info->fbops = &metronomefb_ops;
581
582 metronomefb_fix.line_length = fw;
583 metronomefb_var.xres = fw;
584 metronomefb_var.yres = fh;
585 metronomefb_var.xres_virtual = fw;
586 metronomefb_var.yres_virtual = fh;
587 info->var = metronomefb_var;
588 info->fix = metronomefb_fix;
589 info->fix.smem_len = videomemorysize;
590 par = info->par;
591 par->info = info;
592 par->board = board;
593 par->dt = epd_dt_index;
594 init_waitqueue_head(&par->waitq);
595
596 /* this table caches per page csum values. */
597 par->csum_table = vmalloc(videomemorysize/PAGE_SIZE);
598 if (!par->csum_table)
599 goto err_vfree;
600
601 /* the physical framebuffer that we use is setup by
602 * the platform device driver. It will provide us
603 * with cmd, wfm and image memory in a contiguous area. */
604 retval = board->setup_fb(par);
605 if (retval) {
606 dev_err(&dev->dev, "Failed to setup fb\n");
607 goto err_csum_table;
608 }
609
610 /* after this point we should have a framebuffer */
611 if ((!par->metromem_wfm) || (!par->metromem_img) ||
612 (!par->metromem_dma)) {
613 dev_err(&dev->dev, "fb access failure\n");
614 retval = -EINVAL;
615 goto err_csum_table;
616 }
617
618 info->fix.smem_start = par->metromem_dma;
619
620 /* load the waveform in. assume mode 3, temp 31 for now
621 a) request the waveform file from userspace
622 b) process waveform and decode into metromem */
623 retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev);
624 if (retval < 0) {
625 dev_err(&dev->dev, "Failed to get waveform\n");
626 goto err_csum_table;
627 }
628
629 retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31,
630 par);
631 release_firmware(fw_entry);
632 if (retval < 0) {
633 dev_err(&dev->dev, "Failed processing waveform\n");
634 goto err_csum_table;
635 }
636
637 retval = board->setup_irq(info);
638 if (retval)
639 goto err_csum_table;
640
641 retval = metronome_init_regs(par);
642 if (retval < 0)
643 goto err_free_irq;
644
645 info->flags = FBINFO_VIRTFB;
646
647 info->fbdefio = &metronomefb_defio;
648 fb_deferred_io_init(info);
649
650 retval = fb_alloc_cmap(&info->cmap, 8, 0);
651 if (retval < 0) {
652 dev_err(&dev->dev, "Failed to allocate colormap\n");
653 goto err_free_irq;
654 }
655
656 /* set cmap */
657 for (i = 0; i < 8; i++)
658 info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16;
659 memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8);
660 memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8);
661
662 retval = register_framebuffer(info);
663 if (retval < 0)
664 goto err_cmap;
665
666 platform_set_drvdata(dev, info);
667
668 dev_dbg(&dev->dev,
669 "fb%d: Metronome frame buffer device, using %dK of video"
670 " memory\n", info->node, videomemorysize >> 10);
671
672 return 0;
673
674 err_cmap:
675 fb_dealloc_cmap(&info->cmap);
676 err_free_irq:
677 board->cleanup(par);
678 err_csum_table:
679 vfree(par->csum_table);
680 err_vfree:
681 vfree(videomemory);
682 err_fb_rel:
683 framebuffer_release(info);
684 err:
685 module_put(board->owner);
686 return retval;
687 }
688
metronomefb_remove(struct platform_device * dev)689 static void metronomefb_remove(struct platform_device *dev)
690 {
691 struct fb_info *info = platform_get_drvdata(dev);
692
693 if (info) {
694 struct metronomefb_par *par = info->par;
695
696 unregister_framebuffer(info);
697 fb_deferred_io_cleanup(info);
698 fb_dealloc_cmap(&info->cmap);
699 par->board->cleanup(par);
700 vfree(par->csum_table);
701 vfree(info->screen_buffer);
702 module_put(par->board->owner);
703 dev_dbg(&dev->dev, "calling release\n");
704 framebuffer_release(info);
705 }
706 }
707
708 static struct platform_driver metronomefb_driver = {
709 .probe = metronomefb_probe,
710 .remove = metronomefb_remove,
711 .driver = {
712 .name = "metronomefb",
713 },
714 };
715 module_platform_driver(metronomefb_driver);
716
717 module_param(user_wfm_size, uint, 0);
718 MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size");
719
720 MODULE_DESCRIPTION("fbdev driver for Metronome controller");
721 MODULE_AUTHOR("Jaya Kumar");
722 MODULE_LICENSE("GPL");
723
724 MODULE_FIRMWARE("metronome.wbf");
725