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