brintos

brintos / linux-shallow public Read only

0
0
Text · 17.0 KiB · 6f0942c Raw
725 lines · c
1/*2 * linux/drivers/video/metronomefb.c -- FB driver for Metronome controller3 *4 * Copyright (C) 2008, Jaya Kumar5 *6 * This file is subject to the terms and conditions of the GNU General Public7 * License. See the file COPYING in the main directory of this archive for8 * more details.9 *10 * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.11 *12 * This work was made possible by help and equipment support from E-Ink13 * Corporation. https://www.eink.com/14 *15 * This driver is written to be used with the Metronome display controller.16 * It is intended to be architecture independent. A board specific driver17 * must be used to perform all the physical IO interactions. An example18 * is provided as am200epd.c19 *20 */21#include <linux/module.h>22#include <linux/kernel.h>23#include <linux/errno.h>24#include <linux/string.h>25#include <linux/mm.h>26#include <linux/vmalloc.h>27#include <linux/delay.h>28#include <linux/interrupt.h>29#include <linux/fb.h>30#include <linux/init.h>31#include <linux/platform_device.h>32#include <linux/list.h>33#include <linux/firmware.h>34#include <linux/dma-mapping.h>35#include <linux/uaccess.h>36#include <linux/irq.h>37 38#include <video/metronomefb.h>39 40#include <linux/unaligned.h>41 42/* Display specific information */43#define DPY_W 83244#define DPY_H 62245 46static int user_wfm_size;47 48/* frame differs from image. frame includes non-visible pixels */49struct epd_frame {50	int fw; /* frame width */51	int fh; /* frame height */52	u16 config[4];53	int wfm_size;54};55 56static struct epd_frame epd_frame_table[] = {57	{58		.fw = 832,59		.fh = 622,60		.config = {61			15 /* sdlew */62			| 2 << 8 /* sdosz */63			| 0 << 11 /* sdor */64			| 0 << 12 /* sdces */65			| 0 << 15, /* sdcer */66			42 /* gdspl */67			| 1 << 8 /* gdr1 */68			| 1 << 9 /* sdshr */69			| 0 << 15, /* gdspp */70			18 /* gdspw */71			| 0 << 15, /* dispc */72			599 /* vdlc */73			| 0 << 11 /* dsi */74			| 0 << 12, /* dsic */75		},76		.wfm_size = 47001,77	},78	{79		.fw = 1088,80		.fh = 791,81		.config = {82			0x0104,83			0x031f,84			0x0088,85			0x02ff,86		},87		.wfm_size = 46770,88	},89	{90		.fw = 1200,91		.fh = 842,92		.config = {93			0x0101,94			0x030e,95			0x0012,96			0x0280,97		},98		.wfm_size = 46770,99	},100};101 102static struct fb_fix_screeninfo metronomefb_fix = {103	.id =		"metronomefb",104	.type =		FB_TYPE_PACKED_PIXELS,105	.visual =	FB_VISUAL_STATIC_PSEUDOCOLOR,106	.xpanstep =	0,107	.ypanstep =	0,108	.ywrapstep =	0,109	.line_length =	DPY_W,110	.accel =	FB_ACCEL_NONE,111};112 113static struct fb_var_screeninfo metronomefb_var = {114	.xres		= DPY_W,115	.yres		= DPY_H,116	.xres_virtual	= DPY_W,117	.yres_virtual	= DPY_H,118	.bits_per_pixel	= 8,119	.grayscale	= 1,120	.nonstd		= 1,121	.red =		{ 4, 3, 0 },122	.green =	{ 0, 0, 0 },123	.blue =		{ 0, 0, 0 },124	.transp =	{ 0, 0, 0 },125};126 127/* the waveform structure that is coming from userspace firmware */128struct waveform_hdr {129	u8 stuff[32];130 131	u8 wmta[3];132	u8 fvsn;133 134	u8 luts;135	u8 mc;136	u8 trc;137	u8 stuff3;138 139	u8 endb;140	u8 swtb;141	u8 stuff2a[2];142 143	u8 stuff2b[3];144	u8 wfm_cs;145} __attribute__ ((packed));146 147/* main metronomefb functions */148static u8 calc_cksum(int start, int end, u8 *mem)149{150	u8 tmp = 0;151	int i;152 153	for (i = start; i < end; i++)154		tmp += mem[i];155 156	return tmp;157}158 159static u16 calc_img_cksum(u16 *start, int length)160{161	u16 tmp = 0;162 163	while (length--)164		tmp += *start++;165 166	return tmp;167}168 169/* here we decode the incoming waveform file and populate metromem */170static int load_waveform(u8 *mem, size_t size, int m, int t,171			 struct metronomefb_par *par)172{173	int tta;174	int wmta;175	int trn = 0;176	int i;177	unsigned char v;178	u8 cksum;179	int cksum_idx;180	int wfm_idx, owfm_idx;181	int mem_idx = 0;182	struct waveform_hdr *wfm_hdr;183	u8 *metromem = par->metromem_wfm;184	struct device *dev = par->info->device;185 186	if (user_wfm_size)187		epd_frame_table[par->dt].wfm_size = user_wfm_size;188 189	if (size != epd_frame_table[par->dt].wfm_size) {190		dev_err(dev, "Error: unexpected size %zd != %d\n", size,191					epd_frame_table[par->dt].wfm_size);192		return -EINVAL;193	}194 195	wfm_hdr = (struct waveform_hdr *) mem;196 197	if (wfm_hdr->fvsn != 1) {198		dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn);199		return -EINVAL;200	}201	if (wfm_hdr->luts != 0) {202		dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts);203		return -EINVAL;204	}205	cksum = calc_cksum(32, 47, mem);206	if (cksum != wfm_hdr->wfm_cs) {207		dev_err(dev, "Error: bad cksum %x != %x\n", cksum,208					wfm_hdr->wfm_cs);209		return -EINVAL;210	}211	wfm_hdr->mc += 1;212	wfm_hdr->trc += 1;213	for (i = 0; i < 5; i++) {214		if (*(wfm_hdr->stuff2a + i) != 0) {215			dev_err(dev, "Error: unexpected value in padding\n");216			return -EINVAL;217		}218	}219 220	/* calculating trn. trn is something used to index into221	the waveform. presumably selecting the right one for the222	desired temperature. it works out the offset of the first223	v that exceeds the specified temperature */224	if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size)225		return -EINVAL;226 227	for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) {228		if (mem[i] > t) {229			trn = i - sizeof(*wfm_hdr) - 1;230			break;231		}232	}233 234	/* check temperature range table checksum */235	cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1;236	if (cksum_idx >= size)237		return -EINVAL;238	cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem);239	if (cksum != mem[cksum_idx]) {240		dev_err(dev, "Error: bad temperature range table cksum"241				" %x != %x\n", cksum, mem[cksum_idx]);242		return -EINVAL;243	}244 245	/* check waveform mode table address checksum */246	wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF;247	cksum_idx = wmta + m*4 + 3;248	if (cksum_idx >= size)249		return -EINVAL;250	cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);251	if (cksum != mem[cksum_idx]) {252		dev_err(dev, "Error: bad mode table address cksum"253				" %x != %x\n", cksum, mem[cksum_idx]);254		return -EINVAL;255	}256 257	/* check waveform temperature table address checksum */258	tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF;259	cksum_idx = tta + trn*4 + 3;260	if (cksum_idx >= size)261		return -EINVAL;262	cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);263	if (cksum != mem[cksum_idx]) {264		dev_err(dev, "Error: bad temperature table address cksum"265			" %x != %x\n", cksum, mem[cksum_idx]);266		return -EINVAL;267	}268 269	/* here we do the real work of putting the waveform into the270	metromem buffer. this does runlength decoding of the waveform */271	wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF;272	owfm_idx = wfm_idx;273	if (wfm_idx >= size)274		return -EINVAL;275	while (wfm_idx < size) {276		unsigned char rl;277		v = mem[wfm_idx++];278		if (v == wfm_hdr->swtb) {279			while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) &&280				wfm_idx < size)281				metromem[mem_idx++] = v;282 283			continue;284		}285 286		if (v == wfm_hdr->endb)287			break;288 289		rl = mem[wfm_idx++];290		for (i = 0; i <= rl; i++)291			metromem[mem_idx++] = v;292	}293 294	cksum_idx = wfm_idx;295	if (cksum_idx >= size)296		return -EINVAL;297	cksum = calc_cksum(owfm_idx, cksum_idx, mem);298	if (cksum != mem[cksum_idx]) {299		dev_err(dev, "Error: bad waveform data cksum"300				" %x != %x\n", cksum, mem[cksum_idx]);301		return -EINVAL;302	}303	par->frame_count = (mem_idx/64);304 305	return 0;306}307 308static int metronome_display_cmd(struct metronomefb_par *par)309{310	int i;311	u16 cs;312	u16 opcode;313	static u8 borderval;314 315	/* setup display command316	we can't immediately set the opcode since the controller317	will try parse the command before we've set it all up318	so we just set cs here and set the opcode at the end */319 320	if (par->metromem_cmd->opcode == 0xCC40)321		opcode = cs = 0xCC41;322	else323		opcode = cs = 0xCC40;324 325	/* set the args ( 2 bytes ) for display */326	i = 0;327	par->metromem_cmd->args[i] = 	1 << 3 /* border update */328					| ((borderval++ % 4) & 0x0F) << 4329					| (par->frame_count - 1) << 8;330	cs += par->metromem_cmd->args[i++];331 332	/* the rest are 0 */333	memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);334 335	par->metromem_cmd->csum = cs;336	par->metromem_cmd->opcode = opcode; /* display cmd */337 338	return par->board->met_wait_event_intr(par);339}340 341static int metronome_powerup_cmd(struct metronomefb_par *par)342{343	int i;344	u16 cs;345 346	/* setup power up command */347	par->metromem_cmd->opcode = 0x1234; /* pwr up pseudo cmd */348	cs = par->metromem_cmd->opcode;349 350	/* set pwr1,2,3 to 1024 */351	for (i = 0; i < 3; i++) {352		par->metromem_cmd->args[i] = 1024;353		cs += par->metromem_cmd->args[i];354	}355 356	/* the rest are 0 */357	memset(&par->metromem_cmd->args[i], 0,358	       (ARRAY_SIZE(par->metromem_cmd->args) - i) * 2);359 360	par->metromem_cmd->csum = cs;361 362	msleep(1);363	par->board->set_rst(par, 1);364 365	msleep(1);366	par->board->set_stdby(par, 1);367 368	return par->board->met_wait_event(par);369}370 371static int metronome_config_cmd(struct metronomefb_par *par)372{373	/* setup config command374	we can't immediately set the opcode since the controller375	will try parse the command before we've set it all up */376 377	memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config,378		sizeof(epd_frame_table[par->dt].config));379	/* the rest are 0 */380	memset(&par->metromem_cmd->args[4], 0,381	       (ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2);382 383	par->metromem_cmd->csum = 0xCC10;384	par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4);385	par->metromem_cmd->opcode = 0xCC10; /* config cmd */386 387	return par->board->met_wait_event(par);388}389 390static int metronome_init_cmd(struct metronomefb_par *par)391{392	int i;393	u16 cs;394 395	/* setup init command396	we can't immediately set the opcode since the controller397	will try parse the command before we've set it all up398	so we just set cs here and set the opcode at the end */399 400	cs = 0xCC20;401 402	/* set the args ( 2 bytes ) for init */403	i = 0;404	par->metromem_cmd->args[i] = 0;405	cs += par->metromem_cmd->args[i++];406 407	/* the rest are 0 */408	memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);409 410	par->metromem_cmd->csum = cs;411	par->metromem_cmd->opcode = 0xCC20; /* init cmd */412 413	return par->board->met_wait_event(par);414}415 416static int metronome_init_regs(struct metronomefb_par *par)417{418	int res;419 420	res = par->board->setup_io(par);421	if (res)422		return res;423 424	res = metronome_powerup_cmd(par);425	if (res)426		return res;427 428	res = metronome_config_cmd(par);429	if (res)430		return res;431 432	res = metronome_init_cmd(par);433 434	return res;435}436 437static void metronomefb_dpy_update(struct metronomefb_par *par)438{439	int fbsize;440	u16 cksum;441	unsigned char *buf = par->info->screen_buffer;442 443	fbsize = par->info->fix.smem_len;444	/* copy from vm to metromem */445	memcpy(par->metromem_img, buf, fbsize);446 447	cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2);448	*((u16 *)(par->metromem_img) + fbsize/2) = cksum;449	metronome_display_cmd(par);450}451 452static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)453{454	int i;455	u16 csum = 0;456	u16 *buf = (u16 *)(par->info->screen_buffer + index);457	u16 *img = (u16 *)(par->metromem_img + index);458 459	/* swizzle from vm to metromem and recalc cksum at the same time*/460	for (i = 0; i < PAGE_SIZE/2; i++) {461		*(img + i) = (buf[i] << 5) & 0xE0E0;462		csum += *(img + i);463	}464	return csum;465}466 467/* this is called back from the deferred io workqueue */468static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)469{470	u16 cksum;471	struct fb_deferred_io_pageref *pageref;472	struct metronomefb_par *par = info->par;473 474	/* walk the written page list and swizzle the data */475	list_for_each_entry(pageref, pagereflist, list) {476		unsigned long pgoffset = pageref->offset >> PAGE_SHIFT;477		cksum = metronomefb_dpy_update_page(par, pageref->offset);478		par->metromem_img_csum -= par->csum_table[pgoffset];479		par->csum_table[pgoffset] = cksum;480		par->metromem_img_csum += cksum;481	}482 483	metronome_display_cmd(par);484}485 486static void metronomefb_defio_damage_range(struct fb_info *info, off_t off, size_t len)487{488	struct metronomefb_par *par = info->par;489 490	metronomefb_dpy_update(par);491}492 493static void metronomefb_defio_damage_area(struct fb_info *info, u32 x, u32 y,494					  u32 width, u32 height)495{496	struct metronomefb_par *par = info->par;497 498	metronomefb_dpy_update(par);499}500 501FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(metronomefb,502				   metronomefb_defio_damage_range,503				   metronomefb_defio_damage_area)504 505static const struct fb_ops metronomefb_ops = {506	.owner	= THIS_MODULE,507	FB_DEFAULT_DEFERRED_OPS(metronomefb),508};509 510static struct fb_deferred_io metronomefb_defio = {511	.delay			= HZ,512	.sort_pagereflist	= true,513	.deferred_io		= metronomefb_dpy_deferred_io,514};515 516static int metronomefb_probe(struct platform_device *dev)517{518	struct fb_info *info;519	struct metronome_board *board;520	int retval = -ENOMEM;521	int videomemorysize;522	unsigned char *videomemory;523	struct metronomefb_par *par;524	const struct firmware *fw_entry;525	int i;526	int panel_type;527	int fw, fh;528	int epd_dt_index;529 530	/* pick up board specific routines */531	board = dev->dev.platform_data;532	if (!board)533		return -EINVAL;534 535	/* try to count device specific driver, if can't, platform recalls */536	if (!try_module_get(board->owner))537		return -ENODEV;538 539	info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev);540	if (!info)541		goto err;542 543	/* we have two blocks of memory.544	info->screen_buffer which is vm, and is the fb used by apps.545	par->metromem which is physically contiguous memory and546	contains the display controller commands, waveform,547	processed image data and padding. this is the data pulled548	by the device's LCD controller and pushed to Metronome.549	the metromem memory is allocated by the board driver and550	is provided to us */551 552	panel_type = board->get_panel_type();553	switch (panel_type) {554	case 6:555		epd_dt_index = 0;556		break;557	case 8:558		epd_dt_index = 1;559		break;560	case 97:561		epd_dt_index = 2;562		break;563	default:564		dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n");565		epd_dt_index = 0;566		break;567	}568 569	fw = epd_frame_table[epd_dt_index].fw;570	fh = epd_frame_table[epd_dt_index].fh;571 572	/* we need to add a spare page because our csum caching scheme walks573	 * to the end of the page */574	videomemorysize = PAGE_SIZE + (fw * fh);575	videomemory = vzalloc(videomemorysize);576	if (!videomemory)577		goto err_fb_rel;578 579	info->screen_buffer = videomemory;580	info->fbops = &metronomefb_ops;581 582	metronomefb_fix.line_length = fw;583	metronomefb_var.xres = fw;584	metronomefb_var.yres = fh;585	metronomefb_var.xres_virtual = fw;586	metronomefb_var.yres_virtual = fh;587	info->var = metronomefb_var;588	info->fix = metronomefb_fix;589	info->fix.smem_len = videomemorysize;590	par = info->par;591	par->info = info;592	par->board = board;593	par->dt = epd_dt_index;594	init_waitqueue_head(&par->waitq);595 596	/* this table caches per page csum values. */597	par->csum_table = vmalloc(videomemorysize/PAGE_SIZE);598	if (!par->csum_table)599		goto err_vfree;600 601	/* the physical framebuffer that we use is setup by602	 * the platform device driver. It will provide us603	 * with cmd, wfm and image memory in a contiguous area. */604	retval = board->setup_fb(par);605	if (retval) {606		dev_err(&dev->dev, "Failed to setup fb\n");607		goto err_csum_table;608	}609 610	/* after this point we should have a framebuffer */611	if ((!par->metromem_wfm) ||  (!par->metromem_img) ||612		(!par->metromem_dma)) {613		dev_err(&dev->dev, "fb access failure\n");614		retval = -EINVAL;615		goto err_csum_table;616	}617 618	info->fix.smem_start = par->metromem_dma;619 620	/* load the waveform in. assume mode 3, temp 31 for now621		a) request the waveform file from userspace622		b) process waveform and decode into metromem */623	retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev);624	if (retval < 0) {625		dev_err(&dev->dev, "Failed to get waveform\n");626		goto err_csum_table;627	}628 629	retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31,630				par);631	release_firmware(fw_entry);632	if (retval < 0) {633		dev_err(&dev->dev, "Failed processing waveform\n");634		goto err_csum_table;635	}636 637	retval = board->setup_irq(info);638	if (retval)639		goto err_csum_table;640 641	retval = metronome_init_regs(par);642	if (retval < 0)643		goto err_free_irq;644 645	info->flags = FBINFO_VIRTFB;646 647	info->fbdefio = &metronomefb_defio;648	fb_deferred_io_init(info);649 650	retval = fb_alloc_cmap(&info->cmap, 8, 0);651	if (retval < 0) {652		dev_err(&dev->dev, "Failed to allocate colormap\n");653		goto err_free_irq;654	}655 656	/* set cmap */657	for (i = 0; i < 8; i++)658		info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16;659	memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8);660	memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8);661 662	retval = register_framebuffer(info);663	if (retval < 0)664		goto err_cmap;665 666	platform_set_drvdata(dev, info);667 668	dev_dbg(&dev->dev,669		"fb%d: Metronome frame buffer device, using %dK of video"670		" memory\n", info->node, videomemorysize >> 10);671 672	return 0;673 674err_cmap:675	fb_dealloc_cmap(&info->cmap);676err_free_irq:677	board->cleanup(par);678err_csum_table:679	vfree(par->csum_table);680err_vfree:681	vfree(videomemory);682err_fb_rel:683	framebuffer_release(info);684err:685	module_put(board->owner);686	return retval;687}688 689static void metronomefb_remove(struct platform_device *dev)690{691	struct fb_info *info = platform_get_drvdata(dev);692 693	if (info) {694		struct metronomefb_par *par = info->par;695 696		unregister_framebuffer(info);697		fb_deferred_io_cleanup(info);698		fb_dealloc_cmap(&info->cmap);699		par->board->cleanup(par);700		vfree(par->csum_table);701		vfree(info->screen_buffer);702		module_put(par->board->owner);703		dev_dbg(&dev->dev, "calling release\n");704		framebuffer_release(info);705	}706}707 708static struct platform_driver metronomefb_driver = {709	.probe	= metronomefb_probe,710	.remove	= metronomefb_remove,711	.driver	= {712		.name	= "metronomefb",713	},714};715module_platform_driver(metronomefb_driver);716 717module_param(user_wfm_size, uint, 0);718MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size");719 720MODULE_DESCRIPTION("fbdev driver for Metronome controller");721MODULE_AUTHOR("Jaya Kumar");722MODULE_LICENSE("GPL");723 724MODULE_FIRMWARE("metronome.wbf");725