drm/panel: Add panel driver for Ilitek ILI9806E panel

The Ilitek ILI9806E driver is used in the Pimoroni HyperPixel4
and potentially other displays. Whilst it can support multiple
interfaces, this driver only accounts for SPI configuration and
DPI video data.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
This commit is contained in:
Dave Stevenson
2022-01-05 19:14:48 +00:00
committed by Dom Cobley
parent 5e64a6f033
commit 72a36feda1
3 changed files with 491 additions and 0 deletions

View File

@@ -266,6 +266,17 @@ config DRM_PANEL_ILITEK_ILI9806E
Say Y if you want to enable support for panels based on the Say Y if you want to enable support for panels based on the
Ilitek ILI9806E controller. Ilitek ILI9806E controller.
config DRM_PANEL_ILITEK_ILI9806E_SPI
tristate "Ilitek ILI9806E-based panels on SPI/DPI"
depends on OF && SPI
select DRM_KMS_HELPER
depends on BACKLIGHT_CLASS_DEVICE
select DRM_MIPI_DBI
help
Say Y if you want to enable support for panels based on the
Ilitek ILI9806e controller using DPI for video and SPI for
configuration.
config DRM_PANEL_ILITEK_ILI9881C config DRM_PANEL_ILITEK_ILI9881C
tristate "Ilitek ILI9881C-based panels" tristate "Ilitek ILI9881C-based panels"
depends on OF depends on OF

View File

@@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9805) += panel-ilitek-ili9805.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9805) += panel-ilitek-ili9805.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI) += panel-ilitek-ili9806e-spi.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9882T) += panel-ilitek-ili9882t.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9882T) += panel-ilitek-ili9882t.o
obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o

View File

