diff options
Diffstat (limited to 'drivers/hid/hid-picolcd.c')
-rw-r--r-- | drivers/hid/hid-picolcd.c | 199 |
1 files changed, 166 insertions, 33 deletions
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 7aabf65c48ef..346f0e34987e 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c | |||
@@ -127,6 +127,26 @@ static const struct fb_var_screeninfo picolcdfb_var = { | |||
127 | .height = 26, | 127 | .height = 26, |
128 | .bits_per_pixel = 1, | 128 | .bits_per_pixel = 1, |
129 | .grayscale = 1, | 129 | .grayscale = 1, |
130 | .red = { | ||
131 | .offset = 0, | ||
132 | .length = 1, | ||
133 | .msb_right = 0, | ||
134 | }, | ||
135 | .green = { | ||
136 | .offset = 0, | ||
137 | .length = 1, | ||
138 | .msb_right = 0, | ||
139 | }, | ||
140 | .blue = { | ||
141 | .offset = 0, | ||
142 | .length = 1, | ||
143 | .msb_right = 0, | ||
144 | }, | ||
145 | .transp = { | ||
146 | .offset = 0, | ||
147 | .length = 0, | ||
148 | .msb_right = 0, | ||
149 | }, | ||
130 | }; | 150 | }; |
131 | #endif /* CONFIG_HID_PICOLCD_FB */ | 151 | #endif /* CONFIG_HID_PICOLCD_FB */ |
132 | 152 | ||
@@ -188,6 +208,7 @@ struct picolcd_data { | |||
188 | /* Framebuffer stuff */ | 208 | /* Framebuffer stuff */ |
189 | u8 fb_update_rate; | 209 | u8 fb_update_rate; |
190 | u8 fb_bpp; | 210 | u8 fb_bpp; |
211 | u8 fb_force; | ||
191 | u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ | 212 | u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ |
192 | u8 *fb_bitmap; /* framebuffer */ | 213 | u8 *fb_bitmap; /* framebuffer */ |
193 | struct fb_info *fb_info; | 214 | struct fb_info *fb_info; |
@@ -346,7 +367,7 @@ static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp, | |||
346 | const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32; | 367 | const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32; |
347 | for (i = 0; i < 64; i++) { | 368 | for (i = 0; i < 64; i++) { |
348 | tdata[i] <<= 1; | 369 | tdata[i] <<= 1; |
349 | tdata[i] |= (bdata[i/8] >> (7 - i % 8)) & 0x01; | 370 | tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01; |
350 | } | 371 | } |
351 | } | 372 | } |
352 | } else if (bpp == 8) { | 373 | } else if (bpp == 8) { |
@@ -399,13 +420,10 @@ static int picolcd_fb_reset(struct picolcd_data *data, int clear) | |||
399 | 420 | ||
400 | if (data->fb_bitmap) { | 421 | if (data->fb_bitmap) { |
401 | if (clear) { | 422 | if (clear) { |
402 | memset(data->fb_vbitmap, 0xff, PICOLCDFB_SIZE); | 423 | memset(data->fb_vbitmap, 0, PICOLCDFB_SIZE); |
403 | memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); | 424 | memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); |
404 | } else { | ||
405 | /* invert 1 byte in each tile to force resend */ | ||
406 | for (i = 0; i < PICOLCDFB_SIZE; i += 64) | ||
407 | data->fb_vbitmap[i] = ~data->fb_vbitmap[i]; | ||
408 | } | 425 | } |
426 | data->fb_force = 1; | ||
409 | } | 427 | } |
410 | 428 | ||
411 | /* schedule first output of framebuffer */ | 429 | /* schedule first output of framebuffer */ |
@@ -421,6 +439,9 @@ static void picolcd_fb_update(struct picolcd_data *data) | |||
421 | int chip, tile, n; | 439 | int chip, tile, n; |
422 | unsigned long flags; | 440 | unsigned long flags; |
423 | 441 | ||
442 | if (!data) | ||
443 | return; | ||
444 | |||
424 | spin_lock_irqsave(&data->lock, flags); | 445 | spin_lock_irqsave(&data->lock, flags); |
425 | if (!(data->status & PICOLCD_READY_FB)) { | 446 | if (!(data->status & PICOLCD_READY_FB)) { |
426 | spin_unlock_irqrestore(&data->lock, flags); | 447 | spin_unlock_irqrestore(&data->lock, flags); |
@@ -440,14 +461,18 @@ static void picolcd_fb_update(struct picolcd_data *data) | |||
440 | for (chip = 0; chip < 4; chip++) | 461 | for (chip = 0; chip < 4; chip++) |
441 | for (tile = 0; tile < 8; tile++) | 462 | for (tile = 0; tile < 8; tile++) |
442 | if (picolcd_fb_update_tile(data->fb_vbitmap, | 463 | if (picolcd_fb_update_tile(data->fb_vbitmap, |
443 | data->fb_bitmap, data->fb_bpp, chip, tile)) { | 464 | data->fb_bitmap, data->fb_bpp, chip, tile) || |
465 | data->fb_force) { | ||
444 | n += 2; | 466 | n += 2; |
467 | if (!data->fb_info->par) | ||
468 | return; /* device lost! */ | ||
445 | if (n >= HID_OUTPUT_FIFO_SIZE / 2) { | 469 | if (n >= HID_OUTPUT_FIFO_SIZE / 2) { |
446 | usbhid_wait_io(data->hdev); | 470 | usbhid_wait_io(data->hdev); |
447 | n = 0; | 471 | n = 0; |
448 | } | 472 | } |
449 | picolcd_fb_send_tile(data->hdev, chip, tile); | 473 | picolcd_fb_send_tile(data->hdev, chip, tile); |
450 | } | 474 | } |
475 | data->fb_force = false; | ||
451 | if (n) | 476 | if (n) |
452 | usbhid_wait_io(data->hdev); | 477 | usbhid_wait_io(data->hdev); |
453 | } | 478 | } |
@@ -511,11 +536,23 @@ static int picolcd_fb_blank(int blank, struct fb_info *info) | |||
511 | static void picolcd_fb_destroy(struct fb_info *info) | 536 | static void picolcd_fb_destroy(struct fb_info *info) |
512 | { | 537 | { |
513 | struct picolcd_data *data = info->par; | 538 | struct picolcd_data *data = info->par; |
539 | u32 *ref_cnt = info->pseudo_palette; | ||
540 | int may_release; | ||
541 | |||
514 | info->par = NULL; | 542 | info->par = NULL; |
515 | if (data) | 543 | if (data) |
516 | data->fb_info = NULL; | 544 | data->fb_info = NULL; |
517 | fb_deferred_io_cleanup(info); | 545 | fb_deferred_io_cleanup(info); |
518 | framebuffer_release(info); | 546 | |
547 | ref_cnt--; | ||
548 | mutex_lock(&info->lock); | ||
549 | (*ref_cnt)--; | ||
550 | may_release = !ref_cnt; | ||
551 | mutex_unlock(&info->lock); | ||
552 | if (may_release) { | ||
553 | framebuffer_release(info); | ||
554 | vfree((u8 *)info->fix.smem_start); | ||
555 | } | ||
519 | } | 556 | } |
520 | 557 | ||
521 | static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | 558 | static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) |
@@ -526,29 +563,37 @@ static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *i | |||
526 | /* only allow 1/8 bit depth (8-bit is grayscale) */ | 563 | /* only allow 1/8 bit depth (8-bit is grayscale) */ |
527 | *var = picolcdfb_var; | 564 | *var = picolcdfb_var; |
528 | var->activate = activate; | 565 | var->activate = activate; |
529 | if (bpp >= 8) | 566 | if (bpp >= 8) { |
530 | var->bits_per_pixel = 8; | 567 | var->bits_per_pixel = 8; |
531 | else | 568 | var->red.length = 8; |
569 | var->green.length = 8; | ||
570 | var->blue.length = 8; | ||
571 | } else { | ||
532 | var->bits_per_pixel = 1; | 572 | var->bits_per_pixel = 1; |
573 | var->red.length = 1; | ||
574 | var->green.length = 1; | ||
575 | var->blue.length = 1; | ||
576 | } | ||
533 | return 0; | 577 | return 0; |
534 | } | 578 | } |
535 | 579 | ||
536 | static int picolcd_set_par(struct fb_info *info) | 580 | static int picolcd_set_par(struct fb_info *info) |
537 | { | 581 | { |
538 | struct picolcd_data *data = info->par; | 582 | struct picolcd_data *data = info->par; |
539 | u8 *o_fb, *n_fb; | 583 | u8 *tmp_fb, *o_fb; |
584 | if (!data) | ||
585 | return -ENODEV; | ||
540 | if (info->var.bits_per_pixel == data->fb_bpp) | 586 | if (info->var.bits_per_pixel == data->fb_bpp) |
541 | return 0; | 587 | return 0; |
542 | /* switch between 1/8 bit depths */ | 588 | /* switch between 1/8 bit depths */ |
543 | if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8) | 589 | if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8) |
544 | return -EINVAL; | 590 | return -EINVAL; |
545 | 591 | ||
546 | o_fb = data->fb_bitmap; | 592 | o_fb = data->fb_bitmap; |
547 | n_fb = vmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel); | 593 | tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL); |
548 | if (!n_fb) | 594 | if (!tmp_fb) |
549 | return -ENOMEM; | 595 | return -ENOMEM; |
550 | 596 | ||
551 | fb_deferred_io_cleanup(info); | ||
552 | /* translate FB content to new bits-per-pixel */ | 597 | /* translate FB content to new bits-per-pixel */ |
553 | if (info->var.bits_per_pixel == 1) { | 598 | if (info->var.bits_per_pixel == 1) { |
554 | int i, b; | 599 | int i, b; |
@@ -558,24 +603,87 @@ static int picolcd_set_par(struct fb_info *info) | |||
558 | p <<= 1; | 603 | p <<= 1; |
559 | p |= o_fb[i*8+b] ? 0x01 : 0x00; | 604 | p |= o_fb[i*8+b] ? 0x01 : 0x00; |
560 | } | 605 | } |
606 | tmp_fb[i] = p; | ||
561 | } | 607 | } |
608 | memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE); | ||
562 | info->fix.visual = FB_VISUAL_MONO01; | 609 | info->fix.visual = FB_VISUAL_MONO01; |
563 | info->fix.line_length = PICOLCDFB_WIDTH / 8; | 610 | info->fix.line_length = PICOLCDFB_WIDTH / 8; |
564 | } else { | 611 | } else { |
565 | int i; | 612 | int i; |
613 | memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE); | ||
566 | for (i = 0; i < PICOLCDFB_SIZE * 8; i++) | 614 | for (i = 0; i < PICOLCDFB_SIZE * 8; i++) |
567 | n_fb[i] = o_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; | 615 | o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; |
568 | info->fix.visual = FB_VISUAL_TRUECOLOR; | 616 | info->fix.visual = FB_VISUAL_DIRECTCOLOR; |
569 | info->fix.line_length = PICOLCDFB_WIDTH; | 617 | info->fix.line_length = PICOLCDFB_WIDTH; |
570 | } | 618 | } |
571 | 619 | ||
572 | data->fb_bitmap = n_fb; | 620 | kfree(tmp_fb); |
573 | data->fb_bpp = info->var.bits_per_pixel; | 621 | data->fb_bpp = info->var.bits_per_pixel; |
574 | info->screen_base = (char __force __iomem *)n_fb; | 622 | return 0; |
575 | info->fix.smem_start = (unsigned long)n_fb; | 623 | } |
576 | info->fix.smem_len = PICOLCDFB_SIZE*data->fb_bpp; | 624 | |
577 | fb_deferred_io_init(info); | 625 | /* Do refcounting on our FB and cleanup per worker if FB is |
578 | vfree(o_fb); | 626 | * closed after unplug of our device |
627 | * (fb_release holds info->lock and still touches info after | ||
628 | * we return so we can't release it immediately. | ||
629 | */ | ||
630 | struct picolcd_fb_cleanup_item { | ||
631 | struct fb_info *info; | ||
632 | struct picolcd_fb_cleanup_item *next; | ||
633 | }; | ||
634 | static struct picolcd_fb_cleanup_item *fb_pending; | ||
635 | DEFINE_SPINLOCK(fb_pending_lock); | ||
636 | |||
637 | static void picolcd_fb_do_cleanup(struct work_struct *data) | ||
638 | { | ||
639 | struct picolcd_fb_cleanup_item *item; | ||
640 | unsigned long flags; | ||
641 | |||
642 | do { | ||
643 | spin_lock_irqsave(&fb_pending_lock, flags); | ||
644 | item = fb_pending; | ||
645 | fb_pending = item ? item->next : NULL; | ||
646 | spin_unlock_irqrestore(&fb_pending_lock, flags); | ||
647 | |||
648 | if (item) { | ||
649 | u8 *fb = (u8 *)item->info->fix.smem_start; | ||
650 | /* make sure we do not race against fb core when | ||
651 | * releasing */ | ||
652 | mutex_lock(&item->info->lock); | ||
653 | mutex_unlock(&item->info->lock); | ||
654 | framebuffer_release(item->info); | ||
655 | vfree(fb); | ||
656 | } | ||
657 | } while (item); | ||
658 | } | ||
659 | |||
660 | DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup); | ||
661 | |||
662 | static int picolcd_fb_open(struct fb_info *info, int u) | ||
663 | { | ||
664 | u32 *ref_cnt = info->pseudo_palette; | ||
665 | ref_cnt--; | ||
666 | |||
667 | (*ref_cnt)++; | ||
668 | return 0; | ||
669 | } | ||
670 | |||
671 | static int picolcd_fb_release(struct fb_info *info, int u) | ||
672 | { | ||
673 | u32 *ref_cnt = info->pseudo_palette; | ||
674 | ref_cnt--; | ||
675 | |||
676 | (*ref_cnt)++; | ||
677 | if (!*ref_cnt) { | ||
678 | unsigned long flags; | ||
679 | struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt; | ||
680 | item--; | ||
681 | spin_lock_irqsave(&fb_pending_lock, flags); | ||
682 | item->next = fb_pending; | ||
683 | fb_pending = item; | ||
684 | spin_unlock_irqrestore(&fb_pending_lock, flags); | ||
685 | schedule_work(&picolcd_fb_cleanup); | ||
686 | } | ||
579 | return 0; | 687 | return 0; |
580 | } | 688 | } |
581 | 689 | ||
@@ -583,6 +691,8 @@ static int picolcd_set_par(struct fb_info *info) | |||
583 | static struct fb_ops picolcdfb_ops = { | 691 | static struct fb_ops picolcdfb_ops = { |
584 | .owner = THIS_MODULE, | 692 | .owner = THIS_MODULE, |
585 | .fb_destroy = picolcd_fb_destroy, | 693 | .fb_destroy = picolcd_fb_destroy, |
694 | .fb_open = picolcd_fb_open, | ||
695 | .fb_release = picolcd_fb_release, | ||
586 | .fb_read = fb_sys_read, | 696 | .fb_read = fb_sys_read, |
587 | .fb_write = picolcd_fb_write, | 697 | .fb_write = picolcd_fb_write, |
588 | .fb_blank = picolcd_fb_blank, | 698 | .fb_blank = picolcd_fb_blank, |
@@ -660,11 +770,12 @@ static int picolcd_init_framebuffer(struct picolcd_data *data) | |||
660 | { | 770 | { |
661 | struct device *dev = &data->hdev->dev; | 771 | struct device *dev = &data->hdev->dev; |
662 | struct fb_info *info = NULL; | 772 | struct fb_info *info = NULL; |
663 | int error = -ENOMEM; | 773 | int i, error = -ENOMEM; |
664 | u8 *fb_vbitmap = NULL; | 774 | u8 *fb_vbitmap = NULL; |
665 | u8 *fb_bitmap = NULL; | 775 | u8 *fb_bitmap = NULL; |
776 | u32 *palette; | ||
666 | 777 | ||
667 | fb_bitmap = vmalloc(PICOLCDFB_SIZE*picolcdfb_var.bits_per_pixel); | 778 | fb_bitmap = vmalloc(PICOLCDFB_SIZE*8); |
668 | if (fb_bitmap == NULL) { | 779 | if (fb_bitmap == NULL) { |
669 | dev_err(dev, "can't get a free page for framebuffer\n"); | 780 | dev_err(dev, "can't get a free page for framebuffer\n"); |
670 | goto err_nomem; | 781 | goto err_nomem; |
@@ -678,18 +789,29 @@ static int picolcd_init_framebuffer(struct picolcd_data *data) | |||
678 | 789 | ||
679 | data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; | 790 | data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; |
680 | data->fb_defio = picolcd_fb_defio; | 791 | data->fb_defio = picolcd_fb_defio; |
681 | info = framebuffer_alloc(0, dev); | 792 | /* The extra memory is: |
793 | * - struct picolcd_fb_cleanup_item | ||
794 | * - u32 for ref_count | ||
795 | * - 256*u32 for pseudo_palette | ||
796 | */ | ||
797 | info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev); | ||
682 | if (info == NULL) { | 798 | if (info == NULL) { |
683 | dev_err(dev, "failed to allocate a framebuffer\n"); | 799 | dev_err(dev, "failed to allocate a framebuffer\n"); |
684 | goto err_nomem; | 800 | goto err_nomem; |
685 | } | 801 | } |
686 | 802 | ||
803 | palette = info->par + sizeof(struct picolcd_fb_cleanup_item); | ||
804 | *palette = 1; | ||
805 | palette++; | ||
806 | for (i = 0; i < 256; i++) | ||
807 | palette[i] = i > 0 && i < 16 ? 0xff : 0; | ||
808 | info->pseudo_palette = palette; | ||
687 | info->fbdefio = &data->fb_defio; | 809 | info->fbdefio = &data->fb_defio; |
688 | info->screen_base = (char __force __iomem *)fb_bitmap; | 810 | info->screen_base = (char __force __iomem *)fb_bitmap; |
689 | info->fbops = &picolcdfb_ops; | 811 | info->fbops = &picolcdfb_ops; |
690 | info->var = picolcdfb_var; | 812 | info->var = picolcdfb_var; |
691 | info->fix = picolcdfb_fix; | 813 | info->fix = picolcdfb_fix; |
692 | info->fix.smem_len = PICOLCDFB_SIZE; | 814 | info->fix.smem_len = PICOLCDFB_SIZE*8; |
693 | info->fix.smem_start = (unsigned long)fb_bitmap; | 815 | info->fix.smem_start = (unsigned long)fb_bitmap; |
694 | info->par = data; | 816 | info->par = data; |
695 | info->flags = FBINFO_FLAG_DEFAULT; | 817 | info->flags = FBINFO_FLAG_DEFAULT; |
@@ -707,18 +829,20 @@ static int picolcd_init_framebuffer(struct picolcd_data *data) | |||
707 | dev_err(dev, "failed to create sysfs attributes\n"); | 829 | dev_err(dev, "failed to create sysfs attributes\n"); |
708 | goto err_cleanup; | 830 | goto err_cleanup; |
709 | } | 831 | } |
832 | fb_deferred_io_init(info); | ||
710 | data->fb_info = info; | 833 | data->fb_info = info; |
711 | error = register_framebuffer(info); | 834 | error = register_framebuffer(info); |
712 | if (error) { | 835 | if (error) { |
713 | dev_err(dev, "failed to register framebuffer\n"); | 836 | dev_err(dev, "failed to register framebuffer\n"); |
714 | goto err_sysfs; | 837 | goto err_sysfs; |
715 | } | 838 | } |
716 | fb_deferred_io_init(info); | ||
717 | /* schedule first output of framebuffer */ | 839 | /* schedule first output of framebuffer */ |
840 | data->fb_force = 1; | ||
718 | schedule_delayed_work(&info->deferred_work, 0); | 841 | schedule_delayed_work(&info->deferred_work, 0); |
719 | return 0; | 842 | return 0; |
720 | 843 | ||
721 | err_sysfs: | 844 | err_sysfs: |
845 | fb_deferred_io_cleanup(info); | ||
722 | device_remove_file(dev, &dev_attr_fb_update_rate); | 846 | device_remove_file(dev, &dev_attr_fb_update_rate); |
723 | err_cleanup: | 847 | err_cleanup: |
724 | data->fb_vbitmap = NULL; | 848 | data->fb_vbitmap = NULL; |
@@ -737,19 +861,17 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data) | |||
737 | { | 861 | { |
738 | struct fb_info *info = data->fb_info; | 862 | struct fb_info *info = data->fb_info; |
739 | u8 *fb_vbitmap = data->fb_vbitmap; | 863 | u8 *fb_vbitmap = data->fb_vbitmap; |
740 | u8 *fb_bitmap = data->fb_bitmap; | ||
741 | 864 | ||
742 | if (!info) | 865 | if (!info) |
743 | return; | 866 | return; |
744 | 867 | ||
868 | info->par = NULL; | ||
869 | device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); | ||
870 | unregister_framebuffer(info); | ||
745 | data->fb_vbitmap = NULL; | 871 | data->fb_vbitmap = NULL; |
746 | data->fb_bitmap = NULL; | 872 | data->fb_bitmap = NULL; |
747 | data->fb_bpp = 0; | 873 | data->fb_bpp = 0; |
748 | data->fb_info = NULL; | 874 | data->fb_info = NULL; |
749 | device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); | ||
750 | fb_deferred_io_cleanup(info); | ||
751 | unregister_framebuffer(info); | ||
752 | vfree(fb_bitmap); | ||
753 | kfree(fb_vbitmap); | 875 | kfree(fb_vbitmap); |
754 | } | 876 | } |
755 | 877 | ||
@@ -2566,6 +2688,13 @@ static void picolcd_remove(struct hid_device *hdev) | |||
2566 | spin_lock_irqsave(&data->lock, flags); | 2688 | spin_lock_irqsave(&data->lock, flags); |
2567 | data->status |= PICOLCD_FAILED; | 2689 | data->status |= PICOLCD_FAILED; |
2568 | spin_unlock_irqrestore(&data->lock, flags); | 2690 | spin_unlock_irqrestore(&data->lock, flags); |
2691 | #ifdef CONFIG_HID_PICOLCD_FB | ||
2692 | /* short-circuit FB as early as possible in order to | ||
2693 | * avoid long delays if we host console. | ||
2694 | */ | ||
2695 | if (data->fb_info) | ||
2696 | data->fb_info->par = NULL; | ||
2697 | #endif | ||
2569 | 2698 | ||
2570 | picolcd_exit_devfs(data); | 2699 | picolcd_exit_devfs(data); |
2571 | device_remove_file(&hdev->dev, &dev_attr_operation_mode); | 2700 | device_remove_file(&hdev->dev, &dev_attr_operation_mode); |
@@ -2623,6 +2752,10 @@ static int __init picolcd_init(void) | |||
2623 | static void __exit picolcd_exit(void) | 2752 | static void __exit picolcd_exit(void) |
2624 | { | 2753 | { |
2625 | hid_unregister_driver(&picolcd_driver); | 2754 | hid_unregister_driver(&picolcd_driver); |
2755 | #ifdef CONFIG_HID_PICOLCD_FB | ||
2756 | flush_scheduled_work(); | ||
2757 | WARN_ON(fb_pending); | ||
2758 | #endif | ||
2626 | } | 2759 | } |
2627 | 2760 | ||
2628 | module_init(picolcd_init); | 2761 | module_init(picolcd_init); |