Re: [PATCH 4/4] video: Add MIPI DBI compatible SPI driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 29.03.23 12:56, Philipp Zabel wrote:
> Port the panel-mipi-dbi driver from the Linux kernel. It works 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 Barebox. 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";
> ...
> };

This is a strange binding, but if Linux does it this way, I guess
we'll want to stay compatible...

> 
> Signed-off-by: Philipp Zabel <p.zabel@xxxxxxxxxxxxxx>

Reviewed-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx>

> ---
>  drivers/video/Kconfig          |  12 ++
>  drivers/video/Makefile         |   1 +
>  drivers/video/panel-mipi-dbi.c | 330 +++++++++++++++++++++++++++++++++
>  3 files changed, 343 insertions(+)
>  create mode 100644 drivers/video/panel-mipi-dbi.c
> 
> diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
> index 01bdaf47bfca..0e710bb25f79 100644
> --- a/drivers/video/Kconfig
> +++ b/drivers/video/Kconfig
> @@ -198,4 +198,16 @@ config DRIVER_VIDEO_PANEL_ILITEK_ILI9341
>  	  QVGA (240x320) RGB panels. support serial & parallel rgb
>  	  interface.
>  
> +config DRIVER_VIDEO_PANEL_MIPI_DBI
> +	tristate "DRM support for MIPI DBI compatible panels"
> +	depends on OFTREE && SPI
> +	select DRIVER_VIDEO_MIPI_DBI
> +	select FIRMWARE
> +	select VIDEO_VPL
> +	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. For more information see
> +          https://github.com/notro/panel-mipi-dbi/wiki.
> +
>  endif
> diff --git a/drivers/video/Makefile b/drivers/video/Makefile
> index d50d2d3ba562..7718b63f448a 100644
> --- a/drivers/video/Makefile
> +++ b/drivers/video/Makefile
> @@ -11,6 +11,7 @@ obj-$(CONFIG_DRIVER_VIDEO_TC358767) += tc358767.o
>  obj-$(CONFIG_DRIVER_VIDEO_SIMPLE_PANEL) += simple-panel.o
>  obj-$(CONFIG_DRIVER_VIDEO_MIPI_DBI) += mipi_dbi.o
>  obj-$(CONFIG_DRIVER_VIDEO_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
> +obj-$(CONFIG_DRIVER_VIDEO_PANEL_MIPI_DBI) += panel-mipi-dbi.o
>  
>  obj-$(CONFIG_DRIVER_VIDEO_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o
>  obj-$(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o
> diff --git a/drivers/video/panel-mipi-dbi.c b/drivers/video/panel-mipi-dbi.c
> new file mode 100644
> index 000000000000..ac6f585be32c
> --- /dev/null
> +++ b/drivers/video/panel-mipi-dbi.c
> @@ -0,0 +1,330 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DRM driver for MIPI DBI compatible display panels
> + *
> + * Copyright 2022 Noralf Trønnes
> + */
> +
> +#include <clock.h>
> +#include <common.h>
> +#include <fb.h>
> +#include <firmware.h>
> +#include <gpiod.h>
> +#include <linux/printk.h>
> +#include <of.h>
> +#include <regulator.h>
> +#include <spi/spi.h>
> +
> +#include <video/backlight.h>
> +#include <video/mipi_dbi.h>
> +#include <video/mipi_display.h>
> +
> +static const u8 panel_mipi_dbi_magic[15] = { 'M', 'I', 'P', 'I', ' ', 'D', 'B', 'I',
> +					     0, 0, 0, 0, 0, 0, 0 };
> +
> +/*
> + * The 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);
> +	}
> +
> +	dev_dbg(dev, "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)
> +			dev_dbg(dev, "sleep %ums\n", parameters[0]);
> +		else
> +			dev_dbg(dev, "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 = kzalloc(sizeof(*commands), GFP_KERNEL);
> +	if (!commands)
> +		return ERR_PTR(-ENOMEM);
> +
> +	commands->len = commands_len;
> +	commands->buf = kmemdup(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;
> +	char fw_name[40];
> +	int ret;
> +
> +	ret = of_property_read_string_index(dev->of_node, "compatible", 0, &compatible);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	snprintf(fw_name, sizeof(fw_name), "%s.bin", compatible);
> +	ret = request_firmware(&fw, fw_name, dev);
> +	if (ret) {
> +		dev_err(dev, "No config file found for compatible '%s' (error=%d)\n",
> +			compatible, ret);
> +
> +		return ERR_PTR(ret);
> +	}
> +
> +	commands = panel_mipi_dbi_check_commands(dev, fw);
> +	release_firmware(fw);
> +
> +	return commands;
> +}
> +
> +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)
> +			mdelay(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 fb_info *info)
> +{
> +	struct mipi_dbi_dev *dbidev = container_of(info, struct mipi_dbi_dev, info);
> +	struct mipi_dbi *dbi = &dbidev->dbi;
> +	int ret;
> +
> +	if (!info->mode) {
> +		dev_err(dbidev->dev, "No valid mode found\n");
> +		return;
> +	}
> +
> +	if (dbidev->backlight_node && !dbidev->backlight) {
> +		dbidev->backlight = of_backlight_find(dbidev->backlight_node);
> +		if (!dbidev->backlight)
> +			dev_err(dbidev->dev, "No backlight found\n");
> +	}
> +
> +	if (!dbidev->driver_private) {
> +		dbidev->driver_private = panel_mipi_dbi_commands_from_fw(dbidev->dev);
> +		if (IS_ERR(dbidev->driver_private)) {
> +			dbidev->driver_private = NULL;
> +			return;
> +		}
> +	}
> +
> +	ret = mipi_dbi_poweron_conditional_reset(dbidev);
> +	if (ret < 0)
> +		return;
> +	if (!ret)
> +		panel_mipi_dbi_commands_execute(dbi, dbidev->driver_private);
> +
> +	mipi_dbi_enable_flush(dbidev, info);
> +}
> +
> +
> +static struct fb_ops panel_mipi_dbi_ops = {
> +	.fb_enable = panel_mipi_dbi_enable,
> +	.fb_disable = mipi_dbi_fb_disable,
> +	.fb_flush = mipi_dbi_fb_flush,
> +};
> +
> +
> +static int panel_mipi_dbi_get_mode(struct mipi_dbi_dev *dbidev, struct fb_videomode *mode)
> +{
> +	struct device *dev = dbidev->dev;
> +	int ret;
> +
> +	ret = of_get_display_timing(dev->of_node, "panel-timing", mode);
> +	if (ret) {
> +		dev_err(dev, "%pOF: failed to get panel-timing (error=%d)\n", dev->of_node, ret);
> +		return ret;
> +	}
> +
> +	/*
> +	 * Make sure width and height are set and that only back porch and
> +	 * pixelclock are set in the other timing values. Also check that
> +	 * width and height don't exceed the 16-bit value specified by MIPI DCS.
> +	 */
> +	if (!mode->xres || !mode->yres || mode->display_flags ||
> +	    mode->right_margin || mode->hsync_len || (mode->left_margin + mode->xres) > 0xffff ||
> +	    mode->lower_margin || mode->vsync_len || (mode->upper_margin + mode->yres) > 0xffff) {
> +		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 (!mode->pixclock) {
> +		mode->pixclock =
> +			(mode->left_margin + mode->xres + mode->right_margin + mode->hsync_len) *
> +			(mode->upper_margin + mode->yres + mode->lower_margin + mode->vsync_len) *
> +			60 / 1000;
> +	}
> +
> +	return 0;
> +}
> +
> +static int panel_mipi_dbi_spi_probe(struct device *dev)
> +{
> +	struct mipi_dbi_dev *dbidev;
> +	struct spi_device *spi = to_spi_device(dev);
> +	struct mipi_dbi *dbi;
> +	struct fb_info *info;
> +	int dc;
> +	int ret;
> +
> +	dbidev = kzalloc(sizeof(*dbidev), GFP_KERNEL);
> +	if (!dbidev)
> +		return -ENOMEM;
> +
> +	dbidev->dev = dev;
> +	dbi = &dbidev->dbi;
> +	info = &dbidev->info;
> +
> +	ret = panel_mipi_dbi_get_mode(dbidev, &dbidev->mode);
> +	if (ret)
> +		return ret;
> +
> +	dbidev->regulator = 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->io_regulator = regulator_get(dev, "io");
> +	if (IS_ERR(dbidev->io_regulator))
> +		return dev_err_probe(dev, PTR_ERR(dbidev->io_regulator),
> +				     "Failed to get regulator 'io'\n");
> +
> +	dbidev->backlight_node = of_parse_phandle(dev->of_node, "backlight", 0);
> +
> +	dbi->reset = gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> +	if (dbi->reset < 0 && dbi->reset != -ENOENT)
> +		return dev_err_probe(dev, dbi->reset, "Failed to get GPIO 'reset'\n");
> +
> +	dc = gpiod_get(dev, "dc", GPIOD_OUT_LOW);
> +	if (dc < 0 && dc != -ENOENT)
> +		return dev_err_probe(dev, dc, "Failed to get GPIO 'dc'\n");
> +
> +	ret = mipi_dbi_spi_init(spi, dbi, dc);
> +	if (ret)
> +		return ret;
> +
> +	ret = mipi_dbi_dev_init(dbidev, &panel_mipi_dbi_ops, &dbidev->mode);
> +	if (ret)
> +		return ret;
> +
> +	ret = register_framebuffer(info);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Failed to register framebuffer\n");
> +
> +	return 0;
> +}
> +
> +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 struct driver panel_mipi_dbi_spi_driver = {
> +	.name = "panel-mipi-dbi-spi",
> +	.probe = panel_mipi_dbi_spi_probe,
> +	.of_compatible = DRV_OF_COMPAT(panel_mipi_dbi_spi_of_match),
> +};
> +device_spi_driver(panel_mipi_dbi_spi_driver);
> +
> +MODULE_DESCRIPTION("MIPI DBI compatible display panel driver");
> +MODULE_AUTHOR("Noralf Trønnes");
> +MODULE_LICENSE("GPL");

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |





[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux