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