How it works - notro/fbtft GitHub Wiki

Note: This is slowly getting out of tune, but the overview still holds true.

This is (going to be) a complete walkthrough of the inner workings of the fbtft module and driver.

Chapter 1 in Linux Device Drivers (online) is recommended reading if you don't know anything about device drivers.

Linux Cross Reference is a fine place to browse/search the Linux source code.

Summary

FBTFT

The fbtft kernel module is a layer between the driver and the framebuffer subsystem. It provides default functions for all framebuffer actions, except for the LCD Controller initialization. All functions can be overridden to fit a display with special needs.

Device

A device in the Linux kernel is a description of some entity, usually some hardware, like a disk or a keyboard.
fbtft knows about two device types for displays:

  • spi_device to represent a display on the SPI bus.
  • platform_device to represent a display that only uses GPIOs (parallel databus).

A device entity can also hold specific information about the device, like GPIOs needed etc. This is stored in the dev.platform field.

Platform and SPI devices can not be determined by enumeration like on a PCI or USB bus. Linux has to be told explicitly about their presence. This is usally done in the so called board or platform file. For the Raspberry Pi this is arch/arm/mach-bcm2708/bcm2708.c

Another possibility is that a kernel module registers the needed device. fbtft_device in the fbtft project can do this.

Driver

A device driver typically makes some hardware available to the operating system. This is done by providing functions that makes a link between an API and the hardware functions/registers. All Linux drivers are kernel modules, but not all modules are drivers.

Overview

    |=====================================================================================================|
    |  Filesystem                                                                                         |
    |                                        /dev/fb1                                                     |
    |                                            |                                                        |
    |=====================================================================================================|
    |  Framebuffer subsystem                     |                                                        |
    |                                    write, read, ioctl                                               |
    |                                            |                                                        |
    | FBTFT ==============================================================================================|
    |    fb_read    fb_write    fb_fillrect    fb_copyarea    fb_imageblit    fb_setcolreg    fb_blank    |
    |                  |              |            |              |                                       |
    |                  --------------------------------------------                                       |
    |                                            |                                                        |
    |                                    fbtftops.mkdirty                                                 |
    |                                            |                                                        |
    |                                    fbtftops.deferred_io                                             |
    |                                            |                                                        |
    |                                    fbtftops.update_display                                          |
    |                                            |                                                        |
    |                                            |--- fbtftops.set_addr_win                               |
    |                                            |                                                        |
    |=====================================================================================================|
    |  Bus                                       |                                                        |
    |                                    fbtftops.write_vmem                                              |
    |                                            |                                                        |
    |=====================================================================================================|
    |  I/O                                       |                                                        |
    |                                    fbtftops.write                                                   |
    |                                                                                                     |
    |=====================================================================================================|


    |=====================================================================================================|
    |  Driver                                                                                             |   
    |                                          probe                                                      |
    |                                            |                                                        |   
    |                                fbtft_register_framebuffer                                           | 
    |                                            |                                                        |    
    |                                            |---- fbtftops.request_gpios                             |
    |                                            |             |                                          |
    |                                            |     fbtftops.request_gpios_match                       |
    |                                            |                                                        |
    |                                            |                                                        |  
    |                                    fbtftops.init_display                                            |
    |                                            |                                                        | 
    |                                            |---- fbtftops.reset                                     |  
    |                                            |                                                        |    
    |                                            |                                                        |  
    |=====================================================================================================|
    |  Bus                                       |                                                        |
    |                                    fbtftops.write_reg                                               |
    |                                    fbtftops.write_data_command                                      |
    |                                            |                                                        |
    |=====================================================================================================|
    |  I/O                                       |                                                        |
    |                                    fbtftops.write                                                   |
    |                                                                                                     |
    |=====================================================================================================|

Easy LCD

For this walkthrough we use an imagninary display: Easy LCD. This display has a SPI interface, a signal for D/C (data/command bit) and a signal for backlight (LED).

Complete code listing for the driver: easyfb.c, is found at the end of this page.

Device

Linux needs to know about the display. This can be done in the platform file arch/arm/mach-bcm2708/bcm2708.c

static struct spi_board_info bcm2708_spi_devices[] = {
	{
		.modalias = "easyfb",
		.max_speed_hz = 32000000,
		.bus_num = 0,
		.chip_select = 0,
		.mode = SPI_MODE_0,
		.platform_data = &(struct fbtft_platform_data) {
			.gpios = (const struct fbtft_gpio []) {
				{ "reset", 1 },
				{ "dc", 2 },
				{ "led", 3 },
				{},
			},
		}
	}, {
		.modalias = "spidev",
		.max_speed_hz = 500000,
		.bus_num = 0,
		.chip_select = 1,
		.mode = SPI_MODE_0,
	}
};

