162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/*************************************************************************** 362306a36Sopenharmony_ci * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * 462306a36Sopenharmony_ci * * 562306a36Sopenharmony_ci * Based on Logitech G13 driver (v0.4) * 662306a36Sopenharmony_ci * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * 762306a36Sopenharmony_ci * * 862306a36Sopenharmony_ci ***************************************************************************/ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/hid.h> 1162306a36Sopenharmony_ci#include <linux/vmalloc.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/fb.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "hid-picolcd.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/* Framebuffer 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * The PicoLCD use a Topway LCD module of 256x64 pixel 2162306a36Sopenharmony_ci * This display area is tiled over 4 controllers with 8 tiles 2262306a36Sopenharmony_ci * each. Each tile has 8x64 pixel, each data byte representing 2362306a36Sopenharmony_ci * a 1-bit wide vertical line of the tile. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * The display can be updated at a tile granularity. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * Chip 1 Chip 2 Chip 3 Chip 4 2862306a36Sopenharmony_ci * +----------------+----------------+----------------+----------------+ 2962306a36Sopenharmony_ci * | Tile 1 | Tile 1 | Tile 1 | Tile 1 | 3062306a36Sopenharmony_ci * +----------------+----------------+----------------+----------------+ 3162306a36Sopenharmony_ci * | Tile 2 | Tile 2 | Tile 2 | Tile 2 | 3262306a36Sopenharmony_ci * +----------------+----------------+----------------+----------------+ 3362306a36Sopenharmony_ci * ... 3462306a36Sopenharmony_ci * +----------------+----------------+----------------+----------------+ 3562306a36Sopenharmony_ci * | Tile 8 | Tile 8 | Tile 8 | Tile 8 | 3662306a36Sopenharmony_ci * +----------------+----------------+----------------+----------------+ 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci#define PICOLCDFB_NAME "picolcdfb" 3962306a36Sopenharmony_ci#define PICOLCDFB_WIDTH (256) 4062306a36Sopenharmony_ci#define PICOLCDFB_HEIGHT (64) 4162306a36Sopenharmony_ci#define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8) 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define PICOLCDFB_UPDATE_RATE_LIMIT 10 4462306a36Sopenharmony_ci#define PICOLCDFB_UPDATE_RATE_DEFAULT 2 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* Framebuffer visual structures */ 4762306a36Sopenharmony_cistatic const struct fb_fix_screeninfo picolcdfb_fix = { 4862306a36Sopenharmony_ci .id = PICOLCDFB_NAME, 4962306a36Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 5062306a36Sopenharmony_ci .visual = FB_VISUAL_MONO01, 5162306a36Sopenharmony_ci .xpanstep = 0, 5262306a36Sopenharmony_ci .ypanstep = 0, 5362306a36Sopenharmony_ci .ywrapstep = 0, 5462306a36Sopenharmony_ci .line_length = PICOLCDFB_WIDTH / 8, 5562306a36Sopenharmony_ci .accel = FB_ACCEL_NONE, 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic const struct fb_var_screeninfo picolcdfb_var = { 5962306a36Sopenharmony_ci .xres = PICOLCDFB_WIDTH, 6062306a36Sopenharmony_ci .yres = PICOLCDFB_HEIGHT, 6162306a36Sopenharmony_ci .xres_virtual = PICOLCDFB_WIDTH, 6262306a36Sopenharmony_ci .yres_virtual = PICOLCDFB_HEIGHT, 6362306a36Sopenharmony_ci .width = 103, 6462306a36Sopenharmony_ci .height = 26, 6562306a36Sopenharmony_ci .bits_per_pixel = 1, 6662306a36Sopenharmony_ci .grayscale = 1, 6762306a36Sopenharmony_ci .red = { 6862306a36Sopenharmony_ci .offset = 0, 6962306a36Sopenharmony_ci .length = 1, 7062306a36Sopenharmony_ci .msb_right = 0, 7162306a36Sopenharmony_ci }, 7262306a36Sopenharmony_ci .green = { 7362306a36Sopenharmony_ci .offset = 0, 7462306a36Sopenharmony_ci .length = 1, 7562306a36Sopenharmony_ci .msb_right = 0, 7662306a36Sopenharmony_ci }, 7762306a36Sopenharmony_ci .blue = { 7862306a36Sopenharmony_ci .offset = 0, 7962306a36Sopenharmony_ci .length = 1, 8062306a36Sopenharmony_ci .msb_right = 0, 8162306a36Sopenharmony_ci }, 8262306a36Sopenharmony_ci .transp = { 8362306a36Sopenharmony_ci .offset = 0, 8462306a36Sopenharmony_ci .length = 0, 8562306a36Sopenharmony_ci .msb_right = 0, 8662306a36Sopenharmony_ci }, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/* Send a given tile to PicoLCD */ 9062306a36Sopenharmony_cistatic int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap, 9162306a36Sopenharmony_ci int chip, int tile) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci struct hid_report *report1, *report2; 9462306a36Sopenharmony_ci unsigned long flags; 9562306a36Sopenharmony_ci u8 *tdata; 9662306a36Sopenharmony_ci int i; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev); 9962306a36Sopenharmony_ci if (!report1 || report1->maxfield != 1) 10062306a36Sopenharmony_ci return -ENODEV; 10162306a36Sopenharmony_ci report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev); 10262306a36Sopenharmony_ci if (!report2 || report2->maxfield != 1) 10362306a36Sopenharmony_ci return -ENODEV; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 10662306a36Sopenharmony_ci if ((data->status & PICOLCD_FAILED)) { 10762306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 10862306a36Sopenharmony_ci return -ENODEV; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci hid_set_field(report1->field[0], 0, chip << 2); 11162306a36Sopenharmony_ci hid_set_field(report1->field[0], 1, 0x02); 11262306a36Sopenharmony_ci hid_set_field(report1->field[0], 2, 0x00); 11362306a36Sopenharmony_ci hid_set_field(report1->field[0], 3, 0x00); 11462306a36Sopenharmony_ci hid_set_field(report1->field[0], 4, 0xb8 | tile); 11562306a36Sopenharmony_ci hid_set_field(report1->field[0], 5, 0x00); 11662306a36Sopenharmony_ci hid_set_field(report1->field[0], 6, 0x00); 11762306a36Sopenharmony_ci hid_set_field(report1->field[0], 7, 0x40); 11862306a36Sopenharmony_ci hid_set_field(report1->field[0], 8, 0x00); 11962306a36Sopenharmony_ci hid_set_field(report1->field[0], 9, 0x00); 12062306a36Sopenharmony_ci hid_set_field(report1->field[0], 10, 32); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci hid_set_field(report2->field[0], 0, (chip << 2) | 0x01); 12362306a36Sopenharmony_ci hid_set_field(report2->field[0], 1, 0x00); 12462306a36Sopenharmony_ci hid_set_field(report2->field[0], 2, 0x00); 12562306a36Sopenharmony_ci hid_set_field(report2->field[0], 3, 32); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci tdata = vbitmap + (tile * 4 + chip) * 64; 12862306a36Sopenharmony_ci for (i = 0; i < 64; i++) 12962306a36Sopenharmony_ci if (i < 32) 13062306a36Sopenharmony_ci hid_set_field(report1->field[0], 11 + i, tdata[i]); 13162306a36Sopenharmony_ci else 13262306a36Sopenharmony_ci hid_set_field(report2->field[0], 4 + i - 32, tdata[i]); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci hid_hw_request(data->hdev, report1, HID_REQ_SET_REPORT); 13562306a36Sopenharmony_ci hid_hw_request(data->hdev, report2, HID_REQ_SET_REPORT); 13662306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci/* Translate a single tile*/ 14162306a36Sopenharmony_cistatic int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp, 14262306a36Sopenharmony_ci int chip, int tile) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci int i, b, changed = 0; 14562306a36Sopenharmony_ci u8 tdata[64]; 14662306a36Sopenharmony_ci u8 *vdata = vbitmap + (tile * 4 + chip) * 64; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (bpp == 1) { 14962306a36Sopenharmony_ci for (b = 7; b >= 0; b--) { 15062306a36Sopenharmony_ci const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32; 15162306a36Sopenharmony_ci for (i = 0; i < 64; i++) { 15262306a36Sopenharmony_ci tdata[i] <<= 1; 15362306a36Sopenharmony_ci tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci } else if (bpp == 8) { 15762306a36Sopenharmony_ci for (b = 7; b >= 0; b--) { 15862306a36Sopenharmony_ci const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8; 15962306a36Sopenharmony_ci for (i = 0; i < 64; i++) { 16062306a36Sopenharmony_ci tdata[i] <<= 1; 16162306a36Sopenharmony_ci tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci } else { 16562306a36Sopenharmony_ci /* Oops, we should never get here! */ 16662306a36Sopenharmony_ci WARN_ON(1); 16762306a36Sopenharmony_ci return 0; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci for (i = 0; i < 64; i++) 17162306a36Sopenharmony_ci if (tdata[i] != vdata[i]) { 17262306a36Sopenharmony_ci changed = 1; 17362306a36Sopenharmony_ci vdata[i] = tdata[i]; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci return changed; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_civoid picolcd_fb_refresh(struct picolcd_data *data) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci if (data->fb_info) 18162306a36Sopenharmony_ci schedule_delayed_work(&data->fb_info->deferred_work, 0); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci/* Reconfigure LCD display */ 18562306a36Sopenharmony_ciint picolcd_fb_reset(struct picolcd_data *data, int clear) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev); 18862306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = data->fb_info->par; 18962306a36Sopenharmony_ci int i, j; 19062306a36Sopenharmony_ci unsigned long flags; 19162306a36Sopenharmony_ci static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 }; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (!report || report->maxfield != 1) 19462306a36Sopenharmony_ci return -ENODEV; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 19762306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 19862306a36Sopenharmony_ci for (j = 0; j < report->field[0]->maxusage; j++) 19962306a36Sopenharmony_ci if (j == 0) 20062306a36Sopenharmony_ci hid_set_field(report->field[0], j, i << 2); 20162306a36Sopenharmony_ci else if (j < sizeof(mapcmd)) 20262306a36Sopenharmony_ci hid_set_field(report->field[0], j, mapcmd[j]); 20362306a36Sopenharmony_ci else 20462306a36Sopenharmony_ci hid_set_field(report->field[0], j, 0); 20562306a36Sopenharmony_ci hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT); 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (clear) { 21062306a36Sopenharmony_ci memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE); 21162306a36Sopenharmony_ci memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp); 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci fbdata->force = 1; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* schedule first output of framebuffer */ 21662306a36Sopenharmony_ci if (fbdata->ready) 21762306a36Sopenharmony_ci schedule_delayed_work(&data->fb_info->deferred_work, 0); 21862306a36Sopenharmony_ci else 21962306a36Sopenharmony_ci fbdata->ready = 1; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci return 0; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci/* Update fb_vbitmap from the screen_buffer and send changed tiles to device */ 22562306a36Sopenharmony_cistatic void picolcd_fb_update(struct fb_info *info) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci int chip, tile, n; 22862306a36Sopenharmony_ci unsigned long flags; 22962306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = info->par; 23062306a36Sopenharmony_ci struct picolcd_data *data; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci mutex_lock(&info->lock); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci spin_lock_irqsave(&fbdata->lock, flags); 23562306a36Sopenharmony_ci if (!fbdata->ready && fbdata->picolcd) 23662306a36Sopenharmony_ci picolcd_fb_reset(fbdata->picolcd, 0); 23762306a36Sopenharmony_ci spin_unlock_irqrestore(&fbdata->lock, flags); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* 24062306a36Sopenharmony_ci * Translate the framebuffer into the format needed by the PicoLCD. 24162306a36Sopenharmony_ci * See display layout above. 24262306a36Sopenharmony_ci * Do this one tile after the other and push those tiles that changed. 24362306a36Sopenharmony_ci * 24462306a36Sopenharmony_ci * Wait for our IO to complete as otherwise we might flood the queue! 24562306a36Sopenharmony_ci */ 24662306a36Sopenharmony_ci n = 0; 24762306a36Sopenharmony_ci for (chip = 0; chip < 4; chip++) 24862306a36Sopenharmony_ci for (tile = 0; tile < 8; tile++) { 24962306a36Sopenharmony_ci if (!fbdata->force && !picolcd_fb_update_tile( 25062306a36Sopenharmony_ci fbdata->vbitmap, fbdata->bitmap, 25162306a36Sopenharmony_ci fbdata->bpp, chip, tile)) 25262306a36Sopenharmony_ci continue; 25362306a36Sopenharmony_ci n += 2; 25462306a36Sopenharmony_ci if (n >= HID_OUTPUT_FIFO_SIZE / 2) { 25562306a36Sopenharmony_ci spin_lock_irqsave(&fbdata->lock, flags); 25662306a36Sopenharmony_ci data = fbdata->picolcd; 25762306a36Sopenharmony_ci spin_unlock_irqrestore(&fbdata->lock, flags); 25862306a36Sopenharmony_ci mutex_unlock(&info->lock); 25962306a36Sopenharmony_ci if (!data) 26062306a36Sopenharmony_ci return; 26162306a36Sopenharmony_ci hid_hw_wait(data->hdev); 26262306a36Sopenharmony_ci mutex_lock(&info->lock); 26362306a36Sopenharmony_ci n = 0; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci spin_lock_irqsave(&fbdata->lock, flags); 26662306a36Sopenharmony_ci data = fbdata->picolcd; 26762306a36Sopenharmony_ci spin_unlock_irqrestore(&fbdata->lock, flags); 26862306a36Sopenharmony_ci if (!data || picolcd_fb_send_tile(data, 26962306a36Sopenharmony_ci fbdata->vbitmap, chip, tile)) 27062306a36Sopenharmony_ci goto out; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci fbdata->force = false; 27362306a36Sopenharmony_ci if (n) { 27462306a36Sopenharmony_ci spin_lock_irqsave(&fbdata->lock, flags); 27562306a36Sopenharmony_ci data = fbdata->picolcd; 27662306a36Sopenharmony_ci spin_unlock_irqrestore(&fbdata->lock, flags); 27762306a36Sopenharmony_ci mutex_unlock(&info->lock); 27862306a36Sopenharmony_ci if (data) 27962306a36Sopenharmony_ci hid_hw_wait(data->hdev); 28062306a36Sopenharmony_ci return; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ciout: 28362306a36Sopenharmony_ci mutex_unlock(&info->lock); 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci/* Stub to call the system default and update the image on the picoLCD */ 28762306a36Sopenharmony_cistatic void picolcd_fb_fillrect(struct fb_info *info, 28862306a36Sopenharmony_ci const struct fb_fillrect *rect) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci if (!info->par) 29162306a36Sopenharmony_ci return; 29262306a36Sopenharmony_ci sys_fillrect(info, rect); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci schedule_delayed_work(&info->deferred_work, 0); 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci/* Stub to call the system default and update the image on the picoLCD */ 29862306a36Sopenharmony_cistatic void picolcd_fb_copyarea(struct fb_info *info, 29962306a36Sopenharmony_ci const struct fb_copyarea *area) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci if (!info->par) 30262306a36Sopenharmony_ci return; 30362306a36Sopenharmony_ci sys_copyarea(info, area); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci schedule_delayed_work(&info->deferred_work, 0); 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci/* Stub to call the system default and update the image on the picoLCD */ 30962306a36Sopenharmony_cistatic void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci if (!info->par) 31262306a36Sopenharmony_ci return; 31362306a36Sopenharmony_ci sys_imageblit(info, image); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci schedule_delayed_work(&info->deferred_work, 0); 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci/* 31962306a36Sopenharmony_ci * this is the slow path from userspace. they can seek and write to 32062306a36Sopenharmony_ci * the fb. it's inefficient to do anything less than a full screen draw 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_cistatic ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf, 32362306a36Sopenharmony_ci size_t count, loff_t *ppos) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci ssize_t ret; 32662306a36Sopenharmony_ci if (!info->par) 32762306a36Sopenharmony_ci return -ENODEV; 32862306a36Sopenharmony_ci ret = fb_sys_write(info, buf, count, ppos); 32962306a36Sopenharmony_ci if (ret >= 0) 33062306a36Sopenharmony_ci schedule_delayed_work(&info->deferred_work, 0); 33162306a36Sopenharmony_ci return ret; 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic int picolcd_fb_blank(int blank, struct fb_info *info) 33562306a36Sopenharmony_ci{ 33662306a36Sopenharmony_ci /* We let fb notification do this for us via lcd/backlight device */ 33762306a36Sopenharmony_ci return 0; 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic void picolcd_fb_destroy(struct fb_info *info) 34162306a36Sopenharmony_ci{ 34262306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = info->par; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci /* make sure no work is deferred */ 34562306a36Sopenharmony_ci fb_deferred_io_cleanup(info); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* No thridparty should ever unregister our framebuffer! */ 34862306a36Sopenharmony_ci WARN_ON(fbdata->picolcd != NULL); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci vfree((u8 *)info->fix.smem_start); 35162306a36Sopenharmony_ci framebuffer_release(info); 35262306a36Sopenharmony_ci} 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_cistatic int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) 35562306a36Sopenharmony_ci{ 35662306a36Sopenharmony_ci __u32 bpp = var->bits_per_pixel; 35762306a36Sopenharmony_ci __u32 activate = var->activate; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* only allow 1/8 bit depth (8-bit is grayscale) */ 36062306a36Sopenharmony_ci *var = picolcdfb_var; 36162306a36Sopenharmony_ci var->activate = activate; 36262306a36Sopenharmony_ci if (bpp >= 8) { 36362306a36Sopenharmony_ci var->bits_per_pixel = 8; 36462306a36Sopenharmony_ci var->red.length = 8; 36562306a36Sopenharmony_ci var->green.length = 8; 36662306a36Sopenharmony_ci var->blue.length = 8; 36762306a36Sopenharmony_ci } else { 36862306a36Sopenharmony_ci var->bits_per_pixel = 1; 36962306a36Sopenharmony_ci var->red.length = 1; 37062306a36Sopenharmony_ci var->green.length = 1; 37162306a36Sopenharmony_ci var->blue.length = 1; 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci return 0; 37462306a36Sopenharmony_ci} 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_cistatic int picolcd_set_par(struct fb_info *info) 37762306a36Sopenharmony_ci{ 37862306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = info->par; 37962306a36Sopenharmony_ci u8 *tmp_fb, *o_fb; 38062306a36Sopenharmony_ci if (info->var.bits_per_pixel == fbdata->bpp) 38162306a36Sopenharmony_ci return 0; 38262306a36Sopenharmony_ci /* switch between 1/8 bit depths */ 38362306a36Sopenharmony_ci if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8) 38462306a36Sopenharmony_ci return -EINVAL; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci o_fb = fbdata->bitmap; 38762306a36Sopenharmony_ci tmp_fb = kmalloc_array(PICOLCDFB_SIZE, info->var.bits_per_pixel, 38862306a36Sopenharmony_ci GFP_KERNEL); 38962306a36Sopenharmony_ci if (!tmp_fb) 39062306a36Sopenharmony_ci return -ENOMEM; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* translate FB content to new bits-per-pixel */ 39362306a36Sopenharmony_ci if (info->var.bits_per_pixel == 1) { 39462306a36Sopenharmony_ci int i, b; 39562306a36Sopenharmony_ci for (i = 0; i < PICOLCDFB_SIZE; i++) { 39662306a36Sopenharmony_ci u8 p = 0; 39762306a36Sopenharmony_ci for (b = 0; b < 8; b++) { 39862306a36Sopenharmony_ci p <<= 1; 39962306a36Sopenharmony_ci p |= o_fb[i*8+b] ? 0x01 : 0x00; 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci tmp_fb[i] = p; 40262306a36Sopenharmony_ci } 40362306a36Sopenharmony_ci memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE); 40462306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_MONO01; 40562306a36Sopenharmony_ci info->fix.line_length = PICOLCDFB_WIDTH / 8; 40662306a36Sopenharmony_ci } else { 40762306a36Sopenharmony_ci int i; 40862306a36Sopenharmony_ci memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE); 40962306a36Sopenharmony_ci for (i = 0; i < PICOLCDFB_SIZE * 8; i++) 41062306a36Sopenharmony_ci o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; 41162306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_DIRECTCOLOR; 41262306a36Sopenharmony_ci info->fix.line_length = PICOLCDFB_WIDTH; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci kfree(tmp_fb); 41662306a36Sopenharmony_ci fbdata->bpp = info->var.bits_per_pixel; 41762306a36Sopenharmony_ci return 0; 41862306a36Sopenharmony_ci} 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_cistatic const struct fb_ops picolcdfb_ops = { 42162306a36Sopenharmony_ci .owner = THIS_MODULE, 42262306a36Sopenharmony_ci .fb_destroy = picolcd_fb_destroy, 42362306a36Sopenharmony_ci .fb_read = fb_sys_read, 42462306a36Sopenharmony_ci .fb_write = picolcd_fb_write, 42562306a36Sopenharmony_ci .fb_blank = picolcd_fb_blank, 42662306a36Sopenharmony_ci .fb_fillrect = picolcd_fb_fillrect, 42762306a36Sopenharmony_ci .fb_copyarea = picolcd_fb_copyarea, 42862306a36Sopenharmony_ci .fb_imageblit = picolcd_fb_imageblit, 42962306a36Sopenharmony_ci .fb_check_var = picolcd_fb_check_var, 43062306a36Sopenharmony_ci .fb_set_par = picolcd_set_par, 43162306a36Sopenharmony_ci .fb_mmap = fb_deferred_io_mmap, 43262306a36Sopenharmony_ci}; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci/* Callback from deferred IO workqueue */ 43662306a36Sopenharmony_cistatic void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagereflist) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci picolcd_fb_update(info); 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic const struct fb_deferred_io picolcd_fb_defio = { 44262306a36Sopenharmony_ci .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT, 44362306a36Sopenharmony_ci .deferred_io = picolcd_fb_deferred_io, 44462306a36Sopenharmony_ci}; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci/* 44862306a36Sopenharmony_ci * The "fb_update_rate" sysfs attribute 44962306a36Sopenharmony_ci */ 45062306a36Sopenharmony_cistatic ssize_t picolcd_fb_update_rate_show(struct device *dev, 45162306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci struct picolcd_data *data = dev_get_drvdata(dev); 45462306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = data->fb_info->par; 45562306a36Sopenharmony_ci unsigned i, fb_update_rate = fbdata->update_rate; 45662306a36Sopenharmony_ci size_t ret = 0; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++) 45962306a36Sopenharmony_ci if (ret >= PAGE_SIZE) 46062306a36Sopenharmony_ci break; 46162306a36Sopenharmony_ci else if (i == fb_update_rate) 46262306a36Sopenharmony_ci ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); 46362306a36Sopenharmony_ci else 46462306a36Sopenharmony_ci ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i); 46562306a36Sopenharmony_ci if (ret > 0) 46662306a36Sopenharmony_ci buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n'; 46762306a36Sopenharmony_ci return ret; 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_cistatic ssize_t picolcd_fb_update_rate_store(struct device *dev, 47162306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 47262306a36Sopenharmony_ci{ 47362306a36Sopenharmony_ci struct picolcd_data *data = dev_get_drvdata(dev); 47462306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = data->fb_info->par; 47562306a36Sopenharmony_ci int i; 47662306a36Sopenharmony_ci unsigned u; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci if (count < 1 || count > 10) 47962306a36Sopenharmony_ci return -EINVAL; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci i = sscanf(buf, "%u", &u); 48262306a36Sopenharmony_ci if (i != 1) 48362306a36Sopenharmony_ci return -EINVAL; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (u > PICOLCDFB_UPDATE_RATE_LIMIT) 48662306a36Sopenharmony_ci return -ERANGE; 48762306a36Sopenharmony_ci else if (u == 0) 48862306a36Sopenharmony_ci u = PICOLCDFB_UPDATE_RATE_DEFAULT; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci fbdata->update_rate = u; 49162306a36Sopenharmony_ci data->fb_info->fbdefio->delay = HZ / fbdata->update_rate; 49262306a36Sopenharmony_ci return count; 49362306a36Sopenharmony_ci} 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_cistatic DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show, 49662306a36Sopenharmony_ci picolcd_fb_update_rate_store); 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci/* initialize Framebuffer device */ 49962306a36Sopenharmony_ciint picolcd_init_framebuffer(struct picolcd_data *data) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci struct device *dev = &data->hdev->dev; 50262306a36Sopenharmony_ci struct fb_info *info = NULL; 50362306a36Sopenharmony_ci struct picolcd_fb_data *fbdata = NULL; 50462306a36Sopenharmony_ci int i, error = -ENOMEM; 50562306a36Sopenharmony_ci u32 *palette; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci /* The extra memory is: 50862306a36Sopenharmony_ci * - 256*u32 for pseudo_palette 50962306a36Sopenharmony_ci * - struct fb_deferred_io 51062306a36Sopenharmony_ci */ 51162306a36Sopenharmony_ci info = framebuffer_alloc(256 * sizeof(u32) + 51262306a36Sopenharmony_ci sizeof(struct fb_deferred_io) + 51362306a36Sopenharmony_ci sizeof(struct picolcd_fb_data) + 51462306a36Sopenharmony_ci PICOLCDFB_SIZE, dev); 51562306a36Sopenharmony_ci if (!info) 51662306a36Sopenharmony_ci goto err_nomem; 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci info->fbdefio = info->par; 51962306a36Sopenharmony_ci *info->fbdefio = picolcd_fb_defio; 52062306a36Sopenharmony_ci info->par += sizeof(struct fb_deferred_io); 52162306a36Sopenharmony_ci palette = info->par; 52262306a36Sopenharmony_ci info->par += 256 * sizeof(u32); 52362306a36Sopenharmony_ci for (i = 0; i < 256; i++) 52462306a36Sopenharmony_ci palette[i] = i > 0 && i < 16 ? 0xff : 0; 52562306a36Sopenharmony_ci info->pseudo_palette = palette; 52662306a36Sopenharmony_ci info->fbops = &picolcdfb_ops; 52762306a36Sopenharmony_ci info->var = picolcdfb_var; 52862306a36Sopenharmony_ci info->fix = picolcdfb_fix; 52962306a36Sopenharmony_ci info->fix.smem_len = PICOLCDFB_SIZE*8; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci fbdata = info->par; 53262306a36Sopenharmony_ci spin_lock_init(&fbdata->lock); 53362306a36Sopenharmony_ci fbdata->picolcd = data; 53462306a36Sopenharmony_ci fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; 53562306a36Sopenharmony_ci fbdata->bpp = picolcdfb_var.bits_per_pixel; 53662306a36Sopenharmony_ci fbdata->force = 1; 53762306a36Sopenharmony_ci fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data); 53862306a36Sopenharmony_ci fbdata->bitmap = vmalloc(PICOLCDFB_SIZE*8); 53962306a36Sopenharmony_ci if (fbdata->bitmap == NULL) { 54062306a36Sopenharmony_ci dev_err(dev, "can't get a free page for framebuffer\n"); 54162306a36Sopenharmony_ci goto err_nomem; 54262306a36Sopenharmony_ci } 54362306a36Sopenharmony_ci info->screen_buffer = fbdata->bitmap; 54462306a36Sopenharmony_ci info->fix.smem_start = (unsigned long)fbdata->bitmap; 54562306a36Sopenharmony_ci memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE); 54662306a36Sopenharmony_ci data->fb_info = info; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci error = picolcd_fb_reset(data, 1); 54962306a36Sopenharmony_ci if (error) { 55062306a36Sopenharmony_ci dev_err(dev, "failed to configure display\n"); 55162306a36Sopenharmony_ci goto err_cleanup; 55262306a36Sopenharmony_ci } 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci error = device_create_file(dev, &dev_attr_fb_update_rate); 55562306a36Sopenharmony_ci if (error) { 55662306a36Sopenharmony_ci dev_err(dev, "failed to create sysfs attributes\n"); 55762306a36Sopenharmony_ci goto err_cleanup; 55862306a36Sopenharmony_ci } 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci fb_deferred_io_init(info); 56162306a36Sopenharmony_ci error = register_framebuffer(info); 56262306a36Sopenharmony_ci if (error) { 56362306a36Sopenharmony_ci dev_err(dev, "failed to register framebuffer\n"); 56462306a36Sopenharmony_ci goto err_sysfs; 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci return 0; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_cierr_sysfs: 56962306a36Sopenharmony_ci device_remove_file(dev, &dev_attr_fb_update_rate); 57062306a36Sopenharmony_ci fb_deferred_io_cleanup(info); 57162306a36Sopenharmony_cierr_cleanup: 57262306a36Sopenharmony_ci data->fb_info = NULL; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_cierr_nomem: 57562306a36Sopenharmony_ci if (fbdata) 57662306a36Sopenharmony_ci vfree(fbdata->bitmap); 57762306a36Sopenharmony_ci framebuffer_release(info); 57862306a36Sopenharmony_ci return error; 57962306a36Sopenharmony_ci} 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_civoid picolcd_exit_framebuffer(struct picolcd_data *data) 58262306a36Sopenharmony_ci{ 58362306a36Sopenharmony_ci struct fb_info *info = data->fb_info; 58462306a36Sopenharmony_ci struct picolcd_fb_data *fbdata; 58562306a36Sopenharmony_ci unsigned long flags; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci if (!info) 58862306a36Sopenharmony_ci return; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); 59162306a36Sopenharmony_ci fbdata = info->par; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci /* disconnect framebuffer from HID dev */ 59462306a36Sopenharmony_ci spin_lock_irqsave(&fbdata->lock, flags); 59562306a36Sopenharmony_ci fbdata->picolcd = NULL; 59662306a36Sopenharmony_ci spin_unlock_irqrestore(&fbdata->lock, flags); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci /* make sure there is no running update - thus that fbdata->picolcd 59962306a36Sopenharmony_ci * once obtained under lock is guaranteed not to get free() under 60062306a36Sopenharmony_ci * the feet of the deferred work */ 60162306a36Sopenharmony_ci flush_delayed_work(&info->deferred_work); 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci data->fb_info = NULL; 60462306a36Sopenharmony_ci unregister_framebuffer(info); 60562306a36Sopenharmony_ci} 606