Commit | Line | Data |
---|---|---|
39837b91 TP |
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. | |
39837b91 TP |
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 | ||
39837b91 | 29 | /* |
d0b6ecbe AJ |
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 | */ | |
39837b91 TP |
39 | |
40 | /* Init sequence taken from the Adafruit SSD1306 Arduino library */ | |
41 | static int init_display(struct fbtft_par *par) | |
42 | { | |
39837b91 TP |
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 | |
d0b6ecbe AJ |
116 | * 0 in RAM: OFF in display panel |
117 | * 1 in RAM: ON in display panel | |
118 | */ | |
39837b91 TP |
119 | write_reg(par, 0xA6); |
120 | ||
121 | /* Set Display ON */ | |
122 | write_reg(par, 0xAF); | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) | |
128 | { | |
39837b91 TP |
129 | /* Set Lower Column Start Address for Page Addressing Mode */ |
130 | write_reg(par, 0x00 | 0x0); | |
131 | /* Set Higher Column Start Address for Page Addressing Mode */ | |
132 | write_reg(par, 0x10 | 0x0); | |
133 | /* Set Display Start Line */ | |
134 | write_reg(par, 0x40 | 0x0); | |
135 | } | |
136 | ||
137 | static int blank(struct fbtft_par *par, bool on) | |
138 | { | |
139 | fbtft_par_dbg(DEBUG_BLANK, par, "%s(blank=%s)\n", | |
140 | __func__, on ? "true" : "false"); | |
141 | ||
142 | if (on) | |
143 | write_reg(par, 0xAE); | |
144 | else | |
145 | write_reg(par, 0xAF); | |
146 | return 0; | |
147 | } | |
148 | ||
149 | /* Gamma is used to control Contrast */ | |
150 | static int set_gamma(struct fbtft_par *par, unsigned long *curves) | |
151 | { | |
39837b91 TP |
152 | /* apply mask */ |
153 | curves[0] &= 0xFF; | |
154 | ||
155 | /* Set Contrast Control for BANK0 */ | |
156 | write_reg(par, 0x81); | |
157 | write_reg(par, curves[0]); | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
162 | static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) | |
163 | { | |
4b6dc179 | 164 | u16 *vmem16 = (u16 *)par->info->screen_buffer; |
39837b91 TP |
165 | u8 *buf = par->txbuf.buf; |
166 | int x, y, i; | |
167 | int ret = 0; | |
168 | ||
39837b91 TP |
169 | for (x = 0; x < par->info->var.xres; x++) { |
170 | for (y = 0; y < par->info->var.yres/8; y++) { | |
171 | *buf = 0x00; | |
172 | for (i = 0; i < 8; i++) | |
94c0a544 AB |
173 | *buf |= (vmem16[(y * 8 + i) * |
174 | par->info->var.xres + x] ? | |
175 | 1 : 0) << i; | |
39837b91 TP |
176 | buf++; |
177 | } | |
178 | } | |
179 | ||
180 | /* Write data */ | |
181 | gpio_set_value(par->gpio.dc, 1); | |
182 | ret = par->fbtftops.write(par, par->txbuf.buf, | |
94c0a544 AB |
183 | par->info->var.xres * par->info->var.yres / |
184 | 8); | |
39837b91 | 185 | if (ret < 0) |
aed1c72e HM |
186 | dev_err(par->info->device, "write failed and returned: %d\n", |
187 | ret); | |
39837b91 TP |
188 | |
189 | return ret; | |
190 | } | |
191 | ||
39837b91 TP |
192 | static struct fbtft_display display = { |
193 | .regwidth = 8, | |
194 | .width = WIDTH, | |
195 | .height = HEIGHT, | |
196 | .gamma_num = 1, | |
197 | .gamma_len = 1, | |
198 | .gamma = "00", | |
199 | .fbtftops = { | |
200 | .write_vmem = write_vmem, | |
201 | .init_display = init_display, | |
202 | .set_addr_win = set_addr_win, | |
203 | .blank = blank, | |
204 | .set_gamma = set_gamma, | |
205 | }, | |
206 | }; | |
207 | ||
39837b91 TP |
208 | FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1306", &display); |
209 | ||
210 | MODULE_ALIAS("spi:" DRVNAME); | |
211 | MODULE_ALIAS("platform:" DRVNAME); | |
212 | MODULE_ALIAS("spi:ssd1306"); | |
213 | MODULE_ALIAS("platform:ssd1306"); | |
214 | ||
215 | MODULE_DESCRIPTION("SSD1306 OLED Driver"); | |
216 | MODULE_AUTHOR("Noralf Tronnes"); | |
217 | MODULE_LICENSE("GPL"); |