]>
Commit | Line | Data |
---|---|---|
c51cb3f5 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
a2ed00da | 2 | /* |
a895d57d | 3 | * Driver for the Solomon SSD1307 OLED controller |
a2ed00da MR |
4 | * |
5 | * Copyright 2012 Free Electrons | |
a2ed00da MR |
6 | */ |
7 | ||
6ed5e2db | 8 | #include <linux/backlight.h> |
48846ec5 | 9 | #include <linux/delay.h> |
a2ed00da | 10 | #include <linux/fb.h> |
580203f1 | 11 | #include <linux/gpio/consumer.h> |
48846ec5 OS |
12 | #include <linux/i2c.h> |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
72915994 | 15 | #include <linux/property.h> |
a2ed00da | 16 | #include <linux/pwm.h> |
48846ec5 | 17 | #include <linux/uaccess.h> |
ba14301e | 18 | #include <linux/regulator/consumer.h> |
a2ed00da | 19 | |
a2ed00da MR |
20 | #define SSD1307FB_DATA 0x40 |
21 | #define SSD1307FB_COMMAND 0x80 | |
22 | ||
301bc067 MR |
23 | #define SSD1307FB_SET_ADDRESS_MODE 0x20 |
24 | #define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00) | |
25 | #define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01) | |
26 | #define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02) | |
27 | #define SSD1307FB_SET_COL_RANGE 0x21 | |
28 | #define SSD1307FB_SET_PAGE_RANGE 0x22 | |
a2ed00da | 29 | #define SSD1307FB_CONTRAST 0x81 |
de6786be | 30 | #define SSD1307FB_SET_LOOKUP_TABLE 0x91 |
bbc79089 | 31 | #define SSD1307FB_CHARGE_PUMP 0x8d |
a2ed00da MR |
32 | #define SSD1307FB_SEG_REMAP_ON 0xa1 |
33 | #define SSD1307FB_DISPLAY_OFF 0xae | |
bbc79089 | 34 | #define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 |
a2ed00da MR |
35 | #define SSD1307FB_DISPLAY_ON 0xaf |
36 | #define SSD1307FB_START_PAGE_ADDRESS 0xb0 | |
bbc79089 MR |
37 | #define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 |
38 | #define SSD1307FB_SET_CLOCK_FREQ 0xd5 | |
de6786be | 39 | #define SSD1307FB_SET_AREA_COLOR_MODE 0xd8 |
bbc79089 MR |
40 | #define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 |
41 | #define SSD1307FB_SET_COM_PINS_CONFIG 0xda | |
42 | #define SSD1307FB_SET_VCOMH 0xdb | |
43 | ||
6ed5e2db TN |
44 | #define MAX_CONTRAST 255 |
45 | ||
3277e0bb TN |
46 | #define REFRESHRATE 1 |
47 | ||
48 | static u_int refreshrate = REFRESHRATE; | |
49 | module_param(refreshrate, uint, 0); | |
50 | ||
c89eacfc TN |
51 | struct ssd1307fb_deviceinfo { |
52 | u32 default_vcomh; | |
53 | u32 default_dclk_div; | |
54 | u32 default_dclk_frq; | |
55 | int need_pwm; | |
56 | int need_chargepump; | |
bbc79089 | 57 | }; |
a2ed00da MR |
58 | |
59 | struct ssd1307fb_par { | |
de6786be MK |
60 | unsigned area_color_enable : 1; |
61 | unsigned com_invdir : 1; | |
62 | unsigned com_lrremap : 1; | |
63 | unsigned com_seq : 1; | |
64 | unsigned lookup_table_set : 1; | |
65 | unsigned low_power : 1; | |
66 | unsigned seg_remap : 1; | |
c89eacfc | 67 | u32 com_offset; |
c89eacfc TN |
68 | u32 contrast; |
69 | u32 dclk_div; | |
70 | u32 dclk_frq; | |
60169c3f | 71 | const struct ssd1307fb_deviceinfo *device_info; |
a2ed00da | 72 | struct i2c_client *client; |
bbc79089 | 73 | u32 height; |
a2ed00da | 74 | struct fb_info *info; |
de6786be | 75 | u8 lookup_table[4]; |
bbc79089 | 76 | u32 page_offset; |
9ec2832e | 77 | u32 col_offset; |
c89eacfc TN |
78 | u32 prechargep1; |
79 | u32 prechargep2; | |
a2ed00da | 80 | struct pwm_device *pwm; |
72db3335 | 81 | struct gpio_desc *reset; |
ba14301e | 82 | struct regulator *vbat_reg; |
c89eacfc | 83 | u32 vcomh; |
bbc79089 | 84 | u32 width; |
a2ed00da MR |
85 | }; |
86 | ||
9f7714d4 MR |
87 | struct ssd1307fb_array { |
88 | u8 type; | |
451787d3 | 89 | u8 data[]; |
9f7714d4 MR |
90 | }; |
91 | ||
ca9384c5 | 92 | static const struct fb_fix_screeninfo ssd1307fb_fix = { |
a2ed00da MR |
93 | .id = "Solomon SSD1307", |
94 | .type = FB_TYPE_PACKED_PIXELS, | |
95 | .visual = FB_VISUAL_MONO10, | |
96 | .xpanstep = 0, | |
97 | .ypanstep = 0, | |
98 | .ywrapstep = 0, | |
a2ed00da MR |
99 | .accel = FB_ACCEL_NONE, |
100 | }; | |
101 | ||
ca9384c5 | 102 | static const struct fb_var_screeninfo ssd1307fb_var = { |
a2ed00da | 103 | .bits_per_pixel = 1, |
de6786be MK |
104 | .red = { .length = 1 }, |
105 | .green = { .length = 1 }, | |
106 | .blue = { .length = 1 }, | |
a2ed00da MR |
107 | }; |
108 | ||
9f7714d4 | 109 | static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type) |
a2ed00da | 110 | { |
9f7714d4 | 111 | struct ssd1307fb_array *array; |
a2ed00da | 112 | |
9f7714d4 MR |
113 | array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL); |
114 | if (!array) | |
115 | return NULL; | |
a2ed00da | 116 | |
9f7714d4 | 117 | array->type = type; |
a2ed00da | 118 | |
9f7714d4 | 119 | return array; |
a2ed00da MR |
120 | } |
121 | ||
9f7714d4 MR |
122 | static int ssd1307fb_write_array(struct i2c_client *client, |
123 | struct ssd1307fb_array *array, u32 len) | |
a2ed00da | 124 | { |
9f7714d4 MR |
125 | int ret; |
126 | ||
127 | len += sizeof(struct ssd1307fb_array); | |
128 | ||
129 | ret = i2c_master_send(client, (u8 *)array, len); | |
130 | if (ret != len) { | |
131 | dev_err(&client->dev, "Couldn't send I2C command.\n"); | |
132 | return ret; | |
133 | } | |
134 | ||
135 | return 0; | |
a2ed00da MR |
136 | } |
137 | ||
138 | static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) | |
139 | { | |
9f7714d4 MR |
140 | struct ssd1307fb_array *array; |
141 | int ret; | |
a2ed00da | 142 | |
9f7714d4 MR |
143 | array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND); |
144 | if (!array) | |
145 | return -ENOMEM; | |
146 | ||
147 | array->data[0] = cmd; | |
148 | ||
149 | ret = ssd1307fb_write_array(client, array, 1); | |
150 | kfree(array); | |
151 | ||
152 | return ret; | |
a2ed00da MR |
153 | } |
154 | ||
a2ed00da MR |
155 | static void ssd1307fb_update_display(struct ssd1307fb_par *par) |
156 | { | |
301bc067 | 157 | struct ssd1307fb_array *array; |
ed1dc7d5 | 158 | u8 *vmem = par->info->screen_buffer; |
b0020d8a MK |
159 | unsigned int line_length = par->info->fix.line_length; |
160 | unsigned int pages = DIV_ROUND_UP(par->height, 8); | |
a2ed00da MR |
161 | int i, j, k; |
162 | ||
b0020d8a | 163 | array = ssd1307fb_alloc_array(par->width * pages, SSD1307FB_DATA); |
301bc067 MR |
164 | if (!array) |
165 | return; | |
166 | ||
a2ed00da MR |
167 | /* |
168 | * The screen is divided in pages, each having a height of 8 | |
169 | * pixels, and the width of the screen. When sending a byte of | |
170 | * data to the controller, it gives the 8 bits for the current | |
171 | * column. I.e, the first byte are the 8 bits of the first | |
172 | * column, then the 8 bits for the second column, etc. | |
173 | * | |
174 | * | |
175 | * Representation of the screen, assuming it is 5 bits | |
176 | * wide. Each letter-number combination is a bit that controls | |
177 | * one pixel. | |
178 | * | |
179 | * A0 A1 A2 A3 A4 | |
180 | * B0 B1 B2 B3 B4 | |
181 | * C0 C1 C2 C3 C4 | |
182 | * D0 D1 D2 D3 D4 | |
183 | * E0 E1 E2 E3 E4 | |
184 | * F0 F1 F2 F3 F4 | |
185 | * G0 G1 G2 G3 G4 | |
186 | * H0 H1 H2 H3 H4 | |
187 | * | |
188 | * If you want to update this screen, you need to send 5 bytes: | |
189 | * (1) A0 B0 C0 D0 E0 F0 G0 H0 | |
190 | * (2) A1 B1 C1 D1 E1 F1 G1 H1 | |
191 | * (3) A2 B2 C2 D2 E2 F2 G2 H2 | |
192 | * (4) A3 B3 C3 D3 E3 F3 G3 H3 | |
193 | * (5) A4 B4 C4 D4 E4 F4 G4 H4 | |
194 | */ | |
195 | ||
b0020d8a | 196 | for (i = 0; i < pages; i++) { |
bbc79089 | 197 | for (j = 0; j < par->width; j++) { |
b0020d8a | 198 | int m = 8; |
301bc067 MR |
199 | u32 array_idx = i * par->width + j; |
200 | array->data[array_idx] = 0; | |
b0020d8a MK |
201 | /* Last page may be partial */ |
202 | if (i + 1 == pages && par->height % 8) | |
203 | m = par->height % 8; | |
204 | for (k = 0; k < m; k++) { | |
205 | u8 byte = vmem[(8 * i + k) * line_length + | |
206 | j / 8]; | |
207 | u8 bit = (byte >> (j % 8)) & 1; | |
301bc067 | 208 | array->data[array_idx] |= bit << k; |
a2ed00da | 209 | } |
a2ed00da MR |
210 | } |
211 | } | |
301bc067 | 212 | |
b0020d8a | 213 | ssd1307fb_write_array(par->client, array, par->width * pages); |
301bc067 | 214 | kfree(array); |
a2ed00da MR |
215 | } |
216 | ||
217 | ||
218 | static ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf, | |
219 | size_t count, loff_t *ppos) | |
220 | { | |
221 | struct ssd1307fb_par *par = info->par; | |
222 | unsigned long total_size; | |
223 | unsigned long p = *ppos; | |
ed1dc7d5 | 224 | void *dst; |
a2ed00da MR |
225 | |
226 | total_size = info->fix.smem_len; | |
227 | ||
228 | if (p > total_size) | |
229 | return -EINVAL; | |
230 | ||
231 | if (count + p > total_size) | |
232 | count = total_size - p; | |
233 | ||
234 | if (!count) | |
235 | return -EINVAL; | |
236 | ||
ed1dc7d5 | 237 | dst = info->screen_buffer + p; |
a2ed00da MR |
238 | |
239 | if (copy_from_user(dst, buf, count)) | |
240 | return -EFAULT; | |
241 | ||
242 | ssd1307fb_update_display(par); | |
243 | ||
244 | *ppos += count; | |
245 | ||
246 | return count; | |
247 | } | |
248 | ||
550e768c TN |
249 | static int ssd1307fb_blank(int blank_mode, struct fb_info *info) |
250 | { | |
251 | struct ssd1307fb_par *par = info->par; | |
252 | ||
253 | if (blank_mode != FB_BLANK_UNBLANK) | |
254 | return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF); | |
255 | else | |
256 | return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); | |
257 | } | |
258 | ||
a2ed00da MR |
259 | static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) |
260 | { | |
261 | struct ssd1307fb_par *par = info->par; | |
262 | sys_fillrect(info, rect); | |
263 | ssd1307fb_update_display(par); | |
264 | } | |
265 | ||
266 | static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) | |
267 | { | |
268 | struct ssd1307fb_par *par = info->par; | |
269 | sys_copyarea(info, area); | |
270 | ssd1307fb_update_display(par); | |
271 | } | |
272 | ||
273 | static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) | |
274 | { | |
275 | struct ssd1307fb_par *par = info->par; | |
276 | sys_imageblit(info, image); | |
277 | ssd1307fb_update_display(par); | |
278 | } | |
279 | ||
8a48ac33 | 280 | static const struct fb_ops ssd1307fb_ops = { |
a2ed00da MR |
281 | .owner = THIS_MODULE, |
282 | .fb_read = fb_sys_read, | |
283 | .fb_write = ssd1307fb_write, | |
550e768c | 284 | .fb_blank = ssd1307fb_blank, |
a2ed00da MR |
285 | .fb_fillrect = ssd1307fb_fillrect, |
286 | .fb_copyarea = ssd1307fb_copyarea, | |
287 | .fb_imageblit = ssd1307fb_imageblit, | |
288 | }; | |
289 | ||
290 | static void ssd1307fb_deferred_io(struct fb_info *info, | |
291 | struct list_head *pagelist) | |
292 | { | |
293 | ssd1307fb_update_display(info->par); | |
294 | } | |
295 | ||
c89eacfc | 296 | static int ssd1307fb_init(struct ssd1307fb_par *par) |
bbc79089 | 297 | { |
47938236 | 298 | struct pwm_state pwmstate; |
bbc79089 | 299 | int ret; |
c89eacfc | 300 | u32 precharge, dclk, com_invdir, compins; |
bbc79089 | 301 | |
c89eacfc TN |
302 | if (par->device_info->need_pwm) { |
303 | par->pwm = pwm_get(&par->client->dev, NULL); | |
304 | if (IS_ERR(par->pwm)) { | |
305 | dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); | |
306 | return PTR_ERR(par->pwm); | |
307 | } | |
bbc79089 | 308 | |
47938236 AS |
309 | pwm_init_state(par->pwm, &pwmstate); |
310 | pwm_set_relative_duty_cycle(&pwmstate, 50, 100); | |
311 | pwm_apply_state(par->pwm, &pwmstate); | |
717c18f0 | 312 | |
c89eacfc | 313 | /* Enable the PWM */ |
c89eacfc | 314 | pwm_enable(par->pwm); |
bbc79089 | 315 | |
a9d887dc | 316 | dev_dbg(&par->client->dev, "Using PWM%d with a %lluns period.\n", |
47938236 | 317 | par->pwm->pwm, pwm_get_period(par->pwm)); |
6bc94311 | 318 | } |
bbc79089 MR |
319 | |
320 | /* Set initial contrast */ | |
321 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); | |
5b72ae9a PL |
322 | if (ret < 0) |
323 | return ret; | |
324 | ||
c89eacfc | 325 | ret = ssd1307fb_write_cmd(par->client, par->contrast); |
bbc79089 MR |
326 | if (ret < 0) |
327 | return ret; | |
328 | ||
329 | /* Set segment re-map */ | |
c89eacfc TN |
330 | if (par->seg_remap) { |
331 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); | |
332 | if (ret < 0) | |
333 | return ret; | |
6bc94311 | 334 | } |
c89eacfc TN |
335 | |
336 | /* Set COM direction */ | |
de6786be | 337 | com_invdir = 0xc0 | par->com_invdir << 3; |
c89eacfc | 338 | ret = ssd1307fb_write_cmd(par->client, com_invdir); |
bbc79089 MR |
339 | if (ret < 0) |
340 | return ret; | |
341 | ||
342 | /* Set multiplex ratio value */ | |
343 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); | |
5b72ae9a PL |
344 | if (ret < 0) |
345 | return ret; | |
346 | ||
347 | ret = ssd1307fb_write_cmd(par->client, par->height - 1); | |
bbc79089 MR |
348 | if (ret < 0) |
349 | return ret; | |
350 | ||
351 | /* set display offset value */ | |
352 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET); | |
5b72ae9a PL |
353 | if (ret < 0) |
354 | return ret; | |
355 | ||
c89eacfc | 356 | ret = ssd1307fb_write_cmd(par->client, par->com_offset); |
bbc79089 MR |
357 | if (ret < 0) |
358 | return ret; | |
359 | ||
360 | /* Set clock frequency */ | |
361 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ); | |
5b72ae9a PL |
362 | if (ret < 0) |
363 | return ret; | |
364 | ||
c89eacfc TN |
365 | dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4; |
366 | ret = ssd1307fb_write_cmd(par->client, dclk); | |
bbc79089 MR |
367 | if (ret < 0) |
368 | return ret; | |
369 | ||
de6786be MK |
370 | /* Set Set Area Color Mode ON/OFF & Low Power Display Mode */ |
371 | if (par->area_color_enable || par->low_power) { | |
372 | u32 mode; | |
373 | ||
374 | ret = ssd1307fb_write_cmd(par->client, | |
375 | SSD1307FB_SET_AREA_COLOR_MODE); | |
376 | if (ret < 0) | |
377 | return ret; | |
378 | ||
379 | mode = (par->area_color_enable ? 0x30 : 0) | | |
380 | (par->low_power ? 5 : 0); | |
381 | ret = ssd1307fb_write_cmd(par->client, mode); | |
382 | if (ret < 0) | |
383 | return ret; | |
384 | } | |
385 | ||
bbc79089 MR |
386 | /* Set precharge period in number of ticks from the internal clock */ |
387 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD); | |
5b72ae9a PL |
388 | if (ret < 0) |
389 | return ret; | |
390 | ||
c89eacfc TN |
391 | precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4; |
392 | ret = ssd1307fb_write_cmd(par->client, precharge); | |
bbc79089 MR |
393 | if (ret < 0) |
394 | return ret; | |
395 | ||
396 | /* Set COM pins configuration */ | |
397 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG); | |
5b72ae9a PL |
398 | if (ret < 0) |
399 | return ret; | |
400 | ||
de6786be | 401 | compins = 0x02 | !par->com_seq << 4 | par->com_lrremap << 5; |
c89eacfc | 402 | ret = ssd1307fb_write_cmd(par->client, compins); |
bbc79089 MR |
403 | if (ret < 0) |
404 | return ret; | |
405 | ||
406 | /* Set VCOMH */ | |
407 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH); | |
5b72ae9a PL |
408 | if (ret < 0) |
409 | return ret; | |
410 | ||
c89eacfc | 411 | ret = ssd1307fb_write_cmd(par->client, par->vcomh); |
bbc79089 MR |
412 | if (ret < 0) |
413 | return ret; | |
414 | ||
415 | /* Turn on the DC-DC Charge Pump */ | |
416 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP); | |
5b72ae9a PL |
417 | if (ret < 0) |
418 | return ret; | |
419 | ||
c89eacfc | 420 | ret = ssd1307fb_write_cmd(par->client, |
80403b23 | 421 | BIT(4) | (par->device_info->need_chargepump ? BIT(2) : 0)); |
bbc79089 MR |
422 | if (ret < 0) |
423 | return ret; | |
424 | ||
de6786be MK |
425 | /* Set lookup table */ |
426 | if (par->lookup_table_set) { | |
427 | int i; | |
428 | ||
429 | ret = ssd1307fb_write_cmd(par->client, | |
430 | SSD1307FB_SET_LOOKUP_TABLE); | |
431 | if (ret < 0) | |
432 | return ret; | |
433 | ||
434 | for (i = 0; i < ARRAY_SIZE(par->lookup_table); ++i) { | |
435 | u8 val = par->lookup_table[i]; | |
436 | ||
437 | if (val < 31 || val > 63) | |
438 | dev_warn(&par->client->dev, | |
439 | "lookup table index %d value out of range 31 <= %d <= 63\n", | |
440 | i, val); | |
441 | ret = ssd1307fb_write_cmd(par->client, val); | |
442 | if (ret < 0) | |
443 | return ret; | |
444 | } | |
445 | } | |
446 | ||
301bc067 MR |
447 | /* Switch to horizontal addressing mode */ |
448 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); | |
5b72ae9a PL |
449 | if (ret < 0) |
450 | return ret; | |
451 | ||
452 | ret = ssd1307fb_write_cmd(par->client, | |
453 | SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); | |
301bc067 MR |
454 | if (ret < 0) |
455 | return ret; | |
456 | ||
c89eacfc | 457 | /* Set column range */ |
301bc067 | 458 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); |
5b72ae9a PL |
459 | if (ret < 0) |
460 | return ret; | |
461 | ||
9ec2832e | 462 | ret = ssd1307fb_write_cmd(par->client, par->col_offset); |
5b72ae9a PL |
463 | if (ret < 0) |
464 | return ret; | |
465 | ||
9ec2832e | 466 | ret = ssd1307fb_write_cmd(par->client, par->col_offset + par->width - 1); |
301bc067 MR |
467 | if (ret < 0) |
468 | return ret; | |
469 | ||
c89eacfc | 470 | /* Set page range */ |
301bc067 | 471 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); |
5b72ae9a PL |
472 | if (ret < 0) |
473 | return ret; | |
474 | ||
dd978283 | 475 | ret = ssd1307fb_write_cmd(par->client, par->page_offset); |
5b72ae9a PL |
476 | if (ret < 0) |
477 | return ret; | |
478 | ||
479 | ret = ssd1307fb_write_cmd(par->client, | |
b0020d8a MK |
480 | par->page_offset + |
481 | DIV_ROUND_UP(par->height, 8) - 1); | |
301bc067 MR |
482 | if (ret < 0) |
483 | return ret; | |
484 | ||
6e376822 TV |
485 | /* Clear the screen */ |
486 | ssd1307fb_update_display(par); | |
fdde1a81 | 487 | |
bbc79089 MR |
488 | /* Turn on the display */ |
489 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); | |
490 | if (ret < 0) | |
491 | return ret; | |
492 | ||
493 | return 0; | |
494 | } | |
495 | ||
6ed5e2db TN |
496 | static int ssd1307fb_update_bl(struct backlight_device *bdev) |
497 | { | |
498 | struct ssd1307fb_par *par = bl_get_data(bdev); | |
499 | int ret; | |
500 | int brightness = bdev->props.brightness; | |
501 | ||
502 | par->contrast = brightness; | |
503 | ||
504 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); | |
505 | if (ret < 0) | |
506 | return ret; | |
507 | ret = ssd1307fb_write_cmd(par->client, par->contrast); | |
508 | if (ret < 0) | |
509 | return ret; | |
510 | return 0; | |
511 | } | |
512 | ||
513 | static int ssd1307fb_get_brightness(struct backlight_device *bdev) | |
514 | { | |
515 | struct ssd1307fb_par *par = bl_get_data(bdev); | |
516 | ||
517 | return par->contrast; | |
518 | } | |
519 | ||
520 | static int ssd1307fb_check_fb(struct backlight_device *bdev, | |
521 | struct fb_info *info) | |
522 | { | |
523 | return (info->bl_dev == bdev); | |
524 | } | |
525 | ||
526 | static const struct backlight_ops ssd1307fb_bl_ops = { | |
527 | .options = BL_CORE_SUSPENDRESUME, | |
528 | .update_status = ssd1307fb_update_bl, | |
529 | .get_brightness = ssd1307fb_get_brightness, | |
530 | .check_fb = ssd1307fb_check_fb, | |
531 | }; | |
532 | ||
5f2d36b3 TN |
533 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = { |
534 | .default_vcomh = 0x34, | |
535 | .default_dclk_div = 1, | |
536 | .default_dclk_frq = 7, | |
537 | }; | |
538 | ||
c89eacfc TN |
539 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = { |
540 | .default_vcomh = 0x20, | |
541 | .default_dclk_div = 1, | |
542 | .default_dclk_frq = 8, | |
543 | .need_chargepump = 1, | |
544 | }; | |
545 | ||
546 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = { | |
547 | .default_vcomh = 0x20, | |
548 | .default_dclk_div = 2, | |
549 | .default_dclk_frq = 12, | |
550 | .need_pwm = 1, | |
bbc79089 MR |
551 | }; |
552 | ||
3ac58d58 OS |
553 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = { |
554 | .default_vcomh = 0x34, | |
555 | .default_dclk_div = 1, | |
556 | .default_dclk_frq = 10, | |
557 | }; | |
558 | ||
bbc79089 | 559 | static const struct of_device_id ssd1307fb_of_match[] = { |
5f2d36b3 TN |
560 | { |
561 | .compatible = "solomon,ssd1305fb-i2c", | |
562 | .data = (void *)&ssd1307fb_ssd1305_deviceinfo, | |
563 | }, | |
bbc79089 MR |
564 | { |
565 | .compatible = "solomon,ssd1306fb-i2c", | |
c89eacfc | 566 | .data = (void *)&ssd1307fb_ssd1306_deviceinfo, |
bbc79089 MR |
567 | }, |
568 | { | |
569 | .compatible = "solomon,ssd1307fb-i2c", | |
c89eacfc | 570 | .data = (void *)&ssd1307fb_ssd1307_deviceinfo, |
bbc79089 | 571 | }, |
3ac58d58 OS |
572 | { |
573 | .compatible = "solomon,ssd1309fb-i2c", | |
574 | .data = (void *)&ssd1307fb_ssd1309_deviceinfo, | |
575 | }, | |
bbc79089 MR |
576 | {}, |
577 | }; | |
578 | MODULE_DEVICE_TABLE(of, ssd1307fb_of_match); | |
579 | ||
59d961c5 | 580 | static int ssd1307fb_probe(struct i2c_client *client) |
a2ed00da | 581 | { |
fa64c6ba | 582 | struct device *dev = &client->dev; |
6ed5e2db TN |
583 | struct backlight_device *bl; |
584 | char bl_name[12]; | |
a2ed00da | 585 | struct fb_info *info; |
3277e0bb | 586 | struct fb_deferred_io *ssd1307fb_defio; |
bbc79089 | 587 | u32 vmem_size; |
a2ed00da | 588 | struct ssd1307fb_par *par; |
ed1dc7d5 | 589 | void *vmem; |
a2ed00da MR |
590 | int ret; |
591 | ||
fa64c6ba | 592 | info = framebuffer_alloc(sizeof(struct ssd1307fb_par), dev); |
0adcdbcb | 593 | if (!info) |
a2ed00da | 594 | return -ENOMEM; |
a2ed00da | 595 | |
bbc79089 MR |
596 | par = info->par; |
597 | par->info = info; | |
598 | par->client = client; | |
599 | ||
72915994 | 600 | par->device_info = device_get_match_data(dev); |
bbc79089 | 601 | |
fa64c6ba | 602 | par->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); |
72db3335 | 603 | if (IS_ERR(par->reset)) { |
fa64c6ba | 604 | dev_err(dev, "failed to get reset gpio: %ld\n", |
72db3335 JS |
605 | PTR_ERR(par->reset)); |
606 | ret = PTR_ERR(par->reset); | |
bbc79089 MR |
607 | goto fb_alloc_error; |
608 | } | |
609 | ||
fa64c6ba | 610 | par->vbat_reg = devm_regulator_get_optional(dev, "vbat"); |
ba14301e | 611 | if (IS_ERR(par->vbat_reg)) { |
ba14301e | 612 | ret = PTR_ERR(par->vbat_reg); |
cfc5b2b5 BS |
613 | if (ret == -ENODEV) { |
614 | par->vbat_reg = NULL; | |
615 | } else { | |
fa64c6ba | 616 | dev_err(dev, "failed to get VBAT regulator: %d\n", ret); |
cfc5b2b5 BS |
617 | goto fb_alloc_error; |
618 | } | |
ba14301e TV |
619 | } |
620 | ||
72915994 | 621 | if (device_property_read_u32(dev, "solomon,width", &par->width)) |
bbc79089 MR |
622 | par->width = 96; |
623 | ||
72915994 | 624 | if (device_property_read_u32(dev, "solomon,height", &par->height)) |
9561def0 | 625 | par->height = 16; |
bbc79089 | 626 | |
72915994 | 627 | if (device_property_read_u32(dev, "solomon,page-offset", &par->page_offset)) |
bbc79089 MR |
628 | par->page_offset = 1; |
629 | ||
9ec2832e RA |
630 | if (device_property_read_u32(dev, "solomon,col-offset", &par->col_offset)) |
631 | par->col_offset = 0; | |
632 | ||
72915994 | 633 | if (device_property_read_u32(dev, "solomon,com-offset", &par->com_offset)) |
c89eacfc TN |
634 | par->com_offset = 0; |
635 | ||
72915994 | 636 | if (device_property_read_u32(dev, "solomon,prechargep1", &par->prechargep1)) |
c89eacfc TN |
637 | par->prechargep1 = 2; |
638 | ||
72915994 | 639 | if (device_property_read_u32(dev, "solomon,prechargep2", &par->prechargep2)) |
c89eacfc TN |
640 | par->prechargep2 = 2; |
641 | ||
72915994 AS |
642 | if (!device_property_read_u8_array(dev, "solomon,lookup-table", |
643 | par->lookup_table, | |
644 | ARRAY_SIZE(par->lookup_table))) | |
de6786be MK |
645 | par->lookup_table_set = 1; |
646 | ||
72915994 AS |
647 | par->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap"); |
648 | par->com_seq = device_property_read_bool(dev, "solomon,com-seq"); | |
649 | par->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap"); | |
650 | par->com_invdir = device_property_read_bool(dev, "solomon,com-invdir"); | |
de6786be | 651 | par->area_color_enable = |
72915994 AS |
652 | device_property_read_bool(dev, "solomon,area-color-enable"); |
653 | par->low_power = device_property_read_bool(dev, "solomon,low-power"); | |
c89eacfc TN |
654 | |
655 | par->contrast = 127; | |
656 | par->vcomh = par->device_info->default_vcomh; | |
657 | ||
658 | /* Setup display timing */ | |
72915994 | 659 | if (device_property_read_u32(dev, "solomon,dclk-div", &par->dclk_div)) |
de6786be | 660 | par->dclk_div = par->device_info->default_dclk_div; |
72915994 | 661 | if (device_property_read_u32(dev, "solomon,dclk-frq", &par->dclk_frq)) |
de6786be | 662 | par->dclk_frq = par->device_info->default_dclk_frq; |
c89eacfc | 663 | |
b0020d8a | 664 | vmem_size = DIV_ROUND_UP(par->width, 8) * par->height; |
bbc79089 | 665 | |
facd94bc TN |
666 | vmem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, |
667 | get_order(vmem_size)); | |
a2ed00da | 668 | if (!vmem) { |
fa64c6ba | 669 | dev_err(dev, "Couldn't allocate graphical memory.\n"); |
a2ed00da MR |
670 | ret = -ENOMEM; |
671 | goto fb_alloc_error; | |
672 | } | |
673 | ||
fa64c6ba | 674 | ssd1307fb_defio = devm_kzalloc(dev, sizeof(*ssd1307fb_defio), |
3d2ad0a1 | 675 | GFP_KERNEL); |
3277e0bb | 676 | if (!ssd1307fb_defio) { |
fa64c6ba | 677 | dev_err(dev, "Couldn't allocate deferred io.\n"); |
3277e0bb TN |
678 | ret = -ENOMEM; |
679 | goto fb_alloc_error; | |
680 | } | |
681 | ||
682 | ssd1307fb_defio->delay = HZ / refreshrate; | |
683 | ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; | |
684 | ||
a2ed00da MR |
685 | info->fbops = &ssd1307fb_ops; |
686 | info->fix = ssd1307fb_fix; | |
b0020d8a | 687 | info->fix.line_length = DIV_ROUND_UP(par->width, 8); |
3277e0bb | 688 | info->fbdefio = ssd1307fb_defio; |
a2ed00da MR |
689 | |
690 | info->var = ssd1307fb_var; | |
bbc79089 MR |
691 | info->var.xres = par->width; |
692 | info->var.xres_virtual = par->width; | |
693 | info->var.yres = par->height; | |
694 | info->var.yres_virtual = par->height; | |
695 | ||
ed1dc7d5 | 696 | info->screen_buffer = vmem; |
258c0ea2 | 697 | info->fix.smem_start = __pa(vmem); |
a2ed00da MR |
698 | info->fix.smem_len = vmem_size; |
699 | ||
700 | fb_deferred_io_init(info); | |
701 | ||
a2ed00da MR |
702 | i2c_set_clientdata(client, info); |
703 | ||
fdde1a81 JS |
704 | if (par->reset) { |
705 | /* Reset the screen */ | |
64f83a81 BZ |
706 | gpiod_set_value_cansleep(par->reset, 1); |
707 | udelay(4); | |
af4b3a71 MV |
708 | gpiod_set_value_cansleep(par->reset, 0); |
709 | udelay(4); | |
fdde1a81 | 710 | } |
a2ed00da | 711 | |
cfc5b2b5 BS |
712 | if (par->vbat_reg) { |
713 | ret = regulator_enable(par->vbat_reg); | |
714 | if (ret) { | |
fa64c6ba | 715 | dev_err(dev, "failed to enable VBAT: %d\n", ret); |
cfc5b2b5 BS |
716 | goto reset_oled_error; |
717 | } | |
ba14301e TV |
718 | } |
719 | ||
c89eacfc TN |
720 | ret = ssd1307fb_init(par); |
721 | if (ret) | |
ba14301e | 722 | goto regulator_enable_error; |
a2ed00da | 723 | |
bbc79089 MR |
724 | ret = register_framebuffer(info); |
725 | if (ret) { | |
fa64c6ba | 726 | dev_err(dev, "Couldn't register the framebuffer\n"); |
bbc79089 | 727 | goto panel_init_error; |
a2ed00da MR |
728 | } |
729 | ||
6ed5e2db | 730 | snprintf(bl_name, sizeof(bl_name), "ssd1307fb%d", info->node); |
fa64c6ba AS |
731 | bl = backlight_device_register(bl_name, dev, par, &ssd1307fb_bl_ops, |
732 | NULL); | |
6ed5e2db | 733 | if (IS_ERR(bl)) { |
c4e6774d | 734 | ret = PTR_ERR(bl); |
fa64c6ba | 735 | dev_err(dev, "unable to register backlight device: %d\n", ret); |
6ed5e2db TN |
736 | goto bl_init_error; |
737 | } | |
c2b00024 DC |
738 | |
739 | bl->props.brightness = par->contrast; | |
740 | bl->props.max_brightness = MAX_CONTRAST; | |
741 | info->bl_dev = bl; | |
742 | ||
fa64c6ba | 743 | dev_info(dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size); |
a2ed00da MR |
744 | |
745 | return 0; | |
746 | ||
6ed5e2db TN |
747 | bl_init_error: |
748 | unregister_framebuffer(info); | |
bbc79089 | 749 | panel_init_error: |
c89eacfc TN |
750 | if (par->device_info->need_pwm) { |
751 | pwm_disable(par->pwm); | |
752 | pwm_put(par->pwm); | |
6bc94311 | 753 | } |
ba14301e | 754 | regulator_enable_error: |
cfc5b2b5 BS |
755 | if (par->vbat_reg) |
756 | regulator_disable(par->vbat_reg); | |
a2ed00da MR |
757 | reset_oled_error: |
758 | fb_deferred_io_cleanup(info); | |
759 | fb_alloc_error: | |
760 | framebuffer_release(info); | |
761 | return ret; | |
762 | } | |
763 | ||
48c68c4f | 764 | static int ssd1307fb_remove(struct i2c_client *client) |
a2ed00da MR |
765 | { |
766 | struct fb_info *info = i2c_get_clientdata(client); | |
767 | struct ssd1307fb_par *par = info->par; | |
768 | ||
13bad597 TN |
769 | ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF); |
770 | ||
6ed5e2db TN |
771 | backlight_device_unregister(info->bl_dev); |
772 | ||
a2ed00da | 773 | unregister_framebuffer(info); |
c89eacfc TN |
774 | if (par->device_info->need_pwm) { |
775 | pwm_disable(par->pwm); | |
776 | pwm_put(par->pwm); | |
6bc94311 | 777 | } |
ac0c2558 CY |
778 | if (par->vbat_reg) |
779 | regulator_disable(par->vbat_reg); | |
a2ed00da | 780 | fb_deferred_io_cleanup(info); |
facd94bc | 781 | __free_pages(__va(info->fix.smem_start), get_order(info->fix.smem_len)); |
a2ed00da MR |
782 | framebuffer_release(info); |
783 | ||
784 | return 0; | |
785 | } | |
786 | ||
787 | static const struct i2c_device_id ssd1307fb_i2c_id[] = { | |
5f2d36b3 | 788 | { "ssd1305fb", 0 }, |
bbc79089 | 789 | { "ssd1306fb", 0 }, |
a2ed00da | 790 | { "ssd1307fb", 0 }, |
3ac58d58 | 791 | { "ssd1309fb", 0 }, |
a2ed00da MR |
792 | { } |
793 | }; | |
794 | MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); | |
795 | ||
a2ed00da | 796 | static struct i2c_driver ssd1307fb_driver = { |
59d961c5 | 797 | .probe_new = ssd1307fb_probe, |
48c68c4f | 798 | .remove = ssd1307fb_remove, |
a2ed00da MR |
799 | .id_table = ssd1307fb_i2c_id, |
800 | .driver = { | |
801 | .name = "ssd1307fb", | |
d71f8cdf | 802 | .of_match_table = ssd1307fb_of_match, |
a2ed00da MR |
803 | }, |
804 | }; | |
805 | ||
806 | module_i2c_driver(ssd1307fb_driver); | |
807 | ||
a895d57d | 808 | MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller"); |
a2ed00da MR |
809 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); |
810 | MODULE_LICENSE("GPL"); |