From ab7c433560a3895e45f770eacc86616ed7b76e2a Mon Sep 17 00:00:00 2001
From: Tom Callaway <tcallawa@redhat.com>
Date: Fri, 6 Apr 2018 16:42:53 -0400
Subject: [PATCH 1/1] add ili9340 tinydrm driver

---
 .../devicetree/bindings/display/ili9340.txt        |  27 ++
 MAINTAINERS                                        |   6 +
 drivers/gpu/drm/tinydrm/Kconfig                    |   8 +
 drivers/gpu/drm/tinydrm/Makefile                   |   1 +
 drivers/gpu/drm/tinydrm/ili9340.c                  | 290 +++++++++++++++++++++
 include/drm/tinydrm/ili9340.h                      |  51 ++++
 6 files changed, 383 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/ili9340.txt
 create mode 100644 drivers/gpu/drm/tinydrm/ili9340.c
 create mode 100644 include/drm/tinydrm/ili9340.h

diff --git a/Documentation/devicetree/bindings/display/ili9340.txt b/Documentation/devicetree/bindings/display/ili9340.txt
new file mode 100644
index 0000000..fecd768
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/ili9340.txt
@@ -0,0 +1,27 @@
+Ilitek ILI9340 display panel
+
+Required properties:
+- compatible:	"ilitek,ili9340".
+
+The node for this driver must be a child node of a SPI controller, hence
+all mandatory properties described in ../spi/spi-bus.txt must be specified.
+
+Optional properties:
+- dc-gpios:	D/C pin. The presence/absence of this GPIO determines
+		the panel interface mode (IM[3:0] pins):
+		- present: IM=x110 4-wire 8-bit data serial interface
+		- absent:  IM=x101 3-wire 9-bit data serial interface
+- reset-gpios:	Reset pin
+- power-supply:	A regulator node for the supply voltage.
+- backlight:	phandle of the backlight device attached to the panel
+- rotation:	panel rotation in degrees counter clockwise (0,90,180,270)
+
+Example:
+	ili9340@0{
+		compatible = "ilitek,ili9340";
+		reg = <0>;
+		spi-max-frequency = <32000000>;
+		rotation = <90>;
+		dc-gpios = <&gpio 25 0>;
+		backlight = <&backlight>;
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index 6e950b8..289201d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4483,6 +4483,12 @@ S:	Maintained
 F:	drivers/gpu/drm/tinydrm/ili9225.c
 F:	Documentation/devicetree/bindings/display/ili9225.txt
 
+DRM DRIVER FOR ILITEK ILI9340 PANELS
+M:	Tom Callaway <tcallawa@redhat.com>
+S:	Maintained
+F:	drivers/gpu/drm/tinydrm/ili9340.c
+F:	Documentation/devicetree/bindings/display/ili9340.txt
+
 DRM DRIVER FOR INTEL I810 VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/i810/
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index b0e567d..ca43604 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -22,6 +22,14 @@ config TINYDRM_ILI9225
 
 	  If M is selected the module will be called ili9225.
 
+config TINYDRM_ILI9340
+	tristate "DRM support for ILI9340 display panels"
+	depends on DRM_TINYDRM && SPI
+	select TINYDRM_MIPI_DBI
+	help
+	  DRM driver for the Ilitek ILI9340 display panel
+	  If M is selected the module will be called ili9340.
+
 config TINYDRM_MI0283QT
 	tristate "DRM support for MI0283QT"
 	depends on DRM_TINYDRM && SPI
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 49a1119..185b8e6 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
 
 # Displays
 obj-$(CONFIG_TINYDRM_ILI9225)		+= ili9225.o
+obj-$(CONFIG_TINYDRM_ILI9340)		+= ili9340.o
 obj-$(CONFIG_TINYDRM_MI0283QT)		+= mi0283qt.o
 obj-$(CONFIG_TINYDRM_REPAPER)		+= repaper.o
 obj-$(CONFIG_TINYDRM_ST7586)		+= st7586.o
diff --git a/drivers/gpu/drm/tinydrm/ili9340.c b/drivers/gpu/drm/tinydrm/ili9340.c
new file mode 100644
index 0000000..4bed2e7
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/ili9340.c
@@ -0,0 +1,290 @@
+/*
+ * DRM driver for ILI9340 panels
+ *
+ * Copyright 2018 Tom Callaway <tcallawa@redhat.com>
+ * Based on mi0283qt.c, which is
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/tinydrm/ili9340.h>
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+static int ili9340_init(struct mipi_dbi *mipi)
+{
+	struct tinydrm_device *tdev = &mipi->tinydrm;
+	struct device *dev = tdev->drm->dev;
+	u8 addr_mode;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+
+	ret = regulator_enable(mipi->regulator);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "Failed to enable regulator %d\n", ret);
+		return ret;
+	}
+
+	/* Avoid flicker by skipping setup if the bootloader has done it */
+	if (mipi_dbi_display_is_on(mipi))
+		return 0;
+
+	mipi_dbi_hw_reset(mipi);
+	/*
+	ret = mipi_dbi_command(mipi, MIPI_DCS_SOFT_RESET);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "Error sending command %d\n", ret);
+		regulator_disable(mipi->regulator);
+		return ret;
+	}
+
+	msleep(20);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF);
+	*/
+
+	/* Can't find docs to identify 0xEF, but arduino code inits with it */
+	mipi_dbi_command(mipi, ILI9340_MYSTERY, 0x03, 0x80, 0x02);
+	mipi_dbi_command(mipi, ILI9340_PWCTRLB, 0x00, 0xC1, 0x30);
+	mipi_dbi_command(mipi, ILI9340_PWRSEQ, 0x64, 0x03, 0x12, 0x81);
+	mipi_dbi_command(mipi, ILI9340_DTCTRLA, 0x85, 0x00, 0x78);
+	mipi_dbi_command(mipi, ILI9340_PWCTRLA, 0x39, 0x2c, 0x00, 0x34, 0x02);
+	mipi_dbi_command(mipi, ILI9340_PUMPCTRL, 0x20);
+	mipi_dbi_command(mipi, ILI9340_DTCTRLB, 0x00, 0x00);
+
+	/* Power Control */
+	mipi_dbi_command(mipi, ILI9340_PWCTRL1, 0x23);
+	mipi_dbi_command(mipi, ILI9340_PWCTRL2, 0x10);
+	/* VCOM */
+	mipi_dbi_command(mipi, ILI9340_VMCTRL1, 0x3e, 0x28);
+	mipi_dbi_command(mipi, ILI9340_VMCTRL2, 0x86);
+
+	/* Memory Access Control */
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+
+	switch (mipi->rotation) {
+	default:
+		addr_mode = ILI9340_MADCTL_MX;
+		break;
+	case 90:
+		addr_mode = ILI9340_MADCTL_MV | ILI9340_MADCTL_MY | ILI9340_MADCTL_MX;
+		break;
+	case 180:
+		addr_mode = ILI9340_MADCTL_MY;
+		break;
+	case 270:
+		addr_mode = ILI9340_MADCTL_MV;
+		break;
+	}
+	addr_mode |= ILI9340_MADCTL_BGR;
+	mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+
+	/* Frame Rate */
+	mipi_dbi_command(mipi, ILI9340_FRMCTR1, 0x00, 0x1b);
+
+	/* Display */
+	mipi_dbi_command(mipi, ILI9340_DISCTRL, 0x08, 0x82, 0x27);
+
+	/* Gamma Function Disable */
+	mipi_dbi_command(mipi, ILI9340_EN3GAM, 0x00);
+
+	/* Gamma curve selection */
+	mipi_dbi_command(mipi, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
+
+	/* Positive Gamma Correction */
+	mipi_dbi_command(mipi, ILI9340_PGAMCTRL,
+		       0x0f, 0x31, 0x28, 0x0c, 0x0e, 0x08, 0x4e, 0xf1,
+		       0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00);
+
+	/* Negative Gamma Correction */
+	mipi_dbi_command(mipi, ILI9340_NGAMCTRL,
+		       0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1,
+		       0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0f);
+
+	/* DDRAM */
+	// mipi_dbi_command(mipi, ILI9341_ETMOD, 0x07);
+
+	mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(100);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON);
+	msleep(100);
+
+	return 0;
+}
+
+static void ili9340_fini(void *data)
+{
+	struct mipi_dbi *mipi = data;
+
+	DRM_DEBUG_KMS("\n");
+	regulator_disable(mipi->regulator);
+}
+
+static const struct drm_simple_display_pipe_funcs ili9340_pipe_funcs = {
+	.enable = mipi_dbi_pipe_enable,
+	.disable = mipi_dbi_pipe_disable,
+	.update = tinydrm_display_pipe_update,
+	.prepare_fb = tinydrm_display_pipe_prepare_fb,
+};
+
+static const struct drm_display_mode ili9340_mode = {
+	TINYDRM_MODE(320, 240, 58, 43),
+};
+
+DEFINE_DRM_GEM_CMA_FOPS(ili9340_fops);
+
+static struct drm_driver ili9340_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+				  DRIVER_ATOMIC,
+	.fops			= &ili9340_fops,
+	TINYDRM_GEM_DRIVER_OPS,
+	.lastclose		= drm_fb_helper_lastclose,
+	.debugfs_init		= mipi_dbi_debugfs_init,
+	.name			= "ili9340",
+	.desc			= "Ilitek 9340 LCD",
+	.date			= "20180406",
+	.major			= 1,
+	.minor			= 0,
+};
+
+static const struct of_device_id ili9340_of_match[] = {
+	{ .compatible = "ilitek,ili9340" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ili9340_of_match);
+
+static const struct spi_device_id ili9340_id[] = {
+	{ "ili9340", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, ili9340_id);
+
+static int ili9340_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct mipi_dbi *mipi;
+	struct gpio_desc *dc;
+	u32 rotation = 0;
+	int ret;
+
+	mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
+	if (!mipi)
+		return -ENOMEM;
+
+	mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(mipi->reset)) {
+		DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
+		return PTR_ERR(mipi->reset);
+	}
+
+	dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
+	if (IS_ERR(dc)) {
+		DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n");
+		return PTR_ERR(dc);
+	}
+
+	mipi->regulator = devm_regulator_get(dev, "power");
+	if (IS_ERR(mipi->regulator))
+		return PTR_ERR(mipi->regulator);
+
+	mipi->backlight = tinydrm_of_find_backlight(dev);
+	if (IS_ERR(mipi->backlight))
+		return PTR_ERR(mipi->backlight);
+
+	device_property_read_u32(dev, "rotation", &rotation);
+
+	ret = mipi_dbi_spi_init(spi, mipi, dc);
+	if (ret)
+		return ret;
+
+	ret = mipi_dbi_init(&spi->dev, mipi, &ili9340_pipe_funcs,
+			    &ili9340_driver, &ili9340_mode, rotation);
+	if (ret)
+		return ret;
+
+	ret = ili9340_init(mipi);
+	if (ret)
+		return ret;
+
+	/* use devres to fini after drm unregister (drv->remove is before) */
+	ret = devm_add_action(dev, ili9340_fini, mipi);
+	if (ret) {
+		ili9340_fini(mipi);
+		return ret;
+	}
+
+	spi_set_drvdata(spi, mipi);
+
+	return devm_tinydrm_register(&mipi->tinydrm);
+}
+
+static void ili9340_shutdown(struct spi_device *spi)
+{
+	struct mipi_dbi *mipi = spi_get_drvdata(spi);
+
+	tinydrm_shutdown(&mipi->tinydrm);
+}
+
+static int __maybe_unused ili9340_pm_suspend(struct device *dev)
+{
+	struct mipi_dbi *mipi = dev_get_drvdata(dev);
+	int ret;
+
+	ret = drm_mode_config_helper_suspend(mipi->tinydrm.drm);
+	if (ret)
+		return ret;
+
+	ili9340_fini(mipi);
+
+	return 0;
+}
+
+static int __maybe_unused ili9340_pm_resume(struct device *dev)
+{
+	struct mipi_dbi *mipi = dev_get_drvdata(dev);
+	int ret;
+
+	ret = ili9340_init(mipi);
+	if (ret)
+		return ret;
+
+	drm_mode_config_helper_resume(mipi->tinydrm.drm);
+
+	return 0;
+}
+
+static const struct dev_pm_ops ili9340_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(ili9340_pm_suspend, ili9340_pm_resume)
+};
+
+static struct spi_driver ili9340_spi_driver = {
+	.driver = {
+		.name = "ili9340",
+		.owner = THIS_MODULE,
+		.of_match_table = ili9340_of_match,
+		.pm = &ili9340_pm_ops,
+	},
+	.id_table = ili9340_id,
+	.probe = ili9340_probe,
+	.shutdown = ili9340_shutdown,
+};
+module_spi_driver(ili9340_spi_driver);
+
+MODULE_DESCRIPTION("Ilitek ILI9340 DRM driver");
+MODULE_AUTHOR("Tom Callaway");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/ili9340.h b/include/drm/tinydrm/ili9340.h
new file mode 100644
index 0000000..12063c3
--- /dev/null
+++ b/include/drm/tinydrm/ili9340.h
@@ -0,0 +1,53 @@
+/*
+ * ILI9340 LCD controller
+ *
+ * Copyright 2018 Tom Callaway <tcallawa@redhat.com>
+ * Based on the ILI9341 code which is
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_ILI9340_H
+#define __LINUX_ILI9340_H
+
+#define ILI9340_FRMCTR1    0xb1
+#define ILI9340_FRMCTR2    0xb2
+#define ILI9340_FRMCTR3    0xb3
+#define ILI9340_INVTR      0xb4
+#define ILI9340_DISCTRL    0xb6
+
+#define ILI9340_PWCTRL1    0xc0
+#define ILI9340_PWCTRL2    0xc1
+#define ILI9340_PWCTRL3    0xc2
+#define ILI9340_PWCTRL4    0xc3
+#define ILI9340_PWCTRL5    0xc4
+#define ILI9340_VMCTRL1    0xc5
+#define ILI9340_VMCTRL2    0xc7
+#define ILI9340_PWCTRLA    0xcb
+#define ILI9340_PWCTRLB    0xcf
+
+#define ILI9340_RDID1      0xda
+#define ILI9340_RDID2      0xdb
+#define ILI9340_RDID3      0xdc
+#define ILI9340_RDID4      0xdd
+
+#define ILI9340_PGAMCTRL   0xe0
+#define ILI9340_NGAMCTRL   0xe1
+#define ILI9340_DTCTRLA    0xe8
+#define ILI9340_DTCTRLB    0xea
+#define ILI9340_PWRSEQ     0xed
+#define ILI9340_MYSTERY    0xef
+
+#define ILI9340_EN3GAM     0xf2
+#define ILI9340_PUMPCTRL   0xf7
+
+#define ILI9340_MADCTL_BGR BIT(3)
+#define ILI9340_MADCTL_MV  BIT(5)
+#define ILI9340_MADCTL_MX  BIT(6)
+#define ILI9340_MADCTL_MY  BIT(7)
+
+#endif /* __LINUX_ILI9340_H */
-- 
2.16.2

