diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7041161 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +obj-m += sharp.o + +export KROOT=/lib/modules/$(shell uname -r)/build + +all: modules + +modules modules_install clean:: + @$(MAKE) -C $(KROOT) M=$(shell pwd) $@ + +clean:: + rm -rf Module.symvers modules.order diff --git a/README.md b/README.md index 68a4757..c987e56 100644 --- a/README.md +++ b/README.md @@ -1 +1,62 @@ # Sharp Memory LCD Kernel Driver + +**Note**: I did not write this driver. I only modified it to clean up compiler warnings/errors. The original can be found here: +(http://www.librecalc.com/en/wp-content/uploads/sites/4/2014/10/sharp.c) +More information can be found here: +(http://www.librecalc.com/en/downloads/) + +This driver is for the LS027B7DH01. It *should* work with other Sharp Mem LCD displays by modifying all 400/240 references with the correct dimensions for your screen. + +## Hookup Guide +Connect the following pins: +| Display | RasPi | +|:------- | ---------:| +| VIN | 3.3V | +| 3V3 | N/C | +| GND | GND | +| SCLK | 11 (SCLK) | +| MOSI | 10 (MOSI) | +| CS | 23 | +| EXTMD | 3.3V | +| DISP | 24 | +| EXTIN | 25 | + +## Compile/Install the driver +Verify that you have the linux kernel headers for your platform. For the RasPi these can be obtained by: +`sudo apt-get install raspberrypi-kernel-headers` +or more generally: +`sudo apt-get install linux-headers-$(uname -r)` + +To compile the driver, run: +`make` + +To install the driver, run: +`sudo make modules_install` + +If you want the module to load at boot you'll need to add it to the /etc/modules file, like: +``` +... +# This file contains... +# at boot time... +sharp +``` + +## Compile/Install the Device Tree Overlay +The included sharp.dts file is for the Raspberry Pi Zero W. To compile it, run: +`dtc -@ -I dts -O dtb -o sharp.dtbo sharp.dts` + +To load it at runtime, copy it to /boot/overlays: +`sudo cp sharp.dtbo /boot/overlays` + +And then add the following line to /boot/config.txt: +`dtoverlay=sharp` + +## Console on Display +If you want the boot console to show up on the display, you'll need to append `fbcon=map:10` to /boot/cmdline.txt after *rootwait*, like: +`... rootwait ... fbcon=map:10` + +To make sure the console fits on screen, uncomment the following lines in /boot/config.txt and set the resolution appropriately: +``` +framebuffer_width=400 +framebuffer_height=240 +``` \ No newline at end of file diff --git a/sharp.c b/sharp.c new file mode 100644 index 0000000..255ae94 --- /dev/null +++ b/sharp.c @@ -0,0 +1,427 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define LCDWIDTH 400 +#define VIDEOMEMSIZE (1*1024*1024) /* 1 MB */ + +char commandByte = 0b10000000; +char vcomByte = 0b01000000; +char clearByte = 0b00100000; +char paddingByte = 0b00000000; + +char DISP = 24; +char SCS = 23; +char VCOM = 25; + +int lcdWidth = LCDWIDTH; +int lcdHeight = 240; +int fpsCounter; + +static int seuil = 4; // Indispensable pour fbcon +module_param(seuil, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ); + +char vcomState; + +unsigned char lineBuffer[LCDWIDTH/8]; + +struct sharp { + struct spi_device *spi; + int id; + char name[sizeof("sharp-3")]; + + struct mutex mutex; + struct work_struct work; + spinlock_t lock; +}; + +struct sharp *screen; +struct fb_info *info; + +static void *videomemory; +static u_long videomemorysize = VIDEOMEMSIZE; + +void vfb_fillrect(struct fb_info *p, const struct fb_fillrect *region); +static int vfb_mmap(struct fb_info *info, struct vm_area_struct *vma); +void sendLine(char *buffer, char lineNumber); + +static struct fb_var_screeninfo vfb_default = { + .xres = 400, + .yres = 240, + .xres_virtual = 400, + .yres_virtual = 240, + .bits_per_pixel = 24, + .grayscale = 1, + .red = { 0, 8, 0 }, + .green = { 0, 8, 0 }, + .blue = { 0, 8, 0 }, + .activate = FB_ACTIVATE_NOW, + .height = 400, + .width = 240, + .pixclock = 20000, + .left_margin = 0, + .right_margin = 0, + .upper_margin = 0, + .lower_margin = 0, + .hsync_len = 128, + .vsync_len = 128, + .vmode = FB_VMODE_NONINTERLACED, + }; + +static struct fb_fix_screeninfo vfb_fix = { + .id = "Sharp FB", + .type = FB_TYPE_PACKED_PIXELS, + .line_length = 1200, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .visual = FB_VISUAL_MONO10, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_ops vfb_ops = { + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_mmap = vfb_mmap, +}; + +static struct task_struct *thread1; +static struct task_struct *fpsThread; +static struct task_struct *vcomToggleThread; + +static int vfb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + printk(KERN_CRIT "start %ld size %ld offset %ld", start, size, offset); + + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + if (size > info->fix.smem_len) + return -EINVAL; + if (offset > info->fix.smem_len - size) + return -EINVAL; + + pos = (unsigned long)info->fix.smem_start + offset; + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +void vfb_fillrect(struct fb_info *p, const struct fb_fillrect *region) +{ + printk(KERN_CRIT "from fillrect"); +} + +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +void clearDisplay(void) { + char buffer[2] = {clearByte, paddingByte}; + gpio_set_value(SCS, 1); + + spi_write(screen->spi, (const u8 *)buffer, 2); + + gpio_set_value(SCS, 0); +} + +char reverseByte(char b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +int vcomToggleFunction(void* v) +{ + while (!kthread_should_stop()) + { + msleep(50); + vcomState = vcomState ? 0:1; + gpio_set_value(VCOM, vcomState); + } + return 0; +} + +int fpsThreadFunction(void* v) +{ + while (!kthread_should_stop()) + { + msleep(5000); + printk(KERN_DEBUG "FPS sharp : %d\n", fpsCounter); + fpsCounter = 0; + } + return 0; +} + +int thread_fn(void* v) +{ + //int i; + int x,y,i; + char pixel; + char hasChanged = 0; + + unsigned char *screenBufferCompressed; + char bufferByte = 0; + char sendBuffer[1 + (1+50+1)*1 + 1]; + + clearDisplay(); + + //unsigned char *screenBufferCompressed; + screenBufferCompressed = vzalloc((50+4)*240*sizeof(unsigned char)); //plante si on met moins + + //char bufferByte = 0; + //char sendBuffer[1 + (1+50+1)*1 + 1]; + sendBuffer[0] = commandByte; + sendBuffer[52] = paddingByte; + sendBuffer[1 + 52] = paddingByte; + + // Init screen to black + for(y=0 ; y < 240 ; y++) + { + gpio_set_value(SCS, 1); + screenBufferCompressed[y*(50+4)] = commandByte; + screenBufferCompressed[y*(50+4) + 1] = reverseByte(y+1); //sharp display lines are indexed from 1 + screenBufferCompressed[y*(50+4) + 52] = paddingByte; + screenBufferCompressed[y*(50+4) + 53] = paddingByte; + + //screenBufferCompressed is all to 0 by default (vzalloc) + + spi_write(screen->spi, (const u8 *)(screenBufferCompressed+(y*(50+4))), 54); + gpio_set_value(SCS, 0); + } + + // Main loop + while (!kthread_should_stop()) + { + msleep(50); + + for(y=0 ; y < 240 ; y++) + { + hasChanged = 0; + + for(x=0 ; x<50 ; x++) + { + for(i=0 ; i<8 ; i++ ) + { + pixel = ioread8((void*)((uintptr_t)info->fix.smem_start + (x*8 + y*400 + i)*3)); + + if(pixel) + { + // passe le bit 7 - i a 1 + bufferByte |= (1 << (7 - i)); + } + else + { + // passe le bit 7 - i a 0 + bufferByte &= ~(1 << (7 - i)); + } + } + if(!hasChanged && (screenBufferCompressed[x + 2 + y*(50+4)] != bufferByte)) + { + hasChanged = 1; + } + screenBufferCompressed[x+2 + y*(50+4)] = bufferByte; + } + + if(hasChanged) + { + gpio_set_value(SCS, 1); + //la memoire allouee avec vzalloc semble trop lente... + memcpy(sendBuffer, screenBufferCompressed+y*(50+4), 54); + spi_write(screen->spi, (const u8 *)(sendBuffer), 54); + gpio_set_value(SCS, 0); + } + + } + } + + return 0; +} + +static int sharp_probe(struct spi_device *spi) +{ + char our_thread[] = "updateScreen"; + char thread_vcom[] = "vcom"; + char thread_fps[] = "fpsThread"; + int retval; + + screen = devm_kzalloc(&spi->dev, sizeof(*screen), GFP_KERNEL); + if (!screen) + return -ENOMEM; + + spi->bits_per_word = 8; + spi->max_speed_hz = 2000000; + + screen->spi = spi; + + spi_set_drvdata(spi, screen); + + thread1 = kthread_create(thread_fn,NULL,our_thread); + if((thread1)) + { + wake_up_process(thread1); + } + + fpsThread = kthread_create(fpsThreadFunction,NULL,thread_fps); + if((fpsThread)) + { + wake_up_process(fpsThread); + } + + vcomToggleThread = kthread_create(vcomToggleFunction,NULL,thread_vcom); + if((vcomToggleThread)) + { + wake_up_process(vcomToggleThread); + } + + gpio_request(SCS, "SCS"); + gpio_direction_output(SCS, 0); + + gpio_request(VCOM, "VCOM"); + gpio_direction_output(VCOM, 0); + + gpio_request(DISP, "DISP"); + gpio_direction_output(DISP, 1); + + // SCREEN PART + retval = -ENOMEM; + + if (!(videomemory = rvmalloc(videomemorysize))) + return retval; + + memset(videomemory, 0, videomemorysize); + + info = framebuffer_alloc(sizeof(u32) * 256, &spi->dev); + if (!info) + goto err; + + info->screen_base = (char __iomem *)videomemory; + info->fbops = &vfb_ops; + + info->var = vfb_default; + vfb_fix.smem_start = (unsigned long) videomemory; + vfb_fix.smem_len = videomemorysize; + info->fix = vfb_fix; + info->par = NULL; + info->flags = FBINFO_FLAG_DEFAULT; + + retval = fb_alloc_cmap(&info->cmap, 16, 0); + if (retval < 0) + goto err1; + + retval = register_framebuffer(info); + if (retval < 0) + goto err2; + + fb_info(info, "Virtual frame buffer device, using %ldK of video memory\n", + videomemorysize >> 10); + return 0; +err2: + fb_dealloc_cmap(&info->cmap); +err1: + framebuffer_release(info); +err: + rvfree(videomemory, videomemorysize); + + return 0; +} + +static int sharp_remove(struct spi_device *spi) +{ + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + kthread_stop(thread1); + kthread_stop(fpsThread); + kthread_stop(vcomToggleThread); + printk(KERN_CRIT "out of screen module"); + return 0; +} + +static struct spi_driver sharp_driver = { + .probe = sharp_probe, + .remove = sharp_remove, + .driver = { + .name = "sharp", + .owner = THIS_MODULE, + }, +}; + +module_spi_driver(sharp_driver); + +MODULE_AUTHOR("Ael Gain "); +MODULE_DESCRIPTION("Sharp memory lcd driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:sharp"); diff --git a/sharp.dts b/sharp.dts new file mode 100644 index 0000000..918234d --- /dev/null +++ b/sharp.dts @@ -0,0 +1,53 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + sharp_pins: sharp_pins { + brcm,pins = <25 24>; + brcm,function = <1 1>; /* out */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + sharp: sharp@0{ + compatible = "sharp"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&sharp_pins>; + spi-cs-high = <1>; + spi-max-frequency = <2000000>; + buswidth = <8>; + debug = <0>; + + }; + + }; + }; +}; \ No newline at end of file