fb_ssd1306.c (5093B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * FB driver for the SSD1306 OLED Controller 4 * 5 * Copyright (C) 2013 Noralf Tronnes 6 */ 7 8#include <linux/module.h> 9#include <linux/kernel.h> 10#include <linux/init.h> 11#include <linux/gpio/consumer.h> 12#include <linux/delay.h> 13 14#include "fbtft.h" 15 16#define DRVNAME "fb_ssd1306" 17#define WIDTH 128 18#define HEIGHT 64 19 20/* 21 * write_reg() caveat: 22 * 23 * This doesn't work because D/C has to be LOW for both values: 24 * write_reg(par, val1, val2); 25 * 26 * Do it like this: 27 * write_reg(par, val1); 28 * write_reg(par, val2); 29 */ 30 31/* Init sequence taken from the Adafruit SSD1306 Arduino library */ 32static int init_display(struct fbtft_par *par) 33{ 34 par->fbtftops.reset(par); 35 36 if (par->gamma.curves[0] == 0) { 37 mutex_lock(&par->gamma.lock); 38 if (par->info->var.yres == 64) 39 par->gamma.curves[0] = 0xCF; 40 else 41 par->gamma.curves[0] = 0x8F; 42 mutex_unlock(&par->gamma.lock); 43 } 44 45 /* Set Display OFF */ 46 write_reg(par, 0xAE); 47 48 /* Set Display Clock Divide Ratio/ Oscillator Frequency */ 49 write_reg(par, 0xD5); 50 write_reg(par, 0x80); 51 52 /* Set Multiplex Ratio */ 53 write_reg(par, 0xA8); 54 if (par->info->var.yres == 64) 55 write_reg(par, 0x3F); 56 else if (par->info->var.yres == 48) 57 write_reg(par, 0x2F); 58 else 59 write_reg(par, 0x1F); 60 61 /* Set Display Offset */ 62 write_reg(par, 0xD3); 63 write_reg(par, 0x0); 64 65 /* Set Display Start Line */ 66 write_reg(par, 0x40 | 0x0); 67 68 /* Charge Pump Setting */ 69 write_reg(par, 0x8D); 70 /* A[2] = 1b, Enable charge pump during display on */ 71 write_reg(par, 0x14); 72 73 /* Set Memory Addressing Mode */ 74 write_reg(par, 0x20); 75 /* Vertical addressing mode */ 76 write_reg(par, 0x01); 77 78 /* Set Segment Re-map */ 79 /* column address 127 is mapped to SEG0 */ 80 write_reg(par, 0xA0 | 0x1); 81 82 /* Set COM Output Scan Direction */ 83 /* remapped mode. Scan from COM[N-1] to COM0 */ 84 write_reg(par, 0xC8); 85 86 /* Set COM Pins Hardware Configuration */ 87 write_reg(par, 0xDA); 88 if (par->info->var.yres == 64) 89 /* A[4]=1b, Alternative COM pin configuration */ 90 write_reg(par, 0x12); 91 else if (par->info->var.yres == 48) 92 /* A[4]=1b, Alternative COM pin configuration */ 93 write_reg(par, 0x12); 94 else 95 /* A[4]=0b, Sequential COM pin configuration */ 96 write_reg(par, 0x02); 97 98 /* Set Pre-charge Period */ 99 write_reg(par, 0xD9); 100 write_reg(par, 0xF1); 101 102 /* Set VCOMH Deselect Level */ 103 write_reg(par, 0xDB); 104 /* according to the datasheet, this value is out of bounds */ 105 write_reg(par, 0x40); 106 107 /* Entire Display ON */ 108 /* Resume to RAM content display. Output follows RAM content */ 109 write_reg(par, 0xA4); 110 111 /* Set Normal Display 112 * 0 in RAM: OFF in display panel 113 * 1 in RAM: ON in display panel 114 */ 115 write_reg(par, 0xA6); 116 117 /* Set Display ON */ 118 write_reg(par, 0xAF); 119 120 return 0; 121} 122 123static void set_addr_win_64x48(struct fbtft_par *par) 124{ 125 /* Set Column Address */ 126 write_reg(par, 0x21); 127 write_reg(par, 0x20); 128 write_reg(par, 0x5F); 129 130 /* Set Page Address */ 131 write_reg(par, 0x22); 132 write_reg(par, 0x0); 133 write_reg(par, 0x5); 134} 135 136static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) 137{ 138 /* Set Lower Column Start Address for Page Addressing Mode */ 139 write_reg(par, 0x00 | 0x0); 140 /* Set Higher Column Start Address for Page Addressing Mode */ 141 write_reg(par, 0x10 | 0x0); 142 /* Set Display Start Line */ 143 write_reg(par, 0x40 | 0x0); 144 145 if (par->info->var.xres == 64 && par->info->var.yres == 48) 146 set_addr_win_64x48(par); 147} 148 149static int blank(struct fbtft_par *par, bool on) 150{ 151 fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n", 152 __func__, on ? "true" : "false"); 153 154 if (on) 155 write_reg(par, 0xAE); 156 else 157 write_reg(par, 0xAF); 158 return 0; 159} 160 161/* Gamma is used to control Contrast */ 162static int set_gamma(struct fbtft_par *par, u32 *curves) 163{ 164 /* apply mask */ 165 curves[0] &= 0xFF; 166 167 /* Set Contrast Control for BANK0 */ 168 write_reg(par, 0x81); 169 write_reg(par, curves[0]); 170 171 return 0; 172} 173 174static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) 175{ 176 u16 *vmem16 = (u16 *)par->info->screen_buffer; 177 u32 xres = par->info->var.xres; 178 u32 yres = par->info->var.yres; 179 u8 *buf = par->txbuf.buf; 180 int x, y, i; 181 int ret = 0; 182 183 for (x = 0; x < xres; x++) { 184 for (y = 0; y < yres / 8; y++) { 185 *buf = 0x00; 186 for (i = 0; i < 8; i++) 187 if (vmem16[(y * 8 + i) * xres + x]) 188 *buf |= BIT(i); 189 buf++; 190 } 191 } 192 193 /* Write data */ 194 gpiod_set_value(par->gpio.dc, 1); 195 ret = par->fbtftops.write(par, par->txbuf.buf, xres * yres / 8); 196 if (ret < 0) 197 dev_err(par->info->device, "write failed and returned: %d\n", 198 ret); 199 200 return ret; 201} 202 203static struct fbtft_display display = { 204 .regwidth = 8, 205 .width = WIDTH, 206 .height = HEIGHT, 207 .gamma_num = 1, 208 .gamma_len = 1, 209 .gamma = "00", 210 .fbtftops = { 211 .write_vmem = write_vmem, 212 .init_display = init_display, 213 .set_addr_win = set_addr_win, 214 .blank = blank, 215 .set_gamma = set_gamma, 216 }, 217}; 218 219FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1306", &display); 220 221MODULE_ALIAS("spi:" DRVNAME); 222MODULE_ALIAS("platform:" DRVNAME); 223MODULE_ALIAS("spi:ssd1306"); 224MODULE_ALIAS("platform:ssd1306"); 225 226MODULE_DESCRIPTION("SSD1306 OLED Driver"); 227MODULE_AUTHOR("Noralf Tronnes"); 228MODULE_LICENSE("GPL");