Files
Arch-R/kernel/drivers/panel-archr-dsi.c
Douglas Teles 8650d940e2 Migrate to mainline kernel 6.12.61 + board/overlay architecture
Major architecture change: move from BSP kernel 6.6 with pre-merged panel
DTBs to mainline kernel 6.12.61 LTS with separated board DTBs + panel
overlays.

- Board DTB = hardware profile (GPIOs, PMIC, joypad, audio). 16 boards.
  Auto-selected by U-Boot via SARADC ADC reading (hwrev).
- Panel overlay = display init sequence. 20 panels. Applied at boot time
  via boot.ini fdt apply.
- Two image variants: original (a_boot.ini) and clone (b_boot.ini)
- Kernel cross-compiles from x86 host (no ARM chroot needed)
- Initramfs boot splash with SVG rendering at 0.7s
- Out-of-tree joypad driver (singleadc-joypad) for clone boards
- Panel generic-dsi driver with archr,generic-dsi compatible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:20:09 -03:00

661 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Arch R Generic MIPI-DSI panel driver
* Copyright (C) 2025 Arch R Project
*
* Based on panel-generic-dsi.c by ROCKNIX (C) 2024
* Originally based on Rockteck jh057n00900 panel driver
* Copyright (C) Purism SPC 2019
*
* Reads panel configuration from device tree "panel_description" property.
* Supports any MIPI-DSI panel without recompilation — just change the DTS.
*
* panel_description format (array of strings):
* G delays=P,R,I,E,Y size=W,H format=rgb888 lanes=4 flags=0xNNN
* M clock=KHZ horizontal=H,HFP,HSYNC,HBP vertical=V,VFP,VSYNC,VBP default=1
* I dcs=0xCMD data=HEXPARAMS wait=MS
* I seq=HEXBYTES wait=MS
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <video/display_timing.h>
#include <video/mipi_display.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#define DRIVER_NAME "panel-archr-dsi"
struct archr_panel_delays {
int prepare;
int reset;
int init;
int enable;
int ready;
};
struct archr_panel_size {
int width;
int height;
};
struct archr_panel_mode {
int clock;
int horizontal[5];
int vertical[5];
int is_default;
struct archr_panel_mode *prev;
};
#define DCS_PSEUDO_CMD_SEQ 0x10000
struct archr_panel_init_seq {
int dcs;
int len;
int read;
int wait;
u8 *data;
struct archr_panel_init_seq *link;
};
struct archr_panel {
struct device *dev;
struct drm_panel panel;
struct gpio_desc *enable_gpio;
struct gpio_desc *reset_gpio;
struct regulator *vdd;
struct regulator *iovcc;
struct archr_panel_delays delays;
struct archr_panel_size size;
struct archr_panel_mode *modes;
struct archr_panel_init_seq *iseq;
enum drm_panel_orientation orientation;
bool prepared;
};
static int load_globals(char *data, struct mipi_dsi_device *dsi,
struct archr_panel *ctx)
{
struct device *dev = &dsi->dev;
char *param, *val;
while (*data) {
data = next_arg(data, &param, &val);
if (!val)
continue;
if (strcmp(param, "delays") == 0) {
int delays[] = {0, 5, 1, 25, 120, 50};
get_options(val, 6, delays);
ctx->delays.prepare = delays[1];
ctx->delays.reset = delays[2];
ctx->delays.init = delays[3];
ctx->delays.enable = delays[4];
ctx->delays.ready = delays[5];
} else if (strcmp(param, "size") == 0) {
int size[] = {0, -1, -1};
get_options(val, 3, size);
ctx->size.width = size[1];
ctx->size.height = size[2];
} else if (strcmp(param, "format") == 0) {
if (strcmp(val, "rgb888") == 0)
dsi->format = MIPI_DSI_FMT_RGB888;
else if (strcmp(val, "rgb666") == 0)
dsi->format = MIPI_DSI_FMT_RGB666;
else if (strcmp(val, "rgb666_packed") == 0)
dsi->format = MIPI_DSI_FMT_RGB666_PACKED;
else if (strcmp(val, "rgb565") == 0)
dsi->format = MIPI_DSI_FMT_RGB565;
else
dev_info(dev, "bad format %s\n", val);
} else if (strcmp(param, "lanes") == 0) {
if (get_option(&val, &dsi->lanes) == 0)
dev_info(dev, "bad lanes %s\n", val);
} else if (strcmp(param, "flags") == 0) {
int flags;
if (get_option(&val, &flags) == 0)
dev_info(dev, "bad flags %s\n", val);
else
dsi->mode_flags = flags;
} else {
dev_info(dev, "unknown param %s\n", param);
return -1;
}
}
return 0;
}
static int load_mode(char *data, struct mipi_dsi_device *dsi,
struct archr_panel *ctx)
{
struct device *dev = &dsi->dev;
struct archr_panel_mode *mode;
char *param, *val;
mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
if (!mode)
return -ENOMEM;
while (*data) {
data = next_arg(data, &param, &val);
if (!val)
continue;
if (strcmp(param, "clock") == 0) {
if (get_option(&val, &mode->clock) == 0)
dev_info(dev, "bad clock %s\n", val);
} else if (strcmp(param, "horizontal") == 0) {
get_options(val, 5, &mode->horizontal[0]);
} else if (strcmp(param, "vertical") == 0) {
get_options(val, 5, &mode->vertical[0]);
} else if (strcmp(param, "default") == 0) {
get_option(&val, &mode->is_default);
} else {
dev_info(dev, "Mode unhandled %s = %s\n", param, val);
}
}
if ((mode->clock > 5000) && (mode->vertical[1] > 0) &&
(mode->horizontal[1] > 0)) {
mode->prev = ctx->modes;
ctx->modes = mode;
dev_dbg(dev, "Mode %d %d %d %p -> %p\n",
mode->clock, mode->vertical[1],
mode->horizontal[1], mode, mode->prev);
return 0;
}
return -1;
}
static int load_init_seq(char *data, struct mipi_dsi_device *dsi,
struct archr_panel *ctx)
{
struct device *dev = &dsi->dev;
struct archr_panel_init_seq *item;
char *param, *val;
item = devm_kzalloc(dev, sizeof(*item), GFP_KERNEL);
if (!item)
return -ENOMEM;
item->dcs = -1;
item->len = -1;
item->read = 0;
item->wait = 0;
while (*data) {
data = next_arg(data, &param, &val);
if (!val)
continue;
if (strcmp(param, "dcs") == 0) {
item->dcs = simple_strtoul(val, NULL, 16) & 0xFF;
} else if (strcmp(param, "data") == 0) {
item->len = (strlen(val)) >> 1;
item->data = devm_kzalloc(dev, item->len, GFP_KERNEL);
if (!item->data)
return -ENOMEM;
if (hex2bin(item->data, val, item->len) != 0) {
dev_info(dev, "bad data %s\n", val);
return -1;
}
} else if (strcmp(param, "seq") == 0) {
item->dcs = DCS_PSEUDO_CMD_SEQ;
item->len = (strlen(val)) >> 1;
item->data = devm_kzalloc(dev, item->len, GFP_KERNEL);
if (!item->data)
return -ENOMEM;
if (hex2bin(item->data, val, item->len) != 0) {
dev_info(dev, "bad seq %s\n", val);
return -1;
}
dev_dbg(dev, "loaded seq len=%d %s\n", item->len, val);
} else if (strcmp(param, "read") == 0) {
item->read = simple_strtoul(val, NULL, 16);
} else if (strcmp(param, "wait") == 0) {
item->wait = simple_strtoul(val, NULL, 16);
} else {
dev_info(dev, "Init unhandled %s = %s\n", param, val);
}
}
if (item->dcs >= 0) {
item->link = ctx->iseq;
ctx->iseq = item;
return 0;
}
return -1;
}
static int load_panel_description_line(char *data, struct mipi_dsi_device *dsi,
struct archr_panel *ctx)
{
size_t pos;
for (pos = 0; data[pos] != 0; pos++) {
if ((data[pos] == '#') || data[pos] == '\n') {
data[pos] = 0;
break;
}
}
if (pos < 2)
return 0;
switch (data[0]) {
case 'G':
load_globals(data + 1, dsi, ctx);
break;
case 'M':
load_mode(data + 1, dsi, ctx);
break;
case 'I':
load_init_seq(data + 1, dsi, ctx);
break;
default:
dev_info(&dsi->dev, "Unhandled line: %s\n", data);
}
return 0;
}
static int load_panel_description(struct mipi_dsi_device *dsi,
struct archr_panel *ctx)
{
struct device *dev = &dsi->dev;
struct archr_panel_init_seq *rev, *fwd, *tmp;
const char *line;
char *buf;
size_t linescnt, i;
int ret;
linescnt = of_property_count_strings(dev->of_node, "panel_description");
if (linescnt < 1) {
dev_err(dev, "failed to read panel_description from device tree (%ld)\n",
linescnt);
return -EINVAL;
}
for (i = 0; i < linescnt; i++) {
ret = of_property_read_string_index(dev->of_node,
"panel_description",
i, &line);
if (ret < 0) {
dev_err(dev, "failed to read panel_description[%ld]: %d\n",
i, ret);
return ret;
}
buf = kstrdup(line, GFP_KERNEL);
if (!buf)
return -ENOMEM;
dev_dbg(dev, "desc[%ld]: %s\n", i, line);
load_panel_description_line(buf, dsi, ctx);
kfree(buf);
}
/* Reverse init sequence list (DTS order → execution order) */
rev = ctx->iseq;
fwd = NULL;
while (rev) {
tmp = rev;
rev = tmp->link;
tmp->link = fwd;
fwd = tmp;
}
ctx->iseq = fwd;
return 0;
}
static inline struct archr_panel *panel_to_archr(struct drm_panel *panel)
{
return container_of(panel, struct archr_panel, panel);
}
static int archr_panel_init_sequence(struct archr_panel *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
struct device *dev = ctx->dev;
struct archr_panel_init_seq *iseq = ctx->iseq;
int ret;
while (iseq) {
if (iseq->read > 0) {
u8 readbuf[8];
if (iseq->read > 8)
iseq->read = 8;
ret = mipi_dsi_generic_read(dsi, iseq->data,
iseq->len, &readbuf[0],
iseq->read);
if (ret < 0) {
dev_err(dev, "failed to read: %d\n", ret);
} else {
int j;
for (j = 0; j < ret; j++)
dev_info(dev, "read[%d]: %02x\n",
j, readbuf[j]);
}
} else if (iseq->dcs == DCS_PSEUDO_CMD_SEQ) {
ret = mipi_dsi_dcs_write_buffer(dsi, iseq->data,
iseq->len);
dev_dbg(dev, "iseq %px len=%d -> %d\n",
(void *)iseq, iseq->len, ret);
} else {
ret = mipi_dsi_dcs_write(dsi, iseq->dcs, iseq->data,
iseq->len);
dev_dbg(dev, "iseq %02x len=%d -> %d\n",
iseq->dcs, iseq->len, ret);
}
if (iseq->wait)
msleep(iseq->wait);
iseq = iseq->link;
}
dev_dbg(dev, "Panel init sequence done\n");
return 0;
}
static int archr_panel_unprepare(struct drm_panel *panel)
{
struct archr_panel *ctx = panel_to_archr(panel);
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
int ret;
if (!ctx->prepared)
return 0;
ret = mipi_dsi_dcs_set_display_off(dsi);
if (ret < 0)
dev_err(ctx->dev, "failed to set display off: %d\n", ret);
ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
if (ret < 0) {
dev_err(ctx->dev, "failed to enter sleep mode: %d\n", ret);
return ret;
}
if (ctx->enable_gpio)
gpiod_set_value_cansleep(ctx->enable_gpio, 0);
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
regulator_disable(ctx->iovcc);
regulator_disable(ctx->vdd);
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
ctx->prepared = false;
return 0;
}
static int archr_panel_prepare(struct drm_panel *panel)
{
struct archr_panel *ctx = panel_to_archr(panel);
int ret;
if (ctx->prepared)
return 0;
ret = regulator_enable(ctx->vdd);
if (ret < 0) {
dev_err(ctx->dev, "Failed to enable vdd supply: %d\n", ret);
return ret;
}
ret = regulator_enable(ctx->iovcc);
if (ret < 0) {
dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", ret);
goto disable_vdd;
}
if (ctx->enable_gpio)
gpiod_set_value_cansleep(ctx->enable_gpio, 1);
msleep(ctx->delays.prepare);
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
msleep(ctx->delays.reset);
gpiod_set_value_cansleep(ctx->reset_gpio, 0);
msleep(ctx->delays.init);
ret = archr_panel_init_sequence(ctx);
if (ret < 0) {
dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
goto disable_iovcc;
}
ret = mipi_dsi_dcs_set_display_on(
to_mipi_dsi_device(ctx->dev));
if (ret < 0) {
dev_err(ctx->dev, "Failed to set display on: %d\n", ret);
goto disable_iovcc;
}
ret = mipi_dsi_dcs_exit_sleep_mode(
to_mipi_dsi_device(ctx->dev));
if (ret < 0) {
dev_err(ctx->dev, "Failed to exit sleep mode: %d\n", ret);
goto disable_iovcc;
}
msleep(ctx->delays.enable);
ctx->prepared = true;
return 0;
disable_iovcc:
regulator_disable(ctx->iovcc);
disable_vdd:
regulator_disable(ctx->vdd);
return ret;
}
static const struct drm_display_mode mode_template = { };
static int archr_panel_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct archr_panel *ctx = panel_to_archr(panel);
struct drm_display_mode mode_tmp;
struct drm_display_mode *mode;
struct archr_panel_mode *genmode = ctx->modes;
while (genmode) {
dev_dbg(ctx->dev, "mode %d %dx%d\n",
genmode->clock, genmode->horizontal[1],
genmode->vertical[1]);
mode_tmp = mode_template;
mode_tmp.clock = genmode->clock;
mode_tmp.hdisplay = genmode->horizontal[1];
mode_tmp.hsync_start = mode_tmp.hdisplay + genmode->horizontal[2];
mode_tmp.hsync_end = mode_tmp.hsync_start + genmode->horizontal[3];
mode_tmp.htotal = mode_tmp.hsync_end + genmode->horizontal[4];
mode_tmp.vdisplay = genmode->vertical[1];
mode_tmp.vsync_start = mode_tmp.vdisplay + genmode->vertical[2];
mode_tmp.vsync_end = mode_tmp.vsync_start + genmode->vertical[3];
mode_tmp.vtotal = mode_tmp.vsync_end + genmode->vertical[4];
mode_tmp.width_mm = ctx->size.width;
mode_tmp.height_mm = ctx->size.height;
mode_tmp.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
mode = drm_mode_duplicate(connector->dev, &mode_tmp);
if (!mode) {
dev_err(ctx->dev, "Failed to add mode\n");
return -ENOMEM;
}
drm_mode_set_name(mode);
mode->type = DRM_MODE_TYPE_DRIVER;
if (genmode->is_default)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
genmode = genmode->prev;
}
connector->display_info.width_mm = ctx->size.width;
connector->display_info.height_mm = ctx->size.height;
drm_connector_set_panel_orientation(connector, ctx->orientation);
return 1;
}
static enum drm_panel_orientation archr_panel_get_orientation(
struct drm_panel *panel)
{
struct archr_panel *ctx = panel_to_archr(panel);
return ctx->orientation;
}
static const struct drm_panel_funcs archr_panel_funcs = {
.unprepare = archr_panel_unprepare,
.prepare = archr_panel_prepare,
.get_modes = archr_panel_get_modes,
.get_orientation = archr_panel_get_orientation,
};
static int archr_panel_probe(struct mipi_dsi_device *dsi)
{
struct device *dev = &dsi->dev;
struct archr_panel *ctx;
int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable",
GPIOD_OUT_LOW);
if (IS_ERR(ctx->enable_gpio)) {
dev_err(dev, "cannot get enable gpio\n");
ctx->enable_gpio = NULL;
}
ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(ctx->reset_gpio)) {
dev_err(dev, "cannot get reset gpio\n");
return PTR_ERR(ctx->reset_gpio);
}
ctx->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(ctx->vdd)) {
ret = PTR_ERR(ctx->vdd);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Failed to request vdd regulator: %d\n",
ret);
return ret;
}
ctx->iovcc = devm_regulator_get(dev, "iovcc");
if (IS_ERR(ctx->iovcc)) {
ret = PTR_ERR(ctx->iovcc);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Failed to request iovcc regulator: %d\n",
ret);
return ret;
}
ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation);
if (ret < 0) {
dev_err(dev, "%pOF: failed to get orientation %d\n",
dev->of_node, ret);
return ret;
}
mipi_dsi_set_drvdata(dsi, ctx);
ctx->dev = dev;
/* Defaults (overridden by G line in panel_description) */
dsi->lanes = 1;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET |
MIPI_DSI_CLOCK_NON_CONTINUOUS;
ret = load_panel_description(dsi, ctx);
if (ret < 0) {
dev_err(dev, "Failed to load panel description\n");
return ret;
}
dev_info(dev, "lanes %d, format %d, mode %lx\n",
dsi->lanes, dsi->format, dsi->mode_flags);
drm_panel_init(&ctx->panel, &dsi->dev, &archr_panel_funcs,
DRM_MODE_CONNECTOR_DSI);
ret = drm_panel_of_backlight(&ctx->panel);
if (ret)
return ret;
drm_panel_add(&ctx->panel);
ret = mipi_dsi_attach(dsi);
if (ret < 0) {
dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
drm_panel_remove(&ctx->panel);
return ret;
}
return 0;
}
static void archr_panel_shutdown(struct mipi_dsi_device *dsi)
{
struct archr_panel *ctx = mipi_dsi_get_drvdata(dsi);
drm_panel_unprepare(&ctx->panel);
drm_panel_disable(&ctx->panel);
}
static void archr_panel_remove(struct mipi_dsi_device *dsi)
{
struct archr_panel *ctx = mipi_dsi_get_drvdata(dsi);
int ret;
archr_panel_shutdown(dsi);
ret = mipi_dsi_detach(dsi);
if (ret < 0)
dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n",
ret);
drm_panel_remove(&ctx->panel);
}
static const struct of_device_id archr_panel_of_match[] = {
{ .compatible = "archr,generic-dsi" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, archr_panel_of_match);
static struct mipi_dsi_driver archr_panel_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = archr_panel_of_match,
},
.probe = archr_panel_probe,
.remove = archr_panel_remove,
.shutdown = archr_panel_shutdown,
};
module_mipi_dsi_driver(archr_panel_driver);
MODULE_AUTHOR("Arch R Project");
MODULE_DESCRIPTION("Arch R Generic MIPI-DSI panel driver");
MODULE_LICENSE("GPL v2");