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