On 03.02.22 15:34, Thorsten Scherer wrote: > Hi Ahmad, > > uups. I did not see v3 of this patch. Sorry for the noise. All good. Thanks for looking out :) > > On Wed, Feb 02, 2022 at 10:55:53AM +0100, Ahmad Fatoum wrote: >> 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 <a.fatoum@xxxxxxxxxxxxxx> >> --- >> 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 >> barebox@xxxxxxxxxxxxxxxxxxx >> http://lists.infradead.org/mailman/listinfo/barebox > > Best regards > Thorsten > > -- > Thorsten Scherer | Eckelmann AG | www.eckelmann.de | > -- 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 | _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox