18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Sharp LS037V7DW01 LCD Panel Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Texas Instruments Incorporated 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Based on the omapdrm-specific panel-sharp-ls037v7dw01 driver 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright (C) 2013 Texas Instruments Incorporated 108c2ecf20Sopenharmony_ci * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <drm/drm_connector.h> 218c2ecf20Sopenharmony_ci#include <drm/drm_modes.h> 228c2ecf20Sopenharmony_ci#include <drm/drm_panel.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistruct ls037v7dw01_panel { 258c2ecf20Sopenharmony_ci struct drm_panel panel; 268c2ecf20Sopenharmony_ci struct platform_device *pdev; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci struct regulator *vdd; 298c2ecf20Sopenharmony_ci struct gpio_desc *resb_gpio; /* low = reset active min 20 us */ 308c2ecf20Sopenharmony_ci struct gpio_desc *ini_gpio; /* high = power on */ 318c2ecf20Sopenharmony_ci struct gpio_desc *mo_gpio; /* low = 480x640, high = 240x320 */ 328c2ecf20Sopenharmony_ci struct gpio_desc *lr_gpio; /* high = conventional horizontal scanning */ 338c2ecf20Sopenharmony_ci struct gpio_desc *ud_gpio; /* high = conventional vertical scanning */ 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define to_ls037v7dw01_device(p) \ 378c2ecf20Sopenharmony_ci container_of(p, struct ls037v7dw01_panel, panel) 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic int ls037v7dw01_disable(struct drm_panel *panel) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(lcd->ini_gpio, 0); 448c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(lcd->resb_gpio, 0); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci /* Wait at least 5 vsyncs after disabling the LCD. */ 478c2ecf20Sopenharmony_ci msleep(100); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int ls037v7dw01_unprepare(struct drm_panel *panel) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci regulator_disable(lcd->vdd); 578c2ecf20Sopenharmony_ci return 0; 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic int ls037v7dw01_prepare(struct drm_panel *panel) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); 638c2ecf20Sopenharmony_ci int ret; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci ret = regulator_enable(lcd->vdd); 668c2ecf20Sopenharmony_ci if (ret < 0) 678c2ecf20Sopenharmony_ci dev_err(&lcd->pdev->dev, "%s: failed to enable regulator\n", 688c2ecf20Sopenharmony_ci __func__); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci return ret; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int ls037v7dw01_enable(struct drm_panel *panel) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* Wait couple of vsyncs before enabling the LCD. */ 788c2ecf20Sopenharmony_ci msleep(50); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(lcd->resb_gpio, 1); 818c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(lcd->ini_gpio, 1); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return 0; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic const struct drm_display_mode ls037v7dw01_mode = { 878c2ecf20Sopenharmony_ci .clock = 19200, 888c2ecf20Sopenharmony_ci .hdisplay = 480, 898c2ecf20Sopenharmony_ci .hsync_start = 480 + 1, 908c2ecf20Sopenharmony_ci .hsync_end = 480 + 1 + 2, 918c2ecf20Sopenharmony_ci .htotal = 480 + 1 + 2 + 28, 928c2ecf20Sopenharmony_ci .vdisplay = 640, 938c2ecf20Sopenharmony_ci .vsync_start = 640 + 1, 948c2ecf20Sopenharmony_ci .vsync_end = 640 + 1 + 1, 958c2ecf20Sopenharmony_ci .vtotal = 640 + 1 + 1 + 1, 968c2ecf20Sopenharmony_ci .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, 978c2ecf20Sopenharmony_ci .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 988c2ecf20Sopenharmony_ci .width_mm = 56, 998c2ecf20Sopenharmony_ci .height_mm = 75, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic int ls037v7dw01_get_modes(struct drm_panel *panel, 1038c2ecf20Sopenharmony_ci struct drm_connector *connector) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct drm_display_mode *mode; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, &ls037v7dw01_mode); 1088c2ecf20Sopenharmony_ci if (!mode) 1098c2ecf20Sopenharmony_ci return -ENOMEM; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci drm_mode_set_name(mode); 1128c2ecf20Sopenharmony_ci drm_mode_probed_add(connector, mode); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci connector->display_info.width_mm = ls037v7dw01_mode.width_mm; 1158c2ecf20Sopenharmony_ci connector->display_info.height_mm = ls037v7dw01_mode.height_mm; 1168c2ecf20Sopenharmony_ci /* 1178c2ecf20Sopenharmony_ci * FIXME: According to the datasheet pixel data is sampled on the 1188c2ecf20Sopenharmony_ci * rising edge of the clock, but the code running on the SDP3430 1198c2ecf20Sopenharmony_ci * indicates sampling on the negative edge. This should be tested on a 1208c2ecf20Sopenharmony_ci * real device. 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_ci connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH 1238c2ecf20Sopenharmony_ci | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE 1248c2ecf20Sopenharmony_ci | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci return 1; 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic const struct drm_panel_funcs ls037v7dw01_funcs = { 1308c2ecf20Sopenharmony_ci .disable = ls037v7dw01_disable, 1318c2ecf20Sopenharmony_ci .unprepare = ls037v7dw01_unprepare, 1328c2ecf20Sopenharmony_ci .prepare = ls037v7dw01_prepare, 1338c2ecf20Sopenharmony_ci .enable = ls037v7dw01_enable, 1348c2ecf20Sopenharmony_ci .get_modes = ls037v7dw01_get_modes, 1358c2ecf20Sopenharmony_ci}; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic int ls037v7dw01_probe(struct platform_device *pdev) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci struct ls037v7dw01_panel *lcd; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL); 1428c2ecf20Sopenharmony_ci if (!lcd) 1438c2ecf20Sopenharmony_ci return -ENOMEM; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, lcd); 1468c2ecf20Sopenharmony_ci lcd->pdev = pdev; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci lcd->vdd = devm_regulator_get(&pdev->dev, "envdd"); 1498c2ecf20Sopenharmony_ci if (IS_ERR(lcd->vdd)) { 1508c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get regulator\n"); 1518c2ecf20Sopenharmony_ci return PTR_ERR(lcd->vdd); 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci lcd->ini_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW); 1558c2ecf20Sopenharmony_ci if (IS_ERR(lcd->ini_gpio)) { 1568c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get enable gpio\n"); 1578c2ecf20Sopenharmony_ci return PTR_ERR(lcd->ini_gpio); 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci lcd->resb_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); 1618c2ecf20Sopenharmony_ci if (IS_ERR(lcd->resb_gpio)) { 1628c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get reset gpio\n"); 1638c2ecf20Sopenharmony_ci return PTR_ERR(lcd->resb_gpio); 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci lcd->mo_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 0, 1678c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 1688c2ecf20Sopenharmony_ci if (IS_ERR(lcd->mo_gpio)) { 1698c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get mode[0] gpio\n"); 1708c2ecf20Sopenharmony_ci return PTR_ERR(lcd->mo_gpio); 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci lcd->lr_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 1, 1748c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 1758c2ecf20Sopenharmony_ci if (IS_ERR(lcd->lr_gpio)) { 1768c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get mode[1] gpio\n"); 1778c2ecf20Sopenharmony_ci return PTR_ERR(lcd->lr_gpio); 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci lcd->ud_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 2, 1818c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 1828c2ecf20Sopenharmony_ci if (IS_ERR(lcd->ud_gpio)) { 1838c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get mode[2] gpio\n"); 1848c2ecf20Sopenharmony_ci return PTR_ERR(lcd->ud_gpio); 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci drm_panel_init(&lcd->panel, &pdev->dev, &ls037v7dw01_funcs, 1888c2ecf20Sopenharmony_ci DRM_MODE_CONNECTOR_DPI); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci drm_panel_add(&lcd->panel); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return 0; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int ls037v7dw01_remove(struct platform_device *pdev) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci struct ls037v7dw01_panel *lcd = platform_get_drvdata(pdev); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci drm_panel_remove(&lcd->panel); 2008c2ecf20Sopenharmony_ci drm_panel_disable(&lcd->panel); 2018c2ecf20Sopenharmony_ci drm_panel_unprepare(&lcd->panel); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci return 0; 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic const struct of_device_id ls037v7dw01_of_match[] = { 2078c2ecf20Sopenharmony_ci { .compatible = "sharp,ls037v7dw01", }, 2088c2ecf20Sopenharmony_ci { /* sentinel */ }, 2098c2ecf20Sopenharmony_ci}; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ls037v7dw01_of_match); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic struct platform_driver ls037v7dw01_driver = { 2148c2ecf20Sopenharmony_ci .probe = ls037v7dw01_probe, 2158c2ecf20Sopenharmony_ci .remove = ls037v7dw01_remove, 2168c2ecf20Sopenharmony_ci .driver = { 2178c2ecf20Sopenharmony_ci .name = "panel-sharp-ls037v7dw01", 2188c2ecf20Sopenharmony_ci .of_match_table = ls037v7dw01_of_match, 2198c2ecf20Sopenharmony_ci }, 2208c2ecf20Sopenharmony_ci}; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cimodule_platform_driver(ls037v7dw01_driver); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 2258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver"); 2268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 227