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 <asm/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 */ 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 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 */ 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->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 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 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 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 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 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 437 static void metronomefb_dpy_update(struct metronomefb_par *par) 438 { 439 int fbsize; 440 u16 cksum; 441 unsigned char *buf = (unsigned char __force *)par->info->screen_base; 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 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 __force *)(par->info->screen_base + 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 */ 468 static void metronomefb_dpy_deferred_io(struct fb_info *info, 469 struct list_head *pagelist) 470 { 471 u16 cksum; 472 struct page *cur; 473 struct fb_deferred_io *fbdefio = info->fbdefio; 474 struct metronomefb_par *par = info->par; 475 476 /* walk the written page list and swizzle the data */ 477 list_for_each_entry(cur, &fbdefio->pagelist, lru) { 478 cksum = metronomefb_dpy_update_page(par, 479 (cur->index << PAGE_SHIFT)); 480 par->metromem_img_csum -= par->csum_table[cur->index]; 481 par->csum_table[cur->index] = cksum; 482 par->metromem_img_csum += cksum; 483 } 484 485 metronome_display_cmd(par); 486 } 487 488 static void metronomefb_fillrect(struct fb_info *info, 489 const struct fb_fillrect *rect) 490 { 491 struct metronomefb_par *par = info->par; 492 493 sys_fillrect(info, rect); 494 metronomefb_dpy_update(par); 495 } 496 497 static void metronomefb_copyarea(struct fb_info *info, 498 const struct fb_copyarea *area) 499 { 500 struct metronomefb_par *par = info->par; 501 502 sys_copyarea(info, area); 503 metronomefb_dpy_update(par); 504 } 505 506 static void metronomefb_imageblit(struct fb_info *info, 507 const struct fb_image *image) 508 { 509 struct metronomefb_par *par = info->par; 510 511 sys_imageblit(info, image); 512 metronomefb_dpy_update(par); 513 } 514 515 /* 516 * this is the slow path from userspace. they can seek and write to 517 * the fb. it is based on fb_sys_write 518 */ 519 static ssize_t metronomefb_write(struct fb_info *info, const char __user *buf, 520 size_t count, loff_t *ppos) 521 { 522 struct metronomefb_par *par = info->par; 523 unsigned long p = *ppos; 524 void *dst; 525 int err = 0; 526 unsigned long total_size; 527 528 if (info->state != FBINFO_STATE_RUNNING) 529 return -EPERM; 530 531 total_size = info->fix.smem_len; 532 533 if (p > total_size) 534 return -EFBIG; 535 536 if (count > total_size) { 537 err = -EFBIG; 538 count = total_size; 539 } 540 541 if (count + p > total_size) { 542 if (!err) 543 err = -ENOSPC; 544 545 count = total_size - p; 546 } 547 548 dst = (void __force *)(info->screen_base + p); 549 550 if (copy_from_user(dst, buf, count)) 551 err = -EFAULT; 552 553 if (!err) 554 *ppos += count; 555 556 metronomefb_dpy_update(par); 557 558 return (err) ? err : count; 559 } 560 561 static const struct fb_ops metronomefb_ops = { 562 .owner = THIS_MODULE, 563 .fb_write = metronomefb_write, 564 .fb_fillrect = metronomefb_fillrect, 565 .fb_copyarea = metronomefb_copyarea, 566 .fb_imageblit = metronomefb_imageblit, 567 }; 568 569 static struct fb_deferred_io metronomefb_defio = { 570 .delay = HZ, 571 .sort_pagelist = true, 572 .deferred_io = metronomefb_dpy_deferred_io, 573 }; 574 575 static int metronomefb_probe(struct platform_device *dev) 576 { 577 struct fb_info *info; 578 struct metronome_board *board; 579 int retval = -ENOMEM; 580 int videomemorysize; 581 unsigned char *videomemory; 582 struct metronomefb_par *par; 583 const struct firmware *fw_entry; 584 int i; 585 int panel_type; 586 int fw, fh; 587 int epd_dt_index; 588 589 /* pick up board specific routines */ 590 board = dev->dev.platform_data; 591 if (!board) 592 return -EINVAL; 593 594 /* try to count device specific driver, if can't, platform recalls */ 595 if (!try_module_get(board->owner)) 596 return -ENODEV; 597 598 info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev); 599 if (!info) 600 goto err; 601 602 /* we have two blocks of memory. 603 info->screen_base which is vm, and is the fb used by apps. 604 par->metromem which is physically contiguous memory and 605 contains the display controller commands, waveform, 606 processed image data and padding. this is the data pulled 607 by the device's LCD controller and pushed to Metronome. 608 the metromem memory is allocated by the board driver and 609 is provided to us */ 610 611 panel_type = board->get_panel_type(); 612 switch (panel_type) { 613 case 6: 614 epd_dt_index = 0; 615 break; 616 case 8: 617 epd_dt_index = 1; 618 break; 619 case 97: 620 epd_dt_index = 2; 621 break; 622 default: 623 dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n"); 624 epd_dt_index = 0; 625 break; 626 } 627 628 fw = epd_frame_table[epd_dt_index].fw; 629 fh = epd_frame_table[epd_dt_index].fh; 630 631 /* we need to add a spare page because our csum caching scheme walks 632 * to the end of the page */ 633 videomemorysize = PAGE_SIZE + (fw * fh); 634 videomemory = vzalloc(videomemorysize); 635 if (!videomemory) 636 goto err_fb_rel; 637 638 info->screen_base = (char __force __iomem *)videomemory; 639 info->fbops = &metronomefb_ops; 640 641 metronomefb_fix.line_length = fw; 642 metronomefb_var.xres = fw; 643 metronomefb_var.yres = fh; 644 metronomefb_var.xres_virtual = fw; 645 metronomefb_var.yres_virtual = fh; 646 info->var = metronomefb_var; 647 info->fix = metronomefb_fix; 648 info->fix.smem_len = videomemorysize; 649 par = info->par; 650 par->info = info; 651 par->board = board; 652 par->dt = epd_dt_index; 653 init_waitqueue_head(&par->waitq); 654 655 /* this table caches per page csum values. */ 656 par->csum_table = vmalloc(videomemorysize/PAGE_SIZE); 657 if (!par->csum_table) 658 goto err_vfree; 659 660 /* the physical framebuffer that we use is setup by 661 * the platform device driver. It will provide us 662 * with cmd, wfm and image memory in a contiguous area. */ 663 retval = board->setup_fb(par); 664 if (retval) { 665 dev_err(&dev->dev, "Failed to setup fb\n"); 666 goto err_csum_table; 667 } 668 669 /* after this point we should have a framebuffer */ 670 if ((!par->metromem_wfm) || (!par->metromem_img) || 671 (!par->metromem_dma)) { 672 dev_err(&dev->dev, "fb access failure\n"); 673 retval = -EINVAL; 674 goto err_csum_table; 675 } 676 677 info->fix.smem_start = par->metromem_dma; 678 679 /* load the waveform in. assume mode 3, temp 31 for now 680 a) request the waveform file from userspace 681 b) process waveform and decode into metromem */ 682 retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev); 683 if (retval < 0) { 684 dev_err(&dev->dev, "Failed to get waveform\n"); 685 goto err_csum_table; 686 } 687 688 retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31, 689 par); 690 release_firmware(fw_entry); 691 if (retval < 0) { 692 dev_err(&dev->dev, "Failed processing waveform\n"); 693 goto err_csum_table; 694 } 695 696 retval = board->setup_irq(info); 697 if (retval) 698 goto err_csum_table; 699 700 retval = metronome_init_regs(par); 701 if (retval < 0) 702 goto err_free_irq; 703 704 info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; 705 706 info->fbdefio = &metronomefb_defio; 707 fb_deferred_io_init(info); 708 709 retval = fb_alloc_cmap(&info->cmap, 8, 0); 710 if (retval < 0) { 711 dev_err(&dev->dev, "Failed to allocate colormap\n"); 712 goto err_free_irq; 713 } 714 715 /* set cmap */ 716 for (i = 0; i < 8; i++) 717 info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16; 718 memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8); 719 memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8); 720 721 retval = register_framebuffer(info); 722 if (retval < 0) 723 goto err_cmap; 724 725 platform_set_drvdata(dev, info); 726 727 dev_dbg(&dev->dev, 728 "fb%d: Metronome frame buffer device, using %dK of video" 729 " memory\n", info->node, videomemorysize >> 10); 730 731 return 0; 732 733 err_cmap: 734 fb_dealloc_cmap(&info->cmap); 735 err_free_irq: 736 board->cleanup(par); 737 err_csum_table: 738 vfree(par->csum_table); 739 err_vfree: 740 vfree(videomemory); 741 err_fb_rel: 742 framebuffer_release(info); 743 err: 744 module_put(board->owner); 745 return retval; 746 } 747 748 static int metronomefb_remove(struct platform_device *dev) 749 { 750 struct fb_info *info = platform_get_drvdata(dev); 751 752 if (info) { 753 struct metronomefb_par *par = info->par; 754 755 unregister_framebuffer(info); 756 fb_deferred_io_cleanup(info); 757 fb_dealloc_cmap(&info->cmap); 758 par->board->cleanup(par); 759 vfree(par->csum_table); 760 vfree((void __force *)info->screen_base); 761 module_put(par->board->owner); 762 dev_dbg(&dev->dev, "calling release\n"); 763 framebuffer_release(info); 764 } 765 return 0; 766 } 767 768 static struct platform_driver metronomefb_driver = { 769 .probe = metronomefb_probe, 770 .remove = metronomefb_remove, 771 .driver = { 772 .name = "metronomefb", 773 }, 774 }; 775 module_platform_driver(metronomefb_driver); 776 777 module_param(user_wfm_size, uint, 0); 778 MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size"); 779 780 MODULE_DESCRIPTION("fbdev driver for Metronome controller"); 781 MODULE_AUTHOR("Jaya Kumar"); 782 MODULE_LICENSE("GPL"); 783