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