| /* |
| * FB driver for the Watterott LCD Controller |
| * |
| * Copyright (C) 2013 Noralf Tronnes |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/gpio.h> |
| #include <linux/delay.h> |
| |
| #include "fbtft.h" |
| |
| #define DRVNAME "fb_watterott" |
| #define WIDTH 320 |
| #define HEIGHT 240 |
| #define FPS 5 |
| #define TXBUFLEN 1024 |
| #define DEFAULT_BRIGHTNESS 50 |
| |
| #define CMD_VERSION 0x01 |
| #define CMD_LCD_LED 0x10 |
| #define CMD_LCD_RESET 0x11 |
| #define CMD_LCD_ORIENTATION 0x20 |
| #define CMD_LCD_DRAWIMAGE 0x27 |
| #define COLOR_RGB323 8 |
| #define COLOR_RGB332 9 |
| #define COLOR_RGB233 10 |
| #define COLOR_RGB565 16 |
| |
| static short mode = 565; |
| module_param(mode, short, 0000); |
| MODULE_PARM_DESC(mode, "RGB color transfer mode: 332, 565 (default)"); |
| |
| static void write_reg8_bus8(struct fbtft_par *par, int len, ...) |
| { |
| va_list args; |
| int i, ret; |
| u8 *buf = par->buf; |
| |
| va_start(args, len); |
| for (i = 0; i < len; i++) |
| *buf++ = (u8)va_arg(args, unsigned int); |
| va_end(args); |
| |
| fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, |
| par->info->device, u8, par->buf, len, "%s: ", __func__); |
| |
| ret = par->fbtftops.write(par, par->buf, len); |
| if (ret < 0) { |
| dev_err(par->info->device, |
| "write() failed and returned %d\n", ret); |
| return; |
| } |
| } |
| |
| static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) |
| { |
| unsigned int start_line, end_line; |
| u16 *vmem16 = (u16 *)(par->info->screen_buffer + offset); |
| __be16 *pos = par->txbuf.buf + 1; |
| __be16 *buf16 = par->txbuf.buf + 10; |
| int i, j; |
| int ret = 0; |
| |
| start_line = offset / par->info->fix.line_length; |
| end_line = start_line + (len / par->info->fix.line_length) - 1; |
| |
| /* Set command header. pos: x, y, w, h */ |
| ((u8 *)par->txbuf.buf)[0] = CMD_LCD_DRAWIMAGE; |
| pos[0] = 0; |
| pos[2] = cpu_to_be16(par->info->var.xres); |
| pos[3] = cpu_to_be16(1); |
| ((u8 *)par->txbuf.buf)[9] = COLOR_RGB565; |
| |
| for (i = start_line; i <= end_line; i++) { |
| pos[1] = cpu_to_be16(i); |
| for (j = 0; j < par->info->var.xres; j++) |
| buf16[j] = cpu_to_be16(*vmem16++); |
| ret = par->fbtftops.write(par, |
| par->txbuf.buf, 10 + par->info->fix.line_length); |
| if (ret < 0) |
| return ret; |
| udelay(300); |
| } |
| |
| return 0; |
| } |
| |
| #define RGB565toRGB323(c) (((c&0xE000)>>8) | ((c&0600)>>6) | ((c&0x001C)>>2)) |
| #define RGB565toRGB332(c) (((c&0xE000)>>8) | ((c&0700)>>6) | ((c&0x0018)>>3)) |
| #define RGB565toRGB233(c) (((c&0xC000)>>8) | ((c&0700)>>5) | ((c&0x001C)>>2)) |
| |
| static int write_vmem_8bit(struct fbtft_par *par, size_t offset, size_t len) |
| { |
| unsigned int start_line, end_line; |
| u16 *vmem16 = (u16 *)(par->info->screen_buffer + offset); |
| __be16 *pos = par->txbuf.buf + 1; |
| u8 *buf8 = par->txbuf.buf + 10; |
| int i, j; |
| int ret = 0; |
| |
| start_line = offset / par->info->fix.line_length; |
| end_line = start_line + (len / par->info->fix.line_length) - 1; |
| |
| /* Set command header. pos: x, y, w, h */ |
| ((u8 *)par->txbuf.buf)[0] = CMD_LCD_DRAWIMAGE; |
| pos[0] = 0; |
| pos[2] = cpu_to_be16(par->info->var.xres); |
| pos[3] = cpu_to_be16(1); |
| ((u8 *)par->txbuf.buf)[9] = COLOR_RGB332; |
| |
| for (i = start_line; i <= end_line; i++) { |
| pos[1] = cpu_to_be16(i); |
| for (j = 0; j < par->info->var.xres; j++) { |
| buf8[j] = RGB565toRGB332(*vmem16); |
| vmem16++; |
| } |
| ret = par->fbtftops.write(par, |
| par->txbuf.buf, 10 + par->info->var.xres); |
| if (ret < 0) |
| return ret; |
| udelay(700); |
| } |
| |
| return 0; |
| } |
| |
| static unsigned int firmware_version(struct fbtft_par *par) |
| { |
| u8 rxbuf[4] = {0, }; |
| |
| write_reg(par, CMD_VERSION); |
| par->fbtftops.read(par, rxbuf, 4); |
| if (rxbuf[1] != '.') |
| return 0; |
| |
| return (rxbuf[0] - '0') << 8 | (rxbuf[2] - '0') << 4 | (rxbuf[3] - '0'); |
| } |
| |
| static int init_display(struct fbtft_par *par) |
| { |
| int ret; |
| unsigned int version; |
| u8 save_mode; |
| |
| /* enable SPI interface by having CS and MOSI low during reset */ |
| save_mode = par->spi->mode; |
| par->spi->mode |= SPI_CS_HIGH; |
| ret = spi_setup(par->spi); /* set CS inactive low */ |
| if (ret) { |
| dev_err(par->info->device, "Could not set SPI_CS_HIGH\n"); |
| return ret; |
| } |
| write_reg(par, 0x00); /* make sure mode is set */ |
| |
| mdelay(50); |
| par->fbtftops.reset(par); |
| mdelay(1000); |
| par->spi->mode = save_mode; |
| ret = spi_setup(par->spi); |
| if (ret) { |
| dev_err(par->info->device, "Could not restore SPI mode\n"); |
| return ret; |
| } |
| write_reg(par, 0x00); |
| |
| version = firmware_version(par); |
| fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "Firmware version: %x.%02x\n", |
| version >> 8, version & 0xFF); |
| |
| if (mode == 332) |
| par->fbtftops.write_vmem = write_vmem_8bit; |
| return 0; |
| } |
| |
| static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) |
| { |
| /* not used on this controller */ |
| } |
| |
| static int set_var(struct fbtft_par *par) |
| { |
| u8 rotate; |
| |
| /* this controller rotates clock wise */ |
| switch (par->info->var.rotate) { |
| case 90: |
| rotate = 27; |
| break; |
| case 180: |
| rotate = 18; |
| break; |
| case 270: |
| rotate = 9; |
| break; |
| default: |
| rotate = 0; |
| } |
| write_reg(par, CMD_LCD_ORIENTATION, rotate); |
| |
| return 0; |
| } |
| |
| static int verify_gpios(struct fbtft_par *par) |
| { |
| if (par->gpio.reset < 0) { |
| dev_err(par->info->device, "Missing 'reset' gpio. Aborting.\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| #ifdef CONFIG_FB_BACKLIGHT |
| static int backlight_chip_update_status(struct backlight_device *bd) |
| { |
| struct fbtft_par *par = bl_get_data(bd); |
| int brightness = bd->props.brightness; |
| |
| fbtft_par_dbg(DEBUG_BACKLIGHT, par, |
| "%s: brightness=%d, power=%d, fb_blank=%d\n", |
| __func__, bd->props.brightness, bd->props.power, |
| bd->props.fb_blank); |
| |
| if (bd->props.power != FB_BLANK_UNBLANK) |
| brightness = 0; |
| |
| if (bd->props.fb_blank != FB_BLANK_UNBLANK) |
| brightness = 0; |
| |
| write_reg(par, CMD_LCD_LED, brightness); |
| |
| return 0; |
| } |
| |
| static const struct backlight_ops bl_ops = { |
| .update_status = backlight_chip_update_status, |
| }; |
| |
| static void register_chip_backlight(struct fbtft_par *par) |
| { |
| struct backlight_device *bd; |
| struct backlight_properties bl_props = { 0, }; |
| |
| bl_props.type = BACKLIGHT_RAW; |
| bl_props.power = FB_BLANK_POWERDOWN; |
| bl_props.max_brightness = 100; |
| bl_props.brightness = DEFAULT_BRIGHTNESS; |
| |
| bd = backlight_device_register(dev_driver_string(par->info->device), |
| par->info->device, par, &bl_ops, &bl_props); |
| if (IS_ERR(bd)) { |
| dev_err(par->info->device, |
| "cannot register backlight device (%ld)\n", |
| PTR_ERR(bd)); |
| return; |
| } |
| par->info->bl_dev = bd; |
| |
| if (!par->fbtftops.unregister_backlight) |
| par->fbtftops.unregister_backlight = fbtft_unregister_backlight; |
| } |
| #else |
| #define register_chip_backlight NULL |
| #endif |
| |
| static struct fbtft_display display = { |
| .regwidth = 8, |
| .buswidth = 8, |
| .width = WIDTH, |
| .height = HEIGHT, |
| .fps = FPS, |
| .txbuflen = TXBUFLEN, |
| .fbtftops = { |
| .write_register = write_reg8_bus8, |
| .write_vmem = write_vmem, |
| .init_display = init_display, |
| .set_addr_win = set_addr_win, |
| .set_var = set_var, |
| .verify_gpios = verify_gpios, |
| .register_backlight = register_chip_backlight, |
| }, |
| }; |
| |
| FBTFT_REGISTER_DRIVER(DRVNAME, "watterott,openlcd", &display); |
| |
| MODULE_ALIAS("spi:" DRVNAME); |
| |
| MODULE_DESCRIPTION("FB driver for the Watterott LCD Controller"); |
| MODULE_AUTHOR("Noralf Tronnes"); |
| MODULE_LICENSE("GPL"); |