Message ID | 20200822163250.63664-7-paul@crapouillou.net (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | DSI/DBI, panel drivers, & tinyDRM v2 | expand |
> +struct ili9341 { > + struct drm_panel panel; > + struct mipi_dsi_device *dsi; > + const struct ili9341_pdata *pdata; > + > + struct gpio_desc *reset_gpiod; > + u32 rotation; > +}; > + Hi Paul, you put the mipi_dsi_device inside the struct. I think it maybe not a good idea. That means the panel has a MIPI-DSI interface but it doesn't have actually. > +static int ili9341_probe(struct mipi_dsi_device *dsi) > +{ > + struct device *dev = &dsi->dev; > + struct ili9341 *priv; > + int ret; > + > + /* See comment for mipi_dbi_spi_init() */ > + if (!dev->coherent_dma_mask) { > + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); > + if (ret) { > + dev_warn(dev, "Failed to set dma mask %d\n", ret); > + return ret; > + } > + } > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + mipi_dsi_set_drvdata(dsi, priv); > + priv->dsi = dsi; > + > + device_property_read_u32(dev, "rotation", &priv->rotation); > + > + priv->pdata = device_get_match_data(dev); > + if (!priv->pdata) > + return -EINVAL; > + > + drm_panel_init(&priv->panel, dev, &ili9341_funcs, > + DRM_MODE_CONNECTOR_DPI); > + > + priv->reset_gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(priv->reset_gpiod)) { > + dev_err(dev, "Couldn't get our reset GPIO\n"); > + return PTR_ERR(priv->reset_gpiod); > + } > + > + ret = drm_panel_of_backlight(&priv->panel); > + if (ret < 0) { > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "Failed to get backlight handle\n"); > + return ret; > + } > + > + drm_panel_add(&priv->panel); > + > + dsi->bus_type = priv->pdata->bus_type; > + dsi->lanes = priv->pdata->lanes; > + dsi->format = MIPI_DSI_FMT_RGB565; > + > + ret = mipi_dsi_attach(dsi); > + if (ret) { > + dev_err(dev, "Failed to attach DSI panel\n"); > + goto err_panel_remove; > + } > + > + ret = mipi_dsi_maybe_register_tiny_driver(dsi); > + if (ret) { > + dev_err(dev, "Failed to init TinyDRM driver\n"); > + goto err_mipi_dsi_detach; > + } > + > + return 0; > + > +err_mipi_dsi_detach: > + mipi_dsi_detach(dsi); > +err_panel_remove: > + drm_panel_remove(&priv->panel); > + return ret; > +} > + > +static int ili9341_remove(struct mipi_dsi_device *dsi) > +{ > + struct ili9341 *priv = mipi_dsi_get_drvdata(dsi); > + > + mipi_dsi_detach(dsi); > + drm_panel_remove(&priv->panel); > + > + drm_panel_disable(&priv->panel); > + drm_panel_unprepare(&priv->panel); > + > + return 0; > +} > + > +static const struct ili9341_pdata yx240qv29_pdata = { > + .mode = { DRM_SIMPLE_MODE(240, 320, 37, 49) }, > + .width_mm = 0, // TODO > + .height_mm = 0, // TODO > + .bus_type = MIPI_DCS_BUS_TYPE_DBI_SPI_C3, > + .lanes = 1, > +}; > + > +static const struct of_device_id ili9341_of_match[] = { > + { .compatible = "adafruit,yx240qv29", .data = &yx240qv29_pdata }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, ili9341_of_match); > + > +static struct mipi_dsi_driver ili9341_dsi_driver = { > + .probe = ili9341_probe, > + .remove = ili9341_remove, > + .driver = { > + .name = "ili9341-dsi", > + .of_match_table = ili9341_of_match, > + }, > +}; > +module_mipi_dsi_driver(ili9341_dsi_driver); Again, you treat this driver as a mipi dsi driver but for a MIPI-DBI (I8080/SPI) panel device. That will make developers confused. Is it possible to just add a mipi_dbi_driver for I8080/SPI interface panel? Thanks! Best regards
Hi Leon, Le dim. 30 août 2020 à 16:36, 何小龙 (Leon He) <Leon.He@unisoc.com> a écrit : >> +struct ili9341 { >> + struct drm_panel panel; >> + struct mipi_dsi_device *dsi; >> + const struct ili9341_pdata *pdata; >> + >> + struct gpio_desc *reset_gpiod; >> + u32 rotation; >> +}; >> + > > Hi Paul, you put the mipi_dsi_device inside the struct. I think it > maybe not > a good idea. That means the panel has a MIPI-DSI interface but it > doesn't > have actually. > >> +static int ili9341_probe(struct mipi_dsi_device *dsi) >> +{ >> + struct device *dev = &dsi->dev; >> + struct ili9341 *priv; >> + int ret; >> + >> + /* See comment for mipi_dbi_spi_init() */ >> + if (!dev->coherent_dma_mask) { >> + ret = dma_coerce_mask_and_coherent(dev, >> DMA_BIT_MASK(32)); >> + if (ret) { >> + dev_warn(dev, "Failed to set dma mask >> %d\n", ret); >> + return ret; >> + } >> + } >> + >> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); >> + if (!priv) >> + return -ENOMEM; >> + >> + mipi_dsi_set_drvdata(dsi, priv); >> + priv->dsi = dsi; >> + >> + device_property_read_u32(dev, "rotation", &priv->rotation); >> + >> + priv->pdata = device_get_match_data(dev); >> + if (!priv->pdata) >> + return -EINVAL; >> + >> + drm_panel_init(&priv->panel, dev, &ili9341_funcs, >> + DRM_MODE_CONNECTOR_DPI); >> + >> + priv->reset_gpiod = devm_gpiod_get(dev, "reset", >> GPIOD_OUT_HIGH); >> + if (IS_ERR(priv->reset_gpiod)) { >> + dev_err(dev, "Couldn't get our reset GPIO\n"); >> + return PTR_ERR(priv->reset_gpiod); >> + } >> + >> + ret = drm_panel_of_backlight(&priv->panel); >> + if (ret < 0) { >> + if (ret != -EPROBE_DEFER) >> + dev_err(dev, "Failed to get backlight >> handle\n"); >> + return ret; >> + } >> + >> + drm_panel_add(&priv->panel); >> + >> + dsi->bus_type = priv->pdata->bus_type; >> + dsi->lanes = priv->pdata->lanes; >> + dsi->format = MIPI_DSI_FMT_RGB565; >> + >> + ret = mipi_dsi_attach(dsi); >> + if (ret) { >> + dev_err(dev, "Failed to attach DSI panel\n"); >> + goto err_panel_remove; >> + } >> + >> + ret = mipi_dsi_maybe_register_tiny_driver(dsi); >> + if (ret) { >> + dev_err(dev, "Failed to init TinyDRM driver\n"); >> + goto err_mipi_dsi_detach; >> + } >> + >> + return 0; >> + >> +err_mipi_dsi_detach: >> + mipi_dsi_detach(dsi); >> +err_panel_remove: >> + drm_panel_remove(&priv->panel); >> + return ret; >> +} >> + >> +static int ili9341_remove(struct mipi_dsi_device *dsi) >> +{ >> + struct ili9341 *priv = mipi_dsi_get_drvdata(dsi); >> + >> + mipi_dsi_detach(dsi); >> + drm_panel_remove(&priv->panel); >> + >> + drm_panel_disable(&priv->panel); >> + drm_panel_unprepare(&priv->panel); >> + >> + return 0; >> +} >> + >> +static const struct ili9341_pdata yx240qv29_pdata = { >> + .mode = { DRM_SIMPLE_MODE(240, 320, 37, 49) }, >> + .width_mm = 0, // TODO >> + .height_mm = 0, // TODO >> + .bus_type = MIPI_DCS_BUS_TYPE_DBI_SPI_C3, >> + .lanes = 1, >> +}; >> + >> +static const struct of_device_id ili9341_of_match[] = { >> + { .compatible = "adafruit,yx240qv29", .data = >> &yx240qv29_pdata }, >> + { } >> +}; >> +MODULE_DEVICE_TABLE(of, ili9341_of_match); >> + >> +static struct mipi_dsi_driver ili9341_dsi_driver = { >> + .probe = ili9341_probe, >> + .remove = ili9341_remove, >> + .driver = { >> + .name = "ili9341-dsi", >> + .of_match_table = ili9341_of_match, >> + }, >> +}; >> +module_mipi_dsi_driver(ili9341_dsi_driver); > > Again, you treat this driver as a mipi dsi driver but for a MIPI-DBI > (I8080/SPI) > panel device. That will make developers confused. > > Is it possible to just add a mipi_dbi_driver for I8080/SPI interface > panel? > Thanks! Please read the cover letter, it explains why it's done this way. The whole point of this patchset is to merge DSI and DBI frameworks in a way that can be maintained. Cheers, -Paul
Hi Paul, On Sun, Aug 30, 2020 at 06:48:12PM +0200, Paul Cercueil wrote: > Le dim. 30 août 2020 à 16:36, 何小龙 (Leon He) a écrit : > >> +struct ili9341 { > >> + struct drm_panel panel; > >> + struct mipi_dsi_device *dsi; > >> + const struct ili9341_pdata *pdata; > >> + > >> + struct gpio_desc *reset_gpiod; > >> + u32 rotation; > >> +}; > >> + > > > > Hi Paul, you put the mipi_dsi_device inside the struct. I think it > > maybe not > > a good idea. That means the panel has a MIPI-DSI interface but it > > doesn't > > have actually. > > > >> +static int ili9341_probe(struct mipi_dsi_device *dsi) > >> +{ > >> + struct device *dev = &dsi->dev; > >> + struct ili9341 *priv; > >> + int ret; > >> + > >> + /* See comment for mipi_dbi_spi_init() */ > >> + if (!dev->coherent_dma_mask) { > >> + ret = dma_coerce_mask_and_coherent(dev, > >> DMA_BIT_MASK(32)); > >> + if (ret) { > >> + dev_warn(dev, "Failed to set dma mask > >> %d\n", ret); > >> + return ret; > >> + } > >> + } > >> + > >> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > >> + if (!priv) > >> + return -ENOMEM; > >> + > >> + mipi_dsi_set_drvdata(dsi, priv); > >> + priv->dsi = dsi; > >> + > >> + device_property_read_u32(dev, "rotation", &priv->rotation); > >> + > >> + priv->pdata = device_get_match_data(dev); > >> + if (!priv->pdata) > >> + return -EINVAL; > >> + > >> + drm_panel_init(&priv->panel, dev, &ili9341_funcs, > >> + DRM_MODE_CONNECTOR_DPI); > >> + > >> + priv->reset_gpiod = devm_gpiod_get(dev, "reset", > >> GPIOD_OUT_HIGH); > >> + if (IS_ERR(priv->reset_gpiod)) { > >> + dev_err(dev, "Couldn't get our reset GPIO\n"); > >> + return PTR_ERR(priv->reset_gpiod); > >> + } > >> + > >> + ret = drm_panel_of_backlight(&priv->panel); > >> + if (ret < 0) { > >> + if (ret != -EPROBE_DEFER) > >> + dev_err(dev, "Failed to get backlight > >> handle\n"); > >> + return ret; > >> + } > >> + > >> + drm_panel_add(&priv->panel); > >> + > >> + dsi->bus_type = priv->pdata->bus_type; > >> + dsi->lanes = priv->pdata->lanes; > >> + dsi->format = MIPI_DSI_FMT_RGB565; > >> + > >> + ret = mipi_dsi_attach(dsi); > >> + if (ret) { > >> + dev_err(dev, "Failed to attach DSI panel\n"); > >> + goto err_panel_remove; > >> + } > >> + > >> + ret = mipi_dsi_maybe_register_tiny_driver(dsi); > >> + if (ret) { > >> + dev_err(dev, "Failed to init TinyDRM driver\n"); > >> + goto err_mipi_dsi_detach; > >> + } > >> + > >> + return 0; > >> + > >> +err_mipi_dsi_detach: > >> + mipi_dsi_detach(dsi); > >> +err_panel_remove: > >> + drm_panel_remove(&priv->panel); > >> + return ret; > >> +} > >> + > >> +static int ili9341_remove(struct mipi_dsi_device *dsi) > >> +{ > >> + struct ili9341 *priv = mipi_dsi_get_drvdata(dsi); > >> + > >> + mipi_dsi_detach(dsi); > >> + drm_panel_remove(&priv->panel); > >> + > >> + drm_panel_disable(&priv->panel); > >> + drm_panel_unprepare(&priv->panel); > >> + > >> + return 0; > >> +} > >> + > >> +static const struct ili9341_pdata yx240qv29_pdata = { > >> + .mode = { DRM_SIMPLE_MODE(240, 320, 37, 49) }, > >> + .width_mm = 0, // TODO > >> + .height_mm = 0, // TODO > >> + .bus_type = MIPI_DCS_BUS_TYPE_DBI_SPI_C3, > >> + .lanes = 1, > >> +}; > >> + > >> +static const struct of_device_id ili9341_of_match[] = { > >> + { .compatible = "adafruit,yx240qv29", .data = > >> &yx240qv29_pdata }, > >> + { } > >> +}; > >> +MODULE_DEVICE_TABLE(of, ili9341_of_match); > >> + > >> +static struct mipi_dsi_driver ili9341_dsi_driver = { > >> + .probe = ili9341_probe, > >> + .remove = ili9341_remove, > >> + .driver = { > >> + .name = "ili9341-dsi", > >> + .of_match_table = ili9341_of_match, > >> + }, > >> +}; > >> +module_mipi_dsi_driver(ili9341_dsi_driver); > > > > Again, you treat this driver as a mipi dsi driver but for a MIPI-DBI > > (I8080/SPI) > > panel device. That will make developers confused. > > > > Is it possible to just add a mipi_dbi_driver for I8080/SPI interface > > panel? > > Thanks! > > Please read the cover letter, it explains why it's done this way. The > whole point of this patchset is to merge DSI and DBI frameworks in a > way that can be maintained. I think this proves the point that the proposed naming is confusing. At least a rename would be required.
Hi Laurent. > > > > Please read the cover letter, it explains why it's done this way. The > > whole point of this patchset is to merge DSI and DBI frameworks in a > > way that can be maintained. > > I think this proves the point that the proposed naming is confusing. At > least a rename would be required. Do you have any inputs on the amount of rename we are looking into. Is this a simple s/struct mipi_dsi_device/struct mipi_dxi_device/ or something more? We should script the rename as it will tocuh a lot of files, and without a script we would chase this. But once it is scripted it would be trivial to perform. I did not look at this enough, but I had an idea that we would have do to a s/dsi/dxi/ in a lot of places. (dxi is my best proposal at the moment for something covering both dsi and dbi). PS. I am travelling for a few days, so do not expect quick responses. Sam
Le dim. 30 août 2020 à 22:28, Sam Ravnborg <sam@ravnborg.org> a écrit : > Hi Laurent. > >> > >> > Please read the cover letter, it explains why it's done this way. >> The >> > whole point of this patchset is to merge DSI and DBI frameworks >> in a >> > way that can be maintained. >> >> I think this proves the point that the proposed naming is >> confusing. At >> least a rename would be required. > > Do you have any inputs on the amount of rename we are looking into. > Is this a simple s/struct mipi_dsi_device/struct mipi_dxi_device/ > or something more? > > We should script the rename as it will tocuh a lot of files, > and without a script we would chase this. But once it is scripted > it would be trivial to perform. > > I did not look at this enough, but I had an idea that we > would have do to a s/dsi/dxi/ in a lot of places. > > (dxi is my best proposal at the moment for something covering both dsi > and dbi). dcs? Since DBI and DSI panels generally all use DCS commands. -Paul
On 07/09/2020 14:57, Paul Cercueil wrote: > > > Le dim. 30 août 2020 à 22:28, Sam Ravnborg <sam@ravnborg.org> a écrit : >> Hi Laurent. >> >>> > >>> > Please read the cover letter, it explains why it's done this way. The >>> > whole point of this patchset is to merge DSI and DBI frameworks in a >>> > way that can be maintained. >>> >>> I think this proves the point that the proposed naming is confusing. At >>> least a rename would be required. >> >> Do you have any inputs on the amount of rename we are looking into. >> Is this a simple s/struct mipi_dsi_device/struct mipi_dxi_device/ >> or something more? >> >> We should script the rename as it will tocuh a lot of files, >> and without a script we would chase this. But once it is scripted >> it would be trivial to perform. >> >> I did not look at this enough, but I had an idea that we >> would have do to a s/dsi/dxi/ in a lot of places. >> >> (dxi is my best proposal at the moment for something covering both dsi >> and dbi). > > dcs? > > Since DBI and DSI panels generally all use DCS commands. mipi_disp / mipi_display ? since it's all about mipi display interfaces with different transport protocols. Neil > > -Paul > > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi Paul, just a drive-by comment: On Sat, Aug 22, 2020 at 6:33 PM Paul Cercueil <paul@crapouillou.net> wrote: > + gpiod_set_value_cansleep(priv->reset_gpiod, 0); > + usleep_range(20, 1000); > + gpiod_set_value_cansleep(priv->reset_gpiod, 1); This implies that the reset line is active low. I would specify in the DT GPIO handle that it is active low and invert the above. So: reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; gpiod_set_value_cansleep(priv->reset_gpiod, 1); usleep_range(20, 1000); gpiod_set_value_cansleep(priv->reset_gpiod, 0); > + priv->reset_gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(priv->reset_gpiod)) { > + dev_err(dev, "Couldn't get our reset GPIO\n"); > + return PTR_ERR(priv->reset_gpiod); > + } This would then fetch the GPIO as asserted (device in reset) unless changed, but that may be the right thing to do actually. > +static const struct ili9341_pdata yx240qv29_pdata = { > + .mode = { DRM_SIMPLE_MODE(240, 320, 37, 49) }, > + .width_mm = 0, // TODO > + .height_mm = 0, // TODO When nothing else works and data sheets are incomplete I just take out a ruler and measure on the actual device. Yours, Linus Walleij
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 45b003752d65..38c581e91986 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -105,6 +105,15 @@ 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_ILI9341 + tristate "Ilitek ILI9341 320x240 QVGA panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Ilitek IL9341 + QVGA (320x240) RGB, YUV and ITU-T BT.656 panels. + 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 c01743cdc08b..d732c2b8a747 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o 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_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..8f32edaac627 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DRM driver for Ilitek ILI9341 panels + * + * Copyright 2018 David Lechner <david@lechnology.com> + * Copyright 2020 Paul Cercueil <paul@crapouillou.net> + * + * Based on mi0283qt.c: + * Copyright 2016 Noralf Trønnes + */ + +#include <linux/delay.h> +#include <linux/dma-buf.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/property.h> +#include <drm/drm_atomic_helper.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <video/mipi_display.h> + +#define ILI9341_FRMCTR1 0xb1 +#define ILI9341_DISCTRL 0xb6 +#define ILI9341_ETMOD 0xb7 + +#define ILI9341_PWCTRL1 0xc0 +#define ILI9341_PWCTRL2 0xc1 +#define ILI9341_VMCTRL1 0xc5 +#define ILI9341_VMCTRL2 0xc7 +#define ILI9341_PWCTRLA 0xcb +#define ILI9341_PWCTRLB 0xcf + +#define ILI9341_PGAMCTRL 0xe0 +#define ILI9341_NGAMCTRL 0xe1 +#define ILI9341_DTCTRLA 0xe8 +#define ILI9341_DTCTRLB 0xea +#define ILI9341_PWRSEQ 0xed + +#define ILI9341_EN3GAM 0xf2 +#define ILI9341_PUMPCTRL 0xf7 + +#define ILI9341_MADCTL_BGR BIT(3) +#define ILI9341_MADCTL_MV BIT(5) +#define ILI9341_MADCTL_MX BIT(6) +#define ILI9341_MADCTL_MY BIT(7) + +struct ili9341_pdata { + struct drm_display_mode mode; + unsigned int width_mm; + unsigned int height_mm; + unsigned int bus_type; + unsigned int lanes; +}; + +struct ili9341 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + const struct ili9341_pdata *pdata; + + struct gpio_desc *reset_gpiod; + u32 rotation; +}; + +#define mipi_dcs_command(dsi, cmd, seq...) \ +({ \ + u8 d[] = { seq }; \ + mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \ +}) + +static inline struct ili9341 *panel_to_ili9341(struct drm_panel *panel) +{ + return container_of(panel, struct ili9341, panel); +} + +static int ili9341_prepare(struct drm_panel *panel) +{ + struct ili9341 *priv = panel_to_ili9341(panel); + struct mipi_dsi_device *dsi = priv->dsi; + u8 addr_mode; + int ret; + + gpiod_set_value_cansleep(priv->reset_gpiod, 0); + usleep_range(20, 1000); + gpiod_set_value_cansleep(priv->reset_gpiod, 1); + msleep(120); + + ret = mipi_dcs_command(dsi, MIPI_DCS_SOFT_RESET); + if (ret) { + dev_err(panel->dev, "Failed to send reset command: %d\n", ret); + return ret; + } + + /* Wait 5ms after soft reset per MIPI DCS spec */ + usleep_range(5000, 20000); + + mipi_dcs_command(dsi, MIPI_DCS_SET_DISPLAY_OFF); + + mipi_dcs_command(dsi, ILI9341_PWCTRLB, 0x00, 0xc1, 0x30); + mipi_dcs_command(dsi, ILI9341_PWRSEQ, 0x64, 0x03, 0x12, 0x81); + mipi_dcs_command(dsi, ILI9341_DTCTRLA, 0x85, 0x00, 0x78); + mipi_dcs_command(dsi, ILI9341_PWCTRLA, 0x39, 0x2c, 0x00, 0x34, 0x02); + mipi_dcs_command(dsi, ILI9341_PUMPCTRL, 0x20); + mipi_dcs_command(dsi, ILI9341_DTCTRLB, 0x00, 0x00); + + /* Power Control */ + mipi_dcs_command(dsi, ILI9341_PWCTRL1, 0x23); + mipi_dcs_command(dsi, ILI9341_PWCTRL2, 0x10); + /* VCOM */ + mipi_dcs_command(dsi, ILI9341_VMCTRL1, 0x3e, 0x28); + mipi_dcs_command(dsi, ILI9341_VMCTRL2, 0x86); + + /* Memory Access Control */ + mipi_dcs_command(dsi, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT); + + /* Frame Rate */ + mipi_dcs_command(dsi, ILI9341_FRMCTR1, 0x00, 0x1b); + + /* Gamma */ + mipi_dcs_command(dsi, ILI9341_EN3GAM, 0x00); + mipi_dcs_command(dsi, MIPI_DCS_SET_GAMMA_CURVE, 0x01); + mipi_dcs_command(dsi, ILI9341_PGAMCTRL, + 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, + 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00); + mipi_dcs_command(dsi, ILI9341_NGAMCTRL, + 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, + 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f); + + /* DDRAM */ + mipi_dcs_command(dsi, ILI9341_ETMOD, 0x07); + + /* Display */ + mipi_dcs_command(dsi, ILI9341_DISCTRL, 0x08, 0x82, 0x27, 0x00); + mipi_dcs_command(dsi, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(100); + + mipi_dcs_command(dsi, MIPI_DCS_SET_DISPLAY_ON); + msleep(100); + + switch (priv->rotation) { + default: + addr_mode = ILI9341_MADCTL_MX; + break; + case 90: + addr_mode = ILI9341_MADCTL_MV; + break; + case 180: + addr_mode = ILI9341_MADCTL_MY; + break; + case 270: + addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY | + ILI9341_MADCTL_MX; + break; + } + addr_mode |= ILI9341_MADCTL_BGR; + mipi_dcs_command(dsi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); + + return 0; +} + +static int ili9341_unprepare(struct drm_panel *panel) +{ + struct ili9341 *priv = panel_to_ili9341(panel); + + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_DISPLAY_OFF); + + return 0; +} + +static int ili9341_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ili9341 *priv = panel_to_ili9341(panel); + struct drm_display_mode *mode; + u32 format = MEDIA_BUS_FMT_RGB565_1X16; + + mode = drm_mode_duplicate(connector->dev, &priv->pdata->mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u\n", + priv->pdata->mode.hdisplay, priv->pdata->mode.vdisplay); + 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.bpc = 8; + connector->display_info.width_mm = priv->pdata->width_mm; + connector->display_info.height_mm = priv->pdata->height_mm; + + drm_display_info_set_bus_formats(&connector->display_info, &format, 1); + connector->display_info.bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; + + return 1; +} + +static const struct drm_panel_funcs ili9341_funcs = { + .prepare = ili9341_prepare, + .unprepare = ili9341_unprepare, + .get_modes = ili9341_get_modes, +}; + +static int ili9341_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ili9341 *priv; + int ret; + + /* See comment for mipi_dbi_spi_init() */ + if (!dev->coherent_dma_mask) { + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_warn(dev, "Failed to set dma mask %d\n", ret); + return ret; + } + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, priv); + priv->dsi = dsi; + + device_property_read_u32(dev, "rotation", &priv->rotation); + + priv->pdata = device_get_match_data(dev); + if (!priv->pdata) + return -EINVAL; + + drm_panel_init(&priv->panel, dev, &ili9341_funcs, + DRM_MODE_CONNECTOR_DPI); + + priv->reset_gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpiod)) { + dev_err(dev, "Couldn't get our reset GPIO\n"); + return PTR_ERR(priv->reset_gpiod); + } + + ret = drm_panel_of_backlight(&priv->panel); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get backlight handle\n"); + return ret; + } + + drm_panel_add(&priv->panel); + + dsi->bus_type = priv->pdata->bus_type; + dsi->lanes = priv->pdata->lanes; + dsi->format = MIPI_DSI_FMT_RGB565; + + ret = mipi_dsi_attach(dsi); + if (ret) { + dev_err(dev, "Failed to attach DSI panel\n"); + goto err_panel_remove; + } + + ret = mipi_dsi_maybe_register_tiny_driver(dsi); + if (ret) { + dev_err(dev, "Failed to init TinyDRM driver\n"); + goto err_mipi_dsi_detach; + } + + return 0; + +err_mipi_dsi_detach: + mipi_dsi_detach(dsi); +err_panel_remove: + drm_panel_remove(&priv->panel); + return ret; +} + +static int ili9341_remove(struct mipi_dsi_device *dsi) +{ + struct ili9341 *priv = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&priv->panel); + + drm_panel_disable(&priv->panel); + drm_panel_unprepare(&priv->panel); + + return 0; +} + +static const struct ili9341_pdata yx240qv29_pdata = { + .mode = { DRM_SIMPLE_MODE(240, 320, 37, 49) }, + .width_mm = 0, // TODO + .height_mm = 0, // TODO + .bus_type = MIPI_DCS_BUS_TYPE_DBI_SPI_C3, + .lanes = 1, +}; + +static const struct of_device_id ili9341_of_match[] = { + { .compatible = "adafruit,yx240qv29", .data = &yx240qv29_pdata }, + { } +}; +MODULE_DEVICE_TABLE(of, ili9341_of_match); + +static struct mipi_dsi_driver ili9341_dsi_driver = { + .probe = ili9341_probe, + .remove = ili9341_remove, + .driver = { + .name = "ili9341-dsi", + .of_match_table = ili9341_of_match, + }, +}; +module_mipi_dsi_driver(ili9341_dsi_driver); + +MODULE_DESCRIPTION("Ilitek ILI9341 DRM panel driver"); +MODULE_AUTHOR("David Lechner <david@lechnology.com>"); +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_LICENSE("GPL");
This driver is for the Ilitek ILI9341 based YX240QV29-T 2.4" 240x320 TFT LCD panel from Adafruit. v2: - Remove custom handling of backlight - Call drm_panel_disable() / drm_panel_unprepare() on module exit - drm_panel_add() is a void function now Signed-off-by: Paul Cercueil <paul@crapouillou.net> --- drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-ilitek-ili9341.c | 318 +++++++++++++++++++ 3 files changed, 328 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili9341.c