void __init bcm2708_init(void)
{
	spi_register_board_info(bcm2708_spi_devices, ARRAY_SIZE(bcm2708_spi_devices));
}

This results in a spi_device being registered with info about some GPIOs. spi_board_info is turned into a spi_device by spi_register_board_info().

Driver build configuration

For the driver to be known to Linux, the build system needs some information.

Kconfig is the system used by Linux to determine how the kernel is build

config FB_EASY
	tristate "FB driver for the Easy LCD display"
	depends on FB_TFT
	help
	  Framebuffer support for the easy LCD display in 8-bit SPI mode.

This section tells the build system about the easyfb driver by using a configuration variable: FB_EASY.

  • tristate means it can go into the kernel or be build as a module
  • depends states that this config element is only abailable if FB_TFT is set.

The config section has a matching line in the Makefile

obj-$(CONFIG_FB_EASY)            += easyfb.o

The value of CONFIG_FB_EASY is 'y' if to be compiled into the kernel or 'm' if as loadable module. So obj-y's go into the kernel and obj-m's becomes loadable modules.

Driver boilerplate code

Now the configuration system knows about the driver. Next we need a file easyfb.c that presents it self as a module and tells which device(s) it supports.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>

#include "fbtft.h"

This includes the header definitions needed by the driver. module.h, kernel.h and init.h is required by all drivers. spi.h is needed because we support a SPI device, and delay.h because we use mdelay() in the init code.

#define DRVNAME	    "easyfb"
#define WIDTH       128
#define HEIGHT      160
#define BPP         16
#define FPS			10

Here we set the name of the driver, width and height of the display, bits per pixel, and frames per second.

/* Module Parameter: debug  (also available through sysfs) */
MODULE_PARM_DEBUG;

This macro expands to:

static unsigned long debug = 0;
module_param(debug, ulong , 0664);
MODULE_PARM_DESC(debug,"level: 0-7 (the remaining 29 bits is for advanced usage)");

Which defines a kernel module argument: debug.

module_init(easyfb_init);
module_exit(easyfb_exit);

This defines the functions that should be used when the module is loaded and unloaded.

MODULE_DESCRIPTION("FB driver for the Easy LCD display");
MODULE_AUTHOR("Noralf Tronnes");
MODULE_LICENSE("GPL");

This sets some information about the module.

This information can be viewed with modinfo easyfb

filename:       /usr/src/linux/drivers/video/fbtft/easyfb.ko
license:        GPL
author:         Noralf Tronnes
description:    FB driver for the Easy LCD display
srcversion:     CADCD614FF8B93FBF006577
depends:        fbtft
vermagic:       3.6.11+ preempt mod_unload modversions ARMv6
parm:           debug:level: 0-7 (the remaining 29 bits is for advanced usage) (ulong)

Driver load

This is done automatically if the driver is compiled into the kernel, or when modprobe easyfb is used.

static struct spi_driver easyfb_driver = {
	.driver = {
		.name   = DRVNAME,
		.owner  = THIS_MODULE,
	},
	.probe  = easyfb_probe,
	.remove = __devexit_p(easyfb_remove),
};

This describes the driver as the kernel sees it:

  • It's a SPI driver
  • It has name='easyfb' which also specifies the device this driver supports, since there is no id_table entry.
  • probe tells which function to call when the kernel sees a device which this driver supports.
  • remove is called when the device is removed, or if the driver is unloaded while connected to a device.
static int __init easyfb_init(void)
{
	fbtft_pr_debug("\n\n"DRVNAME": %s()\n", __func__);
	return spi_register_driver(&easyfb_driver);
}

As stated by module_init this function gets called when the driver is loaded.

fbtft_pr_debug does two things:

  • expands the shorthand debug levels: 1-7, to a debug bitmap
  • prints the message if debug & DEBUG_DRIVER_INIT_FUNCTIONS

spi_register_driver informs the kernel about this driver, and which device it supports. This is what makes this kernel module a driver.

Driver probe

struct fbtft_display easyfb_display = {
	.width = WIDTH,
	.height = HEIGHT,
	.bpp = BPP,
	.fps = FPS,
};

This is the structure used to tell fbtft about the display the driver supports

easyfb_probe()

static int __devinit easyfb_probe(struct spi_device *spi)
{
	struct fb_info *info;
	struct fbtft_par *par;
	int ret;

	fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "%s()\n", __func__);

fbtft_dev_dbg uses dev_info to display the message if debug & DEBUG_DRIVER_INIT_FUNCTIONS

	info = fbtft_framebuffer_alloc(&easyfb_display, &spi->dev);
	if (!info)
		return -ENOMEM;

