#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/list.h>
#include <linux/firmware.h>
#include <linux/dma-mapping.h>
#include <linux/uaccess.h>
#include <linux/irq.h>
#include <video/metronomefb.h>
#include <asm/unaligned.h>
#define DPY_W 832
#define DPY_H 622
static int user_wfm_size;
struct epd_frame {
int fw;
int fh;
u16 config[4];
int wfm_size;
};
static struct epd_frame epd_frame_table[] = {
{
.fw = 832,
.fh = 622,
.config = {
15
| 2 << 8
| 0 << 11
| 0 << 12
| 0 << 15,
42
| 1 << 8
| 1 << 9
| 0 << 15,
18
| 0 << 15,
599
| 0 << 11
| 0 << 12,
},
.wfm_size = 47001,
},
{
.fw = 1088,
.fh = 791,
.config = {
0x0104,
0x031f,
0x0088,
0x02ff,
},
.wfm_size = 46770,
},
{
.fw = 1200,
.fh = 842,
.config = {
0x0101,
0x030e,
0x0012,
0x0280,
},
.wfm_size = 46770,
},
};
static struct fb_fix_screeninfo metronomefb_fix = {
.id = "metronomefb",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_STATIC_PSEUDOCOLOR,
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
.line_length = DPY_W,
.accel = FB_ACCEL_NONE,
};
static struct fb_var_screeninfo metronomefb_var = {
.xres = DPY_W,
.yres = DPY_H,
.xres_virtual = DPY_W,
.yres_virtual = DPY_H,
.bits_per_pixel = 8,
.grayscale = 1,
.nonstd = 1,
.red = { 4, 3, 0 },
.green = { 0, 0, 0 },
.blue = { 0, 0, 0 },
.transp = { 0, 0, 0 },
};
struct waveform_hdr {
u8 stuff[32];
u8 wmta[3];
u8 fvsn;
u8 luts;
u8 mc;
u8 trc;
u8 stuff3;
u8 endb;
u8 swtb;
u8 stuff2a[2];
u8 stuff2b[3];
u8 wfm_cs;
} __attribute__ ((packed));
static u8 calc_cksum(int start, int end, u8 *mem)
{
u8 tmp = 0;
int i;
for (i = start; i < end; i++)
tmp += mem[i];
return tmp;
}
static u16 calc_img_cksum(u16 *start, int length)
{
u16 tmp = 0;
while (length--)
tmp += *start++;
return tmp;
}
static int load_waveform(u8 *mem, size_t size, int m, int t,
struct metronomefb_par *par)
{
int tta;
int wmta;
int trn = 0;
int i;
unsigned char v;
u8 cksum;
int cksum_idx;
int wfm_idx, owfm_idx;
int mem_idx = 0;
struct waveform_hdr *wfm_hdr;
u8 *metromem = par->metromem_wfm;
struct device *dev = par->info->device;
if (user_wfm_size)
epd_frame_table[par->dt].wfm_size = user_wfm_size;
if (size != epd_frame_table[par->dt].wfm_size) {
dev_err(dev, "Error: unexpected size %zd != %d\n", size,
epd_frame_table[par->dt].wfm_size);
return -EINVAL;
}
wfm_hdr = (struct waveform_hdr *) mem;
if (wfm_hdr->fvsn != 1) {
dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn);
return -EINVAL;
}
if (wfm_hdr->luts != 0) {
dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts);
return -EINVAL;
}
cksum = calc_cksum(32, 47, mem);
if (cksum != wfm_hdr->wfm_cs) {
dev_err(dev, "Error: bad cksum %x != %x\n", cksum,
wfm_hdr->wfm_cs);
return -EINVAL;
}
wfm_hdr->mc += 1;
wfm_hdr->trc += 1;
for (i = 0; i < 5; i++) {
if (*(wfm_hdr->stuff2a + i) != 0) {
dev_err(dev, "Error: unexpected value in padding\n");
return -EINVAL;
}
}
if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size)
return -EINVAL;
for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) {
if (mem[i] > t) {
trn = i - sizeof(*wfm_hdr) - 1;
break;
}
}
cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1;
if (cksum_idx >= size)
return -EINVAL;
cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem);
if (cksum != mem[cksum_idx]) {
dev_err(dev, "Error: bad temperature range table cksum"
" %x != %x\n", cksum, mem[cksum_idx]);
return -EINVAL;
}
wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF;
cksum_idx = wmta + m*4 + 3;
if (cksum_idx >= size)
return -EINVAL;
cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
if (cksum != mem[cksum_idx]) {
dev_err(dev, "Error: bad mode table address cksum"
" %x != %x\n", cksum, mem[cksum_idx]);
return -EINVAL;
}
tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF;
cksum_idx = tta + trn*4 + 3;
if (cksum_idx >= size)
return -EINVAL;
cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem);
if (cksum != mem[cksum_idx]) {
dev_err(dev, "Error: bad temperature table address cksum"
" %x != %x\n", cksum, mem[cksum_idx]);
return -EINVAL;
}
wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF;
owfm_idx = wfm_idx;
if (wfm_idx >= size)
return -EINVAL;
while (wfm_idx < size) {
unsigned char rl;
v = mem[wfm_idx++];
if (v == wfm_hdr->swtb) {
while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) &&
wfm_idx < size)
metromem[mem_idx++] = v;
continue;
}
if (v == wfm_hdr->endb)
break;
rl = mem[wfm_idx++];
for (i = 0; i <= rl; i++)
metromem[mem_idx++] = v;
}
cksum_idx = wfm_idx;
if (cksum_idx >= size)
return -EINVAL;
cksum = calc_cksum(owfm_idx, cksum_idx, mem);
if (cksum != mem[cksum_idx]) {
dev_err(dev, "Error: bad waveform data cksum"
" %x != %x\n", cksum, mem[cksum_idx]);
return -EINVAL;
}
par->frame_count = (mem_idx/64);
return 0;
}
static int metronome_display_cmd(struct metronomefb_par *par)
{
int i;
u16 cs;
u16 opcode;
static u8 borderval;
if (par->metromem_cmd->opcode == 0xCC40)
opcode = cs = 0xCC41;
else
opcode = cs = 0xCC40;
i = 0;
par->metromem_cmd->args[i] = 1 << 3
| ((borderval++ % 4) & 0x0F) << 4
| (par->frame_count - 1) << 8;
cs += par->metromem_cmd->args[i++];
memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
par->metromem_cmd->csum = cs;
par->metromem_cmd->opcode = opcode;
return par->board->met_wait_event_intr(par);
}
static int metronome_powerup_cmd(struct metronomefb_par *par)
{
int i;
u16 cs;
par->metromem_cmd->opcode = 0x1234;
cs = par->metromem_cmd->opcode;
for (i = 0; i < 3; i++) {
par->metromem_cmd->args[i] = 1024;
cs += par->metromem_cmd->args[i];
}
memset(&par->metromem_cmd->args[i], 0,
(ARRAY_SIZE(par->metromem_cmd->args) - i) * 2);
par->metromem_cmd->csum = cs;
msleep(1);
par->board->set_rst(par, 1);
msleep(1);
par->board->set_stdby(par, 1);
return par->board->met_wait_event(par);
}
static int metronome_config_cmd(struct metronomefb_par *par)
{
memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config,
sizeof(epd_frame_table[par->dt].config));
memset(&par->metromem_cmd->args[4], 0,
(ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2);
par->metromem_cmd->csum = 0xCC10;
par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4);
par->metromem_cmd->opcode = 0xCC10;
return par->board->met_wait_event(par);
}
static int metronome_init_cmd(struct metronomefb_par *par)
{
int i;
u16 cs;
cs = 0xCC20;
i = 0;
par->metromem_cmd->args[i] = 0;
cs += par->metromem_cmd->args[i++];
memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2);
par->metromem_cmd->csum = cs;
par->metromem_cmd->opcode = 0xCC20;
return par->board->met_wait_event(par);
}
static int metronome_init_regs(struct metronomefb_par *par)
{
int res;
res = par->board->setup_io(par);
if (res)
return res;
res = metronome_powerup_cmd(par);
if (res)
return res;
res = metronome_config_cmd(par);
if (res)
return res;
res = metronome_init_cmd(par);
return res;
}
static void metronomefb_dpy_update(struct metronomefb_par *par)
{
int fbsize;
u16 cksum;
unsigned char *buf = par->info->screen_buffer;
fbsize = par->info->fix.smem_len;
memcpy(par->metromem_img, buf, fbsize);
cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2);
*((u16 *)(par->metromem_img) + fbsize/2) = cksum;
metronome_display_cmd(par);
}
static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)
{
int i;
u16 csum = 0;
u16 *buf = (u16 *)(par->info->screen_buffer + index);
u16 *img = (u16 *)(par->metromem_img + index);
for (i = 0; i < PAGE_SIZE/2; i++) {
*(img + i) = (buf[i] << 5) & 0xE0E0;
csum += *(img + i);
}
return csum;
}
static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
u16 cksum;
struct fb_deferred_io_pageref *pageref;
struct metronomefb_par *par = info->par;
list_for_each_entry(pageref, pagereflist, list) {
unsigned long pgoffset = pageref->offset >> PAGE_SHIFT;
cksum = metronomefb_dpy_update_page(par, pageref->offset);
par->metromem_img_csum -= par->csum_table[pgoffset];
par->csum_table[pgoffset] = cksum;
par->metromem_img_csum += cksum;
}
metronome_display_cmd(par);
}
static void metronomefb_defio_damage_range(struct fb_info *info, off_t off, size_t len)
{
struct metronomefb_par *par = info->par;
metronomefb_dpy_update(par);
}
static void metronomefb_defio_damage_area(struct fb_info *info, u32 x, u32 y,
u32 width, u32 height)
{
struct metronomefb_par *par = info->par;
metronomefb_dpy_update(par);
}
FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(metronomefb,
metronomefb_defio_damage_range,
metronomefb_defio_damage_area)
static const struct fb_ops metronomefb_ops = {
.owner = THIS_MODULE,
FB_DEFAULT_DEFERRED_OPS(metronomefb),
};
static struct fb_deferred_io metronomefb_defio = {
.delay = HZ,
.sort_pagereflist = true,
.deferred_io = metronomefb_dpy_deferred_io,
};
static int metronomefb_probe(struct platform_device *dev)
{
struct fb_info *info;
struct metronome_board *board;
int retval = -ENOMEM;
int videomemorysize;
unsigned char *videomemory;
struct metronomefb_par *par;
const struct firmware *fw_entry;
int i;
int panel_type;
int fw, fh;
int epd_dt_index;
board = dev->dev.platform_data;
if (!board)
return -EINVAL;
if (!try_module_get(board->owner))
return -ENODEV;
info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev);
if (!info)
goto err;
panel_type = board->get_panel_type();
switch (panel_type) {
case 6:
epd_dt_index = 0;
break;
case 8:
epd_dt_index = 1;
break;
case 97:
epd_dt_index = 2;
break;
default:
dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n");
epd_dt_index = 0;
break;
}
fw = epd_frame_table[epd_dt_index].fw;
fh = epd_frame_table[epd_dt_index].fh;
videomemorysize = PAGE_SIZE + (fw * fh);
videomemory = vzalloc(videomemorysize);
if (!videomemory)
goto err_fb_rel;
info->screen_buffer = videomemory;
info->fbops = &metronomefb_ops;
metronomefb_fix.line_length = fw;
metronomefb_var.xres = fw;
metronomefb_var.yres = fh;
metronomefb_var.xres_virtual = fw;
metronomefb_var.yres_virtual = fh;
info->var = metronomefb_var;
info->fix = metronomefb_fix;
info->fix.smem_len = videomemorysize;
par = info->par;
par->info = info;
par->board = board;
par->dt = epd_dt_index;
init_waitqueue_head(&par->waitq);
par->csum_table = vmalloc(videomemorysize/PAGE_SIZE);
if (!par->csum_table)
goto err_vfree;
retval = board->setup_fb(par);
if (retval) {
dev_err(&dev->dev, "Failed to setup fb\n");
goto err_csum_table;
}
if ((!par->metromem_wfm) || (!par->metromem_img) ||
(!par->metromem_dma)) {
dev_err(&dev->dev, "fb access failure\n");
retval = -EINVAL;
goto err_csum_table;
}
info->fix.smem_start = par->metromem_dma;
retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev);
if (retval < 0) {
dev_err(&dev->dev, "Failed to get waveform\n");
goto err_csum_table;
}
retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31,
par);
release_firmware(fw_entry);
if (retval < 0) {
dev_err(&dev->dev, "Failed processing waveform\n");
goto err_csum_table;
}
retval = board->setup_irq(info);
if (retval)
goto err_csum_table;
retval = metronome_init_regs(par);
if (retval < 0)
goto err_free_irq;
info->flags = FBINFO_VIRTFB;
info->fbdefio = &metronomefb_defio;
fb_deferred_io_init(info);
retval = fb_alloc_cmap(&info->cmap, 8, 0);
if (retval < 0) {
dev_err(&dev->dev, "Failed to allocate colormap\n");
goto err_free_irq;
}
for (i = 0; i < 8; i++)
info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16;
memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8);
memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8);
retval = register_framebuffer(info);
if (retval < 0)
goto err_cmap;
platform_set_drvdata(dev, info);
dev_dbg(&dev->dev,
"fb%d: Metronome frame buffer device, using %dK of video"
" memory\n", info->node, videomemorysize >> 10);
return 0;
err_cmap:
fb_dealloc_cmap(&info->cmap);
err_free_irq:
board->cleanup(par);
err_csum_table:
vfree(par->csum_table);
err_vfree:
vfree(videomemory);
err_fb_rel:
framebuffer_release(info);
err:
module_put(board->owner);
return retval;
}
static void metronomefb_remove(struct platform_device *dev)
{
struct fb_info *info = platform_get_drvdata(dev);
if (info) {
struct metronomefb_par *par = info->par;
unregister_framebuffer(info);
fb_deferred_io_cleanup(info);
fb_dealloc_cmap(&info->cmap);
par->board->cleanup(par);
vfree(par->csum_table);
vfree(info->screen_buffer);
module_put(par->board->owner);
dev_dbg(&dev->dev, "calling release\n");
framebuffer_release(info);
}
}
static struct platform_driver metronomefb_driver = {
.probe = metronomefb_probe,
.remove_new = metronomefb_remove,
.driver = {
.name = "metronomefb",
},
};
module_platform_driver(metronomefb_driver);
module_param(user_wfm_size, uint, 0);
MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size");
MODULE_DESCRIPTION("fbdev driver for Metronome controller");
MODULE_AUTHOR("Jaya Kumar");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE("metronome.wbf"