18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * DRM driver for the HX8357D LCD controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2018 Broadcom 68c2ecf20Sopenharmony_ci * Copyright 2018 David Lechner <david@lechnology.com> 78c2ecf20Sopenharmony_ci * Copyright 2016 Noralf Trønnes 88c2ecf20Sopenharmony_ci * Copyright (C) 2015 Adafruit Industries 98c2ecf20Sopenharmony_ci * Copyright (C) 2013 Christian Vogelgsang 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/backlight.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/property.h> 178c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h> 208c2ecf20Sopenharmony_ci#include <drm/drm_drv.h> 218c2ecf20Sopenharmony_ci#include <drm/drm_fb_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_modeset_helper.h> 278c2ecf20Sopenharmony_ci#include <video/mipi_display.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define HX8357D_SETOSC 0xb0 308c2ecf20Sopenharmony_ci#define HX8357D_SETPOWER 0xb1 318c2ecf20Sopenharmony_ci#define HX8357D_SETRGB 0xb3 328c2ecf20Sopenharmony_ci#define HX8357D_SETCYC 0xb3 338c2ecf20Sopenharmony_ci#define HX8357D_SETCOM 0xb6 348c2ecf20Sopenharmony_ci#define HX8357D_SETEXTC 0xb9 358c2ecf20Sopenharmony_ci#define HX8357D_SETSTBA 0xc0 368c2ecf20Sopenharmony_ci#define HX8357D_SETPANEL 0xcc 378c2ecf20Sopenharmony_ci#define HX8357D_SETGAMMA 0xe0 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_MY 0x80 408c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_MX 0x40 418c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_MV 0x20 428c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_ML 0x10 438c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_RGB 0x00 448c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_BGR 0x08 458c2ecf20Sopenharmony_ci#define HX8357D_MADCTL_MH 0x04 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic void yx240qv29_enable(struct drm_simple_display_pipe *pipe, 488c2ecf20Sopenharmony_ci struct drm_crtc_state *crtc_state, 498c2ecf20Sopenharmony_ci struct drm_plane_state *plane_state) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); 528c2ecf20Sopenharmony_ci struct mipi_dbi *dbi = &dbidev->dbi; 538c2ecf20Sopenharmony_ci u8 addr_mode; 548c2ecf20Sopenharmony_ci int ret, idx; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci if (!drm_dev_enter(pipe->crtc.dev, &idx)) 578c2ecf20Sopenharmony_ci return; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("\n"); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci ret = mipi_dbi_poweron_conditional_reset(dbidev); 628c2ecf20Sopenharmony_ci if (ret < 0) 638c2ecf20Sopenharmony_ci goto out_exit; 648c2ecf20Sopenharmony_ci if (ret == 1) 658c2ecf20Sopenharmony_ci goto out_enable; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* setextc */ 688c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETEXTC, 0xFF, 0x83, 0x57); 698c2ecf20Sopenharmony_ci msleep(150); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci /* setRGB which also enables SDO */ 728c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETRGB, 0x00, 0x00, 0x06, 0x06); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* -1.52V */ 758c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETCOM, 0x25); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* Normal mode 70Hz, Idle mode 55 Hz */ 788c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETOSC, 0x68); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Set Panel - BGR, Gate direction swapped */ 818c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETPANEL, 0x05); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETPOWER, 848c2ecf20Sopenharmony_ci 0x00, /* Not deep standby */ 858c2ecf20Sopenharmony_ci 0x15, /* BT */ 868c2ecf20Sopenharmony_ci 0x1C, /* VSPR */ 878c2ecf20Sopenharmony_ci 0x1C, /* VSNR */ 888c2ecf20Sopenharmony_ci 0x83, /* AP */ 898c2ecf20Sopenharmony_ci 0xAA); /* FS */ 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETSTBA, 928c2ecf20Sopenharmony_ci 0x50, /* OPON normal */ 938c2ecf20Sopenharmony_ci 0x50, /* OPON idle */ 948c2ecf20Sopenharmony_ci 0x01, /* STBA */ 958c2ecf20Sopenharmony_ci 0x3C, /* STBA */ 968c2ecf20Sopenharmony_ci 0x1E, /* STBA */ 978c2ecf20Sopenharmony_ci 0x08); /* GEN */ 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETCYC, 1008c2ecf20Sopenharmony_ci 0x02, /* NW 0x02 */ 1018c2ecf20Sopenharmony_ci 0x40, /* RTN */ 1028c2ecf20Sopenharmony_ci 0x00, /* DIV */ 1038c2ecf20Sopenharmony_ci 0x2A, /* DUM */ 1048c2ecf20Sopenharmony_ci 0x2A, /* DUM */ 1058c2ecf20Sopenharmony_ci 0x0D, /* GDON */ 1068c2ecf20Sopenharmony_ci 0x78); /* GDOFF */ 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, HX8357D_SETGAMMA, 1098c2ecf20Sopenharmony_ci 0x02, 1108c2ecf20Sopenharmony_ci 0x0A, 1118c2ecf20Sopenharmony_ci 0x11, 1128c2ecf20Sopenharmony_ci 0x1d, 1138c2ecf20Sopenharmony_ci 0x23, 1148c2ecf20Sopenharmony_ci 0x35, 1158c2ecf20Sopenharmony_ci 0x41, 1168c2ecf20Sopenharmony_ci 0x4b, 1178c2ecf20Sopenharmony_ci 0x4b, 1188c2ecf20Sopenharmony_ci 0x42, 1198c2ecf20Sopenharmony_ci 0x3A, 1208c2ecf20Sopenharmony_ci 0x27, 1218c2ecf20Sopenharmony_ci 0x1B, 1228c2ecf20Sopenharmony_ci 0x08, 1238c2ecf20Sopenharmony_ci 0x09, 1248c2ecf20Sopenharmony_ci 0x03, 1258c2ecf20Sopenharmony_ci 0x02, 1268c2ecf20Sopenharmony_ci 0x0A, 1278c2ecf20Sopenharmony_ci 0x11, 1288c2ecf20Sopenharmony_ci 0x1d, 1298c2ecf20Sopenharmony_ci 0x23, 1308c2ecf20Sopenharmony_ci 0x35, 1318c2ecf20Sopenharmony_ci 0x41, 1328c2ecf20Sopenharmony_ci 0x4b, 1338c2ecf20Sopenharmony_ci 0x4b, 1348c2ecf20Sopenharmony_ci 0x42, 1358c2ecf20Sopenharmony_ci 0x3A, 1368c2ecf20Sopenharmony_ci 0x27, 1378c2ecf20Sopenharmony_ci 0x1B, 1388c2ecf20Sopenharmony_ci 0x08, 1398c2ecf20Sopenharmony_ci 0x09, 1408c2ecf20Sopenharmony_ci 0x03, 1418c2ecf20Sopenharmony_ci 0x00, 1428c2ecf20Sopenharmony_ci 0x01); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* 16 bit */ 1458c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 1468c2ecf20Sopenharmony_ci MIPI_DCS_PIXEL_FMT_16BIT); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* TE off */ 1498c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_TEAR_ON, 0x00); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* tear line */ 1528c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_TEAR_SCANLINE, 0x00, 0x02); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* Exit Sleep */ 1558c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); 1568c2ecf20Sopenharmony_ci msleep(150); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* display on */ 1598c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); 1608c2ecf20Sopenharmony_ci usleep_range(5000, 7000); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ciout_enable: 1638c2ecf20Sopenharmony_ci switch (dbidev->rotation) { 1648c2ecf20Sopenharmony_ci default: 1658c2ecf20Sopenharmony_ci addr_mode = HX8357D_MADCTL_MX | HX8357D_MADCTL_MY; 1668c2ecf20Sopenharmony_ci break; 1678c2ecf20Sopenharmony_ci case 90: 1688c2ecf20Sopenharmony_ci addr_mode = HX8357D_MADCTL_MV | HX8357D_MADCTL_MY; 1698c2ecf20Sopenharmony_ci break; 1708c2ecf20Sopenharmony_ci case 180: 1718c2ecf20Sopenharmony_ci addr_mode = 0; 1728c2ecf20Sopenharmony_ci break; 1738c2ecf20Sopenharmony_ci case 270: 1748c2ecf20Sopenharmony_ci addr_mode = HX8357D_MADCTL_MV | HX8357D_MADCTL_MX; 1758c2ecf20Sopenharmony_ci break; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); 1788c2ecf20Sopenharmony_ci mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); 1798c2ecf20Sopenharmony_ciout_exit: 1808c2ecf20Sopenharmony_ci drm_dev_exit(idx); 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic const struct drm_simple_display_pipe_funcs hx8357d_pipe_funcs = { 1848c2ecf20Sopenharmony_ci .enable = yx240qv29_enable, 1858c2ecf20Sopenharmony_ci .disable = mipi_dbi_pipe_disable, 1868c2ecf20Sopenharmony_ci .update = mipi_dbi_pipe_update, 1878c2ecf20Sopenharmony_ci .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, 1888c2ecf20Sopenharmony_ci}; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic const struct drm_display_mode yx350hv15_mode = { 1918c2ecf20Sopenharmony_ci DRM_SIMPLE_MODE(320, 480, 60, 75), 1928c2ecf20Sopenharmony_ci}; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ciDEFINE_DRM_GEM_CMA_FOPS(hx8357d_fops); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic struct drm_driver hx8357d_driver = { 1978c2ecf20Sopenharmony_ci .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, 1988c2ecf20Sopenharmony_ci .fops = &hx8357d_fops, 1998c2ecf20Sopenharmony_ci DRM_GEM_CMA_DRIVER_OPS_VMAP, 2008c2ecf20Sopenharmony_ci .debugfs_init = mipi_dbi_debugfs_init, 2018c2ecf20Sopenharmony_ci .name = "hx8357d", 2028c2ecf20Sopenharmony_ci .desc = "HX8357D", 2038c2ecf20Sopenharmony_ci .date = "20181023", 2048c2ecf20Sopenharmony_ci .major = 1, 2058c2ecf20Sopenharmony_ci .minor = 0, 2068c2ecf20Sopenharmony_ci}; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic const struct of_device_id hx8357d_of_match[] = { 2098c2ecf20Sopenharmony_ci { .compatible = "adafruit,yx350hv15" }, 2108c2ecf20Sopenharmony_ci { } 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, hx8357d_of_match); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cistatic const struct spi_device_id hx8357d_id[] = { 2158c2ecf20Sopenharmony_ci { "yx350hv15", 0 }, 2168c2ecf20Sopenharmony_ci { } 2178c2ecf20Sopenharmony_ci}; 2188c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(spi, hx8357d_id); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int hx8357d_probe(struct spi_device *spi) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci struct device *dev = &spi->dev; 2238c2ecf20Sopenharmony_ci struct mipi_dbi_dev *dbidev; 2248c2ecf20Sopenharmony_ci struct drm_device *drm; 2258c2ecf20Sopenharmony_ci struct gpio_desc *dc; 2268c2ecf20Sopenharmony_ci u32 rotation = 0; 2278c2ecf20Sopenharmony_ci int ret; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci dbidev = devm_drm_dev_alloc(dev, &hx8357d_driver, 2308c2ecf20Sopenharmony_ci struct mipi_dbi_dev, drm); 2318c2ecf20Sopenharmony_ci if (IS_ERR(dbidev)) 2328c2ecf20Sopenharmony_ci return PTR_ERR(dbidev); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci drm = &dbidev->drm; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW); 2378c2ecf20Sopenharmony_ci if (IS_ERR(dc)) { 2388c2ecf20Sopenharmony_ci DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n"); 2398c2ecf20Sopenharmony_ci return PTR_ERR(dc); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci dbidev->backlight = devm_of_find_backlight(dev); 2438c2ecf20Sopenharmony_ci if (IS_ERR(dbidev->backlight)) 2448c2ecf20Sopenharmony_ci return PTR_ERR(dbidev->backlight); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci device_property_read_u32(dev, "rotation", &rotation); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci ret = mipi_dbi_spi_init(spi, &dbidev->dbi, dc); 2498c2ecf20Sopenharmony_ci if (ret) 2508c2ecf20Sopenharmony_ci return ret; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci ret = mipi_dbi_dev_init(dbidev, &hx8357d_pipe_funcs, &yx350hv15_mode, rotation); 2538c2ecf20Sopenharmony_ci if (ret) 2548c2ecf20Sopenharmony_ci return ret; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci drm_mode_config_reset(drm); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci ret = drm_dev_register(drm, 0); 2598c2ecf20Sopenharmony_ci if (ret) 2608c2ecf20Sopenharmony_ci return ret; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci spi_set_drvdata(spi, drm); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci drm_fbdev_generic_setup(drm, 0); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci return 0; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic int hx8357d_remove(struct spi_device *spi) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci struct drm_device *drm = spi_get_drvdata(spi); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci drm_dev_unplug(drm); 2748c2ecf20Sopenharmony_ci drm_atomic_helper_shutdown(drm); 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci return 0; 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic void hx8357d_shutdown(struct spi_device *spi) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci drm_atomic_helper_shutdown(spi_get_drvdata(spi)); 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic struct spi_driver hx8357d_spi_driver = { 2858c2ecf20Sopenharmony_ci .driver = { 2868c2ecf20Sopenharmony_ci .name = "hx8357d", 2878c2ecf20Sopenharmony_ci .of_match_table = hx8357d_of_match, 2888c2ecf20Sopenharmony_ci }, 2898c2ecf20Sopenharmony_ci .id_table = hx8357d_id, 2908c2ecf20Sopenharmony_ci .probe = hx8357d_probe, 2918c2ecf20Sopenharmony_ci .remove = hx8357d_remove, 2928c2ecf20Sopenharmony_ci .shutdown = hx8357d_shutdown, 2938c2ecf20Sopenharmony_ci}; 2948c2ecf20Sopenharmony_cimodule_spi_driver(hx8357d_spi_driver); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HX8357D DRM driver"); 2978c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); 2988c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 299