Appendix - notro/fbtft-spindle GitHub Wiki

Placeholder for various detailed information.

X windows multiseat

It is possible to have two X windows sessions, on the LCD and on HDMI, at the same time.

However there is currently a problem with input devices (no sharing):

  • startx first on HDMI and then on TFT: TFT takes the input devices
startx -- -layout HDMI &
startx -- -layout TFT &
  • startx first on TFT and then on HDMI: HDMI takes input devices and TFT goes black
startx -- -layout TFT &
startx -- -layout HDMI &

/etc/X11/xorg.conf.d/50-fbtft.conf

# FBTFT xorg config file
#
# startx -- -layout TFT
# startx -- -layout HDMI
#
# When not specifying the layout, the first is used: TFT
#

Section "ServerLayout"
	Identifier "TFT"
	Screen 0 "ScreenTFT"
EndSection

Section "ServerLayout"
	Identifier "HDMI"
	Screen 0 "ScreenHDMI"
EndSection

Section "Screen"
	Identifier "ScreenHDMI"
	Monitor "MonitorHDMI"
	Device "DeviceHDMI"
Endsection

Section "Screen"
	Identifier "ScreenTFT"
	Monitor "MonitorTFT"
	Device "DeviceTFT"
Endsection

Section "Monitor"
	Identifier "MonitorHDMI"
Endsection

Section "Monitor"
	Identifier "MonitorTFT"
Endsection

Section "Device"
	Identifier "DeviceHDMI"
	Driver "fbturbo"
	Option "fbdev" "/dev/fb0"
	Option "SwapbuffersWait" "true"
EndSection

Section "Device"
	Identifier "DeviceTFT"
	Option "fbdev" "/dev/fb1"
EndSection

Links:

Which framebuffer does X windows use?

Order of precedence:

  • XF86Config 'fbdev' option (set in /usr/share/X11/xorg.conf.d/99-fbturbo.conf)
    /etc/X11/xorg.conf.d/
    /usr/share/X11/xorg.conf.d/
  • FRAMEBUFFER environment variable (set in /etc/profile.d/framebuffer.sh)
  • /dev/fb0

Source code grep for /dev/fb0:

xorg-server-1.15.0/hw/xfree86/fbdevhw/fbdevhw.c:            dev = "/dev/fb0";
xorg-server-1.15.0/hw/kdrive/fbdev/fbdev.c:        fbdevDevicePath = "/dev/fb0";
xorg-server-1.15.0/hw/kdrive/fbdev/fbinit.c:        ("-fb path         Framebuffer device to use. Defaults to /dev/fb0\n");

xorg/xserver/tree/hw/xfree86/fbdevhw/fbdevhw.c

