Merge pull request #348 from makermelissa/pitft-updates

Added PiTFT V2 and stripping out old stuff
This commit is contained in:
Melissa LeBlanc-Williams 2025-08-11 11:49:04 -07:00 committed by GitHub
commit adcb740a19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 461 additions and 173 deletions

View file

@ -7,6 +7,8 @@ Written in Python by Melissa LeBlanc-Williams for Adafruit Industries
import time import time
import os import os
import glob
try: try:
import click import click
except ImportError: except ImportError:
@ -19,13 +21,66 @@ except ImportError:
shell = Shell() shell = Shell()
shell.group = 'PITFT' shell.group = 'PITFT'
__version__ = "3.9.0" __version__ = "4.0.0"
""" """
This is the main configuration. Displays should be placed in the order This is the main configuration. Displays should be placed in the order
they are to appear in the menu. they are to appear in the menu.
Config Parameters:
REQUIRED fields:
type: Unique identifier for the display type.
menulabel: The label to display in the menu.
product: The product name for the display.
kernel_upgrade: Whether the kernel needs to be upgraded for this display.
overlay: The overlay string to apply for the display.
width: Width of the display in pixels.
height: Height of the display in pixels.
OPTIONAL fields:
touchscreen: Dictionary containing touchscreen settings for displays with touchscreens.
identifier: Name of the touchscreen calibration.
product: Product name for the touchscreen.
transforms: X11 transforms for different rotations.
overlay_params: Overlay parameters for different rotations.
calibrations: Calibration data for the touchscreen. Can be overall or per rotation.
overlay_drm_option: Optional parameter to add for DRM support.
overlay_src: Source path for the overlay file (if applicable).
overlay_dest: Destination path for the compiled overlay file (if applicable).
mipi_data (Optional): Dictionary containing MIPI display data.
command_bin: Name of the command binary for MIPI.
gpio: GPIO settings for the display.
viewport: Viewport settings for different rotations.
mirror_rotations: Dictionary mapping pitftrot values to DRM rotation values.
""" """
# Touchscreen Products
TS_STMPE = "stmpe"
TS_TSC2007 = "tsc2007"
TS_FOCALTOUCH = "EP0110M09"
config = [ config = [
{
"type": "24rv2",
"menulabel": "PiTFT 2.4\" V2 resistive (240x320)",
"product": "2.4\" V2 resistive",
"kernel_upgrade": False,
"overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts",
"overlay_dest": "{boot_dir}/overlays/pitft24rv2.dtbo",
"touchscreen": {
"identifier": "TSC2007 Touchscreen Calibration",
"product": TS_TSC2007,
"transforms": {
"0": "-1 0 1 0 -1 1",
"90": "0 1 0 -1 0 1",
"180": "1 0 0 0 1 0",
"270": "0 -1 1 1 0 0",
},
},
"overlay": "dtoverlay=pitft24rv2,rotate={pitftrot},fps=60",
"width": 320,
"height": 240,
},
{ {
"type": "28r", "type": "28r",
"menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)", "menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)",
@ -33,13 +88,20 @@ config = [
"kernel_upgrade": False, "kernel_upgrade": False,
"touchscreen": { "touchscreen": {
"identifier": "STMPE Touchscreen Calibration", "identifier": "STMPE Touchscreen Calibration",
"product": "stmpe", "product": TS_STMPE,
# X11 Transforms
"transforms": { "transforms": {
"0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1",
"90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1",
"180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1",
"270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1",
}, },
"calibrations": {
"0": "5724 -6 -1330074 26 8427 -1034528 65536",
"90": "5 8425 -978304 -5747 61 22119468 65536",
"180": "-5682 -1 22069150 13 -8452 32437698 65536",
"270": "3 -8466 32440206 5703 -1 -1308696 65536",
},
"overlay_params": { "overlay_params": {
"0": None, "0": None,
"90": "touch-swapxy,touch-invx", "90": "touch-swapxy,touch-invx",
@ -49,13 +111,6 @@ config = [
}, },
"overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30",
"overlay_drm_option": "drm", "overlay_drm_option": "drm",
"force_x11": True,
"calibrations": {
"0": "4232 11 -879396 1 5786 -752768 65536",
"90": "33 -5782 21364572 4221 35 -1006432 65536",
"180": "-4273 61 16441290 4 -5772 21627524 65536",
"270": "-9 5786 -784608 -4302 19 16620508 65536",
},
"width": 320, "width": 320,
"height": 240, "height": 240,
}, },
@ -76,7 +131,8 @@ config = [
"kernel_upgrade": False, "kernel_upgrade": False,
"touchscreen": { "touchscreen": {
"identifier": "FocalTech Touchscreen Calibration", "identifier": "FocalTech Touchscreen Calibration",
"product": "EP0110M09", "product": TS_FOCALTOUCH,
# X11 Transforms
"transforms": { "transforms": {
"0": "-1 0 1 0 -1 1 0 0 1", "0": "-1 0 1 0 -1 1 0 0 1",
"90": "0 1 0 -1 0 1 0 0 1", "90": "0 1 0 -1 0 1 0 0 1",
@ -89,11 +145,10 @@ config = [
"180": None, "180": None,
"270": "touch-swapxy,touch-invx", "270": "touch-swapxy,touch-invx",
}, },
"calibrations": "320 65536 0 -65536 0 15728640 65536",
}, },
"overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30", "overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30",
"overlay_drm_option": "drm", "overlay_drm_option": "drm",
"force_x11": True,
"calibrations": "320 65536 0 -65536 0 15728640 65536",
"width": 320, "width": 320,
"height": 240, "height": 240,
}, },
@ -104,7 +159,8 @@ config = [
"kernel_upgrade": False, "kernel_upgrade": False,
"touchscreen": { "touchscreen": {
"identifier": "STMPE Touchscreen Calibration", "identifier": "STMPE Touchscreen Calibration",
"product": "stmpe", "product": TS_STMPE,
# X11 Transforms
"transforms": { "transforms": {
"0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1", "0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1",
"90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1", "90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1",
@ -120,16 +176,9 @@ config = [
}, },
"overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20",
"overlay_drm_option": "drm", "overlay_drm_option": "drm",
"force_x11": True,
"calibrations": {
"0": "5724 -6 -1330074 26 8427 -1034528 65536",
"90": "5 8425 -978304 -5747 61 22119468 65536",
"180": "-5682 -1 22069150 13 -8452 32437698 65536",
"270": "3 -8466 32440206 5703 -1 -1308696 65536",
},
"width": 480, "width": 480,
"height": 320, "height": 320,
"x11_scale": 1.5, "display_scale": 1.5,
}, },
{ {
"type": "st7789_240x240", "type": "st7789_240x240",
@ -140,7 +189,6 @@ config = [
"overlay_src": "overlays/minipitft13-overlay.dts", "overlay_src": "overlays/minipitft13-overlay.dts",
"overlay_dest": "{boot_dir}/overlays/drm-minipitft13.dtbo", "overlay_dest": "{boot_dir}/overlays/drm-minipitft13.dtbo",
"overlay": "dtoverlay=drm-minipitft13,rotate={pitftrot},fps=60", "overlay": "dtoverlay=drm-minipitft13,rotate={pitftrot},fps=60",
"use_kms": True,
"mipi_data": { "mipi_data": {
"command_bin": "adafruit_st7789_drm", "command_bin": "adafruit_st7789_drm",
"gpio": "dc-gpio=25,backlight-gpio=22", "gpio": "dc-gpio=25,backlight-gpio=22",
@ -153,7 +201,7 @@ config = [
}, },
"width": 240, "width": 240,
"height": 240, "height": 240,
"fbcp_rotations": { "mirror_rotations": {
"0": "0", "0": "0",
"90": "1", "90": "1",
"180": "2", "180": "2",
@ -169,7 +217,6 @@ config = [
"overlay_src": "overlays/st7789v_240x320-overlay.dts", "overlay_src": "overlays/st7789v_240x320-overlay.dts",
"overlay_dest": "{boot_dir}/overlays/drm-st7789v_240x320.dtbo", "overlay_dest": "{boot_dir}/overlays/drm-st7789v_240x320.dtbo",
"overlay": "dtoverlay=drm-st7789v_240x320,rotate={pitftrot},fps=30", "overlay": "dtoverlay=drm-st7789v_240x320,rotate={pitftrot},fps=30",
"use_kms": True,
"mipi_data": { "mipi_data": {
"command_bin": "adafruit_st7789_drm", "command_bin": "adafruit_st7789_drm",
"gpio": "dc-gpio=25,backlight-gpio=22", "gpio": "dc-gpio=25,backlight-gpio=22",
@ -192,7 +239,6 @@ config = [
"overlay_src": "overlays/minipitft114-overlay.dts", "overlay_src": "overlays/minipitft114-overlay.dts",
"overlay_dest": "{boot_dir}/overlays/drm-minipitft114.dtbo", "overlay_dest": "{boot_dir}/overlays/drm-minipitft114.dtbo",
"overlay": "dtoverlay=drm-minipitft114,rotate={pitftrot},fps=60", "overlay": "dtoverlay=drm-minipitft114,rotate={pitftrot},fps=60",
"use_kms": True,
"mipi_data": { "mipi_data": {
"command_bin": "adafruit_st7789_drm", "command_bin": "adafruit_st7789_drm",
"gpio": "dc-gpio=25,backlight-gpio=22", "gpio": "dc-gpio=25,backlight-gpio=22",
@ -211,7 +257,7 @@ config = [
}, },
"width": 240, "width": 240,
"height": 135, "height": 135,
"fbcp_rotations": { "mirror_rotations": {
"0": "3", "0": "3",
"90": "2", "90": "2",
"180": "1", "180": "1",
@ -227,7 +273,6 @@ config = [
"overlay_src": "overlays/tftbonnet13-overlay.dts", "overlay_src": "overlays/tftbonnet13-overlay.dts",
"overlay_dest": "{boot_dir}/overlays/drm-tftbonnet13.dtbo", "overlay_dest": "{boot_dir}/overlays/drm-tftbonnet13.dtbo",
"overlay": "dtoverlay=drm-tftbonnet13,rotate={pitftrot},fps=60", "overlay": "dtoverlay=drm-tftbonnet13,rotate={pitftrot},fps=60",
"use_kms": True,
"mipi_data": { "mipi_data": {
"command_bin": "adafruit_st7789_drm", "command_bin": "adafruit_st7789_drm",
"gpio": "dc-gpio=25,backlight-gpio=26", "gpio": "dc-gpio=25,backlight-gpio=26",
@ -240,7 +285,7 @@ config = [
}, },
"width": 240, "width": 240,
"height": 240, "height": 240,
"fbcp_rotations": { "mirror_rotations": {
"0": "0", "0": "0",
"90": "1", "90": "1",
"180": "2", "180": "2",
@ -250,7 +295,7 @@ config = [
] ]
# default rotations # default rotations
fbcp_rotations = { mirror_rotations = {
"0": "1", "0": "1",
"90": "0", "90": "0",
"180": "3", "180": "3",
@ -263,6 +308,12 @@ mipi_data = {
"spi": "spi0-0", "spi": "spi0-0",
} }
install_types = {
"mirror": "Setup PiTFT as desktop display (mirror)",
"console": "Display console on PiTFT (console)",
"drivers": "Install Drivers only"
}
PITFT_ROTATIONS = ("90", "180", "270", "0") PITFT_ROTATIONS = ("90", "180", "270", "0")
UPDATE_DB = False UPDATE_DB = False
SYSTEMD = None SYSTEMD = None
@ -270,19 +321,19 @@ REMOVE_KERNEL_PINNING = False
pitft_config = None pitft_config = None
pitftrot = None pitftrot = None
auto_reboot = None auto_reboot = None
wayland = False is_desktop = False
is_bullseye = False manager = None
def warn_exit(message): def warn_exit(message):
shell.warn(message) shell.warn(message)
shell.exit(1) shell.exit(1)
def uninstall_cb(ctx, param, value): def uninstall_cb(ctx, _param, value):
if not value or ctx.resilient_parsing: if not value or ctx.resilient_parsing:
return return
uninstall() uninstall()
def print_version(ctx, param, value): def print_version_cb(ctx, _param, value):
if not value or ctx.resilient_parsing: if not value or ctx.resilient_parsing:
return return
print("Adafruit PiTFT Helper v{}".format(__version__)) print("Adafruit PiTFT Helper v{}".format(__version__))
@ -299,8 +350,8 @@ def sysupdate():
if not UPDATE_DB: if not UPDATE_DB:
print("Updating apt indexes...", end='') print("Updating apt indexes...", end='')
progress(3) progress(3)
if not shell.run_command('sudo apt update', suppress_message=True): if not shell.run_command('sudo apt-get update', suppress_message=True):
warn_exit("Apt failed to update indexes! Try running 'sudo apt update' manually.") warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.")
if not shell.run_command('sudo apt-get update', suppress_message=True): if not shell.run_command('sudo apt-get update', suppress_message=True):
warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.") warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.")
print("Reading package lists...") print("Reading package lists...")
@ -310,11 +361,6 @@ def sysupdate():
############################ Sub-Scripts ############################ ############################ Sub-Scripts ############################
def is_wayland():
username = os.environ["SUDO_USER"]
output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip()
return len(output) > 0
def softwareinstall(): def softwareinstall():
print("Installing Pre-requisite Software...This may take a few minutes!") print("Installing Pre-requisite Software...This may take a few minutes!")
if not shell.run_command("apt-get install -y libts0", suppress_message=True): if not shell.run_command("apt-get install -y libts0", suppress_message=True):
@ -323,6 +369,8 @@ def softwareinstall():
warn_exit("Apt failed to install TSLIB!") warn_exit("Apt failed to install TSLIB!")
if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler libraspberrypi-dev build-essential python3-evdev"): if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler libraspberrypi-dev build-essential python3-evdev"):
warn_exit("Apt failed to install software!") warn_exit("Apt failed to install software!")
if not shell.run_command("apt-get install -y raspi-config"):
warn_exit("Apt failed to install raspi-config!")
return True return True
def uninstall_bootconfigtxt(): def uninstall_bootconfigtxt():
@ -373,7 +421,7 @@ def install_drivers():
if use_mipi_driver(): if use_mipi_driver():
mipi_data.update(pitft_config['mipi_data']) mipi_data.update(pitft_config['mipi_data'])
if not compile_display_fw(): if not compile_mipi_fw():
shell.bail("Unable to compile MIPI firmware") shell.bail("Unable to compile MIPI firmware")
if is_kernel_upgrade_required(): if is_kernel_upgrade_required():
@ -423,37 +471,34 @@ def update_configtxt(rotation_override=None, tinydrm_install=False):
if "gpio" in mipi_data: if "gpio" in mipi_data:
overlay += f"\ndtparam={mipi_data['gpio']}" overlay += f"\ndtparam={mipi_data['gpio']}"
if tinydrm_install and "overlay_drm_option" in pitft_config: if tinydrm_install: # Use overlay params for Wayland
overlay += "," + pitft_config["overlay_drm_option"] overlay += ",drm"
if tinydrm_install: # Wayland ignores X11 Transformations, so use params instead
if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None:
overlay += "," + pitft_config["overlay_params"][pitftrot] overlay += "," + pitft_config["overlay_params"][pitftrot]
shell.write_text_file(f"{boot_dir}/config.txt", """ config_text_base = shell.load_template("templates/config_text_base.txt", date=shell.date(), overlay=overlay)
# --- added by adafruit-pitft-helper {date} --- if config_text_base is None:
[all] shell.bail("Unable to load config_text_base template!")
hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! shell.write_text_file(f"{boot_dir}/config.txt", config_text_base)
dtparam=spi=on
dtparam=i2c1=on
dtparam=i2c_arm=on
{overlay}
# --- end adafruit-pitft-helper {date} ---
""".format(date=shell.date(), overlay=overlay))
return True return True
def update_udev(): def update_udev():
shell.write_text_file("/etc/udev/rules.d/95-touchmouse.rules", """ product = None
SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" if "touchscreen" in pitft_config and "product" in pitft_config["touchscreen"]:
""", append=False) product = pitft_config["touchscreen"]["product"]
shell.write_text_file("/etc/udev/rules.d/95-ftcaptouch.rules", """ shell.write_templated_file("/etc/udev/rules.d/", "templates/95-touchmouse.rules")
SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" if product == TS_FOCALTOUCH:
SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" shell.write_templated_file("/etc/udev/rules.d/", "templates/95-ftcaptouch.rules")
""", append=False) elif product == TS_STMPE:
shell.write_text_file("/etc/udev/rules.d/95-stmpe.rules", """ shell.write_templated_file("/etc/udev/rules.d/", "templates/95-stmpe.rules")
SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" elif product == TS_TSC2007:
""", append=False) calibration_matrix = "0 1 0 -1 0 1" # Default calibration matrix
if "transforms" in pitft_config["touchscreen"] and pitftrot in pitft_config["touchscreen"]["transforms"]:
calibration_matrix = pitft_config["touchscreen"]["transforms"][pitftrot]
shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen.rules", calibration_matrix=calibration_matrix)
shell.write_templated_file("/etc/udev/rules.d/", "templates/99-spi-tft-drm.rules")
return True return True
def compile_display_fw(): def compile_mipi_fw():
command_src = "mipi/panel.txt" command_src = "mipi/panel.txt"
# We could just copy the file to panel.txt, edit that, then remove it after # We could just copy the file to panel.txt, edit that, then remove it after
@ -473,33 +518,34 @@ def compile_display_fw():
return True return True
def update_pointercal(): def update_pointercal():
if "calibrations" in pitft_config: if "touchscreen" in pitft_config and "calibrations" in pitft_config["touchscreen"]:
if isinstance(pitft_config["calibrations"], dict): if isinstance(pitft_config["touchscreen"]["calibrations"], dict):
shell.write_text_file("/etc/pointercal", pitft_config["calibrations"][pitftrot]) shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"][pitftrot], append=False)
else: else:
shell.write_text_file("/etc/pointercal", pitft_config["calibrations"]) shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"], append=False)
else:
shell.remove("/etc/pointercal")
return True return True
def install_console(): def install_console():
print("Set up main console turn on") print("Installing console fbcon map helper...")
if not shell.pattern_search(f"{boot_dir}/cmdline.txt", 'fbcon=map:10 fbcon=font:VGA8x8'): shell.write_templated_file("/usr/local/bin/", "templates/con2fbmap-helper.sh")
print(f"Updating {boot_dir}/cmdline.txt") shell.chmod("/usr/local/bin/con2fbmap-helper.sh", "+x")
shell.pattern_replace(f"{boot_dir}/cmdline.txt", "rootwait", "rootwait fbcon=map:10 fbcon=font:VGA8x8")
else: print("Installing console fbcon map service...")
print(f"{boot_dir}/cmdline.txt already updated") shell.write_templated_file("/etc/systemd/system/", "templates/con2fbmap.service")
shell.run_command("systemctl daemon-reload")
shell.run_command("systemctl restart con2fbmap.service")
print("Turning off console blanking") print("Turning off console blanking")
# removing old versions
# pre-stretch this is what you'd do:
if shell.exists("/etc/kbd/config"):
shell.pattern_replace("/etc/kbd/config", "BLANK_TIME=.*", "BLANK_TIME=0")
# as of stretch....
# removing any old version
shell.pattern_replace("/etc/rc.local", '# disable console blanking.*') shell.pattern_replace("/etc/rc.local", '# disable console blanking.*')
shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*') shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*')
# adding new version
shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0") shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0")
# Set the console font to Terminus 6x12
shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"") shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"")
shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"") shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"")
@ -514,15 +560,31 @@ def install_console():
def uninstall_console(): def uninstall_console():
print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") print(f"Removing console fbcon map from {boot_dir}/cmdline.txt")
shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait')
print("Screen blanking time reset to 10 minutes")
if shell.exists("/etc/kbd/config"): if is_desktop:
shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'BLANK_TIME=0', 'BLANK_TIME=10') print("Restoring Desktop Environment...")
shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') shell.run_command("apt-get -y install rpd-plym-splash") # Install Splash Screen
shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') shell.run_command("raspi-config nonint do_boot_splash 0") # Enable Splash Screen
shell.run_command("apt-get -y install raspberrypi-ui-mods") # Reinstall Raspberry Pi OS UI mods
shell.run_command("raspi-config nonint do_boot_target B2") # Boot to Desktop
if shell.exists("/etc/systemd/system/con2fbmap.service"):
print("Removing console fbcon map service...")
shell.run_command("systemctl stop con2fbmap.service")
shell.run_command("systemctl disable con2fbmap.service")
shell.remove("/etc/systemd/system/con2fbmap.service")
if shell.exists("/usr/local/bin/con2fbmap-helper.sh"):
print("Removing console fbcon map helper...")
shell.remove("/usr/local/bin/con2fbmap-helper.sh")
if shell.exists("/etc/rc.local"):
shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*')
shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*')
return True return True
def install_fbcp(): def install_mirror():
global fbcp_rotations global mirror_rotations
print("Installing cmake...") print("Installing cmake...")
if not shell.run_command("apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake", suppress_message=True): if not shell.run_command("apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake", suppress_message=True):
warn_exit("Apt failed to install software!") warn_exit("Apt failed to install software!")
@ -547,8 +609,8 @@ def install_fbcp():
shell.popd() shell.popd()
shell.run_command("rm -rf /tmp/rpi-fbcp-master") shell.run_command("rm -rf /tmp/rpi-fbcp-master")
if "fbcp_rotations" in pitft_config: if "mirror_rotations" in pitft_config:
fbcp_rotations = pitft_config['fbcp_rotations'] mirror_rotations = pitft_config['mirror_rotations']
# Start fbcp in the appropriate place, depending on init system: # Start fbcp in the appropriate place, depending on init system:
if SYSTEMD: if SYSTEMD:
@ -561,15 +623,15 @@ def install_fbcp():
# Insert fbcp into rc.local before final 'exit 0': # Insert fbcp into rc.local before final 'exit 0':
shell.pattern_replace("/etc/rc.local", "^exit 0", "/usr/local/bin/fbcp \&\\nexit 0") shell.pattern_replace("/etc/rc.local", "^exit 0", "/usr/local/bin/fbcp \&\\nexit 0")
else: else:
# Install fbcp systemd unit, first making sure it's not in rc.local: # Install fbcp systemd service, first making sure it's not in rc.local:
uninstall_fbcp_rclocal() uninstall_fbcp_rclocal()
print("We have systemd, so install fbcp systemd unit...") print("We have systemd, so install fbcp systemd service...")
if not install_fbcp_unit(): if not install_fbcp_service():
shell.bail("Unable to install fbcp unit file") shell.bail("Unable to install fbcp service file")
shell.run_command("sudo systemctl enable fbcp.service") shell.run_command("sudo systemctl enable fbcp.service")
# if there's X11 installed... # if desktop environment is installed...
if shell.exists("/etc/lightdm"): if is_desktop:
print("Setting raspi-config to boot to desktop w/o login...") print("Setting raspi-config to boot to desktop w/o login...")
shell.run_command("raspi-config nonint do_boot_behaviour B4") shell.run_command("raspi-config nonint do_boot_behaviour B4")
@ -581,16 +643,11 @@ def install_fbcp():
shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2") shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2")
shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87")
# Don't use the 3D driver if we're on X11, unless the display specifically uses kms # if using the desktop version, update driver scale...
if (is_bullseye or not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]):
shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-kms-v3d.*$", "#dtoverlay=vc4-kms-v3d")
shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-fkms-v3d.*$", "#dtoverlay=vc4-fkms-v3d")
# if there's X11 installed...
scale = 1 scale = 1
if shell.exists("/etc/lightdm"): if is_desktop:
if "x11_scale" in pitft_config: if "display_scale" in pitft_config:
scale = pitft_config["x11_scale"] scale = pitft_config["display_scale"]
else: else:
scale = 2 scale = 2
WIDTH = int(pitft_config['width'] * scale) WIDTH = int(pitft_config['width'] * scale)
@ -599,56 +656,86 @@ def install_fbcp():
shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_cvt.*$", "hdmi_cvt={} {} 60 1 0 0 0".format(WIDTH, HEIGHT)) shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_cvt.*$", "hdmi_cvt={} {} 60 1 0 0 0".format(WIDTH, HEIGHT))
try: try:
default_orientation = int(list(fbcp_rotations.keys())[list(fbcp_rotations.values()).index("0")]) default_orientation = int(list(mirror_rotations.keys())[list(mirror_rotations.values()).index("0")])
except ValueError: except ValueError:
default_orientation = 90 default_orientation = 90
if fbcp_rotations[pitftrot] == "0": if mirror_rotations[pitftrot] == "0":
# dont rotate HDMI on default orientation # dont rotate HDMI on default orientation
shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "") shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "")
else: else:
display_rotate = fbcp_rotations[pitftrot] display_rotate = mirror_rotations[pitftrot]
shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "display_hdmi_rotate={}".format(display_rotate)) shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "display_hdmi_rotate={}".format(display_rotate))
# Because we rotate HDMI we have to 'unrotate' the TFT by overriding pitftrot! # Because we rotate HDMI we have to 'unrotate' the TFT by overriding pitftrot!
if not update_configtxt(default_orientation): if not update_configtxt(rotation=default_orientation):
shell.bail(f"Unable to update {boot_dir}/config.txt") shell.bail(f"Unable to update {boot_dir}/config.txt")
return True return True
def update_wayfire_settings(): def update_wayland_settings():
# Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor # Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor
if "x11_scale" in pitft_config: if "display_scale" in pitft_config:
scale = 1/pitft_config["x11_scale"] scale = 1/pitft_config["display_scale"]
else: else:
scale = 0.5 scale = 0.5 # Default Scale
device_name = "SPI-1" device_name = "SPI-1"
wayfire_config = f"{target_homedir}/.config/wayfire.ini"
# Remove any existing settings previously added by this script
shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True)
date = shell.date() date = shell.date()
shell.write_text_file(wayfire_config, f"""
if manager == "wayfire":
### WAYFIRE ###
wayfire_config = f"{target_homedir}/.config/wayfire.ini"
# Remove any existing settings previously added by this script
shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True)
shell.write_text_file(wayfire_config, f"""
# --- added by adafruit-pitft-helper {date} --- # --- added by adafruit-pitft-helper {date} ---
[output:{device_name}] [output:{device_name}]
scale = {scale} scale = {scale}
# --- end adafruit-pitft-helper {date} --- # --- end adafruit-pitft-helper {date} ---
""") """)
shell.pattern_replace(target_homedir, "^.*[output:SPI-1].*$", "hdmi_force_hotplug=0") elif manager == "labwc":
### LABWC (Using Kanshi) ###
# See https://man.archlinux.org/man/kanshi.5.en for more information on kanshi config files
labwc_config = f"{target_homedir}/.config/kanshi/config"
# If the config file doesn't exist or doesn't contain profile, enumerate the devices and create a new profile
if not shell.exists(labwc_config) or not shell.pattern_search(labwc_config, "^profile"):
drm_devices = get_drm_devices(connected_only=True)
profile_content = "profile {"
for device in drm_devices:
device_name = device["name"]
mode = device["modes"][0]
device_refresh = "@60.000"
device_scale = ""
if device_name == "SPI-1":
device_refresh = "" # For SPI displays, we don't specify refresh rate
device_scale = f" scale {scale}"
def install_fbcp_unit(): profile_content += f"\n\t\toutput {device_name} enable mode {mode}{device_refresh} transform normal{device_scale}"
shell.write_text_file("/etc/systemd/system/fbcp.service", profile_content += "\n}\n"
"""[Unit] shell.write_text_file(labwc_config, profile_content, append=True)
Description=Framebuffer copy utility for PiTFT else:
After=network.target profiles = shell.pattern_search(labwc_config, r"(profile(?: .*)?) {([\S\s]*?)}", multi_line=True, return_match=True, find_all=True)
if profiles:
for profile in profiles:
profile_name = profile[0].strip()
profile_content = profile[1].strip()
# Check if the profile already has an output section for the device
if not shell.pattern_search(profile_content, f"(output {device_name}.*)"):
# If not, add the output section for the device
width, height = pitft_config["width"], pitft_config["height"]
new_output_device = f"\t\toutput {device_name} enable mode {width}x{height} transform normal scale {scale}\n"
# insert the new output device line into the profile content
profile_content += new_output_device
# Replace the profile with the updated content
shell.pattern_replace(labwc_config, f"^{profile_name} {{([\S\s]*?)}}", f"{profile_name}: {{{profile_content}}}", multi_line=True)
[Service] # Remove scaling from any existing scale settings for the device
Type=simple shell.pattern_replace(labwc_config, f"(output {device_name}.*) scale [0-9](?:\.[0-9]+)?(.*)", r"\1\2")
ExecStartPre=/bin/sleep 10 # Add new scale to the end of the line
ExecStart=/usr/local/bin/fbcp shell.pattern_replace(labwc_config, f"(output {device_name}.*)", r"\1 scale " + f"{scale}")
[Install] def install_fbcp_service():
WantedBy=multi-user.target shell.write_templated_file("/etc/systemd/system/", "templates/fbcp.service")
""", append=False)
return True return True
def uninstall_fbcp(): def uninstall_fbcp():
@ -666,10 +753,6 @@ def uninstall_fbcp():
shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$') shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$')
shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$') shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$')
if not wayland and not is_bullseye and shell.exists("/etc/lightdm"):
print("Restoring Wayland as default display manager...")
shell.set_window_manager("wayland")
return True return True
def uninstall_fbcp_rclocal(): def uninstall_fbcp_rclocal():
@ -678,13 +761,12 @@ def uninstall_fbcp_rclocal():
shell.pattern_replace("/etc/rc.local", '^.*fbcp.*$') shell.pattern_replace("/etc/rc.local", '^.*fbcp.*$')
return True return True
def update_xorg(tinydrm_install=False): def update_xorg():
if "touchscreen" in pitft_config: if "touchscreen" in pitft_config:
transform_setting = pitft_config["touchscreen"]["transforms"][pitftrot] transform_setting = pitft_config["touchscreen"]["transforms"][pitftrot]
if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]:
transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot]
transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" transform = f"Option \"TransformationMatrix\" \"{transform_setting}\""
shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ if shell.exists("/usr/share/X11/xorg.conf.d"):
shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """
Section "InputClass" Section "InputClass"
Identifier "{identifier}" Identifier "{identifier}"
MatchProduct "{product}" MatchProduct "{product}"
@ -718,7 +800,6 @@ def uninstall():
uninstall_bootconfigtxt() uninstall_bootconfigtxt()
uninstall_console() uninstall_console()
uninstall_fbcp() uninstall_fbcp()
uninstall_fbcp_rclocal()
uninstall_etc_modules() uninstall_etc_modules()
success() success()
@ -737,6 +818,24 @@ Settings take effect on next boot.
shell.reboot() shell.reboot()
shell.exit() shell.exit()
def get_drm_devices(connected_only=False):
# get all drm devices from /sys/class/drm e.g. card1-HDMI-A-1, card2-SPI-1, etc.
devices = []
for item in glob.glob("/sys/class/drm/card?-*"):
status = shell.read_text_file(f"{item}/status").strip()
if connected_only and status != "connected":
continue
modes = shell.read_text_file(f"{item}/modes").strip().splitlines()
# remove duplicate modes
modes = list(dict.fromkeys(modes))
info = {
"name": item.split("-", 1)[1],
"status": status,
"modes": modes,
}
devices.append(info)
return devices
####################################################### MAIN ####################################################### MAIN
target_homedir = "/home/pi" target_homedir = "/home/pi"
username = os.environ["SUDO_USER"] username = os.environ["SUDO_USER"]
@ -755,10 +854,10 @@ if boot_dir is None or not shell.isdir(boot_dir):
shell.bail("Unable to find boot directory") shell.bail("Unable to find boot directory")
if shell.get_raspbian_version() == "bullseye": if shell.get_raspbian_version() == "bullseye":
is_bullseye = True shell.bail("Bullseye is not supported by this script. Please update to Bookworm first.")
@click.command() @click.command()
@click.option('-v', '--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Print version information") @click.option('-v', '--version', is_flag=True, callback=print_version_cb, expose_value=False, is_eager=True, help="Print version information")
@click.option('-u', '--user', nargs=1, default=target_homedir, type=str, help="Specify path of primary user's home directory", show_default=True) @click.option('-u', '--user', nargs=1, default=target_homedir, type=str, help="Specify path of primary user's home directory", show_default=True)
@click.option('--display', nargs=1, default=None, help="Specify a display option (1-{}) or type {}".format(len(config), get_config_types())) @click.option('--display', nargs=1, default=None, help="Specify a display option (1-{}) or type {}".format(len(config), get_config_types()))
@click.option('--rotation', nargs=1, default=None, type=int, help="Specify a rotation option (1-4) or degrees {}".format(tuple(sorted([int(x) for x in PITFT_ROTATIONS])))) @click.option('--rotation', nargs=1, default=None, type=int, help="Specify a rotation option (1-4) or degrees {}".format(tuple(sorted([int(x) for x in PITFT_ROTATIONS]))))
@ -766,7 +865,7 @@ if shell.get_raspbian_version() == "bullseye":
@click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished") @click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished")
@click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True) @click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True)
def main(user, display, rotation, install_type, reboot, boot): def main(user, display, rotation, install_type, reboot, boot):
global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, wayland global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, is_desktop, manager, SYSTEMD
shell.clear() shell.clear()
if user != target_homedir: if user != target_homedir:
target_homedir = user target_homedir = user
@ -777,14 +876,15 @@ def main(user, display, rotation, install_type, reboot, boot):
print(f"Boot dir = {boot_dir}") print(f"Boot dir = {boot_dir}")
else: else:
print(f"{boot} not found or not a directory. Using {boot_dir} instead.") print(f"{boot} not found or not a directory. Using {boot_dir} instead.")
wayland = is_wayland() # Check if we are running on a desktop environment or lite
print(("Wayland" if wayland else "X11") + " Detected") is_desktop = shell.exists("/etc/lightdm")
if is_bullseye: print("Running on a {} environment".format("desktop" if is_desktop else "lite"))
print("Bullseye Detected") if is_desktop:
manager = shell.get_window_manager()
if manager is None:
shell.bail("Unable to determine desktop manager. Please run this script in a terminal emulator, not from the desktop environment.")
print("Desktop manager: {}".format(manager))
print() print()
# "mirror" will be the new install type, but keep fbcp for backwards compatibility
if install_type == "fbcp":
install_type = "mirror"
print("""This script downloads and installs print("""This script downloads and installs
PiTFT Support using userspace touch PiTFT Support using userspace touch
@ -797,17 +897,12 @@ Run time of up to 5 minutes. Reboot required!
if install_type == "uninstall": if install_type == "uninstall":
uninstall() uninstall()
def select_display(config, interactive=False): def select_display(config, interactive=False):
global pitft_config, wayland global pitft_config
pitft_config = config pitft_config = config
print("Display Type: {}".format(pitft_config["menulabel"])) print("Display Type: {}".format(pitft_config["menulabel"]))
if is_kernel_upgrade_required(): if is_kernel_upgrade_required():
print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST") print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST")
if "force_x11" in pitft_config and pitft_config["force_x11"] and wayland:
if not interactive or shell.prompt("This display works better with X11, but Wayland is currently running. Use X11 instead? (Recommended)", default="y"):
shell.set_window_manager("x11")
wayland = False
if display in [str(x) for x in range(1, len(config) + 1)]: if display in [str(x) for x in range(1, len(config) + 1)]:
select_display(config[int(display) - 1]) select_display(config[int(display) - 1])
@ -851,6 +946,11 @@ Run time of up to 5 minutes. Reboot required!
shell.bail("""Unfortunately {rotation} degrees for the {display} is not working at this time. Please shell.bail("""Unfortunately {rotation} degrees for the {display} is not working at this time. Please
restart the script and choose a different orientation.""".format(rotation=pitftrot, display=pitft_config["menulabel"])) restart the script and choose a different orientation.""".format(rotation=pitftrot, display=pitft_config["menulabel"]))
if install_type is None:
# Show a selection menu for install_types using shell.select_n and setting the selection to the key of install_types
install_selection = shell.select_n("Select install type:", install_types.values())
install_type = list(install_types.keys())[install_selection - 1]
update_wayland_settings()
if REMOVE_KERNEL_PINNING: if REMOVE_KERNEL_PINNING:
# Checking if kernel is pinned # Checking if kernel is pinned
if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'): if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'):
@ -896,7 +996,7 @@ restart the script and choose a different orientation.""".format(rotation=pitftr
shell.bail("Unable to install display drivers") shell.bail("Unable to install display drivers")
shell.info(f"Updating {boot_dir}/config.txt...") shell.info(f"Updating {boot_dir}/config.txt...")
if not update_configtxt(tinydrm_install=(not is_bullseye)): if not update_configtxt(tinydrm_install=True):
shell.bail(f"Unable to update {boot_dir}/config.txt") shell.bail(f"Unable to update {boot_dir}/config.txt")
if "touchscreen" in pitft_config: if "touchscreen" in pitft_config:
@ -909,7 +1009,7 @@ restart the script and choose a different orientation.""".format(rotation=pitftr
shell.bail("Unable to update /etc/pointercal") shell.bail("Unable to update /etc/pointercal")
# ask for console access # ask for console access
if install_type == "console" or (install_type is None and shell.prompt("Would you like the console to appear on the PiTFT display?")): if install_type == "console":
shell.info("Updating console to PiTFT...") shell.info("Updating console to PiTFT...")
if not uninstall_fbcp(): if not uninstall_fbcp():
shell.bail("Unable to uninstall fbcp") shell.bail("Unable to uninstall fbcp")
@ -918,24 +1018,15 @@ restart the script and choose a different orientation.""".format(rotation=pitftr
else: else:
shell.info("Making sure console doesn't use PiTFT") shell.info("Making sure console doesn't use PiTFT")
if not uninstall_console(): if not uninstall_console():
shell.bail("Unable to configure console") shell.bail("Unable to uninstall console")
mirror_prompt = "Would you like the HDMI display to mirror to the PiTFT display?" # With wayland, PiTFT shows up as an additional display rather than a mirror
if wayland: if install_type == "mirror":
# With wayland, PiTFT shows up as an additional display rather than a mirror if is_desktop:
mirror_prompt = "Would you like the to use the PiTFT as a desktop display?"
if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)):
if wayland:
shell.info("Updating Wayland desktop settings...") shell.info("Updating Wayland desktop settings...")
update_wayfire_settings() update_wayland_settings()
else:
shell.info("Adding FBCP support...")
if not install_fbcp():
shell.bail("Unable to configure fbcp")
if shell.exists("/etc/lightdm"):
shell.info("Updating Desktop Touch calibration...") shell.info("Updating Desktop Touch calibration...")
if not update_xorg(tinydrm_install=wayland): if not update_xorg():
shell.bail("Unable to update calibration") shell.bail("Unable to update calibration")
else: else:
if not uninstall_fbcp(): if not uninstall_fbcp():

View file

@ -0,0 +1,114 @@
/*
* Device Tree overlay for Adafruit PiTFT 2.4" resistive touch screen with tsc2007
*
*/
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&spi0>;
__overlay__ {
status = "okay";
};
};
fragment@2 {
target = <&spidev0>;
__overlay__ {
status = "disabled";
};
};
fragment@3 {
target = <&spidev1>;
__overlay__ {
status = "disabled";
};
};
fragment@4 {
target = <&gpio>;
__overlay__ {
adafruit_2_4_tft_pins: adafruit_2_4_tft_pins {
brcm,pins = <25 24 18>;
brcm,function = <1 0 1>; /* out in out*/
brcm,pull = <0 2 2>; /* none up up */
};
};
};
fragment@5 {
target = <&spi0>;
__overlay__ {
/* needed to avoid dtc warning */
#address-cells = <1>;
#size-cells = <0>;
cs-gpios = <&gpio 8 1>;
adafruit_2_4_tft: adafruit_2_4_tft@0{
compatible = "adafruit,yx240qv29";
reg = <0>;
pinctrl-names = "default";
pinctrl-0 = <&adafruit_2_4_tft_pins>;
spi-max-frequency = <32000000>;
rotation = <90>;
reset-gpios = <&gpio 23 0>;
dc-gpios = <&gpio 25 0>;
};
};
};
fragment@7 {
target = <&i2c1>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
tsc2007: tsc2007@48 {
compatible = "ti,tsc2007";
reg = <0x48>;
ti,fuzzx = <4>;
ti,fuzzy = <4>;
ti,max-rt = <3700>;
ti,fuzzz = <4>;
ti,x-plate-ohms = <180>;
panel = <&adafruit_2_4_tft>;
pinctrl-0 = <&adafruit_2_4_tft_pins>;
interrupt-parent = <&gpio>;
interrupts = <24 2>;
gpios = <&gpio 24 1>;
};
};
};
fragment@8 {
target-path = "/soc";
__overlay__ {
backlight: backlight {
compatible = "gpio-backlight";
pinctrl-0 = <&adafruit_2_4_tft_pins>;
gpios = <&gpio 18 2>;
default-on;
};
};
};
__overrides__ {
speed = <&adafruit_2_4_tft>,"spi-max-frequency:0";
rotate = <&adafruit_2_4_tft>,"rotation:0";
fps = <&adafruit_2_4_tft>,"fps:0";
debug = <&adafruit_2_4_tft>,"debug:0";
// xohms = <&tsc2007>,"touchscreen-x-plate-ohms;0";
// swapxy = <&tsc2007>,"touchscreen-swapped-x-y?";
};
};

View file

@ -0,0 +1,3 @@
# udev rule for Focaltouch Capactive Touchscreen
SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"
SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"

2
templates/95-stmpe.rules Normal file
View file

@ -0,0 +1,2 @@
udev rule for STMPE Resistive Touchscreen Controller
SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"

View file

@ -0,0 +1 @@
SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"

View file

@ -0,0 +1,14 @@
# udev rules for SPI TFT DRM devices to create reliable symlinks
# Supports ili9341, st7789, and other SPI TFT displays
# Rule for ili9341 DRM device (and other SPI TFT displays)
SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", DRIVERS=="ili9341", SYMLINK+="dri/spitft"
# Generic rule for any SPI TFT display on spi0.0
SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", SUBSYSTEMS=="spi", SYMLINK+="dri/spitft"
# Rule for st7789 displays (add when needed)
SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", DRIVERS=="st7789", SYMLINK+="dri/spitft"
# Alternative rule using device path pattern
SUBSYSTEM=="drm", KERNEL=="card*", DEVPATH=="*/spi0/spi0.0/drm/card*", SYMLINK+="dri/spitft"

View file

@ -0,0 +1,5 @@
# udev rule for TSC2007 touchscreen to create /dev/input/touchscreen symlink and apply transformation
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="TSC2007 Touchscreen", SYMLINK+="input/touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="{calibration_matrix}"
# Backup rule using touchscreen capability and TSC2007 name pattern
SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_TOUCHSCREEN}=="1", ATTRS{name}=="*TSC2007*", SYMLINK+="input/touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="{calibration_matrix}"

View file

@ -0,0 +1,25 @@
#!/bin/bash
# Wait for TFT framebuffer to be ready
echo "Waiting for SPI TFT framebuffer..."
# Wait up to 30 seconds for /dev/fb0 or /dev/fb1 to appear
for i in {1..300}; do
for fbdev in 0 1; do
if [ -e /dev/fb$fbdev ]; then
echo "Found /dev/fb$fbdev, checking if it's ili9341..."
# Check if it's actually the ili9341 device
if dmesg | grep -q "ili9341.*fb$fbdev"; then
echo "ili9341 framebuffer ready, mapping console..."
con2fbmap 1 $fbdev
echo "Console mapped to framebuffer $fbdev"
exit 0
fi
fi
done
sleep 0.1
done
echo "Timeout waiting for SPI TFT framebuffer"
exit 1

View file

@ -0,0 +1,14 @@
[Unit]
Description=Map console to framebuffer for SPI TFT
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/con2fbmap-helper.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
TimeoutStartSec=60
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,8 @@
# --- added by adafruit-pitft-helper {date} ---
[all]
hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in!
dtparam=spi=on
dtparam=i2c1=on
dtparam=i2c_arm=on
{overlay}
# --- end adafruit-pitft-helper {date} ---

11
templates/fbcp.service Normal file
View file

@ -0,0 +1,11 @@
[Unit]
Description=Framebuffer copy utility for PiTFT
After=network.target
[Service]
Type=simple
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/fbcp
[Install]
WantedBy=multi-user.target