From bf7ce2d1c54900ac19fef0bf0e317732682820e5 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Tue, 1 Feb 2022 15:27:01 +0000
Subject: [PATCH] drm/panel/panel-sitronix-st7701: Support SPI config
 and RGB data

The ST7701 supports numerous different interface mechanisms for
MIPI DSI, RGB, or SPI. The driver was only implementing DSI input,
so add RGB parallel input with SPI configuration.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
 drivers/gpu/drm/panel/panel-sitronix-st7701.c | 374 ++++++++++++++++--
 1 file changed, 351 insertions(+), 23 deletions(-)

--- a/drivers/gpu/drm/panel/panel-sitronix-st7701.c
+++ b/drivers/gpu/drm/panel/panel-sitronix-st7701.c
@@ -7,16 +7,21 @@
 #include <drm/drm_mipi_dsi.h>
 #include <drm/drm_modes.h>
 #include <drm/drm_panel.h>
+#include <drm/drm_print.h>
 
 #include <linux/bitfield.h>
 #include <linux/gpio/consumer.h>
 #include <linux/delay.h>
+#include <linux/media-bus-format.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
 
 #include <video/mipi_display.h>
 
+#define SPI_DATA_FLAG			0x100
+
 /* Command2 BKx selection command */
 #define DSI_CMD2BKX_SEL			0xFF
 
@@ -53,6 +58,10 @@
 #define DSI_CMD2BK1_SEL			0x11
 #define DSI_CMD2BK3_SEL			0x13
 #define DSI_CMD2BKX_SEL_NONE		0x00
+#define SPI_CMD2BK3_SEL			(SPI_DATA_FLAG | DSI_CMD2BK3_SEL)
+#define SPI_CMD2BK1_SEL			(SPI_DATA_FLAG | DSI_CMD2BK1_SEL)
+#define SPI_CMD2BK0_SEL			(SPI_DATA_FLAG | DSI_CMD2BK0_SEL)
+#define SPI_CMD2BKX_SEL_NONE		(SPI_DATA_FLAG | DSI_CMD2BKX_SEL_NONE)
 
 /* Command2, BK0 bytes */
 #define DSI_CMD2_BK0_GAMCTRL_AJ_MASK	GENMASK(7, 6)
