HOWTO: Convert an SPI framebuffer driver to stereoscopic
November 21, 2013
At least one person was interested in converting an existing SPI-based Linux framebuffer driver to stereoscopic after I posted the View-Master Video Player. Here’s a quick write-up on how it works.
The Problem
We have two displays that we want to combine into a single framebuffer device, so that software like mplayer or X.org can send data to both devices without knowing that they’re doing so.
SPI
Many small displays are connected to the host computer with a bus standard called SPI.
SPI is a serial bus using 3 or 4 wires: clock, TX (called MOSI for master-out-slave-in), and RX (called MISO for master-in-slave-out), and CS (chip select). The chip select line is used to allow one SPI bus to communicate with several devices:
I don’t know much about the Linux driver model — there are probably several ways of cracking this nut — but the problem I encountered was that the device model plays nice when one driver talks to one device, but in this case we want to write a single driver that talks to two devices. In addition to just writing to each separately, it would be nice to be able to address them simultaneously (in order to play regular monocular video). [I haven’t added support for this into the driver yet, but it would be very easy to do so.]
Normally Linux’ SPI driver takes care of all the details, including toggling the CS lines. You configure the driver with parameters that tell it which CS line the device is attached to, and in addition to driving the clock and formatting the data, it’ll take care of switching CS so that only one device is paying attention at any given time. This is a problem because it maintains control over CS within the SPI device driver — e.g. the bcm2708 chip in the case of the Raspberry Pi — and we want more control than it offers.
Luckily there’s an SPI mode called SPI_NO_CS which tells the driver not to manage CS lines. With this mode enabled, we can take control over CS switching concerns within the display driver like we would any GPIO line.
FBTFT
The work I did is based on the FBTFT library, which is a set of drivers for small displays and some glue to take care of common concerns. I’ll proceed assuming that there’s already an FBTFT driver for a single display, but we want to create another for binocular use.
The fbtft_device.c file is what brings all the drivers together into a single place and configures them. Here is the configuration section for the regular SSD1331 driver:
{ .name = "ssd1331", .spi = &(struct spi_board_info) { .modalias = "fb_ssd1331", .max_speed_hz = 20000000, .mode = SPI_MODE_3, .platform_data = &(struct fbtft_platform_data) { .display = { .buswidth = 8, }, .gpios = (const struct fbtft_gpio []) { { "reset", 24 }, { "dc", 25 }, {}, }, } } }
These are default configuration parameters that can be overridden using module parameters. The CS pin number is not specified here; it’s specified when loading the module using the “cs=…” parameter.
The SSD1331 driver already uses a couple of GPIO lines in addition to SPI: RESET (GPIO pin 24) and DC (GPIO pin 25). In my binocular driver I assumed that these would not be shared, i.e. each display has its own RESET and DC pins, though that’s not necessary; they could both be shared.
Note that there’s already some SPI configuration (mode = SPI_MODE_3). This tells SPI what signalling conventions to use — this display requires mode 3.
Let’s start by duplicating this configuration block and introducing the binocular driver to the FBTFT driver:
{ .name = "ssd1331b", .spi = &(struct spi_board_info) { .modalias = "fb_ssd1331b", .max_speed_hz = 2000000, .mode = SPI_MODE_3 | SPI_NO_CS, .platform_data = &(struct fbtft_platform_data) { .display = { .buswidth = 8, }, } } }
This is identical to the above, except:
- We’ve given it a new name and modalias (ssd1331b, with “b” for binocular)
- We’ve added the SPI_NO_CS mode to disable built-in CS signalling
- We haven’t specified pin numbers for GPIOs — for the moment I’ve hard-coded those into the driver.
A note on default functions
The FBTFT libary allows individual display drivers to specify what functions are used to perform various operations, and offers default implementations for cases where drivers don’t specify them.
The default implementations are specified in fbtft-core.c.
If a driver wants to use its own implementation rather than the default, then the replacement function should be implemented in that specific driver, then the default should be specified to FBTFT using the fbtftops mapping.
Much of the meat of the binocular driver is based on copies of the default functions that are brought into the binocular driver and then modified there.
The ssd1331b driver
Now let’s look at the binocular driver ssd1331b.c itself. The full source is on github.com, and I’ll link to individual lines of code there for readability.
I started with a copy of the original ssd1331.c driver and made the following housekeeping changes:
- Changed DRVNAME to the value specified in the above configuration work in fbtft_device.c
- Changed the driver identification details at the bottom.
- Added a Kconfig entry
- Added a Makefile entry
Now on to the meat of the changes. Summarized, here’s what we have to do:
- Change the driver so that it’s double the height it previously was. The top half will talk to the left display; the bottom half will talk to the right display.
- Replace the old GPIO request/free code to handle the additional concerns of this driver.
- Alter any code that was relying on the SPI driver to switch CS lines so that this driver will tackle it instead.
Doubling the display height
This involves two changes:
- altering the constants at the top of the file to introduce EYEHEIGHT, TOTALHEIGHT, and EYEBOUNDARY
- Making write_vmem smart enough to know which display to send content to, which I’ll cover below.
Replacing the GPIO request/free code
Rather than using the default fbtft_request_gpios function, we describe the pin configuration inside the new driver and use a new request_gpios function that grabs all the pins we need, not forgetting to tell FBTFT to use the new function instead of the default. Same with verify_gpios and free_gpios.
Making the driver CS-aware
As above, we need to extend the existing driver to make it handle CS lines in addition to what it was doing before.
These include:
- Reset
- Write 8-bit register on 8-bit bus
- Write data to video memory. This one includes the code to check what screen is being written to and send the data there. This uses the EYEBOUNDARY constant we defined earlier, which represents the VMEM address of the first pixel of the second screen, i.e. the boundary at which the first screen ends and the second one begins.
- Blanking function
Don’t forget in each case to ensure that the new function is specified as an override at the end of the driver file.
In almost every case, these functions are copies of the default functions in which explicit GPIO set/reset calls are added in order to switch the CS lines.
Happy hacking!
Categorized as: Uncategorized
Very slick! I can think of at least one use for this that doesn’t even involve stereoscopic displays: Using pre-rendered video to be played on two separate LCDs as robot “eyes”. Using this approach would make it trivial to keep the video on both eyes synced properly. Thanks for the idea!
If you do tackle anything like this, please let me know and I’ll post a link!