static int
fbdev_open(int scrnIndex, char *dev, char** namep)
{
	struct	fb_fix_screeninfo fix;
	int    fd;

	/* try argument (from XF86Config) first */
	if (dev) {
	    fd = open(dev,O_RDWR,0);
	} else {
	    /* second: environment variable */
	    dev = getenv("FRAMEBUFFER");
	    if ((NULL == dev) || ((fd = open(dev,O_RDWR,0)) == -1)) {
		/* last try: default device */
		dev = "/dev/fb0";
		fd = open(dev,O_RDWR,0);
	    }
	}

Source code browser: http://sourcecodebrowser.com/xorg-server/1.12.1/fbdevhw_8c.html

X windows backlight script

Workaround to make X windows turn off the backlight instead of just making the screen go black.

Install xscreensaver

$ sudo apt-get install xscreensaver

Screensaver mode
To save CPU, change Mode (while X is running):

$ DISPLAY=:0 xscreensaver-demo

Mode: Blank Screen Only
File->Quit

Changing blanking timeout
On small displays it's difficult to get to all the settings in the xscreensaver-demo window.
Edit ~/.xscreensaver

timeout:        0:10:00

Apply change if X is running (I had to do this even though the manual said it wasn't necessary):

$ DISPLAY=:0 xscreensaver-command -restart

Script
Create /usr/bin/X11/xbacklight.sh

#!/bin/sh

# Make sure there's backlight control
ls /sys/class/backlight/* >/dev/null 2>&1 || exit

process() {
	while read line; do 
		# echo output appears in ~/.xsession-errors when script is autostarted
		#echo "$line"
		case "$line" in
			UNBLANK*)
				echo 0 | sudo tee /sys/class/backlight/*/bl_power 1>/dev/null
			;;
			BLANK*)
				echo 4 | sudo tee /sys/class/backlight/*/bl_power 1>/dev/null
			;;
		esac
	done
}

xscreensaver-command -watch | process

Executable

sudo chmod +x /usr/bin/X11/xbacklight.sh

Add to /etc/xdg/lxsession/LXDE/autostart

/usr/bin/X11/xbacklight.sh

Refs:

X windows and backlight

X windows can't turn off the backlight on fbdev displays.

xbacklight

$ sudo apt-get install xbacklight
$ DISPLAY=:0 xbacklight -get
No outputs have backlight property

xorg/app/xbacklight/tree/xbacklight.c

    backlight_cookie[0] = xcb_intern_atom (conn, 1, strlen("Backlight"), "Backlight");
    backlight_cookie[1] = xcb_intern_atom (conn, 1, strlen("BACKLIGHT"), "BACKLIGHT");

    backlight_reply = xcb_intern_atom_reply (conn, backlight_cookie[0], &error);
    backlight_new = backlight_reply->atom;

    backlight_reply = xcb_intern_atom_reply (conn, backlight_cookie[1], &error);
    backlight_legacy = backlight_reply->atom;

    if (backlight_new == XCB_NONE && backlight_legacy == XCB_NONE) {
	fprintf (stderr, "No outputs have backlight property\n");
	exit (1);
    }

Is this the code that should have implemented the 'Backlight' property in our case?
http://cgit.freedesktop.org/xorg/driver/xf86-video-fbdev/tree/src/fbdev.c

[PATCH 2/2] radeon: add libbacklight support.

Environment variables

The FBTFT image exports several touch and framebuffer related environment variables.
They are set with files in the /etc/profile.d directory.
Show variables with:

env

sudo and environment variables

By default sudo only lets through a couple of enviroment variables to the executed program.
The FBTFT image lets through some more variables using files in the /etc/sudoers.d directory.
Show the effective variables with:

sudo env

spidev

Enable spidev for userspace SPI access. This can only be done on shields without a touch controller, as the Pi only has 2 available chip selects. Or disable touch by removing it from /etc/modules.

sudo modprobe spi-config devices=bus=0:cs=1:modalias=spidev:speed=500000

dmesg | tail
[ 7721.899378] spi_config_register: device description: bus=0:cs=1:modalias=spidev:speed=500000
[ 7721.899471]  spi_config_match_cs: SPI0: check CS=0 to be 1
[ 7721.903949] spi_config_register:spi0.1: registering modalias=spidev with max_speed_hz=500000 mode=0 and no interrupt

ls -l /dev/spi*
crw-rw---T 1 root spi 153, 0 Jan 27 20:43 /dev/spidev0.1

firstboot.sh

/boot/firstboot.sh is executed on the first boot if it exists. It is run from /etc/rc.local

xinput_calibrator and SwapAxes

xinput_calibrator seem to have problems when the touchpanel is rotated 90 degrees left or right.
That's why a workaround is used to swap axes before running the program. This is done in /etc/X11/xorg.conf.d/99-calibration.conf

rpi_power_switch

This module by Adafruit provides support for a Power on/off button.
Add to /etc/modules

rpi_power_switch gpio_pin=23 mode=0

Source code: https://github.com/notro/fbtft_tools/blob/master/rpi_power_switch/rpi_power_switch.c
Adafruit tutorial: Tactile switch as power button

Console blanking

consoleblank is a kernel command line argument. The value can be read back from /sys/module/kernel/parameters/consoleblank. By default consoleblank = 600 seconds (the kernel variable name is 'blankinterval').
Even if set on the commandline, it can be overwritten later.

setterm
blankinterval can be set with: setterm -blank 10 (value is in minutes)

Raspian

On Raspian blankinterval is set to 30 minutes by default (setterm and kbd is in the util-linux package).

Excerpts /etc/init.d/kbd

PKG=kbd
if [ -r /etc/$PKG/config ]; then
    . /etc/$PKG/config
fi

    # screensaver stuff
    setterm_args=""
    if [ "$BLANK_TIME" ]; then
        setterm_args="$setterm_args -blank $BLANK_TIME"
    fi
    if [ "$setterm_args" ]; then
        setterm $setterm_args
    fi

The dot . includes the config file.

Excerpt /etc/kbd/config:

# screen blanking timeout.  monitor remains on, but the screen is cleared to
# range: 0-60 min (0==never)  kernels I've looked at default to 10 minutes.
# (see linux/drivers/char/console.c)
BLANK_TIME=30

Linux kernel code

Short version:
consoleblank sets blankinterval, or value 600 seconds is used.
kbd uses setterm to set blankinterval (1800 seconds).
console_timer fires after blankinterval expires and calls blank_screen_t().
blank_screen_t() sets blank_timer_expired=1 and schedules console_work which runs console_callback().
console_callback() calls do_blank_screen(), beacuse blank_timer_expired.
do_blank_screen() calls con_blank which is fbcon_blank().
fbcon_blank() calls fb_blank() which returns -EINVAL and therefore calls fbcon_generic_blank().
fbcon_generic_blank() sends a fb notifier: FB_EVENT_CONBLANK.
This is picked up by the backlight subsytem which calls backlight_update_status().

Virtual console

Excerpts from drivers/tty/vt/vt.c

int do_poke_blanked_console;
int console_blanked;

static int blankinterval = 10*60;
core_param(consoleblank, blankinterval, int, 0444);


static int __init con_init(void)
{
        if (blankinterval) {
                blank_state = blank_normal_wait;
                mod_timer(&console_timer, jiffies + (blankinterval * HZ));
        }
}
console_initcall(con_init);

static void setterm_command(struct vc_data *vc)
{

        switch(vc->vc_par[0]) {
                case 9: /* set blanking interval */
                        blankinterval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60;
                        poke_blanked_console();
                        break;
                case 13: /* unblank the screen */
                        poke_blanked_console();
                        break;
        }
}

