#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/types.h>
#include "fbtft.h"
#define DRVNAME "lph88fb"
#define WIDTH 176
#define HEIGHT 132
/* Module Parameter: debug (also available through sysfs) */
MODULE_PARM_DEBUG;
u8 buffer[(WIDTH*HEIGHT*2)+1];
void lph88fb_write(struct fbtft_par *par, uint8_t a, uint8_t b, uint8_t c) {
uint8_t tx[3];
tx[0] = a;
tx[1] = b;
tx[2] = c;
par->fbtftops.write(par, tx, 3);
}
void lph88fb_reset(struct fbtft_par *par)
{
fbtft_dev_dbg(DEBUG_RESET, par->info->device, "[IN] %s()\n", __func__);
if (par->gpio.reset == -1)
return;
gpio_set_value(par->gpio.reset, 0);
msleep(50);
gpio_set_value(par->gpio.reset, 1);
msleep(50);
fbtft_dev_dbg(DEBUG_RESET, par->info->device, "[OUT] %s()\n", __func__);
}
void lph88fb_set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
{
uint16_t x,y,c;
fbtft_fbtft_dev_dbg(DEBUG_SET_ADDR_WIN, par, par->info->device, "[IN] %s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye);
// set y
y = (ye<<8)|(ys);
lph88fb_write(par, 0x74, 0x00, 0x16);
lph88fb_write(par, 0x76, y>>8, y);
// set x
x = (xe<<8)|(xs);
lph88fb_write(par, 0x74, 0x00, 0x17);
lph88fb_write(par, 0x76, x>>8, x);
// set cursor
c = ((xs<<8)|ys);
lph88fb_write(par, 0x74, 0x00, 0x21);
lph88fb_write(par, 0x76, c>>8, c);
fbtft_fbtft_dev_dbg(DEBUG_SET_ADDR_WIN, par, par->info->device, "[OUT] %s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye);
}
static int lph88fb_init_display(struct fbtft_par *par)
{
fbtft_dev_dbg(DEBUG_INIT_DISPLAY, par->info->device, "[IN] %s()\n", __func__);
// Reset the device
par->fbtftops.reset(par);
// Display off
lph88fb_write(par, 0x74, 0x00, 0x07);
lph88fb_write(par, 0x76, 0x00, 0x00);
msleep(10);
//power on sequence
//lcd drive control
lph88fb_write(par, 0x74, 0x00, 0x02);
lph88fb_write(par, 0x76, 0x04, 0x00);
//power control 3: VC //step 1
lph88fb_write(par, 0x74, 0x00, 0x0C);
lph88fb_write(par, 0x76, 0x00, 0x01);
//power control 4: VRH
lph88fb_write(par, 0x74, 0x00, 0x0D);
lph88fb_write(par, 0x76, 0x00, 0x06);
//power control 2: CAD
lph88fb_write(par, 0x74, 0x00, 0x04);
lph88fb_write(par, 0x76, 0x00, 0x00);
//power control 4: VRL
lph88fb_write(par, 0x74, 0x00, 0x0D);
lph88fb_write(par, 0x76, 0x06, 0x16);
//power control 5: VCM
lph88fb_write(par, 0x74, 0x00, 0x0E);
lph88fb_write(par, 0x76, 0x00, 0x10);
//power control 5: VDV
lph88fb_write(par, 0x74, 0x00, 0x0E);
lph88fb_write(par, 0x76, 0x10, 0x10);
//power control 1: BT //step 2
lph88fb_write(par, 0x74, 0x00, 0x03);
lph88fb_write(par, 0x76, 0x00, 0x00);
//power control 1: DC
lph88fb_write(par, 0x74, 0x00, 0x03);
lph88fb_write(par, 0x76, 0x00, 0x00);
//power control 1: AP
lph88fb_write(par, 0x74, 0x00, 0x03);
lph88fb_write(par, 0x76, 0x00, 0x0C);
msleep(40);
//power control 5: VCOMG //step 3
lph88fb_write(par, 0x74, 0x00, 0x0E);
lph88fb_write(par, 0x76, 0x2D, 0x1F);
msleep(40);
//power control 4: PON //step 4
lph88fb_write(par, 0x74, 0x00, 0x0D);
lph88fb_write(par, 0x76, 0x06, 0x16);
msleep(100);
//Entry mode ++
lph88fb_write(par, 0x74, 0x00, 0x05);
lph88fb_write(par, 0x76, 0x00, 0x38);
lph88fb_set_addr_win(par, 0, 0, (WIDTH-1), (HEIGHT-1));
//display on sequence (bit2 = reversed colors)
//display control: D0
lph88fb_write(par, 0x74, 0x00, 0x07);
lph88fb_write(par, 0x76, 0x00, 0x05);
//display control: GON
lph88fb_write(par, 0x74, 0x00, 0x07);
lph88fb_write(par, 0x76, 0x00, 0x25);
//display control: D1
lph88fb_write(par, 0x74, 0x00, 0x07);
lph88fb_write(par, 0x76, 0x00, 0x27);
//display control: DTE
lph88fb_write(par, 0x74, 0x00, 0x07);
lph88fb_write(par, 0x76, 0x00, 0x37);
msleep(10);
fbtft_dev_dbg(DEBUG_INIT_DISPLAY, par->info->device, "[OUT] %s()\n", __func__);
return 0;
}
int lph88fb_write_vmem(struct fbtft_par *par)
{
u8 *vmem8;
size_t remain;
int i;
int ret = 0;
size_t offset, len;
offset = par->dirty_lines_start * par->info->fix.line_length;
len = (par->dirty_lines_end - par->dirty_lines_start + 1) * par->info->fix.line_length;
remain = len;
vmem8 = par->info->screen_base + offset;
fbtft_fbtft_dev_dbg(DEBUG_WRITE_VMEM, par, par->info->device, "[IN] %s: offset=%d, len=%d\n", __func__, offset, len);
if (par->gpio.dc != -1)
gpio_set_value(par->gpio.dc, 1);
// Write prefix 1
lph88fb_write(par, 0x74, 0x00, 0x22);
// Shit on this
buffer[0] = 0x76;
for (i=0;i<len;i+=2) {
buffer[i+1] = vmem8[i+1];
buffer[i+2] = vmem8[i];
}
ret = par->fbtftops.write(par, buffer, len+1);
fbtft_fbtft_dev_dbg(DEBUG_WRITE_VMEM, par, par->info->device, "[OUT] %s: offset=%d, len=%d, ret=%d\n", __func__, offset, len, ret);
return ret;
}
struct fbtft_display lph88fb_display = {
.width = WIDTH,
.height = HEIGHT,
};
static int lph88fb_probe(struct spi_device *spi)
{
struct fb_info *info;
struct fbtft_par *par;
int ret;
fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "[IN] %s()\n", __func__);
info = fbtft_framebuffer_alloc(&lph88fb_display, &spi->dev);
if (!info)
return -ENOMEM;
par = info->par;
par->spi = spi;
fbtft_debug_init(par);
par->fbtftops.init_display = lph88fb_init_display;
par->fbtftops.reset = lph88fb_reset;
par->fbtftops.set_addr_win = lph88fb_set_addr_win;
par->fbtftops.write_vmem = lph88fb_write_vmem;
ret = fbtft_register_framebuffer(info);
if (ret < 0)
goto out_release;
fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "[OUT] %s()\n", __func__);
return 0;
out_release:
fbtft_framebuffer_release(info);
fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "[OUT] %s()\n", __func__);
return ret;
}
static int lph88fb_remove(struct spi_device *spi)
{
struct fb_info *info = spi_get_drvdata(spi);
fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "%s()\n", __func__);
if (info) {
struct fbtft_par *par = info->par;
lph88fb_write(par, 0x74, 0x00, 0x0D);
lph88fb_write(par, 0x76, 0x05, 0x05);
lph88fb_write(par, 0x74, 0x00, 0x0E);
lph88fb_write(par, 0x76, 0x1D, 0x1F);
lph88fb_write(par, 0x74, 0x00, 0x03);
lph88fb_write(par, 0x76, 0x00, 0x00);
fbtft_unregister_framebuffer(info);
fbtft_framebuffer_release(info);
}
return 0;
}
static struct spi_driver lph88fb_driver = {
.driver = {
.name = DRVNAME,
.owner = THIS_MODULE,
},
.probe = lph88fb_probe,
.remove = lph88fb_remove,
};
static int __init lph88fb_init(void)
{
fbtft_pr_debug("\n\n"DRVNAME": %s()\n", __func__);
return spi_register_driver(&lph88fb_driver);
}
static void __exit lph88fb_exit(void)
{
fbtft_pr_debug(DRVNAME": %s()\n", __func__);
spi_unregister_driver(&lph88fb_driver);
}
/* ------------------------------------------------------------------------- */
module_init(lph88fb_init);
module_exit(lph88fb_exit);
MODULE_DESCRIPTION("LPH88 S65 LCD Driver");
MODULE_AUTHOR("Mirko Kohns");
MODULE_LICENSE("GPL");