162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Panel driver for the TPO TPG110 400CH LTPS TFT LCD Single Chip
462306a36Sopenharmony_ci * Digital Driver.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This chip drives a TFT LCD, so it does not know what kind of
762306a36Sopenharmony_ci * display is actually connected to it, so the width and height of that
862306a36Sopenharmony_ci * display needs to be supplied from the machine configuration.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Author:
1162306a36Sopenharmony_ci * Linus Walleij <linus.walleij@linaro.org>
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci#include <drm/drm_modes.h>
1462306a36Sopenharmony_ci#include <drm/drm_panel.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/bitops.h>
1762306a36Sopenharmony_ci#include <linux/delay.h>
1862306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1962306a36Sopenharmony_ci#include <linux/init.h>
2062306a36Sopenharmony_ci#include <linux/kernel.h>
2162306a36Sopenharmony_ci#include <linux/module.h>
2262306a36Sopenharmony_ci#include <linux/of.h>
2362306a36Sopenharmony_ci#include <linux/platform_device.h>
2462306a36Sopenharmony_ci#include <linux/spi/spi.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define TPG110_TEST			0x00
2762306a36Sopenharmony_ci#define TPG110_CHIPID			0x01
2862306a36Sopenharmony_ci#define TPG110_CTRL1			0x02
2962306a36Sopenharmony_ci#define TPG110_RES_MASK			GENMASK(2, 0)
3062306a36Sopenharmony_ci#define TPG110_RES_800X480		0x07
3162306a36Sopenharmony_ci#define TPG110_RES_640X480		0x06
3262306a36Sopenharmony_ci#define TPG110_RES_480X272		0x05
3362306a36Sopenharmony_ci#define TPG110_RES_480X640		0x04
3462306a36Sopenharmony_ci#define TPG110_RES_480X272_D		0x01 /* Dual scan: outputs 800x480 */
3562306a36Sopenharmony_ci#define TPG110_RES_400X240_D		0x00 /* Dual scan: outputs 800x480 */
3662306a36Sopenharmony_ci#define TPG110_CTRL2			0x03
3762306a36Sopenharmony_ci#define TPG110_CTRL2_PM			BIT(0)
3862306a36Sopenharmony_ci#define TPG110_CTRL2_RES_PM_CTRL	BIT(7)
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/**
4162306a36Sopenharmony_ci * struct tpg110_panel_mode - lookup struct for the supported modes
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_cistruct tpg110_panel_mode {
4462306a36Sopenharmony_ci	/**
4562306a36Sopenharmony_ci	 * @name: the name of this panel
4662306a36Sopenharmony_ci	 */
4762306a36Sopenharmony_ci	const char *name;
4862306a36Sopenharmony_ci	/**
4962306a36Sopenharmony_ci	 * @magic: the magic value from the detection register
5062306a36Sopenharmony_ci	 */
5162306a36Sopenharmony_ci	u32 magic;
5262306a36Sopenharmony_ci	/**
5362306a36Sopenharmony_ci	 * @mode: the DRM display mode for this panel
5462306a36Sopenharmony_ci	 */
5562306a36Sopenharmony_ci	struct drm_display_mode mode;
5662306a36Sopenharmony_ci	/**
5762306a36Sopenharmony_ci	 * @bus_flags: the DRM bus flags for this panel e.g. inverted clock
5862306a36Sopenharmony_ci	 */
5962306a36Sopenharmony_ci	u32 bus_flags;
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/**
6362306a36Sopenharmony_ci * struct tpg110 - state container for the TPG110 panel
6462306a36Sopenharmony_ci */
6562306a36Sopenharmony_cistruct tpg110 {
6662306a36Sopenharmony_ci	/**
6762306a36Sopenharmony_ci	 * @dev: the container device
6862306a36Sopenharmony_ci	 */
6962306a36Sopenharmony_ci	struct device *dev;
7062306a36Sopenharmony_ci	/**
7162306a36Sopenharmony_ci	 * @spi: the corresponding SPI device
7262306a36Sopenharmony_ci	 */
7362306a36Sopenharmony_ci	struct spi_device *spi;
7462306a36Sopenharmony_ci	/**
7562306a36Sopenharmony_ci	 * @panel: the DRM panel instance for this device
7662306a36Sopenharmony_ci	 */
7762306a36Sopenharmony_ci	struct drm_panel panel;
7862306a36Sopenharmony_ci	/**
7962306a36Sopenharmony_ci	 * @panel_mode: the panel mode as detected
8062306a36Sopenharmony_ci	 */
8162306a36Sopenharmony_ci	const struct tpg110_panel_mode *panel_mode;
8262306a36Sopenharmony_ci	/**
8362306a36Sopenharmony_ci	 * @width: the width of this panel in mm
8462306a36Sopenharmony_ci	 */
8562306a36Sopenharmony_ci	u32 width;
8662306a36Sopenharmony_ci	/**
8762306a36Sopenharmony_ci	 * @height: the height of this panel in mm
8862306a36Sopenharmony_ci	 */
8962306a36Sopenharmony_ci	u32 height;
9062306a36Sopenharmony_ci	/**
9162306a36Sopenharmony_ci	 * @grestb: reset GPIO line
9262306a36Sopenharmony_ci	 */
9362306a36Sopenharmony_ci	struct gpio_desc *grestb;
9462306a36Sopenharmony_ci};
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci/*
9762306a36Sopenharmony_ci * TPG110 modes, these are the simple modes, the dualscan modes that
9862306a36Sopenharmony_ci * take 400x240 or 480x272 in and display as 800x480 are not listed.
9962306a36Sopenharmony_ci */
10062306a36Sopenharmony_cistatic const struct tpg110_panel_mode tpg110_modes[] = {
10162306a36Sopenharmony_ci	{
10262306a36Sopenharmony_ci		.name = "800x480 RGB",
10362306a36Sopenharmony_ci		.magic = TPG110_RES_800X480,
10462306a36Sopenharmony_ci		.mode = {
10562306a36Sopenharmony_ci			.clock = 33200,
10662306a36Sopenharmony_ci			.hdisplay = 800,
10762306a36Sopenharmony_ci			.hsync_start = 800 + 40,
10862306a36Sopenharmony_ci			.hsync_end = 800 + 40 + 1,
10962306a36Sopenharmony_ci			.htotal = 800 + 40 + 1 + 216,
11062306a36Sopenharmony_ci			.vdisplay = 480,
11162306a36Sopenharmony_ci			.vsync_start = 480 + 10,
11262306a36Sopenharmony_ci			.vsync_end = 480 + 10 + 1,
11362306a36Sopenharmony_ci			.vtotal = 480 + 10 + 1 + 35,
11462306a36Sopenharmony_ci		},
11562306a36Sopenharmony_ci		.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
11662306a36Sopenharmony_ci	},
11762306a36Sopenharmony_ci	{
11862306a36Sopenharmony_ci		.name = "640x480 RGB",
11962306a36Sopenharmony_ci		.magic = TPG110_RES_640X480,
12062306a36Sopenharmony_ci		.mode = {
12162306a36Sopenharmony_ci			.clock = 25200,
12262306a36Sopenharmony_ci			.hdisplay = 640,
12362306a36Sopenharmony_ci			.hsync_start = 640 + 24,
12462306a36Sopenharmony_ci			.hsync_end = 640 + 24 + 1,
12562306a36Sopenharmony_ci			.htotal = 640 + 24 + 1 + 136,
12662306a36Sopenharmony_ci			.vdisplay = 480,
12762306a36Sopenharmony_ci			.vsync_start = 480 + 18,
12862306a36Sopenharmony_ci			.vsync_end = 480 + 18 + 1,
12962306a36Sopenharmony_ci			.vtotal = 480 + 18 + 1 + 27,
13062306a36Sopenharmony_ci		},
13162306a36Sopenharmony_ci		.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
13262306a36Sopenharmony_ci	},
13362306a36Sopenharmony_ci	{
13462306a36Sopenharmony_ci		.name = "480x272 RGB",
13562306a36Sopenharmony_ci		.magic = TPG110_RES_480X272,
13662306a36Sopenharmony_ci		.mode = {
13762306a36Sopenharmony_ci			.clock = 9000,
13862306a36Sopenharmony_ci			.hdisplay = 480,
13962306a36Sopenharmony_ci			.hsync_start = 480 + 2,
14062306a36Sopenharmony_ci			.hsync_end = 480 + 2 + 1,
14162306a36Sopenharmony_ci			.htotal = 480 + 2 + 1 + 43,
14262306a36Sopenharmony_ci			.vdisplay = 272,
14362306a36Sopenharmony_ci			.vsync_start = 272 + 2,
14462306a36Sopenharmony_ci			.vsync_end = 272 + 2 + 1,
14562306a36Sopenharmony_ci			.vtotal = 272 + 2 + 1 + 12,
14662306a36Sopenharmony_ci		},
14762306a36Sopenharmony_ci		.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
14862306a36Sopenharmony_ci	},
14962306a36Sopenharmony_ci	{
15062306a36Sopenharmony_ci		.name = "480x640 RGB",
15162306a36Sopenharmony_ci		.magic = TPG110_RES_480X640,
15262306a36Sopenharmony_ci		.mode = {
15362306a36Sopenharmony_ci			.clock = 20500,
15462306a36Sopenharmony_ci			.hdisplay = 480,
15562306a36Sopenharmony_ci			.hsync_start = 480 + 2,
15662306a36Sopenharmony_ci			.hsync_end = 480 + 2 + 1,
15762306a36Sopenharmony_ci			.htotal = 480 + 2 + 1 + 43,
15862306a36Sopenharmony_ci			.vdisplay = 640,
15962306a36Sopenharmony_ci			.vsync_start = 640 + 4,
16062306a36Sopenharmony_ci			.vsync_end = 640 + 4 + 1,
16162306a36Sopenharmony_ci			.vtotal = 640 + 4 + 1 + 8,
16262306a36Sopenharmony_ci		},
16362306a36Sopenharmony_ci		.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
16462306a36Sopenharmony_ci	},
16562306a36Sopenharmony_ci	{
16662306a36Sopenharmony_ci		.name = "400x240 RGB",
16762306a36Sopenharmony_ci		.magic = TPG110_RES_400X240_D,
16862306a36Sopenharmony_ci		.mode = {
16962306a36Sopenharmony_ci			.clock = 8300,
17062306a36Sopenharmony_ci			.hdisplay = 400,
17162306a36Sopenharmony_ci			.hsync_start = 400 + 20,
17262306a36Sopenharmony_ci			.hsync_end = 400 + 20 + 1,
17362306a36Sopenharmony_ci			.htotal = 400 + 20 + 1 + 108,
17462306a36Sopenharmony_ci			.vdisplay = 240,
17562306a36Sopenharmony_ci			.vsync_start = 240 + 2,
17662306a36Sopenharmony_ci			.vsync_end = 240 + 2 + 1,
17762306a36Sopenharmony_ci			.vtotal = 240 + 2 + 1 + 20,
17862306a36Sopenharmony_ci		},
17962306a36Sopenharmony_ci		.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
18062306a36Sopenharmony_ci	},
18162306a36Sopenharmony_ci};
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic inline struct tpg110 *
18462306a36Sopenharmony_cito_tpg110(struct drm_panel *panel)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	return container_of(panel, struct tpg110, panel);
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic u8 tpg110_readwrite_reg(struct tpg110 *tpg, bool write,
19062306a36Sopenharmony_ci			       u8 address, u8 outval)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	struct spi_message m;
19362306a36Sopenharmony_ci	struct spi_transfer t[2];
19462306a36Sopenharmony_ci	u8 buf[2];
19562306a36Sopenharmony_ci	int ret;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	spi_message_init(&m);
19862306a36Sopenharmony_ci	memset(t, 0, sizeof(t));
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (write) {
20162306a36Sopenharmony_ci		/*
20262306a36Sopenharmony_ci		 * Clear address bit 0, 1 when writing, just to be sure
20362306a36Sopenharmony_ci		 * The actual bit indicating a write here is bit 1, bit
20462306a36Sopenharmony_ci		 * 0 is just surplus to pad it up to 8 bits.
20562306a36Sopenharmony_ci		 */
20662306a36Sopenharmony_ci		buf[0] = address << 2;
20762306a36Sopenharmony_ci		buf[0] &= ~0x03;
20862306a36Sopenharmony_ci		buf[1] = outval;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		t[0].bits_per_word = 8;
21162306a36Sopenharmony_ci		t[0].tx_buf = &buf[0];
21262306a36Sopenharmony_ci		t[0].len = 1;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		t[1].tx_buf = &buf[1];
21562306a36Sopenharmony_ci		t[1].len = 1;
21662306a36Sopenharmony_ci		t[1].bits_per_word = 8;
21762306a36Sopenharmony_ci	} else {
21862306a36Sopenharmony_ci		/* Set address bit 0 to 1 to read */
21962306a36Sopenharmony_ci		buf[0] = address << 1;
22062306a36Sopenharmony_ci		buf[0] |= 0x01;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		/*
22362306a36Sopenharmony_ci		 * The last bit/clock is Hi-Z turnaround cycle, so we need
22462306a36Sopenharmony_ci		 * to send only 7 bits here. The 8th bit is the high impedance
22562306a36Sopenharmony_ci		 * turn-around cycle.
22662306a36Sopenharmony_ci		 */
22762306a36Sopenharmony_ci		t[0].bits_per_word = 7;
22862306a36Sopenharmony_ci		t[0].tx_buf = &buf[0];
22962306a36Sopenharmony_ci		t[0].len = 1;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		t[1].rx_buf = &buf[1];
23262306a36Sopenharmony_ci		t[1].len = 1;
23362306a36Sopenharmony_ci		t[1].bits_per_word = 8;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	spi_message_add_tail(&t[0], &m);
23762306a36Sopenharmony_ci	spi_message_add_tail(&t[1], &m);
23862306a36Sopenharmony_ci	ret = spi_sync(tpg->spi, &m);
23962306a36Sopenharmony_ci	if (ret) {
24062306a36Sopenharmony_ci		dev_err(tpg->dev, "SPI message error %d\n", ret);
24162306a36Sopenharmony_ci		return ret;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci	if (write)
24462306a36Sopenharmony_ci		return 0;
24562306a36Sopenharmony_ci	/* Read */
24662306a36Sopenharmony_ci	return buf[1];
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic u8 tpg110_read_reg(struct tpg110 *tpg, u8 address)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	return tpg110_readwrite_reg(tpg, false, address, 0);
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistatic void tpg110_write_reg(struct tpg110 *tpg, u8 address, u8 outval)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	tpg110_readwrite_reg(tpg, true, address, outval);
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic int tpg110_startup(struct tpg110 *tpg)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	u8 val;
26262306a36Sopenharmony_ci	int i;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	/* De-assert the reset signal */
26562306a36Sopenharmony_ci	gpiod_set_value_cansleep(tpg->grestb, 0);
26662306a36Sopenharmony_ci	usleep_range(1000, 2000);
26762306a36Sopenharmony_ci	dev_dbg(tpg->dev, "de-asserted GRESTB\n");
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* Test display communication */
27062306a36Sopenharmony_ci	tpg110_write_reg(tpg, TPG110_TEST, 0x55);
27162306a36Sopenharmony_ci	val = tpg110_read_reg(tpg, TPG110_TEST);
27262306a36Sopenharmony_ci	if (val != 0x55) {
27362306a36Sopenharmony_ci		dev_err(tpg->dev, "failed communication test\n");
27462306a36Sopenharmony_ci		return -ENODEV;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	val = tpg110_read_reg(tpg, TPG110_CHIPID);
27862306a36Sopenharmony_ci	dev_info(tpg->dev, "TPG110 chip ID: %d version: %d\n",
27962306a36Sopenharmony_ci		 val >> 4, val & 0x0f);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	/* Show display resolution */
28262306a36Sopenharmony_ci	val = tpg110_read_reg(tpg, TPG110_CTRL1);
28362306a36Sopenharmony_ci	val &= TPG110_RES_MASK;
28462306a36Sopenharmony_ci	switch (val) {
28562306a36Sopenharmony_ci	case TPG110_RES_400X240_D:
28662306a36Sopenharmony_ci		dev_info(tpg->dev, "IN 400x240 RGB -> OUT 800x480 RGB (dual scan)\n");
28762306a36Sopenharmony_ci		break;
28862306a36Sopenharmony_ci	case TPG110_RES_480X272_D:
28962306a36Sopenharmony_ci		dev_info(tpg->dev, "IN 480x272 RGB -> OUT 800x480 RGB (dual scan)\n");
29062306a36Sopenharmony_ci		break;
29162306a36Sopenharmony_ci	case TPG110_RES_480X640:
29262306a36Sopenharmony_ci		dev_info(tpg->dev, "480x640 RGB\n");
29362306a36Sopenharmony_ci		break;
29462306a36Sopenharmony_ci	case TPG110_RES_480X272:
29562306a36Sopenharmony_ci		dev_info(tpg->dev, "480x272 RGB\n");
29662306a36Sopenharmony_ci		break;
29762306a36Sopenharmony_ci	case TPG110_RES_640X480:
29862306a36Sopenharmony_ci		dev_info(tpg->dev, "640x480 RGB\n");
29962306a36Sopenharmony_ci		break;
30062306a36Sopenharmony_ci	case TPG110_RES_800X480:
30162306a36Sopenharmony_ci		dev_info(tpg->dev, "800x480 RGB\n");
30262306a36Sopenharmony_ci		break;
30362306a36Sopenharmony_ci	default:
30462306a36Sopenharmony_ci		dev_err(tpg->dev, "ILLEGAL RESOLUTION 0x%02x\n", val);
30562306a36Sopenharmony_ci		break;
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	/* From the producer side, this is the same resolution */
30962306a36Sopenharmony_ci	if (val == TPG110_RES_480X272_D)
31062306a36Sopenharmony_ci		val = TPG110_RES_480X272;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(tpg110_modes); i++) {
31362306a36Sopenharmony_ci		const struct tpg110_panel_mode *pm;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci		pm = &tpg110_modes[i];
31662306a36Sopenharmony_ci		if (pm->magic == val) {
31762306a36Sopenharmony_ci			tpg->panel_mode = pm;
31862306a36Sopenharmony_ci			break;
31962306a36Sopenharmony_ci		}
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci	if (i == ARRAY_SIZE(tpg110_modes)) {
32262306a36Sopenharmony_ci		dev_err(tpg->dev, "unsupported mode (%02x) detected\n", val);
32362306a36Sopenharmony_ci		return -ENODEV;
32462306a36Sopenharmony_ci	}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	val = tpg110_read_reg(tpg, TPG110_CTRL2);
32762306a36Sopenharmony_ci	dev_info(tpg->dev, "resolution and standby is controlled by %s\n",
32862306a36Sopenharmony_ci		 (val & TPG110_CTRL2_RES_PM_CTRL) ? "software" : "hardware");
32962306a36Sopenharmony_ci	/* Take control over resolution and standby */
33062306a36Sopenharmony_ci	val |= TPG110_CTRL2_RES_PM_CTRL;
33162306a36Sopenharmony_ci	tpg110_write_reg(tpg, TPG110_CTRL2, val);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	return 0;
33462306a36Sopenharmony_ci}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_cistatic int tpg110_disable(struct drm_panel *panel)
33762306a36Sopenharmony_ci{
33862306a36Sopenharmony_ci	struct tpg110 *tpg = to_tpg110(panel);
33962306a36Sopenharmony_ci	u8 val;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	/* Put chip into standby */
34262306a36Sopenharmony_ci	val = tpg110_read_reg(tpg, TPG110_CTRL2_PM);
34362306a36Sopenharmony_ci	val &= ~TPG110_CTRL2_PM;
34462306a36Sopenharmony_ci	tpg110_write_reg(tpg, TPG110_CTRL2_PM, val);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	return 0;
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic int tpg110_enable(struct drm_panel *panel)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	struct tpg110 *tpg = to_tpg110(panel);
35262306a36Sopenharmony_ci	u8 val;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	/* Take chip out of standby */
35562306a36Sopenharmony_ci	val = tpg110_read_reg(tpg, TPG110_CTRL2_PM);
35662306a36Sopenharmony_ci	val |= TPG110_CTRL2_PM;
35762306a36Sopenharmony_ci	tpg110_write_reg(tpg, TPG110_CTRL2_PM, val);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	return 0;
36062306a36Sopenharmony_ci}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci/**
36362306a36Sopenharmony_ci * tpg110_get_modes() - return the appropriate mode
36462306a36Sopenharmony_ci * @panel: the panel to get the mode for
36562306a36Sopenharmony_ci * @connector: reference to the central DRM connector control structure
36662306a36Sopenharmony_ci *
36762306a36Sopenharmony_ci * This currently does not present a forest of modes, instead it
36862306a36Sopenharmony_ci * presents the mode that is configured for the system under use,
36962306a36Sopenharmony_ci * and which is detected by reading the registers of the display.
37062306a36Sopenharmony_ci */
37162306a36Sopenharmony_cistatic int tpg110_get_modes(struct drm_panel *panel,
37262306a36Sopenharmony_ci			    struct drm_connector *connector)
37362306a36Sopenharmony_ci{
37462306a36Sopenharmony_ci	struct tpg110 *tpg = to_tpg110(panel);
37562306a36Sopenharmony_ci	struct drm_display_mode *mode;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	connector->display_info.width_mm = tpg->width;
37862306a36Sopenharmony_ci	connector->display_info.height_mm = tpg->height;
37962306a36Sopenharmony_ci	connector->display_info.bus_flags = tpg->panel_mode->bus_flags;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, &tpg->panel_mode->mode);
38262306a36Sopenharmony_ci	if (!mode)
38362306a36Sopenharmony_ci		return -ENOMEM;
38462306a36Sopenharmony_ci	drm_mode_set_name(mode);
38562306a36Sopenharmony_ci	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	mode->width_mm = tpg->width;
38862306a36Sopenharmony_ci	mode->height_mm = tpg->height;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	drm_mode_probed_add(connector, mode);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	return 1;
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cistatic const struct drm_panel_funcs tpg110_drm_funcs = {
39662306a36Sopenharmony_ci	.disable = tpg110_disable,
39762306a36Sopenharmony_ci	.enable = tpg110_enable,
39862306a36Sopenharmony_ci	.get_modes = tpg110_get_modes,
39962306a36Sopenharmony_ci};
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic int tpg110_probe(struct spi_device *spi)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	struct device *dev = &spi->dev;
40462306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
40562306a36Sopenharmony_ci	struct tpg110 *tpg;
40662306a36Sopenharmony_ci	int ret;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	tpg = devm_kzalloc(dev, sizeof(*tpg), GFP_KERNEL);
40962306a36Sopenharmony_ci	if (!tpg)
41062306a36Sopenharmony_ci		return -ENOMEM;
41162306a36Sopenharmony_ci	tpg->dev = dev;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	/* We get the physical display dimensions from the DT */
41462306a36Sopenharmony_ci	ret = of_property_read_u32(np, "width-mm", &tpg->width);
41562306a36Sopenharmony_ci	if (ret)
41662306a36Sopenharmony_ci		dev_err(dev, "no panel width specified\n");
41762306a36Sopenharmony_ci	ret = of_property_read_u32(np, "height-mm", &tpg->height);
41862306a36Sopenharmony_ci	if (ret)
41962306a36Sopenharmony_ci		dev_err(dev, "no panel height specified\n");
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	/* This asserts the GRESTB signal, putting the display into reset */
42262306a36Sopenharmony_ci	tpg->grestb = devm_gpiod_get(dev, "grestb", GPIOD_OUT_HIGH);
42362306a36Sopenharmony_ci	if (IS_ERR(tpg->grestb)) {
42462306a36Sopenharmony_ci		dev_err(dev, "no GRESTB GPIO\n");
42562306a36Sopenharmony_ci		return -ENODEV;
42662306a36Sopenharmony_ci	}
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	spi->bits_per_word = 8;
42962306a36Sopenharmony_ci	spi->mode |= SPI_3WIRE_HIZ;
43062306a36Sopenharmony_ci	ret = spi_setup(spi);
43162306a36Sopenharmony_ci	if (ret < 0) {
43262306a36Sopenharmony_ci		dev_err(dev, "spi setup failed.\n");
43362306a36Sopenharmony_ci		return ret;
43462306a36Sopenharmony_ci	}
43562306a36Sopenharmony_ci	tpg->spi = spi;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	ret = tpg110_startup(tpg);
43862306a36Sopenharmony_ci	if (ret)
43962306a36Sopenharmony_ci		return ret;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	drm_panel_init(&tpg->panel, dev, &tpg110_drm_funcs,
44262306a36Sopenharmony_ci		       DRM_MODE_CONNECTOR_DPI);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	ret = drm_panel_of_backlight(&tpg->panel);
44562306a36Sopenharmony_ci	if (ret)
44662306a36Sopenharmony_ci		return ret;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	spi_set_drvdata(spi, tpg);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	drm_panel_add(&tpg->panel);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	return 0;
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic void tpg110_remove(struct spi_device *spi)
45662306a36Sopenharmony_ci{
45762306a36Sopenharmony_ci	struct tpg110 *tpg = spi_get_drvdata(spi);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	drm_panel_remove(&tpg->panel);
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_cistatic const struct of_device_id tpg110_match[] = {
46362306a36Sopenharmony_ci	{ .compatible = "tpo,tpg110", },
46462306a36Sopenharmony_ci	{},
46562306a36Sopenharmony_ci};
46662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tpg110_match);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_cistatic const struct spi_device_id tpg110_ids[] = {
46962306a36Sopenharmony_ci	{ "tpg110" },
47062306a36Sopenharmony_ci	{ },
47162306a36Sopenharmony_ci};
47262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(spi, tpg110_ids);
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_cistatic struct spi_driver tpg110_driver = {
47562306a36Sopenharmony_ci	.probe		= tpg110_probe,
47662306a36Sopenharmony_ci	.remove		= tpg110_remove,
47762306a36Sopenharmony_ci	.id_table	= tpg110_ids,
47862306a36Sopenharmony_ci	.driver		= {
47962306a36Sopenharmony_ci		.name	= "tpo-tpg110-panel",
48062306a36Sopenharmony_ci		.of_match_table = tpg110_match,
48162306a36Sopenharmony_ci	},
48262306a36Sopenharmony_ci};
48362306a36Sopenharmony_cimodule_spi_driver(tpg110_driver);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ciMODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
48662306a36Sopenharmony_ciMODULE_DESCRIPTION("TPO TPG110 panel driver");
48762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
488