void poke_blanked_console(void)
{
        if (console_blanked)
                unblank_screen();
        else if (blankinterval) {
                mod_timer(&console_timer, jiffies + (blankinterval * HZ));
                blank_state = blank_normal_wait;
        }
}

void do_blank_screen(int entering_gfx)
{
        blank_state = blank_off;

        i = vc->vc_sw->con_blank(vc, vesa_off_interval ? 1 : (vesa_blank_mode + 1), 0);

        del_timer_sync(&console_timer);
        blank_timer_expired = 0;
}

/*
 * Called by timer as well as from vt_console_driver
 */
void do_unblank_screen(int leaving_gfx)
{
        if (blankinterval) {
                mod_timer(&console_timer, jiffies + (blankinterval * HZ));
                blank_state = blank_normal_wait;
        }

        console_blanked = 0;
        if (vc->vc_sw->con_blank(vc, 0, leaving_gfx) || vt_force_oops_output(vc))
}

static DEFINE_TIMER(console_timer, blank_screen_t, 0, 0);
/*
 * We defer the timer blanking to work queue so it can take the console mutex
 * (console operations can still happen at irq time, but only from printk which
 * has the console mutex. Not perfect yet, but better than no locking
 */
static void blank_screen_t(unsigned long dummy)
{
        blank_timer_expired = 1;
        schedule_work(&console_work);
}

static DECLARE_WORK(console_work, console_callback);
static void console_callback(struct work_struct *ignored)
{
        if (blank_timer_expired) {
                do_blank_screen(0);
                blank_timer_expired = 0;
        }
}
Frambuffer console

Excerpts from drivers/video/console/fbcon.c

static const struct consw fb_con = {
        .con_blank              = fbcon_blank,
};

static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch)
{
                                if (fb_blank(info, blank))
                                        fbcon_generic_blank(vc, info, blank);
}

static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info,
                                int blank)
{
        fb_notifier_call_chain(FB_EVENT_CONBLANK, &event);
}
Backlight subsystem

The backlight subsystem picks up a FB_EVENT_CONBLANK event.
drivers/video/backlight/backlight.c

static int fb_notifier_callback(struct notifier_block *self,
                                unsigned long event, void *data)
{
                        if (fb_blank == FB_BLANK_UNBLANK &&
                            !bd->fb_bl_on[node]) {
                                bd->fb_bl_on[node] = true;
                                if (!bd->use_count++) {
                                        bd->props.state &= ~BL_CORE_FBBLANK;
                                        bd->props.fb_blank = FB_BLANK_UNBLANK;
                                        backlight_update_status(bd);
                                }
                        } else if (fb_blank != FB_BLANK_UNBLANK &&
                                   bd->fb_bl_on[node]) {
                                bd->fb_bl_on[node] = false;
                                if (!(--bd->use_count)) {
                                        bd->props.state |= BL_CORE_FBBLANK;
                                        bd->props.fb_blank = fb_blank;
                                        backlight_update_status(bd);
                                }
                        }
FBTFT

Backlight handling in FBTFT
fb_blank returns -EINVAL on most FBTFT drivers.

fbtft-core.c

int fbtft_backlight_update_status(struct backlight_device *bd)
{
	if ((bd->props.power == FB_BLANK_UNBLANK) && (bd->props.fb_blank == FB_BLANK_UNBLANK))
		gpio_set_value(par->gpio.led[0], polarity);
	else
		gpio_set_value(par->gpio.led[0], !polarity);
}
⚠️ **GitHub.com Fallback** ⚠️