Import the Linux v5.15 state of the driver to allow easy porting of
MIPI-DBI displays like the Ilitek 9431 added in a follow-up commit.

Signed-off-by: Ahmad Fatoum <[email protected]>
---
 commands/Kconfig         |  23 ++
 commands/Makefile        |   1 +
 drivers/video/Kconfig    |   3 +
 drivers/video/Makefile   |   1 +
 drivers/video/mipi_dbi.c | 467 +++++++++++++++++++++++++++++++++++++++
 include/spi/spi.h        |  20 ++
 6 files changed, 515 insertions(+)
 create mode 100644 drivers/video/mipi_dbi.c

diff --git a/commands/Kconfig b/commands/Kconfig
index ba8ca5cdebce..af60f7be1587 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -1969,6 +1969,29 @@ config CMD_SPI
                  -w BIT        bits per word (default 8)
                  -v            verbose
 
+config CMD_MIPI_DBI
+       bool
+       depends on DRIVER_VIDEO_MIPI_DBI && SPI
+       select PRINTF_HEXSTR
+       prompt "mipi_dbi command"
+       help
+         write/read from MIPI DBI SPI device
+
+         Usage: mipi_dbi [-wld] [REG] [DATA...]
+
+         Options:
+                 -l            list all MIPI DBI devices
+                 -d DEVICE     select specific device (default is first 
registered)
+                 -w            issue write command
+
+BAREBOX_CMD_START(mipi_dbi)
+       .cmd            = do_mipi_dbi,
+       BAREBOX_CMD_DESC("write/read from MIPI DBI SPI device")
+       BAREBOX_CMD_OPTS("[-wld] [REG] [DATA...]")
+       BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
+       BAREBOX_CMD_HELP(cmd_mipi_dbi_help)
+BAREBOX_CMD_END
+
 config CMD_LED_TRIGGER
        bool
        depends on LED_TRIGGERS
diff --git a/commands/Makefile b/commands/Makefile
index db78d0b877f6..fffb6d979e82 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_CMD_GPIO)                += gpio.o
 obj-$(CONFIG_CMD_UNCOMPRESS)   += uncompress.o
 obj-$(CONFIG_CMD_I2C)          += i2c.o
 obj-$(CONFIG_CMD_SPI)          += spi.o
+obj-$(CONFIG_CMD_MIPI_DBI)     += mipi_dbi.o
 obj-$(CONFIG_CMD_UBI)          += ubi.o
 obj-$(CONFIG_CMD_UBIFORMAT)    += ubiformat.o
 obj-$(CONFIG_CMD_MENU)         += menu.o
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 1b8672fdea82..70d1d809536b 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -129,6 +129,9 @@ config DRIVER_VIDEO_EDID
          This enabled support for reading and parsing EDID data from an 
attached
          monitor.
 
+config DRIVER_VIDEO_MIPI_DBI
+       bool
+
 config DRIVER_VIDEO_BACKLIGHT
        bool "Add backlight support"
        help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 7f4429278987..a7b70d82072a 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_VIDEO_VPL) += vpl.o
 obj-$(CONFIG_DRIVER_VIDEO_MTL017) += mtl017.o
 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_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/mipi_dbi.c b/drivers/video/mipi_dbi.c
