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