Skip to content

Instantly share code, notes, and snippets.

@devsnek
Last active April 29, 2026 11:46
Show Gist options
  • Select an option

  • Save devsnek/1aee3108b55c8bf104ceeecc4162acce to your computer and use it in GitHub Desktop.

Select an option

Save devsnek/1aee3108b55c8bf104ceeecc4162acce to your computer and use it in GitHub Desktop.
diff --git a/target/linux/ipq40xx/dts/qcom-ipq4018-utr.dts b/target/linux/ipq40xx/dts/qcom-ipq4018-utr.dts
index 6fc2769145..e1a5040644 100644
--- a/target/linux/ipq40xx/dts/qcom-ipq4018-utr.dts
+++ b/target/linux/ipq40xx/dts/qcom-ipq4018-utr.dts
@@ -49,6 +49,8 @@
keys {
compatible = "gpio-keys";
+ pinctrl-0 = <&key_pins>;
+ pinctrl-names = "default";
reset {
label = "reset";
@@ -63,6 +65,22 @@
gpios = <&tlmm 2 GPIO_ACTIVE_HIGH>;
default-on;
};
+
+ i2c {
+ compatible = "i2c-gpio";
+ sda-gpios = <&tlmm 5 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&tlmm 58 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ i2c-gpio,delay-us = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ accelerometer@19 {
+ compatible = "st,lis2dw12";
+ reg = <0x19>;
+ interrupt-parent = <&tlmm>;
+ interrupts = <1 IRQ_TYPE_EDGE_FALLING>;
+ };
+ };
};
&prng {
@@ -253,6 +271,7 @@
pinmux {
function = "blsp_spi0";
pins = "gpio55", "gpio56", "gpio57";
+ bias-disable;
};
/*
@@ -261,6 +280,7 @@
pinmux_cs {
function = "gpio";
pins = "gpio54", "gpio59", "gpio4";
+ bias-pull-up;
};
};
@@ -283,9 +303,18 @@
pins = "gpio62";
function = "gpio";
bias-disable;
+ drive-strength = <4>;
output-high;
};
};
+
+ key_pins: key_pins {
+ key_reset {
+ pins = "gpio63";
+ function = "gpio";
+ bias-pull-up;
+ };
+ };
};
&mdio {
diff --git a/target/linux/ipq40xx/image/generic.mk b/target/linux/ipq40xx/image/generic.mk
index 3e6d55f873..02b348baa4 100644
--- a/target/linux/ipq40xx/image/generic.mk
+++ b/target/linux/ipq40xx/image/generic.mk
@@ -1285,7 +1285,7 @@ define Device/ubnt_utr
IMAGES := factory.ubi sysupgrade.bin
IMAGE/factory.ubi := append-ubi
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
- DEVICE_PACKAGES := kmod-input-gpio-keys kmod-fb-tft-st7789v
+ DEVICE_PACKAGES := kmod-input-gpio-keys kmod-fb-tft-st7789v kmod-bluetooth kmod-btusb
endef
TARGET_DEVICES += ubnt_utr
diff --git a/target/linux/ipq40xx/patches-6.12/875-fb-st7789v-geometry.patch b/target/linux/ipq40xx/patches-6.12/875-fb-st7789v-geometry.patch
index c5974ebb3a..870ef04941 100644
--- a/target/linux/ipq40xx/patches-6.12/875-fb-st7789v-geometry.patch
+++ b/target/linux/ipq40xx/patches-6.12/875-fb-st7789v-geometry.patch
@@ -1,14 +1,20 @@
From: Matt Eaton <linux@divinehawk.com>
Subject: [PATCH] staging: fbtft: st7789v: add configurable panel geometry and offset
+Add device-tree-driven panel geometry and a fixed x/y offset for the
+addr-window. Pad xres_virtual up to a 32-bit boundary so fbcon's
+sys_imageblit slow path doesn't scatter glyph rows when the visible
+width isn't word-aligned at the framebuffer's bpp; trim each row back
+to the visible width before shipping pixels to the panel.
+
Signed-off-by: Matt Eaton <linux@divinehawk.com>
---
- drivers/staging/fbtft/fb_st7789v.c | 102 +++++++++++++++++++++++++++++++++++--
- 1 file changed, 98 insertions(+), 4 deletions(-)
+ drivers/staging/fbtft/fb_st7789v.c | 146 ++++++++++++++++++++++
+ 1 file changed, 126 insertions(+), 20 deletions(-)
--- a/drivers/staging/fbtft/fb_st7789v.c
+++ b/drivers/staging/fbtft/fb_st7789v.c
-@@ -76,6 +76,92 @@ enum st7789v_command {
+@@ -76,6 +76,102 @@
static struct completion panel_te; /* completion for panel TE line */
static int irq_te; /* Linux IRQ for LCD TE line */
@@ -50,44 +56,36 @@ Signed-off-by: Matt Eaton <linux@divinehawk.com>
+static void st7789v_apply_panel_cfg(struct fbtft_par *par)
+{
+ struct st7789v_panel_cfg *cfg = par->extra;
++ unsigned int bpp = par->info->var.bits_per_pixel;
++ unsigned int xres_v;
++
++ /* Pad xres_virtual so line_length is 32-bit aligned: fbcon's
++ * sys_imageblit slow path writes glyphs as u32 words and
++ * assumes each row starts at the same alignment, so a
++ * non-word-aligned stride scatters glyph rows horizontally.
++ */
++ xres_v = round_up(cfg->panel_width * bpp, 32) / bpp;
+
+ par->info->var.xres = cfg->panel_width;
+ par->info->var.yres = cfg->panel_height;
-+ par->info->var.xres_virtual = cfg->panel_width;
++ par->info->var.xres_virtual = xres_v;
+ par->info->var.yres_virtual = cfg->panel_height;
-+ par->info->fix.line_length = cfg->panel_width *
-+ par->info->var.bits_per_pixel / 8;
-+ par->info->fix.smem_len = cfg->panel_width * cfg->panel_height *
-+ par->info->var.bits_per_pixel / 8;
++ par->info->fix.line_length = xres_v * bpp / 8;
++ par->info->fix.smem_len = par->info->fix.line_length *
++ cfg->panel_height;
+}
+
+static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
+{
+ struct st7789v_panel_cfg *cfg = par->extra;
-+ u16 xoff = cfg->x_offset;
-+ u16 yoff = cfg->y_offset;
-+
-+ switch (par->info->var.rotate) {
-+ case 0:
-+ break;
-+ case 90:
-+ xoff = cfg->y_offset;
-+ yoff = 320 - cfg->x_offset - cfg->panel_width;
-+ break;
-+ case 180:
-+ xoff = 240 - cfg->x_offset - cfg->panel_width;
-+ yoff = 320 - cfg->y_offset - cfg->panel_height;
-+ break;
-+ case 270:
-+ xoff = 240 - cfg->y_offset - cfg->panel_height;
-+ yoff = cfg->x_offset;
-+ break;
-+ default:
-+ break;
-+ }
+
-+ xs += xoff; xe += xoff;
-+ ys += yoff; ye += yoff;
++ /* MADCTL_MX/MY/MV reverse the *write order* within the addr
++ * window; they don't translate the physical address mapping.
++ * The window therefore always stays at the panel's natural
++ * visible region regardless of rotation.
++ */
++ xs += cfg->x_offset; xe += cfg->x_offset;
++ ys += cfg->y_offset; ye += cfg->y_offset;
+
+ write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+ (xs >> 8) & 0xff, xs & 0xff,
@@ -97,11 +95,29 @@ Signed-off-by: Matt Eaton <linux@divinehawk.com>
+ (ye >> 8) & 0xff, ye & 0xff);
+ write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
++
++static int write_vmem_bus(struct fbtft_par *par, size_t offset, size_t len)
++{
++ struct device *dev = par->info->device;
++
++ switch (par->pdata->display.buswidth) {
++ case 8:
++ return fbtft_write_vmem16_bus8(par, offset, len);
++ case 9:
++ return fbtft_write_vmem16_bus9(par, offset, len);
++ case 16:
++ return fbtft_write_vmem16_bus16(par, offset, len);
++ default:
++ dev_err(dev, "Unsupported buswidth %d\n",
++ par->pdata->display.buswidth);
++ return 0;
++ }
++}
+
static irqreturn_t panel_te_handler(int irq, void *data)
{
complete(&panel_te);
-@@ -214,6 +300,13 @@ static int init_display(struct fbtft_par
+@@ -214,6 +310,13 @@
if (HSD20_IPS)
write_reg(par, MIPI_DCS_ENTER_INVERT_MODE);
@@ -115,7 +131,55 @@ Signed-off-by: Matt Eaton <linux@divinehawk.com>
return 0;
}
-@@ -375,10 +468,11 @@ static struct fbtft_display display = {
+@@ -228,7 +331,9 @@
+ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
+ {
+ struct device *dev = par->info->device;
+- int ret;
++ struct st7789v_panel_cfg *cfg = par->extra;
++ size_t row_bytes, stride, sent;
++ int ret = 0;
+
+ if (irq_te) {
+ enable_irq(irq_te);
+@@ -241,21 +346,21 @@
+ disable_irq(irq_te);
+ }
+
+- switch (par->pdata->display.buswidth) {
+- case 8:
+- ret = fbtft_write_vmem16_bus8(par, offset, len);
+- break;
+- case 9:
+- ret = fbtft_write_vmem16_bus9(par, offset, len);
+- break;
+- case 16:
+- ret = fbtft_write_vmem16_bus16(par, offset, len);
+- break;
+- default:
+- dev_err(dev, "Unsupported buswidth %d\n",
+- par->pdata->display.buswidth);
+- ret = 0;
+- break;
++ stride = par->info->fix.line_length;
++ row_bytes = cfg ? (size_t)cfg->panel_width *
++ par->info->var.bits_per_pixel / 8 : stride;
++
++ if (row_bytes == stride)
++ return write_vmem_bus(par, offset, len);
++
++ /* line_length is padded so sys_imageblit stays 32-bit aligned
++ * across rows; ship only the visible portion of each row and
++ * skip the trailing pad bytes.
++ */
++ for (sent = 0; sent < len; sent += stride) {
++ ret = write_vmem_bus(par, offset + sent, row_bytes);
++ if (ret < 0)
++ return ret;
+ }
+
+ return ret;
+@@ -375,10 +480,11 @@
.gamma = HSD20_IPS_GAMMA,
.fbtftops = {
.init_display = init_display,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment