linux/drivers/hid/hid-picolcd_fb.c
<<
>>
Prefs
   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 */
  47static 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
  58static 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 */
  90static 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*/
 141static 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
 178void 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 */
 185int 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 */
 225static 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        }
 282out:
 283        mutex_unlock(&info->lock);
 284}
 285
 286/* Stub to call the system default and update the image on the picoLCD */
 287static 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 */
 298static 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 */
 309static 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 */
 322static 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
 334static 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
 340static 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
 354static 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
 376static 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
 420static const struct fb_ops picolcdfb_ops = {
 421        .owner        = THIS_MODULE,
 422        .fb_destroy   = picolcd_fb_destroy,
 423        .fb_read      = fb_sys_read,
 424        .fb_write     = picolcd_fb_write,
 425        .fb_blank     = picolcd_fb_blank,
 426        .fb_fillrect  = picolcd_fb_fillrect,
 427        .fb_copyarea  = picolcd_fb_copyarea,
 428        .fb_imageblit = picolcd_fb_imageblit,
 429        .fb_check_var = picolcd_fb_check_var,
 430        .fb_set_par   = picolcd_set_par,
 431};
 432
 433
 434/* Callback from deferred IO workqueue */
 435static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist)
 436{
 437        picolcd_fb_update(info);
 438}
 439
 440static const struct fb_deferred_io picolcd_fb_defio = {
 441        .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
 442        .deferred_io = picolcd_fb_deferred_io,
 443};
 444
 445
 446/*
 447 * The "fb_update_rate" sysfs attribute
 448 */
 449static ssize_t picolcd_fb_update_rate_show(struct device *dev,
 450                struct device_attribute *attr, char *buf)
 451{
 452        struct picolcd_data *data = dev_get_drvdata(dev);
 453        struct picolcd_fb_data *fbdata = data->fb_info->par;
 454        unsigned i, fb_update_rate = fbdata->update_rate;
 455        size_t ret = 0;
 456
 457        for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
 458                if (ret >= PAGE_SIZE)
 459                        break;
 460                else if (i == fb_update_rate)
 461                        ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
 462                else
 463                        ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
 464        if (ret > 0)
 465                buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
 466        return ret;
 467}
 468
 469static ssize_t picolcd_fb_update_rate_store(struct device *dev,
 470                struct device_attribute *attr, const char *buf, size_t count)
 471{
 472        struct picolcd_data *data = dev_get_drvdata(dev);
 473        struct picolcd_fb_data *fbdata = data->fb_info->par;
 474        int i;
 475        unsigned u;
 476
 477        if (count < 1 || count > 10)
 478                return -EINVAL;
 479
 480        i = sscanf(buf, "%u", &u);
 481        if (i != 1)
 482                return -EINVAL;
 483
 484        if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
 485                return -ERANGE;
 486        else if (u == 0)
 487                u = PICOLCDFB_UPDATE_RATE_DEFAULT;
 488
 489        fbdata->update_rate = u;
 490        data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
 491        return count;
 492}
 493
 494static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show,
 495                picolcd_fb_update_rate_store);
 496
 497/* initialize Framebuffer device */
 498int picolcd_init_framebuffer(struct picolcd_data *data)
 499{
 500        struct device *dev = &data->hdev->dev;
 501        struct fb_info *info = NULL;
 502        struct picolcd_fb_data *fbdata = NULL;
 503        int i, error = -ENOMEM;
 504        u32 *palette;
 505
 506        /* The extra memory is:
 507         * - 256*u32 for pseudo_palette
 508         * - struct fb_deferred_io
 509         */
 510        info = framebuffer_alloc(256 * sizeof(u32) +
 511                        sizeof(struct fb_deferred_io) +
 512                        sizeof(struct picolcd_fb_data) +
 513                        PICOLCDFB_SIZE, dev);
 514        if (!info)
 515                goto err_nomem;
 516
 517        info->fbdefio = info->par;
 518        *info->fbdefio = picolcd_fb_defio;
 519        info->par += sizeof(struct fb_deferred_io);
 520        palette = info->par;
 521        info->par += 256 * sizeof(u32);
 522        for (i = 0; i < 256; i++)
 523                palette[i] = i > 0 && i < 16 ? 0xff : 0;
 524        info->pseudo_palette = palette;
 525        info->fbops = &picolcdfb_ops;
 526        info->var = picolcdfb_var;
 527        info->fix = picolcdfb_fix;
 528        info->fix.smem_len   = PICOLCDFB_SIZE*8;
 529        info->flags = FBINFO_FLAG_DEFAULT;
 530
 531        fbdata = info->par;
 532        spin_lock_init(&fbdata->lock);
 533        fbdata->picolcd = data;
 534        fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
 535        fbdata->bpp     = picolcdfb_var.bits_per_pixel;
 536        fbdata->force   = 1;
 537        fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
 538        fbdata->bitmap  = vmalloc(PICOLCDFB_SIZE*8);
 539        if (fbdata->bitmap == NULL) {
 540                dev_err(dev, "can't get a free page for framebuffer\n");
 541                goto err_nomem;
 542        }
 543        info->screen_base = (char __force __iomem *)fbdata->bitmap;
 544        info->fix.smem_start = (unsigned long)fbdata->bitmap;
 545        memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
 546        data->fb_info = info;
 547
 548        error = picolcd_fb_reset(data, 1);
 549        if (error) {
 550                dev_err(dev, "failed to configure display\n");
 551                goto err_cleanup;
 552        }
 553
 554        error = device_create_file(dev, &dev_attr_fb_update_rate);
 555        if (error) {
 556                dev_err(dev, "failed to create sysfs attributes\n");
 557                goto err_cleanup;
 558        }
 559
 560        fb_deferred_io_init(info);
 561        error = register_framebuffer(info);
 562        if (error) {
 563                dev_err(dev, "failed to register framebuffer\n");
 564                goto err_sysfs;
 565        }
 566        return 0;
 567
 568err_sysfs:
 569        device_remove_file(dev, &dev_attr_fb_update_rate);
 570        fb_deferred_io_cleanup(info);
 571err_cleanup:
 572        data->fb_info    = NULL;
 573
 574err_nomem:
 575        if (fbdata)
 576                vfree(fbdata->bitmap);
 577        framebuffer_release(info);
 578        return error;
 579}
 580
 581void picolcd_exit_framebuffer(struct picolcd_data *data)
 582{
 583        struct fb_info *info = data->fb_info;
 584        struct picolcd_fb_data *fbdata;
 585        unsigned long flags;
 586
 587        if (!info)
 588                return;
 589
 590        device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
 591        fbdata = info->par;
 592
 593        /* disconnect framebuffer from HID dev */
 594        spin_lock_irqsave(&fbdata->lock, flags);
 595        fbdata->picolcd = NULL;
 596        spin_unlock_irqrestore(&fbdata->lock, flags);
 597
 598        /* make sure there is no running update - thus that fbdata->picolcd
 599         * once obtained under lock is guaranteed not to get free() under
 600         * the feet of the deferred work */
 601        flush_delayed_work(&info->deferred_work);
 602
 603        data->fb_info = NULL;
 604        unregister_framebuffer(info);
 605}
 606