18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * DRM driver for display panels connected to a Sitronix ST7715R or ST7735R 48c2ecf20Sopenharmony_ci * display controller in SPI mode. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright 2017 David Lechner <david@lechnology.com> 78c2ecf20Sopenharmony_ci * Copyright (C) 2019 Glider bvba 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/backlight.h> 118c2ecf20Sopenharmony_ci#include <linux/delay.h> 128c2ecf20Sopenharmony_ci#include <linux/dma-buf.h> 138c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/property.h> 168c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 178c2ecf20Sopenharmony_ci#include <video/mipi_display.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 278c2ecf20Sopenharmony_ci#define ST7735R_FRMCTR1 0xb1 288c2ecf20Sopenharmony_ci#define ST7735R_FRMCTR2 0xb2 298c2ecf20Sopenharmony_ci#define ST7735R_FRMCTR3 0xb3 308c2ecf20Sopenharmony_ci#define ST7735R_INVCTR 0xb4 318c2ecf20Sopenharmony_ci#define ST7735R_PWCTR1 0xc0 328c2ecf20Sopenharmony_ci#define ST7735R_PWCTR2 0xc1 338c2ecf20Sopenharmony_ci#define ST7735R_PWCTR3 0xc2 348c2ecf20Sopenharmony_ci#define ST7735R_PWCTR4 0xc3 358c2ecf20Sopenharmony_ci#define ST7735R_PWCTR5 0xc4 368c2ecf20Sopenharmony_ci#define ST7735R_VMCTR1 0xc5 378c2ecf20Sopenharmony_ci#define ST7735R_GAMCTRP1 0xe0 388c2ecf20Sopenharmony_ci#define ST7735R_GAMCTRN1 0xe1 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define ST7735R_MY BIT(7) 418c2ecf20Sopenharmony_ci#define ST7735R_MX BIT(6) 428c2ecf20Sopenharmony_ci#define ST7735R_MV BIT(5) 438c2ecf20Sopenharmony_ci#define ST7735R_RGB BIT(3) 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistruct st7735r_cfg { 468c2ecf20Sopenharmony_ci const struct drm_display_mode mode; 478c2ecf20Sopenharmony_ci unsigned int left_offset; 488c2ecf20Sopenharmony_ci unsigned int top_offset; 498c2ecf20Sopenharmony_ci unsigned int write_only:1; 508c2ecf20Sopenharmony_ci unsigned int rgb:1; /* RGB (vs. BGR) */ 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct st7735r_priv { 548c2ecf20Sopenharmony_ci struct mipi_dbi_dev dbidev; /* Must be first for .release() */ 558c2ecf20Sopenharmony_ci const struct st7735r_cfg *cfg; 568c2ecf20Sopenharmony_ci}; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic void st7735r_pipe_enable(struct drm_simple_display_pipe *pipe, 598c2ecf20Sopenharmony_ci struct drm_crtc_state *crtc_state, 608c2ecf20Sopenharmony_ci struct drm_plane_state *plane_state) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); 638c2ecf20Sopenharmony_ci struct st7735r_priv *priv = container_of(dbidev, struct st7735r_priv, 648c2ecf20Sopenharmony_ci dbidev); 658c2ecf20Sopenharmony_ci struct mipi_dbi *dbi = &dbidev->dbi; 668c2ecf20Sopenharmony_ci int ret, idx; 678c2ecf20Sopenharmony_ci u8 addr_mode; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci if (!drm_dev_enter(pipe->crtc.dev, &idx)) 708c2ecf20Sopenharmony_ci return; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("\n"); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = mipi_dbi_poweron_reset(dbidev); 758c2ecf20Sopenharmony_ci if (ret) 768c2ecf20Sopenharmony_ci goto out_exit; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci msleep(150); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); 818c2ecf20Sopenharmony_ci msleep(500); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_FRMCTR1, 0x01, 0x2c, 0x2d); 848c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_FRMCTR2, 0x01, 0x2c, 0x2d); 858c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_FRMCTR3, 0x01, 0x2c, 0x2d, 0x01, 0x2c, 868c2ecf20Sopenharmony_ci 0x2d); 878c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_INVCTR, 0x07); 888c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_PWCTR1, 0xa2, 0x02, 0x84); 898c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_PWCTR2, 0xc5); 908c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_PWCTR3, 0x0a, 0x00); 918c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_PWCTR4, 0x8a, 0x2a); 928c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_PWCTR5, 0x8a, 0xee); 938c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_VMCTR1, 0x0e); 948c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_EXIT_INVERT_MODE); 958c2ecf20Sopenharmony_ci switch (dbidev->rotation) { 968c2ecf20Sopenharmony_ci default: 978c2ecf20Sopenharmony_ci addr_mode = ST7735R_MX | ST7735R_MY; 988c2ecf20Sopenharmony_ci break; 998c2ecf20Sopenharmony_ci case 90: 1008c2ecf20Sopenharmony_ci addr_mode = ST7735R_MX | ST7735R_MV; 1018c2ecf20Sopenharmony_ci break; 1028c2ecf20Sopenharmony_ci case 180: 1038c2ecf20Sopenharmony_ci addr_mode = 0; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci case 270: 1068c2ecf20Sopenharmony_ci addr_mode = ST7735R_MY | ST7735R_MV; 1078c2ecf20Sopenharmony_ci break; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (priv->cfg->rgb) 1118c2ecf20Sopenharmony_ci addr_mode |= ST7735R_RGB; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); 1148c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 1158c2ecf20Sopenharmony_ci MIPI_DCS_PIXEL_FMT_16BIT); 1168c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_GAMCTRP1, 0x02, 0x1c, 0x07, 0x12, 0x37, 1178c2ecf20Sopenharmony_ci 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 1188c2ecf20Sopenharmony_ci 0x03, 0x10); 1198c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, ST7735R_GAMCTRN1, 0x03, 0x1d, 0x07, 0x06, 0x2e, 1208c2ecf20Sopenharmony_ci 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 1218c2ecf20Sopenharmony_ci 0x02, 0x10); 1228c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci msleep(100); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_ENTER_NORMAL_MODE); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci msleep(20); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); 1318c2ecf20Sopenharmony_ciout_exit: 1328c2ecf20Sopenharmony_ci drm_dev_exit(idx); 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic const struct drm_simple_display_pipe_funcs st7735r_pipe_funcs = { 1368c2ecf20Sopenharmony_ci .enable = st7735r_pipe_enable, 1378c2ecf20Sopenharmony_ci .disable = mipi_dbi_pipe_disable, 1388c2ecf20Sopenharmony_ci .update = mipi_dbi_pipe_update, 1398c2ecf20Sopenharmony_ci .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic const struct st7735r_cfg jd_t18003_t01_cfg = { 1438c2ecf20Sopenharmony_ci .mode = { DRM_SIMPLE_MODE(128, 160, 28, 35) }, 1448c2ecf20Sopenharmony_ci /* Cannot read from Adafruit 1.8" display via SPI */ 1458c2ecf20Sopenharmony_ci .write_only = true, 1468c2ecf20Sopenharmony_ci}; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic const struct st7735r_cfg rh128128t_cfg = { 1498c2ecf20Sopenharmony_ci .mode = { DRM_SIMPLE_MODE(128, 128, 25, 26) }, 1508c2ecf20Sopenharmony_ci .left_offset = 2, 1518c2ecf20Sopenharmony_ci .top_offset = 3, 1528c2ecf20Sopenharmony_ci .rgb = true, 1538c2ecf20Sopenharmony_ci}; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ciDEFINE_DRM_GEM_CMA_FOPS(st7735r_fops); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic struct drm_driver st7735r_driver = { 1588c2ecf20Sopenharmony_ci .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, 1598c2ecf20Sopenharmony_ci .fops = &st7735r_fops, 1608c2ecf20Sopenharmony_ci DRM_GEM_CMA_DRIVER_OPS_VMAP, 1618c2ecf20Sopenharmony_ci .debugfs_init = mipi_dbi_debugfs_init, 1628c2ecf20Sopenharmony_ci .name = "st7735r", 1638c2ecf20Sopenharmony_ci .desc = "Sitronix ST7735R", 1648c2ecf20Sopenharmony_ci .date = "20171128", 1658c2ecf20Sopenharmony_ci .major = 1, 1668c2ecf20Sopenharmony_ci .minor = 0, 1678c2ecf20Sopenharmony_ci}; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic const struct of_device_id st7735r_of_match[] = { 1708c2ecf20Sopenharmony_ci { .compatible = "jianda,jd-t18003-t01", .data = &jd_t18003_t01_cfg }, 1718c2ecf20Sopenharmony_ci { .compatible = "okaya,rh128128t", .data = &rh128128t_cfg }, 1728c2ecf20Sopenharmony_ci { }, 1738c2ecf20Sopenharmony_ci}; 1748c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, st7735r_of_match); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic const struct spi_device_id st7735r_id[] = { 1778c2ecf20Sopenharmony_ci { "jd-t18003-t01", (uintptr_t)&jd_t18003_t01_cfg }, 1788c2ecf20Sopenharmony_ci { "rh128128t", (uintptr_t)&rh128128t_cfg }, 1798c2ecf20Sopenharmony_ci { }, 1808c2ecf20Sopenharmony_ci}; 1818c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(spi, st7735r_id); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic int st7735r_probe(struct spi_device *spi) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci struct device *dev = &spi->dev; 1868c2ecf20Sopenharmony_ci const struct st7735r_cfg *cfg; 1878c2ecf20Sopenharmony_ci struct mipi_dbi_dev *dbidev; 1888c2ecf20Sopenharmony_ci struct st7735r_priv *priv; 1898c2ecf20Sopenharmony_ci struct drm_device *drm; 1908c2ecf20Sopenharmony_ci struct mipi_dbi *dbi; 1918c2ecf20Sopenharmony_ci struct gpio_desc *dc; 1928c2ecf20Sopenharmony_ci u32 rotation = 0; 1938c2ecf20Sopenharmony_ci int ret; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci cfg = device_get_match_data(&spi->dev); 1968c2ecf20Sopenharmony_ci if (!cfg) 1978c2ecf20Sopenharmony_ci cfg = (void *)spi_get_device_id(spi)->driver_data; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci priv = devm_drm_dev_alloc(dev, &st7735r_driver, 2008c2ecf20Sopenharmony_ci struct st7735r_priv, dbidev.drm); 2018c2ecf20Sopenharmony_ci if (IS_ERR(priv)) 2028c2ecf20Sopenharmony_ci return PTR_ERR(priv); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci dbidev = &priv->dbidev; 2058c2ecf20Sopenharmony_ci priv->cfg = cfg; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci dbi = &dbidev->dbi; 2088c2ecf20Sopenharmony_ci drm = &dbidev->drm; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci dbi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 2118c2ecf20Sopenharmony_ci if (IS_ERR(dbi->reset)) { 2128c2ecf20Sopenharmony_ci DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n"); 2138c2ecf20Sopenharmony_ci return PTR_ERR(dbi->reset); 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW); 2178c2ecf20Sopenharmony_ci if (IS_ERR(dc)) { 2188c2ecf20Sopenharmony_ci DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n"); 2198c2ecf20Sopenharmony_ci return PTR_ERR(dc); 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci dbidev->backlight = devm_of_find_backlight(dev); 2238c2ecf20Sopenharmony_ci if (IS_ERR(dbidev->backlight)) 2248c2ecf20Sopenharmony_ci return PTR_ERR(dbidev->backlight); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci device_property_read_u32(dev, "rotation", &rotation); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci ret = mipi_dbi_spi_init(spi, dbi, dc); 2298c2ecf20Sopenharmony_ci if (ret) 2308c2ecf20Sopenharmony_ci return ret; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (cfg->write_only) 2338c2ecf20Sopenharmony_ci dbi->read_commands = NULL; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci dbidev->left_offset = cfg->left_offset; 2368c2ecf20Sopenharmony_ci dbidev->top_offset = cfg->top_offset; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci ret = mipi_dbi_dev_init(dbidev, &st7735r_pipe_funcs, &cfg->mode, 2398c2ecf20Sopenharmony_ci rotation); 2408c2ecf20Sopenharmony_ci if (ret) 2418c2ecf20Sopenharmony_ci return ret; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci drm_mode_config_reset(drm); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci ret = drm_dev_register(drm, 0); 2468c2ecf20Sopenharmony_ci if (ret) 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci spi_set_drvdata(spi, drm); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci drm_fbdev_generic_setup(drm, 0); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci return 0; 2548c2ecf20Sopenharmony_ci} 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic int st7735r_remove(struct spi_device *spi) 2578c2ecf20Sopenharmony_ci{ 2588c2ecf20Sopenharmony_ci struct drm_device *drm = spi_get_drvdata(spi); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci drm_dev_unplug(drm); 2618c2ecf20Sopenharmony_ci drm_atomic_helper_shutdown(drm); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci return 0; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic void st7735r_shutdown(struct spi_device *spi) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci drm_atomic_helper_shutdown(spi_get_drvdata(spi)); 2698c2ecf20Sopenharmony_ci} 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cistatic struct spi_driver st7735r_spi_driver = { 2728c2ecf20Sopenharmony_ci .driver = { 2738c2ecf20Sopenharmony_ci .name = "st7735r", 2748c2ecf20Sopenharmony_ci .of_match_table = st7735r_of_match, 2758c2ecf20Sopenharmony_ci }, 2768c2ecf20Sopenharmony_ci .id_table = st7735r_id, 2778c2ecf20Sopenharmony_ci .probe = st7735r_probe, 2788c2ecf20Sopenharmony_ci .remove = st7735r_remove, 2798c2ecf20Sopenharmony_ci .shutdown = st7735r_shutdown, 2808c2ecf20Sopenharmony_ci}; 2818c2ecf20Sopenharmony_cimodule_spi_driver(st7735r_spi_driver); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Sitronix ST7735R DRM driver"); 2848c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Lechner <david@lechnology.com>"); 2858c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 286