@@ -112,11 +121,23 @@ enum op_bias {
 
 struct st7701;
 
+struct st7701;
+
+enum st7701_ctrl_if {
+	ST7701_CTRL_DSI,
+	ST7701_CTRL_SPI,
+};
+
 struct st7701_panel_desc {
 	const struct drm_display_mode *mode;
 	unsigned int lanes;
 	enum mipi_dsi_pixel_format format;
+	u32 mediabus_format;
 	unsigned int panel_sleep_delay;
+	void (*init_sequence)(struct st7701 *st7701);
+	unsigned int conn_type;
+	enum st7701_ctrl_if interface;
+	u32 bus_flags;
 
 	/* TFT matrix driver configuration, panel specific. */
 	const u8	pv_gamma[16];	/* Positive voltage gamma control */
@@ -142,6 +163,9 @@ struct st7701_panel_desc {
 struct st7701 {
 	struct drm_panel panel;
 	struct mipi_dsi_device *dsi;
+	struct spi_device *spi;
+	const struct device *dev;
+
 	const struct st7701_panel_desc *desc;
 
 	struct regulator_bulk_data supplies[2];
@@ -191,7 +215,23 @@ static u8 st7701_vgls_map(struct st7701
 	return 0;
 }
 
-static void st7701_init_sequence(struct st7701 *st7701)
+#define ST7701_SPI(st7701, seq...)				\
+	{							\
+		const u16 d[] = { seq };			\
+		struct spi_transfer xfer = { };			\
+		struct spi_message spi;				\
+								\
+		spi_message_init(&spi);				\
+								\
+		xfer.tx_buf = d;				\
+		xfer.bits_per_word = 9;				\
+		xfer.len = sizeof(u16) * ARRAY_SIZE(d);		\
+								\
+		spi_message_add_tail(&xfer, &spi);		\
+		spi_sync((st7701)->spi, &spi);			\
+	}
+
+static void ts8550b_init_sequence(struct st7701 *st7701)
 {
 	const struct st7701_panel_desc *desc = st7701->desc;
 	const struct drm_display_mode *mode = desc->mode;
@@ -404,6 +444,111 @@ static void dmt028vghmcmi_1a_gip_sequenc
 	ST7701_DSI(st7701, 0x3A, 0x70);
 }
 
+static void txw210001b0_init_sequence(struct st7701 *st7701)
+{
+	ST7701_SPI(st7701, MIPI_DCS_SOFT_RESET);
+
+	usleep_range(5000, 7000);
+
+	ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+		   0x177, 0x101, 0x100, 0x100, SPI_CMD2BK0_SEL);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK0_LNESET, 0x13B, 0x100);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK0_PORCTRL, 0x10B, 0x102);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK0_INVSEL, 0x100, 0x102);
+
+	ST7701_SPI(st7701, 0xCC, 0x110);
+
+	/*
+	 * Gamma option B:
+	 * Positive Voltage Gamma Control
+	 */
+	ST7701_SPI(st7701, DSI_CMD2_BK0_PVGAMCTRL,
+		   0x102, 0x113, 0x11B, 0x10D, 0x110, 0x105, 0x108, 0x107,
+		   0x107, 0x124, 0x104, 0x111, 0x10E, 0x12C, 0x133, 0x11D);
+
+	/* Negative Voltage Gamma Control */
+	ST7701_SPI(st7701, DSI_CMD2_BK0_NVGAMCTRL,
+		   0x105, 0x113, 0x11B, 0x10D, 0x111, 0x105, 0x108, 0x107,
+		   0x107, 0x124, 0x104, 0x111, 0x10E, 0x12C, 0x133, 0x11D);
+
+	ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+		   0x177, 0x101, 0x100, 0x100, SPI_CMD2BK1_SEL);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_VRHS, 0x15D);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_VCOM, 0x143);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_VGHSS, 0x181);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_TESTCMD, 0x180);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_VGLS, 0x143);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_PWCTLR1, 0x185);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_PWCTLR2, 0x120);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_SPD1, 0x178);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_SPD2, 0x178);
+
+	ST7701_SPI(st7701, DSI_CMD2_BK1_MIPISET1, 0x188);
+
+	ST7701_SPI(st7701, 0xE0, 0x100, 0x100, 0x102);
+
+	ST7701_SPI(st7701, 0xE1,
+		   0x103, 0x1A0, 0x100, 0x100, 0x104, 0x1A0, 0x100, 0x100,
+		   0x100, 0x120, 0x120);
+
+	ST7701_SPI(st7701, 0xE2,
+		   0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100,
+		   0x100, 0x100, 0x100, 0x100, 0x100);
+
+	ST7701_SPI(st7701, 0xE3, 0x100, 0x100, 0x111, 0x100);
+
+	ST7701_SPI(st7701, 0xE4, 0x122, 0x100);
+
+	ST7701_SPI(st7701, 0xE5,
+		   0x105, 0x1EC, 0x1A0, 0x1A0, 0x107, 0x1EE, 0x1A0, 0x1A0,
+		   0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100);
+
+	ST7701_SPI(st7701, 0xE6, 0x100, 0x100, 0x111, 0x100);
+
+	ST7701_SPI(st7701, 0xE7, 0x122, 0x100);
+
+	ST7701_SPI(st7701, 0xE8,
+		   0x106, 0x1ED, 0x1A0, 0x1A0, 0x108, 0x1EF, 0x1A0, 0x1A0,
+		   0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100);
+
+	ST7701_SPI(st7701, 0xEB,
+		   0x100, 0x100, 0x140, 0x140, 0x100, 0x100, 0x100);
+
+	ST7701_SPI(st7701, 0xED,
+		   0x1FF, 0x1FF, 0x1FF, 0x1BA, 0x10A, 0x1BF, 0x145, 0x1FF,
+		   0x1FF, 0x154, 0x1FB, 0x1A0, 0x1AB, 0x1FF, 0x1FF, 0x1FF);
+
+	ST7701_SPI(st7701, 0xEF, 0x110, 0x10D, 0x104, 0x108, 0x13F, 0x11F);
+
+	ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+		   0x177, 0x101, 0x100, 0x100, SPI_CMD2BK3_SEL);
+
+	ST7701_SPI(st7701, 0xEF, 0x108);
+
+	ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+		   0x177, 0x101, 0x100, 0x100, SPI_CMD2BKX_SEL_NONE);
+
+	ST7701_SPI(st7701, 0xCD, 0x108);  /* RGB format COLCTRL */
+
+	ST7701_SPI(st7701, 0x36, 0x108); /* MadCtl */
+
+	ST7701_SPI(st7701, 0x3A, 0x166);  /* Colmod */
+
+	ST7701_SPI(st7701, MIPI_DCS_EXIT_SLEEP_MODE);
+}
+
 static int st7701_prepare(struct drm_panel *panel)
 {
 	struct st7701 *st7701 = panel_to_st7701(panel);
@@ -420,7 +565,7 @@ static int st7701_prepare(struct drm_pan
 	gpiod_set_value(st7701->reset, 1);
 	msleep(150);
 
-	st7701_init_sequence(st7701);
+	st7701->desc->init_sequence(st7701);
 
 	if (st7701->desc->gip_sequence)
 		st7701->desc->gip_sequence(st7701);
@@ -436,7 +581,15 @@ static int st7701_enable(struct drm_pane
 {
 	struct st7701 *st7701 = panel_to_st7701(panel);
 
-	ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00);
+	switch (st7701->desc->interface) {
+	case ST7701_CTRL_DSI:
+		ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00);
+		break;
+	case ST7701_CTRL_SPI:
+		ST7701_SPI(st7701, MIPI_DCS_SET_DISPLAY_ON);
+		msleep(30);
+		break;
+	}
 
 	return 0;
 }
@@ -445,7 +598,14 @@ static int st7701_disable(struct drm_pan
 {
 	struct st7701 *st7701 = panel_to_st7701(panel);
 
-	ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_OFF, 0x00);
+	switch (st7701->desc->interface) {
+	case ST7701_CTRL_DSI:
+		ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_OFF, 0x00);
+		break;
+	case ST7701_CTRL_SPI:
+		ST7701_SPI(st7701, MIPI_DCS_SET_DISPLAY_OFF);
+		break;
+	}
 
 	return 0;
 }
