162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * DRM driver for Ilitek ILI9486 panels
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2020 Kamlesh Gurudasani <kamlesh.gurudasani@gmail.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/backlight.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/property.h>
1362306a36Sopenharmony_ci#include <linux/spi/spi.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <video/mipi_display.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
1862306a36Sopenharmony_ci#include <drm/drm_drv.h>
1962306a36Sopenharmony_ci#include <drm/drm_fbdev_generic.h>
2062306a36Sopenharmony_ci#include <drm/drm_gem_atomic_helper.h>
2162306a36Sopenharmony_ci#include <drm/drm_gem_dma_helper.h>
2262306a36Sopenharmony_ci#include <drm/drm_managed.h>
2362306a36Sopenharmony_ci#include <drm/drm_mipi_dbi.h>
2462306a36Sopenharmony_ci#include <drm/drm_modeset_helper.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define ILI9486_ITFCTR1         0xb0
2762306a36Sopenharmony_ci#define ILI9486_PWCTRL1         0xc2
2862306a36Sopenharmony_ci#define ILI9486_VMCTRL1         0xc5
2962306a36Sopenharmony_ci#define ILI9486_PGAMCTRL        0xe0
3062306a36Sopenharmony_ci#define ILI9486_NGAMCTRL        0xe1
3162306a36Sopenharmony_ci#define ILI9486_DGAMCTRL        0xe2
3262306a36Sopenharmony_ci#define ILI9486_MADCTL_BGR      BIT(3)
3362306a36Sopenharmony_ci#define ILI9486_MADCTL_MV       BIT(5)
3462306a36Sopenharmony_ci#define ILI9486_MADCTL_MX       BIT(6)
3562306a36Sopenharmony_ci#define ILI9486_MADCTL_MY       BIT(7)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci/*
3862306a36Sopenharmony_ci * The PiScreen/waveshare rpi-lcd-35 has a SPI to 16-bit parallel bus converter
3962306a36Sopenharmony_ci * in front of the  display controller. This means that 8-bit values have to be
4062306a36Sopenharmony_ci * transferred as 16-bit.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_cistatic int waveshare_command(struct mipi_dbi *mipi, u8 *cmd, u8 *par,
4362306a36Sopenharmony_ci			     size_t num)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct spi_device *spi = mipi->spi;
4662306a36Sopenharmony_ci	unsigned int bpw = 8;
4762306a36Sopenharmony_ci	void *data = par;
4862306a36Sopenharmony_ci	u32 speed_hz;
4962306a36Sopenharmony_ci	int i, ret;
5062306a36Sopenharmony_ci	__be16 *buf;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	buf = kmalloc(32 * sizeof(u16), GFP_KERNEL);
5362306a36Sopenharmony_ci	if (!buf)
5462306a36Sopenharmony_ci		return -ENOMEM;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	/*
5762306a36Sopenharmony_ci	 * The displays are Raspberry Pi HATs and connected to the 8-bit only
5862306a36Sopenharmony_ci	 * SPI controller, so 16-bit command and parameters need byte swapping
5962306a36Sopenharmony_ci	 * before being transferred as 8-bit on the big endian SPI bus.
6062306a36Sopenharmony_ci	 */
6162306a36Sopenharmony_ci	buf[0] = cpu_to_be16(*cmd);
6262306a36Sopenharmony_ci	spi_bus_lock(spi->controller);
6362306a36Sopenharmony_ci	gpiod_set_value_cansleep(mipi->dc, 0);
6462306a36Sopenharmony_ci	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 2);
6562306a36Sopenharmony_ci	ret = mipi_dbi_spi_transfer(spi, speed_hz, 8, buf, 2);
6662306a36Sopenharmony_ci	spi_bus_unlock(spi->controller);
6762306a36Sopenharmony_ci	if (ret || !num)
6862306a36Sopenharmony_ci		goto free;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	/* 8-bit configuration data, not 16-bit pixel data */
7162306a36Sopenharmony_ci	if (num <= 32) {
7262306a36Sopenharmony_ci		for (i = 0; i < num; i++)
7362306a36Sopenharmony_ci			buf[i] = cpu_to_be16(par[i]);
7462306a36Sopenharmony_ci		num *= 2;
7562306a36Sopenharmony_ci		data = buf;
7662306a36Sopenharmony_ci	}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/*
7962306a36Sopenharmony_ci	 * Check whether pixel data bytes needs to be swapped or not
8062306a36Sopenharmony_ci	 */
8162306a36Sopenharmony_ci	if (*cmd == MIPI_DCS_WRITE_MEMORY_START && !mipi->swap_bytes)
8262306a36Sopenharmony_ci		bpw = 16;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	spi_bus_lock(spi->controller);
8562306a36Sopenharmony_ci	gpiod_set_value_cansleep(mipi->dc, 1);
8662306a36Sopenharmony_ci	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
8762306a36Sopenharmony_ci	ret = mipi_dbi_spi_transfer(spi, speed_hz, bpw, data, num);
8862306a36Sopenharmony_ci	spi_bus_unlock(spi->controller);
8962306a36Sopenharmony_ci free:
9062306a36Sopenharmony_ci	kfree(buf);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	return ret;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic void waveshare_enable(struct drm_simple_display_pipe *pipe,
9662306a36Sopenharmony_ci			     struct drm_crtc_state *crtc_state,
9762306a36Sopenharmony_ci			     struct drm_plane_state *plane_state)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
10062306a36Sopenharmony_ci	struct mipi_dbi *dbi = &dbidev->dbi;
10162306a36Sopenharmony_ci	u8 addr_mode;
10262306a36Sopenharmony_ci	int ret, idx;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (!drm_dev_enter(pipe->crtc.dev, &idx))
10562306a36Sopenharmony_ci		return;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	DRM_DEBUG_KMS("\n");
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	ret = mipi_dbi_poweron_conditional_reset(dbidev);
11062306a36Sopenharmony_ci	if (ret < 0)
11162306a36Sopenharmony_ci		goto out_exit;
11262306a36Sopenharmony_ci	if (ret == 1)
11362306a36Sopenharmony_ci		goto out_enable;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	mipi_dbi_command(dbi, ILI9486_ITFCTR1);
11662306a36Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
11762306a36Sopenharmony_ci	msleep(250);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	mipi_dbi_command(dbi, ILI9486_PWCTRL1, 0x44);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	mipi_dbi_command(dbi, ILI9486_VMCTRL1, 0x00, 0x00, 0x00, 0x00);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	mipi_dbi_command(dbi, ILI9486_PGAMCTRL,
12662306a36Sopenharmony_ci			 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98,
12762306a36Sopenharmony_ci			 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x0);
12862306a36Sopenharmony_ci	mipi_dbi_command(dbi, ILI9486_NGAMCTRL,
12962306a36Sopenharmony_ci			 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
13062306a36Sopenharmony_ci			 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00);
13162306a36Sopenharmony_ci	mipi_dbi_command(dbi, ILI9486_DGAMCTRL,
13262306a36Sopenharmony_ci			 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
13362306a36Sopenharmony_ci			 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
13662306a36Sopenharmony_ci	msleep(100);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci out_enable:
13962306a36Sopenharmony_ci	switch (dbidev->rotation) {
14062306a36Sopenharmony_ci	case 90:
14162306a36Sopenharmony_ci		addr_mode = ILI9486_MADCTL_MY;
14262306a36Sopenharmony_ci		break;
14362306a36Sopenharmony_ci	case 180:
14462306a36Sopenharmony_ci		addr_mode = ILI9486_MADCTL_MV;
14562306a36Sopenharmony_ci		break;
14662306a36Sopenharmony_ci	case 270:
14762306a36Sopenharmony_ci		addr_mode = ILI9486_MADCTL_MX;
14862306a36Sopenharmony_ci		break;
14962306a36Sopenharmony_ci	default:
15062306a36Sopenharmony_ci		addr_mode = ILI9486_MADCTL_MV | ILI9486_MADCTL_MY |
15162306a36Sopenharmony_ci			ILI9486_MADCTL_MX;
15262306a36Sopenharmony_ci		break;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci	addr_mode |= ILI9486_MADCTL_BGR;
15562306a36Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
15662306a36Sopenharmony_ci	mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);
15762306a36Sopenharmony_ci out_exit:
15862306a36Sopenharmony_ci	drm_dev_exit(idx);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic const struct drm_simple_display_pipe_funcs waveshare_pipe_funcs = {
16262306a36Sopenharmony_ci	DRM_MIPI_DBI_SIMPLE_DISPLAY_PIPE_FUNCS(waveshare_enable),
16362306a36Sopenharmony_ci};
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic const struct drm_display_mode waveshare_mode = {
16662306a36Sopenharmony_ci	DRM_SIMPLE_MODE(480, 320, 73, 49),
16762306a36Sopenharmony_ci};
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ciDEFINE_DRM_GEM_DMA_FOPS(ili9486_fops);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic const struct drm_driver ili9486_driver = {
17262306a36Sopenharmony_ci	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
17362306a36Sopenharmony_ci	.fops			= &ili9486_fops,
17462306a36Sopenharmony_ci	DRM_GEM_DMA_DRIVER_OPS_VMAP,
17562306a36Sopenharmony_ci	.debugfs_init		= mipi_dbi_debugfs_init,
17662306a36Sopenharmony_ci	.name			= "ili9486",
17762306a36Sopenharmony_ci	.desc			= "Ilitek ILI9486",
17862306a36Sopenharmony_ci	.date			= "20200118",
17962306a36Sopenharmony_ci	.major			= 1,
18062306a36Sopenharmony_ci	.minor			= 0,
18162306a36Sopenharmony_ci};
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic const struct of_device_id ili9486_of_match[] = {
18462306a36Sopenharmony_ci	{ .compatible = "waveshare,rpi-lcd-35" },
18562306a36Sopenharmony_ci	{ .compatible = "ozzmaker,piscreen" },
18662306a36Sopenharmony_ci	{},
18762306a36Sopenharmony_ci};
18862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ili9486_of_match);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic const struct spi_device_id ili9486_id[] = {
19162306a36Sopenharmony_ci	{ "ili9486", 0 },
19262306a36Sopenharmony_ci	{ "rpi-lcd-35", 0 },
19362306a36Sopenharmony_ci	{ "piscreen", 0 },
19462306a36Sopenharmony_ci	{ }
19562306a36Sopenharmony_ci};
19662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(spi, ili9486_id);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic int ili9486_probe(struct spi_device *spi)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	struct device *dev = &spi->dev;
20162306a36Sopenharmony_ci	struct mipi_dbi_dev *dbidev;
20262306a36Sopenharmony_ci	struct drm_device *drm;
20362306a36Sopenharmony_ci	struct mipi_dbi *dbi;
20462306a36Sopenharmony_ci	struct gpio_desc *dc;
20562306a36Sopenharmony_ci	u32 rotation = 0;
20662306a36Sopenharmony_ci	int ret;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	dbidev = devm_drm_dev_alloc(dev, &ili9486_driver,
20962306a36Sopenharmony_ci				    struct mipi_dbi_dev, drm);
21062306a36Sopenharmony_ci	if (IS_ERR(dbidev))
21162306a36Sopenharmony_ci		return PTR_ERR(dbidev);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	dbi = &dbidev->dbi;
21462306a36Sopenharmony_ci	drm = &dbidev->drm;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	dbi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
21762306a36Sopenharmony_ci	if (IS_ERR(dbi->reset))
21862306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(dbi->reset), "Failed to get GPIO 'reset'\n");
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
22162306a36Sopenharmony_ci	if (IS_ERR(dc))
22262306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(dc), "Failed to get GPIO 'dc'\n");
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	dbidev->backlight = devm_of_find_backlight(dev);
22562306a36Sopenharmony_ci	if (IS_ERR(dbidev->backlight))
22662306a36Sopenharmony_ci		return PTR_ERR(dbidev->backlight);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	device_property_read_u32(dev, "rotation", &rotation);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ret = mipi_dbi_spi_init(spi, dbi, dc);
23162306a36Sopenharmony_ci	if (ret)
23262306a36Sopenharmony_ci		return ret;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	dbi->command = waveshare_command;
23562306a36Sopenharmony_ci	dbi->read_commands = NULL;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	ret = mipi_dbi_dev_init(dbidev, &waveshare_pipe_funcs,
23862306a36Sopenharmony_ci				&waveshare_mode, rotation);
23962306a36Sopenharmony_ci	if (ret)
24062306a36Sopenharmony_ci		return ret;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	drm_mode_config_reset(drm);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	ret = drm_dev_register(drm, 0);
24562306a36Sopenharmony_ci	if (ret)
24662306a36Sopenharmony_ci		return ret;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	spi_set_drvdata(spi, drm);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	drm_fbdev_generic_setup(drm, 0);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return 0;
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic void ili9486_remove(struct spi_device *spi)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	struct drm_device *drm = spi_get_drvdata(spi);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	drm_dev_unplug(drm);
26062306a36Sopenharmony_ci	drm_atomic_helper_shutdown(drm);
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic void ili9486_shutdown(struct spi_device *spi)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	drm_atomic_helper_shutdown(spi_get_drvdata(spi));
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic struct spi_driver ili9486_spi_driver = {
26962306a36Sopenharmony_ci	.driver = {
27062306a36Sopenharmony_ci		.name = "ili9486",
27162306a36Sopenharmony_ci		.of_match_table = ili9486_of_match,
27262306a36Sopenharmony_ci	},
27362306a36Sopenharmony_ci	.id_table = ili9486_id,
27462306a36Sopenharmony_ci	.probe = ili9486_probe,
27562306a36Sopenharmony_ci	.remove = ili9486_remove,
27662306a36Sopenharmony_ci	.shutdown = ili9486_shutdown,
27762306a36Sopenharmony_ci};
27862306a36Sopenharmony_cimodule_spi_driver(ili9486_spi_driver);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ciMODULE_DESCRIPTION("Ilitek ILI9486 DRM driver");
28162306a36Sopenharmony_ciMODULE_AUTHOR("Kamlesh Gurudasani <kamlesh.gurudasani@gmail.com>");
28262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
283