SPI Device Driver Tutorial – Linux Device Driver Tutorial Part 47

This article is a continuation of the  Series on Linux Device Driver and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the SPI Device Driver Tutorial (SPI Protocol Driver) – Linux Device Driver Tutorial Part 47.

We are using the Raspberry PI 4 Model B for this demonstration.

Prerequisites

In this tutorial, we will be using the concepts which have been explained already in the below-given tutorials. So I would request you to go through those tutorials first if you are not familiar with those topics.

Hardware Required

Bring up Raspberry PI

  1. Install Raspberry Pi OS (32-bit) with desktop in the SD card.
  2. Then install the kernel header using sudo apt install raspberrypi-kernel-headers

For your information, In our Raspberry PI 4 board, kernel 5.10.27-v7l is installed.

Enable SPI in the Raspberry PI

In order to write the SPI driver, we have to enable the SPI bus. In this tutorial, we are using SPI 1. To enable SPI 1, please add the below line in the /boot/config.h.

dtoverlay=spi1-1cs,cs0_spidev=disabled

The above line enables the SPI 1 and it disables the SPIDev application. So that, we can use the SPI 1 bus. You can refer to this if you have any doubt.

SPI Device Driver Tutorial

An SPI – Serial Peripheral Interface protocol is synchronous serial interface technology introduced by Motorola. consists of one master device and one or more slave devices.

SPI needs 4 wires at least. But some of the recent day devices support 3 pin mode and 4pin modes also.

  1. MOSI – Master Out Slave In
  2. MISO – Master In Slave Out
  3. SCLK – Serial Clock
  4. SS/CS/CE – Salve Select/Chip Select/Chip Enable

You can read more about SPI Communication.

SPI Subsystem in Linux

The SPI device driver in Linux is mainly managed by the SPI subsystem, and it is divided into 3 sections.

  • SPI Core
  • SPI Controller Driver
  • SPI Protocol Driver

SPI Core

The SPI core provides APIs for the definition of core data structures, registration, cancellation management of SPI controller drivers and device drivers. It is a hardware platform-independent layer, that shields the differences of the physical bus controller downwards and defines a unified access strategy and interface. It provides a unified interface upwards. So that, the SPI device driver can send and receive data through the SPI bus controller. In Linux kernel, the SPI core code is located in kernel/drivers/spi/spi.c.

SPI Controller Driver

The SPI Controller driver is the platform-specific driver. So, each SoC manufactures has to write this driver for their platform or MCU. controllers These SPI controller drivers may be built into System-On-Chip processors, and often support both Master and Slave roles. These drivers touch hardware registers and may use DMA or they can be GPIO bit bangers, needing just GPIO pins. Its responsibility is to implement a corresponding read and write a method for each SPI bus in the system. Physically, each SPI controller can connect several SPI slave devices. When the system is turned on, the SPI controller driver is loaded first. A controller driver is used to support the reading and writing of a specific SPI bus. So, this SPI controller driver is similar to the adapter/bus driver in the I2C.

You can see the all SPI controller driver in kernel/drivers/spi/.

We will see how to write the SPI controller driver in our next tutorial.

SPI Protocol Driver

Each SPI bus controller may connect to multiple slave devices. This driver is used to communicate to specific devices through the SPI bus. This is similar to the I2C client driver.

In this tutorial, we will just focus on the SPI protocol driver.

SPI Protocol Driver in Linux Kernel

Steps that involve writing the SPI protocol device driver are given below.

  1. Get the SPI Controller driver
  2. Add the slave device to the SPI Controller
  3. Configure the SPI
  4. Now the driver is ready. So you can transfer the data between master and slave.
  5. Once you are done, then remove the device.

Get the SPI Controller driver

First, you need to get the controller driver. To do that, you can use the below API. Before using that API, you need to know which SPI bus you are using. In this example, I am using the SPI Bus 1.

struct spi_controller * spi_busnum_to_master(u16 bus_num)

where,

bus_num – the master’s bus number

Return

It returns a pointer to the relevant spi_controller (which the caller must release), or NULL if there is no such master registered.

Example

struct  spi_master *master;
master = spi_busnum_to_master( 1 );

Add the slave device to the SPI Controller

In this step, you need to create a spi_board_info structure that has the slave device’s details.

struct spi_board_info {
  char modalias[SPI_NAME_SIZE];
  const void      *platform_data;
  const struct property_entry *properties;
  void *controller_data;
  int irq;
  u32 max_speed_hz;
  u16 bus_num;
  u16 chip_select;
  u32 mode;
};

where,

modalias – This string is used to identifies the driver.

platform_data – the particular data stored there is driver-specific.

properties – Additional device properties for the device.

controller_data – some controllers need hints about hardware setup, e.g. for DMA.

irq – Interrupt number

max_speed_hz – Maximum speed that slave supports. Refer to the slave’s datasheet.

bus_num– SPI bus number that slave is going to connect.

chip_select – Chip select that you want to use.

mode – SPI mode. Refer to the slave’s datasheet.

Once you have filled this structure, then you can use the below API to add the slave device to the SPI controller driver.

struct spi_device * spi_new_device( struct spi_controller *ctlr, struct spi_board_info *chip )

where,

ctlr – Controller to which device is connected. In our case, it has been returned by spi_busnum_to_master().

chip – SPI slave device structure.

Return

It returns the new device, or NULL.

This spi_new_device() is enough as this function handles the spi_alloc_device() and spi_add_device() inside.

Example

struct spi_board_info etx_spi_device_info = 
{
  .modalias     = "etx-spi-ssd1306-driver",
  .max_speed_hz = 4000000,              // speed your device (slave) can handle
  .bus_num      = SPI_BUS_NUM,          // SPI 1
  .chip_select  = 0,                    // Use 0 Chip select (GPIO 18)
  .mode         = SPI_MODE_0            // SPI mode 0
};

static struct spi_device *etx_spi_device;

etx_spi_device = spi_new_device( master, &etx_spi_device_info );
if( etx_spi_device == NULL ) 
{
  pr_err("FAILED to create slave.\n");
  return -ENODEV;
}

Configure the SPI

Till now, we have added the slave device to the controller driver. If we change any mode or clock frequency, then we have to call this below API to take effect on the bus.

int spi_setup(struct spi_device *spi)

where,

spi – the device whose settings are being modified.

Return

It returns zero on success, else a negative error code.

Message Transfer

We have done with our initialization. Now we can transfer the data using the below APIs.

This function will be blocked until the master completes the transfer.

int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers)

where,

