18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * linux/drivers/video/metronomefb.c -- FB driver for Metronome controller
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2008, Jaya Kumar
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
78c2ecf20Sopenharmony_ci * License. See the file COPYING in the main directory of this archive for
88c2ecf20Sopenharmony_ci * more details.
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * This work was made possible by help and equipment support from E-Ink
138c2ecf20Sopenharmony_ci * Corporation. https://www.eink.com/
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * This driver is written to be used with the Metronome display controller.
168c2ecf20Sopenharmony_ci * It is intended to be architecture independent. A board specific driver
178c2ecf20Sopenharmony_ci * must be used to perform all the physical IO interactions. An example
188c2ecf20Sopenharmony_ci * is provided as am200epd.c
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci */
218c2ecf20Sopenharmony_ci#include <linux/module.h>
228c2ecf20Sopenharmony_ci#include <linux/kernel.h>
238c2ecf20Sopenharmony_ci#include <linux/errno.h>
248c2ecf20Sopenharmony_ci#include <linux/string.h>
258c2ecf20Sopenharmony_ci#include <linux/mm.h>
268c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
278c2ecf20Sopenharmony_ci#include <linux/delay.h>
288c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
298c2ecf20Sopenharmony_ci#include <linux/fb.h>
308c2ecf20Sopenharmony_ci#include <linux/init.h>
318c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
328c2ecf20Sopenharmony_ci#include <linux/list.h>
338c2ecf20Sopenharmony_ci#include <linux/firmware.h>
348c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
358c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
368c2ecf20Sopenharmony_ci#include <linux/irq.h>
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#include <video/metronomefb.h>
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#include <asm/unaligned.h>
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/* Display specific information */
438c2ecf20Sopenharmony_ci#define DPY_W 832
448c2ecf20Sopenharmony_ci#define DPY_H 622
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic int user_wfm_size;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/* frame differs from image. frame includes non-visible pixels */
498c2ecf20Sopenharmony_cistruct epd_frame {
508c2ecf20Sopenharmony_ci	int fw; /* frame width */
518c2ecf20Sopenharmony_ci	int fh; /* frame height */
528c2ecf20Sopenharmony_ci	u16 config[4];
538c2ecf20Sopenharmony_ci	int wfm_size;
548c2ecf20Sopenharmony_ci};
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic struct epd_frame epd_frame_table[] = {
578c2ecf20Sopenharmony_ci	{
588c2ecf20Sopenharmony_ci		.fw = 832,
598c2ecf20Sopenharmony_ci		.fh = 622,
608c2ecf20Sopenharmony_ci		.config = {
618c2ecf20Sopenharmony_ci			15 /* sdlew */
628c2ecf20Sopenharmony_ci			| 2 << 8 /* sdosz */
638c2ecf20Sopenharmony_ci			| 0 << 11 /* sdor */
648c2ecf20Sopenharmony_ci			| 0 << 12 /* sdces */
658c2ecf20Sopenharmony_ci			| 0 << 15, /* sdcer */
668c2ecf20Sopenharmony_ci			42 /* gdspl */
678c2ecf20Sopenharmony_ci			| 1 << 8 /* gdr1 */
688c2ecf20Sopenharmony_ci			| 1 << 9 /* sdshr */
698c2ecf20Sopenharmony_ci			| 0 << 15, /* gdspp */
708c2ecf20Sopenharmony_ci			18 /* gdspw */
718c2ecf20Sopenharmony_ci			| 0 << 15, /* dispc */
728c2ecf20Sopenharmony_ci			599 /* vdlc */
738c2ecf20Sopenharmony_ci			| 0 << 11 /* dsi */
748c2ecf20Sopenharmony_ci			| 0 << 12, /* dsic */
758c2ecf20Sopenharmony_ci		},
768c2ecf20Sopenharmony_ci		.wfm_size = 47001,
778c2ecf20Sopenharmony_ci	},
788c2ecf20Sopenharmony_ci	{
798c2ecf20Sopenharmony_ci		.fw = 1088,
808c2ecf20Sopenharmony_ci		.fh = 791,
818c2ecf20Sopenharmony_ci		.config = {
828c2ecf20Sopenharmony_ci			0x0104,
838c2ecf20Sopenharmony_ci			0x031f,
848c2ecf20Sopenharmony_ci			0x0088,
858c2ecf20Sopenharmony_ci			0x02ff,
868c2ecf20Sopenharmony_ci		},
878c2ecf20Sopenharmony_ci		.wfm_size = 46770,
888c2ecf20Sopenharmony_ci	},
898c2ecf20Sopenharmony_ci	{
908c2ecf20Sopenharmony_ci		.fw = 1200,
918c2ecf20Sopenharmony_ci		.fh = 842,
928c2ecf20Sopenharmony_ci		.config = {
938c2ecf20Sopenharmony_ci			0x0101,
948c2ecf20Sopenharmony_ci			0x030e,
958c2ecf20Sopenharmony_ci			0x0012,
968c2ecf20Sopenharmony_ci			0x0280,
978c2ecf20Sopenharmony_ci		},
988c2ecf20Sopenharmony_ci		.wfm_size = 46770,
998c2ecf20Sopenharmony_ci	},
1008c2ecf20Sopenharmony_ci};
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic struct fb_fix_screeninfo metronomefb_fix = {
1038c2ecf20Sopenharmony_ci	.id =		"metronomefb",
1048c2ecf20Sopenharmony_ci	.type =		FB_TYPE_PACKED_PIXELS,
1058c2ecf20Sopenharmony_ci	.visual =	FB_VISUAL_STATIC_PSEUDOCOLOR,
1068c2ecf20Sopenharmony_ci	.xpanstep =	0,
1078c2ecf20Sopenharmony_ci	.ypanstep =	0,
1088c2ecf20Sopenharmony_ci	.ywrapstep =	0,
1098c2ecf20Sopenharmony_ci	.line_length =	DPY_W,
1108c2ecf20Sopenharmony_ci	.accel =	FB_ACCEL_NONE,
1118c2ecf20Sopenharmony_ci};
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic struct fb_var_screeninfo metronomefb_var = {
1148c2ecf20Sopenharmony_ci	.xres		= DPY_W,
1158c2ecf20Sopenharmony_ci	.yres		= DPY_H,
1168c2ecf20Sopenharmony_ci	.xres_virtual	= DPY_W,
1178c2ecf20Sopenharmony_ci	.yres_virtual	= DPY_H,
1188c2ecf20Sopenharmony_ci	.bits_per_pixel	= 8,
1198c2ecf20Sopenharmony_ci	.grayscale	= 1,
1208c2ecf20Sopenharmony_ci	.nonstd		= 1,
1218c2ecf20Sopenharmony_ci	.red =		{ 4, 3, 0 },
1228c2ecf20Sopenharmony_ci	.green =	{ 0, 0, 0 },
1238c2ecf20Sopenharmony_ci	.blue =		{ 0, 0, 0 },
1248c2ecf20Sopenharmony_ci	.transp =	{ 0, 0, 0 },
1258c2ecf20Sopenharmony_ci};
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci/* the waveform structure that is coming from userspace firmware */
1288c2ecf20Sopenharmony_cistruct waveform_hdr {
1298c2ecf20Sopenharmony_ci	u8 stuff[32];
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	u8 wmta[3];
1328c2ecf20Sopenharmony_ci	u8 fvsn;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	u8 luts;
1358c2ecf20Sopenharmony_ci	u8 mc;
1368c2ecf20Sopenharmony_ci	u8 trc;
1378c2ecf20Sopenharmony_ci	u8 stuff3;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	u8 endb;
1408c2ecf20Sopenharmony_ci	u8 swtb;
1418c2ecf20Sopenharmony_ci	u8 stuff2a[2];
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	u8 stuff2b[3];
1448c2ecf20Sopenharmony_ci	u8 wfm_cs;
1458c2ecf20Sopenharmony_ci} __attribute__ ((packed));
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci/* main metronomefb functions */
1488c2ecf20Sopenharmony_cistatic u8 calc_cksum(int start, int end, u8 *mem)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	u8 tmp = 0;
1518c2ecf20Sopenharmony_ci	int i;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	for (i = start; i < end; i++)
1548c2ecf20Sopenharmony_ci		tmp += mem[i];
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	return tmp;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic u16 calc_img_cksum(u16 *start, int length)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	u16 tmp = 0;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	while (length--)
1648c2ecf20Sopenharmony_ci		tmp += *start++;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	return tmp;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci/* here we decode the incoming waveform file and populate metromem */
1708c2ecf20Sopenharmony_cistatic int load_waveform(u8 *mem, size_t size, int m, int t,
1718c2ecf20Sopenharmony_ci			 struct metronomefb_par *par)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	int tta;
1748c2ecf20Sopenharmony_ci	int wmta;
1758c2ecf20Sopenharmony_ci	int trn = 0;
1768c2ecf20Sopenharmony_ci	int i;
1778c2ecf20Sopenharmony_ci	unsigned char v;
1788c2ecf20Sopenharmony_ci	u8 cksum;
1798c2ecf20Sopenharmony_ci	int cksum_idx;
1808c2ecf20Sopenharmony_ci	int wfm_idx, owfm_idx;
1818c2ecf20Sopenharmony_ci	int mem_idx = 0;
1828c2ecf20Sopenharmony_ci	struct waveform_hdr *wfm_hdr;
1838c2ecf20Sopenharmony_ci	u8 *metromem = par->metromem_wfm;
1848c2ecf20Sopenharmony_ci	struct device *dev = par->info->dev;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	if (user_wfm_size)
1878c2ecf20Sopenharmony_ci		epd_frame_table[par->dt].wfm_size = user_wfm_size;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (size != epd_frame_table[par->dt].wfm_size) {
1908c2ecf20Sopenharmony_ci		dev_err(dev, "Error: unexpected size %zd != %d\n", size,
1918c2ecf20Sopenharmony_ci					epd_frame_table[par->dt].wfm_size);
1928c2ecf20Sopenharmony_ci		return -EINVAL;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	wfm_hdr = (struct waveform_hdr *) mem;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	if (wfm_hdr->fvsn != 1) {
1988c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn);
1998c2ecf20Sopenharmony_ci		return -EINVAL;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci	if (wfm_hdr->luts != 0) {
2028c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts);
2038c2ecf20Sopenharmony_ci		return -EINVAL;
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci	cksum = calc_cksum(32, 47, mem);
2068c2ecf20Sopenharmony_ci	if (cksum != wfm_hdr->wfm_cs) {
2078c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad cksum %x != %x\n", cksum,
2088c2ecf20Sopenharmony_ci					wfm_hdr->wfm_cs);
2098c2ecf20Sopenharmony_ci		return -EINVAL;
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci	wfm_hdr->mc += 1;
2128c2ecf20Sopenharmony_ci	wfm_hdr->trc += 1;
2138c2ecf20Sopenharmony_ci	for (i = 0; i < 5; i++) {
2148c2ecf20Sopenharmony_ci		if (*(wfm_hdr->stuff2a + i) != 0) {
2158c2ecf20Sopenharmony_ci			dev_err(dev, "Error: unexpected value in padding\n");
2168c2ecf20Sopenharmony_ci			return -EINVAL;
2178c2ecf20Sopenharmony_ci		}
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	/* calculating trn. trn is something used to index into
2218c2ecf20Sopenharmony_ci	the waveform. presumably selecting the right one for the
2228c2ecf20Sopenharmony_ci	desired temperature. it works out the offset of the first
2238c2ecf20Sopenharmony_ci	v that exceeds the specified temperature */
2248c2ecf20Sopenharmony_ci	if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size)
2258c2ecf20Sopenharmony_ci		return -EINVAL;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) {
2288c2ecf20Sopenharmony_ci		if (mem[i] > t) {
2298c2ecf20Sopenharmony_ci			trn = i - sizeof(*wfm_hdr) - 1;
2308c2ecf20Sopenharmony_ci			break;
2318c2ecf20Sopenharmony_ci		}
2328c2ecf20Sopenharmony_ci	}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	/* check temperature range table checksum */
2358c2ecf20Sopenharmony_ci	cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1;
2368c2ecf20Sopenharmony_ci	if (cksum_idx >= size)
2378c2ecf20Sopenharmony_ci		return -EINVAL;
2388c2ecf20Sopenharmony_ci	cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem);
2398c2ecf20Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
2408c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad temperature range table cksum"
2418c2ecf20Sopenharmony_ci				" %x != %x\n", cksum, mem[cksum_idx]);
2428c2ecf20Sopenharmony_ci		return -EINVAL;
2438c2ecf20Sopenharmony_ci	}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	/* check waveform mode table address checksum */
2468c2ecf20Sopenharmony_ci	wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF;
2478c2ecf20Sopenharmony_ci	cksum_idx = wmta + m*4 + 3;
2488c2ecf20Sopenharmony_ci	if (cksum_idx >= size)
2498c2ecf20Sopenharmony_ci		return -EINVAL;
2508c2ecf20Sopenharmony_ci	cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
2518c2ecf20Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
2528c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad mode table address cksum"
2538c2ecf20Sopenharmony_ci				" %x != %x\n", cksum, mem[cksum_idx]);
2548c2ecf20Sopenharmony_ci		return -EINVAL;
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	/* check waveform temperature table address checksum */
2588c2ecf20Sopenharmony_ci	tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF;
2598c2ecf20Sopenharmony_ci	cksum_idx = tta + trn*4 + 3;
2608c2ecf20Sopenharmony_ci	if (cksum_idx >= size)
2618c2ecf20Sopenharmony_ci		return -EINVAL;
2628c2ecf20Sopenharmony_ci	cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
2638c2ecf20Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
2648c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad temperature table address cksum"
2658c2ecf20Sopenharmony_ci			" %x != %x\n", cksum, mem[cksum_idx]);
2668c2ecf20Sopenharmony_ci		return -EINVAL;
2678c2ecf20Sopenharmony_ci	}
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	/* here we do the real work of putting the waveform into the
2708c2ecf20Sopenharmony_ci	metromem buffer. this does runlength decoding of the waveform */
2718c2ecf20Sopenharmony_ci	wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF;
2728c2ecf20Sopenharmony_ci	owfm_idx = wfm_idx;
2738c2ecf20Sopenharmony_ci	if (wfm_idx >= size)
2748c2ecf20Sopenharmony_ci		return -EINVAL;
2758c2ecf20Sopenharmony_ci	while (wfm_idx < size) {
2768c2ecf20Sopenharmony_ci		unsigned char rl;
2778c2ecf20Sopenharmony_ci		v = mem[wfm_idx++];
2788c2ecf20Sopenharmony_ci		if (v == wfm_hdr->swtb) {
2798c2ecf20Sopenharmony_ci			while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) &&
2808c2ecf20Sopenharmony_ci				wfm_idx < size)
2818c2ecf20Sopenharmony_ci				metromem[mem_idx++] = v;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci			continue;
2848c2ecf20Sopenharmony_ci		}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci		if (v == wfm_hdr->endb)
2878c2ecf20Sopenharmony_ci			break;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci		rl = mem[wfm_idx++];
2908c2ecf20Sopenharmony_ci		for (i = 0; i <= rl; i++)
2918c2ecf20Sopenharmony_ci			metromem[mem_idx++] = v;
2928c2ecf20Sopenharmony_ci	}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	cksum_idx = wfm_idx;
2958c2ecf20Sopenharmony_ci	if (cksum_idx >= size)
2968c2ecf20Sopenharmony_ci		return -EINVAL;
2978c2ecf20Sopenharmony_ci	cksum = calc_cksum(owfm_idx, cksum_idx, mem);
2988c2ecf20Sopenharmony_ci	if (cksum != mem[cksum_idx]) {
2998c2ecf20Sopenharmony_ci		dev_err(dev, "Error: bad waveform data cksum"
3008c2ecf20Sopenharmony_ci				" %x != %x\n", cksum, mem[cksum_idx]);
3018c2ecf20Sopenharmony_ci		return -EINVAL;
3028c2ecf20Sopenharmony_ci	}
3038c2ecf20Sopenharmony_ci	par->frame_count = (mem_idx/64);
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	return 0;
3068c2ecf20Sopenharmony_ci}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_cistatic int metronome_display_cmd(struct metronomefb_par *par)
3098c2ecf20Sopenharmony_ci{
3108c2ecf20Sopenharmony_ci	int i;
3118c2ecf20Sopenharmony_ci	u16 cs;
3128c2ecf20Sopenharmony_ci	u16 opcode;
3138c2ecf20Sopenharmony_ci	static u8 borderval;
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	/* setup display command
3168c2ecf20Sopenharmony_ci	we can't immediately set the opcode since the controller
3178c2ecf20Sopenharmony_ci	will try parse the command before we've set it all up
3188c2ecf20Sopenharmony_ci	so we just set cs here and set the opcode at the end */
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	if (par->metromem_cmd->opcode == 0xCC40)
3218c2ecf20Sopenharmony_ci		opcode = cs = 0xCC41;
3228c2ecf20Sopenharmony_ci	else
3238c2ecf20Sopenharmony_ci		opcode = cs = 0xCC40;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	/* set the args ( 2 bytes ) for display */
3268c2ecf20Sopenharmony_ci	i = 0;
3278c2ecf20Sopenharmony_ci	par->metromem_cmd->args[i] = 	1 << 3 /* border update */
3288c2ecf20Sopenharmony_ci					| ((borderval++ % 4) & 0x0F) << 4
3298c2ecf20Sopenharmony_ci					| (par->frame_count - 1) << 8;
3308c2ecf20Sopenharmony_ci	cs += par->metromem_cmd->args[i++];
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	/* the rest are 0 */
3338c2ecf20Sopenharmony_ci	memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	par->metromem_cmd->csum = cs;
3368c2ecf20Sopenharmony_ci	par->metromem_cmd->opcode = opcode; /* display cmd */
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	return par->board->met_wait_event_intr(par);
3398c2ecf20Sopenharmony_ci}
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_cistatic int metronome_powerup_cmd(struct metronomefb_par *par)
3428c2ecf20Sopenharmony_ci{
3438c2ecf20Sopenharmony_ci	int i;
3448c2ecf20Sopenharmony_ci	u16 cs;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	/* setup power up command */
3478c2ecf20Sopenharmony_ci	par->metromem_cmd->opcode = 0x1234; /* pwr up pseudo cmd */
3488c2ecf20Sopenharmony_ci	cs = par->metromem_cmd->opcode;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	/* set pwr1,2,3 to 1024 */
3518c2ecf20Sopenharmony_ci	for (i = 0; i < 3; i++) {
3528c2ecf20Sopenharmony_ci		par->metromem_cmd->args[i] = 1024;
3538c2ecf20Sopenharmony_ci		cs += par->metromem_cmd->args[i];
3548c2ecf20Sopenharmony_ci	}
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	/* the rest are 0 */
3578c2ecf20Sopenharmony_ci	memset(&par->metromem_cmd->args[i], 0,
3588c2ecf20Sopenharmony_ci	       (ARRAY_SIZE(par->metromem_cmd->args) - i) * 2);
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	par->metromem_cmd->csum = cs;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	msleep(1);
3638c2ecf20Sopenharmony_ci	par->board->set_rst(par, 1);
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	msleep(1);
3668c2ecf20Sopenharmony_ci	par->board->set_stdby(par, 1);
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	return par->board->met_wait_event(par);
3698c2ecf20Sopenharmony_ci}
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_cistatic int metronome_config_cmd(struct metronomefb_par *par)
3728c2ecf20Sopenharmony_ci{
3738c2ecf20Sopenharmony_ci	/* setup config command
3748c2ecf20Sopenharmony_ci	we can't immediately set the opcode since the controller
3758c2ecf20Sopenharmony_ci	will try parse the command before we've set it all up */
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config,
3788c2ecf20Sopenharmony_ci		sizeof(epd_frame_table[par->dt].config));
3798c2ecf20Sopenharmony_ci	/* the rest are 0 */
3808c2ecf20Sopenharmony_ci	memset(&par->metromem_cmd->args[4], 0,
3818c2ecf20Sopenharmony_ci	       (ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2);
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	par->metromem_cmd->csum = 0xCC10;
3848c2ecf20Sopenharmony_ci	par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4);
3858c2ecf20Sopenharmony_ci	par->metromem_cmd->opcode = 0xCC10; /* config cmd */
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	return par->board->met_wait_event(par);
3888c2ecf20Sopenharmony_ci}
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_cistatic int metronome_init_cmd(struct metronomefb_par *par)
3918c2ecf20Sopenharmony_ci{
3928c2ecf20Sopenharmony_ci	int i;
3938c2ecf20Sopenharmony_ci	u16 cs;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	/* setup init command
3968c2ecf20Sopenharmony_ci	we can't immediately set the opcode since the controller
3978c2ecf20Sopenharmony_ci	will try parse the command before we've set it all up
3988c2ecf20Sopenharmony_ci	so we just set cs here and set the opcode at the end */
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	cs = 0xCC20;
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	/* set the args ( 2 bytes ) for init */
4038c2ecf20Sopenharmony_ci	i = 0;
4048c2ecf20Sopenharmony_ci	par->metromem_cmd->args[i] = 0;
4058c2ecf20Sopenharmony_ci	cs += par->metromem_cmd->args[i++];
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	/* the rest are 0 */
4088c2ecf20Sopenharmony_ci	memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci	par->metromem_cmd->csum = cs;
4118c2ecf20Sopenharmony_ci	par->metromem_cmd->opcode = 0xCC20; /* init cmd */
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	return par->board->met_wait_event(par);
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic int metronome_init_regs(struct metronomefb_par *par)
4178c2ecf20Sopenharmony_ci{
4188c2ecf20Sopenharmony_ci	int res;
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	res = par->board->setup_io(par);
4218c2ecf20Sopenharmony_ci	if (res)
4228c2ecf20Sopenharmony_ci		return res;
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	res = metronome_powerup_cmd(par);
4258c2ecf20Sopenharmony_ci	if (res)
4268c2ecf20Sopenharmony_ci		return res;
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	res = metronome_config_cmd(par);
4298c2ecf20Sopenharmony_ci	if (res)
4308c2ecf20Sopenharmony_ci		return res;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	res = metronome_init_cmd(par);
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	return res;
4358c2ecf20Sopenharmony_ci}
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_cistatic void metronomefb_dpy_update(struct metronomefb_par *par)
4388c2ecf20Sopenharmony_ci{
4398c2ecf20Sopenharmony_ci	int fbsize;
4408c2ecf20Sopenharmony_ci	u16 cksum;
4418c2ecf20Sopenharmony_ci	unsigned char *buf = (unsigned char __force *)par->info->screen_base;
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_ci	fbsize = par->info->fix.smem_len;
4448c2ecf20Sopenharmony_ci	/* copy from vm to metromem */
4458c2ecf20Sopenharmony_ci	memcpy(par->metromem_img, buf, fbsize);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2);
4488c2ecf20Sopenharmony_ci	*((u16 *)(par->metromem_img) + fbsize/2) = cksum;
4498c2ecf20Sopenharmony_ci	metronome_display_cmd(par);
4508c2ecf20Sopenharmony_ci}
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_cistatic u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)
4538c2ecf20Sopenharmony_ci{
4548c2ecf20Sopenharmony_ci	int i;
4558c2ecf20Sopenharmony_ci	u16 csum = 0;
4568c2ecf20Sopenharmony_ci	u16 *buf = (u16 __force *)(par->info->screen_base + index);
4578c2ecf20Sopenharmony_ci	u16 *img = (u16 *)(par->metromem_img + index);
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	/* swizzle from vm to metromem and recalc cksum at the same time*/
4608c2ecf20Sopenharmony_ci	for (i = 0; i < PAGE_SIZE/2; i++) {
4618c2ecf20Sopenharmony_ci		*(img + i) = (buf[i] << 5) & 0xE0E0;
4628c2ecf20Sopenharmony_ci		csum += *(img + i);
4638c2ecf20Sopenharmony_ci	}
4648c2ecf20Sopenharmony_ci	return csum;
4658c2ecf20Sopenharmony_ci}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci/* this is called back from the deferred io workqueue */
4688c2ecf20Sopenharmony_cistatic void metronomefb_dpy_deferred_io(struct fb_info *info,
4698c2ecf20Sopenharmony_ci				struct list_head *pagelist)
4708c2ecf20Sopenharmony_ci{
4718c2ecf20Sopenharmony_ci	u16 cksum;
4728c2ecf20Sopenharmony_ci	struct page *cur;
4738c2ecf20Sopenharmony_ci	struct fb_deferred_io *fbdefio = info->fbdefio;
4748c2ecf20Sopenharmony_ci	struct metronomefb_par *par = info->par;
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	/* walk the written page list and swizzle the data */
4778c2ecf20Sopenharmony_ci	list_for_each_entry(cur, &fbdefio->pagelist, lru) {
4788c2ecf20Sopenharmony_ci		cksum = metronomefb_dpy_update_page(par,
4798c2ecf20Sopenharmony_ci					(cur->index << PAGE_SHIFT));
4808c2ecf20Sopenharmony_ci		par->metromem_img_csum -= par->csum_table[cur->index];
4818c2ecf20Sopenharmony_ci		par->csum_table[cur->index] = cksum;
4828c2ecf20Sopenharmony_ci		par->metromem_img_csum += cksum;
4838c2ecf20Sopenharmony_ci	}
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	metronome_display_cmd(par);
4868c2ecf20Sopenharmony_ci}
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_cistatic void metronomefb_fillrect(struct fb_info *info,
4898c2ecf20Sopenharmony_ci				   const struct fb_fillrect *rect)
4908c2ecf20Sopenharmony_ci{
4918c2ecf20Sopenharmony_ci	struct metronomefb_par *par = info->par;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci	sys_fillrect(info, rect);
4948c2ecf20Sopenharmony_ci	metronomefb_dpy_update(par);
4958c2ecf20Sopenharmony_ci}
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_cistatic void metronomefb_copyarea(struct fb_info *info,
4988c2ecf20Sopenharmony_ci				   const struct fb_copyarea *area)
4998c2ecf20Sopenharmony_ci{
5008c2ecf20Sopenharmony_ci	struct metronomefb_par *par = info->par;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci	sys_copyarea(info, area);
5038c2ecf20Sopenharmony_ci	metronomefb_dpy_update(par);
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic void metronomefb_imageblit(struct fb_info *info,
5078c2ecf20Sopenharmony_ci				const struct fb_image *image)
5088c2ecf20Sopenharmony_ci{
5098c2ecf20Sopenharmony_ci	struct metronomefb_par *par = info->par;
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci	sys_imageblit(info, image);
5128c2ecf20Sopenharmony_ci	metronomefb_dpy_update(par);
5138c2ecf20Sopenharmony_ci}
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci/*
5168c2ecf20Sopenharmony_ci * this is the slow path from userspace. they can seek and write to
5178c2ecf20Sopenharmony_ci * the fb. it is based on fb_sys_write
5188c2ecf20Sopenharmony_ci */
5198c2ecf20Sopenharmony_cistatic ssize_t metronomefb_write(struct fb_info *info, const char __user *buf,
5208c2ecf20Sopenharmony_ci				size_t count, loff_t *ppos)
5218c2ecf20Sopenharmony_ci{
5228c2ecf20Sopenharmony_ci	struct metronomefb_par *par = info->par;
5238c2ecf20Sopenharmony_ci	unsigned long p = *ppos;
5248c2ecf20Sopenharmony_ci	void *dst;
5258c2ecf20Sopenharmony_ci	int err = 0;
5268c2ecf20Sopenharmony_ci	unsigned long total_size;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	if (info->state != FBINFO_STATE_RUNNING)
5298c2ecf20Sopenharmony_ci		return -EPERM;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	total_size = info->fix.smem_len;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	if (p > total_size)
5348c2ecf20Sopenharmony_ci		return -EFBIG;
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci	if (count > total_size) {
5378c2ecf20Sopenharmony_ci		err = -EFBIG;
5388c2ecf20Sopenharmony_ci		count = total_size;
5398c2ecf20Sopenharmony_ci	}
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	if (count + p > total_size) {
5428c2ecf20Sopenharmony_ci		if (!err)
5438c2ecf20Sopenharmony_ci			err = -ENOSPC;
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci		count = total_size - p;
5468c2ecf20Sopenharmony_ci	}
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_ci	dst = (void __force *)(info->screen_base + p);
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	if (copy_from_user(dst, buf, count))
5518c2ecf20Sopenharmony_ci		err = -EFAULT;
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	if  (!err)
5548c2ecf20Sopenharmony_ci		*ppos += count;
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	metronomefb_dpy_update(par);
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	return (err) ? err : count;
5598c2ecf20Sopenharmony_ci}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_cistatic const struct fb_ops metronomefb_ops = {
5628c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
5638c2ecf20Sopenharmony_ci	.fb_write	= metronomefb_write,
5648c2ecf20Sopenharmony_ci	.fb_fillrect	= metronomefb_fillrect,
5658c2ecf20Sopenharmony_ci	.fb_copyarea	= metronomefb_copyarea,
5668c2ecf20Sopenharmony_ci	.fb_imageblit	= metronomefb_imageblit,
5678c2ecf20Sopenharmony_ci};
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_cistatic struct fb_deferred_io metronomefb_defio = {
5708c2ecf20Sopenharmony_ci	.delay		= HZ,
5718c2ecf20Sopenharmony_ci	.deferred_io	= metronomefb_dpy_deferred_io,
5728c2ecf20Sopenharmony_ci};
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_cistatic int metronomefb_probe(struct platform_device *dev)
5758c2ecf20Sopenharmony_ci{
5768c2ecf20Sopenharmony_ci	struct fb_info *info;
5778c2ecf20Sopenharmony_ci	struct metronome_board *board;
5788c2ecf20Sopenharmony_ci	int retval = -ENOMEM;
5798c2ecf20Sopenharmony_ci	int videomemorysize;
5808c2ecf20Sopenharmony_ci	unsigned char *videomemory;
5818c2ecf20Sopenharmony_ci	struct metronomefb_par *par;
5828c2ecf20Sopenharmony_ci	const struct firmware *fw_entry;
5838c2ecf20Sopenharmony_ci	int i;
5848c2ecf20Sopenharmony_ci	int panel_type;
5858c2ecf20Sopenharmony_ci	int fw, fh;
5868c2ecf20Sopenharmony_ci	int epd_dt_index;
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci	/* pick up board specific routines */
5898c2ecf20Sopenharmony_ci	board = dev->dev.platform_data;
5908c2ecf20Sopenharmony_ci	if (!board)
5918c2ecf20Sopenharmony_ci		return -EINVAL;
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci	/* try to count device specific driver, if can't, platform recalls */
5948c2ecf20Sopenharmony_ci	if (!try_module_get(board->owner))
5958c2ecf20Sopenharmony_ci		return -ENODEV;
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev);
5988c2ecf20Sopenharmony_ci	if (!info)
5998c2ecf20Sopenharmony_ci		goto err;
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	/* we have two blocks of memory.
6028c2ecf20Sopenharmony_ci	info->screen_base which is vm, and is the fb used by apps.
6038c2ecf20Sopenharmony_ci	par->metromem which is physically contiguous memory and
6048c2ecf20Sopenharmony_ci	contains the display controller commands, waveform,
6058c2ecf20Sopenharmony_ci	processed image data and padding. this is the data pulled
6068c2ecf20Sopenharmony_ci	by the device's LCD controller and pushed to Metronome.
6078c2ecf20Sopenharmony_ci	the metromem memory is allocated by the board driver and
6088c2ecf20Sopenharmony_ci	is provided to us */
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci	panel_type = board->get_panel_type();
6118c2ecf20Sopenharmony_ci	switch (panel_type) {
6128c2ecf20Sopenharmony_ci	case 6:
6138c2ecf20Sopenharmony_ci		epd_dt_index = 0;
6148c2ecf20Sopenharmony_ci		break;
6158c2ecf20Sopenharmony_ci	case 8:
6168c2ecf20Sopenharmony_ci		epd_dt_index = 1;
6178c2ecf20Sopenharmony_ci		break;
6188c2ecf20Sopenharmony_ci	case 97:
6198c2ecf20Sopenharmony_ci		epd_dt_index = 2;
6208c2ecf20Sopenharmony_ci		break;
6218c2ecf20Sopenharmony_ci	default:
6228c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n");
6238c2ecf20Sopenharmony_ci		epd_dt_index = 0;
6248c2ecf20Sopenharmony_ci		break;
6258c2ecf20Sopenharmony_ci	}
6268c2ecf20Sopenharmony_ci
6278c2ecf20Sopenharmony_ci	fw = epd_frame_table[epd_dt_index].fw;
6288c2ecf20Sopenharmony_ci	fh = epd_frame_table[epd_dt_index].fh;
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_ci	/* we need to add a spare page because our csum caching scheme walks
6318c2ecf20Sopenharmony_ci	 * to the end of the page */
6328c2ecf20Sopenharmony_ci	videomemorysize = PAGE_SIZE + (fw * fh);
6338c2ecf20Sopenharmony_ci	videomemory = vzalloc(videomemorysize);
6348c2ecf20Sopenharmony_ci	if (!videomemory)
6358c2ecf20Sopenharmony_ci		goto err_fb_rel;
6368c2ecf20Sopenharmony_ci
6378c2ecf20Sopenharmony_ci	info->screen_base = (char __force __iomem *)videomemory;
6388c2ecf20Sopenharmony_ci	info->fbops = &metronomefb_ops;
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci	metronomefb_fix.line_length = fw;
6418c2ecf20Sopenharmony_ci	metronomefb_var.xres = fw;
6428c2ecf20Sopenharmony_ci	metronomefb_var.yres = fh;
6438c2ecf20Sopenharmony_ci	metronomefb_var.xres_virtual = fw;
6448c2ecf20Sopenharmony_ci	metronomefb_var.yres_virtual = fh;
6458c2ecf20Sopenharmony_ci	info->var = metronomefb_var;
6468c2ecf20Sopenharmony_ci	info->fix = metronomefb_fix;
6478c2ecf20Sopenharmony_ci	info->fix.smem_len = videomemorysize;
6488c2ecf20Sopenharmony_ci	par = info->par;
6498c2ecf20Sopenharmony_ci	par->info = info;
6508c2ecf20Sopenharmony_ci	par->board = board;
6518c2ecf20Sopenharmony_ci	par->dt = epd_dt_index;
6528c2ecf20Sopenharmony_ci	init_waitqueue_head(&par->waitq);
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	/* this table caches per page csum values. */
6558c2ecf20Sopenharmony_ci	par->csum_table = vmalloc(videomemorysize/PAGE_SIZE);
6568c2ecf20Sopenharmony_ci	if (!par->csum_table)
6578c2ecf20Sopenharmony_ci		goto err_vfree;
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	/* the physical framebuffer that we use is setup by
6608c2ecf20Sopenharmony_ci	 * the platform device driver. It will provide us
6618c2ecf20Sopenharmony_ci	 * with cmd, wfm and image memory in a contiguous area. */
6628c2ecf20Sopenharmony_ci	retval = board->setup_fb(par);
6638c2ecf20Sopenharmony_ci	if (retval) {
6648c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Failed to setup fb\n");
6658c2ecf20Sopenharmony_ci		goto err_csum_table;
6668c2ecf20Sopenharmony_ci	}
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_ci	/* after this point we should have a framebuffer */
6698c2ecf20Sopenharmony_ci	if ((!par->metromem_wfm) ||  (!par->metromem_img) ||
6708c2ecf20Sopenharmony_ci		(!par->metromem_dma)) {
6718c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "fb access failure\n");
6728c2ecf20Sopenharmony_ci		retval = -EINVAL;
6738c2ecf20Sopenharmony_ci		goto err_csum_table;
6748c2ecf20Sopenharmony_ci	}
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci	info->fix.smem_start = par->metromem_dma;
6778c2ecf20Sopenharmony_ci
6788c2ecf20Sopenharmony_ci	/* load the waveform in. assume mode 3, temp 31 for now
6798c2ecf20Sopenharmony_ci		a) request the waveform file from userspace
6808c2ecf20Sopenharmony_ci		b) process waveform and decode into metromem */
6818c2ecf20Sopenharmony_ci	retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev);
6828c2ecf20Sopenharmony_ci	if (retval < 0) {
6838c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Failed to get waveform\n");
6848c2ecf20Sopenharmony_ci		goto err_csum_table;
6858c2ecf20Sopenharmony_ci	}
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_ci	retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31,
6888c2ecf20Sopenharmony_ci				par);
6898c2ecf20Sopenharmony_ci	release_firmware(fw_entry);
6908c2ecf20Sopenharmony_ci	if (retval < 0) {
6918c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Failed processing waveform\n");
6928c2ecf20Sopenharmony_ci		goto err_csum_table;
6938c2ecf20Sopenharmony_ci	}
6948c2ecf20Sopenharmony_ci
6958c2ecf20Sopenharmony_ci	retval = board->setup_irq(info);
6968c2ecf20Sopenharmony_ci	if (retval)
6978c2ecf20Sopenharmony_ci		goto err_csum_table;
6988c2ecf20Sopenharmony_ci
6998c2ecf20Sopenharmony_ci	retval = metronome_init_regs(par);
7008c2ecf20Sopenharmony_ci	if (retval < 0)
7018c2ecf20Sopenharmony_ci		goto err_free_irq;
7028c2ecf20Sopenharmony_ci
7038c2ecf20Sopenharmony_ci	info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_ci	info->fbdefio = &metronomefb_defio;
7068c2ecf20Sopenharmony_ci	fb_deferred_io_init(info);
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_ci	retval = fb_alloc_cmap(&info->cmap, 8, 0);
7098c2ecf20Sopenharmony_ci	if (retval < 0) {
7108c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Failed to allocate colormap\n");
7118c2ecf20Sopenharmony_ci		goto err_free_irq;
7128c2ecf20Sopenharmony_ci	}
7138c2ecf20Sopenharmony_ci
7148c2ecf20Sopenharmony_ci	/* set cmap */
7158c2ecf20Sopenharmony_ci	for (i = 0; i < 8; i++)
7168c2ecf20Sopenharmony_ci		info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16;
7178c2ecf20Sopenharmony_ci	memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8);
7188c2ecf20Sopenharmony_ci	memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8);
7198c2ecf20Sopenharmony_ci
7208c2ecf20Sopenharmony_ci	retval = register_framebuffer(info);
7218c2ecf20Sopenharmony_ci	if (retval < 0)
7228c2ecf20Sopenharmony_ci		goto err_cmap;
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, info);
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_ci	dev_dbg(&dev->dev,
7278c2ecf20Sopenharmony_ci		"fb%d: Metronome frame buffer device, using %dK of video"
7288c2ecf20Sopenharmony_ci		" memory\n", info->node, videomemorysize >> 10);
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci	return 0;
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_cierr_cmap:
7338c2ecf20Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
7348c2ecf20Sopenharmony_cierr_free_irq:
7358c2ecf20Sopenharmony_ci	board->cleanup(par);
7368c2ecf20Sopenharmony_cierr_csum_table:
7378c2ecf20Sopenharmony_ci	vfree(par->csum_table);
7388c2ecf20Sopenharmony_cierr_vfree:
7398c2ecf20Sopenharmony_ci	vfree(videomemory);
7408c2ecf20Sopenharmony_cierr_fb_rel:
7418c2ecf20Sopenharmony_ci	framebuffer_release(info);
7428c2ecf20Sopenharmony_cierr:
7438c2ecf20Sopenharmony_ci	module_put(board->owner);
7448c2ecf20Sopenharmony_ci	return retval;
7458c2ecf20Sopenharmony_ci}
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_cistatic int metronomefb_remove(struct platform_device *dev)
7488c2ecf20Sopenharmony_ci{
7498c2ecf20Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(dev);
7508c2ecf20Sopenharmony_ci
7518c2ecf20Sopenharmony_ci	if (info) {
7528c2ecf20Sopenharmony_ci		struct metronomefb_par *par = info->par;
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci		unregister_framebuffer(info);
7558c2ecf20Sopenharmony_ci		fb_deferred_io_cleanup(info);
7568c2ecf20Sopenharmony_ci		fb_dealloc_cmap(&info->cmap);
7578c2ecf20Sopenharmony_ci		par->board->cleanup(par);
7588c2ecf20Sopenharmony_ci		vfree(par->csum_table);
7598c2ecf20Sopenharmony_ci		vfree((void __force *)info->screen_base);
7608c2ecf20Sopenharmony_ci		module_put(par->board->owner);
7618c2ecf20Sopenharmony_ci		dev_dbg(&dev->dev, "calling release\n");
7628c2ecf20Sopenharmony_ci		framebuffer_release(info);
7638c2ecf20Sopenharmony_ci	}
7648c2ecf20Sopenharmony_ci	return 0;
7658c2ecf20Sopenharmony_ci}
7668c2ecf20Sopenharmony_ci
7678c2ecf20Sopenharmony_cistatic struct platform_driver metronomefb_driver = {
7688c2ecf20Sopenharmony_ci	.probe	= metronomefb_probe,
7698c2ecf20Sopenharmony_ci	.remove = metronomefb_remove,
7708c2ecf20Sopenharmony_ci	.driver	= {
7718c2ecf20Sopenharmony_ci		.name	= "metronomefb",
7728c2ecf20Sopenharmony_ci	},
7738c2ecf20Sopenharmony_ci};
7748c2ecf20Sopenharmony_cimodule_platform_driver(metronomefb_driver);
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_cimodule_param(user_wfm_size, uint, 0);
7778c2ecf20Sopenharmony_ciMODULE_PARM_DESC(user_wfm_size, "Set custom waveform size");
7788c2ecf20Sopenharmony_ci
7798c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("fbdev driver for Metronome controller");
7808c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaya Kumar");
7818c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
782