162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * linux/drivers/video/metronomefb.c -- FB driver for Metronome controller
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2008, Jaya Kumar
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
762306a36Sopenharmony_ci * License. See the file COPYING in the main directory of this archive for
862306a36Sopenharmony_ci * more details.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * This work was made possible by help and equipment support from E-Ink
1362306a36Sopenharmony_ci * Corporation. https://www.eink.com/
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * This driver is written to be used with the Metronome display controller.
1662306a36Sopenharmony_ci * It is intended to be architecture independent. A board specific driver
1762306a36Sopenharmony_ci * must be used to perform all the physical IO interactions. An example
1862306a36Sopenharmony_ci * is provided as am200epd.c
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ci#include <linux/module.h>
2262306a36Sopenharmony_ci#include <linux/kernel.h>
2362306a36Sopenharmony_ci#include <linux/errno.h>
2462306a36Sopenharmony_ci#include <linux/string.h>
2562306a36Sopenharmony_ci#include <linux/mm.h>
2662306a36Sopenharmony_ci#include <linux/vmalloc.h>
2762306a36Sopenharmony_ci#include <linux/delay.h>
2862306a36Sopenharmony_ci#include <linux/interrupt.h>
2962306a36Sopenharmony_ci#include <linux/fb.h>
3062306a36Sopenharmony_ci#include <linux/init.h>
3162306a36Sopenharmony_ci#include <linux/platform_device.h>
3262306a36Sopenharmony_ci#include <linux/list.h>
3362306a36Sopenharmony_ci#include <linux/firmware.h>
3462306a36Sopenharmony_ci#include <linux/dma-mapping.h>
3562306a36Sopenharmony_ci#include <linux/uaccess.h>
3662306a36Sopenharmony_ci#include <linux/irq.h>
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#include <video/metronomefb.h>
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#include <asm/unaligned.h>
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci/* Display specific information */
4362306a36Sopenharmony_ci#define DPY_W 832
4462306a36Sopenharmony_ci#define DPY_H 622
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic int user_wfm_size;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* frame differs from image. frame includes non-visible pixels */
4962306a36Sopenharmony_cistruct epd_frame {
5062306a36Sopenharmony_ci	int fw; /* frame width */
5162306a36Sopenharmony_ci	int fh; /* frame height */
5262306a36Sopenharmony_ci	u16 config[4];
5362306a36Sopenharmony_ci	int wfm_size;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic struct epd_frame epd_frame_table[] = {
5762306a36Sopenharmony_ci	{
5862306a36Sopenharmony_ci		.fw = 832,
5962306a36Sopenharmony_ci		.fh = 622,
6062306a36Sopenharmony_ci		.config = {
6162306a36Sopenharmony_ci			15 /* sdlew */
6262306a36Sopenharmony_ci			| 2 << 8 /* sdosz */
6362306a36Sopenharmony_ci			| 0 << 11 /* sdor */
6462306a36Sopenharmony_ci			| 0 << 12 /* sdces */
6562306a36Sopenharmony_ci			| 0 << 15, /* sdcer */
6662306a36Sopenharmony_ci			42 /* gdspl */
6762306a36Sopenharmony_ci			| 1 << 8 /* gdr1 */
6862306a36Sopenharmony_ci			| 1 << 9 /* sdshr */
6962306a36Sopenharmony_ci			| 0 << 15, /* gdspp */
7062306a36Sopenharmony_ci			18 /* gdspw */
7162306a36Sopenharmony_ci			| 0 << 15, /* dispc */
7262306a36Sopenharmony_ci			599 /* vdlc */
7362306a36Sopenharmony_ci			| 0 << 11 /* dsi */
7462306a36Sopenharmony_ci			| 0 << 12, /* dsic */
7562306a36Sopenharmony_ci		},
7662306a36Sopenharmony_ci		.wfm_size = 47001,
7762306a36Sopenharmony_ci	},
7862306a36Sopenharmony_ci	{
7962306a36Sopenharmony_ci		.fw = 1088,
8062306a36Sopenharmony_ci		.fh = 791,
8162306a36Sopenharmony_ci		.config = {
8262306a36Sopenharmony_ci			0x0104,
8362306a36Sopenharmony_ci			0x031f,
8462306a36Sopenharmony_ci			0x0088,
8562306a36Sopenharmony_ci			0x02ff,
8662306a36Sopenharmony_ci		},
8762306a36Sopenharmony_ci		.wfm_size = 46770,
8862306a36Sopenharmony_ci	},
8962306a36Sopenharmony_ci	{
9062306a36Sopenharmony_ci		.fw = 1200,
9162306a36Sopenharmony_ci		.fh = 842,
9262306a36Sopenharmony_ci		.config = {
9362306a36Sopenharmony_ci			0x0101,
9462306a36Sopenharmony_ci			0x030e,
9562306a36Sopenharmony_ci			0x0012,
9662306a36Sopenharmony_ci			0x0280,
9762306a36Sopenharmony_ci		},
9862306a36Sopenharmony_ci		.wfm_size = 46770,
9962306a36Sopenharmony_ci	},
10062306a36Sopenharmony_ci};
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic struct fb_fix_screeninfo metronomefb_fix = {
10362306a36Sopenharmony_ci	.id =		"metronomefb",
10462306a36Sopenharmony_ci	.type =		FB_TYPE_PACKED_PIXELS,
10562306a36Sopenharmony_ci	.visual =	FB_VISUAL_STATIC_PSEUDOCOLOR,
10662306a36Sopenharmony_ci	.xpanstep =	0,
10762306a36Sopenharmony_ci	.ypanstep =	0,
10862306a36Sopenharmony_ci	.ywrapstep =	0,
10962306a36Sopenharmony_ci	.line_length =	DPY_W,
11062306a36Sopenharmony_ci	.accel =	FB_ACCEL_NONE,
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic struct fb_var_screeninfo metronomefb_var = {
11462306a36Sopenharmony_ci	.xres		= DPY_W,
11562306a36Sopenharmony_ci	.yres		= DPY_H,
11662306a36Sopenharmony_ci	.xres_virtual	= DPY_W,
11762306a36Sopenharmony_ci	.yres_virtual	= DPY_H,
11862306a36Sopenharmony_ci	.bits_per_pixel	= 8,
11962306a36Sopenharmony_ci	.grayscale	= 1,
12062306a36Sopenharmony_ci	.nonstd		= 1,
12162306a36Sopenharmony_ci	.red =		{ 4, 3, 0 },
12262306a36Sopenharmony_ci	.green =	{ 0, 0, 0 },
12362306a36Sopenharmony_ci	.blue =		{ 0, 0, 0 },
12462306a36Sopenharmony_ci	.transp =	{ 0, 0, 0 },
12562306a36Sopenharmony_ci};
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci/* the waveform structure that is coming from userspace firmware */
12862306a36Sopenharmony_cistruct waveform_hdr {
12962306a36Sopenharmony_ci	u8 stuff[32];
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	u8 wmta[3];
13262306a36Sopenharmony_ci	u8 fvsn;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	u8 luts;
13562306a36Sopenharmony_ci	u8 mc;
13662306a36Sopenharmony_ci	u8 trc;
13762306a36Sopenharmony_ci	u8 stuff3;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	u8 endb;
14062306a36Sopenharmony_ci	u8 swtb;
14162306a36Sopenharmony_ci	u8 stuff2a[2];
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	u8 stuff2b[3];
14462306a36Sopenharmony_ci	u8 wfm_cs;
14562306a36Sopenharmony_ci} __attribute__ ((packed));
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci/* main metronomefb functions */
14862306a36Sopenharmony_cistatic u8 calc_cksum(int start, int end, u8 *mem)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	u8 tmp = 0;
15162306a36Sopenharmony_ci	int i;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	for (i = start; i < end; i++)
15462306a36Sopenharmony_ci		tmp += mem[i];
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return tmp;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic u16 calc_img_cksum(u16 *start, int length)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	u16 tmp = 0;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	while (length--)
16462306a36Sopenharmony_ci		tmp += *start++;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return tmp;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci/* here we decode the incoming waveform file and populate metromem */
17062306a36Sopenharmony_cistatic int load_waveform(u8 *mem, size_t size, int m, int t,
17162306a36Sopenharmony_ci			 struct metronomefb_par *par)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	int tta;
17462306a36Sopenharmony_ci	int wmta;
17562306a36Sopenharmony_ci	int trn = 0;
17662306a36Sopenharmony_ci	int i;
17762306a36Sopenharmony_ci	unsigned char v;
17862306a36Sopenharmony_ci	u8 cksum;
17962306a36Sopenharmony_ci	int cksum_idx;
18062306a36Sopenharmony_ci	int wfm_idx, owfm_idx;
18162306a36Sopenharmony_ci	int mem_idx = 0;
18262306a36Sopenharmony_ci	struct waveform_hdr *wfm_hdr;
18362306a36Sopenharmony_ci	u8 *metromem = par->metromem_wfm;
18462306a36Sopenharmony_ci	struct device *dev = par->info->device;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (user_wfm_size)
18762306a36Sopenharmony_ci		epd_frame_table[par->dt].wfm_size = user_wfm_size;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (size != epd_frame_table[par->dt].wfm_size) {
19062306a36Sopenharmony_ci		dev_err(dev, "Error: unexpected size %zd != %d\n", size,
19162306a36Sopenharmony_ci					epd_frame_table[par->dt].wfm_size);
19262306a36Sopenharmony_ci		return -EINVAL;
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	wfm_hdr = (struct waveform_hdr *) mem;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (wfm_hdr->fvsn != 1) {
19862306a36Sopenharmony_ci		dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn);
19962306a36Sopenharmony_ci		return -EINVAL;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci	if (wfm_hdr->luts != 0) {
20262306a36Sopenharmony_ci		dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts);
20362306a36Sopenharmony_ci		return -EINVAL;
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci	cksum = calc_cksum(32, 47, mem);
20662306a36Sopenharmony_ci	if (cksum != wfm_hdr->wfm_cs) {
20762306a36Sopenharmony_ci		dev_err(dev, "Error: bad cksum %x != %x\n", cksum,
20862306a36Sopenharmony_ci					wfm_hdr->wfm_cs);
20962306a36Sopenharmony_ci		return -EINVAL;
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci	wfm_hdr->mc += 1;
21262306a36Sopenharmony_ci	wfm_hdr->trc += 1;
21362306a36Sopenharmony_ci	for (i = 0; i < 5; i++) {
21462306a36Sopenharmony_ci		if (*(wfm_hdr->stuff2a + i) != 0) {
21562306a36Sopenharmony_ci			dev_err(dev, "Error: unexpected value in padding\n");
21662306a36Sopenharmony_ci			return -EINVAL;
21762306a36Sopenharmony_ci		}
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* calculating trn. trn is something used to index into
22162306a36Sopenharmony_ci	the waveform. presumably selecting the right one for the
22262306a36Sopenharmony_ci	desired temperature. it works out the offset of the first
22362306a36Sopenharmony_ci	v that exceeds the specified temperature */
22462306a36Sopenharmony_ci	if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size)
22562306a36Sopenharmony_ci		return -EINVAL;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) {
22862306a36Sopenharmony_ci		if (mem[i] > t) {
22962306a36Sopenharmony_ci			trn = i - sizeof(*wfm_hdr) - 1;
23062306a36Sopenharmony_ci			break;
23162306a36Sopenharmony_ci		}
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	/* check temperature range table checksum */
23562306a36Sopenharmony_ci	cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1;
23662306a36Sopenharmony_ci	if (cksum_idx >= size)
23762306a36Sopenharmony_ci		return -EINVAL;
23862306a36Sopenharmony_ci	cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem);
23962306a36Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
24062306a36Sopenharmony_ci		dev_err(dev, "Error: bad temperature range table cksum"
24162306a36Sopenharmony_ci				" %x != %x\n", cksum, mem[cksum_idx]);
24262306a36Sopenharmony_ci		return -EINVAL;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/* check waveform mode table address checksum */
24662306a36Sopenharmony_ci	wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF;
24762306a36Sopenharmony_ci	cksum_idx = wmta + m*4 + 3;
24862306a36Sopenharmony_ci	if (cksum_idx >= size)
24962306a36Sopenharmony_ci		return -EINVAL;
25062306a36Sopenharmony_ci	cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
25162306a36Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
25262306a36Sopenharmony_ci		dev_err(dev, "Error: bad mode table address cksum"
25362306a36Sopenharmony_ci				" %x != %x\n", cksum, mem[cksum_idx]);
25462306a36Sopenharmony_ci		return -EINVAL;
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	/* check waveform temperature table address checksum */
25862306a36Sopenharmony_ci	tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF;
25962306a36Sopenharmony_ci	cksum_idx = tta + trn*4 + 3;
26062306a36Sopenharmony_ci	if (cksum_idx >= size)
26162306a36Sopenharmony_ci		return -EINVAL;
26262306a36Sopenharmony_ci	cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
26362306a36Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
26462306a36Sopenharmony_ci		dev_err(dev, "Error: bad temperature table address cksum"
26562306a36Sopenharmony_ci			" %x != %x\n", cksum, mem[cksum_idx]);
26662306a36Sopenharmony_ci		return -EINVAL;
26762306a36Sopenharmony_ci	}
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* here we do the real work of putting the waveform into the
27062306a36Sopenharmony_ci	metromem buffer. this does runlength decoding of the waveform */
27162306a36Sopenharmony_ci	wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF;
27262306a36Sopenharmony_ci	owfm_idx = wfm_idx;
27362306a36Sopenharmony_ci	if (wfm_idx >= size)
27462306a36Sopenharmony_ci		return -EINVAL;
27562306a36Sopenharmony_ci	while (wfm_idx < size) {
27662306a36Sopenharmony_ci		unsigned char rl;
27762306a36Sopenharmony_ci		v = mem[wfm_idx++];
27862306a36Sopenharmony_ci		if (v == wfm_hdr->swtb) {
27962306a36Sopenharmony_ci			while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) &&
28062306a36Sopenharmony_ci				wfm_idx < size)
28162306a36Sopenharmony_ci				metromem[mem_idx++] = v;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci			continue;
28462306a36Sopenharmony_ci		}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci		if (v == wfm_hdr->endb)
28762306a36Sopenharmony_ci			break;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		rl = mem[wfm_idx++];
29062306a36Sopenharmony_ci		for (i = 0; i <= rl; i++)
29162306a36Sopenharmony_ci			metromem[mem_idx++] = v;
29262306a36Sopenharmony_ci	}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	cksum_idx = wfm_idx;
29562306a36Sopenharmony_ci	if (cksum_idx >= size)
29662306a36Sopenharmony_ci		return -EINVAL;
29762306a36Sopenharmony_ci	cksum = calc_cksum(owfm_idx, cksum_idx, mem);
29862306a36Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
29962306a36Sopenharmony_ci		dev_err(dev, "Error: bad waveform data cksum"
30062306a36Sopenharmony_ci				" %x != %x\n", cksum, mem[cksum_idx]);
30162306a36Sopenharmony_ci		return -EINVAL;
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci	par->frame_count = (mem_idx/64);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return 0;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int metronome_display_cmd(struct metronomefb_par *par)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	int i;
31162306a36Sopenharmony_ci	u16 cs;
31262306a36Sopenharmony_ci	u16 opcode;
31362306a36Sopenharmony_ci	static u8 borderval;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	/* setup display command
31662306a36Sopenharmony_ci	we can't immediately set the opcode since the controller
31762306a36Sopenharmony_ci	will try parse the command before we've set it all up
31862306a36Sopenharmony_ci	so we just set cs here and set the opcode at the end */
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	if (par->metromem_cmd->opcode == 0xCC40)
32162306a36Sopenharmony_ci		opcode = cs = 0xCC41;
32262306a36Sopenharmony_ci	else
32362306a36Sopenharmony_ci		opcode = cs = 0xCC40;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	/* set the args ( 2 bytes ) for display */
32662306a36Sopenharmony_ci	i = 0;
32762306a36Sopenharmony_ci	par->metromem_cmd->args[i] = 	1 << 3 /* border update */
32862306a36Sopenharmony_ci					| ((borderval++ % 4) & 0x0F) << 4
32962306a36Sopenharmony_ci					| (par->frame_count - 1) << 8;
33062306a36Sopenharmony_ci	cs += par->metromem_cmd->args[i++];
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	/* the rest are 0 */
33362306a36Sopenharmony_ci	memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	par->metromem_cmd->csum = cs;
33662306a36Sopenharmony_ci	par->metromem_cmd->opcode = opcode; /* display cmd */
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	return par->board->met_wait_event_intr(par);
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic int metronome_powerup_cmd(struct metronomefb_par *par)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	int i;
34462306a36Sopenharmony_ci	u16 cs;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	/* setup power up command */
34762306a36Sopenharmony_ci	par->metromem_cmd->opcode = 0x1234; /* pwr up pseudo cmd */
34862306a36Sopenharmony_ci	cs = par->metromem_cmd->opcode;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	/* set pwr1,2,3 to 1024 */
35162306a36Sopenharmony_ci	for (i = 0; i < 3; i++) {
35262306a36Sopenharmony_ci		par->metromem_cmd->args[i] = 1024;
35362306a36Sopenharmony_ci		cs += par->metromem_cmd->args[i];
35462306a36Sopenharmony_ci	}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	/* the rest are 0 */
35762306a36Sopenharmony_ci	memset(&par->metromem_cmd->args[i], 0,
35862306a36Sopenharmony_ci	       (ARRAY_SIZE(par->metromem_cmd->args) - i) * 2);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	par->metromem_cmd->csum = cs;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	msleep(1);
36362306a36Sopenharmony_ci	par->board->set_rst(par, 1);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	msleep(1);
36662306a36Sopenharmony_ci	par->board->set_stdby(par, 1);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	return par->board->met_wait_event(par);
36962306a36Sopenharmony_ci}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_cistatic int metronome_config_cmd(struct metronomefb_par *par)
37262306a36Sopenharmony_ci{
37362306a36Sopenharmony_ci	/* setup config command
37462306a36Sopenharmony_ci	we can't immediately set the opcode since the controller
37562306a36Sopenharmony_ci	will try parse the command before we've set it all up */
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config,
37862306a36Sopenharmony_ci		sizeof(epd_frame_table[par->dt].config));
37962306a36Sopenharmony_ci	/* the rest are 0 */
38062306a36Sopenharmony_ci	memset(&par->metromem_cmd->args[4], 0,
38162306a36Sopenharmony_ci	       (ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	par->metromem_cmd->csum = 0xCC10;
38462306a36Sopenharmony_ci	par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4);
38562306a36Sopenharmony_ci	par->metromem_cmd->opcode = 0xCC10; /* config cmd */
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	return par->board->met_wait_event(par);
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_cistatic int metronome_init_cmd(struct metronomefb_par *par)
39162306a36Sopenharmony_ci{
39262306a36Sopenharmony_ci	int i;
39362306a36Sopenharmony_ci	u16 cs;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	/* setup init command
39662306a36Sopenharmony_ci	we can't immediately set the opcode since the controller
39762306a36Sopenharmony_ci	will try parse the command before we've set it all up
39862306a36Sopenharmony_ci	so we just set cs here and set the opcode at the end */
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	cs = 0xCC20;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	/* set the args ( 2 bytes ) for init */
40362306a36Sopenharmony_ci	i = 0;
40462306a36Sopenharmony_ci	par->metromem_cmd->args[i] = 0;
40562306a36Sopenharmony_ci	cs += par->metromem_cmd->args[i++];
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	/* the rest are 0 */
40862306a36Sopenharmony_ci	memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	par->metromem_cmd->csum = cs;
41162306a36Sopenharmony_ci	par->metromem_cmd->opcode = 0xCC20; /* init cmd */
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	return par->board->met_wait_event(par);
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic int metronome_init_regs(struct metronomefb_par *par)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	int res;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	res = par->board->setup_io(par);
42162306a36Sopenharmony_ci	if (res)
42262306a36Sopenharmony_ci		return res;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	res = metronome_powerup_cmd(par);
42562306a36Sopenharmony_ci	if (res)
42662306a36Sopenharmony_ci		return res;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	res = metronome_config_cmd(par);
42962306a36Sopenharmony_ci	if (res)
43062306a36Sopenharmony_ci		return res;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	res = metronome_init_cmd(par);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	return res;
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_cistatic void metronomefb_dpy_update(struct metronomefb_par *par)
43862306a36Sopenharmony_ci{
43962306a36Sopenharmony_ci	int fbsize;
44062306a36Sopenharmony_ci	u16 cksum;
44162306a36Sopenharmony_ci	unsigned char *buf = par->info->screen_buffer;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	fbsize = par->info->fix.smem_len;
44462306a36Sopenharmony_ci	/* copy from vm to metromem */
44562306a36Sopenharmony_ci	memcpy(par->metromem_img, buf, fbsize);
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2);
44862306a36Sopenharmony_ci	*((u16 *)(par->metromem_img) + fbsize/2) = cksum;
44962306a36Sopenharmony_ci	metronome_display_cmd(par);
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	int i;
45562306a36Sopenharmony_ci	u16 csum = 0;
45662306a36Sopenharmony_ci	u16 *buf = (u16 *)(par->info->screen_buffer + index);
45762306a36Sopenharmony_ci	u16 *img = (u16 *)(par->metromem_img + index);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	/* swizzle from vm to metromem and recalc cksum at the same time*/
46062306a36Sopenharmony_ci	for (i = 0; i < PAGE_SIZE/2; i++) {
46162306a36Sopenharmony_ci		*(img + i) = (buf[i] << 5) & 0xE0E0;
46262306a36Sopenharmony_ci		csum += *(img + i);
46362306a36Sopenharmony_ci	}
46462306a36Sopenharmony_ci	return csum;
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci/* this is called back from the deferred io workqueue */
46862306a36Sopenharmony_cistatic void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	u16 cksum;
47162306a36Sopenharmony_ci	struct fb_deferred_io_pageref *pageref;
47262306a36Sopenharmony_ci	struct metronomefb_par *par = info->par;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	/* walk the written page list and swizzle the data */
47562306a36Sopenharmony_ci	list_for_each_entry(pageref, pagereflist, list) {
47662306a36Sopenharmony_ci		unsigned long pgoffset = pageref->offset >> PAGE_SHIFT;
47762306a36Sopenharmony_ci		cksum = metronomefb_dpy_update_page(par, pageref->offset);
47862306a36Sopenharmony_ci		par->metromem_img_csum -= par->csum_table[pgoffset];
47962306a36Sopenharmony_ci		par->csum_table[pgoffset] = cksum;
48062306a36Sopenharmony_ci		par->metromem_img_csum += cksum;
48162306a36Sopenharmony_ci	}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	metronome_display_cmd(par);
48462306a36Sopenharmony_ci}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_cistatic void metronomefb_defio_damage_range(struct fb_info *info, off_t off, size_t len)
48762306a36Sopenharmony_ci{
48862306a36Sopenharmony_ci	struct metronomefb_par *par = info->par;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	metronomefb_dpy_update(par);
49162306a36Sopenharmony_ci}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_cistatic void metronomefb_defio_damage_area(struct fb_info *info, u32 x, u32 y,
49462306a36Sopenharmony_ci					  u32 width, u32 height)
49562306a36Sopenharmony_ci{
49662306a36Sopenharmony_ci	struct metronomefb_par *par = info->par;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	metronomefb_dpy_update(par);
49962306a36Sopenharmony_ci}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ciFB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(metronomefb,
50262306a36Sopenharmony_ci				   metronomefb_defio_damage_range,
50362306a36Sopenharmony_ci				   metronomefb_defio_damage_area)
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cistatic const struct fb_ops metronomefb_ops = {
50662306a36Sopenharmony_ci	.owner	= THIS_MODULE,
50762306a36Sopenharmony_ci	FB_DEFAULT_DEFERRED_OPS(metronomefb),
50862306a36Sopenharmony_ci};
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_cistatic struct fb_deferred_io metronomefb_defio = {
51162306a36Sopenharmony_ci	.delay			= HZ,
51262306a36Sopenharmony_ci	.sort_pagereflist	= true,
51362306a36Sopenharmony_ci	.deferred_io		= metronomefb_dpy_deferred_io,
51462306a36Sopenharmony_ci};
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic int metronomefb_probe(struct platform_device *dev)
51762306a36Sopenharmony_ci{
51862306a36Sopenharmony_ci	struct fb_info *info;
51962306a36Sopenharmony_ci	struct metronome_board *board;
52062306a36Sopenharmony_ci	int retval = -ENOMEM;
52162306a36Sopenharmony_ci	int videomemorysize;
52262306a36Sopenharmony_ci	unsigned char *videomemory;
52362306a36Sopenharmony_ci	struct metronomefb_par *par;
52462306a36Sopenharmony_ci	const struct firmware *fw_entry;
52562306a36Sopenharmony_ci	int i;
52662306a36Sopenharmony_ci	int panel_type;
52762306a36Sopenharmony_ci	int fw, fh;
52862306a36Sopenharmony_ci	int epd_dt_index;
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	/* pick up board specific routines */
53162306a36Sopenharmony_ci	board = dev->dev.platform_data;
53262306a36Sopenharmony_ci	if (!board)
53362306a36Sopenharmony_ci		return -EINVAL;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	/* try to count device specific driver, if can't, platform recalls */
53662306a36Sopenharmony_ci	if (!try_module_get(board->owner))
53762306a36Sopenharmony_ci		return -ENODEV;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev);
54062306a36Sopenharmony_ci	if (!info)
54162306a36Sopenharmony_ci		goto err;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	/* we have two blocks of memory.
54462306a36Sopenharmony_ci	info->screen_buffer which is vm, and is the fb used by apps.
54562306a36Sopenharmony_ci	par->metromem which is physically contiguous memory and
54662306a36Sopenharmony_ci	contains the display controller commands, waveform,
54762306a36Sopenharmony_ci	processed image data and padding. this is the data pulled
54862306a36Sopenharmony_ci	by the device's LCD controller and pushed to Metronome.
54962306a36Sopenharmony_ci	the metromem memory is allocated by the board driver and
55062306a36Sopenharmony_ci	is provided to us */
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	panel_type = board->get_panel_type();
55362306a36Sopenharmony_ci	switch (panel_type) {
55462306a36Sopenharmony_ci	case 6:
55562306a36Sopenharmony_ci		epd_dt_index = 0;
55662306a36Sopenharmony_ci		break;
55762306a36Sopenharmony_ci	case 8:
55862306a36Sopenharmony_ci		epd_dt_index = 1;
55962306a36Sopenharmony_ci		break;
56062306a36Sopenharmony_ci	case 97:
56162306a36Sopenharmony_ci		epd_dt_index = 2;
56262306a36Sopenharmony_ci		break;
56362306a36Sopenharmony_ci	default:
56462306a36Sopenharmony_ci		dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n");
56562306a36Sopenharmony_ci		epd_dt_index = 0;
56662306a36Sopenharmony_ci		break;
56762306a36Sopenharmony_ci	}
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_ci	fw = epd_frame_table[epd_dt_index].fw;
57062306a36Sopenharmony_ci	fh = epd_frame_table[epd_dt_index].fh;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	/* we need to add a spare page because our csum caching scheme walks
57362306a36Sopenharmony_ci	 * to the end of the page */
57462306a36Sopenharmony_ci	videomemorysize = PAGE_SIZE + (fw * fh);
57562306a36Sopenharmony_ci	videomemory = vzalloc(videomemorysize);
57662306a36Sopenharmony_ci	if (!videomemory)
57762306a36Sopenharmony_ci		goto err_fb_rel;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	info->screen_buffer = videomemory;
58062306a36Sopenharmony_ci	info->fbops = &metronomefb_ops;
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	metronomefb_fix.line_length = fw;
58362306a36Sopenharmony_ci	metronomefb_var.xres = fw;
58462306a36Sopenharmony_ci	metronomefb_var.yres = fh;
58562306a36Sopenharmony_ci	metronomefb_var.xres_virtual = fw;
58662306a36Sopenharmony_ci	metronomefb_var.yres_virtual = fh;
58762306a36Sopenharmony_ci	info->var = metronomefb_var;
58862306a36Sopenharmony_ci	info->fix = metronomefb_fix;
58962306a36Sopenharmony_ci	info->fix.smem_len = videomemorysize;
59062306a36Sopenharmony_ci	par = info->par;
59162306a36Sopenharmony_ci	par->info = info;
59262306a36Sopenharmony_ci	par->board = board;
59362306a36Sopenharmony_ci	par->dt = epd_dt_index;
59462306a36Sopenharmony_ci	init_waitqueue_head(&par->waitq);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	/* this table caches per page csum values. */
59762306a36Sopenharmony_ci	par->csum_table = vmalloc(videomemorysize/PAGE_SIZE);
59862306a36Sopenharmony_ci	if (!par->csum_table)
59962306a36Sopenharmony_ci		goto err_vfree;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	/* the physical framebuffer that we use is setup by
60262306a36Sopenharmony_ci	 * the platform device driver. It will provide us
60362306a36Sopenharmony_ci	 * with cmd, wfm and image memory in a contiguous area. */
60462306a36Sopenharmony_ci	retval = board->setup_fb(par);
60562306a36Sopenharmony_ci	if (retval) {
60662306a36Sopenharmony_ci		dev_err(&dev->dev, "Failed to setup fb\n");
60762306a36Sopenharmony_ci		goto err_csum_table;
60862306a36Sopenharmony_ci	}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	/* after this point we should have a framebuffer */
61162306a36Sopenharmony_ci	if ((!par->metromem_wfm) ||  (!par->metromem_img) ||
61262306a36Sopenharmony_ci		(!par->metromem_dma)) {
61362306a36Sopenharmony_ci		dev_err(&dev->dev, "fb access failure\n");
61462306a36Sopenharmony_ci		retval = -EINVAL;
61562306a36Sopenharmony_ci		goto err_csum_table;
61662306a36Sopenharmony_ci	}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	info->fix.smem_start = par->metromem_dma;
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	/* load the waveform in. assume mode 3, temp 31 for now
62162306a36Sopenharmony_ci		a) request the waveform file from userspace
62262306a36Sopenharmony_ci		b) process waveform and decode into metromem */
62362306a36Sopenharmony_ci	retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev);
62462306a36Sopenharmony_ci	if (retval < 0) {
62562306a36Sopenharmony_ci		dev_err(&dev->dev, "Failed to get waveform\n");
62662306a36Sopenharmony_ci		goto err_csum_table;
62762306a36Sopenharmony_ci	}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31,
63062306a36Sopenharmony_ci				par);
63162306a36Sopenharmony_ci	release_firmware(fw_entry);
63262306a36Sopenharmony_ci	if (retval < 0) {
63362306a36Sopenharmony_ci		dev_err(&dev->dev, "Failed processing waveform\n");
63462306a36Sopenharmony_ci		goto err_csum_table;
63562306a36Sopenharmony_ci	}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	retval = board->setup_irq(info);
63862306a36Sopenharmony_ci	if (retval)
63962306a36Sopenharmony_ci		goto err_csum_table;
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	retval = metronome_init_regs(par);
64262306a36Sopenharmony_ci	if (retval < 0)
64362306a36Sopenharmony_ci		goto err_free_irq;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	info->flags = FBINFO_VIRTFB;
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	info->fbdefio = &metronomefb_defio;
64862306a36Sopenharmony_ci	fb_deferred_io_init(info);
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	retval = fb_alloc_cmap(&info->cmap, 8, 0);
65162306a36Sopenharmony_ci	if (retval < 0) {
65262306a36Sopenharmony_ci		dev_err(&dev->dev, "Failed to allocate colormap\n");
65362306a36Sopenharmony_ci		goto err_free_irq;
65462306a36Sopenharmony_ci	}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	/* set cmap */
65762306a36Sopenharmony_ci	for (i = 0; i < 8; i++)
65862306a36Sopenharmony_ci		info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16;
65962306a36Sopenharmony_ci	memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8);
66062306a36Sopenharmony_ci	memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8);
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	retval = register_framebuffer(info);
66362306a36Sopenharmony_ci	if (retval < 0)
66462306a36Sopenharmony_ci		goto err_cmap;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	platform_set_drvdata(dev, info);
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	dev_dbg(&dev->dev,
66962306a36Sopenharmony_ci		"fb%d: Metronome frame buffer device, using %dK of video"
67062306a36Sopenharmony_ci		" memory\n", info->node, videomemorysize >> 10);
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	return 0;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_cierr_cmap:
67562306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
67662306a36Sopenharmony_cierr_free_irq:
67762306a36Sopenharmony_ci	board->cleanup(par);
67862306a36Sopenharmony_cierr_csum_table:
67962306a36Sopenharmony_ci	vfree(par->csum_table);
68062306a36Sopenharmony_cierr_vfree:
68162306a36Sopenharmony_ci	vfree(videomemory);
68262306a36Sopenharmony_cierr_fb_rel:
68362306a36Sopenharmony_ci	framebuffer_release(info);
68462306a36Sopenharmony_cierr:
68562306a36Sopenharmony_ci	module_put(board->owner);
68662306a36Sopenharmony_ci	return retval;
68762306a36Sopenharmony_ci}
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_cistatic void metronomefb_remove(struct platform_device *dev)
69062306a36Sopenharmony_ci{
69162306a36Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(dev);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	if (info) {
69462306a36Sopenharmony_ci		struct metronomefb_par *par = info->par;
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci		unregister_framebuffer(info);
69762306a36Sopenharmony_ci		fb_deferred_io_cleanup(info);
69862306a36Sopenharmony_ci		fb_dealloc_cmap(&info->cmap);
69962306a36Sopenharmony_ci		par->board->cleanup(par);
70062306a36Sopenharmony_ci		vfree(par->csum_table);
70162306a36Sopenharmony_ci		vfree(info->screen_buffer);
70262306a36Sopenharmony_ci		module_put(par->board->owner);
70362306a36Sopenharmony_ci		dev_dbg(&dev->dev, "calling release\n");
70462306a36Sopenharmony_ci		framebuffer_release(info);
70562306a36Sopenharmony_ci	}
70662306a36Sopenharmony_ci}
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_cistatic struct platform_driver metronomefb_driver = {
70962306a36Sopenharmony_ci	.probe	= metronomefb_probe,
71062306a36Sopenharmony_ci	.remove_new = metronomefb_remove,
71162306a36Sopenharmony_ci	.driver	= {
71262306a36Sopenharmony_ci		.name	= "metronomefb",
71362306a36Sopenharmony_ci	},
71462306a36Sopenharmony_ci};
71562306a36Sopenharmony_cimodule_platform_driver(metronomefb_driver);
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_cimodule_param(user_wfm_size, uint, 0);
71862306a36Sopenharmony_ciMODULE_PARM_DESC(user_wfm_size, "Set custom waveform size");
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ciMODULE_DESCRIPTION("fbdev driver for Metronome controller");
72162306a36Sopenharmony_ciMODULE_AUTHOR("Jaya Kumar");
72262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ciMODULE_FIRMWARE("metronome.wbf");
725