spi – device with which data will be exchanged.

xfers – An array of spi_transfers.

num_xfers – Number of items in the xfer array.

Return

It returns zero on success, else a negative error code.

This API is used to transfer the data asynchronously. This call may be used in_irq and other contexts which can’t sleep,

int spi_async(struct spi_device *spi, struct spi_message *message)

where,

spidevice with which data will be exchanged.

messagedescribes the data transfers, including completion callback.

Return

It returns zero on success, else a negative error code.

This API is used to write the data and followed by a read. This is synchronous.

int spi_write_then_read(struct spi_device * spi, const void * txbuf, unsigned n_tx, void * rxbuf, unsigned n_rx)

where,

spi – device with which data will be exchanged.

txbuf – data to be written.

n_tx – size of txbuf (in bytes).

rxbuf – buffer into which data will be read.

n_rx – size of rxbuf (in bytes).

Like these, there are many other APIs also available. Please refer to the spi.c file.

Remove the device

Using the below API, you can unregister the slave device.

void spi_unregister_device(struct spi_device *spi)

where,

spi – a device that needs to be removed.

Example

static void __exit etx_spi_exit(void)
{ 
  if( etx_spi_device )
  {
    spi_unregister_device( etx_spi_device );    // Unregister the SPI slave
    pr_info("SPI driver Unregistered\n");
  }
}

SSD1306 OLED

In this example, we were using the SSD1306 OLED as an SPI slave device. You may find a very good inbuilt SPI driver for SSD1306. So, our motto is to implement an SPI protocol driver.

SSD1306 is a single-chip CMOS OLED/PLED driver with a controller for an organic / polymer light-emitting diode dot-matrix graphic display system. It consists of 128 segments and 64commons.

The SSD1306 embeds with contrast control, display RAM, and oscillator, which reduces the number of external components and power consumption. It has 256-step brightness control. Data/Commands are sent from general MCU through the hardware selectable 6800/8000 series compatible Parallel Interface, I2C interface, or Serial Peripheral Interface. It is suitable for many compact portable applications, such as mobile phone sub-display, MP3 player and calculator, etc.

The operating voltage of the SSD1306 controller is from 1.65V to 3.3V while the OLED panel requires 7V to 15V supply voltage. All these different power requirements are sufficed using internal charge pump circuitry. This makes it possible to connect it to any 5V logic microcontroller easily without using any logic level converter.

SSD1306 OLED Memory Map

Regardless of the size of the OLED module, the SSD1306 driver has a built-in 1KB Graphic Display Data RAM (GDDRAM) for the screen which holds the bit pattern to be displayed. This 1K memory area is organized into 8 pages (from 0 to 7). Each page contains 128 columns/segments (block 0 to 127). And each column can store 8 bits of data (from 0 to 7). That surely tells us we have

8 pages x 128 segments x 8 bits of data = 8192 bits = 1024 bytes = 1KB memory

Here are the complete specifications:

Display Technology OLED (Organic LED)
MCU Interface I2C / SPI
Screen Size 0.96 Inch Across
Resolution 128×64 pixels
Operating Voltage 3.3V – 5V
Operating Current 20mA max
Viewing Angle 160°
Characters Per Row 21
Number of Character Rows 7

Data in SSD1306 OLED

There are two types of data that we can send to the SSD1306 OLED.

  1. Command
  2. Data to be written into the GDDRAM

Command

The command is nothing but, tells the LED to do some operations like a scroll, increases the brightness, etc.

Basically SSD1306 has 8 lines (pages) and 128 columns(segments). Please refer to the below image for a clear understanding.

Each page contains 8 rows. So, a total of 64 rows will be present in the SSD1306’s GDDRAM (Graphics Display Data RAM). The GDDRAM (Graphics Display Data RAM) is a bitmapped static RAM. It holds the bit pattern to be displayed. For understanding the rows, please look at the image below.

GDDRAM Structure

As per our understanding, it has 128 columns and 64 rows. So the size of GDDRAM is 128 x 64 bits and it is divided into eight pages, from PAGE0 to PAGE7, which are used for monochrome 128×64 dot matrix display as shown in the above figure.

In this tutorial, we are going to write one character in 5×8. The below image explains how to write our characters. In that, I have taken Page 0’s row 0 to row 7 and column 0 to column 4 to explain how to write our characters.

Print A

I think the above image is self-explanatory. I am trying to write the character ‘A’ (5×8) from row 0 and column 0. In order to write ‘A’, I need to make the cursor on page 0 and column 0. Then write 0x7C, 0x12, 0x11, 0x12 and 0x7C. Look at the array SSD1306_font in the source code to get the data for almost all characters.

Since you can write data pixel by pixel, you can print anything you want. In this example, we are also printing the image.

Commands in SSD1306
Command Command Description
0x00 COMMAND This command is used to indicate the next data byte is acted as a command.
0x40~0x7F Display Start Line This command sets the Display Start Line register to determine the starting address of display RAM
0xAE Display OFF This command is used to turn OFF the OLED display panel.
0xAF Display ON This command is used to turn ON the OLED display panel.
0x20 Memory Addressing Mode If horizontal address increment mode is enabled by command 20h, after finishing read/write one column data, it is incremented automatically to the next column address.
0x26/0x27 Continuous Horizontal Scroll 0x26 – Right horizontal scroll

0x27 – Left horizontal scroll

0x29/0x2A Continuous Vertical and Horizontal Scroll 0x29 – Vertical and Right Horizontal Scroll

0x2A – Vertical and Left Horizontal Scroll

0x2E Deactivate Scroll This command deactivates the scroll.
0x2F Activate Scroll This command activates the scroll if it is configured before.
0x21 Column Address This command is used to define the current read/write column address in graphic display data RAM.
0x22 Page Address This command is used to define the current read/write Line(Page as per data sheet) address in graphic display data RAM.
0x81 Contrast Control This command sets the Contrast Setting of the display. The chip has 256 contrast steps from 00h to FFh. The segment output current increases as the contrast step value increases.
0xA0 Segment Re-map This command sets the column address 0 is mapped to SEG0.
0xA1 Segment Re-map This command sets the column address 127 is mapped to SEG0.
0xA6 Normal Display This command sets the display to normal mode. In the normal display, a RAM data of 1 indicates an “ON” pixel.
0xA7 Inverse Display This command sets the display to inverse mode. In the normal display, a RAM data of 0 indicates an “ON” pixel.
0xA8 Multiplex Ratio This command sets the display multiplex ratio.
0xC0/0xC8 COM Output Scan direction 0xC0 – Normal mode (RESET) Scan from COM0 to COM[N –1]

