* [PATCH 0/2] drm: panel: Add Generic MIPI Panel Driver
@ 2025-01-31 6:43 Hironori KIKUCHI
2025-01-31 6:43 ` [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels Hironori KIKUCHI
2025-01-31 6:43 ` [PATCH 2/2] drm: panel: Add a driver for Generic MIPI-DSI/DPI(+SPI) panels Hironori KIKUCHI
0 siblings, 2 replies; 5+ messages in thread
From: Hironori KIKUCHI @ 2025-01-31 6:43 UTC (permalink / raw)
To: linux-kernel
Cc: Hironori KIKUCHI, Neil Armstrong, Jessica Zhang, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, dri-devel, devicetree
This is a Generic MIPI-DSI / DPI(+SPI) dual stack panel driver.
Although the MIPI specifications define how to communicate with a panel
to display an image, some panels still require a panel-specific
initialization sequence to be sent.
While there are many dedicated drivers for such panels, they are very
similar to each other, except for the configuration parameters, including
the initialization sequence for each panel.
Since there are numerous panels in the wild, adding parameters to
the driver for each panel every time is a pain.
Instead, this driver offers more generic and convenient method.
Its fundamental approach is similar to the `panel-mipi-dbi` driver,
which sends an initialization sequence stored in a firmware file.
Moreover, this driver allows display modes, timings, and panel
configuration parameters to be stored in the same file or in DT.
I've also come up with a firmware generator:
https://kikuchan.github.io/panel-firmware-generator/
It helps to create and configure the panel parameters on the web.
As a reference, similar generic drivers currently exist:
* drivers/gpu/drm/panel/panel-simple.c
- Supports wide variety of panels, but no init-sequence support
* drivers/gpu/drm/tiny/panel-mipi-dbi.c
- Supports init-sequence, but only supports DBI panels
* drivers/gpu/drm/panel/panel-dsi-cm.c
- Supports DSI Command Mode, but no init-sequence support
Regards,
kikuchan.
Hironori KIKUCHI (2):
dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels
drm: panel: Add a driver for Generic MIPI-DSI/DPI(+SPI) panels
.../bindings/display/panel/panel-mipi.yaml | 244 +++
drivers/gpu/drm/panel/Kconfig | 10 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-mipi.c | 1355 +++++++++++++++++
4 files changed, 1610 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
create mode 100644 drivers/gpu/drm/panel/panel-mipi.c
--
2.48.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels
2025-01-31 6:43 [PATCH 0/2] drm: panel: Add Generic MIPI Panel Driver Hironori KIKUCHI
@ 2025-01-31 6:43 ` Hironori KIKUCHI
2025-01-31 7:47 ` Krzysztof Kozlowski
2025-01-31 6:43 ` [PATCH 2/2] drm: panel: Add a driver for Generic MIPI-DSI/DPI(+SPI) panels Hironori KIKUCHI
1 sibling, 1 reply; 5+ messages in thread
From: Hironori KIKUCHI @ 2025-01-31 6:43 UTC (permalink / raw)
To: linux-kernel
Cc: Hironori KIKUCHI, Neil Armstrong, Jessica Zhang, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, dri-devel, devicetree
This is a binding for generic MIPI-DSI/DPI panels that require
initialization with a simple command sequence before use.
Although the `panel-simple` binding exists for generic panels, it is not
suitable for panels that require initialization via their communication
bus, as such panels cannot be considered "simple".
While many dedicated bindings exist for such panels, the generic panels
are not complex enough to warrant a dedicated binding because they only
require a "simple" initialization before use.
This binding addresses that gap.
Signed-off-by: Hironori KIKUCHI <kikuchan98@gmail.com>
---
.../bindings/display/panel/panel-mipi.yaml | 244 ++++++++++++++++++
1 file changed, 244 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
new file mode 100644
index 00000000000..681614333d8
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
@@ -0,0 +1,244 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/panel-mipi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic MIPI-DSI/DPI Panels Requiring Initialization
+
+maintainers:
+ - Hironori KIKUCHI <kikuchan98@gmail.com>
+
+description:
+ This is a binding for generic MIPI-DSI/DPI panels that require
+ initialization with a simple command sequence before use.
+
+properties:
+ compatible:
+ oneOf:
+ - enum:
+ - panel-mipi-dsi
+ - panel-mipi-dpi-spi
+ - items:
+ - {}
+ - enum:
+ - panel-mipi-dsi
+ - panel-mipi-dpi-spi
+
+ reg:
+ description: DSI / SPI channel used by that screen
+ maxItems: 1
+
+ power-supply: true
+
+ io-supply:
+ description: I/O system regulator.
+ No need to set if this is the same as polwer-supply.
+
+ dc-gpios:
+ maxItems: 1
+ description: Controller data/command selection (D/CX) in 4-line SPI mode.
+ If not set, the controller is in 3-line SPI mode.
+ Disallowed for DSI.
+
+ width-mm: true
+ height-mm: true
+ panel-timing: true
+ display-timings: true
+
+ reset-delay:
+ description: Delay (in ms) after the reset command
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ init-delay:
+ description: Delay (in ms) for sending the initial command sequence
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ sleep-delay:
+ description: Delay (in ms) after the sleep command
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ backlight-delay:
+ description: Delay (in ms) for enabling the backlight
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ port: true
+ reset-gpios: true
+ rotation: true
+
+ backlight: true
+
+ init-sequence:
+ description: Encoded initial command sequence.
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+
+ firmware-name:
+ maxItems: 1
+
+ dsi-mode-video:
+ description: Enable MIPI_DSI_MODE_VIDEO.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-burst:
+ description: Enable MIPI_DSI_MODE_VIDEO_BURST.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-sync-pulse:
+ description: Enable MIPI_DSI_MODE_VIDEO_SYNC_PULSE.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-auto-vert:
+ description: Enable MIPI_DSI_MODE_VIDEO_AUTO_VERT.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-hse:
+ description: Enable MIPI_DSI_MODE_VIDEO_HSE.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-no-hfp:
+ description: Enable MIPI_DSI_MODE_VIDEO_NO_HFP.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-no-hbp:
+ description: Enable MIPI_DSI_MODE_VIDEO_NO_HBP.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-video-no-hsa:
+ description: Enable MIPI_DSI_MODE_VIDEO_NO_HSA.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-vsync-flush:
+ description: Enable MIPI_DSI_MODE_VSYNC_FLUSH.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-no-eot-packet:
+ description: Enable MIPI_DSI_MODE_NO_EOT_PACKET.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-clock-non-continuous:
+ description: Enable MIPI_DSI_CLOCK_NON_CONTINUOUS.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-mode-lpm:
+ description: Enable MIPI_DSI_MODE_LPM.
+ Disallowed for SPI.
+ type: boolean
+
+ dsi-hs-pkt-end-aligned:
+ description: Enable MIPI_DSI_HS_PKT_END_ALIGNED.
+ Disallowed for SPI.
+ type: boolean
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: panel-common.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - panel-mipi-dpi-spi
+ then:
+ # SPI mode
+ $ref: /schemas/spi/spi-peripheral-props.yaml#
+ properties:
+ dsi-mode-video: false
+ dsi-mode-video-burst: false
+ dsi-mode-video-sync-pulse: false
+ dsi-mode-video-auto-vert: false
+ dsi-mode-video-hse: false
+ dsi-mode-video-no-hfp: false
+ dsi-mode-video-no-hbp: false
+ dsi-mode-video-no-hsa: false
+ dsi-mode-vsync-flush: false
+ dsi-mode-no-eot-packet: false
+ dsi-clock-non-continuous: false
+ dsi-mode-lpm: false
+ dsi-hs-pkt-end-aligned: false
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - panel-mipi-dsi
+ then:
+ # DSI mode
+ properties:
+ dc-gpios: false
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ dsi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0 {
+ compatible = "vendor,some-panel", "panel-mipi-dsi";
+ reg = <0>;
+
+ port {
+ mipi_in_panel: endpoint {
+ remote-endpoint = <&mipi_out_panel>;
+ };
+ };
+ };
+ };
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0{
+ compatible = "panel-mipi-dpi-spi";
+ reg = <0>;
+
+ spi-max-frequency = <40000000>;
+
+ dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
+
+ backlight = <&backlight>;
+
+ init-sequence = [
+ 01 11
+ 00
+ 04 b1 01 2c 2d
+ 01 29
+ 80 00 00 00
+ ];
+
+ width-mm = <35>;
+ height-mm = <28>;
+
+ panel-timing {
+ hactive = <160>;
+ vactive = <128>;
+ hback-porch = <0>;
+ vback-porch = <0>;
+ clock-frequency = <0>;
+ hfront-porch = <0>;
+ hsync-len = <0>;
+ vfront-porch = <0>;
+ vsync-len = <0>;
+ };
+ };
+ };
--
2.48.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] drm: panel: Add a driver for Generic MIPI-DSI/DPI(+SPI) panels
2025-01-31 6:43 [PATCH 0/2] drm: panel: Add Generic MIPI Panel Driver Hironori KIKUCHI
2025-01-31 6:43 ` [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels Hironori KIKUCHI
@ 2025-01-31 6:43 ` Hironori KIKUCHI
1 sibling, 0 replies; 5+ messages in thread
From: Hironori KIKUCHI @ 2025-01-31 6:43 UTC (permalink / raw)
To: linux-kernel
Cc: Hironori KIKUCHI, Neil Armstrong, Jessica Zhang, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, dri-devel, devicetree
Although the MIPI specifications define how to communicate with a panel
to display an image, some panels still require a panel-specific
initialization sequence to be sent.
This is a driver for such generic MIPI-DSI/DPI panels that require
initialization with a simple command sequence before use.
Its fundamental approach is similar to `panel-mipi-dbi` driver,
which sends an initialization sequence stored in a firmware file.
Moreover, this driver allows display modes, timings, and panel
configuration parameters to be stored in the same file or in DT.
Signed-off-by: Hironori KIKUCHI <kikuchan98@gmail.com>
---
drivers/gpu/drm/panel/Kconfig | 10 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-mipi.c | 1355 ++++++++++++++++++++++++++++
3 files changed, 1366 insertions(+)
create mode 100644 drivers/gpu/drm/panel/panel-mipi.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d7469c565d1..46eea1974a0 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -408,6 +408,16 @@ config DRM_PANEL_MANTIX_MLAF057WE51
has a resolution of 720x1440 pixels, a built in backlight and touch
controller.
+config DRM_PANEL_MIPI
+ tristate "Generic MIPI-DSI/DPI(+SPI) panel"
+ depends on OF
+ depends on SPI || DRM_MIPI_DSI
+ select DRM_MIPI_DBI if SPI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for Generic MIPI-DSI /
+ MIPI-DPI(+SPI) panels.
+
config DRM_PANEL_NEC_NL8048HL11
tristate "NEC NL8048HL11 RGB panel"
depends on GPIOLIB && OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 7dcf72646ca..22276255a7b 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
+obj-$(CONFIG_DRM_PANEL_MIPI) += panel-mipi.o
obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3052C) += panel-newvision-nv3052c.o
diff --git a/drivers/gpu/drm/panel/panel-mipi.c b/drivers/gpu/drm/panel/panel-mipi.c
new file mode 100644
index 00000000000..bcbaa15b62a
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-mipi.c
@@ -0,0 +1,1355 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Generic MIPI-DSI/DPI(+SPI) Panel Driver
+ *
+ * Supported panels:
+ * - A generic MIPI-DSI panel which implements basic DCS
+ * - A generic MIPI-DPI panel which implements basic DCS over SPI
+ *
+ * Copyright (C) 2025, Hironori KIKUCHI <kikuchan98@gmail.com>
+ */
+
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <linux/backlight.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/display_timing.h>
+#include <video/mipi_display.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+/*
+ * The display panel configuration can be stored in a firmware file,
+ * under the firmware directory of the system.
+ *
+ * The name of the file is `panels/<firmware-name>.panel`, where the
+ * 'firmware-name' should be defined as the property of the device
+ * in the Device Tree, otherwise the first `compatible` string is used.
+ *
+ * File Layout:
+ * A binary file composed with the following data:
+ * <header>
+ * <config>
+ * <timings>
+ * <init-sequence>
+ *
+ * The 'header':
+ * A `struct panel_firmware_header`.
+ * The `file_format_version` must be `1`.
+ *
+ * The 'config':
+ * A `struct panel_firmware_config`.
+ * The values are in big-endian.
+ *
+ * The 'timings':
+ * An array of `struct panel_firmware_panel_timing`.
+ * Its length is `num_timings` in the config.
+ * The values are in big-endian.
+ *
+ * The 'init-sequence':
+ * MIPI commands to execute when the display pipeline is enabled.
+ * This is used to configure the display controller, and it may
+ * contains panel specific parameters as well.
+ *
+ * The commands are encoded and stored in a byte array:
+ *
+ * 0x00 : sleep 10 ms
+ * 0x01 <M> : MIPI command <M> with no arguments
+ * 0x02 <M> <a> : MIPI command <M> with 1 argument <a>
+ * 0x03 <M> <a> <b> : MIPI command <M> with 2 arguments <a> <b>
+ * :
+ * 0x7f <M> <...> : MIPI command <M> with 126 arguments <...>
+ * 0x80 : sleep 100 ms
+ * 0x81 - 0xff : reserved
+ *
+ * Example:
+ * command 0x11
+ * sleep 10ms
+ * command 0xb1 arguments 0x01 0x2c 0x2d
+ * command 0x29
+ * sleep 130ms
+ *
+ * Byte sequence:
+ * 0x01 0x11
+ * 0x00
+ * 0x04 0xb1 0x01 0x2c 0x2d
+ * 0x01 0x29
+ * 0x80 0x00 0x00 0x00
+ *
+ */
+static const u8 panel_firmware_magic[15] = {
+ 0x50, 0x41, 0x4e, 0x45, 0x4c, 0x2d, 0x46, 0x49,
+ 0x52, 0x4d, 0x57, 0x41, 0x52, 0x45, 0x00,
+};
+
+struct panel_firmware_header {
+ u8 magic[15];
+ u8 file_format_version; /* must be 1 */
+} __packed;
+
+struct panel_firmware_config {
+ u16 width_mm, height_mm;
+ u16 rotation;
+ u8 _reserved_1[2];
+ u8 _reserved_2[8];
+
+ u16 reset_delay; /* delay after the reset command, in ms */
+ u16 init_delay; /* delay for sending the initial command sequence, in ms */
+ u16 sleep_delay; /* delay after the sleep command, in ms */
+ u16 backlight_delay; /* delay for enabling the backlight, in ms */
+ u16 _reserved_3[4];
+
+ u16 dsi_lanes; /* unsigned int */
+ u16 dsi_format; /* enum mipi_dsi_pixel_format */
+ u32 dsi_mode_flags; /* unsigned long */
+ u32 bus_flags; /* struct drm_bus_flags */
+ u8 _reserved_4[2];
+ u8 preferred_timing;
+ u8 num_timings;
+} __packed;
+
+struct panel_firmware_panel_timing {
+ u16 hactive;
+ u16 hfp;
+ u16 hslen;
+ u16 hbp;
+
+ u16 vactive;
+ u16 vfp;
+ u16 vslen;
+ u16 vbp;
+
+ u32 dclk; /* in kHz */
+ u32 flags; /* include/drm/drm_modes.h DRM_MODE_FLAG_* */
+
+ u8 _reserved_1[8];
+} __packed;
+
+struct panel_commands {
+ const u8 *data;
+ size_t size;
+};
+
+struct panel_firmware {
+ const struct firmware *blob;
+ const struct panel_firmware_config *config;
+ const struct panel_firmware_panel_timing *timings;
+ struct panel_commands commands;
+};
+
+struct panel_mipi {
+ struct drm_panel panel;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dbi dbi;
+
+ struct mutex lock;
+
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset;
+
+ unsigned int reset_delay;
+ unsigned int init_delay;
+ unsigned int sleep_delay;
+ unsigned int backlight_delay;
+
+ unsigned int dsi_lanes;
+ enum mipi_dsi_pixel_format dsi_format;
+
+ /* Panel info */
+ u32 width_mm, height_mm;
+ u32 bus_format, bus_flags;
+ enum drm_panel_orientation orientation;
+
+ struct list_head mode_list;
+
+ struct panel_firmware *firmware;
+ struct panel_commands commands;
+
+ int (*write_command)(struct panel_mipi *mipi, const u8 *buf,
+ size_t len);
+ int (*read_command)(struct panel_mipi *mipi, u8 cmd, u8 *buf,
+ size_t len);
+
+ /* DMA-able buffer */
+ u8 initial_display_id[3];
+ u8 display_id[3];
+
+ /* debugfs */
+ struct dentry *debugfs;
+ u8 recvbuf[1024];
+ size_t recvlen;
+};
+
+static inline struct panel_mipi *to_panel_mipi(struct drm_panel *panel)
+{
+ return container_of(panel, struct panel_mipi, panel);
+}
+
+static int panel_mipi_dsi_write(struct panel_mipi *mipi, const u8 *buf,
+ size_t len)
+{
+ int ret;
+
+ ret = mipi_dsi_dcs_write_buffer(mipi->dsi, buf, len);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int panel_mipi_dbi_write(struct panel_mipi *mipi, const u8 *buf,
+ size_t len)
+{
+ return mipi_dbi_command_stackbuf(&mipi->dbi, buf[0], buf + 1, len - 1);
+}
+
+#define panel_mipi_write(mipi, ...) \
+ ({ \
+ u8 _buf[] = { __VA_ARGS__ }; \
+ mipi->write_command(mipi, _buf, ARRAY_SIZE(_buf)); \
+ })
+
+static int panel_mipi_dsi_read(struct panel_mipi *mipi, u8 cmd, u8 *buf,
+ size_t len)
+{
+ return mipi_dsi_dcs_read(mipi->dsi, cmd, buf, len);
+}
+
+static int panel_mipi_dbi_read(struct panel_mipi *mipi, u8 cmd, u8 *buf,
+ size_t len)
+{
+ return mipi_dbi_command_buf(&mipi->dbi, cmd, buf, len);
+}
+
+#define panel_mipi_read(mipi, cmd, buf, len) \
+ (mipi)->read_command((mipi), (cmd), (buf), (len))
+
+static unsigned long panel_mipi_dsi_set_mode_flags(struct panel_mipi *mipi,
+ unsigned long new_flags)
+{
+ unsigned long old_flags;
+
+ if (!mipi->dsi)
+ return 0;
+
+ old_flags = mipi->dsi->mode_flags;
+
+ mipi->dsi->mode_flags = new_flags;
+
+ return old_flags;
+}
+
+static bool panel_mipi_dsi_set_lpm(struct panel_mipi *mipi, bool set)
+{
+ unsigned long old_flags;
+
+ if (!mipi->dsi)
+ return false;
+
+ old_flags = panel_mipi_dsi_set_mode_flags(
+ mipi, set ? mipi->dsi->mode_flags | MIPI_DSI_MODE_LPM :
+ mipi->dsi->mode_flags & ~MIPI_DSI_MODE_LPM);
+
+ return old_flags & MIPI_DSI_MODE_LPM ? true : false;
+}
+
+#define PANEL_MIPI_TRANSACTION(mipi, stmt) \
+ do { \
+ mutex_lock(&(mipi)->lock); \
+ do { \
+ stmt; \
+ } while (0); \
+ mutex_unlock(&(mipi)->lock); \
+ } while (0)
+
+#define PANEL_MIPI_TRANSACTION_LPM(mipi, lpm, stmt) \
+ PANEL_MIPI_TRANSACTION( \
+ mipi, \
+ bool _lpm_backup = panel_mipi_dsi_set_lpm((mipi), (lpm)); \
+ do { stmt; } while (0); \
+ panel_mipi_dsi_set_lpm((mipi), _lpm_backup))
+
+static int panel_mipi_validate_commands(struct panel_mipi *mipi, const u8 *data,
+ size_t size, size_t *valid_len)
+{
+ size_t i = 0;
+
+ /* sanity check */
+ while (i < size) {
+ u8 inst = data[i];
+ bool ext = (inst & 0x80) ? true : false;
+ u8 len = inst & 0x7f;
+
+ if (ext && len > 0)
+ return -EINVAL; /* reserved */
+
+ if (i + 1 + len > size)
+ break;
+
+ i += 1 + len;
+ }
+
+ *valid_len = i;
+
+ return 0;
+}
+
+static int panel_mipi_load_commands(struct panel_mipi *mipi, const u8 *data,
+ size_t size)
+{
+ int err;
+ size_t valid_len;
+
+ err = panel_mipi_validate_commands(mipi, data, size, &valid_len);
+ if (err)
+ return err;
+
+ if (valid_len != size)
+ return -EINVAL;
+
+ mipi->commands.data = data;
+ mipi->commands.size = size;
+
+ return 0;
+}
+
+static int panel_mipi_write_commands(struct panel_mipi *mipi, const u8 *data,
+ size_t size)
+{
+ const u8 *read_commands;
+ unsigned int write_memory_bpw;
+ size_t i = 0;
+ int err = 0;
+
+ if (!data)
+ return 0;
+
+ /*
+ * Disable interpretation for special commands for DBI
+ *
+ * This is required because the vendor specific custom commands
+ * may change its command set to the other.
+ */
+ read_commands = mipi->dbi.read_commands;
+ write_memory_bpw = mipi->dbi.write_memory_bpw;
+ mipi->dbi.read_commands = NULL;
+ mipi->dbi.write_memory_bpw = 8;
+
+ while (i < size) {
+ u8 inst = data[i++];
+ bool ext = (inst & 0x80) ? true : false;
+ u8 len = inst & 0x7f;
+
+ if (len == 0x00) {
+ msleep(ext ? 100 : 10);
+ continue;
+ }
+
+ err = mipi->write_command(mipi, data + i, len);
+
+ if (err)
+ break;
+
+ i += len;
+ }
+
+ /* restore */
+ mipi->dbi.read_commands = read_commands;
+ mipi->dbi.write_memory_bpw = write_memory_bpw;
+
+ return err;
+}
+
+static int panel_mipi_read_firmware(const struct device *dev,
+ struct panel_mipi *mipi,
+ const struct panel_firmware *firmware)
+{
+ int rotation;
+ int err;
+
+ if (!firmware)
+ return 0;
+
+ err = panel_mipi_load_commands(mipi, firmware->commands.data,
+ firmware->commands.size);
+ if (err) {
+ dev_err(dev, "firmware: Malformed command sequence\n");
+ return err;
+ }
+
+ mipi->width_mm = be16_to_cpu(firmware->config->width_mm);
+ mipi->height_mm = be16_to_cpu(firmware->config->height_mm);
+
+ rotation = be16_to_cpu(firmware->config->rotation);
+ if (rotation == 0)
+ mipi->orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL;
+ else if (rotation == 90)
+ mipi->orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
+ else if (rotation == 180)
+ mipi->orientation = DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP;
+ else if (rotation == 270)
+ mipi->orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP;
+ else {
+ dev_err(dev, "firmware: Invalid rotation %u\n", rotation);
+ return -EINVAL;
+ }
+
+ if (firmware->config->reset_delay)
+ mipi->reset_delay = be16_to_cpu(firmware->config->reset_delay);
+ if (firmware->config->init_delay)
+ mipi->init_delay = be16_to_cpu(firmware->config->init_delay);
+ if (firmware->config->sleep_delay)
+ mipi->sleep_delay = be16_to_cpu(firmware->config->sleep_delay);
+ if (firmware->config->backlight_delay)
+ mipi->backlight_delay =
+ be16_to_cpu(firmware->config->backlight_delay);
+
+ if (firmware->config->bus_flags)
+ mipi->bus_flags = be32_to_cpu(firmware->config->bus_flags);
+
+ return 0;
+}
+
+static struct panel_firmware *panel_mipi_load_firmware(struct device *dev)
+{
+ const struct firmware *blob;
+ const struct panel_firmware_header *hdr;
+ const struct panel_firmware_config *config;
+ const struct panel_firmware_panel_timing *timings;
+ struct panel_firmware *firmware;
+ const char *firmware_name;
+ size_t command_offset;
+ char filename[128];
+ int err;
+
+ err = of_property_read_string_index(dev->of_node, "compatible", 0,
+ &firmware_name);
+ if (err)
+ return NULL;
+
+ /* Use firmware-name instead if any */
+ of_property_read_string(dev->of_node, "firmware-name", &firmware_name);
+
+ snprintf(filename, sizeof(filename), "panels/%s.panel", firmware_name);
+ err = firmware_request_nowarn(&blob, filename, dev);
+ if (err)
+ return NULL;
+
+ hdr = (const struct panel_firmware_header *)blob->data;
+
+ if (blob->size < sizeof(*hdr)) {
+ dev_err(dev, "firmware: %s: file size %zu is too small\n",
+ filename, blob->size);
+ err = -EINVAL;
+ goto err;
+ }
+
+ if (memcmp(hdr->magic, panel_firmware_magic, sizeof(hdr->magic))) {
+ dev_err(dev, "firmware: %s: Bad magic %15ph\n", filename,
+ hdr->magic);
+ err = -EINVAL;
+ goto err;
+ }
+
+ if (hdr->file_format_version != 1) {
+ dev_err(dev, "firmware: %s: version %u is not supported\n",
+ filename, hdr->file_format_version);
+ err = -EINVAL;
+ goto err;
+ }
+
+ config = (struct panel_firmware_config *)(blob->data + sizeof(*hdr));
+ timings = (struct panel_firmware_panel_timing *)(blob->data +
+ sizeof(*hdr) +
+ sizeof(*config));
+ command_offset = sizeof(*hdr) + sizeof(*config) +
+ config->num_timings * sizeof(*timings);
+ if (blob->size < command_offset) {
+ dev_err(dev, "firmware: %s: file size %zu is too small\n",
+ filename, blob->size);
+ err = -EINVAL;
+ goto err;
+ }
+
+ firmware = devm_kzalloc(dev, sizeof(*firmware), GFP_KERNEL);
+ if (!firmware) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ firmware->blob = blob;
+ firmware->config = config;
+ firmware->timings = timings;
+ firmware->commands.data = blob->data + command_offset;
+ firmware->commands.size = blob->size - command_offset;
+
+ dev_info(dev, "firmware: %s: loaded successfully\n", filename);
+
+ return firmware;
+
+err:
+ release_firmware(blob);
+
+ return ERR_PTR(err);
+}
+
+static int panel_mipi_read_display_id(struct panel_mipi *mipi, u8 *dest)
+{
+ return panel_mipi_read(mipi, MIPI_DCS_GET_DISPLAY_ID, dest, 3);
+}
+
+static int panel_mipi_enable(struct drm_panel *panel)
+{
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+ int err;
+
+ PANEL_MIPI_TRANSACTION(mipi, {
+ err = panel_mipi_write(mipi, MIPI_DCS_SET_DISPLAY_ON);
+ });
+ if (err)
+ return err;
+
+ if (panel->backlight) {
+ /* Wait for the picture to be ready before enabling backlight */
+ msleep(mipi->backlight_delay);
+ }
+
+ return 0;
+}
+
+static int panel_mipi_disable(struct drm_panel *panel)
+{
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+ int err = 0;
+
+ PANEL_MIPI_TRANSACTION(mipi, {
+ err = panel_mipi_write(mipi, MIPI_DCS_SET_DISPLAY_OFF);
+ });
+
+ return err;
+}
+
+static int panel_mipi_prepare(struct drm_panel *panel)
+{
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+ int err;
+
+ err = regulator_bulk_enable(ARRAY_SIZE(mipi->supplies), mipi->supplies);
+ if (err)
+ return err;
+
+ PANEL_MIPI_TRANSACTION(mipi, {
+ /* Reset the chip */
+ if (mipi->reset) {
+ gpiod_set_value_cansleep(mipi->reset, 1);
+ msleep(mipi->reset_delay);
+ gpiod_set_value_cansleep(mipi->reset, 0);
+ }
+
+ msleep(mipi->init_delay);
+
+ /* Read the Display ID */
+ if (!panel_mipi_read_display_id(mipi,
+ mipi->initial_display_id)) {
+ dev_info(panel->dev, "MIPI Display ID: %3phN\n",
+ mipi->initial_display_id);
+ }
+
+ /* Write the init-sequence */
+ err = panel_mipi_write_commands(mipi, mipi->commands.data,
+ mipi->commands.size);
+ if (err)
+ break;
+
+ /* Ensure to exit from a sleep mode */
+ err = panel_mipi_write(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
+ if (err)
+ break;
+ msleep(mipi->sleep_delay);
+
+ /*
+ * Read the Display ID again,
+ * because the init-sequence might have changed the ID.
+ */
+ if (!panel_mipi_read_display_id(mipi, mipi->display_id) &&
+ memcmp(mipi->initial_display_id, mipi->display_id,
+ sizeof(mipi->display_id))) {
+ dev_info(panel->dev,
+ "MIPI Display ID: %3phN (changed)\n",
+ mipi->display_id);
+ }
+ });
+
+ if (err) {
+ regulator_bulk_disable(ARRAY_SIZE(mipi->supplies),
+ mipi->supplies);
+ return err;
+ }
+
+ return 0;
+}
+
+static int panel_mipi_unprepare(struct drm_panel *panel)
+{
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+ int err;
+
+ PANEL_MIPI_TRANSACTION(mipi, {
+ err = panel_mipi_write(mipi, MIPI_DCS_ENTER_SLEEP_MODE);
+ if (err)
+ dev_warn(panel->dev, "Failed to enter a sleep mode\n");
+ msleep(mipi->sleep_delay);
+ });
+
+ if (mipi->reset) {
+ gpiod_set_value_cansleep(mipi->reset, 1);
+ msleep(mipi->reset_delay);
+ }
+
+ regulator_bulk_disable(ARRAY_SIZE(mipi->supplies), mipi->supplies);
+
+ return 0;
+}
+
+static int panel_mipi_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+ struct drm_display_mode *mode;
+ unsigned int count = 0;
+
+ list_for_each_entry(mode, &mipi->mode_list, head) {
+ struct drm_display_mode *dmode;
+
+ dmode = drm_mode_duplicate(connector->dev, mode);
+ if (!dmode)
+ return -EINVAL;
+
+ drm_mode_probed_add(connector, dmode);
+ count++;
+ }
+
+ connector->display_info.bpc = 8;
+ connector->display_info.width_mm = mipi->width_mm;
+ connector->display_info.height_mm = mipi->height_mm;
+ connector->display_info.bus_flags = mipi->bus_flags;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &mipi->bus_format, 1);
+
+ /*
+ * TODO: Remove once all drm drivers call
+ * drm_connector_set_orientation_from_panel()
+ */
+ drm_connector_set_panel_orientation(connector, mipi->orientation);
+
+ return count;
+}
+
+static enum drm_panel_orientation
+panel_mipi_get_orientation(struct drm_panel *panel)
+{
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+
+ return mipi->orientation;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int display_id_show(struct seq_file *s, void *data)
+{
+ struct drm_panel *panel = s->private;
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+ int err = 0;
+
+ PANEL_MIPI_TRANSACTION(mipi, {
+ err = panel_mipi_read_display_id(mipi, mipi->display_id);
+ if (err)
+ break;
+
+ seq_printf(s, "%3phN\n", mipi->display_id);
+ });
+
+ return err;
+}
+DEFINE_SHOW_ATTRIBUTE(display_id);
+
+static int initial_display_id_show(struct seq_file *s, void *data)
+{
+ struct drm_panel *panel = s->private;
+ struct panel_mipi *mipi = to_panel_mipi(panel);
+
+ seq_printf(s, "%3phN\n", mipi->initial_display_id);
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(initial_display_id);
+
+static int commands_open(struct inode *inode, struct file *file)
+{
+ struct panel_mipi *mipi = inode->i_private;
+
+ mipi->recvlen = 0;
+ file->private_data = mipi;
+
+ return 0;
+}
+
+static ssize_t commands_write(struct file *file, const char __user *data,
+ size_t size, loff_t *pos)
+{
+ struct panel_mipi *mipi = file->private_data;
+ ssize_t s = min_t(u64, sizeof(mipi->recvbuf) - mipi->recvlen, size);
+ size_t valid_len;
+ int err;
+
+ if (!s || !mipi)
+ return -EINVAL;
+
+ if (copy_from_user(mipi->recvbuf + mipi->recvlen, data, s))
+ return -EINVAL;
+
+ mipi->recvlen += s;
+
+ err = panel_mipi_validate_commands(mipi, mipi->recvbuf, mipi->recvlen,
+ &valid_len);
+ if (err)
+ return err;
+
+ if (valid_len > 0) {
+ PANEL_MIPI_TRANSACTION(mipi, {
+ err = panel_mipi_write_commands(mipi, mipi->recvbuf,
+ valid_len);
+ });
+ if (err)
+ return err;
+
+ mipi->recvlen -= valid_len;
+ if (mipi->recvlen)
+ memmove(mipi->recvbuf, mipi->recvbuf + valid_len,
+ mipi->recvlen);
+ }
+
+ return s;
+}
+
+static int commands_release(struct inode *inode, struct file *file)
+{
+ struct panel_mipi *mipi = inode->i_private;
+
+ if (mipi->recvlen) {
+ dev_err(mipi->panel.dev, "Premature end of commands\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static const struct file_operations commands_fops = {
+ .owner = THIS_MODULE,
+ .open = commands_open,
+ .write = commands_write,
+ .release = commands_release,
+};
+
+#endif
+
+static void panel_mipi_debugfs_init(struct device *dev, struct panel_mipi *mipi)
+{
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dir;
+
+ mipi->debugfs = debugfs_lookup("panel-mipi", NULL);
+ if (!mipi->debugfs)
+ mipi->debugfs = debugfs_create_dir("panel-mipi", NULL);
+
+ dir = debugfs_create_dir(dev_name(dev), mipi->debugfs);
+ debugfs_create_file("display-id", 0600, dir, mipi, &display_id_fops);
+ debugfs_create_file("initial-display-id", 0600, dir, mipi,
+ &initial_display_id_fops);
+ debugfs_create_file("commands", 0600, dir, mipi, &commands_fops);
+#endif
+}
+
+static void panel_mipi_debugfs_remove(struct panel_mipi *mipi)
+{
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove_recursive(mipi->debugfs);
+ mipi->debugfs = NULL;
+#endif
+}
+
+static const struct drm_panel_funcs panel_mipi_funcs = {
+ .prepare = panel_mipi_prepare,
+ .unprepare = panel_mipi_unprepare,
+ .enable = panel_mipi_enable,
+ .disable = panel_mipi_disable,
+ .get_modes = panel_mipi_get_modes,
+ .get_orientation = panel_mipi_get_orientation,
+};
+
+static int panel_mipi_add_mode(struct device *dev, struct panel_mipi *mipi,
+ const struct drm_display_mode *src_mode,
+ const char *source)
+{
+ struct drm_display_mode *mode;
+
+ mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
+ if (!mode)
+ return -ENOMEM;
+
+ drm_mode_copy(mode, src_mode);
+
+ if (!mode->clock)
+ mode->clock = mode->htotal * mode->vtotal * 60 / 1000;
+
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+
+ dev_info(dev, "Modeline " DRM_MODE_FMT " added by %s\n",
+ DRM_MODE_ARG(mode), source);
+
+ list_add_tail(&mode->head, &mipi->mode_list);
+
+ return 0;
+}
+
+static int panel_mipi_firmware_read_modes(const struct panel_firmware *firmware,
+ struct drm_display_mode *mode,
+ u32 *bus_flags, unsigned int i)
+{
+ const struct panel_firmware_panel_timing *timings =
+ &firmware->timings[i];
+
+ if (!firmware || i >= firmware->config->num_timings)
+ return -ENOENT;
+
+ if (!timings->hactive || !timings->vactive)
+ return -ENOENT;
+
+ memset(mode, 0, sizeof(*mode));
+
+ mode->clock = be32_to_cpu(timings->dclk);
+
+ mode->hdisplay = be16_to_cpu(timings->hactive);
+ mode->hsync_start = mode->hdisplay + be16_to_cpu(timings->hfp);
+ mode->hsync_end = mode->hsync_start + be16_to_cpu(timings->hslen);
+ mode->htotal = mode->hsync_end + be16_to_cpu(timings->hbp);
+
+ mode->vdisplay = be16_to_cpu(timings->vactive);
+ mode->vsync_start = mode->vdisplay + be16_to_cpu(timings->vfp);
+ mode->vsync_end = mode->vsync_start + be16_to_cpu(timings->vslen);
+ mode->vtotal = mode->vsync_end + be16_to_cpu(timings->vbp);
+
+ mode->flags = be32_to_cpu(timings->flags);
+
+ drm_mode_set_name(mode);
+
+ if (bus_flags && firmware->config->bus_flags)
+ *bus_flags = be32_to_cpu(firmware->config->bus_flags);
+
+ return 0;
+}
+
+static void drm_display_mode_from_timing(const struct display_timing *timing,
+ struct drm_display_mode *dmode,
+ u32 *bus_flags)
+{
+ struct videomode vm;
+
+ videomode_from_timing(timing, &vm);
+
+ memset(dmode, 0, sizeof(*dmode));
+ drm_display_mode_from_videomode(&vm, dmode);
+
+ if (bus_flags)
+ drm_bus_flags_from_videomode(&vm, bus_flags);
+}
+
+static int panel_mipi_read_of_display_timings(struct device *dev,
+ struct panel_mipi *mipi)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *timings_np;
+ struct display_timings *timings;
+ struct drm_display_mode mode = {};
+ u32 bus_flags;
+ unsigned int i;
+ int err;
+
+ /* To avoid the not found error message */
+ timings_np = of_get_child_by_name(np, "display-timings");
+ if (!timings_np)
+ return 0;
+ of_node_put(timings_np);
+
+ /* Then, load timings as usual */
+ timings = of_get_display_timings(np);
+ if (!timings)
+ return dev_err_probe(dev, -EINVAL,
+ "%pOF: Failed to get 'display-timings'\n",
+ dev->of_node);
+
+ for (i = 0; i < timings->num_timings; i++) {
+ drm_display_mode_from_timing(timings->timings[i], &mode,
+ &bus_flags);
+
+ if (timings->native_mode == i)
+ mode.type |= DRM_MODE_TYPE_PREFERRED;
+
+ err = panel_mipi_add_mode(dev, mipi, &mode, "display-timings");
+ if (err)
+ return err;
+ if (bus_flags)
+ mipi->bus_flags = bus_flags;
+ }
+
+ display_timings_release(timings);
+
+ return 0;
+}
+
+static int panel_mipi_probe_modes(struct device *dev, struct panel_mipi *mipi)
+{
+ unsigned int i = 0;
+ u32 bus_flags = 0;
+ struct drm_display_mode mode = {};
+ struct display_timing timing = {};
+ int err;
+
+ INIT_LIST_HEAD(&mipi->mode_list);
+
+ /* Read firmware first */
+ for (i = 0; mipi->firmware && i < mipi->firmware->config->num_timings;
+ i++) {
+ if (!panel_mipi_firmware_read_modes(mipi->firmware, &mode,
+ &bus_flags, i)) {
+ if (mipi->firmware->config->preferred_timing == i)
+ mode.type |= DRM_MODE_TYPE_PREFERRED;
+
+ err = panel_mipi_add_mode(dev, mipi, &mode, "firmware");
+ if (err)
+ return err;
+ if (bus_flags)
+ mipi->bus_flags = bus_flags;
+ }
+ }
+
+ /* Read `display-timings` and append if any */
+ err = panel_mipi_read_of_display_timings(dev, mipi);
+ if (err)
+ return err;
+
+ /* Read `panel-timing` and append if any */
+ err = of_get_display_timing(dev->of_node, "panel-timing", &timing);
+ if (err && err != -ENOENT)
+ return err;
+ else if (!err) {
+ drm_display_mode_from_timing(&timing, &mode, &bus_flags);
+ err = panel_mipi_add_mode(dev, mipi, &mode, "panel-timing");
+ if (err)
+ return err;
+ if (bus_flags)
+ mipi->bus_flags = bus_flags;
+ }
+
+ if (list_empty(&mipi->mode_list))
+ return dev_err_probe(dev, -EINVAL, "No modes defined\n");
+
+ return 0;
+}
+
+static void panel_mipi_cleanup(void *data)
+{
+ struct panel_mipi *mipi = (struct panel_mipi *)data;
+
+ panel_mipi_debugfs_remove(mipi);
+
+ drm_panel_remove(&mipi->panel);
+ drm_panel_disable(&mipi->panel);
+ drm_panel_unprepare(&mipi->panel);
+
+ if (mipi->firmware)
+ release_firmware(mipi->firmware->blob);
+}
+
+static int
+panel_mipi_backlight_update_status(struct backlight_device *backlight)
+{
+ struct panel_mipi *mipi = dev_get_drvdata(&backlight->dev);
+ int level = backlight_get_brightness(backlight);
+ int err;
+
+ PANEL_MIPI_TRANSACTION_LPM(mipi, false, {
+ err = panel_mipi_write(mipi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ level, level);
+ });
+
+ return err;
+}
+
+static int
+panel_mipi_backlight_get_brightness(struct backlight_device *backlight)
+{
+ return backlight_get_brightness(backlight);
+}
+
+static const struct backlight_ops panel_mipi_backlight_ops = {
+ .get_brightness = panel_mipi_backlight_get_brightness,
+ .update_status = panel_mipi_backlight_update_status,
+};
+
+static int panel_mipi_set_backlight(struct drm_panel *panel, struct device *dev,
+ struct panel_mipi *mipi)
+{
+ int err;
+
+ err = drm_panel_of_backlight(panel);
+ if (err)
+ return err;
+
+ if (!panel->backlight) {
+ struct backlight_device *backlight = NULL;
+ struct backlight_properties props = {
+ .max_brightness = 255,
+ .type = BACKLIGHT_RAW,
+ };
+
+ backlight = devm_backlight_device_register(
+ dev, dev_name(dev), dev, mipi,
+ &panel_mipi_backlight_ops, &props);
+ if (IS_ERR(backlight))
+ return PTR_ERR(backlight);
+
+ panel->backlight = backlight;
+ }
+
+ return 0;
+}
+
+static int panel_mipi_probe(struct device *dev, int connector_type)
+{
+ struct panel_mipi *mipi;
+ int err;
+
+ mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
+ if (!mipi)
+ return -ENOMEM;
+
+ mutex_init(&mipi->lock);
+
+ /* Get `power-supply` and `io-supply` (if any) */
+ mipi->supplies[0].supply = "power";
+ mipi->supplies[1].supply = "io";
+ err = devm_regulator_bulk_get(dev, ARRAY_SIZE(mipi->supplies),
+ mipi->supplies);
+ if (err < 0) {
+ return dev_err_probe(dev, err,
+ "%pOF: Failed to get regulators\n",
+ dev->of_node);
+ }
+
+ /* GPIO for /RESET */
+ mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(mipi->reset))
+ return dev_err_probe(dev, PTR_ERR(mipi->reset),
+ "%pOF: Failed to get GPIO for RESET\n",
+ dev->of_node);
+
+ /* Default values */
+ mipi->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ mipi->reset_delay = 1;
+ mipi->init_delay = 10;
+ mipi->sleep_delay = 120;
+ mipi->backlight_delay = 120;
+
+ /* Load from the firmware */
+ mipi->firmware = panel_mipi_load_firmware(dev);
+ if (IS_ERR(mipi->firmware))
+ return dev_err_probe(dev, PTR_ERR(mipi->firmware),
+ "Failed to load a firmware\n");
+ err = panel_mipi_read_firmware(dev, mipi, mipi->firmware);
+ if (err)
+ return err;
+
+ /* Load panel modes */
+ err = panel_mipi_probe_modes(dev, mipi);
+ if (err)
+ return err;
+
+ /* Load overrides from the DT */
+ const struct property *prop =
+ of_find_property(dev->of_node, "init-sequence", NULL);
+ if (prop && prop->value && prop->length > 0) {
+ err = panel_mipi_load_commands(mipi, prop->value, prop->length);
+ if (err)
+ return dev_err_probe(
+ dev, err, "%pOF: Malformed command sequence\n",
+ dev->of_node);
+ }
+ of_property_read_u32(dev->of_node, "width-mm", &mipi->width_mm);
+ of_property_read_u32(dev->of_node, "height-mm", &mipi->height_mm);
+ of_property_read_u32(dev->of_node, "reset-delay", &mipi->reset_delay);
+ of_property_read_u32(dev->of_node, "init-delay", &mipi->init_delay);
+ of_property_read_u32(dev->of_node, "sleep-delay", &mipi->sleep_delay);
+ of_property_read_u32(dev->of_node, "backlight-delay",
+ &mipi->backlight_delay);
+ if (of_find_property(dev->of_node, "rotation", NULL)) {
+ err = of_drm_get_panel_orientation(dev->of_node,
+ &mipi->orientation);
+ if (err)
+ return dev_err_probe(dev, err,
+ "%pOF: Invalid rotation\n",
+ dev->of_node);
+ }
+
+ /* DRM panel setup */
+ drm_panel_init(&mipi->panel, dev, &panel_mipi_funcs, connector_type);
+
+ err = panel_mipi_set_backlight(&mipi->panel, dev, mipi);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to set backlight\n");
+
+ drm_panel_add(&mipi->panel);
+
+ dev_set_drvdata(dev, mipi);
+
+ panel_mipi_debugfs_init(dev, mipi);
+
+ return devm_add_action_or_reset(dev, panel_mipi_cleanup, mipi);
+}
+
+static unsigned long panel_mipi_read_dsi_mode_props(struct device_node *np)
+{
+ unsigned long mode = 0;
+
+ mode |= of_property_read_bool(np, "dsi-mode-video") ?
+ MIPI_DSI_MODE_VIDEO :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-burst") ?
+ MIPI_DSI_MODE_VIDEO_BURST :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-sync-pulse") ?
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-auto-vert") ?
+ MIPI_DSI_MODE_VIDEO_AUTO_VERT :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-hse") ?
+ MIPI_DSI_MODE_VIDEO_HSE :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-no-hfp") ?
+ MIPI_DSI_MODE_VIDEO_NO_HFP :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-no-hbp") ?
+ MIPI_DSI_MODE_VIDEO_NO_HBP :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-video-no-hsa") ?
+ MIPI_DSI_MODE_VIDEO_NO_HSA :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-vsync-flush") ?
+ MIPI_DSI_MODE_VSYNC_FLUSH :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-no-eot-packet") ?
+ MIPI_DSI_MODE_NO_EOT_PACKET :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-clock-non-continuous") ?
+ MIPI_DSI_CLOCK_NON_CONTINUOUS :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-mode-lpm") ? MIPI_DSI_MODE_LPM :
+ 0;
+ mode |= of_property_read_bool(np, "dsi-hs-pkt-end-aligned") ?
+ MIPI_DSI_HS_PKT_END_ALIGNED :
+ 0;
+
+ return mode;
+}
+
+static int panel_mipi_dsi_probe(struct mipi_dsi_device *dsi)
+{
+ struct panel_mipi *mipi;
+ const char *format;
+ int err;
+
+ err = panel_mipi_probe(&dsi->dev, DRM_MODE_CONNECTOR_DSI);
+ if (err)
+ return err;
+
+ mipi = dev_get_drvdata(&dsi->dev);
+ mipi->dsi = dsi;
+ mipi->write_command = panel_mipi_dsi_write;
+ mipi->read_command = panel_mipi_dsi_read;
+
+ /* Default values */
+ dsi->lanes = 1;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = 0;
+
+ /* Read from the firmware */
+ if (mipi->firmware) {
+ dsi->lanes = be16_to_cpu(mipi->firmware->config->dsi_lanes);
+ dsi->format = be16_to_cpu(mipi->firmware->config->dsi_format);
+ dsi->mode_flags =
+ be32_to_cpu(mipi->firmware->config->dsi_mode_flags);
+ }
+
+ /* Load from the DT */
+ of_property_read_u32(dsi->dev.of_node, "dsi-lanes", &dsi->lanes);
+ if (!of_property_read_string(dsi->dev.of_node, "dsi-format", &format)) {
+ if (!strcmp(format, "rgb888"))
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ else if (!strcmp(format, "rgb666"))
+ dsi->format = MIPI_DSI_FMT_RGB666;
+ else if (!strcmp(format, "rgb666-packed"))
+ dsi->format = MIPI_DSI_FMT_RGB666_PACKED;
+ else if (!strcmp(format, "rgb565"))
+ dsi->format = MIPI_DSI_FMT_RGB565;
+ else
+ return dev_err_probe(&dsi->dev, -EINVAL,
+ "%pOF: Unknown dsi-format '%s'\n",
+ dsi->dev.of_node, format);
+ }
+ dsi->mode_flags |= panel_mipi_read_dsi_mode_props(dsi->dev.of_node);
+
+ if (!dsi->lanes)
+ return dev_err_probe(&dsi->dev, -EINVAL,
+ "dsi-lanes == 0 for DSI panel\n");
+
+ /* Adjust bus_format */
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB888:
+ mipi->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ mipi->bus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ mipi->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ mipi->bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ break;
+ }
+
+ err = mipi_dsi_attach(dsi);
+ if (err)
+ return dev_err_probe(&dsi->dev, err, "Failed to init DSI\n");
+
+ return 0;
+}
+
+static int panel_mipi_spi_probe(struct spi_device *spi)
+{
+ struct panel_mipi *mipi;
+ struct gpio_desc *dc;
+ int err;
+
+ err = panel_mipi_probe(&spi->dev, DRM_MODE_CONNECTOR_DPI);
+ if (err)
+ return err;
+
+ mipi = dev_get_drvdata(&spi->dev);
+ mipi->write_command = panel_mipi_dbi_write;
+ mipi->read_command = panel_mipi_dbi_read;
+
+ dc = devm_gpiod_get_optional(&spi->dev, "dc", GPIOD_OUT_LOW);
+ if (IS_ERR(dc))
+ return dev_err_probe(&spi->dev, PTR_ERR(dc),
+ "Failed to get GPIO for D/CX\n");
+
+ err = mipi_dbi_spi_init(spi, &mipi->dbi, dc);
+ if (err)
+ return dev_err_probe(&spi->dev, err, "Failed to init SPI\n");
+
+ return 0;
+}
+
+static void panel_mipi_dsi_remove(struct mipi_dsi_device *dsi)
+{
+ mipi_dsi_detach(dsi);
+}
+
+static const struct of_device_id panel_mipi_dsi_of_match[] = {
+ { .compatible = "panel-mipi-dsi" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, panel_mipi_dsi_of_match);
+
+static const struct of_device_id panel_mipi_spi_of_match[] = {
+ { .compatible = "panel-mipi-dpi-spi" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, panel_mipi_spi_of_match);
+
+static const struct spi_device_id panel_mipi_spi_ids[] = {
+ { "panel-mipi-dpi-spi" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, panel_mipi_spi_ids);
+
+static struct mipi_dsi_driver panel_mipi_dsi_driver = {
+ .probe = panel_mipi_dsi_probe,
+ .remove = panel_mipi_dsi_remove,
+ .driver = {
+ .name = "panel-mipi",
+ .of_match_table = panel_mipi_dsi_of_match,
+ },
+};
+
+static struct spi_driver panel_mipi_spi_driver = {
+ .probe = panel_mipi_spi_probe,
+ .id_table = panel_mipi_spi_ids,
+ .driver = {
+ .name = "panel-mipi",
+ .of_match_table = panel_mipi_spi_of_match,
+ },
+};
+
+static int __init panel_mipi_driver_init(void)
+{
+ int err;
+
+ if (IS_ENABLED(CONFIG_SPI)) {
+ err = spi_register_driver(&panel_mipi_spi_driver);
+ if (err)
+ return err;
+ }
+
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
+ err = mipi_dsi_driver_register(&panel_mipi_dsi_driver);
+ if (err) {
+ if (IS_ENABLED(CONFIG_SPI))
+ spi_unregister_driver(&panel_mipi_spi_driver);
+ return err;
+ }
+ }
+
+ return 0;
+}
+module_init(panel_mipi_driver_init);
+
+static void __exit panel_mipi_driver_exit(void)
+{
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_unregister(&panel_mipi_dsi_driver);
+
+ if (IS_ENABLED(CONFIG_SPI))
+ spi_unregister_driver(&panel_mipi_spi_driver);
+}
+module_exit(panel_mipi_driver_exit);
+
+MODULE_DESCRIPTION("Generic MIPI-DSI/DPI(+SPI) Panel Driver");
+MODULE_AUTHOR("Hironori KIKUCHI <kikuchan98@gmail.com>");
+MODULE_LICENSE("GPL v2");
--
2.48.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels
2025-01-31 6:43 ` [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels Hironori KIKUCHI
@ 2025-01-31 7:47 ` Krzysztof Kozlowski
2025-02-01 5:27 ` Hironori KIKUCHI
0 siblings, 1 reply; 5+ messages in thread
From: Krzysztof Kozlowski @ 2025-01-31 7:47 UTC (permalink / raw)
To: Hironori KIKUCHI
Cc: linux-kernel, Neil Armstrong, Jessica Zhang, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, dri-devel, devicetree
On Fri, Jan 31, 2025 at 03:43:53PM +0900, Hironori KIKUCHI wrote:
> This is a binding for generic MIPI-DSI/DPI panels that require
> initialization with a simple command sequence before use.
>
> Although the `panel-simple` binding exists for generic panels, it is not
> suitable for panels that require initialization via their communication
> bus, as such panels cannot be considered "simple".
>
> While many dedicated bindings exist for such panels, the generic panels
> are not complex enough to warrant a dedicated binding because they only
> require a "simple" initialization before use.
All hardware warrants a dedicated binding. I don't get from where did
you take such rule that you can skip actual hardware specific aspects.
>
> This binding addresses that gap.
>
> Signed-off-by: Hironori KIKUCHI <kikuchan98@gmail.com>
> ---
> .../bindings/display/panel/panel-mipi.yaml | 244 ++++++++++++++++++
> 1 file changed, 244 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
>
> diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
> new file mode 100644
> index 00000000000..681614333d8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
> @@ -0,0 +1,244 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/panel/panel-mipi.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Generic MIPI-DSI/DPI Panels Requiring Initialization
> +
> +maintainers:
> + - Hironori KIKUCHI <kikuchan98@gmail.com>
> +
> +description:
> + This is a binding for generic MIPI-DSI/DPI panels that require
> + initialization with a simple command sequence before use.
> +
> +properties:
> + compatible:
> + oneOf:
> + - enum:
> + - panel-mipi-dsi
> + - panel-mipi-dpi-spi
Missing device specific compatibles.
> + - items:
> + - {}
> + - enum:
> + - panel-mipi-dsi
> + - panel-mipi-dpi-spi
> +
> + reg:
> + description: DSI / SPI channel used by that screen
> + maxItems: 1
> +
> + power-supply: true
> +
> + io-supply:
> + description: I/O system regulator.
> + No need to set if this is the same as polwer-supply.
> +
> + dc-gpios:
> + maxItems: 1
> + description: Controller data/command selection (D/CX) in 4-line SPI mode.
> + If not set, the controller is in 3-line SPI mode.
> + Disallowed for DSI.
> +
> + width-mm: true
> + height-mm: true
> + panel-timing: true
> + display-timings: true
> +
> + reset-delay:
> + description: Delay (in ms) after the reset command
So use proper unit suffixes. Everywhere.
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + init-delay:
> + description: Delay (in ms) for sending the initial command sequence
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + sleep-delay:
> + description: Delay (in ms) after the sleep command
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + backlight-delay:
> + description: Delay (in ms) for enabling the backlight
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + port: true
> + reset-gpios: true
> + rotation: true
> +
> + backlight: true
> +
> + init-sequence:
> + description: Encoded initial command sequence.
> + $ref: /schemas/types.yaml#/definitions/uint8-array
> +
> + firmware-name:
> + maxItems: 1
> +
> + dsi-mode-video:
> + description: Enable MIPI_DSI_MODE_VIDEO.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-burst:
> + description: Enable MIPI_DSI_MODE_VIDEO_BURST.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-sync-pulse:
> + description: Enable MIPI_DSI_MODE_VIDEO_SYNC_PULSE.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-auto-vert:
> + description: Enable MIPI_DSI_MODE_VIDEO_AUTO_VERT.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-hse:
> + description: Enable MIPI_DSI_MODE_VIDEO_HSE.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-no-hfp:
> + description: Enable MIPI_DSI_MODE_VIDEO_NO_HFP.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-no-hbp:
> + description: Enable MIPI_DSI_MODE_VIDEO_NO_HBP.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-video-no-hsa:
> + description: Enable MIPI_DSI_MODE_VIDEO_NO_HSA.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-vsync-flush:
> + description: Enable MIPI_DSI_MODE_VSYNC_FLUSH.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-no-eot-packet:
> + description: Enable MIPI_DSI_MODE_NO_EOT_PACKET.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-clock-non-continuous:
> + description: Enable MIPI_DSI_CLOCK_NON_CONTINUOUS.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-mode-lpm:
> + description: Enable MIPI_DSI_MODE_LPM.
> + Disallowed for SPI.
> + type: boolean
> +
> + dsi-hs-pkt-end-aligned:
All this must be deduced from the compatible which makes this binding
redundant. This binding is a no-go for me for obvious reasons: is not
specific and you try to bypass the exact rule of bindings to be specific
about hardware.
> + description: Enable MIPI_DSI_HS_PKT_END_ALIGNED.
> + Disallowed for SPI.
> + type: boolean
> +
> +required:
> + - compatible
> + - reg
> +
> +allOf:
> + - $ref: panel-common.yaml#
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - panel-mipi-dpi-spi
> + then:
> + # SPI mode
> + $ref: /schemas/spi/spi-peripheral-props.yaml#
> + properties:
> + dsi-mode-video: false
> + dsi-mode-video-burst: false
> + dsi-mode-video-sync-pulse: false
> + dsi-mode-video-auto-vert: false
> + dsi-mode-video-hse: false
> + dsi-mode-video-no-hfp: false
> + dsi-mode-video-no-hbp: false
> + dsi-mode-video-no-hsa: false
> + dsi-mode-vsync-flush: false
> + dsi-mode-no-eot-packet: false
> + dsi-clock-non-continuous: false
> + dsi-mode-lpm: false
> + dsi-hs-pkt-end-aligned: false
> +
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - panel-mipi-dsi
> + then:
> + # DSI mode
> + properties:
> + dc-gpios: false
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + dsi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + panel@0 {
> + compatible = "vendor,some-panel", "panel-mipi-dsi";
Undocumented compatible.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels
2025-01-31 7:47 ` Krzysztof Kozlowski
@ 2025-02-01 5:27 ` Hironori KIKUCHI
0 siblings, 0 replies; 5+ messages in thread
From: Hironori KIKUCHI @ 2025-02-01 5:27 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-kernel, Neil Armstrong, Jessica Zhang, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, dri-devel, devicetree
Hello Krzysztof,
Thank you for your feedback.
On Fri, Jan 31, 2025 at 4:47 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On Fri, Jan 31, 2025 at 03:43:53PM +0900, Hironori KIKUCHI wrote:
> > This is a binding for generic MIPI-DSI/DPI panels that require
> > initialization with a simple command sequence before use.
> >
> > Although the `panel-simple` binding exists for generic panels, it is not
> > suitable for panels that require initialization via their communication
> > bus, as such panels cannot be considered "simple".
> >
> > While many dedicated bindings exist for such panels, the generic panels
> > are not complex enough to warrant a dedicated binding because they only
> > require a "simple" initialization before use.
>
> All hardware warrants a dedicated binding. I don't get from where did
> you take such rule that you can skip actual hardware specific aspects.
>
> >
> > This binding addresses that gap.
> >
> > Signed-off-by: Hironori KIKUCHI <kikuchan98@gmail.com>
> > ---
> > .../bindings/display/panel/panel-mipi.yaml | 244 ++++++++++++++++++
> > 1 file changed, 244 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
> > new file mode 100644
> > index 00000000000..681614333d8
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/panel/panel-mipi.yaml
> > @@ -0,0 +1,244 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/display/panel/panel-mipi.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Generic MIPI-DSI/DPI Panels Requiring Initialization
> > +
> > +maintainers:
> > + - Hironori KIKUCHI <kikuchan98@gmail.com>
> > +
> > +description:
> > + This is a binding for generic MIPI-DSI/DPI panels that require
> > + initialization with a simple command sequence before use.
> > +
> > +properties:
> > + compatible:
> > + oneOf:
> > + - enum:
> > + - panel-mipi-dsi
> > + - panel-mipi-dpi-spi
>
> Missing device specific compatibles.
>
> > + - items:
> > + - {}
> > + - enum:
> > + - panel-mipi-dsi
> > + - panel-mipi-dpi-spi
> > +
> > + reg:
> > + description: DSI / SPI channel used by that screen
> > + maxItems: 1
> > +
> > + power-supply: true
> > +
> > + io-supply:
> > + description: I/O system regulator.
> > + No need to set if this is the same as polwer-supply.
> > +
> > + dc-gpios:
> > + maxItems: 1
> > + description: Controller data/command selection (D/CX) in 4-line SPI mode.
> > + If not set, the controller is in 3-line SPI mode.
> > + Disallowed for DSI.
> > +
> > + width-mm: true
> > + height-mm: true
> > + panel-timing: true
> > + display-timings: true
> > +
> > + reset-delay:
> > + description: Delay (in ms) after the reset command
>
> So use proper unit suffixes. Everywhere.
>
>
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > +
> > + init-delay:
> > + description: Delay (in ms) for sending the initial command sequence
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > +
> > + sleep-delay:
> > + description: Delay (in ms) after the sleep command
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > +
> > + backlight-delay:
> > + description: Delay (in ms) for enabling the backlight
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > +
> > + port: true
> > + reset-gpios: true
> > + rotation: true
> > +
> > + backlight: true
> > +
> > + init-sequence:
> > + description: Encoded initial command sequence.
> > + $ref: /schemas/types.yaml#/definitions/uint8-array
> > +
> > + firmware-name:
> > + maxItems: 1
> > +
> > + dsi-mode-video:
> > + description: Enable MIPI_DSI_MODE_VIDEO.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-burst:
> > + description: Enable MIPI_DSI_MODE_VIDEO_BURST.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-sync-pulse:
> > + description: Enable MIPI_DSI_MODE_VIDEO_SYNC_PULSE.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-auto-vert:
> > + description: Enable MIPI_DSI_MODE_VIDEO_AUTO_VERT.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-hse:
> > + description: Enable MIPI_DSI_MODE_VIDEO_HSE.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-no-hfp:
> > + description: Enable MIPI_DSI_MODE_VIDEO_NO_HFP.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-no-hbp:
> > + description: Enable MIPI_DSI_MODE_VIDEO_NO_HBP.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-video-no-hsa:
> > + description: Enable MIPI_DSI_MODE_VIDEO_NO_HSA.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-vsync-flush:
> > + description: Enable MIPI_DSI_MODE_VSYNC_FLUSH.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-no-eot-packet:
> > + description: Enable MIPI_DSI_MODE_NO_EOT_PACKET.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-clock-non-continuous:
> > + description: Enable MIPI_DSI_CLOCK_NON_CONTINUOUS.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-mode-lpm:
> > + description: Enable MIPI_DSI_MODE_LPM.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > + dsi-hs-pkt-end-aligned:
>
> All this must be deduced from the compatible which makes this binding
> redundant. This binding is a no-go for me for obvious reasons: is not
> specific and you try to bypass the exact rule of bindings to be specific
> about hardware.
Ok, I'll delete these redundant properties, and deduce from the compatible.
>
> > + description: Enable MIPI_DSI_HS_PKT_END_ALIGNED.
> > + Disallowed for SPI.
> > + type: boolean
> > +
> > +required:
> > + - compatible
> > + - reg
> > +
> > +allOf:
> > + - $ref: panel-common.yaml#
> > + - if:
> > + properties:
> > + compatible:
> > + contains:
> > + enum:
> > + - panel-mipi-dpi-spi
> > + then:
> > + # SPI mode
> > + $ref: /schemas/spi/spi-peripheral-props.yaml#
> > + properties:
> > + dsi-mode-video: false
> > + dsi-mode-video-burst: false
> > + dsi-mode-video-sync-pulse: false
> > + dsi-mode-video-auto-vert: false
> > + dsi-mode-video-hse: false
> > + dsi-mode-video-no-hfp: false
> > + dsi-mode-video-no-hbp: false
> > + dsi-mode-video-no-hsa: false
> > + dsi-mode-vsync-flush: false
> > + dsi-mode-no-eot-packet: false
> > + dsi-clock-non-continuous: false
> > + dsi-mode-lpm: false
> > + dsi-hs-pkt-end-aligned: false
> > +
> > + - if:
> > + properties:
> > + compatible:
> > + contains:
> > + enum:
> > + - panel-mipi-dsi
> > + then:
> > + # DSI mode
> > + properties:
> > + dc-gpios: false
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > + - |
> > + dsi {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + panel@0 {
> > + compatible = "vendor,some-panel", "panel-mipi-dsi";
>
> Undocumented compatible.
>
> Best regards,
> Krzysztof
>
Regards,
kikuchan.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-02-01 5:27 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-01-31 6:43 [PATCH 0/2] drm: panel: Add Generic MIPI Panel Driver Hironori KIKUCHI
2025-01-31 6:43 ` [PATCH 1/2] dt-bindings: display: panel: Add generic MIPI-DSI/DPI panels Hironori KIKUCHI
2025-01-31 7:47 ` Krzysztof Kozlowski
2025-02-01 5:27 ` Hironori KIKUCHI
2025-01-31 6:43 ` [PATCH 2/2] drm: panel: Add a driver for Generic MIPI-DSI/DPI(+SPI) panels Hironori KIKUCHI
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®