Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #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 "fbtft.h" | |
| #define DRVNAME "fb_ssd1331b" | |
| #define WIDTH 96 | |
| #define EYEHEIGHT 64 | |
| #define TOTALHEIGHT (EYEHEIGHT*2) | |
| #define EYEBOUNDARY (WIDTH*EYEHEIGHT*2) // *2 because of 16bpp | |
| #define GAMMA_NUM 1 | |
| #define GAMMA_LEN 63 | |
| #define DEFAULT_GAMMA "0 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2 2 " \ | |
| "2 2 2 2 2 2 2" \ | |
| /** | |
| * FIXME: The pinout for this module is currently hard-coded. | |
| * This module supports a single framebuffer device for two SSD1331 displays, | |
| * configured in above/below position. The pinouts for each are configured | |
| * below. | |
| */ | |
| // Left display | |
| #define SSD1331_GPIO_CE0 8 | |
| #define SSD1331_GPIO_RST0 24 | |
| #define SSD1331_GPIO_DC0 25 | |
| // Right display | |
| #define SSD1331_GPIO_CE1 7 | |
| #define SSD1331_GPIO_RST1 22 | |
| #define SSD1331_GPIO_DC1 23 | |
| static struct gpio ssd1331_gpios[] = { | |
| { SSD1331_GPIO_CE0, GPIOF_OUT_INIT_HIGH, "CE0" }, | |
| { SSD1331_GPIO_RST0, GPIOF_OUT_INIT_HIGH, "RST0" }, | |
| { SSD1331_GPIO_DC0, GPIOF_OUT_INIT_LOW, "DC0" }, | |
| { SSD1331_GPIO_CE1, GPIOF_OUT_INIT_HIGH, "CE1" }, | |
| { SSD1331_GPIO_RST1, GPIOF_OUT_INIT_HIGH, "RST1" }, | |
| { SSD1331_GPIO_DC1, GPIOF_OUT_INIT_LOW, "DC1" }, | |
| }; | |
| static int init_display(struct fbtft_par *par) | |
| { | |
| fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); | |
| par->fbtftops.reset(par); | |
| write_reg(par, 0xae); /* Display Off */ | |
| write_reg(par, 0xa0, 0x70 | (par->bgr << 2)); /* Set Colour Depth */ | |
| write_reg(par, 0x72); // RGB colour | |
| write_reg(par, 0xa1, 0x00); /* Set Display Start Line */ | |
| write_reg(par, 0xa2, 0x00); /* Set Display Offset */ | |
| write_reg(par, 0xa4); /* NORMALDISPLAY */ | |
| write_reg(par, 0xa8, 0x3f); // Set multiplex | |
| write_reg(par, 0xad, 0x8e); // Set master | |
| // write_reg(par, 0xb0, 0x0b); // Set power mode | |
| write_reg(par, 0xb1, 0x31); // Precharge | |
| write_reg(par, 0xb3, 0xf0); // Clock div | |
| write_reg(par, 0x8a, 0x64); // Precharge A | |
| write_reg(par, 0x8b, 0x78); // Precharge B | |
| write_reg(par, 0x8c, 0x64); // Precharge C | |
| write_reg(par, 0xbb, 0x3a); // Precharge level | |
| write_reg(par, 0xbe, 0x3e); // vcomh | |
| write_reg(par, 0x87, 0x06); // Master current | |
| write_reg(par, 0x81, 0x91); // Contrast A | |
| write_reg(par, 0x82, 0x50); // Contrast B | |
| write_reg(par, 0x83, 0x7d); // Contrast C | |
| write_reg(par, 0xaf); /* Set Sleep Mode Display On */ | |
| return 0; | |
| } | |
| void reset(struct fbtft_par *par) | |
| { | |
| fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__); | |
| gpio_set_value(SSD1331_GPIO_RST0, 0); | |
| gpio_set_value(SSD1331_GPIO_RST1, 0); | |
| udelay(20); | |
| gpio_set_value(SSD1331_GPIO_RST0, 1); | |
| gpio_set_value(SSD1331_GPIO_RST1, 1); | |
| mdelay(120); | |
| } | |
| static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) | |
| { | |
| fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, | |
| "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); | |
| write_reg(par, 0x15, xs, xe); | |
| write_reg(par, 0x75, ys%EYEHEIGHT, ye%EYEHEIGHT); | |
| } | |
| static void write_reg8_bus8(struct fbtft_par *par, int len, ...) | |
| { | |
| va_list args; | |
| int i, ret; | |
| u8 *buf = (u8 *)par->buf; | |
| if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { | |
| va_start(args, len); | |
| for (i = 0; i < len; i++) { | |
| buf[i] = (u8)va_arg(args, unsigned int); | |
| } | |
| va_end(args); | |
| fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device, u8, buf, len, "%s: ", __func__); | |
| } | |
| va_start(args, len); | |
| *buf = (u8)va_arg(args, unsigned int); | |
| gpio_set_value(SSD1331_GPIO_DC0, 0); | |
| gpio_set_value(SSD1331_GPIO_DC1, 0); | |
| gpio_set_value(SSD1331_GPIO_CE0, 0); | |
| gpio_set_value(SSD1331_GPIO_CE1, 0); | |
| ret = par->fbtftops.write(par, par->buf, sizeof(u8)); | |
| if (ret < 0) { | |
| va_end(args); | |
| dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); | |
| return; | |
| } | |
| len--; | |
| if (len) { | |
| i = len; | |
| while (i--) { | |
| *buf++ = (u8)va_arg(args, unsigned int); | |
| } | |
| ret = par->fbtftops.write(par, par->buf, len * (sizeof(u8))); | |
| if (ret < 0) { | |
| va_end(args); | |
| dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); | |
| return; | |
| } | |
| } | |
| gpio_set_value(SSD1331_GPIO_CE0, 1); | |
| gpio_set_value(SSD1331_GPIO_CE1, 1); | |
| va_end(args); | |
| } | |
| int write_vmem(struct fbtft_par *par, size_t offset, size_t len) | |
| { | |
| int ret=0; | |
| if (offset<EYEBOUNDARY && offset+len<=EYEBOUNDARY) { | |
| // Entirely on display 0 | |
| gpio_set_value(SSD1331_GPIO_CE0, 0); | |
| gpio_set_value(SSD1331_GPIO_DC0, 1); | |
| ret = fbtft_write_vmem16_bus8(par, offset, len); | |
| gpio_set_value(SSD1331_GPIO_CE0, 1); | |
| } else if (offset >= EYEBOUNDARY) { | |
| // Entirely on display 1 | |
| gpio_set_value(SSD1331_GPIO_CE1, 0); | |
| gpio_set_value(SSD1331_GPIO_DC1, 1); | |
| ret = fbtft_write_vmem16_bus8(par, offset, len); | |
| gpio_set_value(SSD1331_GPIO_CE1, 1); | |
| } else { | |
| // Starts on display 0, spans to display 1 | |
| gpio_set_value(SSD1331_GPIO_CE0, 0); | |
| gpio_set_value(SSD1331_GPIO_DC0, 1); | |
| fbtft_write_vmem16_bus8(par, offset, EYEBOUNDARY-offset); | |
| gpio_set_value(SSD1331_GPIO_CE0, 1); | |
| gpio_set_value(SSD1331_GPIO_CE1, 0); | |
| gpio_set_value(SSD1331_GPIO_DC1, 1); | |
| ret = fbtft_write_vmem16_bus8(par, EYEBOUNDARY, len+offset-EYEBOUNDARY); | |
| gpio_set_value(SSD1331_GPIO_CE1, 1); | |
| } | |
| return ret; | |
| } | |
| /* | |
| Grayscale Lookup Table | |
| GS1 - GS63 | |
| The driver Gamma curve contains the relative values between the entries | |
| in the Lookup table. | |
| From datasheet: | |
| 8.8 Gray Scale Decoder | |
| there are total 180 Gamma Settings (Setting 0 to Setting 180) | |
| available for the Gray Scale table. | |
| The gray scale is defined in incremental way, with reference | |
| to the length of previous table entry: | |
| Setting of GS1 has to be >= 0 | |
| Setting of GS2 has to be > Setting of GS1 +1 | |
| Setting of GS3 has to be > Setting of GS2 +1 | |
| : | |
| Setting of GS63 has to be > Setting of GS62 +1 | |
| */ | |
| static int set_gamma(struct fbtft_par *par, unsigned long *curves) | |
| { | |
| unsigned long tmp[GAMMA_NUM * GAMMA_LEN]; | |
| int i, acc = 0; | |
| fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); | |
| for (i = 0; i < 63; i++) { | |
| if (i > 0 && curves[i] < 2) { | |
| dev_err(par->info->device, | |
| "Illegal value in Grayscale Lookup Table at index %d. " \ | |
| "Must be greater than 1\n", i); | |
| return -EINVAL; | |
| } | |
| acc += curves[i]; | |
| tmp[i] = acc; | |
| if (acc > 180) { | |
| dev_err(par->info->device, | |
| "Illegal value(s) in Grayscale Lookup Table. " \ | |
| "At index=%d, the accumulated value has exceeded 180\n", i); | |
| return -EINVAL; | |
| } | |
| } | |
| write_reg(par, 0xB8, | |
| tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], | |
| tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14], tmp[15], | |
| tmp[16], tmp[17], tmp[18], tmp[19], tmp[20], tmp[21], tmp[22], tmp[23], | |
| tmp[24], tmp[25], tmp[26], tmp[27], tmp[28], tmp[29], tmp[30], tmp[31], | |
| tmp[32], tmp[33], tmp[34], tmp[35], tmp[36], tmp[37], tmp[38], tmp[39], | |
| tmp[40], tmp[41], tmp[42], tmp[43], tmp[44], tmp[45], tmp[46], tmp[47], | |
| tmp[48], tmp[49], tmp[50], tmp[51], tmp[52], tmp[53], tmp[54], tmp[55], | |
| tmp[56], tmp[57], tmp[58], tmp[59], tmp[60], tmp[61], tmp[62]); | |
| return 0; | |
| } | |
| static int blank(struct fbtft_par *par, bool on) | |
| { | |
| fbtft_par_dbg(DEBUG_BLANK, par, "%s(blank=%s)\n", | |
| __func__, on ? "true" : "false"); | |
| gpio_set_value(SSD1331_GPIO_CE0, 0); | |
| gpio_set_value(SSD1331_GPIO_CE1, 0); | |
| if (on) | |
| write_reg(par, 0xAE); | |
| else | |
| write_reg(par, 0xAF); | |
| gpio_set_value(SSD1331_GPIO_CE0, 1); | |
| gpio_set_value(SSD1331_GPIO_CE1, 1); | |
| return 0; | |
| } | |
| int request_gpios(struct fbtft_par *par) | |
| { | |
| int i; | |
| int ret; | |
| /* Initialize gpios to disabled */ | |
| par->gpio.reset = -1; | |
| par->gpio.dc = -1; | |
| par->gpio.rd = -1; | |
| par->gpio.wr = -1; | |
| par->gpio.cs = -1; | |
| par->gpio.latch = -1; | |
| for (i = 0; i < 16; i++) { | |
| par->gpio.db[i] = -1; | |
| par->gpio.led[i] = -1; | |
| par->gpio.aux[i] = -1; | |
| } | |
| ret = gpio_request_array(ssd1331_gpios, ARRAY_SIZE(ssd1331_gpios)); | |
| if (ret) { | |
| dev_err(par->info->device, | |
| "%s: gpio_request_array(...) failed with %d\n", | |
| __func__, ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| int verify_gpios(struct fbtft_par *par) | |
| { | |
| return 0; | |
| } | |
| void free_gpios(struct fbtft_par *par) | |
| { | |
| fbtft_par_dbg(DEBUG_FREE_GPIOS, par, "%s()\n", __func__); | |
| gpio_free_array(ssd1331_gpios, ARRAY_SIZE(ssd1331_gpios)); | |
| } | |
| static struct fbtft_display display = { | |
| .regwidth = 8, | |
| .width = WIDTH, | |
| .height = TOTALHEIGHT, | |
| .gamma_num = GAMMA_NUM, | |
| .gamma_len = GAMMA_LEN, | |
| .gamma = DEFAULT_GAMMA, | |
| .fbtftops = { | |
| .init_display = init_display, | |
| .reset = reset, | |
| .request_gpios = request_gpios, | |
| .verify_gpios = verify_gpios, | |
| .write_register = write_reg8_bus8, | |
| .set_addr_win = set_addr_win, | |
| .set_gamma = set_gamma, | |
| .blank = blank, | |
| .write_vmem = write_vmem, | |
| .free_gpios = free_gpios, | |
| }, | |
| }; | |
| FBTFT_REGISTER_DRIVER(DRVNAME, &display); | |
| MODULE_ALIAS("spi:" DRVNAME); | |
| MODULE_ALIAS("platform:" DRVNAME); | |
| MODULE_DESCRIPTION("Dual SSD1331 Binocular OLED Driver"); | |
| MODULE_AUTHOR("Alec Smecher (adapted from SSD1351 by James Davies)"); | |
| MODULE_LICENSE("GPL"); |