Here we allocate the memory needed for this driver to work.

easyfb_probe()->fbtft_framebuffer_alloc()

struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, struct device *dev)
{
	struct fb_info *info;
	struct fbtft_par *par;
	struct fb_ops *fbops = NULL;
	struct fb_deferred_io *fbdefio = NULL;
	struct fbtft_platform_data *pdata = dev->platform_data;
	u8 *vmem = NULL;
	void *txbuf = NULL;
	void *buf = NULL;
	int txbuflen = display->txbuflen;
	unsigned bpp = display->bpp;
	unsigned fps = display->fps;
	int vmem_size;

	/* defaults */
	if (!fps)
		fps = 20;
	if (!bpp)
		bpp = 16;

	vmem_size = display->width*display->height*bpp/8;
  • txbuflen defines the size of the transmit buffer
  • bpp - Bit per Pixel, default 16
  • fps - Frames per second, default 20
  • vmem_size - Size of video memory
	/* platform_data override ? */
	if (pdata) {
		if (pdata->fps)
			fps = pdata->fps;
		if (pdata->txbuflen)
			txbuflen = pdata->txbuflen;
	}

It is possible to override fps and txbuflen set in the driver through platform_data

	vmem = vzalloc(vmem_size);
	if (!vmem)
		goto alloc_fail;

	fbops = kzalloc(sizeof(struct fb_ops), GFP_KERNEL);
	if (!fbops)
		goto alloc_fail;

	fbdefio = kzalloc(sizeof(struct fb_deferred_io), GFP_KERNEL);
	if (!fbdefio)
		goto alloc_fail;

	buf = vzalloc(128);
	if (!buf)
		goto alloc_fail;

	info = framebuffer_alloc(sizeof(struct fbtft_par), dev);
	if (!info)
		goto alloc_fail;

	info->screen_base = (u8 __force __iomem *)vmem;
	info->fbops = fbops;
	info->fbdefio = fbdefio;

Allocate memory to

  • vmem - Video memory
  • fbops - Holds pointers to the functions used by fbtft to talk to the display
  • fbdefio - Structure used for info about Deferred IO
  • buf - Small buffer used to send init data to the display

framebuffer_alloc() allocates memory to the main framebuffer structure struct fb_info.
In addtion it allocates memory to the main fbtft driver structure struct fbtft_par, and lets the par field point to this.

struct fbtft_par {
	struct fbtft_display *display;
	struct spi_device *spi;
	struct platform_device *pdev;
	struct fb_info *info;
	struct fbtft_platform_data *pdata;
	u16 *ssbuf;
    u32 pseudo_palette[16];
	struct {
		void *buf;
		size_t len;
	} txbuf;
	u8 *buf;  /* small buffer used when writing init data over SPI */
	struct fbtft_ops fbtftops;
	unsigned dirty_lines_start;
	unsigned dirty_lines_end;
	struct {
		int reset;
		int dc;
		/* the following is not used or requested by core */
		int rd;
		int wr;
		int cs;
		int db[16];
		int led[16];
		int aux[16];
	} gpio;
	unsigned long *debug;
	unsigned long current_debug;
	bool first_update_done;
	void *extra;
};

easyfb_probe()->fbtft_framebuffer_alloc()

	fbops->owner        =      dev->driver->owner;
	fbops->fb_read      =      fb_sys_read;
	fbops->fb_write     =      fbtft_fb_write;
	fbops->fb_fillrect  =      fbtft_fb_fillrect;
	fbops->fb_copyarea  =      fbtft_fb_copyarea;
	fbops->fb_imageblit =      fbtft_fb_imageblit;
	fbops->fb_setcolreg =      fbtft_fb_setcolreg;
	fbops->fb_blank     =      fbtft_fb_blank;

Set the framebuffer operations/functions to the default fbtft functions.

	fbdefio->delay =           HZ/fps;
	fbdefio->deferred_io =     fbtft_deferred_io;
	fb_deferred_io_init(info);

Initialize Deferred IO.

	strncpy(info->fix.id, dev->driver->name, 16);
	info->fix.type =           FB_TYPE_PACKED_PIXELS;
	info->fix.visual =         FB_VISUAL_TRUECOLOR;
	info->fix.xpanstep =	   0;
	info->fix.ypanstep =	   0;
	info->fix.ywrapstep =	   0;
	info->fix.line_length =    display->width*display->bpp/8;
	info->fix.accel =          FB_ACCEL_NONE;
	info->fix.smem_len =       vmem_size;

	info->var.xres =           display->width;
	info->var.yres =           display->height;
	info->var.xres_virtual =   display->width;
	info->var.yres_virtual =   display->height;
	info->var.bits_per_pixel = display->bpp;
	info->var.nonstd =         1;

	// RGB565
	info->var.red.offset =     11;
	info->var.red.length =     5;
	info->var.green.offset =   5;
	info->var.green.length =   6;
	info->var.blue.offset =    0;
	info->var.blue.length =    5;
	info->var.transp.offset =  0;
	info->var.transp.length =  0;

	info->flags =              FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;

Set default values for various fb_info fields.

	par = info->par;
	par->info = info;
	par->display = display;
	par->pdata = dev->platform_data;
	par->buf = buf;
	// Set display line markers as dirty for all. Ensures first update to update all of the display.
	par->dirty_lines_start = 0;
	par->dirty_lines_end = par->info->var.yres - 1;

info->par holds the driver specific data.

    info->pseudo_palette = par->pseudo_palette;

Used by setcolreg.

	// Transmit buffer
	if (txbuflen == -1)
		txbuflen = vmem_size;

#ifdef __LITTLE_ENDIAN
	if ((!txbuflen) && (display->bpp > 8))
		txbuflen = PAGE_SIZE; /* need buffer for byteswapping */
#endif

	if (txbuflen) {
		txbuf = vzalloc(txbuflen);
		if (!txbuf)
			goto alloc_fail;
		par->txbuf.buf = txbuf;
	}
	par->txbuf.len = txbuflen;

This part allocates memory to the transmit buffer if:

  • the driver has asked for it
  • platform_data has asked for it
  • we are Little Endian and need to swap the 16-bit pixel value before transmitting to the display. Displays are always Big Endian.

The special value -1 means allocate the same amount as video memory.

	// default fbtft operations
	par->fbtftops.write = fbtft_write_spi;
	par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
	par->fbtftops.write_data_command = fbtft_write_data_command8_bus8;
	par->fbtftops.write_reg = fbtft_write_reg8_bus8;
	par->fbtftops.set_addr_win = fbtft_set_addr_win;
	par->fbtftops.reset = fbtft_reset;
	par->fbtftops.mkdirty = fbtft_mkdirty;
	par->fbtftops.update_display = fbtft_update_display;
	par->fbtftops.request_gpios = fbtft_request_gpios;
	par->fbtftops.free_gpios = fbtft_free_gpios;

	return info;

These are the default functions used to display data. The driver can override these at will.

alloc_fail:
	if (vmem)
		vfree(vmem);
	if (txbuf)
		vfree(txbuf);
	if (buf)
		vfree(buf);
	if (fbops)
		kfree(fbops);
	if (fbdefio)
		kfree(fbdefio);

	return NULL;
}