@@ -0,0 +1,479 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Ilitek ILI9806E TFT LCD drm_panel driver.
*
* Copyright (C) 2022 Raspberry Pi Ltd
*
* Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
* Copyright (C) 2017 Free Electrons
*/
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <linux/bitops.h>
#include <linux/gpio/consumer.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <video/mipi_display.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
struct ili9806 {
struct drm_panel panel;
struct spi_device *spi;
struct gpio_desc *reset;
struct regulator *power;
u32 bus_format;
};
#define ILI9806_DATA BIT(8)
#define ILI9806_MAX_MSG_LEN 6
struct ili9806e_msg {
unsigned int len;
u16 msg[ILI9806_MAX_MSG_LEN];
};
#define ILI9806_SET_PAGE(page) \
{ \
.len = 6, \
.msg = { \
0xFF, \
ILI9806_DATA | 0xFF, \
ILI9806_DATA | 0x98, \
ILI9806_DATA | 0x06, \
ILI9806_DATA | 0x04, \
ILI9806_DATA | (page) \
}, \
}
#define ILI9806_SET_REG_PARAM(reg, data) \
{ \
.len = 2, \
.msg = { \
(reg), \
ILI9806_DATA | (data), \
}, \
}
#define ILI9806_SET_REG(reg) \
{ \
.len = 1, \
.msg = { (reg) }, \
}
static const struct ili9806e_msg panel_init[] = {
ILI9806_SET_PAGE(1),
/* interface mode
* SEPT_SDIO = 0 (spi interface transfer through SDA pin)
* SDO_STATUS = 1 (always output, but without output tri-state)
*/
ILI9806_SET_REG_PARAM(0x08, 0x10),
/* display control
* VSPL = 1 (vertical sync polarity)
* HSPL = 0 (horizontal sync polarity)
* DPL = 0 (PCLK polarity)
* EPL = 1 (data enable polarity)
*/
ILI9806_SET_REG_PARAM(0x21, 0x0d),
/* resolution control (0x02 = 480x800) */
ILI9806_SET_REG_PARAM(0x30, 0x02),
/* display inversion control (0x00 = column inversion) */
ILI9806_SET_REG_PARAM(0x31, 0x00),
/* power control
* EXB1T = 0 (internal charge pump)
* EXT_CPCK_SEL = 1 (pump clock control signal = output 2 x waveform)
* BT = 0 (DDVDH / DDVDL voltage = VCI x 2 / VCI x -2)
*/
ILI9806_SET_REG_PARAM(0x40, 0x10),
/* power control
* DDVDH_CLP = 5.6 (DDVDH clamp leve)
* DDVDL_CLP = -5.6 (DDVDL clamp leve)
*/
ILI9806_SET_REG_PARAM(0x41, 0x55),
/* power control
* VGH_CP = 2DDVDH - DDVDL (step up factor for VGH)
* VGL_CP = DDVDL + VCL - VCIP (step up factor for VGL)
*/
ILI9806_SET_REG_PARAM(0x42, 0x02),
/* power control
* VGH_CLPEN = 0 (disable VGH clamp level)
* VGH_CLP = 9 (15.0 VGH clamp level - but this is disabled so not used?)
*/
ILI9806_SET_REG_PARAM(0x43, 0x84),
/* power control
* VGL_CLPEN = 0 (disable VGL clamp level)
* VGL_CLP = 9 (-11.0 VGL clamp level - but this is disabled so not used?)
*/
ILI9806_SET_REG_PARAM(0x44, 0x84),
/* power control
* VREG1OUT voltage for positive gamma?
*/
ILI9806_SET_REG_PARAM(0x50, 0x78),
/* power control
* VREG2OUT voltage for negative gamma?
*/
ILI9806_SET_REG_PARAM(0x51, 0x78),
ILI9806_SET_REG_PARAM(0x52, 0x00),
ILI9806_SET_REG_PARAM(0x53, 0x77),
ILI9806_SET_REG_PARAM(0x57, 0x60),
ILI9806_SET_REG_PARAM(0x60, 0x07),
ILI9806_SET_REG_PARAM(0x61, 0x00),
ILI9806_SET_REG_PARAM(0x62, 0x08),
ILI9806_SET_REG_PARAM(0x63, 0x00),
ILI9806_SET_REG_PARAM(0xA0, 0x00),
ILI9806_SET_REG_PARAM(0xA1, 0x07),
ILI9806_SET_REG_PARAM(0xA2, 0x0C),
ILI9806_SET_REG_PARAM(0xA3, 0x0B),
ILI9806_SET_REG_PARAM(0xA4, 0x03),
ILI9806_SET_REG_PARAM(0xA5, 0x07),
ILI9806_SET_REG_PARAM(0xA6, 0x06),
ILI9806_SET_REG_PARAM(0xA7, 0x04),
ILI9806_SET_REG_PARAM(0xA8, 0x08),
ILI9806_SET_REG_PARAM(0xA9, 0x0C),
ILI9806_SET_REG_PARAM(0xAA, 0x13),
ILI9806_SET_REG_PARAM(0xAB, 0x06),
ILI9806_SET_REG_PARAM(0xAC, 0x0D),
ILI9806_SET_REG_PARAM(0xAD, 0x19),
ILI9806_SET_REG_PARAM(0xAE, 0x10),
ILI9806_SET_REG_PARAM(0xAF, 0x00),
/* negative gamma control
* set the gray scale voltage to adjust the gamma characteristics of the panel
*/
ILI9806_SET_REG_PARAM(0xC0, 0x00),
ILI9806_SET_REG_PARAM(0xC1, 0x07),
ILI9806_SET_REG_PARAM(0xC2, 0x0C),
ILI9806_SET_REG_PARAM(0xC3, 0x0B),
ILI9806_SET_REG_PARAM(0xC4, 0x03),
ILI9806_SET_REG_PARAM(0xC5, 0x07),
ILI9806_SET_REG_PARAM(0xC6, 0x07),
ILI9806_SET_REG_PARAM(0xC7, 0x04),
ILI9806_SET_REG_PARAM(0xC8, 0x08),
ILI9806_SET_REG_PARAM(0xC9, 0x0C),
ILI9806_SET_REG_PARAM(0xCA, 0x13),
ILI9806_SET_REG_PARAM(0xCB, 0x06),
ILI9806_SET_REG_PARAM(0xCC, 0x0D),
ILI9806_SET_REG_PARAM(0xCD, 0x18),
ILI9806_SET_REG_PARAM(0xCE, 0x10),
ILI9806_SET_REG_PARAM(0xCF, 0x00),
ILI9806_SET_PAGE(6),
ILI9806_SET_REG_PARAM(0x00, 0x20),
ILI9806_SET_REG_PARAM(0x01, 0x0A),
ILI9806_SET_REG_PARAM(0x02, 0x00),
ILI9806_SET_REG_PARAM(0x03, 0x00),
ILI9806_SET_REG_PARAM(0x04, 0x01),
ILI9806_SET_REG_PARAM(0x05, 0x01),
ILI9806_SET_REG_PARAM(0x06, 0x98),
ILI9806_SET_REG_PARAM(0x07, 0x06),
ILI9806_SET_REG_PARAM(0x08, 0x01),
ILI9806_SET_REG_PARAM(0x09, 0x80),
ILI9806_SET_REG_PARAM(0x0A, 0x00),
ILI9806_SET_REG_PARAM(0x0B, 0x00),
ILI9806_SET_REG_PARAM(0x0C, 0x01),
ILI9806_SET_REG_PARAM(0x0D, 0x01),
ILI9806_SET_REG_PARAM(0x0E, 0x00),
ILI9806_SET_REG_PARAM(0x0F, 0x00),
ILI9806_SET_REG_PARAM(0x10, 0xF0),
ILI9806_SET_REG_PARAM(0x11, 0xF4),
ILI9806_SET_REG_PARAM(0x12, 0x01),
ILI9806_SET_REG_PARAM(0x13, 0x00),
ILI9806_SET_REG_PARAM(0x14, 0x00),
ILI9806_SET_REG_PARAM(0x15, 0xC0),
ILI9806_SET_REG_PARAM(0x16, 0x08),
ILI9806_SET_REG_PARAM(0x17, 0x00),
ILI9806_SET_REG_PARAM(0x18, 0x00),
ILI9806_SET_REG_PARAM(0x19, 0x00),
ILI9806_SET_REG_PARAM(0x1A, 0x00),
ILI9806_SET_REG_PARAM(0x1B, 0x00),
ILI9806_SET_REG_PARAM(0x1C, 0x00),
ILI9806_SET_REG_PARAM(0x1D, 0x00),
ILI9806_SET_REG_PARAM(0x20, 0x01),
ILI9806_SET_REG_PARAM(0x21, 0x23),
ILI9806_SET_REG_PARAM(0x22, 0x45),
ILI9806_SET_REG_PARAM(0x23, 0x67),
ILI9806_SET_REG_PARAM(0x24, 0x01),
ILI9806_SET_REG_PARAM(0x25, 0x23),
ILI9806_SET_REG_PARAM(0x26, 0x45),
ILI9806_SET_REG_PARAM(0x27, 0x67),
ILI9806_SET_REG_PARAM(0x30, 0x11),
ILI9806_SET_REG_PARAM(0x31, 0x11),
ILI9806_SET_REG_PARAM(0x32, 0x00),
ILI9806_SET_REG_PARAM(0x33, 0xEE),
ILI9806_SET_REG_PARAM(0x34, 0xFF),
ILI9806_SET_REG_PARAM(0x35, 0xBB),
ILI9806_SET_REG_PARAM(0x36, 0xAA),
ILI9806_SET_REG_PARAM(0x37, 0xDD),
ILI9806_SET_REG_PARAM(0x38, 0xCC),
ILI9806_SET_REG_PARAM(0x39, 0x66),
ILI9806_SET_REG_PARAM(0x3A, 0x77),
ILI9806_SET_REG_PARAM(0x3B, 0x22),
ILI9806_SET_REG_PARAM(0x3C, 0x22),
ILI9806_SET_REG_PARAM(0x3D, 0x22),
ILI9806_SET_REG_PARAM(0x3E, 0x22),
ILI9806_SET_REG_PARAM(0x3F, 0x22),
ILI9806_SET_REG_PARAM(0x40, 0x22),
/* register doesn't exist on page 6? */
ILI9806_SET_REG_PARAM(0x52, 0x10),
/* doesn't make sense, not valid according to datasheet */
ILI9806_SET_REG_PARAM(0x53, 0x10),
/* doesn't make sense, not valid according to datasheet */
ILI9806_SET_REG_PARAM(0x54, 0x13),
ILI9806_SET_PAGE(7),
/* enable VREG */
ILI9806_SET_REG_PARAM(0x18, 0x1D),
/* enable VGL_REG */
ILI9806_SET_REG_PARAM(0x17, 0x22),
/* register doesn't exist on page 7? */
ILI9806_SET_REG_PARAM(0x02, 0x77),
/* register doesn't exist on page 7? */
ILI9806_SET_REG_PARAM(0x26, 0xB2),
/* register doesn't exist on page 7? */
ILI9806_SET_REG_PARAM(0xE1, 0x79),
ILI9806_SET_PAGE(0),
ILI9806_SET_REG_PARAM(MIPI_DCS_SET_PIXEL_FORMAT,
MIPI_DCS_PIXEL_FMT_18BIT << 4),
ILI9806_SET_REG_PARAM(MIPI_DCS_SET_TEAR_ON, 0x00),
ILI9806_SET_REG(MIPI_DCS_EXIT_SLEEP_MODE),
};
#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
static inline struct ili9806 *panel_to_ili9806(struct drm_panel *panel)
{
return container_of(panel, struct ili9806, panel);
}
static int ili9806_write_msg(struct ili9806 *ctx, const struct ili9806e_msg *msg)
{
struct spi_transfer xfer = { };
struct spi_message spi;
//u16 txbuf[] = { msg->, ILI9806_DATA | data };
spi_message_init(&spi);
xfer.tx_buf = msg->msg;
xfer.bits_per_word = 9;
xfer.len = sizeof(u16) * msg->len;
spi_message_add_tail(&xfer, &spi);
return spi_sync(ctx->spi, &spi);
}
static int ili9806e_write_msg_list(struct ili9806 *ctx,
const struct ili9806e_msg msgs[],
unsigned int num_msgs)
{
int ret, i;
for (i = 0; i < num_msgs; i++) {
ret = ili9806_write_msg(ctx, &msgs[i]);
if (ret)
break;
}
return ret;
}
static const struct drm_display_mode ili9806e_480x800_mode = {
.clock = 32000,
.hdisplay = 480,
.hsync_start = 480 + 10,
.hsync_end = 480 + 10 + 16,
.htotal = 480 + 10 + 16 + 59,
.vdisplay = 800,
.vsync_start = 800 + 15,
.vsync_end = 800 + 15 + 113,
.vtotal = 800 + 15 + 113 + 15,
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
};
static int ili9806_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct ili9806 *ctx = panel_to_ili9806(panel);
struct drm_display_mode *mode;
mode = drm_mode_duplicate(connector->dev, &ili9806e_480x800_mode);
if (!mode) {
dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
ili9806e_480x800_mode.hdisplay,
ili9806e_480x800_mode.vdisplay,
drm_mode_vrefresh(&ili9806e_480x800_mode));
return -ENOMEM;
}
drm_mode_set_name(mode);
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
connector->display_info.width_mm = 61;
connector->display_info.height_mm = 103;
drm_display_info_set_bus_formats(&connector->display_info,
&ctx->bus_format, 1);
connector->display_info.bus_flags =
DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
return 1;
}
static int ili9806_prepare(struct drm_panel *panel)
{
struct ili9806 *ctx = panel_to_ili9806(panel);
int ret;
ret = regulator_enable(ctx->power);
if (ret)
return ret;
ret = ili9806e_write_msg_list(ctx, panel_init, NUM_INIT_REGS);
return ret;
}
static int ili9806_enable(struct drm_panel *panel)
{
struct ili9806 *ctx = panel_to_ili9806(panel);
const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_ON);
int ret;
ret = ili9806_write_msg(ctx, &msg);
return ret;
}
static int ili9806_disable(struct drm_panel *panel)
{
struct ili9806 *ctx = panel_to_ili9806(panel);
const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_OFF);
int ret;
ret = ili9806_write_msg(ctx, &msg);
return ret;
}
static int ili9806_unprepare(struct drm_panel *panel)
{
struct ili9806 *ctx = panel_to_ili9806(panel);
const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_ENTER_SLEEP_MODE);
int ret;
ret = ili9806_write_msg(ctx, &msg);
return ret;
}
static const struct drm_panel_funcs ili9806_drm_funcs = {
.disable = ili9806_disable,
.enable = ili9806_enable,
.get_modes = ili9806_get_modes,
.prepare = ili9806_prepare,
.unprepare = ili9806_unprepare,
};
static const struct of_device_id ili9806_of_match[] = {
{ .compatible = "txw,txw397017s2",
.data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
}, {
.compatible = "pimoroni,hyperpixel4",
.data = (void *)MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
}, {
.compatible = "ilitek,ili9806e",
.data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, ili9806_of_match);
static int ili9806_probe(struct spi_device *spi)
{
const struct ili9806e_msg panel_reset[] = {
ILI9806_SET_PAGE(0),
ILI9806_SET_REG_PARAM(0x01, 0x00)
};
struct ili9806 *ctx;
int ret;
ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->bus_format = (u32)(uintptr_t)of_device_get_match_data(&spi->dev);
spi_set_drvdata(spi, ctx);
ctx->spi = spi;
drm_panel_init(&ctx->panel, &spi->dev, &ili9806_drm_funcs,
DRM_MODE_CONNECTOR_DPI);
ctx->power = devm_regulator_get(&spi->dev, "power");
if (IS_ERR(ctx->power))
return PTR_ERR(ctx->power);
ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ctx->reset)) {
dev_err(&spi->dev, "Couldn't get our reset line\n");
return PTR_ERR(ctx->reset);
}
/* Soft reset */
ili9806e_write_msg_list(ctx, panel_reset, ARRAY_SIZE(panel_reset));
msleep(200);
ret = drm_panel_of_backlight(&ctx->panel);
if (ret)
return ret;
drm_panel_add(&ctx->panel);
return 0;
}
static void ili9806_remove(struct spi_device *spi)
{
struct ili9806 *ctx = spi_get_drvdata(spi);
drm_panel_remove(&ctx->panel);
}
static const struct spi_device_id ili9806_ids[] = {
{ "txw397017s2", 0 },
{ "ili9806e", 0 },
{ "hyperpixel4", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, ili9806_ids);
static struct spi_driver ili9806_driver = {
.probe = ili9806_probe,
.remove = ili9806_remove,
.driver = {
.name = "ili9806e",
.of_match_table = ili9806_of_match,
},
.id_table = ili9806_ids,
};
module_spi_driver(ili9806_driver);
MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
MODULE_DESCRIPTION("ili9806 LCD panel driver");
MODULE_LICENSE("GPL v2");