linux/drivers/staging/fbtft/fb_ssd1306.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * FB driver for the SSD1306 OLED Controller
   4 *
   5 * Copyright (C) 2013 Noralf Tronnes
   6 */
   7
   8#include <linux/module.h>
   9#include <linux/kernel.h>
  10#include <linux/init.h>
  11#include <linux/gpio/consumer.h>
  12#include <linux/delay.h>
  13
  14#include "fbtft.h"
  15
  16#define DRVNAME         "fb_ssd1306"
  17#define WIDTH           128
  18#define HEIGHT          64
  19
  20/*
  21 * write_reg() caveat:
  22 *
  23 * This doesn't work because D/C has to be LOW for both values:
  24 * write_reg(par, val1, val2);
  25 *
  26 * Do it like this:
  27 * write_reg(par, val1);
  28 * write_reg(par, val2);
  29 */
  30
  31/* Init sequence taken from the Adafruit SSD1306 Arduino library */
  32static int init_display(struct fbtft_par *par)
  33{
  34        par->fbtftops.reset(par);
  35
  36        if (par->gamma.curves[0] == 0) {
  37                mutex_lock(&par->gamma.lock);
  38                if (par->info->var.yres == 64)
  39                        par->gamma.curves[0] = 0xCF;
  40                else
  41                        par->gamma.curves[0] = 0x8F;
  42                mutex_unlock(&par->gamma.lock);
  43        }
  44
  45        /* Set Display OFF */
  46        write_reg(par, 0xAE);
  47
  48        /* Set Display Clock Divide Ratio/ Oscillator Frequency */
  49        write_reg(par, 0xD5);
  50        write_reg(par, 0x80);
  51
  52        /* Set Multiplex Ratio */
  53        write_reg(par, 0xA8);
  54        if (par->info->var.yres == 64)
  55                write_reg(par, 0x3F);
  56        else if (par->info->var.yres == 48)
  57                write_reg(par, 0x2F);
  58        else
  59                write_reg(par, 0x1F);
  60
  61        /* Set Display Offset */
  62        write_reg(par, 0xD3);
  63        write_reg(par, 0x0);
  64
  65        /* Set Display Start Line */
  66        write_reg(par, 0x40 | 0x0);
  67
  68        /* Charge Pump Setting */
  69        write_reg(par, 0x8D);
  70        /* A[2] = 1b, Enable charge pump during display on */
  71        write_reg(par, 0x14);
  72
  73        /* Set Memory Addressing Mode */
  74        write_reg(par, 0x20);
  75        /* Vertical addressing mode  */
  76        write_reg(par, 0x01);
  77
  78        /* Set Segment Re-map */
  79        /* column address 127 is mapped to SEG0 */
  80        write_reg(par, 0xA0 | 0x1);
  81
  82        /* Set COM Output Scan Direction */
  83        /* remapped mode. Scan from COM[N-1] to COM0 */
  84        write_reg(par, 0xC8);
  85
  86        /* Set COM Pins Hardware Configuration */
  87        write_reg(par, 0xDA);
  88        if (par->info->var.yres == 64)
  89                /* A[4]=1b, Alternative COM pin configuration */
  90                write_reg(par, 0x12);
  91        else if (par->info->var.yres == 48)
  92                /* A[4]=1b, Alternative COM pin configuration */
  93                write_reg(par, 0x12);
  94        else
  95                /* A[4]=0b, Sequential COM pin configuration */
  96                write_reg(par, 0x02);
  97
  98        /* Set Pre-charge Period */
  99        write_reg(par, 0xD9);
 100        write_reg(par, 0xF1);
 101
 102        /* Set VCOMH Deselect Level */
 103        write_reg(par, 0xDB);
 104        /* according to the datasheet, this value is out of bounds */
 105        write_reg(par, 0x40);
 106
 107        /* Entire Display ON */
 108        /* Resume to RAM content display. Output follows RAM content */
 109        write_reg(par, 0xA4);
 110
 111        /* Set Normal Display
 112         * 0 in RAM: OFF in display panel
 113         * 1 in RAM: ON in display panel
 114         */
 115        write_reg(par, 0xA6);
 116
 117        /* Set Display ON */
 118        write_reg(par, 0xAF);
 119
 120        return 0;
 121}
 122
 123static void set_addr_win_64x48(struct fbtft_par *par)
 124{
 125        /* Set Column Address */
 126        write_reg(par, 0x21);
 127        write_reg(par, 0x20);
 128        write_reg(par, 0x5F);
 129
 130        /* Set Page Address */
 131        write_reg(par, 0x22);
 132        write_reg(par, 0x0);
 133        write_reg(par, 0x5);
 134}
 135
 136static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
 137{
 138        /* Set Lower Column Start Address for Page Addressing Mode */
 139        write_reg(par, 0x00 | 0x0);
 140        /* Set Higher Column Start Address for Page Addressing Mode */
 141        write_reg(par, 0x10 | 0x0);
 142        /* Set Display Start Line */
 143        write_reg(par, 0x40 | 0x0);
 144
 145        if (par->info->var.xres == 64 && par->info->var.yres == 48)
 146                set_addr_win_64x48(par);
 147}
 148
 149static int blank(struct fbtft_par *par, bool on)
 150{
 151        fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n",
 152                      __func__, on ? "true" : "false");
 153
 154        if (on)
 155                write_reg(par, 0xAE);
 156        else
 157                write_reg(par, 0xAF);
 158        return 0;
 159}
 160
 161/* Gamma is used to control Contrast */
 162static int set_gamma(struct fbtft_par *par, u32 *curves)
 163{
 164        /* apply mask */
 165        curves[0] &= 0xFF;
 166
 167        /* Set Contrast Control for BANK0 */
 168        write_reg(par, 0x81);
 169        write_reg(par, curves[0]);
 170
 171        return 0;
 172}
 173
 174static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
 175{
 176        u16 *vmem16 = (u16 *)par->info->screen_buffer;
 177        u32 xres = par->info->var.xres;
 178        u32 yres = par->info->var.yres;
 179        u8 *buf = par->txbuf.buf;
 180        int x, y, i;
 181        int ret = 0;
 182
 183        for (x = 0; x < xres; x++) {
 184                for (y = 0; y < yres / 8; y++) {
 185                        *buf = 0x00;
 186                        for (i = 0; i < 8; i++)
 187                                if (vmem16[(y * 8 + i) * xres + x])
 188                                        *buf |= BIT(i);
 189                        buf++;
 190                }
 191        }
 192
 193        /* Write data */
 194        gpiod_set_value(par->gpio.dc, 1);
 195        ret = par->fbtftops.write(par, par->txbuf.buf, xres * yres / 8);
 196        if (ret < 0)
 197                dev_err(par->info->device, "write failed and returned: %d\n",
 198                        ret);
 199
 200        return ret;
 201}
 202
 203static struct fbtft_display display = {
 204        .regwidth = 8,
 205        .width = WIDTH,
 206        .height = HEIGHT,
 207        .gamma_num = 1,
 208        .gamma_len = 1,
 209        .gamma = "00",
 210        .fbtftops = {
 211                .write_vmem = write_vmem,
 212                .init_display = init_display,
 213                .set_addr_win = set_addr_win,
 214                .blank = blank,
 215                .set_gamma = set_gamma,
 216        },
 217};
 218
 219FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1306", &display);
 220
 221MODULE_ALIAS("spi:" DRVNAME);
 222MODULE_ALIAS("platform:" DRVNAME);
 223MODULE_ALIAS("spi:ssd1306");
 224MODULE_ALIAS("platform:ssd1306");
 225
 226MODULE_DESCRIPTION("SSD1306 OLED Driver");
 227MODULE_AUTHOR("Noralf Tronnes");
 228MODULE_LICENSE("GPL");
 229