0xC8- Remapped mode. Scan from COM[N-1] to COM0 Where N is the Multiplex ratio.

0xD3 Display Offset This command sets vertical shift by COM from 0d~63d.
0xD5 Display Clock Divide Ratio/Oscillator Frequency

 

This command sets the display Clock Divide Ratio and Oscillator Frequency.

Please go through the datasheet for complete commands. Commands are pretty straightforward. You will understand those commands easily.

Data

Data is directly written into the OLED display. We can print anything we want, using this SSD1306 OLED, unlike other alphanumerical displays. Graphical data also we can print.

How the SSD1306 OLED differentiate between command and data?

In I2C communication, we have to send the control byte first, which tells the following data is command or data. But in SPI, there is a dedicated GPIO (DC) available. We have to keep the DC line to low if we are sending the command. Otherwise, we have to keep that to high.

Please refer to the datasheet for further information.

Linux SPI Device Driver Example

Connection Diagram – Linux SPI Device Driver

In this tutorial, we are using the SSD1306 SPI OLED. Some of the SSD1306 SPI OLEDs might have a CS/CE pin. In that case, you can connect that to your controller’s CS/CE pin. In my case, I don’t have the separate CS/CE pin in the SSD1306 OLED as it is already grounded in the board itself.

In this example, we have used the SPI1 of the Raspberry PI 4B.

Please refer to the below connection diagram.

  • SDA (MOSI) – GPIO 20
  • SCL (CLK) –  GPIO 21
  • RES – GPIO 24
  • DC – GPIO 23
  • Vcc –  3.3V
  • GND – Ground

SPI Device Driver connection

Concept of the Driver

The concept is to write some strings and display the image in the SSD1306 OLED display.

Source code – SPI Protocol Driver

I have divided this driver source code into 2 files.

  1. spi_ssd1306_driver.c (This file has the Linux SPI protocol driver-specific functions)
  2. ssd1306.c (This file has the SSD1306 OLED specific functions)

You can get the complete source code from GitHub.

spi_ssd1306_driver.c

/****************************************************************************//**
*  \file       spi_ssd1306_driver.c
*
*  \details    Simple linux driver (SPI Slave Protocol Driver)
*
*  \author     EmbeTronicX
*
*  \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>

#include "ssd1306.h"

static struct spi_device *etx_spi_device;

//Register information about your slave device
struct spi_board_info etx_spi_device_info = 
{
  .modalias     = "etx-spi-ssd1306-driver",
  .max_speed_hz = 4000000,              // speed your device (slave) can handle
  .bus_num      = SPI_BUS_NUM,          // SPI 1
  .chip_select  = 0,                    // Use 0 Chip select (GPIO 18)
  .mode         = SPI_MODE_0            // SPI mode 0
};

/****************************************************************************
 * Name: etx_spi_write
 *
 * Details : This function writes the 1-byte data to the slave device using SPI.
 ****************************************************************************/
int etx_spi_write( uint8_t data )
{
  int     ret = -1;
  uint8_t rx  = 0x00;
  
  if( etx_spi_device )
  {    
    struct spi_transfer	tr = 
    {
      .tx_buf	= &data,
      .rx_buf = &rx,
      .len		= 1,
    };

    spi_sync_transfer( etx_spi_device, &tr, 1 );
  }
  
  //pr_info("Received = 0x%02X \n", rx);
  
  return( ret );
}

/****************************************************************************
 * Name: etx_spi_init
 *
 * Details : This function Register and Initilize the SPI.
 ****************************************************************************/
static int __init etx_spi_init(void)
{
  int     ret;
  struct  spi_master *master;
  
  master = spi_busnum_to_master( etx_spi_device_info.bus_num );
  if( master == NULL )
  {
    pr_err("SPI Master not found.\n");
    return -ENODEV;
  }
   
  // create a new slave device, given the master and device info
  etx_spi_device = spi_new_device( master, &etx_spi_device_info );
  if( etx_spi_device == NULL ) 
  {
    pr_err("FAILED to create slave.\n");
    return -ENODEV;
  }
  
  // 8-bits in a word
  etx_spi_device->bits_per_word = 8;

  // setup the SPI slave device
  ret = spi_setup( etx_spi_device );
  if( ret )
  {
    pr_err("FAILED to setup slave.\n");
    spi_unregister_device( etx_spi_device );
    return -ENODEV;
  }
  
  //Initialize the OLED SSD1306
  ETX_SSD1306_DisplayInit();
  
  /* Print the String */
  ETX_SSD1306_SetBrightness( 255 );           // Full brightness
  ETX_SSD1306_InvertDisplay( false );         // Invert the dispaly : OFF
  
  // Enable the Horizontal scroll for first 3 lines
  ETX_SSD1306_StartScrollHorizontal( true, 0, 2);
  
  
  ETX_SSD1306_SetCursor(0,0);                 // Set cursor at 0th line 0th col
  //Write String to OLED
  ETX_SSD1306_String("Welcome\nTo\nEmbeTronicX\n");
  
  ETX_SSD1306_SetCursor(4,35);                // Set cursor at 4th line 35th col
  //Write String to OLED
  ETX_SSD1306_String("SPI Linux\n");
  ETX_SSD1306_SetCursor(5,23);                // Set cursor at 5th line 23rd col
  ETX_SSD1306_String("Device Driver\n");
  ETX_SSD1306_SetCursor(6,37);                // Set cursor at 6th line 37th col
  ETX_SSD1306_String("Tutorial\n");
  
  msleep(9000);                               // 9secs delay
  
  ETX_SSD1306_ClearDisplay();                 // Clear Display
  ETX_SSD1306_DeactivateScroll();             // Deactivate the scroll
  
  /* Print the Image */
  ETX_SSD1306_PrintLogo();
  
  pr_info("SPI driver Registered\n");
  return 0;
}
 
/****************************************************************************
 * Name: etx_spi_exit
 *
 * Details : This function Unregister and DeInitilize the SPI.
 ****************************************************************************/
static void __exit etx_spi_exit(void)
{ 
  if( etx_spi_device )
  {
    // Clear the display
    ETX_SSD1306_ClearDisplay();                 // Clear Display
    ETX_SSD1306_DisplayDeInit();                // Deinit the SSD1306
    spi_unregister_device( etx_spi_device );    // Unregister the SPI slave
    pr_info("SPI driver Unregistered\n");
  }
}
 
