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