From mboxrd@z Thu Jan 1 00:00:00 1970 From: Bruno =?UTF-8?B?UHLDqW1vbnQ=?= Subject: [PATCH v4 2/6] hid: add framebuffer support to PicoLCD device Date: Sat, 27 Mar 2010 01:22:49 +0100 Message-ID: <20100327012249.29b11ad7@neptune.home> References: <20100324233707.7243b04d@neptune.home> <20100324234945.6b7d1f37@neptune.home> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: QUOTED-PRINTABLE Return-path: In-Reply-To: <20100324234945.6b7d1f37@neptune.home> Sender: linux-kernel-owner@vger.kernel.org To: Jiri Kosina Cc: linux-input@vger.kernel.org, linux-usb@vger.kernel.org, linux-fbdev@vger.kernel.org, linux-kernel@vger.kernel.org, "Rick L. Vinyard Jr." , Nicu Pavel , Oliver Neukum , Jaya Kumar , Dmitry Torokhov List-Id: linux-input@vger.kernel.org Add framebuffer support to PicoLCD device with use of deferred-io. Only changed areas of framebuffer get sent to device in order to save USB bandwidth and especially resources on PicoLCD device or allow higher refresh rate for a small area. Changed tiles are determined while updating shadow framebuffer. Changes since v3: - document fb_update_rate sysfs attribute - change fb_update_rate read behavior to match enum with selected in brackets presentation Changes since v2: - add 8bit grayscale depth as near to no userspace app handles 1bit - properly send whole framebuffer on reset/init - drop inline keywords from non-stub functions - use device_{create,remove}_file instead of sysfs_{create,remove}_fil= e Signed-off-by: Bruno Pr=C3=A9mont --- Documentation/ABI/testing/sysfs-driver-hid-picolcd | 17 + drivers/hid/Kconfig | 7 +- drivers/hid/hid-picolcd.c | 553 ++++++++++++= +++++++- 3 files changed, 575 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-hid-picolcd b/Docum= entation/ABI/testing/sysfs-driver-hid-picolcd index 6fb4f21..14f52d7 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-picolcd +++ b/Documentation/ABI/testing/sysfs-driver-hid-picolcd @@ -15,3 +15,20 @@ Description: Make it possible to switch the PicoLCD = device between LCD disconnected and reconnects after above delay (default value is 5 seconds though this default should not be relied on). =20 + +What: /sys/bus/usb/devices/-:./::./fb_update_rate +Date: March 2010 +Contact: Bruno Pr=C3=A9mont +Description: Make it possible to adjust defio refresh rate. + + Reading: returns list of available refresh rates (expressed in Hz), + the active refresh rate being enclosed in brackets ('[' and ']') + + Writing: accepts new refresh rate expressed in integer Hz + within permitted rates. + + Note: As device can barely do 2 complete refreshes a second + it only makes sense to adjust this value if only one or two + tiles get changed and it's not appropriate to expect the application + to flush it's tiny changes explicitely at higher than default rate. + diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 138ba6a..a813ea9 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -265,6 +265,11 @@ config HID_PETALYNX config HID_PICOLCD tristate "PicoLCD (graphic version)" depends on USB_HID + select FB_DEFERRED_IO if FB + select FB_SYS_FILLRECT if FB + select FB_SYS_COPYAREA if FB + select FB_SYS_IMAGEBLIT if FB + select FB_SYS_FOPS if FB ---help--- This provides support for Minibox PicoLCD devices, currently only the graphical ones are supported. @@ -272,8 +277,8 @@ config HID_PICOLCD This includes support for the following device features: - Keypad - Switching between Firmware and Flash mode - Features that are not (yet) supported: - Framebuffer for monochrome 256x64 display + Features that are not (yet) supported: - Backlight control - Contrast control - IR diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 28f8ea0..b96ea05 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -24,6 +24,9 @@ #include "usbhid/usbhid.h" #include =20 +#include +#include + #include #include =20 @@ -69,6 +72,59 @@ #define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */ #define REPORT_EXIT_FLASHER 0xff /* Bootloader= : OUT[2] */ =20 +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +/* Framebuffer + * + * The PicoLCD use a Topway LCD module of 256x64 pixel + * This display area is tiled over 4 controllers with 8 tiles + * each. Each tile has 8x64 pixel, each data byte representing + * a 1-bit wide vertical line of the tile. + * + * The display can be updated at a tile granularity. + * + * Chip 1 Chip 2 Chip 3 Chip 4 + * +----------------+----------------+----------------+---------------= -+ + * | Tile 1 | Tile 1 | Tile 1 | Tile 1 = | + * +----------------+----------------+----------------+---------------= -+ + * | Tile 2 | Tile 2 | Tile 2 | Tile 2 = | + * +----------------+----------------+----------------+---------------= -+ + * ... + * +----------------+----------------+----------------+---------------= -+ + * | Tile 8 | Tile 8 | Tile 8 | Tile 8 = | + * +----------------+----------------+----------------+---------------= -+ + */ +#define PICOLCDFB_NAME "picolcdfb" +#define PICOLCDFB_WIDTH (256) +#define PICOLCDFB_HEIGHT (64) +#define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8) + +#define PICOLCDFB_UPDATE_RATE_LIMIT 10 +#define PICOLCDFB_UPDATE_RATE_DEFAULT 2 + +/* Framebuffer visual structures */ +static const struct fb_fix_screeninfo picolcdfb_fix =3D { + .id =3D PICOLCDFB_NAME, + .type =3D FB_TYPE_PACKED_PIXELS, + .visual =3D FB_VISUAL_MONO01, + .xpanstep =3D 0, + .ypanstep =3D 0, + .ywrapstep =3D 0, + .line_length =3D PICOLCDFB_WIDTH / 8, + .accel =3D FB_ACCEL_NONE, +}; + +static const struct fb_var_screeninfo picolcdfb_var =3D { + .xres =3D PICOLCDFB_WIDTH, + .yres =3D PICOLCDFB_HEIGHT, + .xres_virtual =3D PICOLCDFB_WIDTH, + .yres_virtual =3D PICOLCDFB_HEIGHT, + .width =3D 103, + .height =3D 26, + .bits_per_pixel =3D 1, + .grayscale =3D 1, +}; +#endif /* CONFIG_FB */ + /* Input device * * The PicoLCD has an IR receiver header, a built-in keypad with 5 key= s @@ -118,6 +174,16 @@ struct picolcd_data { struct input_dev *input_cir; unsigned short keycode[PICOLCD_KEYS]; =20 +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) + /* Framebuffer stuff */ + u8 fb_update_rate; + u8 fb_bpp; + u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ + u8 *fb_bitmap; /* framebuffer */ + struct fb_info *fb_info; + struct fb_deferred_io fb_defio; +#endif /* CONFIG_FB */ + /* Housekeeping stuff */ spinlock_t lock; struct mutex mutex; @@ -125,6 +191,7 @@ struct picolcd_data { int status; #define PICOLCD_BOOTLOADER 1 #define PICOLCD_FAILED 2 +#define PICOLCD_READY_FB 4 }; =20 =20 @@ -197,6 +264,469 @@ static struct picolcd_pending *picolcd_send_and_w= ait(struct hid_device *hdev, return work; } =20 +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +/* Send a given tile to PicoLCD */ +static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int= tile) +{ + struct picolcd_data *data =3D hid_get_drvdata(hdev); + struct hid_report *report1 =3D picolcd_out_report(REPORT_LCD_CMD_DATA= , hdev); + struct hid_report *report2 =3D picolcd_out_report(REPORT_LCD_DATA, hd= ev); + unsigned long flags; + u8 *tdata; + int i; + + if (!report1 || report1->maxfield !=3D 1 || !report2 || report2->maxf= ield !=3D 1) + return -ENODEV; + + spin_lock_irqsave(&data->lock, flags); + hid_set_field(report1->field[0], 0, chip << 2); + hid_set_field(report1->field[0], 1, 0x02); + hid_set_field(report1->field[0], 2, 0x00); + hid_set_field(report1->field[0], 3, 0x00); + hid_set_field(report1->field[0], 4, 0xb8 | tile); + hid_set_field(report1->field[0], 5, 0x00); + hid_set_field(report1->field[0], 6, 0x00); + hid_set_field(report1->field[0], 7, 0x40); + hid_set_field(report1->field[0], 8, 0x00); + hid_set_field(report1->field[0], 9, 0x00); + hid_set_field(report1->field[0], 10, 32); + + hid_set_field(report2->field[0], 0, (chip << 2) | 0x01); + hid_set_field(report2->field[0], 1, 0x00); + hid_set_field(report2->field[0], 2, 0x00); + hid_set_field(report2->field[0], 3, 32); + + tdata =3D data->fb_vbitmap + (tile * 4 + chip) * 64; + for (i =3D 0; i < 64; i++) + if (i < 32) + hid_set_field(report1->field[0], 11 + i, tdata[i]); + else + hid_set_field(report2->field[0], 4 + i - 32, tdata[i]); + + usbhid_submit_report(data->hdev, report1, USB_DIR_OUT); + usbhid_submit_report(data->hdev, report2, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + return 0; +} + +/* Translate a single tile*/ +static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int b= pp, + int chip, int tile) +{ + int i, b, changed =3D 0; + u8 tdata[64]; + u8 *vdata =3D vbitmap + (tile * 4 + chip) * 64; + + if (bpp =3D=3D 1) { + for (b =3D 7; b >=3D 0; b--) { + const u8 *bdata =3D bitmap + tile * 256 + chip * 8 + b * 32; + for (i =3D 0; i < 64; i++) { + tdata[i] <<=3D 1; + tdata[i] |=3D (bdata[i/8] >> (7 - i % 8)) & 0x01; + } + } + } else if (bpp =3D=3D 8) { + for (b =3D 7; b >=3D 0; b--) { + const u8 *bdata =3D bitmap + (tile * 256 + chip * 8 + b * 32) * 8; + for (i =3D 0; i < 64; i++) { + tdata[i] <<=3D 1; + tdata[i] |=3D (bdata[i] & 0x80) ? 0x01 : 0x00; + } + } + } else { + /* Oops, we should never get here! */ + WARN_ON(1); + return 0; + } + + for (i =3D 0; i < 64; i++) + if (tdata[i] !=3D vdata[i]) { + changed =3D 1; + vdata[i] =3D tdata[i]; + } + return changed; +} + +/* Reconfigure LCD display */ +static int picolcd_fb_reset(struct picolcd_data *data, int clear) +{ + struct hid_report *report =3D picolcd_out_report(REPORT_LCD_CMD, data= ->hdev); + int i, j; + unsigned long flags; + static const u8 mapcmd[8] =3D { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0= x64, 0xc0 }; + + if (!report || report->maxfield !=3D 1) + return -ENODEV; + + spin_lock_irqsave(&data->lock, flags); + for (i =3D 0; i < 4; i++) { + for (j =3D 0; j < report->field[0]->maxusage; j++) + if (j =3D=3D 0) + hid_set_field(report->field[0], j, i << 2); + else if (j < sizeof(mapcmd)) + hid_set_field(report->field[0], j, mapcmd[j]); + else + hid_set_field(report->field[0], j, 0); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + } + + data->status |=3D PICOLCD_READY_FB; + spin_unlock_irqrestore(&data->lock, flags); + + if (clear && data->fb_bitmap) { + memset(data->fb_vbitmap, 0xff, PICOLCDFB_SIZE); + memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); + } + + /* schedule first output of framebuffer */ + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); + + return 0; +} + +/* Update fb_vbitmap from the screen_base and send changed tiles to de= vice */ +static void picolcd_fb_update(struct picolcd_data *data) +{ + int chip, tile; + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (!(data->status & PICOLCD_READY_FB)) { + spin_unlock_irqrestore(&data->lock, flags); + picolcd_fb_reset(data, 0); + } else { + spin_unlock_irqrestore(&data->lock, flags); + } + + /* + * Translate the XBM format screen_base into the format needed by the + * PicoLCD. See display layout above. + * Do this one tile after the other and push those tiles that changed= =2E + */ + for (chip =3D 0; chip < 4; chip++) + for (tile =3D 0; tile < 8; tile++) + if (picolcd_fb_update_tile(data->fb_vbitmap, + data->fb_bitmap, data->fb_bpp, chip, tile)) + picolcd_fb_send_tile(data->hdev, chip, tile); +} + +/* Stub to call the system default and update the image on the picoLCD= */ +static void picolcd_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + if (!info->par) + return; + sys_fillrect(info, rect); + + schedule_delayed_work(&info->deferred_work, 0); +} + +/* Stub to call the system default and update the image on the picoLCD= */ +static void picolcd_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + if (!info->par) + return; + sys_copyarea(info, area); + + schedule_delayed_work(&info->deferred_work, 0); +} + +/* Stub to call the system default and update the image on the picoLCD= */ +static void picolcd_fb_imageblit(struct fb_info *info, const struct fb= _image *image) +{ + if (!info->par) + return; + sys_imageblit(info, image); + + schedule_delayed_work(&info->deferred_work, 0); +} + +/* + * this is the slow path from userspace. they can seek and write to + * the fb. it's inefficient to do anything less than a full screen dra= w + */ +static ssize_t picolcd_fb_write(struct fb_info *info, const char __use= r *buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + if (!info->par) + return -ENODEV; + ret =3D fb_sys_write(info, buf, count, ppos); + if (ret >=3D 0) + schedule_delayed_work(&info->deferred_work, 0); + return ret; +} + +static int picolcd_fb_blank(int blank, struct fb_info *info) +{ + if (!info->par) + return -ENODEV; + /* We let fb notification do this for us via lcd/backlight device */ + return 0; +} + +static void picolcd_fb_destroy(struct fb_info *info) +{ + struct picolcd_data *data =3D info->par; + info->par =3D NULL; + if (data) + data->fb_info =3D NULL; + fb_deferred_io_cleanup(info); + framebuffer_release(info); +} + +static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct = fb_info *info) +{ + __u32 bpp =3D var->bits_per_pixel; + __u32 activate =3D var->activate; + + /* only allow 1/8 bit depth (8-bit is grayscale) */ + *var =3D picolcdfb_var; + var->activate =3D activate; + if (bpp >=3D 8) + var->bits_per_pixel =3D 8; + else + var->bits_per_pixel =3D 1; + return 0; +} + +static int picolcd_set_par(struct fb_info *info) +{ + struct picolcd_data *data =3D info->par; + u8 *o_fb, *n_fb; + if (info->var.bits_per_pixel =3D=3D data->fb_bpp) + return 0; + /* switch between 1/8 bit depths */ + if (info->var.bits_per_pixel !=3D 1 && info->var.bits_per_pixel !=3D = 8) + return -EINVAL; + + o_fb =3D data->fb_bitmap; + n_fb =3D vmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel); + if (!n_fb) + return -ENOMEM; + + fb_deferred_io_cleanup(info); + /* translate FB content to new bits-per-pixel */ + if (info->var.bits_per_pixel =3D=3D 1) { + int i, b; + for (i =3D 0; i < PICOLCDFB_SIZE; i++) { + u8 p =3D 0; + for (b =3D 0; b < 8; b++) { + p <<=3D 1; + p |=3D o_fb[i*8+b] ? 0x01 : 0x00; + } + } + info->fix.visual =3D FB_VISUAL_MONO01; + info->fix.line_length =3D PICOLCDFB_WIDTH / 8; + } else { + int i; + for (i =3D 0; i < PICOLCDFB_SIZE * 8; i++) + n_fb[i] =3D o_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; + info->fix.visual =3D FB_VISUAL_TRUECOLOR; + info->fix.line_length =3D PICOLCDFB_WIDTH; + } + + data->fb_bitmap =3D n_fb; + data->fb_bpp =3D info->var.bits_per_pixel; + info->screen_base =3D (char __force __iomem *)n_fb; + info->fix.smem_start =3D (unsigned long)n_fb; + info->fix.smem_len =3D PICOLCDFB_SIZE*data->fb_bpp; + fb_deferred_io_init(info); + vfree(o_fb); + return 0; +} + +/* Note this can't be const because of struct fb_info definition */ +static struct fb_ops picolcdfb_ops =3D { + .owner =3D THIS_MODULE, + .fb_destroy =3D picolcd_fb_destroy, + .fb_read =3D fb_sys_read, + .fb_write =3D picolcd_fb_write, + .fb_blank =3D picolcd_fb_blank, + .fb_fillrect =3D picolcd_fb_fillrect, + .fb_copyarea =3D picolcd_fb_copyarea, + .fb_imageblit =3D picolcd_fb_imageblit, + .fb_check_var =3D picolcd_fb_check_var, + .fb_set_par =3D picolcd_set_par, +}; + + +/* Callback from deferred IO workqueue */ +static void picolcd_fb_deferred_io(struct fb_info *info, struct list_h= ead *pagelist) +{ + picolcd_fb_update(info->par); +} + +static const struct fb_deferred_io picolcd_fb_defio =3D { + .delay =3D HZ / PICOLCDFB_UPDATE_RATE_DEFAULT, + .deferred_io =3D picolcd_fb_deferred_io, +}; + + +/* + * The "fb_update_rate" sysfs attribute + */ +static ssize_t picolcd_fb_update_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct picolcd_data *data =3D dev_get_drvdata(dev); + unsigned i, fb_update_rate =3D data->fb_update_rate; + size_t ret =3D 0; + + for (i =3D 1; i <=3D PICOLCDFB_UPDATE_RATE_LIMIT; i++) + if (ret >=3D PAGE_SIZE) + break; + else if (i =3D=3D fb_update_rate) + ret +=3D snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); + else + ret +=3D snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i); + if (ret > 0) + buf[min(ret, (size_t)PAGE_SIZE)-1] =3D '\n'; + return ret; +} + +static ssize_t picolcd_fb_update_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct picolcd_data *data =3D dev_get_drvdata(dev); + int i; + unsigned u; + + if (count < 1 || count > 10) + return -EINVAL; + + i =3D sscanf(buf, "%u", &u); + if (i !=3D 1) + return -EINVAL; + + if (u > PICOLCDFB_UPDATE_RATE_LIMIT) + return -ERANGE; + else if (u =3D=3D 0) + u =3D PICOLCDFB_UPDATE_RATE_DEFAULT; + + data->fb_update_rate =3D u; + data->fb_defio.delay =3D HZ / data->fb_update_rate; + return count; +} + +static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show, + picolcd_fb_update_rate_store); + +/* initialize Framebuffer device */ +static int picolcd_init_framebuffer(struct picolcd_data *data) +{ + struct device *dev =3D &data->hdev->dev; + struct fb_info *info =3D NULL; + int error =3D -ENOMEM; + u8 *fb_vbitmap =3D NULL; + u8 *fb_bitmap =3D NULL; + + fb_bitmap =3D vmalloc(PICOLCDFB_SIZE*picolcdfb_var.bits_per_pixel); + if (fb_bitmap =3D=3D NULL) { + dev_err(dev, "can't get a free page for framebuffer\n"); + goto err_nomem; + } + + fb_vbitmap =3D kmalloc(PICOLCDFB_SIZE, GFP_KERNEL); + if (fb_vbitmap =3D=3D NULL) { + dev_err(dev, "can't alloc vbitmap image buffer\n"); + goto err_nomem; + } + + data->fb_update_rate =3D PICOLCDFB_UPDATE_RATE_DEFAULT; + data->fb_defio =3D picolcd_fb_defio; + info =3D framebuffer_alloc(0, dev); + if (info =3D=3D NULL) { + dev_err(dev, "failed to allocate a framebuffer\n"); + goto err_nomem; + } + + info->fbdefio =3D &data->fb_defio; + info->screen_base =3D (char __force __iomem *)fb_bitmap; + info->fbops =3D &picolcdfb_ops; + info->var =3D picolcdfb_var; + info->fix =3D picolcdfb_fix; + info->fix.smem_len =3D PICOLCDFB_SIZE; + info->fix.smem_start =3D (unsigned long)fb_bitmap; + info->par =3D data; + info->flags =3D FBINFO_FLAG_DEFAULT; + + data->fb_vbitmap =3D fb_vbitmap; + data->fb_bitmap =3D fb_bitmap; + data->fb_bpp =3D picolcdfb_var.bits_per_pixel; + error =3D picolcd_fb_reset(data, 1); + if (error) { + dev_err(dev, "failed to configure display\n"); + goto err_cleanup; + } + error =3D device_create_file(dev, &dev_attr_fb_update_rate); + if (error) { + dev_err(dev, "failed to create sysfs attributes\n"); + goto err_cleanup; + } + data->fb_info =3D info; + error =3D register_framebuffer(info); + if (error) { + dev_err(dev, "failed to register framebuffer\n"); + goto err_sysfs; + } + fb_deferred_io_init(info); + /* schedule first output of framebuffer */ + schedule_delayed_work(&info->deferred_work, 0); + return 0; + +err_sysfs: + device_remove_file(dev, &dev_attr_fb_update_rate); +err_cleanup: + data->fb_vbitmap =3D NULL; + data->fb_bitmap =3D NULL; + data->fb_bpp =3D 0; + data->fb_info =3D NULL; + +err_nomem: + framebuffer_release(info); + vfree(fb_bitmap); + kfree(fb_vbitmap); + return error; +} + +static void picolcd_exit_framebuffer(struct picolcd_data *data) +{ + struct fb_info *info =3D data->fb_info; + u8 *fb_vbitmap =3D data->fb_vbitmap; + u8 *fb_bitmap =3D data->fb_bitmap; + + if (!info) + return; + + data->fb_vbitmap =3D NULL; + data->fb_bitmap =3D NULL; + data->fb_bpp =3D 0; + data->fb_info =3D NULL; + device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); + fb_deferred_io_cleanup(info); + unregister_framebuffer(info); + vfree(fb_bitmap); + kfree(fb_vbitmap); +} + + +#else +static inline int picolcd_fb_reset(struct picolcd_data *data, int clea= r) +{ + return 0; +} +static inline int picolcd_init_framebuffer(struct picolcd_data *data) +{ + return 0; +} +static void picolcd_exit_framebuffer(struct picolcd_data *data) +{ +} +#endif /* CONFIG_FB */ + /* * input class device */ @@ -314,6 +844,7 @@ static int picolcd_reset(struct hid_device *hdev) struct picolcd_data *data =3D hid_get_drvdata(hdev); struct hid_report *report =3D picolcd_out_report(REPORT_RESET, hdev); unsigned long flags; + int error; =20 if (!data || !report || report->maxfield !=3D 1) return -ENODEV; @@ -327,7 +858,16 @@ static int picolcd_reset(struct hid_device *hdev) usbhid_submit_report(hdev, report, USB_DIR_OUT); spin_unlock_irqrestore(&data->lock, flags); =20 - return picolcd_check_version(hdev); + error =3D picolcd_check_version(hdev); + if (error) + return error; + +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); +#endif /* CONFIG_FB */ + + return 0; } =20 /* @@ -931,6 +1471,9 @@ static int picolcd_reset_resume(struct hid_device = *hdev) ret =3D picolcd_reset(hdev); if (ret) dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret); + ret =3D picolcd_fb_reset(hid_get_drvdata(hdev), 0); + if (ret) + dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", = ret); return 0; } #endif @@ -1027,6 +1570,11 @@ static int picolcd_probe_lcd(struct hid_device *= hdev, struct picolcd_data *data) if (error) goto err; =20 + /* Set up the framebuffer device */ + error =3D picolcd_init_framebuffer(data); + if (error) + goto err; + #ifdef CONFIG_DEBUG_FS report =3D picolcd_out_report(REPORT_READ_MEMORY, hdev); if (report && report->maxfield =3D=3D 1 && report->field[0]->report_s= ize =3D=3D 8) @@ -1036,6 +1584,7 @@ static int picolcd_probe_lcd(struct hid_device *h= dev, struct picolcd_data *data) #endif return 0; err: + picolcd_exit_framebuffer(data); picolcd_exit_cir(data); picolcd_exit_keys(data); return error; @@ -1165,6 +1714,8 @@ static void picolcd_remove(struct hid_device *hde= v) complete(&data->pending->ready); spin_unlock_irqrestore(&data->lock, flags); =20 + /* Clean up the framebuffer */ + picolcd_exit_framebuffer(data); /* Cleanup input */ picolcd_exit_cir(data); picolcd_exit_keys(data); --=20 1.6.4.4