brintos

brintos / linux-shallow public Read only

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