root/drivers/hid/hid-picolcd_fb.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. picolcd_fb_send_tile
  2. picolcd_fb_update_tile
  3. picolcd_fb_refresh
  4. picolcd_fb_reset
  5. picolcd_fb_update
  6. picolcd_fb_fillrect
  7. picolcd_fb_copyarea
  8. picolcd_fb_imageblit
  9. picolcd_fb_write
  10. picolcd_fb_blank
  11. picolcd_fb_destroy
  12. picolcd_fb_check_var
  13. picolcd_set_par
  14. picolcd_fb_deferred_io
  15. picolcd_fb_update_rate_show
  16. picolcd_fb_update_rate_store
  17. picolcd_init_framebuffer
  18. picolcd_exit_framebuffer

   1 // SPDX-License-Identifier: GPL-2.0-only
   2 /***************************************************************************
   3  *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
   4  *                                                                         *
   5  *   Based on Logitech G13 driver (v0.4)                                   *
   6  *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
   7  *                                                                         *
   8  ***************************************************************************/
   9 
  10 #include <linux/hid.h>
  11 #include <linux/vmalloc.h>
  12 
  13 #include <linux/fb.h>
  14 #include <linux/module.h>
  15 
  16 #include "hid-picolcd.h"
  17 
  18 /* Framebuffer
  19  *
  20  * The PicoLCD use a Topway LCD module of 256x64 pixel
  21  * This display area is tiled over 4 controllers with 8 tiles
  22  * each. Each tile has 8x64 pixel, each data byte representing
  23  * a 1-bit wide vertical line of the tile.
  24  *
  25  * The display can be updated at a tile granularity.
  26  *
  27  *       Chip 1           Chip 2           Chip 3           Chip 4
  28  * +----------------+----------------+----------------+----------------+
  29  * |     Tile 1     |     Tile 1     |     Tile 1     |     Tile 1     |
  30  * +----------------+----------------+----------------+----------------+
  31  * |     Tile 2     |     Tile 2     |     Tile 2     |     Tile 2     |
  32  * +----------------+----------------+----------------+----------------+
  33  *                                  ...
  34  * +----------------+----------------+----------------+----------------+
  35  * |     Tile 8     |     Tile 8     |     Tile 8     |     Tile 8     |
  36  * +----------------+----------------+----------------+----------------+
  37  */
  38 #define PICOLCDFB_NAME "picolcdfb"
  39 #define PICOLCDFB_WIDTH (256)
  40 #define PICOLCDFB_HEIGHT (64)
  41 #define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8)
  42 
  43 #define PICOLCDFB_UPDATE_RATE_LIMIT   10
  44 #define PICOLCDFB_UPDATE_RATE_DEFAULT  2
  45 
  46 /* Framebuffer visual structures */
  47 static const struct fb_fix_screeninfo picolcdfb_fix = {
  48         .id          = PICOLCDFB_NAME,
  49         .type        = FB_TYPE_PACKED_PIXELS,
  50         .visual      = FB_VISUAL_MONO01,
  51         .xpanstep    = 0,
  52         .ypanstep    = 0,
  53         .ywrapstep   = 0,
  54         .line_length = PICOLCDFB_WIDTH / 8,
  55         .accel       = FB_ACCEL_NONE,
  56 };
  57 
  58 static const struct fb_var_screeninfo picolcdfb_var = {
  59         .xres           = PICOLCDFB_WIDTH,
  60         .yres           = PICOLCDFB_HEIGHT,
  61         .xres_virtual   = PICOLCDFB_WIDTH,
  62         .yres_virtual   = PICOLCDFB_HEIGHT,
  63         .width          = 103,
  64         .height         = 26,
  65         .bits_per_pixel = 1,
  66         .grayscale      = 1,
  67         .red            = {
  68                 .offset = 0,
  69                 .length = 1,
  70                 .msb_right = 0,
  71         },
  72         .green          = {
  73                 .offset = 0,
  74                 .length = 1,
  75                 .msb_right = 0,
  76         },
  77         .blue           = {
  78                 .offset = 0,
  79                 .length = 1,
  80                 .msb_right = 0,
  81         },
  82         .transp         = {
  83                 .offset = 0,
  84                 .length = 0,
  85                 .msb_right = 0,
  86         },
  87 };
  88 
  89 /* Send a given tile to PicoLCD */
  90 static int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap,
  91                 int chip, int tile)
  92 {
  93         struct hid_report *report1, *report2;
  94         unsigned long flags;
  95         u8 *tdata;
  96         int i;
  97 
  98         report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev);
  99         if (!report1 || report1->maxfield != 1)
 100                 return -ENODEV;
 101         report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev);
 102         if (!report2 || report2->maxfield != 1)
 103                 return -ENODEV;
 104 
 105         spin_lock_irqsave(&data->lock, flags);
 106         if ((data->status & PICOLCD_FAILED)) {
 107                 spin_unlock_irqrestore(&data->lock, flags);
 108                 return -ENODEV;
 109         }
 110         hid_set_field(report1->field[0],  0, chip << 2);
 111         hid_set_field(report1->field[0],  1, 0x02);
 112         hid_set_field(report1->field[0],  2, 0x00);
 113         hid_set_field(report1->field[0],  3, 0x00);
 114         hid_set_field(report1->field[0],  4, 0xb8 | tile);
 115         hid_set_field(report1->field[0],  5, 0x00);
 116         hid_set_field(report1->field[0],  6, 0x00);
 117         hid_set_field(report1->field[0],  7, 0x40);
 118         hid_set_field(report1->field[0],  8, 0x00);
 119         hid_set_field(report1->field[0],  9, 0x00);
 120         hid_set_field(report1->field[0], 10,   32);
 121 
 122         hid_set_field(report2->field[0],  0, (chip << 2) | 0x01);
 123         hid_set_field(report2->field[0],  1, 0x00);
 124         hid_set_field(report2->field[0],  2, 0x00);
 125         hid_set_field(report2->field[0],  3,   32);
 126 
 127         tdata = vbitmap + (tile * 4 + chip) * 64;
 128         for (i = 0; i < 64; i++)
 129                 if (i < 32)
 130                         hid_set_field(report1->field[0], 11 + i, tdata[i]);
 131                 else
 132                         hid_set_field(report2->field[0], 4 + i - 32, tdata[i]);
 133 
 134         hid_hw_request(data->hdev, report1, HID_REQ_SET_REPORT);
 135         hid_hw_request(data->hdev, report2, HID_REQ_SET_REPORT);
 136         spin_unlock_irqrestore(&data->lock, flags);
 137         return 0;
 138 }
 139 
 140 /* Translate a single tile*/
 141 static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
 142                 int chip, int tile)
 143 {
 144         int i, b, changed = 0;
 145         u8 tdata[64];
 146         u8 *vdata = vbitmap + (tile * 4 + chip) * 64;
 147 
 148         if (bpp == 1) {
 149                 for (b = 7; b >= 0; b--) {
 150                         const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
 151                         for (i = 0; i < 64; i++) {
 152                                 tdata[i] <<= 1;
 153                                 tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01;
 154                         }
 155                 }
 156         } else if (bpp == 8) {
 157                 for (b = 7; b >= 0; b--) {
 158                         const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8;
 159                         for (i = 0; i < 64; i++) {
 160                                 tdata[i] <<= 1;
 161                                 tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00;
 162                         }
 163                 }
 164         } else {
 165                 /* Oops, we should never get here! */
 166                 WARN_ON(1);
 167                 return 0;
 168         }
 169 
 170         for (i = 0; i < 64; i++)
 171                 if (tdata[i] != vdata[i]) {
 172                         changed = 1;
 173                         vdata[i] = tdata[i];
 174                 }
 175         return changed;
 176 }
 177 
 178 void picolcd_fb_refresh(struct picolcd_data *data)
 179 {
 180         if (data->fb_info)
 181                 schedule_delayed_work(&data->fb_info->deferred_work, 0);
 182 }
 183 
 184 /* Reconfigure LCD display */
 185 int picolcd_fb_reset(struct picolcd_data *data, int clear)
 186 {
 187         struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
 188         struct picolcd_fb_data *fbdata = data->fb_info->par;
 189         int i, j;
 190         unsigned long flags;
 191         static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 };
 192 
 193         if (!report || report->maxfield != 1)
 194                 return -ENODEV;
 195 
 196         spin_lock_irqsave(&data->lock, flags);
 197         for (i = 0; i < 4; i++) {
 198                 for (j = 0; j < report->field[0]->maxusage; j++)
 199                         if (j == 0)
 200                                 hid_set_field(report->field[0], j, i << 2);
 201                         else if (j < sizeof(mapcmd))
 202                                 hid_set_field(report->field[0], j, mapcmd[j]);
 203                         else
 204                                 hid_set_field(report->field[0], j, 0);
 205                 hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
 206         }
 207         spin_unlock_irqrestore(&data->lock, flags);
 208 
 209         if (clear) {
 210                 memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE);
 211                 memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp);
 212         }
 213         fbdata->force = 1;
 214 
 215         /* schedule first output of framebuffer */
 216         if (fbdata->ready)
 217                 schedule_delayed_work(&data->fb_info->deferred_work, 0);
 218         else
 219                 fbdata->ready = 1;
 220 
 221         return 0;
 222 }
 223 
 224 /* Update fb_vbitmap from the screen_base and send changed tiles to device */
 225 static void picolcd_fb_update(struct fb_info *info)
 226 {
 227         int chip, tile, n;
 228         unsigned long flags;
 229         struct picolcd_fb_data *fbdata = info->par;
 230         struct picolcd_data *data;
 231 
 232         mutex_lock(&info->lock);
 233 
 234         spin_lock_irqsave(&fbdata->lock, flags);
 235         if (!fbdata->ready && fbdata->picolcd)
 236                 picolcd_fb_reset(fbdata->picolcd, 0);
 237         spin_unlock_irqrestore(&fbdata->lock, flags);
 238 
 239         /*
 240          * Translate the framebuffer into the format needed by the PicoLCD.
 241          * See display layout above.
 242          * Do this one tile after the other and push those tiles that changed.
 243          *
 244          * Wait for our IO to complete as otherwise we might flood the queue!
 245          */
 246         n = 0;
 247         for (chip = 0; chip < 4; chip++)
 248                 for (tile = 0; tile < 8; tile++) {
 249                         if (!fbdata->force && !picolcd_fb_update_tile(
 250                                         fbdata->vbitmap, fbdata->bitmap,
 251                                         fbdata->bpp, chip, tile))
 252                                 continue;
 253                         n += 2;
 254                         if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
 255                                 spin_lock_irqsave(&fbdata->lock, flags);
 256                                 data = fbdata->picolcd;
 257                                 spin_unlock_irqrestore(&fbdata->lock, flags);
 258                                 mutex_unlock(&info->lock);
 259                                 if (!data)
 260                                         return;
 261                                 hid_hw_wait(data->hdev);
 262                                 mutex_lock(&info->lock);
 263                                 n = 0;
 264                         }
 265                         spin_lock_irqsave(&fbdata->lock, flags);
 266                         data = fbdata->picolcd;
 267                         spin_unlock_irqrestore(&fbdata->lock, flags);
 268                         if (!data || picolcd_fb_send_tile(data,
 269                                         fbdata->vbitmap, chip, tile))
 270                                 goto out;
 271                 }
 272         fbdata->force = false;
 273         if (n) {
 274                 spin_lock_irqsave(&fbdata->lock, flags);
 275                 data = fbdata->picolcd;
 276                 spin_unlock_irqrestore(&fbdata->lock, flags);
 277                 mutex_unlock(&info->lock);
 278                 if (data)
 279                         hid_hw_wait(data->hdev);
 280                 return;
 281         }
 282 out:
 283         mutex_unlock(&info->lock);
 284 }
 285 
 286 /* Stub to call the system default and update the image on the picoLCD */
 287 static void picolcd_fb_fillrect(struct fb_info *info,
 288                 const struct fb_fillrect *rect)
 289 {
 290         if (!info->par)
 291                 return;
 292         sys_fillrect(info, rect);
 293 
 294         schedule_delayed_work(&info->deferred_work, 0);
 295 }
 296 
 297 /* Stub to call the system default and update the image on the picoLCD */
 298 static void picolcd_fb_copyarea(struct fb_info *info,
 299                 const struct fb_copyarea *area)
 300 {
 301         if (!info->par)
 302                 return;
 303         sys_copyarea(info, area);
 304 
 305         schedule_delayed_work(&info->deferred_work, 0);
 306 }
 307 
 308 /* Stub to call the system default and update the image on the picoLCD */
 309 static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
 310 {
 311         if (!info->par)
 312                 return;
 313         sys_imageblit(info, image);
 314 
 315         schedule_delayed_work(&info->deferred_work, 0);
 316 }
 317 
 318 /*
 319  * this is the slow path from userspace. they can seek and write to
 320  * the fb. it's inefficient to do anything less than a full screen draw
 321  */
 322 static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
 323                 size_t count, loff_t *ppos)
 324 {
 325         ssize_t ret;
 326         if (!info->par)
 327                 return -ENODEV;
 328         ret = fb_sys_write(info, buf, count, ppos);
 329         if (ret >= 0)
 330                 schedule_delayed_work(&info->deferred_work, 0);
 331         return ret;
 332 }
 333 
 334 static int picolcd_fb_blank(int blank, struct fb_info *info)
 335 {
 336         /* We let fb notification do this for us via lcd/backlight device */
 337         return 0;
 338 }
 339 
 340 static void picolcd_fb_destroy(struct fb_info *info)
 341 {
 342         struct picolcd_fb_data *fbdata = info->par;
 343 
 344         /* make sure no work is deferred */
 345         fb_deferred_io_cleanup(info);
 346 
 347         /* No thridparty should ever unregister our framebuffer! */
 348         WARN_ON(fbdata->picolcd != NULL);
 349 
 350         vfree((u8 *)info->fix.smem_start);
 351         framebuffer_release(info);
 352 }
 353 
 354 static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
 355 {
 356         __u32 bpp      = var->bits_per_pixel;
 357         __u32 activate = var->activate;
 358 
 359         /* only allow 1/8 bit depth (8-bit is grayscale) */
 360         *var = picolcdfb_var;
 361         var->activate = activate;
 362         if (bpp >= 8) {
 363                 var->bits_per_pixel = 8;
 364                 var->red.length     = 8;
 365                 var->green.length   = 8;
 366                 var->blue.length    = 8;
 367         } else {
 368                 var->bits_per_pixel = 1;
 369                 var->red.length     = 1;
 370                 var->green.length   = 1;
 371                 var->blue.length    = 1;
 372         }
 373         return 0;
 374 }
 375 
 376 static int picolcd_set_par(struct fb_info *info)
 377 {
 378         struct picolcd_fb_data *fbdata = info->par;
 379         u8 *tmp_fb, *o_fb;
 380         if (info->var.bits_per_pixel == fbdata->bpp)
 381                 return 0;
 382         /* switch between 1/8 bit depths */
 383         if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
 384                 return -EINVAL;
 385 
 386         o_fb   = fbdata->bitmap;
 387         tmp_fb = kmalloc_array(PICOLCDFB_SIZE, info->var.bits_per_pixel,
 388                                GFP_KERNEL);
 389         if (!tmp_fb)
 390                 return -ENOMEM;
 391 
 392         /* translate FB content to new bits-per-pixel */
 393         if (info->var.bits_per_pixel == 1) {
 394                 int i, b;
 395                 for (i = 0; i < PICOLCDFB_SIZE; i++) {
 396                         u8 p = 0;
 397                         for (b = 0; b < 8; b++) {
 398                                 p <<= 1;
 399                                 p |= o_fb[i*8+b] ? 0x01 : 0x00;
 400                         }
 401                         tmp_fb[i] = p;
 402                 }
 403                 memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
 404                 info->fix.visual = FB_VISUAL_MONO01;
 405                 info->fix.line_length = PICOLCDFB_WIDTH / 8;
 406         } else {
 407                 int i;
 408                 memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
 409                 for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
 410                         o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
 411                 info->fix.visual = FB_VISUAL_DIRECTCOLOR;
 412                 info->fix.line_length = PICOLCDFB_WIDTH;
 413         }
 414 
 415         kfree(tmp_fb);
 416         fbdata->bpp = info->var.bits_per_pixel;
 417         return 0;
 418 }
 419 
 420 /* Note this can't be const because of struct fb_info definition */
 421 static struct fb_ops picolcdfb_ops = {
 422         .owner        = THIS_MODULE,
 423         .fb_destroy   = picolcd_fb_destroy,
 424         .fb_read      = fb_sys_read,
 425         .fb_write     = picolcd_fb_write,
 426         .fb_blank     = picolcd_fb_blank,
 427         .fb_fillrect  = picolcd_fb_fillrect,
 428         .fb_copyarea  = picolcd_fb_copyarea,
 429         .fb_imageblit = picolcd_fb_imageblit,
 430         .fb_check_var = picolcd_fb_check_var,
 431         .fb_set_par   = picolcd_set_par,
 432 };
 433 
 434 
 435 /* Callback from deferred IO workqueue */
 436 static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist)
 437 {
 438         picolcd_fb_update(info);
 439 }
 440 
 441 static const struct fb_deferred_io picolcd_fb_defio = {
 442         .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
 443         .deferred_io = picolcd_fb_deferred_io,
 444 };
 445 
 446 
 447 /*
 448  * The "fb_update_rate" sysfs attribute
 449  */
 450 static ssize_t picolcd_fb_update_rate_show(struct device *dev,
 451                 struct device_attribute *attr, char *buf)
 452 {
 453         struct picolcd_data *data = dev_get_drvdata(dev);
 454         struct picolcd_fb_data *fbdata = data->fb_info->par;
 455         unsigned i, fb_update_rate = fbdata->update_rate;
 456         size_t ret = 0;
 457 
 458         for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
 459                 if (ret >= PAGE_SIZE)
 460                         break;
 461                 else if (i == fb_update_rate)
 462                         ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
 463                 else
 464                         ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
 465         if (ret > 0)
 466                 buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
 467         return ret;
 468 }
 469 
 470 static ssize_t picolcd_fb_update_rate_store(struct device *dev,
 471                 struct device_attribute *attr, const char *buf, size_t count)
 472 {
 473         struct picolcd_data *data = dev_get_drvdata(dev);
 474         struct picolcd_fb_data *fbdata = data->fb_info->par;
 475         int i;
 476         unsigned u;
 477 
 478         if (count < 1 || count > 10)
 479                 return -EINVAL;
 480 
 481         i = sscanf(buf, "%u", &u);
 482         if (i != 1)
 483                 return -EINVAL;
 484 
 485         if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
 486                 return -ERANGE;
 487         else if (u == 0)
 488                 u = PICOLCDFB_UPDATE_RATE_DEFAULT;
 489 
 490         fbdata->update_rate = u;
 491         data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
 492         return count;
 493 }
 494 
 495 static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show,
 496                 picolcd_fb_update_rate_store);
 497 
 498 /* initialize Framebuffer device */
 499 int picolcd_init_framebuffer(struct picolcd_data *data)
 500 {
 501         struct device *dev = &data->hdev->dev;
 502         struct fb_info *info = NULL;
 503         struct picolcd_fb_data *fbdata = NULL;
 504         int i, error = -ENOMEM;
 505         u32 *palette;
 506 
 507         /* The extra memory is:
 508          * - 256*u32 for pseudo_palette
 509          * - struct fb_deferred_io
 510          */
 511         info = framebuffer_alloc(256 * sizeof(u32) +
 512                         sizeof(struct fb_deferred_io) +
 513                         sizeof(struct picolcd_fb_data) +
 514                         PICOLCDFB_SIZE, dev);
 515         if (!info)
 516                 goto err_nomem;
 517 
 518         info->fbdefio = info->par;
 519         *info->fbdefio = picolcd_fb_defio;
 520         info->par += sizeof(struct fb_deferred_io);
 521         palette = info->par;
 522         info->par += 256 * sizeof(u32);
 523         for (i = 0; i < 256; i++)
 524                 palette[i] = i > 0 && i < 16 ? 0xff : 0;
 525         info->pseudo_palette = palette;
 526         info->fbops = &picolcdfb_ops;
 527         info->var = picolcdfb_var;
 528         info->fix = picolcdfb_fix;
 529         info->fix.smem_len   = PICOLCDFB_SIZE*8;
 530         info->flags = FBINFO_FLAG_DEFAULT;
 531 
 532         fbdata = info->par;
 533         spin_lock_init(&fbdata->lock);
 534         fbdata->picolcd = data;
 535         fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
 536         fbdata->bpp     = picolcdfb_var.bits_per_pixel;
 537         fbdata->force   = 1;
 538         fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
 539         fbdata->bitmap  = vmalloc(PICOLCDFB_SIZE*8);
 540         if (fbdata->bitmap == NULL) {
 541                 dev_err(dev, "can't get a free page for framebuffer\n");
 542                 goto err_nomem;
 543         }
 544         info->screen_base = (char __force __iomem *)fbdata->bitmap;
 545         info->fix.smem_start = (unsigned long)fbdata->bitmap;
 546         memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
 547         data->fb_info = info;
 548 
 549         error = picolcd_fb_reset(data, 1);
 550         if (error) {
 551                 dev_err(dev, "failed to configure display\n");
 552                 goto err_cleanup;
 553         }
 554 
 555         error = device_create_file(dev, &dev_attr_fb_update_rate);
 556         if (error) {
 557                 dev_err(dev, "failed to create sysfs attributes\n");
 558                 goto err_cleanup;
 559         }
 560 
 561         fb_deferred_io_init(info);
 562         error = register_framebuffer(info);
 563         if (error) {
 564                 dev_err(dev, "failed to register framebuffer\n");
 565                 goto err_sysfs;
 566         }
 567         return 0;
 568 
 569 err_sysfs:
 570         device_remove_file(dev, &dev_attr_fb_update_rate);
 571         fb_deferred_io_cleanup(info);
 572 err_cleanup:
 573         data->fb_info    = NULL;
 574 
 575 err_nomem:
 576         if (fbdata)
 577                 vfree(fbdata->bitmap);
 578         framebuffer_release(info);
 579         return error;
 580 }
 581 
 582 void picolcd_exit_framebuffer(struct picolcd_data *data)
 583 {
 584         struct fb_info *info = data->fb_info;
 585         struct picolcd_fb_data *fbdata;
 586         unsigned long flags;
 587 
 588         if (!info)
 589                 return;
 590 
 591         device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
 592         fbdata = info->par;
 593 
 594         /* disconnect framebuffer from HID dev */
 595         spin_lock_irqsave(&fbdata->lock, flags);
 596         fbdata->picolcd = NULL;
 597         spin_unlock_irqrestore(&fbdata->lock, flags);
 598 
 599         /* make sure there is no running update - thus that fbdata->picolcd
 600          * once obtained under lock is guaranteed not to get free() under
 601          * the feet of the deferred work */
 602         flush_delayed_work(&info->deferred_work);
 603 
 604         data->fb_info = NULL;
 605         unregister_framebuffer(info);
 606 }

/* [<][>][^][v][top][bottom][index][help] */