* [PATCH 1/4] video: dw_mipi_dsi: disable the panel before powering down the host
@ 2026-11-08 10:10 Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 2/4] video: dw_mipi_dsi: refuse transfers while the host is powered down Ahmad Fatoum
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Ahmad Fatoum @ 2026-11-08 10:10 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
VPL_DISABLE powered down the DSI host first and only then passed the
ioctl on to the panel. The DCS commands the panel sends on disable,
e.g. display off and sleep in for the OTM8009A, thus went into a host
held in reset and never reached the panel.
Linux disables the panel before the host's post_disable, so let's do
the same and forward VPL_DISABLE to the panel first.
Fixes: 11331c15e5bd ("video: add Designware MIPI-DSI support")
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/dw_mipi_dsi.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/video/dw_mipi_dsi.c b/drivers/video/dw_mipi_dsi.c
index bf95b1b9f6ff..b6a53844edec 100644
--- a/drivers/video/dw_mipi_dsi.c
+++ b/drivers/video/dw_mipi_dsi.c
@@ -906,6 +906,7 @@ static int dw_mipi_dsi_ioctl(struct vpl *vpl, unsigned int port,
{
struct dw_mipi_dsi *dsi = container_of(vpl, struct dw_mipi_dsi, vpl);
struct drm_display_mode mode = {};
+ int ret;
switch (cmd) {
case VPL_ENABLE:
@@ -913,8 +914,10 @@ static int dw_mipi_dsi_ioctl(struct vpl *vpl, unsigned int port,
dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
break;
case VPL_DISABLE:
+ /* The panel may still need the link to send its disable commands */
+ ret = vpl_bridge_ioctl(dsi->panel_bridge, cmd, data);
dw_mipi_dsi_bridge_post_atomic_disable(dsi);
- break;
+ return ret;
case VPL_PREPARE:
/*
* We store this to simplify debugging by allowing:
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/4] video: dw_mipi_dsi: refuse transfers while the host is powered down
2026-11-08 10:10 [PATCH 1/4] video: dw_mipi_dsi: disable the panel before powering down the host Ahmad Fatoum
@ 2026-11-08 10:10 ` Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 3/4] video: dw_mipi_dsi: give transfers in video mode a frame to complete Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 4/4] commands: mipi_dsi: add command for easier DCS experimentation Ahmad Fatoum
2 siblings, 0 replies; 5+ messages in thread
From: Ahmad Fatoum @ 2026-11-08 10:10 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The host is only set up between VPL_PREPARE and VPL_DISABLE. A
transfer outside that window, e.g. from a panel driver or a debug
command while the display is off, ends up with the controller held in
reset: writes are reported as successful without anything being sent
and reads time out. On SoCs whose glue doesn't keep pclk enabled, the
register accesses would hit a clock-gated block.
Track whether the host is powered and fail transfers early with
-ENETDOWN otherwise.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/dw_mipi_dsi.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/video/dw_mipi_dsi.c b/drivers/video/dw_mipi_dsi.c
index b6a53844edec..c936463261a5 100644
--- a/drivers/video/dw_mipi_dsi.c
+++ b/drivers/video/dw_mipi_dsi.c
@@ -245,6 +245,7 @@ struct dw_mipi_dsi {
u32 format;
unsigned long mode_flags;
struct fb_videomode *mode;
+ bool powered;
int vpg_mode;
@@ -447,6 +448,11 @@ static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
struct mipi_dsi_packet packet;
int ret, nb_bytes;
+ if (!dsi->powered) {
+ dev_dbg(dsi->dev, "transfer while host is powered down\n");
+ return -ENETDOWN;
+ }
+
ret = mipi_dsi_create_packet(&packet, msg);
if (ret) {
dev_err(dsi->dev, "failed to create packet: %d\n", ret);
@@ -827,6 +833,7 @@ static void dw_mipi_dsi_bridge_post_atomic_disable(struct dw_mipi_dsi *dsi)
* Note: panel-bridge disable & panel disable has been called
* before by the drm framework.
*/
+ dsi->powered = false;
dw_mipi_dsi_set_mode(dsi, 0);
if (phy_ops->power_off)
@@ -885,6 +892,8 @@ static void dw_mipi_dsi_mode_set(struct dw_mipi_dsi *dsi,
if (phy_ops->power_on)
phy_ops->power_on(dsi->plat_data->priv_data);
+
+ dsi->powered = true;
}
static int vpg_param_set(struct param_d *param, void *data)
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 3/4] video: dw_mipi_dsi: give transfers in video mode a frame to complete
2026-11-08 10:10 [PATCH 1/4] video: dw_mipi_dsi: disable the panel before powering down the host Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 2/4] video: dw_mipi_dsi: refuse transfers while the host is powered down Ahmad Fatoum
@ 2026-11-08 10:10 ` Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 4/4] commands: mipi_dsi: add command for easier DCS experimentation Ahmad Fatoum
2 siblings, 0 replies; 5+ messages in thread
From: Ahmad Fatoum @ 2026-11-08 10:10 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Once the host is in video mode, DCS reads and long writes that don't
fit into the horizontal blanking are deferred to the vertical blanking,
so they can take up to a frame.
On the STM32MP157C-DK2 at 49 Hz, i.e. 20 ms per frame, a DCS read took
up to 17.3 ms and a 13 byte write up to 19.9 ms depending on when in
the frame it was issued, and the occasional read failed with "Timeout
during read operation".
Raise the timeout to 100 ms, which covers a frame down to 10 Hz. This
also covers the init sequence of panels like the ST7703, which is sent
on enable, after the host has switched to video mode.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/dw_mipi_dsi.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/video/dw_mipi_dsi.c b/drivers/video/dw_mipi_dsi.c
index c936463261a5..a61a454be989 100644
--- a/drivers/video/dw_mipi_dsi.c
+++ b/drivers/video/dw_mipi_dsi.c
@@ -221,7 +221,8 @@
#define MAX_RD_TIME_V131(lbcc) ((lbcc) & 0x7fff)
#define PHY_STATUS_TIMEOUT_US 10000
-#define CMD_PKT_STATUS_TIMEOUT_US 20000
+/* in video mode, LP transfers may wait for the blanking of the next frame */
+#define CMD_PKT_STATUS_TIMEOUT_US 100000
enum vpg_mode {
VPG_DISABLED,
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 4/4] commands: mipi_dsi: add command for easier DCS experimentation
2026-11-08 10:10 [PATCH 1/4] video: dw_mipi_dsi: disable the panel before powering down the host Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 2/4] video: dw_mipi_dsi: refuse transfers while the host is powered down Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 3/4] video: dw_mipi_dsi: give transfers in video mode a frame to complete Ahmad Fatoum
@ 2026-11-08 10:10 ` Ahmad Fatoum
2026-11-09 9:24 ` Sascha Hauer
2 siblings, 1 reply; 5+ messages in thread
From: Ahmad Fatoum @ 2026-11-08 10:10 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
To simplify MIPI DSI panel bring up, add a mipi_dsi command along the
same lines as the mipi_dbi, which lists DSI devices, sends DCS writes
and reads back registers, so this can be done from the shell.
Like the mipi_dbi command, the register dump only reads known read
commands. The panel executes the command byte of a DCS read request:
dumping 0x00-0xff unconditionally soft-reset the OTM8009A on the
STM32MP157C-DK2 and put it into sleep mode.
Tested on the STM32MP157C-DK2 (OTM8009A) and the Powkiddy RGB30 (ST7703).
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
arch/arm/configs/stm32mp_defconfig | 1 +
commands/Kconfig | 27 +++
commands/Makefile | 1 +
commands/mipi_dsi.c | 355 +++++++++++++++++++++++++++++
test/py/test_mipi_dsi.py | 78 +++++++
5 files changed, 462 insertions(+)
create mode 100644 commands/mipi_dsi.c
create mode 100644 test/py/test_mipi_dsi.py
diff --git a/arch/arm/configs/stm32mp_defconfig b/arch/arm/configs/stm32mp_defconfig
index e1aef7c86fc0..13aed489ccea 100644
--- a/arch/arm/configs/stm32mp_defconfig
+++ b/arch/arm/configs/stm32mp_defconfig
@@ -95,6 +95,7 @@ CONFIG_CMD_GPIO=y
CONFIG_CMD_LED=y
CONFIG_CMD_POWEROFF=y
CONFIG_CMD_SPI=y
+CONFIG_CMD_MIPI_DSI=y
CONFIG_CMD_USBGADGET=y
CONFIG_CMD_WD=y
CONFIG_CMD_BAREBOX_UPDATE=y
diff --git a/commands/Kconfig b/commands/Kconfig
index a43f0fbd4d40..eb4067d8108c 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -2263,6 +2263,33 @@ config CMD_MIPI_DBI
-d DEVICE select specific device (default is first registered)
-w issue write command
+config CMD_MIPI_DSI
+ bool
+ depends on DRIVER_VIDEO_MIPI_DSI
+ select PRINTF_HEXSTR
+ prompt "mipi_dsi command"
+ help
+ read/write DCS registers of MIPI DSI devices
+
+ Usage: mipi_dsi [-lvdinwgLH] [-c CH] [-p on|off] [CMD] [DATA...]
+
+ Without CMD, dump the standard DCS registers.
+ CMD and DATA bytes are given in hex.
+
+ Options:
+ -l list all MIPI DSI devices
+ -v list with host, channel, lanes, format and mode flags
+ -d DEVICE select specific device (default is first registered)
+ -i read the panel identification registers
+ -n LEN number of bytes to read (default: known length, else 1)
+ -w issue write command
+ -g send generic packets: CMD and DATA are the payload,
+ reads take up to two bytes
+ -p on|off send Turn On or Shutdown Peripheral
+ -c CH use virtual channel CH (0-3)
+ -L send in low power mode
+ -H send in high speed mode
+
config CMD_LED_TRIGGER
bool
depends on LED_TRIGGERS
diff --git a/commands/Makefile b/commands/Makefile
index 7dae582225f5..f91f6335ff8e 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_CMD_I2C) += i2c.o
obj-$(CONFIG_CMD_SPI) += spi.o
obj-$(CONFIG_CMD_PWM) += pwm.o
obj-$(CONFIG_CMD_MIPI_DBI) += mipi_dbi.o
+obj-$(CONFIG_CMD_MIPI_DSI) += mipi_dsi.o
obj-$(CONFIG_CMD_UBI) += ubi.o
obj-$(CONFIG_CMD_UBIFORMAT) += ubiformat.o
obj-$(CONFIG_CMD_MENU) += menu.o
diff --git a/commands/mipi_dsi.c b/commands/mipi_dsi.c
new file mode 100644
index 000000000000..ccef279b9830
--- /dev/null
+++ b/commands/mipi_dsi.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: © 2025 Ahmad Fatoum
+
+#include <command.h>
+#include <getopt.h>
+#include <video/mipi_dsi.h>
+#include <video/mipi_display.h>
+
+#define MIPI_DSI_CMD_MAX_LEN 64
+
+struct dcs_read {
+ u8 cmd;
+ u8 len;
+};
+
+/*
+ * Panels execute the command byte of a DCS read request like that of
+ * a write, so only ever send commands known to be reads.
+ */
+static const struct dcs_read dcs_reads[] = {
+ { MIPI_DCS_GET_DISPLAY_ID, 3 },
+ { MIPI_DCS_GET_RED_CHANNEL, 1 },
+ { MIPI_DCS_GET_GREEN_CHANNEL, 1 },
+ { MIPI_DCS_GET_BLUE_CHANNEL, 1 },
+ { MIPI_DCS_GET_DISPLAY_STATUS, 4 },
+ { MIPI_DCS_GET_POWER_MODE, 1 },
+ { MIPI_DCS_GET_ADDRESS_MODE, 1 },
+ { MIPI_DCS_GET_DISPLAY_MODE, 1 },
+ { MIPI_DCS_GET_SIGNAL_MODE, 1 },
+ { MIPI_DCS_GET_DIAGNOSTIC_RESULT, 1 },
+ { MIPI_DCS_GET_SCANLINE, 2 },
+ { MIPI_DCS_GET_DISPLAY_BRIGHTNESS, 1 },
+ { MIPI_DCS_GET_CONTROL_DISPLAY, 1 },
+ { MIPI_DCS_GET_CABC_MIN_BRIGHTNESS, 1 },
+};
+
+/* display ID, DDB and the nonstandard, but widespread ID1-ID3 */
+static const struct dcs_read dcs_ids[] = {
+ { MIPI_DCS_GET_DISPLAY_ID, 3 },
+ { MIPI_DCS_READ_DDB_START, 5 },
+ { 0xda, 1 },
+ { 0xdb, 1 },
+ { 0xdc, 1 },
+};
+
+static const struct dcs_read *dcs_read_find(const struct dcs_read *reads,
+ size_t nreads, u8 cmd)
+{
+ for (int i = 0; i < nreads; i++) {
+ if (reads[i].cmd == cmd)
+ return &reads[i];
+ }
+
+ return NULL;
+}
+
+static unsigned int dcs_read_len(u8 cmd)
+{
+ const struct dcs_read *reg;
+
+ reg = dcs_read_find(dcs_reads, ARRAY_SIZE(dcs_reads), cmd) ?:
+ dcs_read_find(dcs_ids, ARRAY_SIZE(dcs_ids), cmd);
+
+ return reg ? reg->len : 1;
+}
+
+/*
+ * DCS reads send CMD, generic reads send CMD and up to one more
+ * parameter byte in @tx.
+ */
+static int mipi_dsi_command_show(struct mipi_dsi_device *dsi, bool generic,
+ const u8 *tx, size_t tx_len, size_t len,
+ bool prefix)
+{
+ u8 buf[MIPI_DSI_CMD_MAX_LEN];
+ ssize_t ret;
+
+ ret = mipi_dsi_set_maximum_return_packet_size(dsi, len);
+ if (!ret && generic)
+ ret = mipi_dsi_generic_read(dsi, tx, tx_len, buf, len);
+ else if (!ret)
+ ret = mipi_dsi_dcs_read(dsi, tx[0], buf, len);
+
+ if (prefix)
+ printf("%02x: ", tx[0]);
+
+ if (ret < 0) {
+ printf("read failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ printf("%*phN\n", (int)ret, buf);
+
+ return 0;
+}
+
+static int mipi_dsi_command_show_list(struct mipi_dsi_device *dsi,
+ const struct dcs_read *reads,
+ size_t nreads)
+{
+ int ret, err = 0;
+
+ for (int i = 0; i < nreads; i++) {
+ ret = mipi_dsi_command_show(dsi, false, &reads[i].cmd, 1,
+ reads[i].len, true);
+ if (ret == -ENETDOWN)
+ return ret;
+ if (ret)
+ err = ret;
+ if (ctrlc())
+ return -EINTR;
+ }
+
+ return err;
+}
+
+static const char * const mipi_dsi_formats[] = {
+ [MIPI_DSI_FMT_RGB888] = "RGB888",
+ [MIPI_DSI_FMT_RGB666] = "RGB666",
+ [MIPI_DSI_FMT_RGB666_PACKED] = "RGB666_PACKED",
+ [MIPI_DSI_FMT_RGB565] = "RGB565",
+};
+
+static const char * const mipi_dsi_mode_flags[] = {
+ "video", "video-burst", "video-sync-pulse", "video-auto-vert",
+ "video-hse", "video-no-hfp", "video-no-hbp", "video-no-hsa",
+ "vsync-flush", "no-eot-packet", "clock-non-continuous", "lpm",
+};
+
+static void mipi_dsi_list(bool verbose)
+{
+ struct mipi_dsi_device *dsi;
+ struct device *dev;
+
+ bus_for_each_device(&mipi_dsi_bus_type, dev) {
+ const char *format = "unknown";
+
+ dsi = to_mipi_dsi_device(dev);
+ printf("%s%s", dev_name(dev),
+ dsi->attached ? "" : " (not attached)");
+
+ if (dsi->format < ARRAY_SIZE(mipi_dsi_formats))
+ format = mipi_dsi_formats[dsi->format];
+
+ if (verbose) {
+ printf(": host %s, channel %u, %u lanes, %s, flags",
+ dev_name(dsi->host->dev), dsi->channel,
+ dsi->lanes, format);
+
+ for (int i = 0; i < ARRAY_SIZE(mipi_dsi_mode_flags); i++) {
+ if (dsi->mode_flags & BIT(i))
+ printf(" %s", mipi_dsi_mode_flags[i]);
+ }
+ }
+
+ printf("\n");
+ }
+}
+
+struct mipi_dsi_opts {
+ bool write, info, generic;
+ int peripheral; /* 1: turn on, 0: shut down, -1: none */
+ unsigned int len; /* 0: the known length of the register or 1 */
+};
+
+static int mipi_dsi_execute(struct mipi_dsi_device *dsi,
+ const struct mipi_dsi_opts *opts,
+ int argc, char *argv[])
+{
+ u8 buf[MIPI_DSI_CMD_MAX_LEN];
+ unsigned int len;
+ int ret, i;
+
+ if (opts->peripheral >= 0) {
+ if (opts->write || opts->info || opts->generic || argc)
+ return COMMAND_ERROR_USAGE;
+ if (opts->peripheral)
+ return mipi_dsi_turn_on_peripheral(dsi);
+ return mipi_dsi_shutdown_peripheral(dsi);
+ }
+
+ if (opts->info) {
+ if (opts->write || opts->generic || argc)
+ return COMMAND_ERROR_USAGE;
+ return mipi_dsi_command_show_list(dsi, dcs_ids,
+ ARRAY_SIZE(dcs_ids));
+ }
+
+ if (!argc) {
+ if (opts->write || opts->generic)
+ return COMMAND_ERROR_USAGE;
+ return mipi_dsi_command_show_list(dsi, dcs_reads,
+ ARRAY_SIZE(dcs_reads));
+ }
+
+ if (argc > sizeof(buf)) {
+ printf("can only send up to %zu bytes at once\n", sizeof(buf));
+ return -EOVERFLOW;
+ }
+
+ for (i = 0; i < argc; i++) {
+ ret = kstrtou8(argv[i], 16, &buf[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (opts->write) {
+ if (opts->generic)
+ ret = mipi_dsi_generic_write(dsi, buf, argc);
+ else
+ ret = mipi_dsi_dcs_write_buffer(dsi, buf, argc);
+ return ret < 0 ? ret : 0;
+ }
+
+ if (argc > (opts->generic ? 2 : 1))
+ return COMMAND_ERROR_USAGE;
+
+ len = opts->len;
+ if (!len)
+ len = opts->generic ? 1 : dcs_read_len(buf[0]);
+
+ return mipi_dsi_command_show(dsi, opts->generic, buf, argc, len,
+ false);
+}
+
+static int do_mipi_dsi(int argc, char *argv[])
+{
+ struct mipi_dsi_opts opts = { .peripheral = -1 };
+ struct mipi_dsi_device *dsi;
+ struct device *dev;
+ bool list = false, verbose = false, lpm = false, hs = false;
+ unsigned long mode_flags;
+ unsigned int channel, vc = UINT_MAX;
+ int opt, ret;
+
+ dev = list_first_entry_or_null(&mipi_dsi_bus_type.device_list,
+ struct device, bus_list);
+
+ while ((opt = getopt(argc, argv, "c:d:gHilLn:p:vw")) > 0) {
+ switch (opt) {
+ case 'c':
+ ret = kstrtouint(optarg, 0, &vc);
+ if (ret)
+ return ret;
+ if (vc > 3)
+ return -EINVAL;
+ break;
+ case 'd':
+ dev = bus_find_device(&mipi_dsi_bus_type, NULL,
+ optarg, device_match_name);
+ break;
+ case 'g':
+ opts.generic = true;
+ break;
+ case 'H':
+ hs = true;
+ break;
+ case 'i':
+ opts.info = true;
+ break;
+ case 'l':
+ list = true;
+ break;
+ case 'L':
+ lpm = true;
+ break;
+ case 'n':
+ ret = kstrtouint(optarg, 0, &opts.len);
+ if (ret)
+ return ret;
+ if (!opts.len || opts.len > MIPI_DSI_CMD_MAX_LEN)
+ return -EINVAL;
+ break;
+ case 'p':
+ if (!strcmp(optarg, "on"))
+ opts.peripheral = 1;
+ else if (!strcmp(optarg, "off"))
+ opts.peripheral = 0;
+ else
+ return COMMAND_ERROR_USAGE;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'w':
+ opts.write = true;
+ break;
+ default:
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+
+ if (list || verbose) {
+ mipi_dsi_list(verbose);
+ return 0;
+ }
+
+ if (lpm && hs)
+ return COMMAND_ERROR_USAGE;
+
+ if (!dev)
+ return -ENODEV;
+
+ dsi = to_mipi_dsi_device(dev);
+ if (!dsi->attached) {
+ printf("%s: not attached to its DSI host\n", dev_name(dev));
+ return -ENODEV;
+ }
+
+ mode_flags = dsi->mode_flags;
+ channel = dsi->channel;
+ if (lpm)
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+ if (hs)
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+ if (vc != UINT_MAX)
+ dsi->channel = vc;
+
+ ret = mipi_dsi_execute(dsi, &opts, argc - optind, argv + optind);
+
+ dsi->mode_flags = mode_flags;
+ dsi->channel = channel;
+
+ if (ret == -ENETDOWN)
+ printf("DSI host is powered down, enable the display first\n");
+
+ return ret;
+}
+
+BAREBOX_CMD_HELP_START(mipi_dsi)
+BAREBOX_CMD_HELP_TEXT("Without CMD, dump the standard DCS registers.")
+BAREBOX_CMD_HELP_TEXT("CMD and DATA bytes are given in hex.")
+BAREBOX_CMD_HELP_TEXT("")
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT ("-l\t", "list all MIPI DSI devices")
+BAREBOX_CMD_HELP_OPT ("-v\t", "list with host, channel, lanes, format and mode flags")
+BAREBOX_CMD_HELP_OPT ("-d DEVICE", "select specific device (default is first registered)")
+BAREBOX_CMD_HELP_OPT ("-i\t", "read the panel identification registers")
+BAREBOX_CMD_HELP_OPT ("-n LEN\t", "number of bytes to read (default: known length, else 1)")
+BAREBOX_CMD_HELP_OPT ("-w\t", "issue write command")
+BAREBOX_CMD_HELP_OPT ("-g\t", "send generic packets: CMD and DATA are the payload,")
+BAREBOX_CMD_HELP_OPT ("\t", "reads take up to two bytes")
+BAREBOX_CMD_HELP_OPT ("-p on|off", "send Turn On or Shutdown Peripheral")
+BAREBOX_CMD_HELP_OPT ("-c CH\t", "use virtual channel CH (0-3)")
+BAREBOX_CMD_HELP_OPT ("-L\t", "send in low power mode")
+BAREBOX_CMD_HELP_OPT ("-H\t", "send in high speed mode")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(mipi_dsi)
+ .cmd = do_mipi_dsi,
+ BAREBOX_CMD_DESC("interact with MIPI DSI device")
+ BAREBOX_CMD_OPTS("[-lvdinwgLH] [-c CH] [-p on|off] [CMD] [DATA...]")
+ BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
+ BAREBOX_CMD_HELP(cmd_mipi_dsi_help)
+BAREBOX_CMD_END
diff --git a/test/py/test_mipi_dsi.py b/test/py/test_mipi_dsi.py
new file mode 100644
index 000000000000..b66cf256de3a
--- /dev/null
+++ b/test/py/test_mipi_dsi.py
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+import pytest
+from .helper import skip_disabled
+
+DISPLAY_ON = 1 << 2
+
+
+@pytest.fixture(autouse=True)
+def mipi_dsi(barebox, barebox_config):
+ skip_disabled(barebox_config, "CONFIG_CMD_MIPI_DSI")
+
+ devices = barebox.run_check("mipi_dsi -l")
+ if not any(d and "not attached" not in d for d in devices):
+ pytest.skip("no attached MIPI DSI device")
+
+ _, _, ret = barebox.run("test -e /dev/fb0")
+ if ret != 0:
+ pytest.skip("no framebuffer to enable the DSI link")
+
+ barebox.run_check("fb0.enable=1")
+ yield
+ barebox.run_check("fb0.enable=1")
+
+
+def power_mode(barebox):
+ out = barebox.run_check("mipi_dsi 0a")
+ return int(out[-1], 16)
+
+
+def test_mipi_dsi_identify(barebox):
+ out = barebox.run_check("mipi_dsi -i")
+ assert any(line.startswith("04: ") and len(line) == 10 for line in out)
+
+
+def test_mipi_dsi_read_len(barebox):
+ assert len(barebox.run_check("mipi_dsi 04")[-1]) == 6
+ assert len(barebox.run_check("mipi_dsi -n 1 04")[-1]) == 2
+
+
+def test_mipi_dsi_dump(barebox):
+ out = barebox.run_check("mipi_dsi")
+ assert any(line.startswith("0a: ") for line in out)
+
+
+def test_mipi_dsi_list_verbose(barebox):
+ out = barebox.run_check("mipi_dsi -l -v")
+ assert any(" lanes, " in line for line in out)
+
+
+@pytest.mark.parametrize("opts", ["", "-g", "-L", "-H"])
+def test_mipi_dsi_display_off_on(barebox, opts):
+ assert power_mode(barebox) & DISPLAY_ON
+
+ barebox.run_check(f"mipi_dsi {opts} -w 28")
+ assert not power_mode(barebox) & DISPLAY_ON
+
+ barebox.run_check(f"mipi_dsi {opts} -w 29")
+ assert power_mode(barebox) & DISPLAY_ON
+
+
+def test_mipi_dsi_channel(barebox):
+ barebox.run_check("mipi_dsi -c 0 0a")
+
+ _, _, ret = barebox.run("mipi_dsi -c 1 0a")
+ assert ret != 0
+
+
+def test_mipi_dsi_peripheral(barebox):
+ barebox.run_check("mipi_dsi -p on")
+
+
+def test_mipi_dsi_powered_down(barebox):
+ barebox.run_check("fb0.enable=0")
+
+ out, _, ret = barebox.run("mipi_dsi -w 29")
+ assert ret != 0
+ assert "DSI host is powered down" in "\n".join(out)
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 4/4] commands: mipi_dsi: add command for easier DCS experimentation
2026-11-08 10:10 ` [PATCH 4/4] commands: mipi_dsi: add command for easier DCS experimentation Ahmad Fatoum
@ 2026-11-09 9:24 ` Sascha Hauer
0 siblings, 0 replies; 5+ messages in thread
From: Sascha Hauer @ 2026-11-09 9:24 UTC (permalink / raw)
To: Ahmad Fatoum; +Cc: barebox, Ahmad Fatoum
On 2026-11-08 11:10, Ahmad Fatoum wrote:
> +static int do_mipi_dsi(int argc, char *argv[])
> +{
> + struct mipi_dsi_opts opts = { .peripheral = -1 };
> + struct mipi_dsi_device *dsi;
> + struct device *dev;
> + bool list = false, verbose = false, lpm = false, hs = false;
> + unsigned long mode_flags;
> + unsigned int channel, vc = UINT_MAX;
> + int opt, ret;
> +
> + dev = list_first_entry_or_null(&mipi_dsi_bus_type.device_list,
> + struct device, bus_list);
> +
> + while ((opt = getopt(argc, argv, "c:d:gHilLn:p:vw")) > 0) {
> + switch (opt) {
> + case 'c':
> + ret = kstrtouint(optarg, 0, &vc);
> + if (ret)
> + return ret;
> + if (vc > 3)
> + return -EINVAL;
> + break;
> + case 'd':
> + dev = bus_find_device(&mipi_dsi_bus_type, NULL,
> + optarg, device_match_name);
> + break;
> + case 'g':
> + opts.generic = true;
> + break;
> + case 'H':
> + hs = true;
> + break;
> + case 'i':
> + opts.info = true;
> + break;
> + case 'l':
> + list = true;
> + break;
> + case 'L':
> + lpm = true;
> + break;
> + case 'n':
> + ret = kstrtouint(optarg, 0, &opts.len);
> + if (ret)
> + return ret;
> + if (!opts.len || opts.len > MIPI_DSI_CMD_MAX_LEN)
> + return -EINVAL;
> + break;
> + case 'p':
> + if (!strcmp(optarg, "on"))
> + opts.peripheral = 1;
> + else if (!strcmp(optarg, "off"))
> + opts.peripheral = 0;
> + else
> + return COMMAND_ERROR_USAGE;
> + break;
> + case 'v':
> + verbose = true;
> + break;
> + case 'w':
> + opts.write = true;
> + break;
> + default:
> + return COMMAND_ERROR_USAGE;
> + }
> + }
> +
> + if (list || verbose) {
> + mipi_dsi_list(verbose);
> + return 0;
> + }
-v intuitively reads as "verbose", at the -v option even results in a
variable named verbose. I think it would be clearer to use -v as "Do
what you ordered to, with verbose output", rather then "list devices
verbosely"
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-09 9:26 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-08 10:10 [PATCH 1/4] video: dw_mipi_dsi: disable the panel before powering down the host Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 2/4] video: dw_mipi_dsi: refuse transfers while the host is powered down Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 3/4] video: dw_mipi_dsi: give transfers in video mode a frame to complete Ahmad Fatoum
2026-11-08 10:10 ` [PATCH 4/4] commands: mipi_dsi: add command for easier DCS experimentation Ahmad Fatoum
2026-11-09 9:24 ` Sascha Hauer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox