On Mon, Mar 4, 2019 at 6:51 AM Josef Lusticky <josef@xxxxxxxxxxx> wrote: > S-o-b and commit msg? > --- > MAINTAINERS | 6 + > drivers/gpu/drm/panel/Kconfig | 7 + > drivers/gpu/drm/panel/Makefile | 1 + > drivers/gpu/drm/panel/panel-ilitek-ili9341.c | 320 +++++++++++++++++++ Would be nice to share some code with the existing driver. > 4 files changed, 334 insertions(+) > create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili9341.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a4a4bf563f94..d2e03c5ad04d 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -4821,6 +4821,12 @@ S: Maintained > F: drivers/gpu/drm/tinydrm/ili9225.c > F: Documentation/devicetree/bindings/display/ilitek,ili9225.txt > > +DRM DRIVER FOR ILITEK ILI9341 PANELS > +M: Josef Lusticky <josef@xxxxxxxxxxx> > +S: Maintained > +F: drivers/gpu/drm/panel/panel-ilitek-ili9341.c > +F: Documentation/devicetree/bindings/display/panel/ilitek,ili9341.txt > + > DRM DRIVER FOR HX8357D PANELS > M: Eric Anholt <eric@xxxxxxxxxx> > T: git git://anongit.freedesktop.org/drm/drm-misc > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig > index f53f817356db..a59cfff614c0 100644 > --- a/drivers/gpu/drm/panel/Kconfig > +++ b/drivers/gpu/drm/panel/Kconfig > @@ -46,6 +46,13 @@ config DRM_PANEL_ILITEK_IL9322 > Say Y here if you want to enable support for Ilitek IL9322 > QVGA (320x240) RGB, YUV and ITU-T BT.656 panels. > > +config DRM_PANEL_ILITEK_IL9341 > + tristate "Ilitek ILI9341 240x320 panels" > + depends on OF && SPI > + help > + Say Y here if you want to enable support for Ilitek IL9341 > + QVGA (240x320) RGB panel. > + > config DRM_PANEL_ILITEK_ILI9881C > tristate "Ilitek ILI9881C-based panels" > depends on OF > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile > index 7834947a53b0..1ce3ff8d6204 100644 > --- a/drivers/gpu/drm/panel/Makefile > +++ b/drivers/gpu/drm/panel/Makefile > @@ -3,6 +3,7 @@ obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o > obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o > obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o > obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o > +obj-$(CONFIG_DRM_PANEL_ILITEK_IL9341) += panel-ilitek-ili9341.o > obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o > obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o > obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o > diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9341.c b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c > new file mode 100644 > index 000000000000..51ed03140f8d > --- /dev/null > +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c > @@ -0,0 +1,320 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Ilitek ILI9341 drm_panel driver > + * 240RGBx320 dots resolution TFT LCD display > + * > + * This driver support the following panel configurations: > + * - 18-bit parallel RGB interface > + * - 8-bit SPI with Data/Command GPIO > + * > + * Copyright (C) 2019 Josef Lusticky <josef@xxxxxxxxxxx> > + * > + */ > + > +#include <drm/drm_modes.h> > +#include <drm/drm_panel.h> > +#include <drm/drm_print.h> > + > +#include <linux/backlight.h> > +#include <linux/delay.h> > +#include <linux/gpio/consumer.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/spi/spi.h> > + > +#include <video/mipi_display.h> > + > +/* ILI9341 extended register set */ > +#define ILI9341_IFMODE 0xB0 // clock polarity > +#define ILI9341_IFCTL 0xF6 // interface conrol > +#define ILI9341_PGAMCTRL 0xE0 // positive gamma control > +#define ILI9341_NGAMCTRL 0xE1 // negative gamma control > + > +#define ILI9341_MADCTL_MV BIT(5) > +#define ILI9341_MADCTL_MX BIT(6) > +#define ILI9341_MADCTL_MY BIT(7) > + > +/** > + * struct ili9341_config - the display specific configuration > + * @width_mm: physical panel width [mm] > + * @height_mm: physical panel height [mm] > + */ > +struct ili9341_config { > + u32 width_mm; > + u32 height_mm; > +}; > + > +struct ili9341 { > + const struct ili9341_config *conf; > + struct drm_panel panel; > + struct spi_device *spi; > + struct backlight_device *backlight; > + struct gpio_desc *dc_gpio; > + struct gpio_desc *reset_gpio; > +}; > + > +static inline struct ili9341 *panel_to_ili9341(struct drm_panel *panel) > +{ > + return container_of(panel, struct ili9341, panel); > +} > + > +static int ili9341_spi_write_command(struct ili9341 *ili, u8 cmd) > +{ > + struct spi_transfer xfer = { > + .tx_buf = &cmd, > + .bits_per_word = 8, > + .len = 1 > + }; > + struct spi_message msg; > + spi_message_init(&msg); > + spi_message_add_tail(&xfer, &msg); > + > + gpiod_set_value(ili->dc_gpio, 0); > + > + return spi_sync(ili->spi, &msg); Can't you use existing mipi_dbi_* functions instead of rolling your own? > +} > + > +static int ili9341_spi_write_data(struct ili9341 *ili, u8 *data, size_t size) > +{ > + struct spi_transfer xfer = { > + .tx_buf = data, > + .bits_per_word = 8, > + .len = size > + }; > + > + struct spi_message msg; > + spi_message_init(&msg); > + spi_message_add_tail(&xfer, &msg); > + > + gpiod_set_value(ili->dc_gpio, 1); > + > + return spi_sync(ili->spi, &msg); > +} > + > +#define ili9341_spi_write(ili, cmd, data...) \ > +({ \ > + u8 d[] = { data }; \ > + ili9341_spi_write_command(ili, cmd); \ > + if (ARRAY_SIZE(d) > 0) \ > + ili9341_spi_write_data(ili, d, ARRAY_SIZE(d)); \ > +}) > + > +static int ili9341_deinit(struct drm_panel *panel, struct ili9341 *ili) > +{ > + ili9341_spi_write(ili, MIPI_DCS_SET_DISPLAY_OFF); > + ili9341_spi_write(ili, MIPI_DCS_ENTER_SLEEP_MODE); > + msleep(5); > + return 0; > +} > + > +static int ili9341_init(struct drm_panel *panel, struct ili9341 *ili) > +{ > + /* HW / SW Reset display and wait */ > + if (ili->reset_gpio) { > + gpiod_set_value(ili->reset_gpio, 0); > + usleep_range(20, 1000); > + gpiod_set_value(ili->reset_gpio, 1); > + } else { > + ili9341_spi_write(ili, MIPI_DCS_SOFT_RESET); > + } > + msleep(120); > + > + /* Polarity */ > + ili9341_spi_write(ili, ILI9341_IFMODE, 0xC0); > + > + /* Interface control */ > + ili9341_spi_write(ili, ILI9341_IFCTL, 0x09, 0x01, 0x26); > + > + /* Pixel format */ > + ili9341_spi_write(ili, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_18BIT << 4); > + > + /* Gamma */ > + ili9341_spi_write(ili, MIPI_DCS_SET_GAMMA_CURVE, 0x01); > + ili9341_spi_write(ili, ILI9341_PGAMCTRL, > + 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, > + 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00); > + ili9341_spi_write(ili, ILI9341_NGAMCTRL, > + 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, > + 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f); > + > + /* Rotation */ > + ili9341_spi_write(ili, MIPI_DCS_SET_ADDRESS_MODE, ILI9341_MADCTL_MX); > + > + /* Exit sleep mode */ > + ili9341_spi_write(ili, MIPI_DCS_EXIT_SLEEP_MODE); > + msleep(120); > + > + ili9341_spi_write(ili, MIPI_DCS_SET_DISPLAY_ON); > + > + return 0; > +} > + > +static int ili9341_unprepare(struct drm_panel *panel) > +{ > + struct ili9341 *ili = panel_to_ili9341(panel); > + return ili9341_deinit(panel, ili); > +} > + > +static int ili9341_prepare(struct drm_panel *panel) > +{ > + struct ili9341 *ili = panel_to_ili9341(panel); > + int ret; > + > + ret = ili9341_init(panel, ili); > + if (ret < 0) > + ili9341_unprepare(panel); > + return ret; > +} > + > +static int ili9341_enable(struct drm_panel *panel) > +{ > + struct ili9341 *ili = panel_to_ili9341(panel); > + return backlight_enable(ili->backlight); > +} > + > +static int ili9341_disable(struct drm_panel *panel) > +{ > + struct ili9341 *ili = panel_to_ili9341(panel); > + return backlight_disable(ili->backlight); > +} > + > +static const struct drm_display_mode prgb_240x320_mode = { > + .clock = 6350, > + > + .hdisplay = 240, > + .hsync_start = 240 + 10, > + .hsync_end = 240 + 10 + 10, > + .htotal = 240 + 10 + 10 + 20, > + > + .vdisplay = 320, > + .vsync_start = 320 + 4, > + .vsync_end = 320 + 4 + 2, > + .vtotal = 320 + 4 + 2 + 2, > + > + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, > + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED > +}; > + > +static int ili9341_get_modes(struct drm_panel *panel) > +{ > + struct drm_connector *connector = panel->connector; > + struct ili9341 *ili = panel_to_ili9341(panel); > + struct drm_display_mode *mode; > + > + mode = drm_mode_duplicate(panel->drm, &prgb_240x320_mode); > + drm_mode_set_name(mode); > + > + mode->width_mm = ili->conf->width_mm; > + mode->height_mm = ili->conf->height_mm; > + > + strncpy(connector->display_info.name, "ILI9341 TFT LCD driver\0", > + DRM_DISPLAY_INFO_LEN); > + connector->display_info.width_mm = mode->width_mm; > + connector->display_info.height_mm = mode->height_mm; > + connector->display_info.bus_flags |= DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE | > + DRM_BUS_FLAG_SYNC_NEGEDGE; > + > + drm_mode_probed_add(connector, mode); > + > + return 1; /* Number of modes */ > +} > + > +static const struct drm_panel_funcs ili9341_drm_funcs = { > + .disable = ili9341_disable, > + .unprepare = ili9341_unprepare, > + .prepare = ili9341_prepare, > + .enable = ili9341_enable, > + .get_modes = ili9341_get_modes, > +}; > + > +static int ili9341_probe(struct spi_device *spi) > +{ > + struct device *dev = &spi->dev; > + struct ili9341 *ili; > + int ret; > + > + ili = devm_kzalloc(dev, sizeof(struct ili9341), GFP_KERNEL); > + if (!ili) > + return -ENOMEM; > + > + spi_set_drvdata(spi, ili); > + > + ili->spi = spi; > + > + /* > + * Every new incarnation of this display must have a unique > + * data entry for the system in this driver. > + */ > + ili->conf = of_device_get_match_data(dev); > + if (!ili->conf) { > + DRM_DEV_ERROR(dev, "missing device configuration\n"); > + return -ENODEV; > + } > + > + ili->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(ili->reset_gpio)) { > + DRM_DEV_ERROR(dev, "failed to get reset gpio\n"); > + return PTR_ERR(ili->reset_gpio); > + } > + > + ili->dc_gpio = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW); > + if (IS_ERR(ili->dc_gpio)) { > + DRM_DEV_ERROR(dev, "failed to get dc gpio\n"); > + return PTR_ERR(ili->dc_gpio); > + } > + > + ili->backlight = devm_of_find_backlight(dev); > + if (IS_ERR(ili->backlight)) { > + DRM_DEV_ERROR(dev, "failed to get backlight\n"); > + return PTR_ERR(ili->backlight); > + } > + > + ret = spi_setup(spi); > + if (ret < 0) { > + DRM_DEV_ERROR(dev, "spi setup failed\n"); > + return ret; > + } > + > + drm_panel_init(&ili->panel); > + ili->panel.dev = dev; > + ili->panel.funcs = &ili9341_drm_funcs; > + > + return drm_panel_add(&ili->panel); > +} > + > +static int ili9341_remove(struct spi_device *spi) > +{ > + struct ili9341 *ili = spi_get_drvdata(spi); > + > + drm_panel_remove(&ili->panel); > + > + ili9341_disable(&ili->panel); > + ili9341_unprepare(&ili->panel); > + > + return 0; > +} > + > +static const struct ili9341_config dt024ctft_data = { > + .width_mm = 37, > + .height_mm = 49, > +}; > + > +static const struct of_device_id ili9341_of_match[] = { > + { .compatible = "displaytech,dt024ctft", .data = &dt024ctft_data }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, ili9341_of_match); > + > +static struct spi_driver ili9341_driver = { > + .probe = ili9341_probe, > + .remove = ili9341_remove, > + .driver = { > + .name = "panel-ilitek-ili9341", > + .of_match_table = ili9341_of_match, > + }, > +}; > +module_spi_driver(ili9341_driver); > + > +MODULE_AUTHOR("Josef Lusticky <josef@xxxxxxxxxxx>"); > +MODULE_DESCRIPTION("ILI9341 LCD panel driver"); > +MODULE_LICENSE("GPL"); > -- > 2.20.1 >