Free memory on failure.

easyfb_probe() continued

	par = info->par;
	par->spi = spi;
	fbtft_debug_init(par);
	par->fbtftops.init_display = easyfb_init_display;
	par->fbtftops.verify_gpios = easyfb_verify_gpios;
	par->fbtftops.register_backlight = fbtft_register_backlight;
  • record info about the SPI device
  • let fbtft know about the debug variable, so the fbtft functions can use it.
  • set driver specific functions

At this point the driver is free to change the defaults provided by fbtft, before the framebuffer is registred.

	ret = fbtft_register_framebuffer(info);
	if (ret < 0)
		goto out_release;

	return 0;

Register framebuffer.

easyfb_probe()->fbtft_register_framebuffer()

int fbtft_register_framebuffer(struct fb_info *fb_info)
{
	int ret;
	char text1[50] = "";
	char text2[50] = "";
	struct fbtft_par *par = fb_info->par;
	struct spi_device *spi = par->spi;

	// sanity checks
	if (!par->fbtftops.init_display) {
		dev_err(fb_info->device, "missing fbtftops.init_display()\n");
		return -EINVAL;
	}
	if (!par->debug)
		par->debug = &dummy_debug;

	if (spi)
		spi_set_drvdata(spi, fb_info);
	if (par->pdev)
		platform_set_drvdata(par->pdev, fb_info);

Make sure

  • the driver has provided an init_display() function
  • if the driver doesn't support dynamic debug, provide variable that is set to zero.
  • on the device, record info about the framebuffer. Used in driver remove to release the framebuffer.
	ret = par->fbtftops.request_gpios(par);
	if (ret < 0)
		goto reg_fail;

Request the GPIOs mentioned in platform_data

easyfb_probe()->fbtft_register_framebuffer()->fbtft_request_gpios()

int fbtft_request_gpios(struct fbtft_par *par)
{
	struct fbtft_platform_data *pdata = par->pdata;
	const struct fbtft_gpio *gpio;
	unsigned long flags;
	int i;
	int ret;

	/* Initialize gpios to disabled */
	par->gpio.reset = -1;
	par->gpio.dc = -1;
	par->gpio.rd = -1;
	par->gpio.wr = -1;
	par->gpio.cs = -1;
	for (i=0;i<16;i++) {
		par->gpio.db[i] = -1;
		par->gpio.led[i] = -1;
		par->gpio.aux[i] = -1;
	}

Initialize gpio values to -1, meaning unused.

	if (pdata && pdata->gpios) {
		gpio = pdata->gpios;
		while (gpio->name[0]) {
			flags = FBTFT_GPIO_NO_MATCH;
			/* if driver provides match function, try it first, if no match use our own */
			if (par->fbtftops.request_gpios_match)
				flags = par->fbtftops.request_gpios_match(par, gpio);
			if (flags == FBTFT_GPIO_NO_MATCH)
				flags = fbtft_request_gpios_match(par, gpio);
			if (flags != FBTFT_GPIO_NO_MATCH) {
				ret = gpio_request_one(gpio->gpio, flags, par->info->device->driver->name);
				if (ret < 0) {
					dev_err(par->info->device, "%s: no match for '%s' GPIO%d\n", __func__, gpio->name, gpio->gpio);
					return ret;
				}
				fbtft_fbtft_dev_dbg(DEBUG_REQUEST_GPIOS, par, par->info->device, "%s: '%s' = GPIO%d\n", __func__, gpio->name, gpio->gpio);
			}
			gpio++;
		}
	}

	return 0;
}

Go through all GPIOs named in platform_data to try and find a match.

If the driver has provided request_gpios_match, it will be called before fbtft_request_gpios_match().
This way the driver can override the default assignements, and provide new ones.

easyfb_probe()->fbtft_register_framebuffer()->fbtft_request_gpios()->fbtft_request_gpios_match()

unsigned long fbtft_request_gpios_match(struct fbtft_par *par, const struct fbtft_gpio *gpio)
{
	int ret;
	long val;

	fbtft_fbtft_dev_dbg(DEBUG_REQUEST_GPIOS_MATCH, par, par->info->device, "%s('%s')\n", __func__, gpio->name);

	if (strcasecmp(gpio->name, "reset") == 0) {
		par->gpio.reset = gpio->gpio;
		return GPIOF_OUT_INIT_HIGH;
	}
	else if (strcasecmp(gpio->name, "dc") == 0) {
		par->gpio.dc = gpio->gpio;
		return GPIOF_OUT_INIT_LOW;
	}
	else if (strcasecmp(gpio->name, "cs") == 0) {
		par->gpio.cs = gpio->gpio;
		return GPIOF_OUT_INIT_HIGH;
	}
	else if (strcasecmp(gpio->name, "wr") == 0) {
		par->gpio.wr = gpio->gpio;
		return GPIOF_OUT_INIT_HIGH;
	}
	else if (strcasecmp(gpio->name, "rd") == 0) {
		par->gpio.rd = gpio->gpio;
		return GPIOF_OUT_INIT_HIGH;
	}
	else if (gpio->name[0] == 'd' && gpio->name[1] == 'b') {
		ret = kstrtol(&gpio->name[2], 10, &val);
		if (ret == 0 && val < 16) {
			par->gpio.db[val] = gpio->gpio;
			return GPIOF_OUT_INIT_LOW;
		}
	}
	else if (strcasecmp(gpio->name, "led") == 0) {
		par->gpio.led[0] = gpio->gpio;
		return GPIOF_OUT_INIT_LOW;
	}

	return FBTFT_GPIO_NO_MATCH;
}

If a matching name is found

  • set the corresponding gpio field to the GPIO number
  • return the initial state of the GPIO pin

If no match is found, say so.

easyfb_probe()->fbtft_register_framebuffer() continues

	if (par->fbtftops.verify_gpios) {
		ret = par->fbtftops.verify_gpios(par);
		if (ret < 0)
			goto reg_fail;
	}

If the driver has provided a verify_gpios function, call it. This is used to ensure that essential pins is present.

easyfb_probe()->fbtft_register_framebuffer()->easyfb_verify_gpios()

static int easyfb_verify_gpios(struct fbtft_par *par)
{
	fbtft_dev_dbg(DEBUG_VERIFY_GPIOS, par->info->device, "%s()\n", __func__);

	if (par->gpio.dc < 0) {
		dev_err(par->info->device, "Missing info about 'dc' gpio. Aborting.\n");
		return -EINVAL;
	}

	return 0;
}

Make sure D/C is present.

easyfb_probe()->fbtft_register_framebuffer() continues

	ret = par->fbtftops.init_display(par);
	if (ret < 0)
		goto reg_fail;

Initialize display.

easyfb_probe()->fbtft_register_framebuffer()->easyfb_init_display()

static int easyfb_init_display(struct fbtft_par *par)
{
	fbtft_dev_dbg(DEBUG_INIT_DISPLAY, par->info->device, "%s()\n", __func__);

	par->fbtftops.reset(par);

Reset display.

easyfb_probe()->fbtft_register_framebuffer()->easyfb_init_display()->fbtft_reset()

void fbtft_reset(struct fbtft_par *par)
{
	if (par->gpio.reset == -1)
		return;
	fbtft_fbtft_dev_dbg(DEBUG_RESET, par, par->info->device, "%s()\n", __func__);
	gpio_set_value(par->gpio.reset, 0);
	udelay(20);
	gpio_set_value(par->gpio.reset, 1);
	mdelay(120);
}

If a reset pin is present, pulse it low.

easyfb_probe()->fbtft_register_framebuffer()->easyfb_init_display() continues

	// SWRESET - Software reset
	write_reg(par, 0x01);
	mdelay(150);

	write_reg(par, 0x09, 0x01, 0x02, 0x03);

	return 0;
}

Write init data into the LCD Controller registers.

The first write_reg writes a command, and the next writes a command byte and 3 data bytes.

write_reg is a macro, and the above expands to:

 par->fbtftops.write_reg(par, (sizeof((int[]){0x01})/sizeof(int)), 0x01);

 par->fbtftops.write_reg(par, (sizeof((int[]){0x09, 0x01, 0x02, 0x03})/sizeof(int)), 0x09, 0x01, 0x02, 0x03);

par->fbtftops.write_reg => fbtft_write_reg8_bus8

easyfb_probe()->fbtft_register_framebuffer()->easyfb_init_display()->fbtft_write_reg8_bus8()
This function is created by a macro

void fbtft_write_reg8_bus8(struct fbtft_par *par, int len, ...)
{
    va_list args;
    int i, ret;
    u8 *buf = (u8 *)par->buf;

    if (*par->debug & DEBUG_WRITE_DATA_COMMAND) {
        va_start(args, len);
        for (i=0;i<len;i++) {
            buf[i] = (u8)va_arg(args, unsigned int);
        }
        va_end(args);
        fbtft_dev_dbg_hex(DEBUG_WRITE_DATA_COMMAND, par, par->info->device, u8, buf, len, "%s: ", __func__);
    }

    va_start(args, len);

    *buf = (u8)va_arg(args, unsigned int);
    if (par->gpio.dc != -1)
        gpio_set_value(par->gpio.dc, 0);
    ret = par->fbtftops.write(par, par->buf, sizeof(u8));
    if (ret < 0) {
        va_end(args);
        dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret);
        return;
    }
    len--;

    if (len) {
        i = len;
        while (i--) {
            *buf++ = (u8)va_arg(args, unsigned int);
        }
        if (par->gpio.dc != -1)
            gpio_set_value(par->gpio.dc, 1);
            ret = par->fbtftops.write(par, par->buf, len * sizeof(u8));
        if (ret < 0) {
            va_end(args);
            dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret);
            return;
        }
    }
    va_end(args);
}

