Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /***************************************************************************
0003  *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
0004  *                                                                         *
0005  *   Based on Logitech G13 driver (v0.4)                                   *
0006  *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
0007  *                                                                         *
0008  ***************************************************************************/
0009 
0010 #include <linux/hid.h>
0011 #include <linux/vmalloc.h>
0012 
0013 #include <linux/fb.h>
0014 #include <linux/module.h>
0015 
0016 #include "hid-picolcd.h"
0017 
0018 /* Framebuffer
0019  *
0020  * The PicoLCD use a Topway LCD module of 256x64 pixel
0021  * This display area is tiled over 4 controllers with 8 tiles
0022  * each. Each tile has 8x64 pixel, each data byte representing
0023  * a 1-bit wide vertical line of the tile.
0024  *
0025  * The display can be updated at a tile granularity.
0026  *
0027  *       Chip 1           Chip 2           Chip 3           Chip 4
0028  * +----------------+----------------+----------------+----------------+
0029  * |     Tile 1     |     Tile 1     |     Tile 1     |     Tile 1     |
0030  * +----------------+----------------+----------------+----------------+
0031  * |     Tile 2     |     Tile 2     |     Tile 2     |     Tile 2     |
0032  * +----------------+----------------+----------------+----------------+
0033  *                                  ...
0034  * +----------------+----------------+----------------+----------------+
0035  * |     Tile 8     |     Tile 8     |     Tile 8     |     Tile 8     |
0036  * +----------------+----------------+----------------+----------------+
0037  */
0038 #define PICOLCDFB_NAME "picolcdfb"
0039 #define PICOLCDFB_WIDTH (256)
0040 #define PICOLCDFB_HEIGHT (64)
0041 #define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8)
0042 
0043 #define PICOLCDFB_UPDATE_RATE_LIMIT   10
0044 #define PICOLCDFB_UPDATE_RATE_DEFAULT  2
0045 
0046 /* Framebuffer visual structures */
0047 static const struct fb_fix_screeninfo picolcdfb_fix = {
0048     .id          = PICOLCDFB_NAME,
0049     .type        = FB_TYPE_PACKED_PIXELS,
0050     .visual      = FB_VISUAL_MONO01,
0051     .xpanstep    = 0,
0052     .ypanstep    = 0,
0053     .ywrapstep   = 0,
0054     .line_length = PICOLCDFB_WIDTH / 8,
0055     .accel       = FB_ACCEL_NONE,
0056 };
0057 
0058 static const struct fb_var_screeninfo picolcdfb_var = {
0059     .xres           = PICOLCDFB_WIDTH,
0060     .yres           = PICOLCDFB_HEIGHT,
0061     .xres_virtual   = PICOLCDFB_WIDTH,
0062     .yres_virtual   = PICOLCDFB_HEIGHT,
0063     .width          = 103,
0064     .height         = 26,
0065     .bits_per_pixel = 1,
0066     .grayscale      = 1,
0067     .red            = {
0068         .offset = 0,
0069         .length = 1,
0070         .msb_right = 0,
0071     },
0072     .green          = {
0073         .offset = 0,
0074         .length = 1,
0075         .msb_right = 0,
0076     },
0077     .blue           = {
0078         .offset = 0,
0079         .length = 1,
0080         .msb_right = 0,
0081     },
0082     .transp         = {
0083         .offset = 0,
0084         .length = 0,
0085         .msb_right = 0,
0086     },
0087 };
0088 
0089 /* Send a given tile to PicoLCD */
0090 static int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap,
0091         int chip, int tile)
0092 {
0093     struct hid_report *report1, *report2;
0094     unsigned long flags;
0095     u8 *tdata;
0096     int i;
0097 
0098     report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev);
0099     if (!report1 || report1->maxfield != 1)
0100         return -ENODEV;
0101     report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev);
0102     if (!report2 || report2->maxfield != 1)
0103         return -ENODEV;
0104 
0105     spin_lock_irqsave(&data->lock, flags);
0106     if ((data->status & PICOLCD_FAILED)) {
0107         spin_unlock_irqrestore(&data->lock, flags);
0108         return -ENODEV;
0109     }
0110     hid_set_field(report1->field[0],  0, chip << 2);
0111     hid_set_field(report1->field[0],  1, 0x02);
0112     hid_set_field(report1->field[0],  2, 0x00);
0113     hid_set_field(report1->field[0],  3, 0x00);
0114     hid_set_field(report1->field[0],  4, 0xb8 | tile);
0115     hid_set_field(report1->field[0],  5, 0x00);
0116     hid_set_field(report1->field[0],  6, 0x00);
0117     hid_set_field(report1->field[0],  7, 0x40);
0118     hid_set_field(report1->field[0],  8, 0x00);
0119     hid_set_field(report1->field[0],  9, 0x00);
0120     hid_set_field(report1->field[0], 10,   32);
0121 
0122     hid_set_field(report2->field[0],  0, (chip << 2) | 0x01);
0123     hid_set_field(report2->field[0],  1, 0x00);
0124     hid_set_field(report2->field[0],  2, 0x00);
0125     hid_set_field(report2->field[0],  3,   32);
0126 
0127     tdata = vbitmap + (tile * 4 + chip) * 64;
0128     for (i = 0; i < 64; i++)
0129         if (i < 32)
0130             hid_set_field(report1->field[0], 11 + i, tdata[i]);
0131         else
0132             hid_set_field(report2->field[0], 4 + i - 32, tdata[i]);
0133 
0134     hid_hw_request(data->hdev, report1, HID_REQ_SET_REPORT);
0135     hid_hw_request(data->hdev, report2, HID_REQ_SET_REPORT);
0136     spin_unlock_irqrestore(&data->lock, flags);
0137     return 0;
0138 }
0139 
0140 /* Translate a single tile*/
0141 static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
0142         int chip, int tile)
0143 {
0144     int i, b, changed = 0;
0145     u8 tdata[64];
0146     u8 *vdata = vbitmap + (tile * 4 + chip) * 64;
0147 
0148     if (bpp == 1) {
0149         for (b = 7; b >= 0; b--) {
0150             const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
0151             for (i = 0; i < 64; i++) {
0152                 tdata[i] <<= 1;
0153                 tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01;
0154             }
0155         }
0156     } else if (bpp == 8) {
0157         for (b = 7; b >= 0; b--) {
0158             const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8;
0159             for (i = 0; i < 64; i++) {
0160                 tdata[i] <<= 1;
0161                 tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00;
0162             }
0163         }
0164     } else {
0165         /* Oops, we should never get here! */
0166         WARN_ON(1);
0167         return 0;
0168     }
0169 
0170     for (i = 0; i < 64; i++)
0171         if (tdata[i] != vdata[i]) {
0172             changed = 1;
0173             vdata[i] = tdata[i];
0174         }
0175     return changed;
0176 }
0177 
0178 void picolcd_fb_refresh(struct picolcd_data *data)
0179 {
0180     if (data->fb_info)
0181         schedule_delayed_work(&data->fb_info->deferred_work, 0);
0182 }
0183 
0184 /* Reconfigure LCD display */
0185 int picolcd_fb_reset(struct picolcd_data *data, int clear)
0186 {
0187     struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
0188     struct picolcd_fb_data *fbdata = data->fb_info->par;
0189     int i, j;
0190     unsigned long flags;
0191     static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 };
0192 
0193     if (!report || report->maxfield != 1)
0194         return -ENODEV;
0195 
0196     spin_lock_irqsave(&data->lock, flags);
0197     for (i = 0; i < 4; i++) {
0198         for (j = 0; j < report->field[0]->maxusage; j++)
0199             if (j == 0)
0200                 hid_set_field(report->field[0], j, i << 2);
0201             else if (j < sizeof(mapcmd))
0202                 hid_set_field(report->field[0], j, mapcmd[j]);
0203             else
0204                 hid_set_field(report->field[0], j, 0);
0205         hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
0206     }
0207     spin_unlock_irqrestore(&data->lock, flags);
0208 
0209     if (clear) {
0210         memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE);
0211         memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp);
0212     }
0213     fbdata->force = 1;
0214 
0215     /* schedule first output of framebuffer */
0216     if (fbdata->ready)
0217         schedule_delayed_work(&data->fb_info->deferred_work, 0);
0218     else
0219         fbdata->ready = 1;
0220 
0221     return 0;
0222 }
0223 
0224 /* Update fb_vbitmap from the screen_base and send changed tiles to device */
0225 static void picolcd_fb_update(struct fb_info *info)
0226 {
0227     int chip, tile, n;
0228     unsigned long flags;
0229     struct picolcd_fb_data *fbdata = info->par;
0230     struct picolcd_data *data;
0231 
0232     mutex_lock(&info->lock);
0233 
0234     spin_lock_irqsave(&fbdata->lock, flags);
0235     if (!fbdata->ready && fbdata->picolcd)
0236         picolcd_fb_reset(fbdata->picolcd, 0);
0237     spin_unlock_irqrestore(&fbdata->lock, flags);
0238 
0239     /*
0240      * Translate the framebuffer into the format needed by the PicoLCD.
0241      * See display layout above.
0242      * Do this one tile after the other and push those tiles that changed.
0243      *
0244      * Wait for our IO to complete as otherwise we might flood the queue!
0245      */
0246     n = 0;
0247     for (chip = 0; chip < 4; chip++)
0248         for (tile = 0; tile < 8; tile++) {
0249             if (!fbdata->force && !picolcd_fb_update_tile(
0250                     fbdata->vbitmap, fbdata->bitmap,
0251                     fbdata->bpp, chip, tile))
0252                 continue;
0253             n += 2;
0254             if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
0255                 spin_lock_irqsave(&fbdata->lock, flags);
0256                 data = fbdata->picolcd;
0257                 spin_unlock_irqrestore(&fbdata->lock, flags);
0258                 mutex_unlock(&info->lock);
0259                 if (!data)
0260                     return;
0261                 hid_hw_wait(data->hdev);
0262                 mutex_lock(&info->lock);
0263                 n = 0;
0264             }
0265             spin_lock_irqsave(&fbdata->lock, flags);
0266             data = fbdata->picolcd;
0267             spin_unlock_irqrestore(&fbdata->lock, flags);
0268             if (!data || picolcd_fb_send_tile(data,
0269                     fbdata->vbitmap, chip, tile))
0270                 goto out;
0271         }
0272     fbdata->force = false;
0273     if (n) {
0274         spin_lock_irqsave(&fbdata->lock, flags);
0275         data = fbdata->picolcd;
0276         spin_unlock_irqrestore(&fbdata->lock, flags);
0277         mutex_unlock(&info->lock);
0278         if (data)
0279             hid_hw_wait(data->hdev);
0280         return;
0281     }
0282 out:
0283     mutex_unlock(&info->lock);
0284 }
0285 
0286 /* Stub to call the system default and update the image on the picoLCD */
0287 static void picolcd_fb_fillrect(struct fb_info *info,
0288         const struct fb_fillrect *rect)
0289 {
0290     if (!info->par)
0291         return;
0292     sys_fillrect(info, rect);
0293 
0294     schedule_delayed_work(&info->deferred_work, 0);
0295 }
0296 
0297 /* Stub to call the system default and update the image on the picoLCD */
0298 static void picolcd_fb_copyarea(struct fb_info *info,
0299         const struct fb_copyarea *area)
0300 {
0301     if (!info->par)
0302         return;
0303     sys_copyarea(info, area);
0304 
0305     schedule_delayed_work(&info->deferred_work, 0);
0306 }
0307 
0308 /* Stub to call the system default and update the image on the picoLCD */
0309 static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
0310 {
0311     if (!info->par)
0312         return;
0313     sys_imageblit(info, image);
0314 
0315     schedule_delayed_work(&info->deferred_work, 0);
0316 }
0317 
0318 /*
0319  * this is the slow path from userspace. they can seek and write to
0320  * the fb. it's inefficient to do anything less than a full screen draw
0321  */
0322 static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
0323         size_t count, loff_t *ppos)
0324 {
0325     ssize_t ret;
0326     if (!info->par)
0327         return -ENODEV;
0328     ret = fb_sys_write(info, buf, count, ppos);
0329     if (ret >= 0)
0330         schedule_delayed_work(&info->deferred_work, 0);
0331     return ret;
0332 }
0333 
0334 static int picolcd_fb_blank(int blank, struct fb_info *info)
0335 {
0336     /* We let fb notification do this for us via lcd/backlight device */
0337     return 0;
0338 }
0339 
0340 static void picolcd_fb_destroy(struct fb_info *info)
0341 {
0342     struct picolcd_fb_data *fbdata = info->par;
0343 
0344     /* make sure no work is deferred */
0345     fb_deferred_io_cleanup(info);
0346 
0347     /* No thridparty should ever unregister our framebuffer! */
0348     WARN_ON(fbdata->picolcd != NULL);
0349 
0350     vfree((u8 *)info->fix.smem_start);
0351     framebuffer_release(info);
0352 }
0353 
0354 static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
0355 {
0356     __u32 bpp      = var->bits_per_pixel;
0357     __u32 activate = var->activate;
0358 
0359     /* only allow 1/8 bit depth (8-bit is grayscale) */
0360     *var = picolcdfb_var;
0361     var->activate = activate;
0362     if (bpp >= 8) {
0363         var->bits_per_pixel = 8;
0364         var->red.length     = 8;
0365         var->green.length   = 8;
0366         var->blue.length    = 8;
0367     } else {
0368         var->bits_per_pixel = 1;
0369         var->red.length     = 1;
0370         var->green.length   = 1;
0371         var->blue.length    = 1;
0372     }
0373     return 0;
0374 }
0375 
0376 static int picolcd_set_par(struct fb_info *info)
0377 {
0378     struct picolcd_fb_data *fbdata = info->par;
0379     u8 *tmp_fb, *o_fb;
0380     if (info->var.bits_per_pixel == fbdata->bpp)
0381         return 0;
0382     /* switch between 1/8 bit depths */
0383     if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
0384         return -EINVAL;
0385 
0386     o_fb   = fbdata->bitmap;
0387     tmp_fb = kmalloc_array(PICOLCDFB_SIZE, info->var.bits_per_pixel,
0388                    GFP_KERNEL);
0389     if (!tmp_fb)
0390         return -ENOMEM;
0391 
0392     /* translate FB content to new bits-per-pixel */
0393     if (info->var.bits_per_pixel == 1) {
0394         int i, b;
0395         for (i = 0; i < PICOLCDFB_SIZE; i++) {
0396             u8 p = 0;
0397             for (b = 0; b < 8; b++) {
0398                 p <<= 1;
0399                 p |= o_fb[i*8+b] ? 0x01 : 0x00;
0400             }
0401             tmp_fb[i] = p;
0402         }
0403         memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
0404         info->fix.visual = FB_VISUAL_MONO01;
0405         info->fix.line_length = PICOLCDFB_WIDTH / 8;
0406     } else {
0407         int i;
0408         memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
0409         for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
0410             o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
0411         info->fix.visual = FB_VISUAL_DIRECTCOLOR;
0412         info->fix.line_length = PICOLCDFB_WIDTH;
0413     }
0414 
0415     kfree(tmp_fb);
0416     fbdata->bpp = info->var.bits_per_pixel;
0417     return 0;
0418 }
0419 
0420 static const struct fb_ops picolcdfb_ops = {
0421     .owner        = THIS_MODULE,
0422     .fb_destroy   = picolcd_fb_destroy,
0423     .fb_read      = fb_sys_read,
0424     .fb_write     = picolcd_fb_write,
0425     .fb_blank     = picolcd_fb_blank,
0426     .fb_fillrect  = picolcd_fb_fillrect,
0427     .fb_copyarea  = picolcd_fb_copyarea,
0428     .fb_imageblit = picolcd_fb_imageblit,
0429     .fb_check_var = picolcd_fb_check_var,
0430     .fb_set_par   = picolcd_set_par,
0431     .fb_mmap      = fb_deferred_io_mmap,
0432 };
0433 
0434 
0435 /* Callback from deferred IO workqueue */
0436 static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagereflist)
0437 {
0438     picolcd_fb_update(info);
0439 }
0440 
0441 static const struct fb_deferred_io picolcd_fb_defio = {
0442     .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
0443     .deferred_io = picolcd_fb_deferred_io,
0444 };
0445 
0446 
0447 /*
0448  * The "fb_update_rate" sysfs attribute
0449  */
0450 static ssize_t picolcd_fb_update_rate_show(struct device *dev,
0451         struct device_attribute *attr, char *buf)
0452 {
0453     struct picolcd_data *data = dev_get_drvdata(dev);
0454     struct picolcd_fb_data *fbdata = data->fb_info->par;
0455     unsigned i, fb_update_rate = fbdata->update_rate;
0456     size_t ret = 0;
0457 
0458     for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
0459         if (ret >= PAGE_SIZE)
0460             break;
0461         else if (i == fb_update_rate)
0462             ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
0463         else
0464             ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
0465     if (ret > 0)
0466         buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
0467     return ret;
0468 }
0469 
0470 static ssize_t picolcd_fb_update_rate_store(struct device *dev,
0471         struct device_attribute *attr, const char *buf, size_t count)
0472 {
0473     struct picolcd_data *data = dev_get_drvdata(dev);
0474     struct picolcd_fb_data *fbdata = data->fb_info->par;
0475     int i;
0476     unsigned u;
0477 
0478     if (count < 1 || count > 10)
0479         return -EINVAL;
0480 
0481     i = sscanf(buf, "%u", &u);
0482     if (i != 1)
0483         return -EINVAL;
0484 
0485     if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
0486         return -ERANGE;
0487     else if (u == 0)
0488         u = PICOLCDFB_UPDATE_RATE_DEFAULT;
0489 
0490     fbdata->update_rate = u;
0491     data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
0492     return count;
0493 }
0494 
0495 static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show,
0496         picolcd_fb_update_rate_store);
0497 
0498 /* initialize Framebuffer device */
0499 int picolcd_init_framebuffer(struct picolcd_data *data)
0500 {
0501     struct device *dev = &data->hdev->dev;
0502     struct fb_info *info = NULL;
0503     struct picolcd_fb_data *fbdata = NULL;
0504     int i, error = -ENOMEM;
0505     u32 *palette;
0506 
0507     /* The extra memory is:
0508      * - 256*u32 for pseudo_palette
0509      * - struct fb_deferred_io
0510      */
0511     info = framebuffer_alloc(256 * sizeof(u32) +
0512             sizeof(struct fb_deferred_io) +
0513             sizeof(struct picolcd_fb_data) +
0514             PICOLCDFB_SIZE, dev);
0515     if (!info)
0516         goto err_nomem;
0517 
0518     info->fbdefio = info->par;
0519     *info->fbdefio = picolcd_fb_defio;
0520     info->par += sizeof(struct fb_deferred_io);
0521     palette = info->par;
0522     info->par += 256 * sizeof(u32);
0523     for (i = 0; i < 256; i++)
0524         palette[i] = i > 0 && i < 16 ? 0xff : 0;
0525     info->pseudo_palette = palette;
0526     info->fbops = &picolcdfb_ops;
0527     info->var = picolcdfb_var;
0528     info->fix = picolcdfb_fix;
0529     info->fix.smem_len   = PICOLCDFB_SIZE*8;
0530     info->flags = FBINFO_FLAG_DEFAULT;
0531 
0532     fbdata = info->par;
0533     spin_lock_init(&fbdata->lock);
0534     fbdata->picolcd = data;
0535     fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
0536     fbdata->bpp     = picolcdfb_var.bits_per_pixel;
0537     fbdata->force   = 1;
0538     fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
0539     fbdata->bitmap  = vmalloc(PICOLCDFB_SIZE*8);
0540     if (fbdata->bitmap == NULL) {
0541         dev_err(dev, "can't get a free page for framebuffer\n");
0542         goto err_nomem;
0543     }
0544     info->screen_base = (char __force __iomem *)fbdata->bitmap;
0545     info->fix.smem_start = (unsigned long)fbdata->bitmap;
0546     memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
0547     data->fb_info = info;
0548 
0549     error = picolcd_fb_reset(data, 1);
0550     if (error) {
0551         dev_err(dev, "failed to configure display\n");
0552         goto err_cleanup;
0553     }
0554 
0555     error = device_create_file(dev, &dev_attr_fb_update_rate);
0556     if (error) {
0557         dev_err(dev, "failed to create sysfs attributes\n");
0558         goto err_cleanup;
0559     }
0560 
0561     fb_deferred_io_init(info);
0562     error = register_framebuffer(info);
0563     if (error) {
0564         dev_err(dev, "failed to register framebuffer\n");
0565         goto err_sysfs;
0566     }
0567     return 0;
0568 
0569 err_sysfs:
0570     device_remove_file(dev, &dev_attr_fb_update_rate);
0571     fb_deferred_io_cleanup(info);
0572 err_cleanup:
0573     data->fb_info    = NULL;
0574 
0575 err_nomem:
0576     if (fbdata)
0577         vfree(fbdata->bitmap);
0578     framebuffer_release(info);
0579     return error;
0580 }
0581 
0582 void picolcd_exit_framebuffer(struct picolcd_data *data)
0583 {
0584     struct fb_info *info = data->fb_info;
0585     struct picolcd_fb_data *fbdata;
0586     unsigned long flags;
0587 
0588     if (!info)
0589         return;
0590 
0591     device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
0592     fbdata = info->par;
0593 
0594     /* disconnect framebuffer from HID dev */
0595     spin_lock_irqsave(&fbdata->lock, flags);
0596     fbdata->picolcd = NULL;
0597     spin_unlock_irqrestore(&fbdata->lock, flags);
0598 
0599     /* make sure there is no running update - thus that fbdata->picolcd
0600      * once obtained under lock is guaranteed not to get free() under
0601      * the feet of the deferred work */
0602     flush_delayed_work(&info->deferred_work);
0603 
0604     data->fb_info = NULL;
0605     unregister_framebuffer(info);
0606 }