xref: /OK3568_Linux_fs/kernel/drivers/hid/hid-picolcd_fb.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /***************************************************************************
3*4882a593Smuzhiyun  *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
4*4882a593Smuzhiyun  *                                                                         *
5*4882a593Smuzhiyun  *   Based on Logitech G13 driver (v0.4)                                   *
6*4882a593Smuzhiyun  *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
7*4882a593Smuzhiyun  *                                                                         *
8*4882a593Smuzhiyun  ***************************************************************************/
9*4882a593Smuzhiyun 
10*4882a593Smuzhiyun #include <linux/hid.h>
11*4882a593Smuzhiyun #include <linux/vmalloc.h>
12*4882a593Smuzhiyun 
13*4882a593Smuzhiyun #include <linux/fb.h>
14*4882a593Smuzhiyun #include <linux/module.h>
15*4882a593Smuzhiyun 
16*4882a593Smuzhiyun #include "hid-picolcd.h"
17*4882a593Smuzhiyun 
18*4882a593Smuzhiyun /* Framebuffer
19*4882a593Smuzhiyun  *
20*4882a593Smuzhiyun  * The PicoLCD use a Topway LCD module of 256x64 pixel
21*4882a593Smuzhiyun  * This display area is tiled over 4 controllers with 8 tiles
22*4882a593Smuzhiyun  * each. Each tile has 8x64 pixel, each data byte representing
23*4882a593Smuzhiyun  * a 1-bit wide vertical line of the tile.
24*4882a593Smuzhiyun  *
25*4882a593Smuzhiyun  * The display can be updated at a tile granularity.
26*4882a593Smuzhiyun  *
27*4882a593Smuzhiyun  *       Chip 1           Chip 2           Chip 3           Chip 4
28*4882a593Smuzhiyun  * +----------------+----------------+----------------+----------------+
29*4882a593Smuzhiyun  * |     Tile 1     |     Tile 1     |     Tile 1     |     Tile 1     |
30*4882a593Smuzhiyun  * +----------------+----------------+----------------+----------------+
31*4882a593Smuzhiyun  * |     Tile 2     |     Tile 2     |     Tile 2     |     Tile 2     |
32*4882a593Smuzhiyun  * +----------------+----------------+----------------+----------------+
33*4882a593Smuzhiyun  *                                  ...
34*4882a593Smuzhiyun  * +----------------+----------------+----------------+----------------+
35*4882a593Smuzhiyun  * |     Tile 8     |     Tile 8     |     Tile 8     |     Tile 8     |
36*4882a593Smuzhiyun  * +----------------+----------------+----------------+----------------+
37*4882a593Smuzhiyun  */
38*4882a593Smuzhiyun #define PICOLCDFB_NAME "picolcdfb"
39*4882a593Smuzhiyun #define PICOLCDFB_WIDTH (256)
40*4882a593Smuzhiyun #define PICOLCDFB_HEIGHT (64)
41*4882a593Smuzhiyun #define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8)
42*4882a593Smuzhiyun 
43*4882a593Smuzhiyun #define PICOLCDFB_UPDATE_RATE_LIMIT   10
44*4882a593Smuzhiyun #define PICOLCDFB_UPDATE_RATE_DEFAULT  2
45*4882a593Smuzhiyun 
46*4882a593Smuzhiyun /* Framebuffer visual structures */
47*4882a593Smuzhiyun static const struct fb_fix_screeninfo picolcdfb_fix = {
48*4882a593Smuzhiyun 	.id          = PICOLCDFB_NAME,
49*4882a593Smuzhiyun 	.type        = FB_TYPE_PACKED_PIXELS,
50*4882a593Smuzhiyun 	.visual      = FB_VISUAL_MONO01,
51*4882a593Smuzhiyun 	.xpanstep    = 0,
52*4882a593Smuzhiyun 	.ypanstep    = 0,
53*4882a593Smuzhiyun 	.ywrapstep   = 0,
54*4882a593Smuzhiyun 	.line_length = PICOLCDFB_WIDTH / 8,
55*4882a593Smuzhiyun 	.accel       = FB_ACCEL_NONE,
56*4882a593Smuzhiyun };
57*4882a593Smuzhiyun 
58*4882a593Smuzhiyun static const struct fb_var_screeninfo picolcdfb_var = {
59*4882a593Smuzhiyun 	.xres           = PICOLCDFB_WIDTH,
60*4882a593Smuzhiyun 	.yres           = PICOLCDFB_HEIGHT,
61*4882a593Smuzhiyun 	.xres_virtual   = PICOLCDFB_WIDTH,
62*4882a593Smuzhiyun 	.yres_virtual   = PICOLCDFB_HEIGHT,
63*4882a593Smuzhiyun 	.width          = 103,
64*4882a593Smuzhiyun 	.height         = 26,
65*4882a593Smuzhiyun 	.bits_per_pixel = 1,
66*4882a593Smuzhiyun 	.grayscale      = 1,
67*4882a593Smuzhiyun 	.red            = {
68*4882a593Smuzhiyun 		.offset = 0,
69*4882a593Smuzhiyun 		.length = 1,
70*4882a593Smuzhiyun 		.msb_right = 0,
71*4882a593Smuzhiyun 	},
72*4882a593Smuzhiyun 	.green          = {
73*4882a593Smuzhiyun 		.offset = 0,
74*4882a593Smuzhiyun 		.length = 1,
75*4882a593Smuzhiyun 		.msb_right = 0,
76*4882a593Smuzhiyun 	},
77*4882a593Smuzhiyun 	.blue           = {
78*4882a593Smuzhiyun 		.offset = 0,
79*4882a593Smuzhiyun 		.length = 1,
80*4882a593Smuzhiyun 		.msb_right = 0,
81*4882a593Smuzhiyun 	},
82*4882a593Smuzhiyun 	.transp         = {
83*4882a593Smuzhiyun 		.offset = 0,
84*4882a593Smuzhiyun 		.length = 0,
85*4882a593Smuzhiyun 		.msb_right = 0,
86*4882a593Smuzhiyun 	},
87*4882a593Smuzhiyun };
88*4882a593Smuzhiyun 
89*4882a593Smuzhiyun /* Send a given tile to PicoLCD */
picolcd_fb_send_tile(struct picolcd_data * data,u8 * vbitmap,int chip,int tile)90*4882a593Smuzhiyun static int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap,
91*4882a593Smuzhiyun 		int chip, int tile)
92*4882a593Smuzhiyun {
93*4882a593Smuzhiyun 	struct hid_report *report1, *report2;
94*4882a593Smuzhiyun 	unsigned long flags;
95*4882a593Smuzhiyun 	u8 *tdata;
96*4882a593Smuzhiyun 	int i;
97*4882a593Smuzhiyun 
98*4882a593Smuzhiyun 	report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev);
99*4882a593Smuzhiyun 	if (!report1 || report1->maxfield != 1)
100*4882a593Smuzhiyun 		return -ENODEV;
101*4882a593Smuzhiyun 	report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev);
102*4882a593Smuzhiyun 	if (!report2 || report2->maxfield != 1)
103*4882a593Smuzhiyun 		return -ENODEV;
104*4882a593Smuzhiyun 
105*4882a593Smuzhiyun 	spin_lock_irqsave(&data->lock, flags);
106*4882a593Smuzhiyun 	if ((data->status & PICOLCD_FAILED)) {
107*4882a593Smuzhiyun 		spin_unlock_irqrestore(&data->lock, flags);
108*4882a593Smuzhiyun 		return -ENODEV;
109*4882a593Smuzhiyun 	}
110*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  0, chip << 2);
111*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  1, 0x02);
112*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  2, 0x00);
113*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  3, 0x00);
114*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  4, 0xb8 | tile);
115*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  5, 0x00);
116*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  6, 0x00);
117*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  7, 0x40);
118*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  8, 0x00);
119*4882a593Smuzhiyun 	hid_set_field(report1->field[0],  9, 0x00);
120*4882a593Smuzhiyun 	hid_set_field(report1->field[0], 10,   32);
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 	hid_set_field(report2->field[0],  0, (chip << 2) | 0x01);
123*4882a593Smuzhiyun 	hid_set_field(report2->field[0],  1, 0x00);
124*4882a593Smuzhiyun 	hid_set_field(report2->field[0],  2, 0x00);
125*4882a593Smuzhiyun 	hid_set_field(report2->field[0],  3,   32);
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun 	tdata = vbitmap + (tile * 4 + chip) * 64;
128*4882a593Smuzhiyun 	for (i = 0; i < 64; i++)
129*4882a593Smuzhiyun 		if (i < 32)
130*4882a593Smuzhiyun 			hid_set_field(report1->field[0], 11 + i, tdata[i]);
131*4882a593Smuzhiyun 		else
132*4882a593Smuzhiyun 			hid_set_field(report2->field[0], 4 + i - 32, tdata[i]);
133*4882a593Smuzhiyun 
134*4882a593Smuzhiyun 	hid_hw_request(data->hdev, report1, HID_REQ_SET_REPORT);
135*4882a593Smuzhiyun 	hid_hw_request(data->hdev, report2, HID_REQ_SET_REPORT);
136*4882a593Smuzhiyun 	spin_unlock_irqrestore(&data->lock, flags);
137*4882a593Smuzhiyun 	return 0;
138*4882a593Smuzhiyun }
139*4882a593Smuzhiyun 
140*4882a593Smuzhiyun /* Translate a single tile*/
picolcd_fb_update_tile(u8 * vbitmap,const u8 * bitmap,int bpp,int chip,int tile)141*4882a593Smuzhiyun static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
142*4882a593Smuzhiyun 		int chip, int tile)
143*4882a593Smuzhiyun {
144*4882a593Smuzhiyun 	int i, b, changed = 0;
145*4882a593Smuzhiyun 	u8 tdata[64];
146*4882a593Smuzhiyun 	u8 *vdata = vbitmap + (tile * 4 + chip) * 64;
147*4882a593Smuzhiyun 
148*4882a593Smuzhiyun 	if (bpp == 1) {
149*4882a593Smuzhiyun 		for (b = 7; b >= 0; b--) {
150*4882a593Smuzhiyun 			const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
151*4882a593Smuzhiyun 			for (i = 0; i < 64; i++) {
152*4882a593Smuzhiyun 				tdata[i] <<= 1;
153*4882a593Smuzhiyun 				tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01;
154*4882a593Smuzhiyun 			}
155*4882a593Smuzhiyun 		}
156*4882a593Smuzhiyun 	} else if (bpp == 8) {
157*4882a593Smuzhiyun 		for (b = 7; b >= 0; b--) {
158*4882a593Smuzhiyun 			const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8;
159*4882a593Smuzhiyun 			for (i = 0; i < 64; i++) {
160*4882a593Smuzhiyun 				tdata[i] <<= 1;
161*4882a593Smuzhiyun 				tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00;
162*4882a593Smuzhiyun 			}
163*4882a593Smuzhiyun 		}
164*4882a593Smuzhiyun 	} else {
165*4882a593Smuzhiyun 		/* Oops, we should never get here! */
166*4882a593Smuzhiyun 		WARN_ON(1);
167*4882a593Smuzhiyun 		return 0;
168*4882a593Smuzhiyun 	}
169*4882a593Smuzhiyun 
170*4882a593Smuzhiyun 	for (i = 0; i < 64; i++)
171*4882a593Smuzhiyun 		if (tdata[i] != vdata[i]) {
172*4882a593Smuzhiyun 			changed = 1;
173*4882a593Smuzhiyun 			vdata[i] = tdata[i];
174*4882a593Smuzhiyun 		}
175*4882a593Smuzhiyun 	return changed;
176*4882a593Smuzhiyun }
177*4882a593Smuzhiyun 
picolcd_fb_refresh(struct picolcd_data * data)178*4882a593Smuzhiyun void picolcd_fb_refresh(struct picolcd_data *data)
179*4882a593Smuzhiyun {
180*4882a593Smuzhiyun 	if (data->fb_info)
181*4882a593Smuzhiyun 		schedule_delayed_work(&data->fb_info->deferred_work, 0);
182*4882a593Smuzhiyun }
183*4882a593Smuzhiyun 
184*4882a593Smuzhiyun /* Reconfigure LCD display */
picolcd_fb_reset(struct picolcd_data * data,int clear)185*4882a593Smuzhiyun int picolcd_fb_reset(struct picolcd_data *data, int clear)
186*4882a593Smuzhiyun {
187*4882a593Smuzhiyun 	struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
188*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = data->fb_info->par;
189*4882a593Smuzhiyun 	int i, j;
190*4882a593Smuzhiyun 	unsigned long flags;
191*4882a593Smuzhiyun 	static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 };
192*4882a593Smuzhiyun 
193*4882a593Smuzhiyun 	if (!report || report->maxfield != 1)
194*4882a593Smuzhiyun 		return -ENODEV;
195*4882a593Smuzhiyun 
196*4882a593Smuzhiyun 	spin_lock_irqsave(&data->lock, flags);
197*4882a593Smuzhiyun 	for (i = 0; i < 4; i++) {
198*4882a593Smuzhiyun 		for (j = 0; j < report->field[0]->maxusage; j++)
199*4882a593Smuzhiyun 			if (j == 0)
200*4882a593Smuzhiyun 				hid_set_field(report->field[0], j, i << 2);
201*4882a593Smuzhiyun 			else if (j < sizeof(mapcmd))
202*4882a593Smuzhiyun 				hid_set_field(report->field[0], j, mapcmd[j]);
203*4882a593Smuzhiyun 			else
204*4882a593Smuzhiyun 				hid_set_field(report->field[0], j, 0);
205*4882a593Smuzhiyun 		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
206*4882a593Smuzhiyun 	}
207*4882a593Smuzhiyun 	spin_unlock_irqrestore(&data->lock, flags);
208*4882a593Smuzhiyun 
209*4882a593Smuzhiyun 	if (clear) {
210*4882a593Smuzhiyun 		memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE);
211*4882a593Smuzhiyun 		memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp);
212*4882a593Smuzhiyun 	}
213*4882a593Smuzhiyun 	fbdata->force = 1;
214*4882a593Smuzhiyun 
215*4882a593Smuzhiyun 	/* schedule first output of framebuffer */
216*4882a593Smuzhiyun 	if (fbdata->ready)
217*4882a593Smuzhiyun 		schedule_delayed_work(&data->fb_info->deferred_work, 0);
218*4882a593Smuzhiyun 	else
219*4882a593Smuzhiyun 		fbdata->ready = 1;
220*4882a593Smuzhiyun 
221*4882a593Smuzhiyun 	return 0;
222*4882a593Smuzhiyun }
223*4882a593Smuzhiyun 
224*4882a593Smuzhiyun /* Update fb_vbitmap from the screen_base and send changed tiles to device */
picolcd_fb_update(struct fb_info * info)225*4882a593Smuzhiyun static void picolcd_fb_update(struct fb_info *info)
226*4882a593Smuzhiyun {
227*4882a593Smuzhiyun 	int chip, tile, n;
228*4882a593Smuzhiyun 	unsigned long flags;
229*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = info->par;
230*4882a593Smuzhiyun 	struct picolcd_data *data;
231*4882a593Smuzhiyun 
232*4882a593Smuzhiyun 	mutex_lock(&info->lock);
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun 	spin_lock_irqsave(&fbdata->lock, flags);
235*4882a593Smuzhiyun 	if (!fbdata->ready && fbdata->picolcd)
236*4882a593Smuzhiyun 		picolcd_fb_reset(fbdata->picolcd, 0);
237*4882a593Smuzhiyun 	spin_unlock_irqrestore(&fbdata->lock, flags);
238*4882a593Smuzhiyun 
239*4882a593Smuzhiyun 	/*
240*4882a593Smuzhiyun 	 * Translate the framebuffer into the format needed by the PicoLCD.
241*4882a593Smuzhiyun 	 * See display layout above.
242*4882a593Smuzhiyun 	 * Do this one tile after the other and push those tiles that changed.
243*4882a593Smuzhiyun 	 *
244*4882a593Smuzhiyun 	 * Wait for our IO to complete as otherwise we might flood the queue!
245*4882a593Smuzhiyun 	 */
246*4882a593Smuzhiyun 	n = 0;
247*4882a593Smuzhiyun 	for (chip = 0; chip < 4; chip++)
248*4882a593Smuzhiyun 		for (tile = 0; tile < 8; tile++) {
249*4882a593Smuzhiyun 			if (!fbdata->force && !picolcd_fb_update_tile(
250*4882a593Smuzhiyun 					fbdata->vbitmap, fbdata->bitmap,
251*4882a593Smuzhiyun 					fbdata->bpp, chip, tile))
252*4882a593Smuzhiyun 				continue;
253*4882a593Smuzhiyun 			n += 2;
254*4882a593Smuzhiyun 			if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
255*4882a593Smuzhiyun 				spin_lock_irqsave(&fbdata->lock, flags);
256*4882a593Smuzhiyun 				data = fbdata->picolcd;
257*4882a593Smuzhiyun 				spin_unlock_irqrestore(&fbdata->lock, flags);
258*4882a593Smuzhiyun 				mutex_unlock(&info->lock);
259*4882a593Smuzhiyun 				if (!data)
260*4882a593Smuzhiyun 					return;
261*4882a593Smuzhiyun 				hid_hw_wait(data->hdev);
262*4882a593Smuzhiyun 				mutex_lock(&info->lock);
263*4882a593Smuzhiyun 				n = 0;
264*4882a593Smuzhiyun 			}
265*4882a593Smuzhiyun 			spin_lock_irqsave(&fbdata->lock, flags);
266*4882a593Smuzhiyun 			data = fbdata->picolcd;
267*4882a593Smuzhiyun 			spin_unlock_irqrestore(&fbdata->lock, flags);
268*4882a593Smuzhiyun 			if (!data || picolcd_fb_send_tile(data,
269*4882a593Smuzhiyun 					fbdata->vbitmap, chip, tile))
270*4882a593Smuzhiyun 				goto out;
271*4882a593Smuzhiyun 		}
272*4882a593Smuzhiyun 	fbdata->force = false;
273*4882a593Smuzhiyun 	if (n) {
274*4882a593Smuzhiyun 		spin_lock_irqsave(&fbdata->lock, flags);
275*4882a593Smuzhiyun 		data = fbdata->picolcd;
276*4882a593Smuzhiyun 		spin_unlock_irqrestore(&fbdata->lock, flags);
277*4882a593Smuzhiyun 		mutex_unlock(&info->lock);
278*4882a593Smuzhiyun 		if (data)
279*4882a593Smuzhiyun 			hid_hw_wait(data->hdev);
280*4882a593Smuzhiyun 		return;
281*4882a593Smuzhiyun 	}
282*4882a593Smuzhiyun out:
283*4882a593Smuzhiyun 	mutex_unlock(&info->lock);
284*4882a593Smuzhiyun }
285*4882a593Smuzhiyun 
286*4882a593Smuzhiyun /* Stub to call the system default and update the image on the picoLCD */
picolcd_fb_fillrect(struct fb_info * info,const struct fb_fillrect * rect)287*4882a593Smuzhiyun static void picolcd_fb_fillrect(struct fb_info *info,
288*4882a593Smuzhiyun 		const struct fb_fillrect *rect)
289*4882a593Smuzhiyun {
290*4882a593Smuzhiyun 	if (!info->par)
291*4882a593Smuzhiyun 		return;
292*4882a593Smuzhiyun 	sys_fillrect(info, rect);
293*4882a593Smuzhiyun 
294*4882a593Smuzhiyun 	schedule_delayed_work(&info->deferred_work, 0);
295*4882a593Smuzhiyun }
296*4882a593Smuzhiyun 
297*4882a593Smuzhiyun /* Stub to call the system default and update the image on the picoLCD */
picolcd_fb_copyarea(struct fb_info * info,const struct fb_copyarea * area)298*4882a593Smuzhiyun static void picolcd_fb_copyarea(struct fb_info *info,
299*4882a593Smuzhiyun 		const struct fb_copyarea *area)
300*4882a593Smuzhiyun {
301*4882a593Smuzhiyun 	if (!info->par)
302*4882a593Smuzhiyun 		return;
303*4882a593Smuzhiyun 	sys_copyarea(info, area);
304*4882a593Smuzhiyun 
305*4882a593Smuzhiyun 	schedule_delayed_work(&info->deferred_work, 0);
306*4882a593Smuzhiyun }
307*4882a593Smuzhiyun 
308*4882a593Smuzhiyun /* Stub to call the system default and update the image on the picoLCD */
picolcd_fb_imageblit(struct fb_info * info,const struct fb_image * image)309*4882a593Smuzhiyun static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
310*4882a593Smuzhiyun {
311*4882a593Smuzhiyun 	if (!info->par)
312*4882a593Smuzhiyun 		return;
313*4882a593Smuzhiyun 	sys_imageblit(info, image);
314*4882a593Smuzhiyun 
315*4882a593Smuzhiyun 	schedule_delayed_work(&info->deferred_work, 0);
316*4882a593Smuzhiyun }
317*4882a593Smuzhiyun 
318*4882a593Smuzhiyun /*
319*4882a593Smuzhiyun  * this is the slow path from userspace. they can seek and write to
320*4882a593Smuzhiyun  * the fb. it's inefficient to do anything less than a full screen draw
321*4882a593Smuzhiyun  */
picolcd_fb_write(struct fb_info * info,const char __user * buf,size_t count,loff_t * ppos)322*4882a593Smuzhiyun static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
323*4882a593Smuzhiyun 		size_t count, loff_t *ppos)
324*4882a593Smuzhiyun {
325*4882a593Smuzhiyun 	ssize_t ret;
326*4882a593Smuzhiyun 	if (!info->par)
327*4882a593Smuzhiyun 		return -ENODEV;
328*4882a593Smuzhiyun 	ret = fb_sys_write(info, buf, count, ppos);
329*4882a593Smuzhiyun 	if (ret >= 0)
330*4882a593Smuzhiyun 		schedule_delayed_work(&info->deferred_work, 0);
331*4882a593Smuzhiyun 	return ret;
332*4882a593Smuzhiyun }
333*4882a593Smuzhiyun 
picolcd_fb_blank(int blank,struct fb_info * info)334*4882a593Smuzhiyun static int picolcd_fb_blank(int blank, struct fb_info *info)
335*4882a593Smuzhiyun {
336*4882a593Smuzhiyun 	/* We let fb notification do this for us via lcd/backlight device */
337*4882a593Smuzhiyun 	return 0;
338*4882a593Smuzhiyun }
339*4882a593Smuzhiyun 
picolcd_fb_destroy(struct fb_info * info)340*4882a593Smuzhiyun static void picolcd_fb_destroy(struct fb_info *info)
341*4882a593Smuzhiyun {
342*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = info->par;
343*4882a593Smuzhiyun 
344*4882a593Smuzhiyun 	/* make sure no work is deferred */
345*4882a593Smuzhiyun 	fb_deferred_io_cleanup(info);
346*4882a593Smuzhiyun 
347*4882a593Smuzhiyun 	/* No thridparty should ever unregister our framebuffer! */
348*4882a593Smuzhiyun 	WARN_ON(fbdata->picolcd != NULL);
349*4882a593Smuzhiyun 
350*4882a593Smuzhiyun 	vfree((u8 *)info->fix.smem_start);
351*4882a593Smuzhiyun 	framebuffer_release(info);
352*4882a593Smuzhiyun }
353*4882a593Smuzhiyun 
picolcd_fb_check_var(struct fb_var_screeninfo * var,struct fb_info * info)354*4882a593Smuzhiyun static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
355*4882a593Smuzhiyun {
356*4882a593Smuzhiyun 	__u32 bpp      = var->bits_per_pixel;
357*4882a593Smuzhiyun 	__u32 activate = var->activate;
358*4882a593Smuzhiyun 
359*4882a593Smuzhiyun 	/* only allow 1/8 bit depth (8-bit is grayscale) */
360*4882a593Smuzhiyun 	*var = picolcdfb_var;
361*4882a593Smuzhiyun 	var->activate = activate;
362*4882a593Smuzhiyun 	if (bpp >= 8) {
363*4882a593Smuzhiyun 		var->bits_per_pixel = 8;
364*4882a593Smuzhiyun 		var->red.length     = 8;
365*4882a593Smuzhiyun 		var->green.length   = 8;
366*4882a593Smuzhiyun 		var->blue.length    = 8;
367*4882a593Smuzhiyun 	} else {
368*4882a593Smuzhiyun 		var->bits_per_pixel = 1;
369*4882a593Smuzhiyun 		var->red.length     = 1;
370*4882a593Smuzhiyun 		var->green.length   = 1;
371*4882a593Smuzhiyun 		var->blue.length    = 1;
372*4882a593Smuzhiyun 	}
373*4882a593Smuzhiyun 	return 0;
374*4882a593Smuzhiyun }
375*4882a593Smuzhiyun 
picolcd_set_par(struct fb_info * info)376*4882a593Smuzhiyun static int picolcd_set_par(struct fb_info *info)
377*4882a593Smuzhiyun {
378*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = info->par;
379*4882a593Smuzhiyun 	u8 *tmp_fb, *o_fb;
380*4882a593Smuzhiyun 	if (info->var.bits_per_pixel == fbdata->bpp)
381*4882a593Smuzhiyun 		return 0;
382*4882a593Smuzhiyun 	/* switch between 1/8 bit depths */
383*4882a593Smuzhiyun 	if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
384*4882a593Smuzhiyun 		return -EINVAL;
385*4882a593Smuzhiyun 
386*4882a593Smuzhiyun 	o_fb   = fbdata->bitmap;
387*4882a593Smuzhiyun 	tmp_fb = kmalloc_array(PICOLCDFB_SIZE, info->var.bits_per_pixel,
388*4882a593Smuzhiyun 			       GFP_KERNEL);
389*4882a593Smuzhiyun 	if (!tmp_fb)
390*4882a593Smuzhiyun 		return -ENOMEM;
391*4882a593Smuzhiyun 
392*4882a593Smuzhiyun 	/* translate FB content to new bits-per-pixel */
393*4882a593Smuzhiyun 	if (info->var.bits_per_pixel == 1) {
394*4882a593Smuzhiyun 		int i, b;
395*4882a593Smuzhiyun 		for (i = 0; i < PICOLCDFB_SIZE; i++) {
396*4882a593Smuzhiyun 			u8 p = 0;
397*4882a593Smuzhiyun 			for (b = 0; b < 8; b++) {
398*4882a593Smuzhiyun 				p <<= 1;
399*4882a593Smuzhiyun 				p |= o_fb[i*8+b] ? 0x01 : 0x00;
400*4882a593Smuzhiyun 			}
401*4882a593Smuzhiyun 			tmp_fb[i] = p;
402*4882a593Smuzhiyun 		}
403*4882a593Smuzhiyun 		memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
404*4882a593Smuzhiyun 		info->fix.visual = FB_VISUAL_MONO01;
405*4882a593Smuzhiyun 		info->fix.line_length = PICOLCDFB_WIDTH / 8;
406*4882a593Smuzhiyun 	} else {
407*4882a593Smuzhiyun 		int i;
408*4882a593Smuzhiyun 		memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
409*4882a593Smuzhiyun 		for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
410*4882a593Smuzhiyun 			o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
411*4882a593Smuzhiyun 		info->fix.visual = FB_VISUAL_DIRECTCOLOR;
412*4882a593Smuzhiyun 		info->fix.line_length = PICOLCDFB_WIDTH;
413*4882a593Smuzhiyun 	}
414*4882a593Smuzhiyun 
415*4882a593Smuzhiyun 	kfree(tmp_fb);
416*4882a593Smuzhiyun 	fbdata->bpp = info->var.bits_per_pixel;
417*4882a593Smuzhiyun 	return 0;
418*4882a593Smuzhiyun }
419*4882a593Smuzhiyun 
420*4882a593Smuzhiyun static const struct fb_ops picolcdfb_ops = {
421*4882a593Smuzhiyun 	.owner        = THIS_MODULE,
422*4882a593Smuzhiyun 	.fb_destroy   = picolcd_fb_destroy,
423*4882a593Smuzhiyun 	.fb_read      = fb_sys_read,
424*4882a593Smuzhiyun 	.fb_write     = picolcd_fb_write,
425*4882a593Smuzhiyun 	.fb_blank     = picolcd_fb_blank,
426*4882a593Smuzhiyun 	.fb_fillrect  = picolcd_fb_fillrect,
427*4882a593Smuzhiyun 	.fb_copyarea  = picolcd_fb_copyarea,
428*4882a593Smuzhiyun 	.fb_imageblit = picolcd_fb_imageblit,
429*4882a593Smuzhiyun 	.fb_check_var = picolcd_fb_check_var,
430*4882a593Smuzhiyun 	.fb_set_par   = picolcd_set_par,
431*4882a593Smuzhiyun };
432*4882a593Smuzhiyun 
433*4882a593Smuzhiyun 
434*4882a593Smuzhiyun /* Callback from deferred IO workqueue */
picolcd_fb_deferred_io(struct fb_info * info,struct list_head * pagelist)435*4882a593Smuzhiyun static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist)
436*4882a593Smuzhiyun {
437*4882a593Smuzhiyun 	picolcd_fb_update(info);
438*4882a593Smuzhiyun }
439*4882a593Smuzhiyun 
440*4882a593Smuzhiyun static const struct fb_deferred_io picolcd_fb_defio = {
441*4882a593Smuzhiyun 	.delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
442*4882a593Smuzhiyun 	.deferred_io = picolcd_fb_deferred_io,
443*4882a593Smuzhiyun };
444*4882a593Smuzhiyun 
445*4882a593Smuzhiyun 
446*4882a593Smuzhiyun /*
447*4882a593Smuzhiyun  * The "fb_update_rate" sysfs attribute
448*4882a593Smuzhiyun  */
picolcd_fb_update_rate_show(struct device * dev,struct device_attribute * attr,char * buf)449*4882a593Smuzhiyun static ssize_t picolcd_fb_update_rate_show(struct device *dev,
450*4882a593Smuzhiyun 		struct device_attribute *attr, char *buf)
451*4882a593Smuzhiyun {
452*4882a593Smuzhiyun 	struct picolcd_data *data = dev_get_drvdata(dev);
453*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = data->fb_info->par;
454*4882a593Smuzhiyun 	unsigned i, fb_update_rate = fbdata->update_rate;
455*4882a593Smuzhiyun 	size_t ret = 0;
456*4882a593Smuzhiyun 
457*4882a593Smuzhiyun 	for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
458*4882a593Smuzhiyun 		if (ret >= PAGE_SIZE)
459*4882a593Smuzhiyun 			break;
460*4882a593Smuzhiyun 		else if (i == fb_update_rate)
461*4882a593Smuzhiyun 			ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
462*4882a593Smuzhiyun 		else
463*4882a593Smuzhiyun 			ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
464*4882a593Smuzhiyun 	if (ret > 0)
465*4882a593Smuzhiyun 		buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
466*4882a593Smuzhiyun 	return ret;
467*4882a593Smuzhiyun }
468*4882a593Smuzhiyun 
picolcd_fb_update_rate_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)469*4882a593Smuzhiyun static ssize_t picolcd_fb_update_rate_store(struct device *dev,
470*4882a593Smuzhiyun 		struct device_attribute *attr, const char *buf, size_t count)
471*4882a593Smuzhiyun {
472*4882a593Smuzhiyun 	struct picolcd_data *data = dev_get_drvdata(dev);
473*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = data->fb_info->par;
474*4882a593Smuzhiyun 	int i;
475*4882a593Smuzhiyun 	unsigned u;
476*4882a593Smuzhiyun 
477*4882a593Smuzhiyun 	if (count < 1 || count > 10)
478*4882a593Smuzhiyun 		return -EINVAL;
479*4882a593Smuzhiyun 
480*4882a593Smuzhiyun 	i = sscanf(buf, "%u", &u);
481*4882a593Smuzhiyun 	if (i != 1)
482*4882a593Smuzhiyun 		return -EINVAL;
483*4882a593Smuzhiyun 
484*4882a593Smuzhiyun 	if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
485*4882a593Smuzhiyun 		return -ERANGE;
486*4882a593Smuzhiyun 	else if (u == 0)
487*4882a593Smuzhiyun 		u = PICOLCDFB_UPDATE_RATE_DEFAULT;
488*4882a593Smuzhiyun 
489*4882a593Smuzhiyun 	fbdata->update_rate = u;
490*4882a593Smuzhiyun 	data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
491*4882a593Smuzhiyun 	return count;
492*4882a593Smuzhiyun }
493*4882a593Smuzhiyun 
494*4882a593Smuzhiyun static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show,
495*4882a593Smuzhiyun 		picolcd_fb_update_rate_store);
496*4882a593Smuzhiyun 
497*4882a593Smuzhiyun /* initialize Framebuffer device */
picolcd_init_framebuffer(struct picolcd_data * data)498*4882a593Smuzhiyun int picolcd_init_framebuffer(struct picolcd_data *data)
499*4882a593Smuzhiyun {
500*4882a593Smuzhiyun 	struct device *dev = &data->hdev->dev;
501*4882a593Smuzhiyun 	struct fb_info *info = NULL;
502*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata = NULL;
503*4882a593Smuzhiyun 	int i, error = -ENOMEM;
504*4882a593Smuzhiyun 	u32 *palette;
505*4882a593Smuzhiyun 
506*4882a593Smuzhiyun 	/* The extra memory is:
507*4882a593Smuzhiyun 	 * - 256*u32 for pseudo_palette
508*4882a593Smuzhiyun 	 * - struct fb_deferred_io
509*4882a593Smuzhiyun 	 */
510*4882a593Smuzhiyun 	info = framebuffer_alloc(256 * sizeof(u32) +
511*4882a593Smuzhiyun 			sizeof(struct fb_deferred_io) +
512*4882a593Smuzhiyun 			sizeof(struct picolcd_fb_data) +
513*4882a593Smuzhiyun 			PICOLCDFB_SIZE, dev);
514*4882a593Smuzhiyun 	if (!info)
515*4882a593Smuzhiyun 		goto err_nomem;
516*4882a593Smuzhiyun 
517*4882a593Smuzhiyun 	info->fbdefio = info->par;
518*4882a593Smuzhiyun 	*info->fbdefio = picolcd_fb_defio;
519*4882a593Smuzhiyun 	info->par += sizeof(struct fb_deferred_io);
520*4882a593Smuzhiyun 	palette = info->par;
521*4882a593Smuzhiyun 	info->par += 256 * sizeof(u32);
522*4882a593Smuzhiyun 	for (i = 0; i < 256; i++)
523*4882a593Smuzhiyun 		palette[i] = i > 0 && i < 16 ? 0xff : 0;
524*4882a593Smuzhiyun 	info->pseudo_palette = palette;
525*4882a593Smuzhiyun 	info->fbops = &picolcdfb_ops;
526*4882a593Smuzhiyun 	info->var = picolcdfb_var;
527*4882a593Smuzhiyun 	info->fix = picolcdfb_fix;
528*4882a593Smuzhiyun 	info->fix.smem_len   = PICOLCDFB_SIZE*8;
529*4882a593Smuzhiyun 	info->flags = FBINFO_FLAG_DEFAULT;
530*4882a593Smuzhiyun 
531*4882a593Smuzhiyun 	fbdata = info->par;
532*4882a593Smuzhiyun 	spin_lock_init(&fbdata->lock);
533*4882a593Smuzhiyun 	fbdata->picolcd = data;
534*4882a593Smuzhiyun 	fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
535*4882a593Smuzhiyun 	fbdata->bpp     = picolcdfb_var.bits_per_pixel;
536*4882a593Smuzhiyun 	fbdata->force   = 1;
537*4882a593Smuzhiyun 	fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
538*4882a593Smuzhiyun 	fbdata->bitmap  = vmalloc(PICOLCDFB_SIZE*8);
539*4882a593Smuzhiyun 	if (fbdata->bitmap == NULL) {
540*4882a593Smuzhiyun 		dev_err(dev, "can't get a free page for framebuffer\n");
541*4882a593Smuzhiyun 		goto err_nomem;
542*4882a593Smuzhiyun 	}
543*4882a593Smuzhiyun 	info->screen_base = (char __force __iomem *)fbdata->bitmap;
544*4882a593Smuzhiyun 	info->fix.smem_start = (unsigned long)fbdata->bitmap;
545*4882a593Smuzhiyun 	memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
546*4882a593Smuzhiyun 	data->fb_info = info;
547*4882a593Smuzhiyun 
548*4882a593Smuzhiyun 	error = picolcd_fb_reset(data, 1);
549*4882a593Smuzhiyun 	if (error) {
550*4882a593Smuzhiyun 		dev_err(dev, "failed to configure display\n");
551*4882a593Smuzhiyun 		goto err_cleanup;
552*4882a593Smuzhiyun 	}
553*4882a593Smuzhiyun 
554*4882a593Smuzhiyun 	error = device_create_file(dev, &dev_attr_fb_update_rate);
555*4882a593Smuzhiyun 	if (error) {
556*4882a593Smuzhiyun 		dev_err(dev, "failed to create sysfs attributes\n");
557*4882a593Smuzhiyun 		goto err_cleanup;
558*4882a593Smuzhiyun 	}
559*4882a593Smuzhiyun 
560*4882a593Smuzhiyun 	fb_deferred_io_init(info);
561*4882a593Smuzhiyun 	error = register_framebuffer(info);
562*4882a593Smuzhiyun 	if (error) {
563*4882a593Smuzhiyun 		dev_err(dev, "failed to register framebuffer\n");
564*4882a593Smuzhiyun 		goto err_sysfs;
565*4882a593Smuzhiyun 	}
566*4882a593Smuzhiyun 	return 0;
567*4882a593Smuzhiyun 
568*4882a593Smuzhiyun err_sysfs:
569*4882a593Smuzhiyun 	device_remove_file(dev, &dev_attr_fb_update_rate);
570*4882a593Smuzhiyun 	fb_deferred_io_cleanup(info);
571*4882a593Smuzhiyun err_cleanup:
572*4882a593Smuzhiyun 	data->fb_info    = NULL;
573*4882a593Smuzhiyun 
574*4882a593Smuzhiyun err_nomem:
575*4882a593Smuzhiyun 	if (fbdata)
576*4882a593Smuzhiyun 		vfree(fbdata->bitmap);
577*4882a593Smuzhiyun 	framebuffer_release(info);
578*4882a593Smuzhiyun 	return error;
579*4882a593Smuzhiyun }
580*4882a593Smuzhiyun 
picolcd_exit_framebuffer(struct picolcd_data * data)581*4882a593Smuzhiyun void picolcd_exit_framebuffer(struct picolcd_data *data)
582*4882a593Smuzhiyun {
583*4882a593Smuzhiyun 	struct fb_info *info = data->fb_info;
584*4882a593Smuzhiyun 	struct picolcd_fb_data *fbdata;
585*4882a593Smuzhiyun 	unsigned long flags;
586*4882a593Smuzhiyun 
587*4882a593Smuzhiyun 	if (!info)
588*4882a593Smuzhiyun 		return;
589*4882a593Smuzhiyun 
590*4882a593Smuzhiyun 	device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
591*4882a593Smuzhiyun 	fbdata = info->par;
592*4882a593Smuzhiyun 
593*4882a593Smuzhiyun 	/* disconnect framebuffer from HID dev */
594*4882a593Smuzhiyun 	spin_lock_irqsave(&fbdata->lock, flags);
595*4882a593Smuzhiyun 	fbdata->picolcd = NULL;
596*4882a593Smuzhiyun 	spin_unlock_irqrestore(&fbdata->lock, flags);
597*4882a593Smuzhiyun 
598*4882a593Smuzhiyun 	/* make sure there is no running update - thus that fbdata->picolcd
599*4882a593Smuzhiyun 	 * once obtained under lock is guaranteed not to get free() under
600*4882a593Smuzhiyun 	 * the feet of the deferred work */
601*4882a593Smuzhiyun 	flush_delayed_work(&info->deferred_work);
602*4882a593Smuzhiyun 
603*4882a593Smuzhiyun 	data->fb_info = NULL;
604*4882a593Smuzhiyun 	unregister_framebuffer(info);
605*4882a593Smuzhiyun }
606