1
2
3
4
5
6
7
8
9#include <linux/backlight.h>
10#include <linux/delay.h>
11#include <linux/fb.h>
12#include <linux/i2c.h>
13#include <linux/kernel.h>
14#include <linux/module.h>
15#include <linux/of_device.h>
16#include <linux/of_gpio.h>
17#include <linux/pwm.h>
18#include <linux/uaccess.h>
19
20#define SSD1307FB_DATA 0x40
21#define SSD1307FB_COMMAND 0x80
22
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
29#define SSD1307FB_CONTRAST 0x81
30#define SSD1307FB_CHARGE_PUMP 0x8d
31#define SSD1307FB_SEG_REMAP_ON 0xa1
32#define SSD1307FB_DISPLAY_OFF 0xae
33#define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8
34#define SSD1307FB_DISPLAY_ON 0xaf
35#define SSD1307FB_START_PAGE_ADDRESS 0xb0
36#define SSD1307FB_SET_DISPLAY_OFFSET 0xd3
37#define SSD1307FB_SET_CLOCK_FREQ 0xd5
38#define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9
39#define SSD1307FB_SET_COM_PINS_CONFIG 0xda
40#define SSD1307FB_SET_VCOMH 0xdb
41
42#define MAX_CONTRAST 255
43
44#define REFRESHRATE 1
45
46static u_int refreshrate = REFRESHRATE;
47module_param(refreshrate, uint, 0);
48
49struct ssd1307fb_par;
50
51struct ssd1307fb_deviceinfo {
52 u32 default_vcomh;
53 u32 default_dclk_div;
54 u32 default_dclk_frq;
55 int need_pwm;
56 int need_chargepump;
57};
58
59struct ssd1307fb_par {
60 u32 com_invdir;
61 u32 com_lrremap;
62 u32 com_offset;
63 u32 com_seq;
64 u32 contrast;
65 u32 dclk_div;
66 u32 dclk_frq;
67 struct ssd1307fb_deviceinfo *device_info;
68 struct i2c_client *client;
69 u32 height;
70 struct fb_info *info;
71 u32 page_offset;
72 u32 prechargep1;
73 u32 prechargep2;
74 struct pwm_device *pwm;
75 u32 pwm_period;
76 int reset;
77 u32 seg_remap;
78 u32 vcomh;
79 u32 width;
80};
81
82struct ssd1307fb_array {
83 u8 type;
84 u8 data[0];
85};
86
87static struct fb_fix_screeninfo ssd1307fb_fix = {
88 .id = "Solomon SSD1307",
89 .type = FB_TYPE_PACKED_PIXELS,
90 .visual = FB_VISUAL_MONO10,
91 .xpanstep = 0,
92 .ypanstep = 0,
93 .ywrapstep = 0,
94 .accel = FB_ACCEL_NONE,
95};
96
97static struct fb_var_screeninfo ssd1307fb_var = {
98 .bits_per_pixel = 1,
99};
100
101static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type)
102{
103 struct ssd1307fb_array *array;
104
105 array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL);
106 if (!array)
107 return NULL;
108
109 array->type = type;
110
111 return array;
112}
113
114static int ssd1307fb_write_array(struct i2c_client *client,
115 struct ssd1307fb_array *array, u32 len)
116{
117 int ret;
118
119 len += sizeof(struct ssd1307fb_array);
120
121 ret = i2c_master_send(client, (u8 *)array, len);
122 if (ret != len) {
123 dev_err(&client->dev, "Couldn't send I2C command.\n");
124 return ret;
125 }
126
127 return 0;
128}
129
130static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd)
131{
132 struct ssd1307fb_array *array;
133 int ret;
134
135 array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND);
136 if (!array)
137 return -ENOMEM;
138
139 array->data[0] = cmd;
140
141 ret = ssd1307fb_write_array(client, array, 1);
142 kfree(array);
143
144 return ret;
145}
146
147static void ssd1307fb_update_display(struct ssd1307fb_par *par)
148{
149 struct ssd1307fb_array *array;
150 u8 *vmem = par->info->screen_base;
151 int i, j, k;
152
153 array = ssd1307fb_alloc_array(par->width * par->height / 8,
154 SSD1307FB_DATA);
155 if (!array)
156 return;
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 for (i = 0; i < (par->height / 8); i++) {
188 for (j = 0; j < par->width; j++) {
189 u32 array_idx = i * par->width + j;
190 array->data[array_idx] = 0;
191 for (k = 0; k < 8; k++) {
192 u32 page_length = par->width * i;
193 u32 index = page_length + (par->width * k + j) / 8;
194 u8 byte = *(vmem + index);
195 u8 bit = byte & (1 << (j % 8));
196 bit = bit >> (j % 8);
197 array->data[array_idx] |= bit << k;
198 }
199 }
200 }
201
202 ssd1307fb_write_array(par->client, array, par->width * par->height / 8);
203 kfree(array);
204}
205
206
207static ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf,
208 size_t count, loff_t *ppos)
209{
210 struct ssd1307fb_par *par = info->par;
211 unsigned long total_size;
212 unsigned long p = *ppos;
213 u8 __iomem *dst;
214
215 total_size = info->fix.smem_len;
216
217 if (p > total_size)
218 return -EINVAL;
219
220 if (count + p > total_size)
221 count = total_size - p;
222
223 if (!count)
224 return -EINVAL;
225
226 dst = (void __force *) (info->screen_base + p);
227
228 if (copy_from_user(dst, buf, count))
229 return -EFAULT;
230
231 ssd1307fb_update_display(par);
232
233 *ppos += count;
234
235 return count;
236}
237
238static int ssd1307fb_blank(int blank_mode, struct fb_info *info)
239{
240 struct ssd1307fb_par *par = info->par;
241
242 if (blank_mode != FB_BLANK_UNBLANK)
243 return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF);
244 else
245 return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON);
246}
247
248static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
249{
250 struct ssd1307fb_par *par = info->par;
251 sys_fillrect(info, rect);
252 ssd1307fb_update_display(par);
253}
254
255static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
256{
257 struct ssd1307fb_par *par = info->par;
258 sys_copyarea(info, area);
259 ssd1307fb_update_display(par);
260}
261
262static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image)
263{
264 struct ssd1307fb_par *par = info->par;
265 sys_imageblit(info, image);
266 ssd1307fb_update_display(par);
267}
268
269static struct fb_ops ssd1307fb_ops = {
270 .owner = THIS_MODULE,
271 .fb_read = fb_sys_read,
272 .fb_write = ssd1307fb_write,
273 .fb_blank = ssd1307fb_blank,
274 .fb_fillrect = ssd1307fb_fillrect,
275 .fb_copyarea = ssd1307fb_copyarea,
276 .fb_imageblit = ssd1307fb_imageblit,
277};
278
279static void ssd1307fb_deferred_io(struct fb_info *info,
280 struct list_head *pagelist)
281{
282 ssd1307fb_update_display(info->par);
283}
284
285static int ssd1307fb_init(struct ssd1307fb_par *par)
286{
287 int ret;
288 u32 precharge, dclk, com_invdir, compins;
289
290 if (par->device_info->need_pwm) {
291 par->pwm = pwm_get(&par->client->dev, NULL);
292 if (IS_ERR(par->pwm)) {
293 dev_err(&par->client->dev, "Could not get PWM from device tree!\n");
294 return PTR_ERR(par->pwm);
295 }
296
297 par->pwm_period = pwm_get_period(par->pwm);
298
299 pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period);
300 pwm_enable(par->pwm);
301
302 dev_dbg(&par->client->dev, "Using PWM%d with a %dns period.\n",
303 par->pwm->pwm, par->pwm_period);
304 };
305
306
307 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST);
308 if (ret < 0)
309 return ret;
310
311 ret = ssd1307fb_write_cmd(par->client, par->contrast);
312 if (ret < 0)
313 return ret;
314
315
316 if (par->seg_remap) {
317 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON);
318 if (ret < 0)
319 return ret;
320 };
321
322
323 com_invdir = 0xc0 | (par->com_invdir & 0x1) << 3;
324 ret = ssd1307fb_write_cmd(par->client, com_invdir);
325 if (ret < 0)
326 return ret;
327
328
329 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO);
330 if (ret < 0)
331 return ret;
332
333 ret = ssd1307fb_write_cmd(par->client, par->height - 1);
334 if (ret < 0)
335 return ret;
336
337
338 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET);
339 if (ret < 0)
340 return ret;
341
342 ret = ssd1307fb_write_cmd(par->client, par->com_offset);
343 if (ret < 0)
344 return ret;
345
346
347 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ);
348 if (ret < 0)
349 return ret;
350
351 dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4;
352 ret = ssd1307fb_write_cmd(par->client, dclk);
353 if (ret < 0)
354 return ret;
355
356
357 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD);
358 if (ret < 0)
359 return ret;
360
361 precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4;
362 ret = ssd1307fb_write_cmd(par->client, precharge);
363 if (ret < 0)
364 return ret;
365
366
367 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG);
368 if (ret < 0)
369 return ret;
370
371 compins = 0x02 | !(par->com_seq & 0x1) << 4
372 | (par->com_lrremap & 0x1) << 5;
373 ret = ssd1307fb_write_cmd(par->client, compins);
374 if (ret < 0)
375 return ret;
376
377
378 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH);
379 if (ret < 0)
380 return ret;
381
382 ret = ssd1307fb_write_cmd(par->client, par->vcomh);
383 if (ret < 0)
384 return ret;
385
386
387 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP);
388 if (ret < 0)
389 return ret;
390
391 ret = ssd1307fb_write_cmd(par->client,
392 (par->device_info->need_chargepump & 0x1 << 2) & 0x14);
393 if (ret < 0)
394 return ret;
395
396
397 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE);
398 if (ret < 0)
399 return ret;
400
401 ret = ssd1307fb_write_cmd(par->client,
402 SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL);
403 if (ret < 0)
404 return ret;
405
406
407 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE);
408 if (ret < 0)
409 return ret;
410
411 ret = ssd1307fb_write_cmd(par->client, 0x0);
412 if (ret < 0)
413 return ret;
414
415 ret = ssd1307fb_write_cmd(par->client, par->width - 1);
416 if (ret < 0)
417 return ret;
418
419
420 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE);
421 if (ret < 0)
422 return ret;
423
424 ret = ssd1307fb_write_cmd(par->client, 0x0);
425 if (ret < 0)
426 return ret;
427
428 ret = ssd1307fb_write_cmd(par->client,
429 par->page_offset + (par->height / 8) - 1);
430 if (ret < 0)
431 return ret;
432
433
434 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON);
435 if (ret < 0)
436 return ret;
437
438 return 0;
439}
440
441static int ssd1307fb_update_bl(struct backlight_device *bdev)
442{
443 struct ssd1307fb_par *par = bl_get_data(bdev);
444 int ret;
445 int brightness = bdev->props.brightness;
446
447 par->contrast = brightness;
448
449 ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST);
450 if (ret < 0)
451 return ret;
452 ret = ssd1307fb_write_cmd(par->client, par->contrast);
453 if (ret < 0)
454 return ret;
455 return 0;
456}
457
458static int ssd1307fb_get_brightness(struct backlight_device *bdev)
459{
460 struct ssd1307fb_par *par = bl_get_data(bdev);
461
462 return par->contrast;
463}
464
465static int ssd1307fb_check_fb(struct backlight_device *bdev,
466 struct fb_info *info)
467{
468 return (info->bl_dev == bdev);
469}
470
471static const struct backlight_ops ssd1307fb_bl_ops = {
472 .options = BL_CORE_SUSPENDRESUME,
473 .update_status = ssd1307fb_update_bl,
474 .get_brightness = ssd1307fb_get_brightness,
475 .check_fb = ssd1307fb_check_fb,
476};
477
478static struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = {
479 .default_vcomh = 0x34,
480 .default_dclk_div = 1,
481 .default_dclk_frq = 7,
482};
483
484static struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = {
485 .default_vcomh = 0x20,
486 .default_dclk_div = 1,
487 .default_dclk_frq = 8,
488 .need_chargepump = 1,
489};
490
491static struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = {
492 .default_vcomh = 0x20,
493 .default_dclk_div = 2,
494 .default_dclk_frq = 12,
495 .need_pwm = 1,
496};
497
498static struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = {
499 .default_vcomh = 0x34,
500 .default_dclk_div = 1,
501 .default_dclk_frq = 10,
502};
503
504static const struct of_device_id ssd1307fb_of_match[] = {
505 {
506 .compatible = "solomon,ssd1305fb-i2c",
507 .data = (void *)&ssd1307fb_ssd1305_deviceinfo,
508 },
509 {
510 .compatible = "solomon,ssd1306fb-i2c",
511 .data = (void *)&ssd1307fb_ssd1306_deviceinfo,
512 },
513 {
514 .compatible = "solomon,ssd1307fb-i2c",
515 .data = (void *)&ssd1307fb_ssd1307_deviceinfo,
516 },
517 {
518 .compatible = "solomon,ssd1309fb-i2c",
519 .data = (void *)&ssd1307fb_ssd1309_deviceinfo,
520 },
521 {},
522};
523MODULE_DEVICE_TABLE(of, ssd1307fb_of_match);
524
525static int ssd1307fb_probe(struct i2c_client *client,
526 const struct i2c_device_id *id)
527{
528 struct backlight_device *bl;
529 char bl_name[12];
530 struct fb_info *info;
531 struct device_node *node = client->dev.of_node;
532 struct fb_deferred_io *ssd1307fb_defio;
533 u32 vmem_size;
534 struct ssd1307fb_par *par;
535 u8 *vmem;
536 int ret;
537
538 if (!node) {
539 dev_err(&client->dev, "No device tree data found!\n");
540 return -EINVAL;
541 }
542
543 info = framebuffer_alloc(sizeof(struct ssd1307fb_par), &client->dev);
544 if (!info) {
545 dev_err(&client->dev, "Couldn't allocate framebuffer.\n");
546 return -ENOMEM;
547 }
548
549 par = info->par;
550 par->info = info;
551 par->client = client;
552
553 par->device_info = (struct ssd1307fb_deviceinfo *)of_match_device(
554 ssd1307fb_of_match, &client->dev)->data;
555
556 par->reset = of_get_named_gpio(client->dev.of_node,
557 "reset-gpios", 0);
558 if (!gpio_is_valid(par->reset)) {
559 ret = -EINVAL;
560 goto fb_alloc_error;
561 }
562
563 if (of_property_read_u32(node, "solomon,width", &par->width))
564 par->width = 96;
565
566 if (of_property_read_u32(node, "solomon,height", &par->height))
567 par->height = 16;
568
569 if (of_property_read_u32(node, "solomon,page-offset", &par->page_offset))
570 par->page_offset = 1;
571
572 if (of_property_read_u32(node, "solomon,com-offset", &par->com_offset))
573 par->com_offset = 0;
574
575 if (of_property_read_u32(node, "solomon,prechargep1", &par->prechargep1))
576 par->prechargep1 = 2;
577
578 if (of_property_read_u32(node, "solomon,prechargep2", &par->prechargep2))
579 par->prechargep2 = 2;
580
581 par->seg_remap = !of_property_read_bool(node, "solomon,segment-no-remap");
582 par->com_seq = of_property_read_bool(node, "solomon,com-seq");
583 par->com_lrremap = of_property_read_bool(node, "solomon,com-lrremap");
584 par->com_invdir = of_property_read_bool(node, "solomon,com-invdir");
585
586 par->contrast = 127;
587 par->vcomh = par->device_info->default_vcomh;
588
589
590 par->dclk_div = par->device_info->default_dclk_div;
591 par->dclk_frq = par->device_info->default_dclk_frq;
592
593 vmem_size = par->width * par->height / 8;
594
595 vmem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
596 get_order(vmem_size));
597 if (!vmem) {
598 dev_err(&client->dev, "Couldn't allocate graphical memory.\n");
599 ret = -ENOMEM;
600 goto fb_alloc_error;
601 }
602
603 ssd1307fb_defio = devm_kzalloc(&client->dev, sizeof(struct fb_deferred_io), GFP_KERNEL);
604 if (!ssd1307fb_defio) {
605 dev_err(&client->dev, "Couldn't allocate deferred io.\n");
606 ret = -ENOMEM;
607 goto fb_alloc_error;
608 }
609
610 ssd1307fb_defio->delay = HZ / refreshrate;
611 ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io;
612
613 info->fbops = &ssd1307fb_ops;
614 info->fix = ssd1307fb_fix;
615 info->fix.line_length = par->width / 8;
616 info->fbdefio = ssd1307fb_defio;
617
618 info->var = ssd1307fb_var;
619 info->var.xres = par->width;
620 info->var.xres_virtual = par->width;
621 info->var.yres = par->height;
622 info->var.yres_virtual = par->height;
623
624 info->var.red.length = 1;
625 info->var.red.offset = 0;
626 info->var.green.length = 1;
627 info->var.green.offset = 0;
628 info->var.blue.length = 1;
629 info->var.blue.offset = 0;
630
631 info->screen_base = (u8 __force __iomem *)vmem;
632 info->fix.smem_start = __pa(vmem);
633 info->fix.smem_len = vmem_size;
634
635 fb_deferred_io_init(info);
636
637 ret = devm_gpio_request_one(&client->dev, par->reset,
638 GPIOF_OUT_INIT_HIGH,
639 "oled-reset");
640 if (ret) {
641 dev_err(&client->dev,
642 "failed to request gpio %d: %d\n",
643 par->reset, ret);
644 goto reset_oled_error;
645 }
646
647 i2c_set_clientdata(client, info);
648
649
650 gpio_set_value(par->reset, 0);
651 udelay(4);
652 gpio_set_value(par->reset, 1);
653 udelay(4);
654
655 ret = ssd1307fb_init(par);
656 if (ret)
657 goto reset_oled_error;
658
659 ret = register_framebuffer(info);
660 if (ret) {
661 dev_err(&client->dev, "Couldn't register the framebuffer\n");
662 goto panel_init_error;
663 }
664
665 snprintf(bl_name, sizeof(bl_name), "ssd1307fb%d", info->node);
666 bl = backlight_device_register(bl_name, &client->dev, par,
667 &ssd1307fb_bl_ops, NULL);
668 if (IS_ERR(bl)) {
669 ret = PTR_ERR(bl);
670 dev_err(&client->dev, "unable to register backlight device: %d\n",
671 ret);
672 goto bl_init_error;
673 }
674
675 bl->props.brightness = par->contrast;
676 bl->props.max_brightness = MAX_CONTRAST;
677 info->bl_dev = bl;
678
679 dev_info(&client->dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size);
680
681 return 0;
682
683bl_init_error:
684 unregister_framebuffer(info);
685panel_init_error:
686 if (par->device_info->need_pwm) {
687 pwm_disable(par->pwm);
688 pwm_put(par->pwm);
689 };
690reset_oled_error:
691 fb_deferred_io_cleanup(info);
692fb_alloc_error:
693 framebuffer_release(info);
694 return ret;
695}
696
697static int ssd1307fb_remove(struct i2c_client *client)
698{
699 struct fb_info *info = i2c_get_clientdata(client);
700 struct ssd1307fb_par *par = info->par;
701
702 ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF);
703
704 backlight_device_unregister(info->bl_dev);
705
706 unregister_framebuffer(info);
707 if (par->device_info->need_pwm) {
708 pwm_disable(par->pwm);
709 pwm_put(par->pwm);
710 };
711 fb_deferred_io_cleanup(info);
712 __free_pages(__va(info->fix.smem_start), get_order(info->fix.smem_len));
713 framebuffer_release(info);
714
715 return 0;
716}
717
718static const struct i2c_device_id ssd1307fb_i2c_id[] = {
719 { "ssd1305fb", 0 },
720 { "ssd1306fb", 0 },
721 { "ssd1307fb", 0 },
722 { "ssd1309fb", 0 },
723 { }
724};
725MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id);
726
727static struct i2c_driver ssd1307fb_driver = {
728 .probe = ssd1307fb_probe,
729 .remove = ssd1307fb_remove,
730 .id_table = ssd1307fb_i2c_id,
731 .driver = {
732 .name = "ssd1307fb",
733 .of_match_table = ssd1307fb_of_match,
734 },
735};
736
737module_i2c_driver(ssd1307fb_driver);
738
739MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller");
740MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
741MODULE_LICENSE("GPL");
742