hid-picolcd_fb.c (16771B)
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 .fb_mmap = fb_deferred_io_mmap, 432}; 433 434 435/* Callback from deferred IO workqueue */ 436static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagereflist) 437{ 438 picolcd_fb_update(info); 439} 440 441static 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 */ 450static 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 += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); 463 else 464 ret += scnprintf(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 470static 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 495static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show, 496 picolcd_fb_update_rate_store); 497 498/* initialize Framebuffer device */ 499int 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 569err_sysfs: 570 device_remove_file(dev, &dev_attr_fb_update_rate); 571 fb_deferred_io_cleanup(info); 572err_cleanup: 573 data->fb_info = NULL; 574 575err_nomem: 576 if (fbdata) 577 vfree(fbdata->bitmap); 578 framebuffer_release(info); 579 return error; 580} 581 582void 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}