module_init(etx_spi_init);
module_exit(etx_spi_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <[email protected]>");
MODULE_DESCRIPTION("A simple device driver - SPI Slave Protocol Driver");
MODULE_VERSION("1.44");

ssd1306.c

/****************************************************************************//**
*  \file       ssd1306.c
*
*  \details    SSD1306 related functions (SPI)
*
*  \author     EmbeTronicX
*
*  \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include <linux/gpio.h>     //GPIO
#include <linux/delay.h>

#include "ssd1306.h"

/*
** Variable to store Line Number and Cursor Position.
*/ 
static uint8_t SSD1306_LineNum   = 0;
static uint8_t SSD1306_CursorPos = 0;
static uint8_t SSD1306_FontSize  = SSD1306_DEF_FONT_SIZE;

/*
**  EmbeTronicX Logo
*/
static const uint8_t etx_logo[1024] = {
  0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xE1, 0xF9, 0xFF, 0xF9, 0xE1, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF8, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF,
  0xF8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x0F, 0xF8, 0xF7, 0x00, 0xBF, 0xC0, 0x7F,
  0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3F, 0xE0, 0x0F, 0x7F, 0x00, 0xFF, 0x7F, 0x80,
  0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x04, 0xFC, 0xFC, 0x0C, 0x0C, 0x0C, 0x0C, 0x7C, 0x00, 0x00, 0x00,
  0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xF8,
  0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x08,
  0x04, 0x04, 0xFC, 0xFC, 0x04, 0x04, 0x04, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x98, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x00, 0x00, 0x04, 0x0C, 0x38, 0xE0, 0x80, 0xE0, 0x38, 0x0C, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0x00, 0xFD, 0xFE, 0xFF,
  0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x06, 0x06, 0x06, 0x06, 0xE0, 0x00, 0x00, 0x00,
  0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
  0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x7C, 0xFF, 0x11, 0x10, 0x1F, 0x1F, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x7C, 0xFF, 0x01, 0x00, 0x01, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x87,
  0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1F, 0x3F, 0xFC, 0xF7, 0x00, 0xDF, 0xE3, 0x7D,
  0x3E, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
  0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x01, 0x03, 0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x03,
  0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x02, 0x02, 0x03,
  0x00, 0x00, 0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0xFF, 0x03, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x88, 0x88, 0x08, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x20, 0xE0,
  0x00, 0xFC, 0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xFC, 0x00,
  0xE0, 0x20, 0x20, 0xFC, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xFC, 0x00, 0x00,
  0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFC, 0x20, 0x20, 0x00, 0xE0,
  0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0x00, 0xEC, 0x00, 0x20, 0x20, 0x20, 0xE0, 0x00, 0xFC,
  0x00, 0xE0, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0xC8, 0x38, 0x00, 0x00, 0xE0,
  0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x9C, 0x9C, 0x88, 0x8E, 0x83, 0x98, 0x83, 0x8E, 0x88,
  0x88, 0x9C, 0x9C, 0x80, 0x80, 0x87, 0x84, 0x84, 0x84, 0x80, 0x87, 0x80, 0x80, 0x87, 0x80, 0x87,
  0x80, 0x87, 0x84, 0x84, 0x87, 0x80, 0x87, 0x85, 0x85, 0x85, 0x80, 0x87, 0x84, 0x84, 0x87, 0x80,
  0x87, 0x84, 0x84, 0x87, 0x80, 0x87, 0x85, 0x85, 0x85, 0x80, 0x87, 0x84, 0x84, 0x87, 0x80, 0x80,
  0x80, 0x80, 0x80, 0x87, 0x80, 0x80, 0x80, 0x87, 0x84, 0x87, 0x80, 0x87, 0x84, 0x84, 0x80, 0x87,
  0x84, 0x84, 0x87, 0x80, 0x87, 0x80, 0x80, 0x80, 0x87, 0x80, 0x87, 0x85, 0x85, 0x87, 0x80, 0x87,
  0x80, 0x85, 0x85, 0x85, 0x87, 0x80, 0x80, 0x80, 0x80, 0x86, 0x85, 0x84, 0x84, 0x84, 0x80, 0x87,
  0x84, 0x84, 0x87, 0x80, 0x87, 0x80, 0x87, 0x80, 0x87, 0x85, 0x85, 0x85, 0x80, 0x80, 0x80, 0xFF
};

/*
** Array Variable to store the letters.
*/ 
static const unsigned char SSD1306_font[][SSD1306_DEF_FONT_SIZE]= 
{
    {0x00, 0x00, 0x00, 0x00, 0x00},   // space
    {0x00, 0x00, 0x2f, 0x00, 0x00},   // !
    {0x00, 0x07, 0x00, 0x07, 0x00},   // "
    {0x14, 0x7f, 0x14, 0x7f, 0x14},   // #
    {0x24, 0x2a, 0x7f, 0x2a, 0x12},   // $
    {0x23, 0x13, 0x08, 0x64, 0x62},   // %
    {0x36, 0x49, 0x55, 0x22, 0x50},   // &
    {0x00, 0x05, 0x03, 0x00, 0x00},   // '
    {0x00, 0x1c, 0x22, 0x41, 0x00},   // (
    {0x00, 0x41, 0x22, 0x1c, 0x00},   // )
    {0x14, 0x08, 0x3E, 0x08, 0x14},   // *
    {0x08, 0x08, 0x3E, 0x08, 0x08},   // +
    {0x00, 0x00, 0xA0, 0x60, 0x00},   // ,
    {0x08, 0x08, 0x08, 0x08, 0x08},   // -
    {0x00, 0x60, 0x60, 0x00, 0x00},   // .
    {0x20, 0x10, 0x08, 0x04, 0x02},   // /
    {0x3E, 0x51, 0x49, 0x45, 0x3E},   // 0
    {0x00, 0x42, 0x7F, 0x40, 0x00},   // 1
    {0x42, 0x61, 0x51, 0x49, 0x46},   // 2
    {0x21, 0x41, 0x45, 0x4B, 0x31},   // 3
    {0x18, 0x14, 0x12, 0x7F, 0x10},   // 4
    {0x27, 0x45, 0x45, 0x45, 0x39},   // 5
    {0x3C, 0x4A, 0x49, 0x49, 0x30},   // 6
    {0x01, 0x71, 0x09, 0x05, 0x03},   // 7
    {0x36, 0x49, 0x49, 0x49, 0x36},   // 8
    {0x06, 0x49, 0x49, 0x29, 0x1E},   // 9
    {0x00, 0x36, 0x36, 0x00, 0x00},   // :
    {0x00, 0x56, 0x36, 0x00, 0x00},   // ;
    {0x08, 0x14, 0x22, 0x41, 0x00},   // <
    {0x14, 0x14, 0x14, 0x14, 0x14},   // =
    {0x00, 0x41, 0x22, 0x14, 0x08},   // >
    {0x02, 0x01, 0x51, 0x09, 0x06},   // ?
    {0x32, 0x49, 0x59, 0x51, 0x3E},   // @
    {0x7C, 0x12, 0x11, 0x12, 0x7C},   // A
    {0x7F, 0x49, 0x49, 0x49, 0x36},   // B
    {0x3E, 0x41, 0x41, 0x41, 0x22},   // C
    {0x7F, 0x41, 0x41, 0x22, 0x1C},   // D
    {0x7F, 0x49, 0x49, 0x49, 0x41},   // E
    {0x7F, 0x09, 0x09, 0x09, 0x01},   // F
    {0x3E, 0x41, 0x49, 0x49, 0x7A},   // G
    {0x7F, 0x08, 0x08, 0x08, 0x7F},   // H
    {0x00, 0x41, 0x7F, 0x41, 0x00},   // I
    {0x20, 0x40, 0x41, 0x3F, 0x01},   // J
    {0x7F, 0x08, 0x14, 0x22, 0x41},   // K
    {0x7F, 0x40, 0x40, 0x40, 0x40},   // L
    {0x7F, 0x02, 0x0C, 0x02, 0x7F},   // M
    {0x7F, 0x04, 0x08, 0x10, 0x7F},   // N
    {0x3E, 0x41, 0x41, 0x41, 0x3E},   // O
    {0x7F, 0x09, 0x09, 0x09, 0x06},   // P
    {0x3E, 0x41, 0x51, 0x21, 0x5E},   // Q
    {0x7F, 0x09, 0x19, 0x29, 0x46},   // R
    {0x46, 0x49, 0x49, 0x49, 0x31},   // S
    {0x01, 0x01, 0x7F, 0x01, 0x01},   // T
    {0x3F, 0x40, 0x40, 0x40, 0x3F},   // U
    {0x1F, 0x20, 0x40, 0x20, 0x1F},   // V
    {0x3F, 0x40, 0x38, 0x40, 0x3F},   // W
    {0x63, 0x14, 0x08, 0x14, 0x63},   // X
    {0x07, 0x08, 0x70, 0x08, 0x07},   // Y
    {0x61, 0x51, 0x49, 0x45, 0x43},   // Z
    {0x00, 0x7F, 0x41, 0x41, 0x00},   // [
    {0x55, 0xAA, 0x55, 0xAA, 0x55},   // Backslash (Checker pattern)
    {0x00, 0x41, 0x41, 0x7F, 0x00},   // ]
    {0x04, 0x02, 0x01, 0x02, 0x04},   // ^
    {0x40, 0x40, 0x40, 0x40, 0x40},   // _
    {0x00, 0x03, 0x05, 0x00, 0x00},   // `
    {0x20, 0x54, 0x54, 0x54, 0x78},   // a
    {0x7F, 0x48, 0x44, 0x44, 0x38},   // b
    {0x38, 0x44, 0x44, 0x44, 0x20},   // c
    {0x38, 0x44, 0x44, 0x48, 0x7F},   // d
    {0x38, 0x54, 0x54, 0x54, 0x18},   // e
    {0x08, 0x7E, 0x09, 0x01, 0x02},   // f
    {0x18, 0xA4, 0xA4, 0xA4, 0x7C},   // g
    {0x7F, 0x08, 0x04, 0x04, 0x78},   // h
    {0x00, 0x44, 0x7D, 0x40, 0x00},   // i
    {0x40, 0x80, 0x84, 0x7D, 0x00},   // j
    {0x7F, 0x10, 0x28, 0x44, 0x00},   // k
    {0x00, 0x41, 0x7F, 0x40, 0x00},   // l
    {0x7C, 0x04, 0x18, 0x04, 0x78},   // m
    {0x7C, 0x08, 0x04, 0x04, 0x78},   // n
    {0x38, 0x44, 0x44, 0x44, 0x38},   // o
    {0xFC, 0x24, 0x24, 0x24, 0x18},   // p
    {0x18, 0x24, 0x24, 0x18, 0xFC},   // q
    {0x7C, 0x08, 0x04, 0x04, 0x08},   // r
    {0x48, 0x54, 0x54, 0x54, 0x20},   // s
    {0x04, 0x3F, 0x44, 0x40, 0x20},   // t
    {0x3C, 0x40, 0x40, 0x20, 0x7C},   // u
    {0x1C, 0x20, 0x40, 0x20, 0x1C},   // v
    {0x3C, 0x40, 0x30, 0x40, 0x3C},   // w
    {0x44, 0x28, 0x10, 0x28, 0x44},   // x
    {0x1C, 0xA0, 0xA0, 0xA0, 0x7C},   // y
    {0x44, 0x64, 0x54, 0x4C, 0x44},   // z
    {0x00, 0x10, 0x7C, 0x82, 0x00},   // {
    {0x00, 0x00, 0xFF, 0x00, 0x00},   // |
    {0x00, 0x82, 0x7C, 0x10, 0x00},   // }
    {0x00, 0x06, 0x09, 0x09, 0x06}    // ~ (Degrees)
};

/****************************************************************************
 * Name: ETX_SSD1306_ResetDcInit
 *
 * Details : This function Initializes and Configures the Reset and DC Pin
 ****************************************************************************/
static int ETX_SSD1306_ResetDcInit( void )
{
  int ret = 0;
    
  //do while(false) loop to break if any error
  do
  {
    
    /* Register the Reset GPIO */
    
    //Checking the Reset GPIO is valid or not
    if( gpio_is_valid( SSD1306_RST_PIN ) == false )
    {
      pr_err("Reset GPIO %d is not valid\n", SSD1306_RST_PIN);
      ret = -1;
      break;
    }
    
    //Requesting the Reset GPIO
    if( gpio_request( SSD1306_RST_PIN, "SSD1306_RST_PIN" ) < 0 )
    {
      pr_err("ERROR: Reset GPIO %d request\n", SSD1306_RST_PIN);
      ret = -1;
      break;
    }
    
    //configure the Reset GPIO as output
    gpio_direction_output( SSD1306_RST_PIN, 1 );
    
    /* Register the DC GPIO */
    
    //Checking the DC GPIO is valid or not
    if( gpio_is_valid( SSD1306_DC_PIN ) == false )
    {
      pr_err("DC GPIO %d is not valid\n", SSD1306_DC_PIN);
      gpio_free( SSD1306_RST_PIN );   // free the reset GPIO
      ret = -1;
      break;
    }
    
    //Requesting the DC GPIO
    if( gpio_request( SSD1306_DC_PIN, "SSD1306_DC_PIN" ) < 0 )
    {
      pr_err("ERROR: DC GPIO %d request\n", SSD1306_DC_PIN);
      gpio_free( SSD1306_RST_PIN );   // free the reset GPIO
      ret = -1;
      break;
    }
    
    //configure the Reset GPIO as output
    gpio_direction_output( SSD1306_DC_PIN, 1 );
    
  } while( false );
  
  //pr_info("DC Reset GPIOs init Done!\n");
  return( ret );
}

/****************************************************************************
 * Name: ETX_SSD1306_ResetDcDeInit
 *
 * Details : This function De-initializes the Reset and DC Pin
 ****************************************************************************/
static void ETX_SSD1306_ResetDcDeInit( void )
{
  gpio_free( SSD1306_RST_PIN );   // free the reset GPIO
  gpio_free( SSD1306_DC_PIN );    // free the DC GPIO
}

/****************************************************************************
 * Name: ETX_SSD1306_setRst
 *
 * Details : This function writes the value to the Reset GPIO
 * 
 * Argument: 
 *            value - value to be set ( 0 or 1 )
 * 
 ****************************************************************************/
static void ETX_SSD1306_setRst( uint8_t value )
{
  gpio_set_value( SSD1306_RST_PIN, value );
}

/****************************************************************************
 * Name: ETX_SSD1306_setDc
 *
 * Details : This function writes the value to the DC GPIO
 * 
 * Argument: 
 *            value - value to be set ( 0 or 1 )
 * 
 ****************************************************************************/
static void ETX_SSD1306_setDc( uint8_t value )
{
  gpio_set_value( SSD1306_DC_PIN, value );
}

/****************************************************************************
 * Name: ETX_SSD1306_Write
 *
 * Details : This function sends the command/data to the Display
 *
 * Argument: is_cmd
 *              true  - if we need to send command
 *              false - if we need to send data
 *           value
 *              value to be transmitted
 ****************************************************************************/
static int ETX_SSD1306_Write( bool is_cmd, uint8_t data )
{
  int     ret = 0;
  uint8_t pin_value;

  if( is_cmd )
  {
    //DC pin has to be low, if this is command.
    pin_value = 0u;
  }
  else
  {
    //DC pin has to be high, if this is data.
    pin_value = 1u;
  }
  
  ETX_SSD1306_setDc( pin_value );
  
  //pr_info("Writing 0x%02X \n", data);
  
  //send the byte
  ret = etx_spi_write( data );
  
  return( ret );
}

/****************************************************************************
 * Name: ETX_SSD1306_SetCursor
 *
 * Details : This function is specific to the SSD_1306 OLED.
 *
 * Argument:
 *              lineNo    -> Line Number
 *              cursorPos -> Cursor Position
 * 
 ****************************************************************************/
void ETX_SSD1306_SetCursor( uint8_t lineNo, uint8_t cursorPos )
{
  /* Move the Cursor to specified position only if it is in range */
  if((lineNo <= SSD1306_MAX_LINE) && (cursorPos < SSD1306_MAX_SEG))
  {
    SSD1306_LineNum   = lineNo;                 // Save the specified line number
    SSD1306_CursorPos = cursorPos;              // Save the specified cursor position
    
    ETX_SSD1306_Write(true, 0x21);              // cmd for the column start and end address
    ETX_SSD1306_Write(true, cursorPos);         // column start addr
    ETX_SSD1306_Write(true, SSD1306_MAX_SEG-1); // column end addr
    ETX_SSD1306_Write(true, 0x22);              // cmd for the page start and end address
    ETX_SSD1306_Write(true, lineNo);            // page start addr
    ETX_SSD1306_Write(true, SSD1306_MAX_LINE);  // page end addr
  }
}

/****************************************************************************
 * Name: ETX_SSD1306_GoToNextLine
 *
 * Details : This function is specific to the SSD_1306 OLED and move the cursor 
 *           to the next line.
 ****************************************************************************/
void ETX_SSD1306_GoToNextLine( void )
{
  /*
  ** Increment the current line number.
  ** roll it back to first line, if it exceeds the limit. 
  */
  SSD1306_LineNum++;
  SSD1306_LineNum = (SSD1306_LineNum & SSD1306_MAX_LINE);
  ETX_SSD1306_SetCursor(SSD1306_LineNum,0); /* Finally move it to next line */
}

/****************************************************************************
 * Name: ETX_SSD1306_PrintChar
 *
 * Details : This function is specific to the SSD_1306 OLED and sends 
 *           the single char to the OLED.
 * 
 * Arguments:
 *           c   -> character to be written
 * 
 ****************************************************************************/
void ETX_SSD1306_PrintChar( unsigned char c )
{
  uint8_t data_byte;
  uint8_t temp = 0;
  
  /*
  ** If we character is greater than segment len or we got new line charcter
  ** then move the cursor to the new line
  */ 
  if( (( SSD1306_CursorPos + SSD1306_FontSize ) >= SSD1306_MAX_SEG ) ||
      ( c == '\n' )
  )
  {
    ETX_SSD1306_GoToNextLine();
  }
  
  // print charcters other than new line
  if( c != '\n' )
  {
  
    /*
    ** In our font array (SSD1306_font), space starts in 0th index.
    ** But in ASCII table, Space starts from 32 (0x20).
    ** So we need to match the ASCII table with our font table.
    ** We can subtract 32 (0x20) in order to match with our font table.
    */
    c -= 0x20;  //or c -= ' ';
    do
    {
      data_byte= SSD1306_font[c][temp];         // Get the data to be displayed from LookUptable
      ETX_SSD1306_Write(false, data_byte);  // write data to the OLED
      SSD1306_CursorPos++;
      
      temp++;
      
    } while ( temp < SSD1306_FontSize);
    
    ETX_SSD1306_Write(false, 0x00);         //Display the data
    SSD1306_CursorPos++;
  }
}

/****************************************************************************
 * Name: ETX_SSD1306_String
 *
 * Details : This function is specific to the SSD_1306 OLED and sends 
 *           the string to the OLED.
 * 
 * Arguments:
 *           str   -> string to be written
 * 
 ****************************************************************************/
void ETX_SSD1306_String(char *str)
{
  while( *str )
  {
    ETX_SSD1306_PrintChar(*str++);
  }
}


/****************************************************************************
 * Name: ETX_SSD1306_InvertDisplay
 *
 * Details : This function is specific to the SSD_1306 OLED and 
 *           inverts the display.
 * 
 * Arguments:
 *           need_to_invert   -> true  - invert display
 *                               false - normal display 
 * 
 ****************************************************************************/
void ETX_SSD1306_InvertDisplay(bool need_to_invert)
{
  if(need_to_invert)
  {
    ETX_SSD1306_Write(true, 0xA7); // Invert the display
  }
  else
  {
    ETX_SSD1306_Write(true, 0xA6); // Normal display
  }
}

/****************************************************************************
 * Name: ETX_SSD1306_SetBrightness
 *
 * Details : This function is specific to the SSD_1306 OLED and 
 *           sets the brightness of  the display.
 * 
 * Arguments:
 *           brightnessValue   -> brightness value ( 0 - 255 )
 * 
 ****************************************************************************/
void ETX_SSD1306_SetBrightness(uint8_t brightnessValue)
{
    ETX_SSD1306_Write(true, 0x81);            // Contrast command
    ETX_SSD1306_Write(true, brightnessValue); // Contrast value (default value = 0x7F)
}

/****************************************************************************
 * Name: ETX_SSD1306_StartScrollHorizontal
 *
 * Details : This function is specific to the SSD_1306 OLED and 
 *           Scrolls the data right/left in horizontally.
 * 
 * Arguments:
 *           is_left_scroll   -> true  - left horizontal scroll
 *                               false - right horizontal scroll
 *           start_line_no    -> Start address of the line to scroll 
 *           end_line_no      -> End address of the line to scroll 
 * 
 ****************************************************************************/
void ETX_SSD1306_StartScrollHorizontal( bool is_left_scroll,
                                        uint8_t start_line_no,
                                        uint8_t end_line_no
                                      )
{
  if(is_left_scroll)
  {
    // left horizontal scroll
    ETX_SSD1306_Write(true, 0x27);
  }
  else
  {
    // right horizontal scroll 
    ETX_SSD1306_Write(true, 0x26);
  }
  
  ETX_SSD1306_Write(true, 0x00);            // Dummy byte (dont change)
  ETX_SSD1306_Write(true, start_line_no);   // Start page address
  ETX_SSD1306_Write(true, 0x00);            // 5 frames interval
  ETX_SSD1306_Write(true, end_line_no);     // End page address
  ETX_SSD1306_Write(true, 0x00);            // Dummy byte (dont change)
  ETX_SSD1306_Write(true, 0xFF);            // Dummy byte (dont change)
  ETX_SSD1306_Write(true, 0x2F);            // activate scroll
}

/****************************************************************************
 * Name: ETX_SSD1306_StartScrollVerticalHorizontal
 *
 * Details : This function is specific to the SSD_1306 OLED and 
 *           Scrolls the data in vertically and right/left horizontally
 *           (Diagonally).
 * 
 * Arguments:
 *      is_vertical_left_scroll -> true  - vertical and left horizontal scroll
 *                                 false - vertical and right horizontal scroll
 *      start_line_no           -> Start address of the line to scroll 
 *      end_line_no             -> End address of the line to scroll 
 *      vertical_area           -> Area for vertical scroll (0-63)
 *      rows                    -> Number of rows to scroll vertically 
 * 
 ****************************************************************************/
void ETX_SSD1306_StartScrollVerticalHorizontal( 
                                                bool is_vertical_left_scroll,
                                                uint8_t start_line_no,
                                                uint8_t end_line_no,
                                                uint8_t vertical_area,
                                                uint8_t rows
                                              )
{
  
  ETX_SSD1306_Write(true, 0xA3);            // Set Vertical Scroll Area
  ETX_SSD1306_Write(true, 0x00);            // Check datasheet
  ETX_SSD1306_Write(true, vertical_area);   // area for vertical scroll
  
  if(is_vertical_left_scroll)
  {
    // vertical and left horizontal scroll
    ETX_SSD1306_Write(true, 0x2A);
  }
  else
  {
    // vertical and right horizontal scroll 
    ETX_SSD1306_Write(true, 0x29);
  }
  
  ETX_SSD1306_Write(true, 0x00);            // Dummy byte (dont change)
  ETX_SSD1306_Write(true, start_line_no);   // Start page address
  ETX_SSD1306_Write(true, 0x00);            // 5 frames interval
  ETX_SSD1306_Write(true, end_line_no);     // End page address
  ETX_SSD1306_Write(true, rows);            // Vertical scrolling offset
  ETX_SSD1306_Write(true, 0x2F);            // activate scroll
}

/****************************************************************************
 * Name: ETX_SSD1306_DeactivateScroll
 *
 * Details : This function disables the scroll.
 ****************************************************************************/
void ETX_SSD1306_DeactivateScroll( void )
{
  ETX_SSD1306_Write(true, 0x2E); // Deactivate scroll
}

/****************************************************************************
 * Name: ETX_SSD1306_fill
 *
 * Details : This function fills the data to the Display
 ****************************************************************************/
void ETX_SSD1306_fill( uint8_t data )
{
  // 8 pages x 128 segments x 8 bits of data
  unsigned int total  = ( SSD1306_MAX_SEG * (SSD1306_MAX_LINE + 1) );
  unsigned int i      = 0;
  
  //Fill the Display
  for(i = 0; i < total; i++)
  {
    ETX_SSD1306_Write(false, data);
  }
}

/****************************************************************************
 * Name: ETX_SSD1306_ClearDisplay
 *
 * Details : This function clears the Display
 ****************************************************************************/
void ETX_SSD1306_ClearDisplay( void )
{
  //Set cursor
  ETX_SSD1306_SetCursor(0,0);
  
  ETX_SSD1306_fill( 0x00 );
}

/****************************************************************************
 * Name: ETX_SSD1306_PrintLogo
 *
 * Details : This function prints the EmbeTronicX Logo
 ****************************************************************************/
void ETX_SSD1306_PrintLogo( void )
{
  int i;
  
  //Set cursor
  ETX_SSD1306_SetCursor(0,0);
  
  for( i = 0; i < ( SSD1306_MAX_SEG * (SSD1306_MAX_LINE + 1) ); i++ )
  {
    ETX_SSD1306_Write(false, etx_logo[i]);
  }
}

/****************************************************************************
 * Name: ETX_SSD1306_DisplayInit
 *
 * Details : This function Initializes the Display
 ****************************************************************************/
int ETX_SSD1306_DisplayInit(void)
{
  int ret = 0;
  
  //Initialize the Reset and DC GPIOs
  ret = ETX_SSD1306_ResetDcInit();
  
  if( ret >= 0 )
  {
    //Make the RESET Line to 0
    ETX_SSD1306_setRst( 0u );
    msleep(100);                          // delay
    //Make the DC Line to 1
    ETX_SSD1306_setRst( 1u );
    msleep(100);                          // delay
    
    /*
    ** Commands to initialize the SSD_1306 OLED Display
    */
    ETX_SSD1306_Write(true, 0xAE); // Entire Display OFF
    ETX_SSD1306_Write(true, 0xD5); // Set Display Clock Divide Ratio and Oscillator Frequency
    ETX_SSD1306_Write(true, 0x80); // Default Setting for Display Clock Divide Ratio and Oscillator Frequency that is recommended
    ETX_SSD1306_Write(true, 0xA8); // Set Multiplex Ratio
    ETX_SSD1306_Write(true, 0x3F); // 64 COM lines
    ETX_SSD1306_Write(true, 0xD3); // Set display offset
    ETX_SSD1306_Write(true, 0x00); // 0 offset
    ETX_SSD1306_Write(true, 0x40); // Set first line as the start line of the display
    ETX_SSD1306_Write(true, 0x8D); // Charge pump
    ETX_SSD1306_Write(true, 0x14); // Enable charge dump during display on
    ETX_SSD1306_Write(true, 0x20); // Set memory addressing mode
    ETX_SSD1306_Write(true, 0x00); // Horizontal addressing mode
    ETX_SSD1306_Write(true, 0xA1); // Set segment remap with column address 127 mapped to segment 0
    ETX_SSD1306_Write(true, 0xC8); // Set com output scan direction, scan from com63 to com 0
    ETX_SSD1306_Write(true, 0xDA); // Set com pins hardware configuration
    ETX_SSD1306_Write(true, 0x12); // Alternative com pin configuration, disable com left/right remap
    ETX_SSD1306_Write(true, 0x81); // Set contrast control
    ETX_SSD1306_Write(true, 0x80); // Set Contrast to 128
    ETX_SSD1306_Write(true, 0xD9); // Set pre-charge period
    ETX_SSD1306_Write(true, 0xF1); // Phase 1 period of 15 DCLK, Phase 2 period of 1 DCLK
    ETX_SSD1306_Write(true, 0xDB); // Set Vcomh deselect level
    ETX_SSD1306_Write(true, 0x20); // Vcomh deselect level ~ 0.77 Vcc
    ETX_SSD1306_Write(true, 0xA4); // Entire display ON, resume to RAM content display
    ETX_SSD1306_Write(true, 0xA6); // Set Display in Normal Mode, 1 = ON, 0 = OFF
    ETX_SSD1306_Write(true, 0x2E); // Deactivate scroll
    ETX_SSD1306_Write(true, 0xAF); // Display ON in normal mode
    
    // Clear the display
    ETX_SSD1306_ClearDisplay();
  }
    
  return( ret );
}

/****************************************************************************
 * Name: ETX_SSD1306_DisplayDeInit
 *
 * Details : This function De-initializes the Display
 ****************************************************************************/
void ETX_SSD1306_DisplayDeInit(void)
{
  ETX_SSD1306_ResetDcDeInit();  //Free the Reset and DC GPIO
}

ssd1306.h

/****************************************************************************//**
*  \file       ssd1306.h
*
*  \details    SSD1306 related macros (SPI)
*
*  \author     EmbeTronicX
*
*  \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/

#define SPI_BUS_NUM             (  1 )    // SPI 1

#define SSD1306_RST_PIN         (  24 )   // Reset pin is GPIO 24
#define SSD1306_DC_PIN          (  23 )   // Data/Command pin is GPIO 23
#define SSD1306_MAX_SEG         ( 128 )   // Maximum segment
#define SSD1306_MAX_LINE        (   7 )   // Maximum line
#define SSD1306_DEF_FONT_SIZE   (   5 )   // Default font size


extern int etx_spi_write( uint8_t data );

extern int  ETX_SSD1306_DisplayInit(void);
extern void ETX_SSD1306_DisplayDeInit(void);

void ETX_SSD1306_SetCursor( uint8_t lineNo, uint8_t cursorPos );
void ETX_SSD1306_GoToNextLine( void );
void ETX_SSD1306_PrintChar(unsigned char c);
void ETX_SSD1306_String(char *str);
void ETX_SSD1306_InvertDisplay(bool need_to_invert);
void ETX_SSD1306_SetBrightness(uint8_t brightnessValue);
void ETX_SSD1306_StartScrollHorizontal( bool is_left_scroll,
                                        uint8_t start_line_no,
                                        uint8_t end_line_no
                                      );
void ETX_SSD1306_StartScrollVerticalHorizontal( 
                                                bool is_vertical_left_scroll,
                                                uint8_t start_line_no,
                                                uint8_t end_line_no,
                                                uint8_t vertical_area,
                                                uint8_t rows
                                              );
void ETX_SSD1306_DeactivateScroll( void );
void ETX_SSD1306_fill( uint8_t data );
void ETX_SSD1306_ClearDisplay( void );
void ETX_SSD1306_PrintLogo( void );

Makefile

obj-m += spissd1306.o
spissd1306-objs := spi_ssd1306_driver.o ssd1306.o
 
KDIR = /lib/modules/$(shell uname -r)/build
 
 
all:
  make -C $(KDIR)  M=$(shell pwd) modules
 
clean:
  make -C $(KDIR)  M=$(shell pwd) clean

Testing the Device Driver

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod spissd1306.ko
  • See the Display. You should get some string that has been scrolling and after that image has to be filled.
  • Unload the driver using sudo rmmod spissd1306
  • See the Display has been cleared.

Note: If you want to change the image, you can follow this process (Video). You can print your desired image also instead of the EmbeTronicX logo.

Output Video

In our next tutorial, we will see how to write the SPI controller driver.

Please find the other Linux device driver tutorials here.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
Bootloader Tutorials

Reference

  • https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md
  • https://elinux.org/RPi_SPI
  • https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
  • https://github.com/STMicroelectronics/linux/blob/v5.10-stm32mp/Documentation/spi/spi-summary.rst
  • https://linux-sunxi.org/SPIdev
  • https://elixir.bootlin.com/linux/latest/source/drivers/spi/spi.c

 

5 1 vote
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x