18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * DRM driver for Sitronix ST7586 panels
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2017 David Lechner <david@lechnology.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/delay.h>
98c2ecf20Sopenharmony_ci#include <linux/dma-buf.h>
108c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/property.h>
138c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
148c2ecf20Sopenharmony_ci#include <video/mipi_display.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
178c2ecf20Sopenharmony_ci#include <drm/drm_damage_helper.h>
188c2ecf20Sopenharmony_ci#include <drm/drm_drv.h>
198c2ecf20Sopenharmony_ci#include <drm/drm_fb_cma_helper.h>
208c2ecf20Sopenharmony_ci#include <drm/drm_fb_helper.h>
218c2ecf20Sopenharmony_ci#include <drm/drm_format_helper.h>
228c2ecf20Sopenharmony_ci#include <drm/drm_gem_cma_helper.h>
238c2ecf20Sopenharmony_ci#include <drm/drm_gem_framebuffer_helper.h>
248c2ecf20Sopenharmony_ci#include <drm/drm_managed.h>
258c2ecf20Sopenharmony_ci#include <drm/drm_mipi_dbi.h>
268c2ecf20Sopenharmony_ci#include <drm/drm_rect.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci/* controller-specific commands */
298c2ecf20Sopenharmony_ci#define ST7586_DISP_MODE_GRAY	0x38
308c2ecf20Sopenharmony_ci#define ST7586_DISP_MODE_MONO	0x39
318c2ecf20Sopenharmony_ci#define ST7586_ENABLE_DDRAM	0x3a
328c2ecf20Sopenharmony_ci#define ST7586_SET_DISP_DUTY	0xb0
338c2ecf20Sopenharmony_ci#define ST7586_SET_PART_DISP	0xb4
348c2ecf20Sopenharmony_ci#define ST7586_SET_NLINE_INV	0xb5
358c2ecf20Sopenharmony_ci#define ST7586_SET_VOP		0xc0
368c2ecf20Sopenharmony_ci#define ST7586_SET_BIAS_SYSTEM	0xc3
378c2ecf20Sopenharmony_ci#define ST7586_SET_BOOST_LEVEL	0xc4
388c2ecf20Sopenharmony_ci#define ST7586_SET_VOP_OFFSET	0xc7
398c2ecf20Sopenharmony_ci#define ST7586_ENABLE_ANALOG	0xd0
408c2ecf20Sopenharmony_ci#define ST7586_AUTO_READ_CTRL	0xd7
418c2ecf20Sopenharmony_ci#define ST7586_OTP_RW_CTRL	0xe0
428c2ecf20Sopenharmony_ci#define ST7586_OTP_CTRL_OUT	0xe1
438c2ecf20Sopenharmony_ci#define ST7586_OTP_READ		0xe3
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci#define ST7586_DISP_CTRL_MX	BIT(6)
468c2ecf20Sopenharmony_ci#define ST7586_DISP_CTRL_MY	BIT(7)
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/*
498c2ecf20Sopenharmony_ci * The ST7586 controller has an unusual pixel format where 2bpp grayscale is
508c2ecf20Sopenharmony_ci * packed 3 pixels per byte with the first two pixels using 3 bits and the 3rd
518c2ecf20Sopenharmony_ci * pixel using only 2 bits.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * |  D7  |  D6  |  D5  ||      |      || 2bpp |
548c2ecf20Sopenharmony_ci * | (D4) | (D3) | (D2) ||  D1  |  D0  || GRAY |
558c2ecf20Sopenharmony_ci * +------+------+------++------+------++------+
568c2ecf20Sopenharmony_ci * |  1   |  1   |  1   ||  1   |  1   || 0  0 | black
578c2ecf20Sopenharmony_ci * |  1   |  0   |  0   ||  1   |  0   || 0  1 | dark gray
588c2ecf20Sopenharmony_ci * |  0   |  1   |  0   ||  0   |  1   || 1  0 | light gray
598c2ecf20Sopenharmony_ci * |  0   |  0   |  0   ||  0   |  0   || 1  1 | white
608c2ecf20Sopenharmony_ci */
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic const u8 st7586_lookup[] = { 0x7, 0x4, 0x2, 0x0 };
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic void st7586_xrgb8888_to_gray332(u8 *dst, void *vaddr,
658c2ecf20Sopenharmony_ci				       struct drm_framebuffer *fb,
668c2ecf20Sopenharmony_ci				       struct drm_rect *clip)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	size_t len = (clip->x2 - clip->x1) * (clip->y2 - clip->y1);
698c2ecf20Sopenharmony_ci	unsigned int x, y;
708c2ecf20Sopenharmony_ci	u8 *src, *buf, val;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	buf = kmalloc(len, GFP_KERNEL);
738c2ecf20Sopenharmony_ci	if (!buf)
748c2ecf20Sopenharmony_ci		return;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	drm_fb_xrgb8888_to_gray8(buf, vaddr, fb, clip);
778c2ecf20Sopenharmony_ci	src = buf;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	for (y = clip->y1; y < clip->y2; y++) {
808c2ecf20Sopenharmony_ci		for (x = clip->x1; x < clip->x2; x += 3) {
818c2ecf20Sopenharmony_ci			val = st7586_lookup[*src++ >> 6] << 5;
828c2ecf20Sopenharmony_ci			val |= st7586_lookup[*src++ >> 6] << 2;
838c2ecf20Sopenharmony_ci			val |= st7586_lookup[*src++ >> 6] >> 1;
848c2ecf20Sopenharmony_ci			*dst++ = val;
858c2ecf20Sopenharmony_ci		}
868c2ecf20Sopenharmony_ci	}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	kfree(buf);
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic int st7586_buf_copy(void *dst, struct drm_framebuffer *fb,
928c2ecf20Sopenharmony_ci			   struct drm_rect *clip)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
958c2ecf20Sopenharmony_ci	struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
968c2ecf20Sopenharmony_ci	void *src = cma_obj->vaddr;
978c2ecf20Sopenharmony_ci	int ret = 0;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (import_attach) {
1008c2ecf20Sopenharmony_ci		ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
1018c2ecf20Sopenharmony_ci					       DMA_FROM_DEVICE);
1028c2ecf20Sopenharmony_ci		if (ret)
1038c2ecf20Sopenharmony_ci			return ret;
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	st7586_xrgb8888_to_gray332(dst, src, fb, clip);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	if (import_attach)
1098c2ecf20Sopenharmony_ci		ret = dma_buf_end_cpu_access(import_attach->dmabuf,
1108c2ecf20Sopenharmony_ci					     DMA_FROM_DEVICE);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	return ret;
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic void st7586_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(fb->dev);
1188c2ecf20Sopenharmony_ci	struct mipi_dbi *dbi = &dbidev->dbi;
1198c2ecf20Sopenharmony_ci	int start, end, idx, ret = 0;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	if (!drm_dev_enter(fb->dev, &idx))
1228c2ecf20Sopenharmony_ci		return;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	/* 3 pixels per byte, so grow clip to nearest multiple of 3 */
1258c2ecf20Sopenharmony_ci	rect->x1 = rounddown(rect->x1, 3);
1268c2ecf20Sopenharmony_ci	rect->x2 = roundup(rect->x2, 3);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect));
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	ret = st7586_buf_copy(dbidev->tx_buf, fb, rect);
1318c2ecf20Sopenharmony_ci	if (ret)
1328c2ecf20Sopenharmony_ci		goto err_msg;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	/* Pixels are packed 3 per byte */
1358c2ecf20Sopenharmony_ci	start = rect->x1 / 3;
1368c2ecf20Sopenharmony_ci	end = rect->x2 / 3;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_COLUMN_ADDRESS,
1398c2ecf20Sopenharmony_ci			 (start >> 8) & 0xFF, start & 0xFF,
1408c2ecf20Sopenharmony_ci			 (end >> 8) & 0xFF, (end - 1) & 0xFF);
1418c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_PAGE_ADDRESS,
1428c2ecf20Sopenharmony_ci			 (rect->y1 >> 8) & 0xFF, rect->y1 & 0xFF,
1438c2ecf20Sopenharmony_ci			 (rect->y2 >> 8) & 0xFF, (rect->y2 - 1) & 0xFF);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	ret = mipi_dbi_command_buf(dbi, MIPI_DCS_WRITE_MEMORY_START,
1468c2ecf20Sopenharmony_ci				   (u8 *)dbidev->tx_buf,
1478c2ecf20Sopenharmony_ci				   (end - start) * (rect->y2 - rect->y1));
1488c2ecf20Sopenharmony_cierr_msg:
1498c2ecf20Sopenharmony_ci	if (ret)
1508c2ecf20Sopenharmony_ci		dev_err_once(fb->dev->dev, "Failed to update display %d\n", ret);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	drm_dev_exit(idx);
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_cistatic void st7586_pipe_update(struct drm_simple_display_pipe *pipe,
1568c2ecf20Sopenharmony_ci			       struct drm_plane_state *old_state)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct drm_plane_state *state = pipe->plane.state;
1598c2ecf20Sopenharmony_ci	struct drm_rect rect;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	if (!pipe->crtc.state->active)
1628c2ecf20Sopenharmony_ci		return;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	if (drm_atomic_helper_damage_merged(old_state, state, &rect))
1658c2ecf20Sopenharmony_ci		st7586_fb_dirty(state->fb, &rect);
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic void st7586_pipe_enable(struct drm_simple_display_pipe *pipe,
1698c2ecf20Sopenharmony_ci			       struct drm_crtc_state *crtc_state,
1708c2ecf20Sopenharmony_ci			       struct drm_plane_state *plane_state)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
1738c2ecf20Sopenharmony_ci	struct drm_framebuffer *fb = plane_state->fb;
1748c2ecf20Sopenharmony_ci	struct mipi_dbi *dbi = &dbidev->dbi;
1758c2ecf20Sopenharmony_ci	struct drm_rect rect = {
1768c2ecf20Sopenharmony_ci		.x1 = 0,
1778c2ecf20Sopenharmony_ci		.x2 = fb->width,
1788c2ecf20Sopenharmony_ci		.y1 = 0,
1798c2ecf20Sopenharmony_ci		.y2 = fb->height,
1808c2ecf20Sopenharmony_ci	};
1818c2ecf20Sopenharmony_ci	int idx, ret;
1828c2ecf20Sopenharmony_ci	u8 addr_mode;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	if (!drm_dev_enter(pipe->crtc.dev, &idx))
1858c2ecf20Sopenharmony_ci		return;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	DRM_DEBUG_KMS("\n");
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	ret = mipi_dbi_poweron_reset(dbidev);
1908c2ecf20Sopenharmony_ci	if (ret)
1918c2ecf20Sopenharmony_ci		goto out_exit;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_AUTO_READ_CTRL, 0x9f);
1948c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_OTP_RW_CTRL, 0x00);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	msleep(10);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_OTP_READ);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	msleep(20);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_OTP_CTRL_OUT);
2038c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
2048c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	msleep(50);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_VOP_OFFSET, 0x00);
2098c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_VOP, 0xe3, 0x00);
2108c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_BIAS_SYSTEM, 0x02);
2118c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_BOOST_LEVEL, 0x04);
2128c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_ENABLE_ANALOG, 0x1d);
2138c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_NLINE_INV, 0x00);
2148c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_DISP_MODE_GRAY);
2158c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_ENABLE_DDRAM, 0x02);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	switch (dbidev->rotation) {
2188c2ecf20Sopenharmony_ci	default:
2198c2ecf20Sopenharmony_ci		addr_mode = 0x00;
2208c2ecf20Sopenharmony_ci		break;
2218c2ecf20Sopenharmony_ci	case 90:
2228c2ecf20Sopenharmony_ci		addr_mode = ST7586_DISP_CTRL_MY;
2238c2ecf20Sopenharmony_ci		break;
2248c2ecf20Sopenharmony_ci	case 180:
2258c2ecf20Sopenharmony_ci		addr_mode = ST7586_DISP_CTRL_MX | ST7586_DISP_CTRL_MY;
2268c2ecf20Sopenharmony_ci		break;
2278c2ecf20Sopenharmony_ci	case 270:
2288c2ecf20Sopenharmony_ci		addr_mode = ST7586_DISP_CTRL_MX;
2298c2ecf20Sopenharmony_ci		break;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_DISP_DUTY, 0x7f);
2348c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, ST7586_SET_PART_DISP, 0xa0);
2358c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_PARTIAL_ROWS, 0x00, 0x00, 0x00, 0x77);
2368c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_EXIT_INVERT_MODE);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	msleep(100);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	st7586_fb_dirty(fb, &rect);
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
2438c2ecf20Sopenharmony_ciout_exit:
2448c2ecf20Sopenharmony_ci	drm_dev_exit(idx);
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic void st7586_pipe_disable(struct drm_simple_display_pipe *pipe)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	/*
2528c2ecf20Sopenharmony_ci	 * This callback is not protected by drm_dev_enter/exit since we want to
2538c2ecf20Sopenharmony_ci	 * turn off the display on regular driver unload. It's highly unlikely
2548c2ecf20Sopenharmony_ci	 * that the underlying SPI controller is gone should this be called after
2558c2ecf20Sopenharmony_ci	 * unplug.
2568c2ecf20Sopenharmony_ci	 */
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	DRM_DEBUG_KMS("\n");
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	mipi_dbi_command(&dbidev->dbi, MIPI_DCS_SET_DISPLAY_OFF);
2618c2ecf20Sopenharmony_ci}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_cistatic const u32 st7586_formats[] = {
2648c2ecf20Sopenharmony_ci	DRM_FORMAT_XRGB8888,
2658c2ecf20Sopenharmony_ci};
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cistatic const struct drm_simple_display_pipe_funcs st7586_pipe_funcs = {
2688c2ecf20Sopenharmony_ci	.enable		= st7586_pipe_enable,
2698c2ecf20Sopenharmony_ci	.disable	= st7586_pipe_disable,
2708c2ecf20Sopenharmony_ci	.update		= st7586_pipe_update,
2718c2ecf20Sopenharmony_ci	.prepare_fb	= drm_gem_fb_simple_display_pipe_prepare_fb,
2728c2ecf20Sopenharmony_ci};
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_cistatic const struct drm_display_mode st7586_mode = {
2758c2ecf20Sopenharmony_ci	DRM_SIMPLE_MODE(178, 128, 37, 27),
2768c2ecf20Sopenharmony_ci};
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ciDEFINE_DRM_GEM_CMA_FOPS(st7586_fops);
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cistatic struct drm_driver st7586_driver = {
2818c2ecf20Sopenharmony_ci	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
2828c2ecf20Sopenharmony_ci	.fops			= &st7586_fops,
2838c2ecf20Sopenharmony_ci	DRM_GEM_CMA_DRIVER_OPS_VMAP,
2848c2ecf20Sopenharmony_ci	.debugfs_init		= mipi_dbi_debugfs_init,
2858c2ecf20Sopenharmony_ci	.name			= "st7586",
2868c2ecf20Sopenharmony_ci	.desc			= "Sitronix ST7586",
2878c2ecf20Sopenharmony_ci	.date			= "20170801",
2888c2ecf20Sopenharmony_ci	.major			= 1,
2898c2ecf20Sopenharmony_ci	.minor			= 0,
2908c2ecf20Sopenharmony_ci};
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_cistatic const struct of_device_id st7586_of_match[] = {
2938c2ecf20Sopenharmony_ci	{ .compatible = "lego,ev3-lcd" },
2948c2ecf20Sopenharmony_ci	{},
2958c2ecf20Sopenharmony_ci};
2968c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, st7586_of_match);
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_cistatic const struct spi_device_id st7586_id[] = {
2998c2ecf20Sopenharmony_ci	{ "ev3-lcd", 0 },
3008c2ecf20Sopenharmony_ci	{ },
3018c2ecf20Sopenharmony_ci};
3028c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(spi, st7586_id);
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cistatic int st7586_probe(struct spi_device *spi)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	struct device *dev = &spi->dev;
3078c2ecf20Sopenharmony_ci	struct mipi_dbi_dev *dbidev;
3088c2ecf20Sopenharmony_ci	struct drm_device *drm;
3098c2ecf20Sopenharmony_ci	struct mipi_dbi *dbi;
3108c2ecf20Sopenharmony_ci	struct gpio_desc *a0;
3118c2ecf20Sopenharmony_ci	u32 rotation = 0;
3128c2ecf20Sopenharmony_ci	size_t bufsize;
3138c2ecf20Sopenharmony_ci	int ret;
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	dbidev = devm_drm_dev_alloc(dev, &st7586_driver,
3168c2ecf20Sopenharmony_ci				    struct mipi_dbi_dev, drm);
3178c2ecf20Sopenharmony_ci	if (IS_ERR(dbidev))
3188c2ecf20Sopenharmony_ci		return PTR_ERR(dbidev);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	dbi = &dbidev->dbi;
3218c2ecf20Sopenharmony_ci	drm = &dbidev->drm;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	bufsize = (st7586_mode.vdisplay + 2) / 3 * st7586_mode.hdisplay;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	dbi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
3268c2ecf20Sopenharmony_ci	if (IS_ERR(dbi->reset)) {
3278c2ecf20Sopenharmony_ci		DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
3288c2ecf20Sopenharmony_ci		return PTR_ERR(dbi->reset);
3298c2ecf20Sopenharmony_ci	}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	a0 = devm_gpiod_get(dev, "a0", GPIOD_OUT_LOW);
3328c2ecf20Sopenharmony_ci	if (IS_ERR(a0)) {
3338c2ecf20Sopenharmony_ci		DRM_DEV_ERROR(dev, "Failed to get gpio 'a0'\n");
3348c2ecf20Sopenharmony_ci		return PTR_ERR(a0);
3358c2ecf20Sopenharmony_ci	}
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	device_property_read_u32(dev, "rotation", &rotation);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	ret = mipi_dbi_spi_init(spi, dbi, a0);
3408c2ecf20Sopenharmony_ci	if (ret)
3418c2ecf20Sopenharmony_ci		return ret;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	/* Cannot read from this controller via SPI */
3448c2ecf20Sopenharmony_ci	dbi->read_commands = NULL;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	ret = mipi_dbi_dev_init_with_formats(dbidev, &st7586_pipe_funcs,
3478c2ecf20Sopenharmony_ci					     st7586_formats, ARRAY_SIZE(st7586_formats),
3488c2ecf20Sopenharmony_ci					     &st7586_mode, rotation, bufsize);
3498c2ecf20Sopenharmony_ci	if (ret)
3508c2ecf20Sopenharmony_ci		return ret;
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	/*
3538c2ecf20Sopenharmony_ci	 * we are using 8-bit data, so we are not actually swapping anything,
3548c2ecf20Sopenharmony_ci	 * but setting mipi->swap_bytes makes mipi_dbi_typec3_command() do the
3558c2ecf20Sopenharmony_ci	 * right thing and not use 16-bit transfers (which results in swapped
3568c2ecf20Sopenharmony_ci	 * bytes on little-endian systems and causes out of order data to be
3578c2ecf20Sopenharmony_ci	 * sent to the display).
3588c2ecf20Sopenharmony_ci	 */
3598c2ecf20Sopenharmony_ci	dbi->swap_bytes = true;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	drm_mode_config_reset(drm);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	ret = drm_dev_register(drm, 0);
3648c2ecf20Sopenharmony_ci	if (ret)
3658c2ecf20Sopenharmony_ci		return ret;
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	spi_set_drvdata(spi, drm);
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	drm_fbdev_generic_setup(drm, 0);
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	return 0;
3728c2ecf20Sopenharmony_ci}
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_cistatic int st7586_remove(struct spi_device *spi)
3758c2ecf20Sopenharmony_ci{
3768c2ecf20Sopenharmony_ci	struct drm_device *drm = spi_get_drvdata(spi);
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	drm_dev_unplug(drm);
3798c2ecf20Sopenharmony_ci	drm_atomic_helper_shutdown(drm);
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	return 0;
3828c2ecf20Sopenharmony_ci}
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_cistatic void st7586_shutdown(struct spi_device *spi)
3858c2ecf20Sopenharmony_ci{
3868c2ecf20Sopenharmony_ci	drm_atomic_helper_shutdown(spi_get_drvdata(spi));
3878c2ecf20Sopenharmony_ci}
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_cistatic struct spi_driver st7586_spi_driver = {
3908c2ecf20Sopenharmony_ci	.driver = {
3918c2ecf20Sopenharmony_ci		.name = "st7586",
3928c2ecf20Sopenharmony_ci		.owner = THIS_MODULE,
3938c2ecf20Sopenharmony_ci		.of_match_table = st7586_of_match,
3948c2ecf20Sopenharmony_ci	},
3958c2ecf20Sopenharmony_ci	.id_table = st7586_id,
3968c2ecf20Sopenharmony_ci	.probe = st7586_probe,
3978c2ecf20Sopenharmony_ci	.remove = st7586_remove,
3988c2ecf20Sopenharmony_ci	.shutdown = st7586_shutdown,
3998c2ecf20Sopenharmony_ci};
4008c2ecf20Sopenharmony_cimodule_spi_driver(st7586_spi_driver);
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Sitronix ST7586 DRM driver");
4038c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Lechner <david@lechnology.com>");
4048c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
405