1ac1d6d74SLinus Walleij // SPDX-License-Identifier: GPL-2.0+ 2ac1d6d74SLinus Walleij /* 3ac1d6d74SLinus Walleij * MIPI-DSI Samsung s6d16d0 panel driver. This is a 864x480 4ac1d6d74SLinus Walleij * AMOLED panel with a command-only DSI interface. 5ac1d6d74SLinus Walleij */ 6ac1d6d74SLinus Walleij 7ac1d6d74SLinus Walleij #include <drm/drm_modes.h> 8ac1d6d74SLinus Walleij #include <drm/drm_mipi_dsi.h> 9ac1d6d74SLinus Walleij #include <drm/drm_panel.h> 10ac1d6d74SLinus Walleij #include <drm/drm_print.h> 11ac1d6d74SLinus Walleij 12ac1d6d74SLinus Walleij #include <linux/gpio/consumer.h> 13ac1d6d74SLinus Walleij #include <linux/regulator/consumer.h> 14ac1d6d74SLinus Walleij #include <linux/delay.h> 15ac1d6d74SLinus Walleij #include <linux/of_device.h> 16ac1d6d74SLinus Walleij #include <linux/module.h> 17ac1d6d74SLinus Walleij 18ac1d6d74SLinus Walleij struct s6d16d0 { 19ac1d6d74SLinus Walleij struct device *dev; 20ac1d6d74SLinus Walleij struct drm_panel panel; 21ac1d6d74SLinus Walleij struct regulator *supply; 22ac1d6d74SLinus Walleij struct gpio_desc *reset_gpio; 23ac1d6d74SLinus Walleij }; 24ac1d6d74SLinus Walleij 25ac1d6d74SLinus Walleij /* 26ac1d6d74SLinus Walleij * The timings are not very helpful as the display is used in 27ac1d6d74SLinus Walleij * command mode. 28ac1d6d74SLinus Walleij */ 29ac1d6d74SLinus Walleij static const struct drm_display_mode samsung_s6d16d0_mode = { 30ac1d6d74SLinus Walleij /* HS clock, (htotal*vtotal*vrefresh)/1000 */ 31ac1d6d74SLinus Walleij .clock = 420160, 32ac1d6d74SLinus Walleij .hdisplay = 864, 33ac1d6d74SLinus Walleij .hsync_start = 864 + 154, 34ac1d6d74SLinus Walleij .hsync_end = 864 + 154 + 16, 35ac1d6d74SLinus Walleij .htotal = 864 + 154 + 16 + 32, 36ac1d6d74SLinus Walleij .vdisplay = 480, 37ac1d6d74SLinus Walleij .vsync_start = 480 + 1, 38ac1d6d74SLinus Walleij .vsync_end = 480 + 1 + 1, 39ac1d6d74SLinus Walleij .vtotal = 480 + 1 + 1 + 1, 40ac1d6d74SLinus Walleij .width_mm = 84, 41ac1d6d74SLinus Walleij .height_mm = 48, 42ac1d6d74SLinus Walleij }; 43ac1d6d74SLinus Walleij 44ac1d6d74SLinus Walleij static inline struct s6d16d0 *panel_to_s6d16d0(struct drm_panel *panel) 45ac1d6d74SLinus Walleij { 46ac1d6d74SLinus Walleij return container_of(panel, struct s6d16d0, panel); 47ac1d6d74SLinus Walleij } 48ac1d6d74SLinus Walleij 49ac1d6d74SLinus Walleij static int s6d16d0_unprepare(struct drm_panel *panel) 50ac1d6d74SLinus Walleij { 51ac1d6d74SLinus Walleij struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 52ac1d6d74SLinus Walleij struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 53ac1d6d74SLinus Walleij int ret; 54ac1d6d74SLinus Walleij 55ac1d6d74SLinus Walleij /* Enter sleep mode */ 56ac1d6d74SLinus Walleij ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 57ac1d6d74SLinus Walleij if (ret) { 58ac1d6d74SLinus Walleij DRM_DEV_ERROR(s6->dev, "failed to enter sleep mode (%d)\n", 59ac1d6d74SLinus Walleij ret); 60ac1d6d74SLinus Walleij return ret; 61ac1d6d74SLinus Walleij } 62ac1d6d74SLinus Walleij 63ac1d6d74SLinus Walleij /* Assert RESET */ 64ac1d6d74SLinus Walleij gpiod_set_value_cansleep(s6->reset_gpio, 1); 65ac1d6d74SLinus Walleij regulator_disable(s6->supply); 66ac1d6d74SLinus Walleij 67ac1d6d74SLinus Walleij return 0; 68ac1d6d74SLinus Walleij } 69ac1d6d74SLinus Walleij 70ac1d6d74SLinus Walleij static int s6d16d0_prepare(struct drm_panel *panel) 71ac1d6d74SLinus Walleij { 72ac1d6d74SLinus Walleij struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 73ac1d6d74SLinus Walleij struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 74ac1d6d74SLinus Walleij int ret; 75ac1d6d74SLinus Walleij 76ac1d6d74SLinus Walleij ret = regulator_enable(s6->supply); 77ac1d6d74SLinus Walleij if (ret) { 78ac1d6d74SLinus Walleij DRM_DEV_ERROR(s6->dev, "failed to enable supply (%d)\n", ret); 79ac1d6d74SLinus Walleij return ret; 80ac1d6d74SLinus Walleij } 81ac1d6d74SLinus Walleij 82ac1d6d74SLinus Walleij /* Assert RESET */ 83ac1d6d74SLinus Walleij gpiod_set_value_cansleep(s6->reset_gpio, 1); 84ac1d6d74SLinus Walleij udelay(10); 85ac1d6d74SLinus Walleij /* De-assert RESET */ 86ac1d6d74SLinus Walleij gpiod_set_value_cansleep(s6->reset_gpio, 0); 87ac1d6d74SLinus Walleij msleep(120); 88ac1d6d74SLinus Walleij 89ac1d6d74SLinus Walleij /* Enabe tearing mode: send TE (tearing effect) at VBLANK */ 90ac1d6d74SLinus Walleij ret = mipi_dsi_dcs_set_tear_on(dsi, 91ac1d6d74SLinus Walleij MIPI_DSI_DCS_TEAR_MODE_VBLANK); 92ac1d6d74SLinus Walleij if (ret) { 93f577f7eaSColin Ian King DRM_DEV_ERROR(s6->dev, "failed to enable vblank TE (%d)\n", 94ac1d6d74SLinus Walleij ret); 95ac1d6d74SLinus Walleij return ret; 96ac1d6d74SLinus Walleij } 97ac1d6d74SLinus Walleij /* Exit sleep mode and power on */ 98ac1d6d74SLinus Walleij ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 99ac1d6d74SLinus Walleij if (ret) { 100ac1d6d74SLinus Walleij DRM_DEV_ERROR(s6->dev, "failed to exit sleep mode (%d)\n", 101ac1d6d74SLinus Walleij ret); 102ac1d6d74SLinus Walleij return ret; 103ac1d6d74SLinus Walleij } 104ac1d6d74SLinus Walleij 105ac1d6d74SLinus Walleij return 0; 106ac1d6d74SLinus Walleij } 107ac1d6d74SLinus Walleij 108ac1d6d74SLinus Walleij static int s6d16d0_enable(struct drm_panel *panel) 109ac1d6d74SLinus Walleij { 110ac1d6d74SLinus Walleij struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 111ac1d6d74SLinus Walleij struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 112ac1d6d74SLinus Walleij int ret; 113ac1d6d74SLinus Walleij 114ac1d6d74SLinus Walleij ret = mipi_dsi_dcs_set_display_on(dsi); 115ac1d6d74SLinus Walleij if (ret) { 116ac1d6d74SLinus Walleij DRM_DEV_ERROR(s6->dev, "failed to turn display on (%d)\n", 117ac1d6d74SLinus Walleij ret); 118ac1d6d74SLinus Walleij return ret; 119ac1d6d74SLinus Walleij } 120ac1d6d74SLinus Walleij 121ac1d6d74SLinus Walleij return 0; 122ac1d6d74SLinus Walleij } 123ac1d6d74SLinus Walleij 124ac1d6d74SLinus Walleij static int s6d16d0_disable(struct drm_panel *panel) 125ac1d6d74SLinus Walleij { 126ac1d6d74SLinus Walleij struct s6d16d0 *s6 = panel_to_s6d16d0(panel); 127ac1d6d74SLinus Walleij struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); 128ac1d6d74SLinus Walleij int ret; 129ac1d6d74SLinus Walleij 130ac1d6d74SLinus Walleij ret = mipi_dsi_dcs_set_display_off(dsi); 131ac1d6d74SLinus Walleij if (ret) { 132ac1d6d74SLinus Walleij DRM_DEV_ERROR(s6->dev, "failed to turn display off (%d)\n", 133ac1d6d74SLinus Walleij ret); 134ac1d6d74SLinus Walleij return ret; 135ac1d6d74SLinus Walleij } 136ac1d6d74SLinus Walleij 137ac1d6d74SLinus Walleij return 0; 138ac1d6d74SLinus Walleij } 139ac1d6d74SLinus Walleij 1400ce8ddd8SSam Ravnborg static int s6d16d0_get_modes(struct drm_panel *panel, 1410ce8ddd8SSam Ravnborg struct drm_connector *connector) 142ac1d6d74SLinus Walleij { 143ac1d6d74SLinus Walleij struct drm_display_mode *mode; 144ac1d6d74SLinus Walleij 145aa6c4364SSam Ravnborg mode = drm_mode_duplicate(connector->dev, &samsung_s6d16d0_mode); 146ac1d6d74SLinus Walleij if (!mode) { 147ac1d6d74SLinus Walleij DRM_ERROR("bad mode or failed to add mode\n"); 148ac1d6d74SLinus Walleij return -EINVAL; 149ac1d6d74SLinus Walleij } 150ac1d6d74SLinus Walleij drm_mode_set_name(mode); 151ac1d6d74SLinus Walleij mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 152ac1d6d74SLinus Walleij 153ac1d6d74SLinus Walleij connector->display_info.width_mm = mode->width_mm; 154ac1d6d74SLinus Walleij connector->display_info.height_mm = mode->height_mm; 155ac1d6d74SLinus Walleij 156ac1d6d74SLinus Walleij drm_mode_probed_add(connector, mode); 157ac1d6d74SLinus Walleij 158ac1d6d74SLinus Walleij return 1; /* Number of modes */ 159ac1d6d74SLinus Walleij } 160ac1d6d74SLinus Walleij 161ac1d6d74SLinus Walleij static const struct drm_panel_funcs s6d16d0_drm_funcs = { 162ac1d6d74SLinus Walleij .disable = s6d16d0_disable, 163ac1d6d74SLinus Walleij .unprepare = s6d16d0_unprepare, 164ac1d6d74SLinus Walleij .prepare = s6d16d0_prepare, 165ac1d6d74SLinus Walleij .enable = s6d16d0_enable, 166ac1d6d74SLinus Walleij .get_modes = s6d16d0_get_modes, 167ac1d6d74SLinus Walleij }; 168ac1d6d74SLinus Walleij 169ac1d6d74SLinus Walleij static int s6d16d0_probe(struct mipi_dsi_device *dsi) 170ac1d6d74SLinus Walleij { 171ac1d6d74SLinus Walleij struct device *dev = &dsi->dev; 172ac1d6d74SLinus Walleij struct s6d16d0 *s6; 173ac1d6d74SLinus Walleij int ret; 174ac1d6d74SLinus Walleij 175ac1d6d74SLinus Walleij s6 = devm_kzalloc(dev, sizeof(struct s6d16d0), GFP_KERNEL); 176ac1d6d74SLinus Walleij if (!s6) 177ac1d6d74SLinus Walleij return -ENOMEM; 178ac1d6d74SLinus Walleij 179ac1d6d74SLinus Walleij mipi_dsi_set_drvdata(dsi, s6); 180ac1d6d74SLinus Walleij s6->dev = dev; 181ac1d6d74SLinus Walleij 182ac1d6d74SLinus Walleij dsi->lanes = 2; 183ac1d6d74SLinus Walleij dsi->format = MIPI_DSI_FMT_RGB888; 184ac1d6d74SLinus Walleij dsi->hs_rate = 420160000; 185ac1d6d74SLinus Walleij dsi->lp_rate = 19200000; 186ac1d6d74SLinus Walleij /* 187ac1d6d74SLinus Walleij * This display uses command mode so no MIPI_DSI_MODE_VIDEO 188ac1d6d74SLinus Walleij * or MIPI_DSI_MODE_VIDEO_SYNC_PULSE 189ac1d6d74SLinus Walleij * 190ac1d6d74SLinus Walleij * As we only send commands we do not need to be continuously 191ac1d6d74SLinus Walleij * clocked. 192ac1d6d74SLinus Walleij */ 193ac1d6d74SLinus Walleij dsi->mode_flags = 194ac1d6d74SLinus Walleij MIPI_DSI_CLOCK_NON_CONTINUOUS | 195ac1d6d74SLinus Walleij MIPI_DSI_MODE_EOT_PACKET; 196ac1d6d74SLinus Walleij 197ac1d6d74SLinus Walleij s6->supply = devm_regulator_get(dev, "vdd1"); 198ac1d6d74SLinus Walleij if (IS_ERR(s6->supply)) 199ac1d6d74SLinus Walleij return PTR_ERR(s6->supply); 200ac1d6d74SLinus Walleij 201ac1d6d74SLinus Walleij /* This asserts RESET by default */ 202ac1d6d74SLinus Walleij s6->reset_gpio = devm_gpiod_get_optional(dev, "reset", 203ac1d6d74SLinus Walleij GPIOD_OUT_HIGH); 204ac1d6d74SLinus Walleij if (IS_ERR(s6->reset_gpio)) { 205ac1d6d74SLinus Walleij ret = PTR_ERR(s6->reset_gpio); 206ac1d6d74SLinus Walleij if (ret != -EPROBE_DEFER) 207ac1d6d74SLinus Walleij DRM_DEV_ERROR(dev, "failed to request GPIO (%d)\n", 208ac1d6d74SLinus Walleij ret); 209ac1d6d74SLinus Walleij return ret; 210ac1d6d74SLinus Walleij } 211ac1d6d74SLinus Walleij 2129a2654c0SLaurent Pinchart drm_panel_init(&s6->panel, dev, &s6d16d0_drm_funcs, 2139a2654c0SLaurent Pinchart DRM_MODE_CONNECTOR_DSI); 214ac1d6d74SLinus Walleij 215c3ee8c65SBernard Zhao drm_panel_add(&s6->panel); 216ac1d6d74SLinus Walleij 217ac1d6d74SLinus Walleij ret = mipi_dsi_attach(dsi); 218ac1d6d74SLinus Walleij if (ret < 0) 219ac1d6d74SLinus Walleij drm_panel_remove(&s6->panel); 220ac1d6d74SLinus Walleij 221ac1d6d74SLinus Walleij return ret; 222ac1d6d74SLinus Walleij } 223ac1d6d74SLinus Walleij 224ac1d6d74SLinus Walleij static int s6d16d0_remove(struct mipi_dsi_device *dsi) 225ac1d6d74SLinus Walleij { 226ac1d6d74SLinus Walleij struct s6d16d0 *s6 = mipi_dsi_get_drvdata(dsi); 227ac1d6d74SLinus Walleij 228ac1d6d74SLinus Walleij mipi_dsi_detach(dsi); 229ac1d6d74SLinus Walleij drm_panel_remove(&s6->panel); 230ac1d6d74SLinus Walleij 231ac1d6d74SLinus Walleij return 0; 232ac1d6d74SLinus Walleij } 233ac1d6d74SLinus Walleij 234ac1d6d74SLinus Walleij static const struct of_device_id s6d16d0_of_match[] = { 235ac1d6d74SLinus Walleij { .compatible = "samsung,s6d16d0" }, 236ac1d6d74SLinus Walleij { } 237ac1d6d74SLinus Walleij }; 238ac1d6d74SLinus Walleij MODULE_DEVICE_TABLE(of, s6d16d0_of_match); 239ac1d6d74SLinus Walleij 240ac1d6d74SLinus Walleij static struct mipi_dsi_driver s6d16d0_driver = { 241ac1d6d74SLinus Walleij .probe = s6d16d0_probe, 242ac1d6d74SLinus Walleij .remove = s6d16d0_remove, 243ac1d6d74SLinus Walleij .driver = { 244ac1d6d74SLinus Walleij .name = "panel-samsung-s6d16d0", 245ac1d6d74SLinus Walleij .of_match_table = s6d16d0_of_match, 246ac1d6d74SLinus Walleij }, 247ac1d6d74SLinus Walleij }; 248ac1d6d74SLinus Walleij module_mipi_dsi_driver(s6d16d0_driver); 249ac1d6d74SLinus Walleij 250ac1d6d74SLinus Walleij MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>"); 251ac1d6d74SLinus Walleij MODULE_DESCRIPTION("MIPI-DSI s6d16d0 Panel Driver"); 252ac1d6d74SLinus Walleij MODULE_LICENSE("GPL v2"); 253