D/C is 0 for command and 1 for data
par->fbtftops.write => fbtft_write_spi

easyfb_probe()->fbtft_register_framebuffer()->easyfb_init_display()->fbtft_write_reg8_bus8()->fbtft_write_spi()

int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len)
{
	fbtft_dev_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, "%s(len=%d): ", __func__, len);

	if (!par->spi) {
		dev_err(par->info->device, "%s: par->spi is unexpectedly NULL\n", __func__);
		return -1;
	}
	return spi_write(par->spi, buf, len);
}

Pass the data on to the SPI subsystem.

easyfb_probe()->fbtft_register_framebuffer() continues

	par->fbtftops.update_display(par);

The video memory is zeroed on allocation. Make sure the display is updated and thereby cleared.

update_display is addressed later as part of the framebuffer operations.

	if (par->fbtftops.register_backlight)
		par->fbtftops.register_backlight(par);

Call register_backlight if set.

easyfb_probe()->fbtft_register_framebuffer()->fbtft_register_backlight()

void fbtft_register_backlight(struct fbtft_par *par)
{
	struct backlight_device *bd;
	struct backlight_properties bl_props = { 0, };
	struct backlight_ops *bl_ops;

	fbtft_fbtft_dev_dbg(DEBUG_BACKLIGHT, par, par->info->device, "%s()\n", __func__);

	if (par->gpio.led[0] == -1) {
		fbtft_fbtft_dev_dbg(DEBUG_BACKLIGHT, par, par->info->device, "%s(): led pin not set, exiting.\n", __func__);
		return;
	}

	bl_ops = kzalloc(sizeof(struct backlight_ops), GFP_KERNEL);
	if (!bl_ops) {
		dev_err(par->info->device, "%s: could not allocate memeory for backlight operations.\n", __func__);
		return;
	}

Make sure the LED pin is set, and allocate memory for the operations structure.

	bl_ops->get_brightness = fbtft_backlight_get_brightness;
	bl_ops->update_status = fbtft_backlight_update_status;
	bl_props.type = BACKLIGHT_RAW;
	/* Assume backlight is off, get polarity from current state of pin */
	bl_props.power = FB_BLANK_POWERDOWN;
	if (!gpio_get_value(par->gpio.led[0]))
		bl_props.state |= BL_CORE_DRIVER1;

Set default functions for the backlight operations.

This function assumes that the LED pin is in 'Backlight off' state. It reads the GPIO to determine the polarity of the pin, to determine whether Active Backlight is High or Low.

	bd = backlight_device_register(dev_driver_string(par->info->device), par->info->device, par, bl_ops, &bl_props);
	if (IS_ERR(bd)) {
		dev_err(par->info->device, "cannot register backlight device (%ld)\n", PTR_ERR(bd));
		goto failed;
	}
	par->info->bl_dev = bd;

	if (!par->fbtftops.unregister_backlight)
		par->fbtftops.unregister_backlight = fbtft_unregister_backlight;

	return;

failed:
	if (bl_ops)
		kfree(bl_ops);
}
  • Register the backlight device
  • Provide default unregister_backlight function if needed

