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