18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MIPI-DSI Samsung s6d16d0 panel driver. This is a 864x480 48c2ecf20Sopenharmony_ci * AMOLED panel with a command-only DSI interface. 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <drm/drm_modes.h> 88c2ecf20Sopenharmony_ci#include <drm/drm_mipi_dsi.h> 98c2ecf20Sopenharmony_ci#include <drm/drm_panel.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 128c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/of_device.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistruct s6d16d0 { 188c2ecf20Sopenharmony_ci struct device *dev; 198c2ecf20Sopenharmony_ci struct drm_panel panel; 208c2ecf20Sopenharmony_ci struct regulator *supply; 218c2ecf20Sopenharmony_ci struct gpio_desc *reset_gpio; 228c2ecf20Sopenharmony_ci}; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * The timings are not very helpful as the display is used in 268c2ecf20Sopenharmony_ci * command mode. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistatic const struct drm_display_mode samsung_s6d16d0_mode = { 298c2ecf20Sopenharmony_ci /* HS clock, (htotal*vtotal*vrefresh)/1000 */ 308c2ecf20Sopenharmony_ci .clock = 420160, 318c2ecf20Sopenharmony_ci .hdisplay = 864, 328c2ecf20Sopenharmony_ci .hsync_start = 864 + 154, 338c2ecf20Sopenharmony_ci .hsync_end = 864 + 154 + 16, 348c2ecf20Sopenharmony_ci .htotal = 864 + 154 + 16 + 32, 358c2ecf20Sopenharmony_ci .vdisplay = 480, 368c2ecf20Sopenharmony_ci .vsync_start = 480 + 1, 378c2ecf20Sopenharmony_ci .vsync_end = 480 + 1 + 1, 388c2ecf20Sopenharmony_ci .vtotal = 480 + 1 + 1 + 1, 398c2ecf20Sopenharmony_ci .width_mm = 84, 408c2ecf20Sopenharmony_ci .height_mm = 48, 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic inline struct s6d16d0 *panel_to_s6d16d0(struct drm_panel *panel) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci return container_of(panel, struct s6d16d0, panel); 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic int s6d16d0_unprepare(struct drm_panel *panel) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 518c2ecf20Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 528c2ecf20Sopenharmony_ci int ret; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci /* Enter sleep mode */ 558c2ecf20Sopenharmony_ci ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 568c2ecf20Sopenharmony_ci if (ret) { 578c2ecf20Sopenharmony_ci dev_err(s6->dev, "failed to enter sleep mode (%d)\n", ret); 588c2ecf20Sopenharmony_ci return ret; 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci /* Assert RESET */ 628c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(s6->reset_gpio, 1); 638c2ecf20Sopenharmony_ci regulator_disable(s6->supply); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci return 0; 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int s6d16d0_prepare(struct drm_panel *panel) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 718c2ecf20Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 728c2ecf20Sopenharmony_ci int ret; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = regulator_enable(s6->supply); 758c2ecf20Sopenharmony_ci if (ret) { 768c2ecf20Sopenharmony_ci dev_err(s6->dev, "failed to enable supply (%d)\n", ret); 778c2ecf20Sopenharmony_ci return ret; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Assert RESET */ 818c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(s6->reset_gpio, 1); 828c2ecf20Sopenharmony_ci udelay(10); 838c2ecf20Sopenharmony_ci /* De-assert RESET */ 848c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(s6->reset_gpio, 0); 858c2ecf20Sopenharmony_ci msleep(120); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* Enabe tearing mode: send TE (tearing effect) at VBLANK */ 888c2ecf20Sopenharmony_ci ret = mipi_dsi_dcs_set_tear_on(dsi, 898c2ecf20Sopenharmony_ci MIPI_DSI_DCS_TEAR_MODE_VBLANK); 908c2ecf20Sopenharmony_ci if (ret) { 918c2ecf20Sopenharmony_ci dev_err(s6->dev, "failed to enable vblank TE (%d)\n", ret); 928c2ecf20Sopenharmony_ci return ret; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci /* Exit sleep mode and power on */ 958c2ecf20Sopenharmony_ci ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 968c2ecf20Sopenharmony_ci if (ret) { 978c2ecf20Sopenharmony_ci dev_err(s6->dev, "failed to exit sleep mode (%d)\n", ret); 988c2ecf20Sopenharmony_ci return ret; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return 0; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int s6d16d0_enable(struct drm_panel *panel) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 1078c2ecf20Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 1088c2ecf20Sopenharmony_ci int ret; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci ret = mipi_dsi_dcs_set_display_on(dsi); 1118c2ecf20Sopenharmony_ci if (ret) { 1128c2ecf20Sopenharmony_ci dev_err(s6->dev, "failed to turn display on (%d)\n", ret); 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci return 0; 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int s6d16d0_disable(struct drm_panel *panel) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 1228c2ecf20Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 1238c2ecf20Sopenharmony_ci int ret; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci ret = mipi_dsi_dcs_set_display_off(dsi); 1268c2ecf20Sopenharmony_ci if (ret) { 1278c2ecf20Sopenharmony_ci dev_err(s6->dev, "failed to turn display off (%d)\n", ret); 1288c2ecf20Sopenharmony_ci return ret; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci return 0; 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic int s6d16d0_get_modes(struct drm_panel *panel, 1358c2ecf20Sopenharmony_ci struct drm_connector *connector) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci struct drm_display_mode *mode; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, &samsung_s6d16d0_mode); 1408c2ecf20Sopenharmony_ci if (!mode) { 1418c2ecf20Sopenharmony_ci dev_err(panel->dev, "bad mode or failed to add mode\n"); 1428c2ecf20Sopenharmony_ci return -EINVAL; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci drm_mode_set_name(mode); 1458c2ecf20Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci connector->display_info.width_mm = mode->width_mm; 1488c2ecf20Sopenharmony_ci connector->display_info.height_mm = mode->height_mm; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci drm_mode_probed_add(connector, mode); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci return 1; /* Number of modes */ 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic const struct drm_panel_funcs s6d16d0_drm_funcs = { 1568c2ecf20Sopenharmony_ci .disable = s6d16d0_disable, 1578c2ecf20Sopenharmony_ci .unprepare = s6d16d0_unprepare, 1588c2ecf20Sopenharmony_ci .prepare = s6d16d0_prepare, 1598c2ecf20Sopenharmony_ci .enable = s6d16d0_enable, 1608c2ecf20Sopenharmony_ci .get_modes = s6d16d0_get_modes, 1618c2ecf20Sopenharmony_ci}; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic int s6d16d0_probe(struct mipi_dsi_device *dsi) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci struct device *dev = &dsi->dev; 1668c2ecf20Sopenharmony_ci struct s6d16d0 *s6; 1678c2ecf20Sopenharmony_ci int ret; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci s6 = devm_kzalloc(dev, sizeof(struct s6d16d0), GFP_KERNEL); 1708c2ecf20Sopenharmony_ci if (!s6) 1718c2ecf20Sopenharmony_ci return -ENOMEM; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci mipi_dsi_set_drvdata(dsi, s6); 1748c2ecf20Sopenharmony_ci s6->dev = dev; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci dsi->lanes = 2; 1778c2ecf20Sopenharmony_ci dsi->format = MIPI_DSI_FMT_RGB888; 1788c2ecf20Sopenharmony_ci dsi->hs_rate = 420160000; 1798c2ecf20Sopenharmony_ci dsi->lp_rate = 19200000; 1808c2ecf20Sopenharmony_ci /* 1818c2ecf20Sopenharmony_ci * This display uses command mode so no MIPI_DSI_MODE_VIDEO 1828c2ecf20Sopenharmony_ci * or MIPI_DSI_MODE_VIDEO_SYNC_PULSE 1838c2ecf20Sopenharmony_ci * 1848c2ecf20Sopenharmony_ci * As we only send commands we do not need to be continuously 1858c2ecf20Sopenharmony_ci * clocked. 1868c2ecf20Sopenharmony_ci */ 1878c2ecf20Sopenharmony_ci dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci s6->supply = devm_regulator_get(dev, "vdd1"); 1908c2ecf20Sopenharmony_ci if (IS_ERR(s6->supply)) 1918c2ecf20Sopenharmony_ci return PTR_ERR(s6->supply); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci /* This asserts RESET by default */ 1948c2ecf20Sopenharmony_ci s6->reset_gpio = devm_gpiod_get_optional(dev, "reset", 1958c2ecf20Sopenharmony_ci GPIOD_OUT_HIGH); 1968c2ecf20Sopenharmony_ci if (IS_ERR(s6->reset_gpio)) { 1978c2ecf20Sopenharmony_ci ret = PTR_ERR(s6->reset_gpio); 1988c2ecf20Sopenharmony_ci if (ret != -EPROBE_DEFER) 1998c2ecf20Sopenharmony_ci dev_err(dev, "failed to request GPIO (%d)\n", ret); 2008c2ecf20Sopenharmony_ci return ret; 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci drm_panel_init(&s6->panel, dev, &s6d16d0_drm_funcs, 2048c2ecf20Sopenharmony_ci DRM_MODE_CONNECTOR_DSI); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci drm_panel_add(&s6->panel); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci ret = mipi_dsi_attach(dsi); 2098c2ecf20Sopenharmony_ci if (ret < 0) 2108c2ecf20Sopenharmony_ci drm_panel_remove(&s6->panel); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci return ret; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic int s6d16d0_remove(struct mipi_dsi_device *dsi) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci struct s6d16d0 *s6 = mipi_dsi_get_drvdata(dsi); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci mipi_dsi_detach(dsi); 2208c2ecf20Sopenharmony_ci drm_panel_remove(&s6->panel); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return 0; 2238c2ecf20Sopenharmony_ci} 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistatic const struct of_device_id s6d16d0_of_match[] = { 2268c2ecf20Sopenharmony_ci { .compatible = "samsung,s6d16d0" }, 2278c2ecf20Sopenharmony_ci { } 2288c2ecf20Sopenharmony_ci}; 2298c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, s6d16d0_of_match); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_cistatic struct mipi_dsi_driver s6d16d0_driver = { 2328c2ecf20Sopenharmony_ci .probe = s6d16d0_probe, 2338c2ecf20Sopenharmony_ci .remove = s6d16d0_remove, 2348c2ecf20Sopenharmony_ci .driver = { 2358c2ecf20Sopenharmony_ci .name = "panel-samsung-s6d16d0", 2368c2ecf20Sopenharmony_ci .of_match_table = s6d16d0_of_match, 2378c2ecf20Sopenharmony_ci }, 2388c2ecf20Sopenharmony_ci}; 2398c2ecf20Sopenharmony_cimodule_mipi_dsi_driver(s6d16d0_driver); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ciMODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>"); 2428c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MIPI-DSI s6d16d0 Panel Driver"); 2438c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 244