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++)
187e54c2b0aSBhagyashri Dighole if (vmem16[(y * 8 + i) * xres + x])
188e54c2b0aSBhagyashri 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