Hi,
This patchset adds a driver that will work with most MIPI DBI compatible SPI panels out there.
Maxime gave[1] a good overview of the situation with these displays and proposed to make a driver that works with all MIPI DBI compatible controllers and use a firmware file to provide the controller setup for a particular panel.
I have now made a script[2] that can create the firmware file, example in the wiki[3]
Main change since version 2: - Use Device Tree for all properties (Maxime)
The MIPI DPI specification has optional support for DPI where the controller is configured over DBI. Because of this I put the driver in drm/panel so it could be extended in the future to support panel-mipi-dpi-spi. I have now looked at this more closely and the only thing that can be shared between the two are the firmware command functions. These functions can be moved to the drm_mipi_dbi_helper for sharing. Now that I know that there won't be one driver module that supports both use cases, I'm thinking of moving the driver to drm/tiny where the other drivers of its kind are located. I'll move the driver in the next version of the patchset unless someone have reasons for leaving it in drm/panel.
Noralf.
[1] https://lore.kernel.org/dri-devel/20211129093946.xhp22mvdut3m67sc@houat/ [2] https://github.com/notro/panel-mipi-dbi/blob/main/mipi-dbi-cmd [3] https://github.com/notro/panel-mipi-dbi/wiki
Noralf Trønnes (3): dt-bindings: display: add bindings for MIPI DBI compatible SPI panels drm/mipi-dbi: Add driver_private member to struct mipi_dbi_dev drm/panel: Add MIPI DBI compatible SPI driver
.../display/panel/panel-mipi-dbi-spi.yaml | 124 ++++++ MAINTAINERS | 8 + drivers/gpu/drm/panel/Kconfig | 13 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-mipi-dbi.c | 413 ++++++++++++++++++ include/drm/drm_mipi_dbi.h | 8 + 6 files changed, 567 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml create mode 100644 drivers/gpu/drm/panel/panel-mipi-dbi.c
Add binding for MIPI DBI compatible SPI panels.
v3: - Move properties to Device Tree (Maxime) - Use contains for compatible (Maxime) - Add backlight property to example - Flesh out description
v2: - Fix path for panel-common.yaml - Use unevaluatedProperties - Drop properties which are in the allOf section - Drop model property (Rob)
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- .../display/panel/panel-mipi-dbi-spi.yaml | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml new file mode 100644 index 000000000000..4d017a36ad4d --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/panel-mipi-dbi-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MIPI DBI SPI Panel + +maintainers: + - Noralf Trønnes noralf@tronnes.org + +description: | + This binding is for display panels using a MIPI DBI compatible controller + in SPI mode. + + The MIPI Alliance Standard for Display Bus Interface defines the electrical + and logical interfaces for display controllers historically used in mobile + phones. The standard defines 4 display architecture types and this binding is + for type 1 which has full frame memory. There are 3 interface types in the + standard and type C is the serial interface. + + The standard defines the following interface signals for type C: + - Power: + - Vdd: Power supply for display module + - Vddi: Logic level supply for interface signals + Combined into one in this binding called: power-supply + - Interface: + - CSx: Chip select + - SCL: Serial clock + - Dout: Serial out + - Din: Serial in + - SDA: Bidrectional in/out + - D/CX: Data/command selection, high=data, low=command + Called dc-gpios in this binding. + - RESX: Reset when low + Called reset-gpios in this binding. + + The type C interface has 3 options: + + - Option 1: 9-bit mode and D/CX as the 9th bit + | Command | the next command or following data | + |<0><D7><D6><D5><D4><D3><D2><D1><D0>|<D/CX><D7><D6><D5><D4><D3><D2><D1><D0>| + + - Option 2: 16-bit mode and D/CX as a 9th bit + | Command or data | + |<X><X><X><X><X><X><X><D/CX><D7><D6><D5><D4><D3><D2><D1><D0>| + + - Option 3: 8-bit mode and D/CX as a separate interface line + | Command or data | + |<D7><D6><D5><D4><D3><D2><D1><D0>| + + The panel resolution is specified using the panel-timing node properties + hactive (width) and vactive (height). The other mandatory panel-timing + properties should be set to zero except clock-frequency which can be + optionally set to inform about the actual pixel clock frequency. + + If the panel is wired to the controller at an offset specify this using + hback-porch (x-offset) and vback-porch (y-offset). + +allOf: + - $ref: panel-common.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + contains: + const: panel-dbi-spi + + write-only: + type: boolean + description: + Controller is not readable (ie. MISO is not wired up). + + 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. + +required: + - compatible + - reg + - panel-timing + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + display@0{ + compatible = "sainsmart18", "panel-dbi-spi"; + reg = <0>; + spi-max-frequency = <40000000>; + + dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>; + write-only; + + backlight = <&backlight>; + + 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>; + }; + }; + }; + +...
On Fri, Feb 11, 2022 at 02:04:32PM +0100, Noralf Trønnes wrote:
Add binding for MIPI DBI compatible SPI panels.
v3:
- Move properties to Device Tree (Maxime)
- Use contains for compatible (Maxime)
- Add backlight property to example
- Flesh out description
v2:
- Fix path for panel-common.yaml
- Use unevaluatedProperties
- Drop properties which are in the allOf section
- Drop model property (Rob)
Signed-off-by: Noralf Trønnes noralf@tronnes.org
.../display/panel/panel-mipi-dbi-spi.yaml | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml new file mode 100644 index 000000000000..4d017a36ad4d --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/panel-mipi-dbi-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: MIPI DBI SPI Panel
+maintainers:
- Noralf Trønnes noralf@tronnes.org
+description: |
- This binding is for display panels using a MIPI DBI compatible controller
- in SPI mode.
- The MIPI Alliance Standard for Display Bus Interface defines the electrical
- and logical interfaces for display controllers historically used in mobile
- phones. The standard defines 4 display architecture types and this binding is
- for type 1 which has full frame memory. There are 3 interface types in the
- standard and type C is the serial interface.
- The standard defines the following interface signals for type C:
- Power:
- Vdd: Power supply for display module
- Vddi: Logic level supply for interface signals
- Combined into one in this binding called: power-supply
- Interface:
- CSx: Chip select
- SCL: Serial clock
- Dout: Serial out
- Din: Serial in
- SDA: Bidrectional in/out
- D/CX: Data/command selection, high=data, low=command
Called dc-gpios in this binding.
- RESX: Reset when low
Called reset-gpios in this binding.
- The type C interface has 3 options:
- Option 1: 9-bit mode and D/CX as the 9th bit
| Command | the next command or following data |
|<0><D7><D6><D5><D4><D3><D2><D1><D0>|<D/CX><D7><D6><D5><D4><D3><D2><D1><D0>|
- Option 2: 16-bit mode and D/CX as a 9th bit
| Command or data |
|<X><X><X><X><X><X><X><D/CX><D7><D6><D5><D4><D3><D2><D1><D0>|
- Option 3: 8-bit mode and D/CX as a separate interface line
| Command or data |
|<D7><D6><D5><D4><D3><D2><D1><D0>|
- The panel resolution is specified using the panel-timing node properties
- hactive (width) and vactive (height). The other mandatory panel-timing
- properties should be set to zero except clock-frequency which can be
- optionally set to inform about the actual pixel clock frequency.
- If the panel is wired to the controller at an offset specify this using
- hback-porch (x-offset) and vback-porch (y-offset).
+allOf:
- $ref: panel-common.yaml#
- $ref: /schemas/spi/spi-peripheral-props.yaml#
+properties:
- compatible:
- contains:
const: panel-dbi-spi
This could be further improved by using
properties: compatible: items: - {} # Panel Specific Compatible - const: panel-dbi-spi
To make it obvious we expect two compatible, and what the first one should be.
Once fixed,
Acked-by: Maxime Ripard maxime@cerno.tech
Maxime
Den 11.02.2022 14.27, skrev Maxime Ripard:
On Fri, Feb 11, 2022 at 02:04:32PM +0100, Noralf Trønnes wrote:
Add binding for MIPI DBI compatible SPI panels.
v3:
- Move properties to Device Tree (Maxime)
- Use contains for compatible (Maxime)
- Add backlight property to example
- Flesh out description
v2:
- Fix path for panel-common.yaml
- Use unevaluatedProperties
- Drop properties which are in the allOf section
- Drop model property (Rob)
Signed-off-by: Noralf Trønnes noralf@tronnes.org
.../display/panel/panel-mipi-dbi-spi.yaml | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml new file mode 100644 index 000000000000..4d017a36ad4d --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/panel-mipi-dbi-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: MIPI DBI SPI Panel
+maintainers:
- Noralf Trønnes noralf@tronnes.org
+description: |
- This binding is for display panels using a MIPI DBI compatible controller
- in SPI mode.
- The MIPI Alliance Standard for Display Bus Interface defines the electrical
- and logical interfaces for display controllers historically used in mobile
- phones. The standard defines 4 display architecture types and this binding is
- for type 1 which has full frame memory. There are 3 interface types in the
- standard and type C is the serial interface.
- The standard defines the following interface signals for type C:
- Power:
- Vdd: Power supply for display module
- Vddi: Logic level supply for interface signals
- Combined into one in this binding called: power-supply
- Interface:
- CSx: Chip select
- SCL: Serial clock
- Dout: Serial out
- Din: Serial in
- SDA: Bidrectional in/out
- D/CX: Data/command selection, high=data, low=command
Called dc-gpios in this binding.
- RESX: Reset when low
Called reset-gpios in this binding.
- The type C interface has 3 options:
- Option 1: 9-bit mode and D/CX as the 9th bit
| Command | the next command or following data |
|<0><D7><D6><D5><D4><D3><D2><D1><D0>|<D/CX><D7><D6><D5><D4><D3><D2><D1><D0>|
- Option 2: 16-bit mode and D/CX as a 9th bit
| Command or data |
|<X><X><X><X><X><X><X><D/CX><D7><D6><D5><D4><D3><D2><D1><D0>|
- Option 3: 8-bit mode and D/CX as a separate interface line
| Command or data |
|<D7><D6><D5><D4><D3><D2><D1><D0>|
- The panel resolution is specified using the panel-timing node properties
- hactive (width) and vactive (height). The other mandatory panel-timing
- properties should be set to zero except clock-frequency which can be
- optionally set to inform about the actual pixel clock frequency.
- If the panel is wired to the controller at an offset specify this using
- hback-porch (x-offset) and vback-porch (y-offset).
+allOf:
- $ref: panel-common.yaml#
- $ref: /schemas/spi/spi-peripheral-props.yaml#
+properties:
- compatible:
- contains:
const: panel-dbi-spi
This could be further improved by using
properties: compatible: items: - {} # Panel Specific Compatible - const: panel-dbi-spi
To make it obvious we expect two compatible, and what the first one should be.
Ok.
I see that for some reason I've dropped the mipi infix here in the compatible and in the example. I'll fix that and make it panel-mipi-dbi-spi in the next version.
Noralf.
Once fixed,
Acked-by: Maxime Ripard maxime@cerno.tech
Maxime
devm_drm_dev_alloc() can't allocate structures that embed a structure which then again embeds drm_device. Workaround this by adding a driver_private pointer to struct mipi_dbi_dev which the driver can use for its additional state.
v3: - Add documentation
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- include/drm/drm_mipi_dbi.h | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/include/drm/drm_mipi_dbi.h b/include/drm/drm_mipi_dbi.h index 6fe13cce2670..dad2f187b64b 100644 --- a/include/drm/drm_mipi_dbi.h +++ b/include/drm/drm_mipi_dbi.h @@ -130,6 +130,14 @@ struct mipi_dbi_dev { * @dbi: MIPI DBI interface */ struct mipi_dbi dbi; + + /** + * @driver_private: Driver private data. + * Necessary for drivers with private data since devm_drm_dev_alloc() + * can't allocate structures that embed a structure which then again + * embeds drm_device. + */ + void *driver_private; };
static inline struct mipi_dbi_dev *drm_to_mipi_dbi_dev(struct drm_device *drm)
On Fri, Feb 11, 2022 at 02:04:33PM +0100, Noralf Trønnes wrote:
devm_drm_dev_alloc() can't allocate structures that embed a structure which then again embeds drm_device. Workaround this by adding a driver_private pointer to struct mipi_dbi_dev which the driver can use for its additional state.
v3:
- Add documentation
Signed-off-by: Noralf Trønnes noralf@tronnes.org
Acked-by: Maxime Ripard maxime@cerno.tech
Maxime
Add a driver that will work with most MIPI DBI compatible SPI panels. This avoids adding a driver for every new MIPI DBI compatible controller that is to be used by Linux. The 'compatible' Device Tree property with a '.bin' suffix will be used to load a firmware file that contains the controller configuration.
Example (driver will load sainsmart18.bin):
display@0 { compatible = "sainsmart18", "panel-mipi-dbi-spi"; ... };
v3: - Move properties to DT (Maxime) - The MIPI DPI spec has optional support for DPI where the controller is configured over DBI. Rework the command functions so they can be moved to drm_mipi_dbi and shared with a future panel-mipi-dpi-spi driver
v2: - Drop model property and use compatible instead (Rob) - Add wiki entry in MAINTAINERS
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- MAINTAINERS | 8 + drivers/gpu/drm/panel/Kconfig | 13 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-mipi-dbi.c | 413 +++++++++++++++++++++++++ 4 files changed, 435 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-mipi-dbi.c
diff --git a/MAINTAINERS b/MAINTAINERS index d03ad8da1f36..8baa98723bdc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6047,6 +6047,14 @@ T: git git://anongit.freedesktop.org/drm/drm-misc F: Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt F: drivers/gpu/drm/tiny/mi0283qt.c
+DRM DRIVER FOR MIPI DBI compatible panels +M: Noralf Trønnes noralf@tronnes.org +S: Maintained +W: https://github.com/notro/panel-mipi-dbi/wiki +T: git git://anongit.freedesktop.org/drm/drm-misc +F: Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml +F: drivers/gpu/drm/panel/panel-mipi-dbi.c + DRM DRIVER FOR MSM ADRENO GPU M: Rob Clark robdclark@gmail.com M: Sean Paul sean@poorly.run diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 434c2861bb40..a83349e91cb6 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -274,6 +274,19 @@ config DRM_PANEL_LG_LG4573 Say Y here if you want to enable support for LG4573 RGB panel. To compile this driver as a module, choose M here.
+config DRM_PANEL_MIPI_DBI + tristate "MIPI DBI compatible panel" + depends on SPI + depends on BACKLIGHT_CLASS_DEVICE + depends on DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_MIPI_DBI + help + Say Y here if you want to enable support for MIPI DBI compatible + panels. The controller command setup can be provided using a + firmware file. + To compile this driver as a module, choose M here. + 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 d99fbbce49d1..a90c30459964 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o +obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35510) += panel-novatek-nt35510.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o diff --git a/drivers/gpu/drm/panel/panel-mipi-dbi.c b/drivers/gpu/drm/panel/panel-mipi-dbi.c new file mode 100644 index 000000000000..9240fdec38d6 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-mipi-dbi.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DRM driver for MIPI DBI compatible display panels + * + * Copyright 2022 Noralf Trønnes + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_modeset_helper.h> + +#include <video/display_timing.h> +#include <video/mipi_display.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +static const u8 panel_mipi_dbi_magic[15] = { 'M', 'I', 'P', 'I', ' ', 'D', 'B', 'I', + 0, 0, 0, 0, 0, 0, 0 }; + +/* + * The optional display controller configuration is stored in a firmware file. + * The Device Tree 'compatible' property value with a '.bin' suffix is passed + * to request_firmware() to fetch this file. + */ +struct panel_mipi_dbi_config { + /* Magic string: panel_mipi_dbi_magic */ + u8 magic[15]; + + /* Config file format version */ + u8 file_format_version; + + /* + * MIPI commands to execute when the display pipeline is enabled. + * This is used to configure the display controller. + * + * The commands are stored in a byte array with the format: + * command, num_parameters, [ parameter, ...], command, ... + * + * Some commands require a pause before the next command can be received. + * Inserting a delay in the command sequence is done by using the NOP command with one + * parameter: delay in miliseconds (the No Operation command is part of the MIPI Display + * Command Set where it has no parameters). + * + * Example: + * command 0x11 + * sleep 120ms + * command 0xb1 parameters 0x01, 0x2c, 0x2d + * command 0x29 + * + * Byte sequence: + * 0x11 0x00 + * 0x00 0x01 0x78 + * 0xb1 0x03 0x01 0x2c 0x2d + * 0x29 0x00 + */ + u8 commands[]; +}; + +struct panel_mipi_dbi_commands { + const u8 *buf; + size_t len; +}; + +static struct panel_mipi_dbi_commands * +panel_mipi_dbi_check_commands(struct device *dev, const struct firmware *fw) +{ + const struct panel_mipi_dbi_config *config = (struct panel_mipi_dbi_config *)fw->data; + struct panel_mipi_dbi_commands *commands; + size_t size = fw->size, commands_len; + unsigned int i = 0; + + if (size < sizeof(*config) + 2) { /* At least 1 command */ + dev_err(dev, "config: file size=%zu is too small\n", size); + return ERR_PTR(-EINVAL); + } + + if (memcmp(config->magic, panel_mipi_dbi_magic, sizeof(config->magic))) { + dev_err(dev, "config: Bad magic: %15ph\n", config->magic); + return ERR_PTR(-EINVAL); + } + + if (config->file_format_version != 1) { + dev_err(dev, "config: version=%u is not supported\n", config->file_format_version); + return ERR_PTR(-EINVAL); + } + + drm_dev_dbg(dev, DRM_UT_DRIVER, "size=%zu version=%u\n", size, config->file_format_version); + + commands_len = size - sizeof(*config); + + while ((i + 1) < commands_len) { + u8 command = config->commands[i++]; + u8 num_parameters = config->commands[i++]; + const u8 *parameters = &config->commands[i]; + + i += num_parameters; + if (i > commands_len) { + dev_err(dev, "config: command=0x%02x num_parameters=%u overflows\n", + command, num_parameters); + return ERR_PTR(-EINVAL); + } + + if (command == 0x00 && num_parameters == 1) + drm_dev_dbg(dev, DRM_UT_DRIVER, "sleep %ums\n", parameters[0]); + else + drm_dev_dbg(dev, DRM_UT_DRIVER, "command %02x %*ph\n", + command, num_parameters, parameters); + } + + if (i != commands_len) { + dev_err(dev, "config: malformed command array\n"); + return ERR_PTR(-EINVAL); + } + + commands = devm_kzalloc(dev, sizeof(*commands), GFP_KERNEL); + if (!commands) + return ERR_PTR(-ENOMEM); + + commands->len = commands_len; + commands->buf = devm_kmemdup(dev, config->commands, commands->len, GFP_KERNEL); + if (!commands->buf) + return ERR_PTR(-ENOMEM); + + return commands; +} + +static struct panel_mipi_dbi_commands *panel_mipi_dbi_commands_from_fw(struct device *dev) +{ + struct panel_mipi_dbi_commands *commands; + const struct firmware *fw; + const char *compatible; + struct property *prop; + char fw_name[40]; + int ret; + + of_property_for_each_string(dev->of_node, "compatible", prop, compatible) { + snprintf(fw_name, sizeof(fw_name), "%s.bin", compatible); + ret = firmware_request_nowarn(&fw, fw_name, dev); + if (ret) { + drm_dev_dbg(dev, DRM_UT_DRIVER, + "No config file found for compatible: '%s' (error=%d)\n", + compatible, ret); + continue; + } + + commands = panel_mipi_dbi_check_commands(dev, fw); + release_firmware(fw); + return commands; + } + + return NULL; +} + +static void panel_mipi_dbi_commands_execute(struct mipi_dbi *dbi, + struct panel_mipi_dbi_commands *commands) +{ + unsigned int i = 0; + + if (!commands) + return; + + while (i < commands->len) { + u8 command = commands->buf[i++]; + u8 num_parameters = commands->buf[i++]; + const u8 *parameters = &commands->buf[i]; + + if (command == 0x00 && num_parameters == 1) + msleep(parameters[0]); + else if (num_parameters) + mipi_dbi_command_stackbuf(dbi, command, parameters, num_parameters); + else + mipi_dbi_command(dbi, command); + + i += num_parameters; + } +} + +static void panel_mipi_dbi_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); + struct mipi_dbi *dbi = &dbidev->dbi; + int ret, idx; + + if (!drm_dev_enter(pipe->crtc.dev, &idx)) + return; + + drm_dbg(pipe->crtc.dev, "\n"); + + ret = mipi_dbi_poweron_conditional_reset(dbidev); + if (ret < 0) + goto out_exit; + if (!ret) + panel_mipi_dbi_commands_execute(dbi, dbidev->driver_private); + + mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); +out_exit: + drm_dev_exit(idx); +} + +static const struct drm_simple_display_pipe_funcs panel_mipi_dbi_pipe_funcs = { + .enable = panel_mipi_dbi_enable, + .disable = mipi_dbi_pipe_disable, + .update = mipi_dbi_pipe_update, +}; + +DEFINE_DRM_GEM_CMA_FOPS(panel_mipi_dbi_fops); + +static const struct drm_driver panel_mipi_dbi_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &panel_mipi_dbi_fops, + DRM_GEM_CMA_DRIVER_OPS_VMAP, + .debugfs_init = mipi_dbi_debugfs_init, + .name = "panel-mipi-dbi", + .desc = "MIPI DBI compatible display panel", + .date = "20220103", + .major = 1, + .minor = 0, +}; + +static int panel_mipi_dbi_get_mode(struct mipi_dbi_dev *dbidev, struct drm_display_mode *mode) +{ + struct device *dev = dbidev->drm.dev; + u32 width_mm = 0, height_mm = 0; + struct display_timing timing; + struct videomode vm; + int ret; + + ret = of_get_display_timing(dev->of_node, "panel-timing", &timing); + if (ret) { + dev_err(dev, "%pOF: failed to get panel-timing (error=%d)\n", dev->of_node, ret); + return ret; + } + + videomode_from_timing(&timing, &vm); + + if (!vm.hactive || vm.hfront_porch || vm.hsync_len || + (vm.hback_porch + vm.hactive) > 0xffff || + !vm.vactive || vm.vfront_porch || vm.vsync_len || + (vm.vback_porch + vm.vactive) > 0xffff || + vm.flags) { + dev_err(dev, "%pOF: panel-timing out of bounds\n", dev->of_node); + return -EINVAL; + } + + /* The driver doesn't use the pixel clock but it is mandatory so fake one if not set */ + if (!vm.pixelclock) + vm.pixelclock = (vm.hback_porch + vm.hactive) * (vm.vback_porch + vm.vactive) * 60; + + dbidev->top_offset = vm.vback_porch; + dbidev->left_offset = vm.hback_porch; + + memset(mode, 0, sizeof(*mode)); + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + ret = device_property_read_u32(dev, "width-mm", &width_mm); + if (ret && ret != -EINVAL) + return ret; + + ret = device_property_read_u32(dev, "height-mm", &height_mm); + if (ret && ret != -EINVAL) + return ret; + + mode->width_mm = width_mm; + mode->height_mm = height_mm; + + drm_mode_debug_printmodeline(mode); + + return 0; +} + +static int panel_mipi_dbi_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct drm_display_mode mode; + struct mipi_dbi_dev *dbidev; + struct drm_device *drm; + struct mipi_dbi *dbi; + struct gpio_desc *dc; + int ret; + + dbidev = devm_drm_dev_alloc(dev, &panel_mipi_dbi_driver, struct mipi_dbi_dev, drm); + if (IS_ERR(dbidev)) + return PTR_ERR(dbidev); + + dbi = &dbidev->dbi; + drm = &dbidev->drm; + + ret = panel_mipi_dbi_get_mode(dbidev, &mode); + if (ret) + return ret; + + dbidev->regulator = devm_regulator_get(dev, "power"); + if (IS_ERR(dbidev->regulator)) + return dev_err_probe(dev, PTR_ERR(dbidev->regulator), + "Failed to get regulator 'power'\n"); + + dbidev->backlight = devm_of_find_backlight(dev); + if (IS_ERR(dbidev->backlight)) + return dev_err_probe(dev, PTR_ERR(dbidev->backlight), "Failed to get backlight\n"); + + dbi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(dbi->reset)) + return dev_err_probe(dev, PTR_ERR(dbi->reset), "Failed to get GPIO 'reset'\n"); + + dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW); + if (IS_ERR(dc)) + return dev_err_probe(dev, PTR_ERR(dc), "Failed to get GPIO 'dc'\n"); + + ret = mipi_dbi_spi_init(spi, dbi, dc); + if (ret) + return ret; + + if (device_property_present(dev, "write-only")) + dbi->read_commands = NULL; + + dbidev->driver_private = panel_mipi_dbi_commands_from_fw(dev); + if (IS_ERR(dbidev->driver_private)) + return PTR_ERR(dbidev->driver_private); + + ret = mipi_dbi_dev_init(dbidev, &panel_mipi_dbi_pipe_funcs, &mode, 0); + if (ret) + return ret; + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + return ret; + + spi_set_drvdata(spi, drm); + + drm_fbdev_generic_setup(drm, 0); + + return 0; +} + +static int panel_mipi_dbi_spi_remove(struct spi_device *spi) +{ + struct drm_device *drm = spi_get_drvdata(spi); + + drm_dev_unplug(drm); + drm_atomic_helper_shutdown(drm); + + return 0; +} + +static void panel_mipi_dbi_spi_shutdown(struct spi_device *spi) +{ + drm_atomic_helper_shutdown(spi_get_drvdata(spi)); +} + +static int __maybe_unused panel_mipi_dbi_pm_suspend(struct device *dev) +{ + return drm_mode_config_helper_suspend(dev_get_drvdata(dev)); +} + +static int __maybe_unused panel_mipi_dbi_pm_resume(struct device *dev) +{ + drm_mode_config_helper_resume(dev_get_drvdata(dev)); + + return 0; +} + +static const struct dev_pm_ops panel_mipi_dbi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(panel_mipi_dbi_pm_suspend, panel_mipi_dbi_pm_resume) +}; + +static const struct of_device_id panel_mipi_dbi_spi_of_match[] = { + { .compatible = "panel-mipi-dbi-spi" }, + {}, +}; +MODULE_DEVICE_TABLE(of, panel_mipi_dbi_spi_of_match); + +static const struct spi_device_id panel_mipi_dbi_spi_id[] = { + { "panel-mipi-dbi-spi", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, panel_mipi_dbi_spi_id); + +static struct spi_driver panel_mipi_dbi_spi_driver = { + .driver = { + .name = "panel-mipi-dbi-spi", + .owner = THIS_MODULE, + .of_match_table = panel_mipi_dbi_spi_of_match, + .pm = &panel_mipi_dbi_pm_ops, + }, + .id_table = panel_mipi_dbi_spi_id, + .probe = panel_mipi_dbi_spi_probe, + .remove = panel_mipi_dbi_spi_remove, + .shutdown = panel_mipi_dbi_spi_shutdown, +}; +module_spi_driver(panel_mipi_dbi_spi_driver); + +MODULE_DESCRIPTION("MIPI DBI compatible display panel driver"); +MODULE_AUTHOR("Noralf Trønnes"); +MODULE_LICENSE("GPL");
On Fri, Feb 11, 2022 at 02:04:34PM +0100, Noralf Trønnes wrote:
Add a driver that will work with most MIPI DBI compatible SPI panels. This avoids adding a driver for every new MIPI DBI compatible controller that is to be used by Linux. The 'compatible' Device Tree property with a '.bin' suffix will be used to load a firmware file that contains the controller configuration.
Example (driver will load sainsmart18.bin):
display@0 { compatible = "sainsmart18", "panel-mipi-dbi-spi"; ... };
v3:
- Move properties to DT (Maxime)
- The MIPI DPI spec has optional support for DPI where the controller is configured over DBI. Rework the command functions so they can be moved to drm_mipi_dbi and shared with a future panel-mipi-dpi-spi driver
v2:
- Drop model property and use compatible instead (Rob)
- Add wiki entry in MAINTAINERS
Signed-off-by: Noralf Trønnes noralf@tronnes.org
Acked-by: Maxime Ripard maxime@cerno.tech
Maxime
dri-devel@lists.freedesktop.org