easyfb_probe()->fbtft_register_framebuffer() continues

	ret = register_framebuffer(fb_info);
	if (ret < 0)
		goto reg_fail;

Register framebuffer with the kernel.

	// [Tue Jan  8 19:36:41 2013] graphics fb1: hx8340fb frame buffer, 75 KiB video memory, 151 KiB buffer memory, spi0.0 at 32 MHz
	if (par->txbuf.buf)
		sprintf(text1, ", %d KiB buffer memory", par->txbuf.len >> 10);
	if (spi)
		sprintf(text2, ", spi%d.%d at %d MHz", spi->master->bus_num, spi->chip_select, spi->max_speed_hz/1000000);
	dev_info(fb_info->dev, "%s frame buffer, %d KiB video memory%s, fps=%lu%s\n",
		fb_info->fix.id, fb_info->fix.smem_len >> 10, text1, HZ/fb_info->fbdefio->delay, text2);

Print message in the kernel log about the framebuffer.

	/* Turn on backlight if available */
	if (fb_info->bl_dev) {
		fb_info->bl_dev->props.power = FB_BLANK_UNBLANK;
		fb_info->bl_dev->ops->update_status(fb_info->bl_dev);
	}

	return 0;

Turn on backlight if device is present and return.

update_status is addressed later.