new file mode 100644
index 000000000000..48b1110f72ab
--- /dev/null
+++ b/drivers/video/mipi_dbi.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ */
+
+#define pr_fmt(fmt) "mipi-dbi: " fmt
+
+#include <common.h>
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+#include <gpiod.h>
+#include <regulator.h>
+#include <spi/spi.h>
+#include <video/mipi_dbi.h>
+
+#include <video/vpl.h>
+#include <video/mipi_display.h>
+#include <video/fourcc.h>
+
+#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
+
+#define DCS_POWER_MODE_DISPLAY                 BIT(2)
+#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE     BIT(3)
+#define DCS_POWER_MODE_SLEEP_MODE              BIT(4)
+#define DCS_POWER_MODE_PARTIAL_MODE            BIT(5)
+#define DCS_POWER_MODE_IDLE_MODE               BIT(6)
+#define DCS_POWER_MODE_RESERVED_MASK           (BIT(0) | BIT(1) | BIT(7))
+
+LIST_HEAD(mipi_dbi_list);
+EXPORT_SYMBOL(mipi_dbi_list);
+
+/**
+ * DOC: overview
+ *
+ * This library provides helpers for MIPI Display Bus Interface (DBI)
+ * compatible display controllers.
+ *
+ * Many controllers for tiny lcd displays are MIPI compliant and can use this
+ * library. If a controller uses registers 0x2A and 0x2B to set the area to
+ * update and uses register 0x2C to write to frame memory, it is most likely
+ * MIPI compliant.
+ *
+ * Only MIPI Type 1 displays are supported since a full frame memory is needed.
+ *
+ * There are 3 MIPI DBI implementation types:
+ *
+ * A. Motorola 6800 type parallel bus
+ *
+ * B. Intel 8080 type parallel bus
+ *
+ * C. SPI type with 3 options:
+ *
+ *    1. 9-bit with the Data/Command signal as the ninth bit
+ *    2. Same as above except it's sent as 16 bits
+ *    3. 8-bit with the Data/Command signal as a separate D/CX pin
+ *
+ * Currently barebox mipi_dbi only supports Type C option 3 with
+ * mipi_dbi_spi_init().
+ */
+
+#define MIPI_DBI_DEBUG_COMMAND(cmd, data, len) \
+({ \
+       if (!len) \
+               pr_debug("cmd=%02x\n", cmd); \
+       else if (len <= 32) \
+               pr_debug("cmd=%02x, par=%*ph\n", cmd, (int)len, data);\
+       else \
+               pr_debug("cmd=%02x, len=%zu\n", cmd, len); \
+})
+
+static const u8 mipi_dbi_dcs_read_commands[] = {
+       MIPI_DCS_GET_DISPLAY_ID,
+       MIPI_DCS_GET_RED_CHANNEL,
+       MIPI_DCS_GET_GREEN_CHANNEL,
+       MIPI_DCS_GET_BLUE_CHANNEL,
+       MIPI_DCS_GET_DISPLAY_STATUS,
+       MIPI_DCS_GET_POWER_MODE,
+       MIPI_DCS_GET_ADDRESS_MODE,
+       MIPI_DCS_GET_PIXEL_FORMAT,
+       MIPI_DCS_GET_DISPLAY_MODE,
+       MIPI_DCS_GET_SIGNAL_MODE,
+       MIPI_DCS_GET_DIAGNOSTIC_RESULT,
+       MIPI_DCS_READ_MEMORY_START,
+       MIPI_DCS_READ_MEMORY_CONTINUE,
+       MIPI_DCS_GET_SCANLINE,
+       MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+       MIPI_DCS_GET_CONTROL_DISPLAY,
+       MIPI_DCS_GET_POWER_SAVE,
+       MIPI_DCS_GET_CABC_MIN_BRIGHTNESS,
+       MIPI_DCS_READ_DDB_START,
+       MIPI_DCS_READ_DDB_CONTINUE,
+       0, /* sentinel */
+};
+
+bool mipi_dbi_command_is_read(struct mipi_dbi *dbi, u8 cmd)
+{
+       unsigned int i;
+
+       if (!dbi->read_commands)
+               return false;
+
+       for (i = 0; i < 0xff; i++) {
+               if (!dbi->read_commands[i])
+                       return false;
+               if (cmd == dbi->read_commands[i])
+                       return true;
+       }
+
+       return false;
+}
+
+int mipi_dbi_command_read_len(int cmd)
+{
+       switch (cmd) {
+       case MIPI_DCS_READ_MEMORY_START:
+       case MIPI_DCS_READ_MEMORY_CONTINUE:
+               return 2;
+       case MIPI_DCS_GET_DISPLAY_ID:
+               return 3;
+       case MIPI_DCS_GET_DISPLAY_STATUS:
+               return 4;
+       default:
+               return 1;
+       }
+}
+
+/**
+ * mipi_dbi_command_read - MIPI DCS read command
+ * @dbi: MIPI DBI structure
+ * @cmd: Command
+ * @val: Value read
+ *
+ * Send MIPI DCS read command to the controller.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_read(struct mipi_dbi *dbi, u8 cmd, u8 *val)
+{
+       if (!dbi->read_commands)
+               return -EACCES;
+
+       if (!mipi_dbi_command_is_read(dbi, cmd))
+               return -EINVAL;
+
+       return mipi_dbi_command_buf(dbi, cmd, val, 1);
+}
+EXPORT_SYMBOL(mipi_dbi_command_read);
+
+/**
+ * mipi_dbi_command_buf - MIPI DCS command with parameter(s) in an array
+ * @dbi: MIPI DBI structure
+ * @cmd: Command
+ * @data: Parameter buffer
+ * @len: Buffer length
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_buf(struct mipi_dbi *dbi, u8 cmd, u8 *data, size_t len)
+{
+       u8 *cmdbuf;
+       int ret;
+
+       /* SPI requires dma-safe buffers */
+       cmdbuf = kmemdup(&cmd, 1, GFP_KERNEL);
+       if (!cmdbuf)
+               return -ENOMEM;
+
+       ret = dbi->command(dbi, cmdbuf, data, len);
+
+       kfree(cmdbuf);
+
+       return ret;
+}
+EXPORT_SYMBOL(mipi_dbi_command_buf);
+
+/* This should only be used by mipi_dbi_command() */
+int mipi_dbi_command_stackbuf(struct mipi_dbi *dbi, u8 cmd, const u8 *data,
+                             size_t len)
+{
+       u8 *buf;
+       int ret;
+
+       buf = kmemdup(data, len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = mipi_dbi_command_buf(dbi, cmd, buf, len);
+
+       kfree(buf);
+
+       return ret;
+}
+EXPORT_SYMBOL(mipi_dbi_command_stackbuf);
+
+/**
+ * mipi_dbi_hw_reset - Hardware reset of controller
+ * @dbi: MIPI DBI structure
+ *
+ * Reset controller if the &mipi_dbi->reset gpio is set.
+ */
+void mipi_dbi_hw_reset(struct mipi_dbi *dbi)
+{
+       if (!gpio_is_valid(dbi->reset))
+               return;
+
+       gpiod_set_value(dbi->reset, 0);
+       udelay(20);
+       gpiod_set_value(dbi->reset, 1);
+       mdelay(120);
+}
+EXPORT_SYMBOL(mipi_dbi_hw_reset);
+
+/**
+ * mipi_dbi_display_is_on - Check if display is on
+ * @dbi: MIPI DBI structure
+ *
+ * This function checks the Power Mode register (if readable) to see if
+ * display output is turned on. This can be used to see if the bootloader
+ * has already turned on the display avoiding flicker when the pipeline is
+ * enabled.
+ *
+ * Returns:
+ * true if the display can be verified to be on, false otherwise.
+ */
+bool mipi_dbi_display_is_on(struct mipi_dbi *dbi)
+{
+       u8 val;
+
+       if (mipi_dbi_command_read(dbi, MIPI_DCS_GET_POWER_MODE, &val))
+               return false;
+
+       val &= ~DCS_POWER_MODE_RESERVED_MASK;
+
+       /* The poweron/reset value is 08h DCS_POWER_MODE_DISPLAY_NORMAL_MODE */
+       if (val != (DCS_POWER_MODE_DISPLAY |
+           DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
+               return false;
+
+       pr_debug("Display is ON\n");
+
+       return true;
+}
+EXPORT_SYMBOL(mipi_dbi_display_is_on);
+
+#if IS_ENABLED(CONFIG_SPI)
+
+/**
+ * mipi_dbi_spi_cmd_max_speed - get the maximum SPI bus speed
+ * @spi: SPI device
+ * @len: The transfer buffer length.
+ *
+ * Many controllers have a max speed of 10MHz, but can be pushed way beyond
+ * that. Increase reliability by running pixel data at max speed and the rest
+ * at 10MHz, preventing transfer glitches from messing up the init settings.
+ */
+u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len)
+{
+       if (len > 64)
+               return 0; /* use default */
+
+       return min_t(u32, 10000000, spi->max_speed_hz);
+}
+EXPORT_SYMBOL(mipi_dbi_spi_cmd_max_speed);
+
+static bool mipi_dbi_machine_little_endian(void)
+{
+#if defined(__LITTLE_ENDIAN)
+       return true;
+#else
+       return false;
+#endif
+}
+
+/* MIPI DBI Type C Option 3 */
+
+static int mipi_dbi_typec3_command_read(struct mipi_dbi *dbi, u8 *cmd,
+                                       u8 *data, size_t len)
+{
+       struct spi_device *spi = dbi->spi;
+       u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED,
+                            spi->max_speed_hz / 2);
+       struct spi_transfer tr[2] = {
+               {
+                       .speed_hz = speed_hz,
+                       .tx_buf = cmd,
+                       .len = 1,
+               }, {
+                       .speed_hz = speed_hz,
+                       .len = len,
+               },
+       };
+       struct spi_message m;
+       u8 *buf;
+       int ret;
+
+       if (!len)
+               return -EINVAL;
+
+       /*
+        * Support non-standard 24-bit and 32-bit Nokia read commands which
+        * start with a dummy clock, so we need to read an extra byte.
+        */
+       if (*cmd == MIPI_DCS_GET_DISPLAY_ID ||
+           *cmd == MIPI_DCS_GET_DISPLAY_STATUS) {
+               if (!(len == 3 || len == 4))
+                       return -EINVAL;
+
+               tr[1].len = len + 1;
+       }
+
+       buf = kmalloc(tr[1].len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       tr[1].rx_buf = buf;
+       gpiod_set_value(dbi->dc, 0);
+
+       spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr));
+       ret = spi_sync(spi, &m);
+       if (ret)
+               goto err_free;
+
+       if (tr[1].len == len) {
+               memcpy(data, buf, len);
+       } else {
+               unsigned int i;
+
+               for (i = 0; i < len; i++)
+                       data[i] = (buf[i] << 1) | (buf[i + 1] >> 7);
+       }
+
+       MIPI_DBI_DEBUG_COMMAND(*cmd, data, len);
+
+err_free:
+       kfree(buf);
+
+       return ret;
+}
+
+static int mipi_dbi_typec3_command(struct mipi_dbi *dbi, u8 *cmd,
+                                  u8 *par, size_t num)
+{
+       struct spi_device *spi = dbi->spi;
+       unsigned int bpw = 8;
+       u32 speed_hz;
+       int ret;
+
+       if (mipi_dbi_command_is_read(dbi, *cmd))
+               return mipi_dbi_typec3_command_read(dbi, cmd, par, num);
+
+       MIPI_DBI_DEBUG_COMMAND(*cmd, par, num);
+
+       gpiod_set_value(dbi->dc, 0);
+       speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
+       ret = mipi_dbi_spi_transfer(spi, speed_hz, 8, cmd, 1);
+       if (ret || !num)
+               return ret;
+
+       if (*cmd == MIPI_DCS_WRITE_MEMORY_START && !dbi->swap_bytes)
+               bpw = 16;
+
+       gpiod_set_value(dbi->dc, 1);
+       speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
+
+       return mipi_dbi_spi_transfer(spi, speed_hz, bpw, par, num);
+}
+
+/**
+ * mipi_dbi_spi_init - Initialize MIPI DBI SPI interface
+ * @spi: SPI device
+ * @dbi: MIPI DBI structure to initialize
+ * @dc: D/C gpio
+ *
+ * This function sets &mipi_dbi->command, enables &mipi_dbi->read_commands for 
the
+ * usual read commands. It should be followed by a call to mipi_dbi_dev_init() 
or
+ * a driver-specific init.
+ *
+ * Type C Option 3 interface is assumed, Type C Option 1 is not yet supported,
+ * because barebox has no generic way yet to require a 9-bit SPI transfer
+ *
+ * If the SPI master driver doesn't support the necessary bits per word,
+ * the following transformation is used:
+ *
+ * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
+ * - 16-bit: if big endian send as 8-bit, if little endian swap bytes
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *dbi,
+                     int dc)
+{
+       struct device_d *dev = &spi->dev;
+
+       dbi->spi = spi;
+       dbi->read_commands = mipi_dbi_dcs_read_commands;
+
+       if (!gpio_is_valid(dc)) {
+               dev_dbg(dev, "MIPI DBI Type-C 1 unsupported\n");
+               return -ENOSYS;
+       }
+
+       dbi->command = mipi_dbi_typec3_command;
+       dbi->dc = dc;
+       // TODO: can we just force 16 bit?
+       if (mipi_dbi_machine_little_endian() && spi->bits_per_word != 16)
+               dbi->swap_bytes = true;
+
+       dev_dbg(dev, "SPI speed: %uMHz\n", spi->max_speed_hz / 1000000);
+
+       list_add(&dbi->list, &mipi_dbi_list);
+       return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_spi_init);
+
+/**
+ * mipi_dbi_spi_transfer - SPI transfer helper
+ * @spi: SPI device
+ * @speed_hz: Override speed (optional)
+ * @bpw: Bits per word
+ * @buf: Buffer to transfer
+ * @len: Buffer length
+ *
+ * This SPI transfer helper breaks up the transfer of @buf into chunks which
+ * the SPI controller driver can handle.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_spi_transfer(struct spi_device *spi, u32 speed_hz,
+                         u8 bpw, const void *buf, size_t len)
+{
+       size_t max_chunk = spi_max_transfer_size(spi);
+       struct spi_transfer tr = {
+               .bits_per_word = bpw,
+               .speed_hz = speed_hz,
+       };
+       struct spi_message m;
+       size_t chunk;
+       int ret;
+
+       spi_message_init_with_transfers(&m, &tr, 1);
+
+       while (len) {
+               chunk = min(len, max_chunk);
+
+               tr.tx_buf = buf;
+               tr.len = chunk;
+               buf += chunk;
+               len -= chunk;
+
+               ret = spi_sync(spi, &m);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_spi_transfer);
+
+#endif /* CONFIG_SPI */
+
+MODULE_LICENSE("GPL");
diff --git a/include/spi/spi.h b/include/spi/spi.h
index c5ad6bd39ff9..d133e0e21265 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -409,6 +409,26 @@ spi_message_add_tail(struct spi_transfer *t, struct 
spi_message *m)
        list_add_tail(&t->transfer_list, &m->transfers);
 }
 
+/**
+ * spi_message_init_with_transfers - Initialize spi_message and append 
transfers
+ * @m: spi_message to be initialized
+ * @xfers: An array of spi transfers
+ * @num_xfers: Number of items in the xfer array
+ *
+ * This function initializes the given spi_message and adds each spi_transfer 
in
+ * the given array to the message.
+ */
+static inline void
+spi_message_init_with_transfers(struct spi_message *m,
+struct spi_transfer *xfers, unsigned int num_xfers)
+{
+       unsigned int i;
+
+       spi_message_init(m);
+       for (i = 0; i < num_xfers; ++i)
+               spi_message_add_tail(&xfers[i], m);
+}
+
 static inline void
 spi_transfer_del(struct spi_transfer *t)
 {
-- 
2.30.2


_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to