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