reg_fail:
	if (spi)
		spi_set_drvdata(spi, NULL);
	if (par->pdev)
		platform_set_drvdata(par->pdev, NULL);
	par->fbtftops.free_gpios(par);

	return ret;
}

Handle failure.

easyfb_probe() continues

out_release:
	fbtft_framebuffer_release(info);

	return ret;
}

Handle failure.

Now the framebuffer is registered with the kernel and present in the filesystem as /dev/fbX.

Device remove

If the device is removed, the following happens

easyfb_remove()

static int __devexit easyfb_remove(struct spi_device *spi)
{
	struct fb_info *info = spi_get_drvdata(spi);

	fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "%s()\n", __func__);

	if (info) {
		fbtft_unregister_framebuffer(info);
		fbtft_framebuffer_release(info);
	}

	return 0;
}

easyfb_remove()->fbtft_unregister_framebuffer()

int fbtft_unregister_framebuffer(struct fb_info *fb_info)
{
	struct fbtft_par *par = fb_info->par;
	struct spi_device *spi = par->spi;
	int ret;

	if (spi)
		spi_set_drvdata(spi, NULL);
	if (par->pdev)
		platform_set_drvdata(par->pdev, NULL);

Don't store info about the framebuffer anymore.

	par->fbtftops.free_gpios(par);

Free the previously requested GPIOs.

easyfb_remove()->fbtft_unregister_framebuffer()->fbtft_free_gpios()

void fbtft_free_gpios(struct fbtft_par *par)
{
	struct fbtft_platform_data *pdata = NULL;
	const struct fbtft_gpio *gpio;

	fbtft_fbtft_dev_dbg(DEBUG_FREE_GPIOS, par, par->info->device, "%s()\n", __func__);

	if(par->spi)
		pdata = par->spi->dev.platform_data;
	if (par->pdev)
		pdata = par->pdev->dev.platform_data;

	if (pdata && pdata->gpios) {
		gpio = pdata->gpios;
		while (gpio->name[0]) {
			dev_dbg(par->info->device, "fbtft_free_gpios: freeing '%s'\n", gpio->name);
			gpio_direction_input(gpio->gpio);  /* if the gpio wasn't recognized by request_gpios, WARN() will protest */
			gpio_free(gpio->gpio);
			gpio++;
		}
	}
}

Loop through the pin names in platform_data and free them.
Also set them as inputs, so they don't drive anything.

Note: If request_gpios fail, free_gpios will be called. The GPIOs not requested will be set as inputs here. This results in a WARN dump in the kernel log about GPIOs not being requested.

easyfb_remove()->fbtft_unregister_framebuffer() continued

	ret = unregister_framebuffer(fb_info);

Unregister the framebuffer. /dev/fbX is no longer present.

	if (par->fbtftops.unregister_backlight)
		par->fbtftops.unregister_backlight(par);
	return ret;
}

