summaryrefslogtreecommitdiff
path: root/drivers/hid/hid-picolcd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/hid-picolcd.c')
-rw-r--r--drivers/hid/hid-picolcd.c199
1 files changed, 166 insertions, 33 deletions
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index 7aabf65..346f0e3 100644
--- a/drivers/hid/hid-picolcd.c
+++ b/drivers/hid/hid-picolcd.c
@@ -127,6 +127,26 @@ static const struct fb_var_screeninfo picolcdfb_var = {
.height = 26,
.bits_per_pixel = 1,
.grayscale = 1,
+ .red = {
+ .offset = 0,
+ .length = 1,
+ .msb_right = 0,
+ },
+ .green = {
+ .offset = 0,
+ .length = 1,
+ .msb_right = 0,
+ },
+ .blue = {
+ .offset = 0,
+ .length = 1,
+ .msb_right = 0,
+ },
+ .transp = {
+ .offset = 0,
+ .length = 0,
+ .msb_right = 0,
+ },
};
#endif /* CONFIG_HID_PICOLCD_FB */
@@ -188,6 +208,7 @@ struct picolcd_data {
/* Framebuffer stuff */
u8 fb_update_rate;
u8 fb_bpp;
+ u8 fb_force;
u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */
u8 *fb_bitmap; /* framebuffer */
struct fb_info *fb_info;
@@ -346,7 +367,7 @@ static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
for (i = 0; i < 64; i++) {
tdata[i] <<= 1;
- tdata[i] |= (bdata[i/8] >> (7 - i % 8)) & 0x01;
+ tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01;
}
}
} else if (bpp == 8) {
@@ -399,13 +420,10 @@ static int picolcd_fb_reset(struct picolcd_data *data, int clear)
if (data->fb_bitmap) {
if (clear) {
- memset(data->fb_vbitmap, 0xff, PICOLCDFB_SIZE);
+ memset(data->fb_vbitmap, 0, PICOLCDFB_SIZE);
memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp);
- } else {
- /* invert 1 byte in each tile to force resend */
- for (i = 0; i < PICOLCDFB_SIZE; i += 64)
- data->fb_vbitmap[i] = ~data->fb_vbitmap[i];
}
+ data->fb_force = 1;
}
/* schedule first output of framebuffer */
@@ -421,6 +439,9 @@ static void picolcd_fb_update(struct picolcd_data *data)
int chip, tile, n;
unsigned long flags;
+ if (!data)
+ return;
+
spin_lock_irqsave(&data->lock, flags);
if (!(data->status & PICOLCD_READY_FB)) {
spin_unlock_irqrestore(&data->lock, flags);
@@ -440,14 +461,18 @@ static void picolcd_fb_update(struct picolcd_data *data)
for (chip = 0; chip < 4; chip++)
for (tile = 0; tile < 8; tile++)
if (picolcd_fb_update_tile(data->fb_vbitmap,
- data->fb_bitmap, data->fb_bpp, chip, tile)) {
+ data->fb_bitmap, data->fb_bpp, chip, tile) ||
+ data->fb_force) {
n += 2;
+ if (!data->fb_info->par)
+ return; /* device lost! */
if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
usbhid_wait_io(data->hdev);
n = 0;
}
picolcd_fb_send_tile(data->hdev, chip, tile);
}
+ data->fb_force = false;
if (n)
usbhid_wait_io(data->hdev);
}
@@ -511,11 +536,23 @@ static int picolcd_fb_blank(int blank, struct fb_info *info)
static void picolcd_fb_destroy(struct fb_info *info)
{
struct picolcd_data *data = info->par;
+ u32 *ref_cnt = info->pseudo_palette;
+ int may_release;
+
info->par = NULL;
if (data)
data->fb_info = NULL;
fb_deferred_io_cleanup(info);
- framebuffer_release(info);
+
+ ref_cnt--;
+ mutex_lock(&info->lock);
+ (*ref_cnt)--;
+ may_release = !ref_cnt;
+ mutex_unlock(&info->lock);
+ if (may_release) {
+ framebuffer_release(info);
+ vfree((u8 *)info->fix.smem_start);
+ }
}
static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
@@ -526,29 +563,37 @@ static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *i
/* only allow 1/8 bit depth (8-bit is grayscale) */
*var = picolcdfb_var;
var->activate = activate;
- if (bpp >= 8)
+ if (bpp >= 8) {
var->bits_per_pixel = 8;
- else
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ } else {
var->bits_per_pixel = 1;
+ var->red.length = 1;
+ var->green.length = 1;
+ var->blue.length = 1;
+ }
return 0;
}
static int picolcd_set_par(struct fb_info *info)
{
struct picolcd_data *data = info->par;
- u8 *o_fb, *n_fb;
+ u8 *tmp_fb, *o_fb;
+ if (!data)
+ return -ENODEV;
if (info->var.bits_per_pixel == data->fb_bpp)
return 0;
/* switch between 1/8 bit depths */
if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
return -EINVAL;
- o_fb = data->fb_bitmap;
- n_fb = vmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel);
- if (!n_fb)
+ o_fb = data->fb_bitmap;
+ tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL);
+ if (!tmp_fb)
return -ENOMEM;
- fb_deferred_io_cleanup(info);
/* translate FB content to new bits-per-pixel */
if (info->var.bits_per_pixel == 1) {
int i, b;
@@ -558,24 +603,87 @@ static int picolcd_set_par(struct fb_info *info)
p <<= 1;
p |= o_fb[i*8+b] ? 0x01 : 0x00;
}
+ tmp_fb[i] = p;
}
+ memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
info->fix.visual = FB_VISUAL_MONO01;
info->fix.line_length = PICOLCDFB_WIDTH / 8;
} else {
int i;
+ memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
- n_fb[i] = o_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
- info->fix.visual = FB_VISUAL_TRUECOLOR;
+ o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
+ info->fix.visual = FB_VISUAL_DIRECTCOLOR;
info->fix.line_length = PICOLCDFB_WIDTH;
}
- data->fb_bitmap = n_fb;
+ kfree(tmp_fb);
data->fb_bpp = info->var.bits_per_pixel;
- info->screen_base = (char __force __iomem *)n_fb;
- info->fix.smem_start = (unsigned long)n_fb;
- info->fix.smem_len = PICOLCDFB_SIZE*data->fb_bpp;
- fb_deferred_io_init(info);
- vfree(o_fb);
+ return 0;
+}
+
+/* Do refcounting on our FB and cleanup per worker if FB is
+ * closed after unplug of our device
+ * (fb_release holds info->lock and still touches info after
+ * we return so we can't release it immediately.
+ */
+struct picolcd_fb_cleanup_item {
+ struct fb_info *info;
+ struct picolcd_fb_cleanup_item *next;
+};
+static struct picolcd_fb_cleanup_item *fb_pending;
+DEFINE_SPINLOCK(fb_pending_lock);
+
+static void picolcd_fb_do_cleanup(struct work_struct *data)
+{
+ struct picolcd_fb_cleanup_item *item;
+ unsigned long flags;
+
+ do {
+ spin_lock_irqsave(&fb_pending_lock, flags);
+ item = fb_pending;
+ fb_pending = item ? item->next : NULL;
+ spin_unlock_irqrestore(&fb_pending_lock, flags);
+
+ if (item) {
+ u8 *fb = (u8 *)item->info->fix.smem_start;
+ /* make sure we do not race against fb core when
+ * releasing */
+ mutex_lock(&item->info->lock);
+ mutex_unlock(&item->info->lock);
+ framebuffer_release(item->info);
+ vfree(fb);
+ }
+ } while (item);
+}
+
+DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup);
+
+static int picolcd_fb_open(struct fb_info *info, int u)
+{
+ u32 *ref_cnt = info->pseudo_palette;
+ ref_cnt--;
+
+ (*ref_cnt)++;
+ return 0;
+}
+
+static int picolcd_fb_release(struct fb_info *info, int u)
+{
+ u32 *ref_cnt = info->pseudo_palette;
+ ref_cnt--;
+
+ (*ref_cnt)++;
+ if (!*ref_cnt) {
+ unsigned long flags;
+ struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt;
+ item--;
+ spin_lock_irqsave(&fb_pending_lock, flags);
+ item->next = fb_pending;
+ fb_pending = item;
+ spin_unlock_irqrestore(&fb_pending_lock, flags);
+ schedule_work(&picolcd_fb_cleanup);
+ }
return 0;
}
@@ -583,6 +691,8 @@ static int picolcd_set_par(struct fb_info *info)
static struct fb_ops picolcdfb_ops = {
.owner = THIS_MODULE,
.fb_destroy = picolcd_fb_destroy,
+ .fb_open = picolcd_fb_open,
+ .fb_release = picolcd_fb_release,
.fb_read = fb_sys_read,
.fb_write = picolcd_fb_write,
.fb_blank = picolcd_fb_blank,
@@ -660,11 +770,12 @@ static int picolcd_init_framebuffer(struct picolcd_data *data)
{
struct device *dev = &data->hdev->dev;
struct fb_info *info = NULL;
- int error = -ENOMEM;
+ int i, error = -ENOMEM;
u8 *fb_vbitmap = NULL;
u8 *fb_bitmap = NULL;
+ u32 *palette;
- fb_bitmap = vmalloc(PICOLCDFB_SIZE*picolcdfb_var.bits_per_pixel);
+ fb_bitmap = vmalloc(PICOLCDFB_SIZE*8);
if (fb_bitmap == NULL) {
dev_err(dev, "can't get a free page for framebuffer\n");
goto err_nomem;
@@ -678,18 +789,29 @@ static int picolcd_init_framebuffer(struct picolcd_data *data)
data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
data->fb_defio = picolcd_fb_defio;
- info = framebuffer_alloc(0, dev);
+ /* The extra memory is:
+ * - struct picolcd_fb_cleanup_item
+ * - u32 for ref_count
+ * - 256*u32 for pseudo_palette
+ */
+ info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev);
if (info == NULL) {
dev_err(dev, "failed to allocate a framebuffer\n");
goto err_nomem;
}
+ palette = info->par + sizeof(struct picolcd_fb_cleanup_item);
+ *palette = 1;
+ palette++;
+ for (i = 0; i < 256; i++)
+ palette[i] = i > 0 && i < 16 ? 0xff : 0;
+ info->pseudo_palette = palette;
info->fbdefio = &data->fb_defio;
info->screen_base = (char __force __iomem *)fb_bitmap;
info->fbops = &picolcdfb_ops;
info->var = picolcdfb_var;
info->fix = picolcdfb_fix;
- info->fix.smem_len = PICOLCDFB_SIZE;
+ info->fix.smem_len = PICOLCDFB_SIZE*8;
info->fix.smem_start = (unsigned long)fb_bitmap;
info->par = data;
info->flags = FBINFO_FLAG_DEFAULT;
@@ -707,18 +829,20 @@ static int picolcd_init_framebuffer(struct picolcd_data *data)
dev_err(dev, "failed to create sysfs attributes\n");
goto err_cleanup;
}
+ fb_deferred_io_init(info);
data->fb_info = info;
error = 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 */
+ data->fb_force = 1;
schedule_delayed_work(&info->deferred_work, 0);
return 0;
err_sysfs:
+ fb_deferred_io_cleanup(info);
device_remove_file(dev, &dev_attr_fb_update_rate);
err_cleanup:
data->fb_vbitmap = NULL;
@@ -737,19 +861,17 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data)
{
struct fb_info *info = data->fb_info;
u8 *fb_vbitmap = data->fb_vbitmap;
- u8 *fb_bitmap = data->fb_bitmap;
if (!info)
return;
+ info->par = NULL;
+ device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
+ unregister_framebuffer(info);
data->fb_vbitmap = NULL;
data->fb_bitmap = NULL;
data->fb_bpp = 0;
data->fb_info = 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);
}
@@ -2566,6 +2688,13 @@ static void picolcd_remove(struct hid_device *hdev)
spin_lock_irqsave(&data->lock, flags);
data->status |= PICOLCD_FAILED;
spin_unlock_irqrestore(&data->lock, flags);
+#ifdef CONFIG_HID_PICOLCD_FB
+ /* short-circuit FB as early as possible in order to
+ * avoid long delays if we host console.
+ */
+ if (data->fb_info)
+ data->fb_info->par = NULL;
+#endif
picolcd_exit_devfs(data);
device_remove_file(&hdev->dev, &dev_attr_operation_mode);
@@ -2623,6 +2752,10 @@ static int __init picolcd_init(void)
static void __exit picolcd_exit(void)
{
hid_unregister_driver(&picolcd_driver);
+#ifdef CONFIG_HID_PICOLCD_FB
+ flush_scheduled_work();
+ WARN_ON(fb_pending);
+#endif
}
module_init(picolcd_init);