@@ -454,7 +614,14 @@ static int st7701_unprepare(struct drm_p
 {
 	struct st7701 *st7701 = panel_to_st7701(panel);
 
-	ST7701_DSI(st7701, MIPI_DCS_ENTER_SLEEP_MODE, 0x00);
+	switch (st7701->desc->interface) {
+	case ST7701_CTRL_DSI:
+		ST7701_DSI(st7701, MIPI_DCS_ENTER_SLEEP_MODE, 0x00);
+		break;
+	case ST7701_CTRL_SPI:
+		ST7701_SPI(st7701, MIPI_DCS_ENTER_SLEEP_MODE);
+		break;
+	}
 
 	msleep(st7701->sleep_delay);
 
@@ -485,7 +652,7 @@ static int st7701_get_modes(struct drm_p
 
 	mode = drm_mode_duplicate(connector->dev, desc_mode);
 	if (!mode) {
-		dev_err(&st7701->dsi->dev, "failed to add mode %ux%u@%u\n",
+		dev_err(st7701->dev, "failed to add mode %ux%u@%u\n",
 			desc_mode->hdisplay, desc_mode->vdisplay,
 			drm_mode_vrefresh(desc_mode));
 		return -ENOMEM;
@@ -494,9 +661,18 @@ static int st7701_get_modes(struct drm_p
 	drm_mode_set_name(mode);
 	drm_mode_probed_add(connector, mode);
 
+	if (st7701->desc->mediabus_format)
+		drm_display_info_set_bus_formats(&connector->display_info,
+						 &st7701->desc->mediabus_format,
+						 1);
+	connector->display_info.bus_flags = 0;
+
 	connector->display_info.width_mm = desc_mode->width_mm;
 	connector->display_info.height_mm = desc_mode->height_mm;
 
+	if (st7701->desc->bus_flags)
+		connector->display_info.bus_flags = st7701->desc->bus_flags;
+
 	return 1;
 }
 
@@ -532,6 +708,9 @@ static const struct st7701_panel_desc ts
 	.lanes = 2,
 	.format = MIPI_DSI_FMT_RGB888,
 	.panel_sleep_delay = 80, /* panel need extra 80ms for sleep out cmd */
+	.init_sequence = ts8550b_init_sequence,
+	.conn_type = DRM_MODE_CONNECTOR_DSI,
+	.interface = ST7701_CTRL_DSI,
 
 	.pv_gamma = {
 		CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
@@ -708,38 +887,74 @@ static const struct st7701_panel_desc dm
 	.gip_sequence = dmt028vghmcmi_1a_gip_sequence,
 };
 
-static int st7701_dsi_probe(struct mipi_dsi_device *dsi)
+static const struct drm_display_mode txw210001b0_mode = {
+	.clock		= 19200,
+
+	.hdisplay	= 480,
+	.hsync_start	= 480 + 10,
+	.hsync_end	= 480 + 10 + 16,
+	.htotal		= 480 + 10 + 16 + 56,
+
+	.vdisplay	= 480,
+	.vsync_start	= 480 + 15,
+	.vsync_end	= 480 + 15 + 60,
+	.vtotal		= 480 + 15 + 60 + 15,
+
+	.width_mm	= 53,
+	.height_mm	= 53,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static const struct st7701_panel_desc txw210001b0_desc = {
+	.mode = &txw210001b0_mode,
+	.mediabus_format = MEDIA_BUS_FMT_RGB888_1X24,
+	.init_sequence = txw210001b0_init_sequence,
+	.conn_type = DRM_MODE_CONNECTOR_DPI,
+	.interface = ST7701_CTRL_SPI,
+	.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+};
+
+static const struct st7701_panel_desc hyperpixel2r_desc = {
+	.mode = &txw210001b0_mode,
+	.mediabus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
+	.init_sequence = txw210001b0_init_sequence,
+	.conn_type = DRM_MODE_CONNECTOR_DPI,
+	.interface = ST7701_CTRL_SPI,
+	.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+};
+
+static int st7701_probe(struct device *dev, struct st7701 **ret_st7701)
 {
 	const struct st7701_panel_desc *desc;
 	struct st7701 *st7701;
 	int ret;
 
-	st7701 = devm_kzalloc(&dsi->dev, sizeof(*st7701), GFP_KERNEL);
+	st7701 = devm_kzalloc(dev, sizeof(*st7701), GFP_KERNEL);
 	if (!st7701)
 		return -ENOMEM;
 
-	desc = of_device_get_match_data(&dsi->dev);
-	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
-			  MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
-	dsi->format = desc->format;
-	dsi->lanes = desc->lanes;
+	desc = of_device_get_match_data(dev);
+	if (!desc)
+		return -EINVAL;
 
 	st7701->supplies[0].supply = "VCC";
 	st7701->supplies[1].supply = "IOVCC";
 
-	ret = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(st7701->supplies),
+	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st7701->supplies),
 				      st7701->supplies);
 	if (ret < 0)
 		return ret;
 
-	st7701->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
+	st7701->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
 	if (IS_ERR(st7701->reset)) {
-		dev_err(&dsi->dev, "Couldn't get our reset GPIO\n");
+		dev_err(dev, "Couldn't get our reset GPIO\n");
 		return PTR_ERR(st7701->reset);
 	}
 
-	drm_panel_init(&st7701->panel, &dsi->dev, &st7701_funcs,
-		       DRM_MODE_CONNECTOR_DSI);
+	drm_panel_init(&st7701->panel, dev, &st7701_funcs,
+		       desc->conn_type);
 
 	/**
 	 * Once sleep out has been issued, ST7701 IC required to wait 120ms
@@ -758,9 +973,30 @@ static int st7701_dsi_probe(struct mipi_
 
 	drm_panel_add(&st7701->panel);
 
+	st7701->desc = desc;
+	st7701->dev = dev;
+
+	*ret_st7701 = st7701;
+
+	return 0;
+}
+
+static int st7701_dsi_probe(struct mipi_dsi_device *dsi)
+{
+	struct st7701 *st7701;
+	int ret;
+
+	ret = st7701_probe(&dsi->dev, &st7701);
+
+	if (ret)
+		return ret;
+
+	dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
+	dsi->format = st7701->desc->format;
+	dsi->lanes = st7701->desc->lanes;
+
 	mipi_dsi_set_drvdata(dsi, st7701);
 	st7701->dsi = dsi;
-	st7701->desc = desc;
 
 	ret = mipi_dsi_attach(dsi);
 	if (ret)
@@ -781,22 +1017,114 @@ static void st7701_dsi_remove(struct mip
 	drm_panel_remove(&st7701->panel);
 }
 
-static const struct of_device_id st7701_of_match[] = {
+static const struct of_device_id st7701_dsi_of_match[] = {
 	{ .compatible = "densitron,dmt028vghmcmi-1a", .data = &dmt028vghmcmi_1a_desc },
 	{ .compatible = "techstar,ts8550b", .data = &ts8550b_desc },
 	{ }
 };
-MODULE_DEVICE_TABLE(of, st7701_of_match);
+MODULE_DEVICE_TABLE(of, st7701_dsi_of_match);
 
 static struct mipi_dsi_driver st7701_dsi_driver = {
 	.probe		= st7701_dsi_probe,
 	.remove		= st7701_dsi_remove,
 	.driver = {
 		.name		= "st7701",
-		.of_match_table	= st7701_of_match,
+		.of_match_table	= st7701_dsi_of_match,
 	},
 };
-module_mipi_dsi_driver(st7701_dsi_driver);
+
+/* SPI display  probe */
+static const struct of_device_id st7701_spi_of_match[] = {
+	{	.compatible = "txw,txw210001b0",
+		.data = &txw210001b0_desc,
+	}, {
+		.compatible = "pimoroni,hyperpixel2round",
+		.data = &hyperpixel2r_desc,
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, st7701_spi_of_match);
+
+static int st7701_spi_probe(struct spi_device *spi)
+{
+	struct st7701 *st7701;
+	int ret;
+
+	spi->mode = SPI_MODE_3;
+	spi->bits_per_word = 9;
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+		return ret;
+	}
+
+	ret = st7701_probe(&spi->dev, &st7701);
+
+	if (ret)
+		return ret;
+
+	spi_set_drvdata(spi, st7701);
+	st7701->spi = spi;
+
+	return 0;
+}
+
+static void st7701_spi_remove(struct spi_device *spi)
+{
+	struct st7701 *ctx = spi_get_drvdata(spi);
+
+	drm_panel_remove(&ctx->panel);
+}
+
+static const struct spi_device_id st7701_spi_ids[] = {
+	{ "txw210001b0", 0 },
+	{ "hyperpixel2round", 0 },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, st7701_spi_ids);
+
+static struct spi_driver st7701_spi_driver = {
+	.probe = st7701_spi_probe,
+	.remove = st7701_spi_remove,
+	.driver = {
+		.name = "st7701",
+		.of_match_table = st7701_spi_of_match,
+	},
+	.id_table = st7701_spi_ids,
+};
+
+static int __init panel_st7701_init(void)
+{
+	int err;
+
+	err = spi_register_driver(&st7701_spi_driver);
+	if (err < 0)
+		return err;
+
+	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
+		err = mipi_dsi_driver_register(&st7701_dsi_driver);
+		if (err < 0)
+			goto err_did_spi_register;
+	}
+
+	return 0;
+
+err_did_spi_register:
+	spi_unregister_driver(&st7701_spi_driver);
+
+	return err;
+}
+module_init(panel_st7701_init);
+
+static void __exit panel_st7701_exit(void)
+{
+	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+		mipi_dsi_driver_unregister(&st7701_dsi_driver);
+
+	spi_unregister_driver(&st7701_spi_driver);
+}
+module_exit(panel_st7701_exit);
 
 MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>");
 MODULE_DESCRIPTION("Sitronix ST7701 LCD Panel Driver");
