xref: /linux/drivers/staging/fbtft/fb_ssd1306.c (revision e54c2b0a)
1783de57cSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
239837b91SThomas Petazzoni /*
339837b91SThomas Petazzoni  * FB driver for the SSD1306 OLED Controller
439837b91SThomas Petazzoni  *
539837b91SThomas Petazzoni  * Copyright (C) 2013 Noralf Tronnes
639837b91SThomas Petazzoni  */
739837b91SThomas Petazzoni 
839837b91SThomas Petazzoni #include <linux/module.h>
939837b91SThomas Petazzoni #include <linux/kernel.h>
1039837b91SThomas Petazzoni #include <linux/init.h>
11c440eee1SNishad Kamdar #include <linux/gpio/consumer.h>
1239837b91SThomas Petazzoni #include <linux/delay.h>
1339837b91SThomas Petazzoni 
1439837b91SThomas Petazzoni #include "fbtft.h"
1539837b91SThomas Petazzoni 
1639837b91SThomas Petazzoni #define DRVNAME		"fb_ssd1306"
1739837b91SThomas Petazzoni #define WIDTH		128
1839837b91SThomas Petazzoni #define HEIGHT		64
1939837b91SThomas Petazzoni 
2039837b91SThomas Petazzoni /*
21d0b6ecbeSAnson Jacob  * write_reg() caveat:
22d0b6ecbeSAnson Jacob  *
23d0b6ecbeSAnson Jacob  * This doesn't work because D/C has to be LOW for both values:
24d0b6ecbeSAnson Jacob  * write_reg(par, val1, val2);
25d0b6ecbeSAnson Jacob  *
26d0b6ecbeSAnson Jacob  * Do it like this:
27d0b6ecbeSAnson Jacob  * write_reg(par, val1);
28d0b6ecbeSAnson Jacob  * write_reg(par, val2);
2939837b91SThomas Petazzoni  */
3039837b91SThomas Petazzoni 
3139837b91SThomas Petazzoni /* Init sequence taken from the Adafruit SSD1306 Arduino library */
init_display(struct fbtft_par * par)3239837b91SThomas Petazzoni static int init_display(struct fbtft_par *par)
3339837b91SThomas Petazzoni {
3439837b91SThomas Petazzoni 	par->fbtftops.reset(par);
3539837b91SThomas Petazzoni 
3639837b91SThomas Petazzoni 	if (par->gamma.curves[0] == 0) {
3739837b91SThomas Petazzoni 		mutex_lock(&par->gamma.lock);
3839837b91SThomas Petazzoni 		if (par->info->var.yres == 64)
3939837b91SThomas Petazzoni 			par->gamma.curves[0] = 0xCF;
4039837b91SThomas Petazzoni 		else
4139837b91SThomas Petazzoni 			par->gamma.curves[0] = 0x8F;
4239837b91SThomas Petazzoni 		mutex_unlock(&par->gamma.lock);
4339837b91SThomas Petazzoni 	}
4439837b91SThomas Petazzoni 
4539837b91SThomas Petazzoni 	/* Set Display OFF */
4639837b91SThomas Petazzoni 	write_reg(par, 0xAE);
4739837b91SThomas Petazzoni 
4839837b91SThomas Petazzoni 	/* Set Display Clock Divide Ratio/ Oscillator Frequency */
4939837b91SThomas Petazzoni 	write_reg(par, 0xD5);
5039837b91SThomas Petazzoni 	write_reg(par, 0x80);
5139837b91SThomas Petazzoni 
5239837b91SThomas Petazzoni 	/* Set Multiplex Ratio */
5339837b91SThomas Petazzoni 	write_reg(par, 0xA8);
5439837b91SThomas Petazzoni 	if (par->info->var.yres == 64)
5539837b91SThomas Petazzoni 		write_reg(par, 0x3F);
5695ecde0fSAndy Shevchenko 	else if (par->info->var.yres == 48)
5795ecde0fSAndy Shevchenko 		write_reg(par, 0x2F);
5839837b91SThomas Petazzoni 	else
5939837b91SThomas Petazzoni 		write_reg(par, 0x1F);
6039837b91SThomas Petazzoni 
6139837b91SThomas Petazzoni 	/* Set Display Offset */
6239837b91SThomas Petazzoni 	write_reg(par, 0xD3);
6339837b91SThomas Petazzoni 	write_reg(par, 0x0);
6439837b91SThomas Petazzoni 
6539837b91SThomas Petazzoni 	/* Set Display Start Line */
6639837b91SThomas Petazzoni 	write_reg(par, 0x40 | 0x0);
6739837b91SThomas Petazzoni 
6839837b91SThomas Petazzoni 	/* Charge Pump Setting */
6939837b91SThomas Petazzoni 	write_reg(par, 0x8D);
7039837b91SThomas Petazzoni 	/* A[2] = 1b, Enable charge pump during display on */
7139837b91SThomas Petazzoni 	write_reg(par, 0x14);
7239837b91SThomas Petazzoni 
7339837b91SThomas Petazzoni 	/* Set Memory Addressing Mode */
7439837b91SThomas Petazzoni 	write_reg(par, 0x20);
7539837b91SThomas Petazzoni 	/* Vertical addressing mode  */
7639837b91SThomas Petazzoni 	write_reg(par, 0x01);
7739837b91SThomas Petazzoni 
7839837b91SThomas Petazzoni 	/* Set Segment Re-map */
7939837b91SThomas Petazzoni 	/* column address 127 is mapped to SEG0 */
8039837b91SThomas Petazzoni 	write_reg(par, 0xA0 | 0x1);
8139837b91SThomas Petazzoni 
8239837b91SThomas Petazzoni 	/* Set COM Output Scan Direction */
8339837b91SThomas Petazzoni 	/* remapped mode. Scan from COM[N-1] to COM0 */
8439837b91SThomas Petazzoni 	write_reg(par, 0xC8);
8539837b91SThomas Petazzoni 
8639837b91SThomas Petazzoni 	/* Set COM Pins Hardware Configuration */
8739837b91SThomas Petazzoni 	write_reg(par, 0xDA);
8839837b91SThomas Petazzoni 	if (par->info->var.yres == 64)
8939837b91SThomas Petazzoni 		/* A[4]=1b, Alternative COM pin configuration */
9039837b91SThomas Petazzoni 		write_reg(par, 0x12);
9195ecde0fSAndy Shevchenko 	else if (par->info->var.yres == 48)
9295ecde0fSAndy Shevchenko 		/* A[4]=1b, Alternative COM pin configuration */
9395ecde0fSAndy Shevchenko 		write_reg(par, 0x12);
9439837b91SThomas Petazzoni 	else
9539837b91SThomas Petazzoni 		/* A[4]=0b, Sequential COM pin configuration */
9639837b91SThomas Petazzoni 		write_reg(par, 0x02);
9739837b91SThomas Petazzoni 
9839837b91SThomas Petazzoni 	/* Set Pre-charge Period */
9939837b91SThomas Petazzoni 	write_reg(par, 0xD9);
10039837b91SThomas Petazzoni 	write_reg(par, 0xF1);
10139837b91SThomas Petazzoni 
10239837b91SThomas Petazzoni 	/* Set VCOMH Deselect Level */
10339837b91SThomas Petazzoni 	write_reg(par, 0xDB);
10439837b91SThomas Petazzoni 	/* according to the datasheet, this value is out of bounds */
10539837b91SThomas Petazzoni 	write_reg(par, 0x40);
10639837b91SThomas Petazzoni 
10739837b91SThomas Petazzoni 	/* Entire Display ON */
10839837b91SThomas Petazzoni 	/* Resume to RAM content display. Output follows RAM content */
10939837b91SThomas Petazzoni 	write_reg(par, 0xA4);
11039837b91SThomas Petazzoni 
11139837b91SThomas Petazzoni 	/* Set Normal Display
112d0b6ecbeSAnson Jacob 	 * 0 in RAM: OFF in display panel
113d0b6ecbeSAnson Jacob 	 * 1 in RAM: ON in display panel
114d0b6ecbeSAnson Jacob 	 */
11539837b91SThomas Petazzoni 	write_reg(par, 0xA6);
11639837b91SThomas Petazzoni 
11739837b91SThomas Petazzoni 	/* Set Display ON */
11839837b91SThomas Petazzoni 	write_reg(par, 0xAF);
11939837b91SThomas Petazzoni 
12039837b91SThomas Petazzoni 	return 0;
12139837b91SThomas Petazzoni }
12239837b91SThomas Petazzoni 
set_addr_win_64x48(struct fbtft_par * par)12395ecde0fSAndy Shevchenko static void set_addr_win_64x48(struct fbtft_par *par)
12495ecde0fSAndy Shevchenko {
12595ecde0fSAndy Shevchenko 	/* Set Column Address */
12695ecde0fSAndy Shevchenko 	write_reg(par, 0x21);
12795ecde0fSAndy Shevchenko 	write_reg(par, 0x20);
12895ecde0fSAndy Shevchenko 	write_reg(par, 0x5F);
12995ecde0fSAndy Shevchenko 
13095ecde0fSAndy Shevchenko 	/* Set Page Address */
13195ecde0fSAndy Shevchenko 	write_reg(par, 0x22);
13295ecde0fSAndy Shevchenko 	write_reg(par, 0x0);
13395ecde0fSAndy Shevchenko 	write_reg(par, 0x5);
13495ecde0fSAndy Shevchenko }
13595ecde0fSAndy Shevchenko 
set_addr_win(struct fbtft_par * par,int xs,int ys,int xe,int ye)13639837b91SThomas Petazzoni static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
13739837b91SThomas Petazzoni {
13839837b91SThomas Petazzoni 	/* Set Lower Column Start Address for Page Addressing Mode */
13939837b91SThomas Petazzoni 	write_reg(par, 0x00 | 0x0);
14039837b91SThomas Petazzoni 	/* Set Higher Column Start Address for Page Addressing Mode */
14139837b91SThomas Petazzoni 	write_reg(par, 0x10 | 0x0);
14239837b91SThomas Petazzoni 	/* Set Display Start Line */
14339837b91SThomas Petazzoni 	write_reg(par, 0x40 | 0x0);
14495ecde0fSAndy Shevchenko 
14595ecde0fSAndy Shevchenko 	if (par->info->var.xres == 64 && par->info->var.yres == 48)
14695ecde0fSAndy Shevchenko 		set_addr_win_64x48(par);
14739837b91SThomas Petazzoni }
14839837b91SThomas Petazzoni 
blank(struct fbtft_par * par,bool on)14939837b91SThomas Petazzoni static int blank(struct fbtft_par *par, bool on)
15039837b91SThomas Petazzoni {
1511315e8baSLeonardo Brás 	fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
15239837b91SThomas Petazzoni 		      __func__, on ? "true" : "false");
15339837b91SThomas Petazzoni 
15439837b91SThomas Petazzoni 	if (on)
15539837b91SThomas Petazzoni 		write_reg(par, 0xAE);
15639837b91SThomas Petazzoni 	else
15739837b91SThomas Petazzoni 		write_reg(par, 0xAF);
15839837b91SThomas Petazzoni 	return 0;
15939837b91SThomas Petazzoni }
16039837b91SThomas Petazzoni 
16139837b91SThomas Petazzoni /* Gamma is used to control Contrast */
set_gamma(struct fbtft_par * par,u32 * curves)16222eb36b8SArnd Bergmann static int set_gamma(struct fbtft_par *par, u32 *curves)
16339837b91SThomas Petazzoni {
16439837b91SThomas Petazzoni 	/* apply mask */
16539837b91SThomas Petazzoni 	curves[0] &= 0xFF;
16639837b91SThomas Petazzoni 
16739837b91SThomas Petazzoni 	/* Set Contrast Control for BANK0 */
16839837b91SThomas Petazzoni 	write_reg(par, 0x81);
16939837b91SThomas Petazzoni 	write_reg(par, curves[0]);
17039837b91SThomas Petazzoni 
17139837b91SThomas Petazzoni 	return 0;
17239837b91SThomas Petazzoni }
17339837b91SThomas Petazzoni 
write_vmem(struct fbtft_par * par,size_t offset,size_t len)17439837b91SThomas Petazzoni static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
17539837b91SThomas Petazzoni {
1764b6dc179SLars Svensson 	u16 *vmem16 = (u16 *)par->info->screen_buffer;
17709249ecdSAndy Shevchenko 	u32 xres = par->info->var.xres;
17809249ecdSAndy Shevchenko 	u32 yres = par->info->var.yres;
17939837b91SThomas Petazzoni 	u8 *buf = par->txbuf.buf;
18039837b91SThomas Petazzoni 	int x, y, i;
18139837b91SThomas Petazzoni 	int ret = 0;
18239837b91SThomas Petazzoni 
18309249ecdSAndy Shevchenko 	for (x = 0; x < xres; x++) {
18409249ecdSAndy Shevchenko 		for (y = 0; y < yres / 8; y++) {
18539837b91SThomas Petazzoni 			*buf = 0x00;
18639837b91SThomas Petazzoni 			for (i = 0; i < 8; i++)
187*e54c2b0aSBhagyashri Dighole 				if (vmem16[(y * 8 + i) * xres + x])
188*e54c2b0aSBhagyashri Dighole 					*buf |= BIT(i);
18939837b91SThomas Petazzoni 			buf++;
19039837b91SThomas Petazzoni 		}
19139837b91SThomas Petazzoni 	}
19239837b91SThomas Petazzoni 
19339837b91SThomas Petazzoni 	/* Write data */
194c440eee1SNishad Kamdar 	gpiod_set_value(par->gpio.dc, 1);
19509249ecdSAndy Shevchenko 	ret = par->fbtftops.write(par, par->txbuf.buf, xres * yres / 8);
19639837b91SThomas Petazzoni 	if (ret < 0)
197aed1c72eSHaneen Mohammed 		dev_err(par->info->device, "write failed and returned: %d\n",
198aed1c72eSHaneen Mohammed 			ret);
19939837b91SThomas Petazzoni 
20039837b91SThomas Petazzoni 	return ret;
20139837b91SThomas Petazzoni }
20239837b91SThomas Petazzoni 
20339837b91SThomas Petazzoni static struct fbtft_display display = {
20439837b91SThomas Petazzoni 	.regwidth = 8,
20539837b91SThomas Petazzoni 	.width = WIDTH,
20639837b91SThomas Petazzoni 	.height = HEIGHT,
20739837b91SThomas Petazzoni 	.gamma_num = 1,
20839837b91SThomas Petazzoni 	.gamma_len = 1,
20939837b91SThomas Petazzoni 	.gamma = "00",
21039837b91SThomas Petazzoni 	.fbtftops = {
21139837b91SThomas Petazzoni 		.write_vmem = write_vmem,
21239837b91SThomas Petazzoni 		.init_display = init_display,
21339837b91SThomas Petazzoni 		.set_addr_win = set_addr_win,
21439837b91SThomas Petazzoni 		.blank = blank,
21539837b91SThomas Petazzoni 		.set_gamma = set_gamma,
21639837b91SThomas Petazzoni 	},
21739837b91SThomas Petazzoni };
21839837b91SThomas Petazzoni 
21939837b91SThomas Petazzoni FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1306", &display);
22039837b91SThomas Petazzoni 
22139837b91SThomas Petazzoni MODULE_ALIAS("spi:" DRVNAME);
22239837b91SThomas Petazzoni MODULE_ALIAS("platform:" DRVNAME);
22339837b91SThomas Petazzoni MODULE_ALIAS("spi:ssd1306");
22439837b91SThomas Petazzoni MODULE_ALIAS("platform:ssd1306");
22539837b91SThomas Petazzoni 
22639837b91SThomas Petazzoni MODULE_DESCRIPTION("SSD1306 OLED Driver");
22739837b91SThomas Petazzoni MODULE_AUTHOR("Noralf Tronnes");
22839837b91SThomas Petazzoni MODULE_LICENSE("GPL");
229