Unregister the backlight device if needed. And return.

easyfb_remove()->fbtft_unregister_framebuffer()->fbtft_unregister_backlight()

void fbtft_unregister_backlight(struct fbtft_par *par)
{
	const struct backlight_ops *bl_ops;

	fbtft_fbtft_dev_dbg(DEBUG_BACKLIGHT, par, par->info->device, "%s()\n", __func__);

	if (par->info->bl_dev) {
		bl_ops = par->info->bl_dev->ops;
		backlight_device_unregister(par->info->bl_dev);
		par->info->bl_dev = NULL;
		kfree(bl_ops);
	}
}

Unregister backlight device and free memory.

Module unload

Unload module rmmod easyfb.

static void __exit easyfb_exit(void)
{
	fbtft_pr_debug(DRVNAME": %s()\n", __func__);
	spi_unregister_driver(&easyfb_driver);
}

Unregister the driver.

=====================================================================================================================================================

easyfb.c

/*
 * FB driver for the Easy LCD display
 *
 * Copyright (C) 2013 Noralf Tronnes
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>

#include "fbtft.h"

#define DRVNAME	    "easyfb"
#define WIDTH       128
#define HEIGHT      160


/* Module Parameter: debug  (also available through sysfs) */
MODULE_PARM_DEBUG;

static int easyfb_init_display(struct fbtft_par *par)
{
	fbtft_dev_dbg(DEBUG_INIT_DISPLAY, par->info->device, "%s()\n", __func__);

	par->fbtftops.reset(par);

	// SWRESET - Software reset
	write_reg(par, 0x01);
	mdelay(150);

	write_reg(par, 0x09, 0x01, 0x02, 0x03);

	return 0;
}


struct fbtft_display easyfb_display = {
	.width = WIDTH,
	.height = HEIGHT,
};

static int easyfb_probe(struct spi_device *spi)
{
	struct fb_info *info;
	struct fbtft_par *par;
	int ret;

	fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "%s()\n", __func__);

	info = fbtft_framebuffer_alloc(&easyfb_display, &spi->dev);
	if (!info)
		return -ENOMEM;

	par = info->par;
	par->spi = spi;
	fbtft_debug_init(par);
	par->fbtftops.init_display = easyfb_init_display;
	par->fbtftops.verify_gpios = easyfb_verify_gpios;
	par->fbtftops.register_backlight = fbtft_register_backlight;

	ret = fbtft_register_framebuffer(info);
	if (ret < 0)
		goto out_release;

	return 0;

out_release:
	fbtft_framebuffer_release(info);

	return ret;
}

static int easyfb_remove(struct spi_device *spi)
{
	struct fb_info *info = spi_get_drvdata(spi);

	fbtft_dev_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, &spi->dev, "%s()\n", __func__);

	if (info) {
		fbtft_unregister_framebuffer(info);
		fbtft_framebuffer_release(info);
	}

	return 0;
}

static struct spi_driver easyfb_driver = {
	.driver = {
		.name   = DRVNAME,
		.owner  = THIS_MODULE,
	},
	.probe  = easyfb_probe,
	.remove = easyfb_remove,
};

static int __init easyfb_init(void)
{
	fbtft_pr_debug("\n\n"DRVNAME": %s()\n", __func__);
	return spi_register_driver(&easyfb_driver);
}

static void __exit easyfb_exit(void)
{
	fbtft_pr_debug(DRVNAME": %s()\n", __func__);
	spi_unregister_driver(&easyfb_driver);
}

/* ------------------------------------------------------------------------- */

module_init(easyfb_init);
module_exit(easyfb_exit);

MODULE_DESCRIPTION("FB driver for the Easy LCD display");
MODULE_AUTHOR("Noralf Tronnes");
MODULE_LICENSE("GPL");

piwik