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