forked from notro/fbtft
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add dual SSD1331 binocular display driver
- Loading branch information
Showing
4 changed files
with
349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,330 @@ | ||
#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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters