1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2020 BayLibre, SAS 4 * Author: Neil Armstrong <narmstrong@baylibre.com> 5 */ 6 7 #include <linux/delay.h> 8 #include <linux/gpio/consumer.h> 9 #include <linux/module.h> 10 #include <linux/of.h> 11 #include <linux/regulator/consumer.h> 12 13 #include <video/mipi_display.h> 14 15 #include <drm/drm_crtc.h> 16 #include <drm/drm_device.h> 17 #include <drm/drm_mipi_dsi.h> 18 #include <drm/drm_modes.h> 19 #include <drm/drm_panel.h> 20 21 struct tdo_tl070wsh30_panel { 22 struct drm_panel base; 23 struct mipi_dsi_device *link; 24 25 struct regulator *supply; 26 struct gpio_desc *reset_gpio; 27 }; 28 29 static inline 30 struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel) 31 { 32 return container_of(panel, struct tdo_tl070wsh30_panel, base); 33 } 34 35 static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel) 36 { 37 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); 38 struct mipi_dsi_multi_context dsi_ctx = { .dsi = tdo_tl070wsh30->link }; 39 int err; 40 41 err = regulator_enable(tdo_tl070wsh30->supply); 42 if (err < 0) 43 return err; 44 45 usleep_range(10000, 11000); 46 47 gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 1); 48 49 usleep_range(10000, 11000); 50 51 gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 0); 52 53 msleep(200); 54 55 mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 56 mipi_dsi_msleep(&dsi_ctx, 200); 57 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 58 mipi_dsi_msleep(&dsi_ctx, 20); 59 60 if (dsi_ctx.accum_err) 61 regulator_disable(tdo_tl070wsh30->supply); 62 63 return dsi_ctx.accum_err; 64 } 65 66 static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel) 67 { 68 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); 69 struct mipi_dsi_multi_context dsi_ctx = { .dsi = tdo_tl070wsh30->link }; 70 71 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 72 /* Reset error to continue power-down sequence even if display off failed */ 73 dsi_ctx.accum_err = 0; 74 usleep_range(10000, 11000); 75 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 76 usleep_range(10000, 11000); 77 78 regulator_disable(tdo_tl070wsh30->supply); 79 80 return 0; 81 } 82 83 static const struct drm_display_mode default_mode = { 84 .clock = 47250, 85 .hdisplay = 1024, 86 .hsync_start = 1024 + 46, 87 .hsync_end = 1024 + 46 + 80, 88 .htotal = 1024 + 46 + 80 + 100, 89 .vdisplay = 600, 90 .vsync_start = 600 + 5, 91 .vsync_end = 600 + 5 + 5, 92 .vtotal = 600 + 5 + 5 + 20, 93 .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, 94 }; 95 96 static int tdo_tl070wsh30_panel_get_modes(struct drm_panel *panel, 97 struct drm_connector *connector) 98 { 99 struct drm_display_mode *mode; 100 101 mode = drm_mode_duplicate(connector->dev, &default_mode); 102 if (!mode) { 103 dev_err(panel->dev, "failed to add mode %ux%u@%u\n", 104 default_mode.hdisplay, default_mode.vdisplay, 105 drm_mode_vrefresh(&default_mode)); 106 return -ENOMEM; 107 } 108 109 drm_mode_set_name(mode); 110 111 drm_mode_probed_add(connector, mode); 112 113 connector->display_info.width_mm = 154; 114 connector->display_info.height_mm = 85; 115 connector->display_info.bpc = 8; 116 117 return 1; 118 } 119 120 static const struct drm_panel_funcs tdo_tl070wsh30_panel_funcs = { 121 .unprepare = tdo_tl070wsh30_panel_unprepare, 122 .prepare = tdo_tl070wsh30_panel_prepare, 123 .get_modes = tdo_tl070wsh30_panel_get_modes, 124 }; 125 126 static const struct of_device_id tdo_tl070wsh30_of_match[] = { 127 { .compatible = "tdo,tl070wsh30", }, 128 { /* sentinel */ } 129 }; 130 MODULE_DEVICE_TABLE(of, tdo_tl070wsh30_of_match); 131 132 static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel *tdo_tl070wsh30) 133 { 134 struct device *dev = &tdo_tl070wsh30->link->dev; 135 int err; 136 137 tdo_tl070wsh30->supply = devm_regulator_get(dev, "power"); 138 if (IS_ERR(tdo_tl070wsh30->supply)) 139 return PTR_ERR(tdo_tl070wsh30->supply); 140 141 tdo_tl070wsh30->reset_gpio = devm_gpiod_get(dev, "reset", 142 GPIOD_OUT_LOW); 143 if (IS_ERR(tdo_tl070wsh30->reset_gpio)) { 144 err = PTR_ERR(tdo_tl070wsh30->reset_gpio); 145 dev_dbg(dev, "failed to get reset gpio: %d\n", err); 146 return err; 147 } 148 149 err = drm_panel_of_backlight(&tdo_tl070wsh30->base); 150 if (err) 151 return err; 152 153 drm_panel_add(&tdo_tl070wsh30->base); 154 155 return 0; 156 } 157 158 static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device *dsi) 159 { 160 struct tdo_tl070wsh30_panel *tdo_tl070wsh30; 161 int err; 162 163 dsi->lanes = 4; 164 dsi->format = MIPI_DSI_FMT_RGB888; 165 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM; 166 167 tdo_tl070wsh30 = devm_drm_panel_alloc(&dsi->dev, 168 __typeof(*tdo_tl070wsh30), base, 169 &tdo_tl070wsh30_panel_funcs, 170 DRM_MODE_CONNECTOR_DSI); 171 172 if (IS_ERR(tdo_tl070wsh30)) 173 return PTR_ERR(tdo_tl070wsh30); 174 175 mipi_dsi_set_drvdata(dsi, tdo_tl070wsh30); 176 tdo_tl070wsh30->link = dsi; 177 178 err = tdo_tl070wsh30_panel_add(tdo_tl070wsh30); 179 if (err < 0) 180 return err; 181 182 return mipi_dsi_attach(dsi); 183 } 184 185 static void tdo_tl070wsh30_panel_remove(struct mipi_dsi_device *dsi) 186 { 187 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi); 188 int err; 189 190 err = mipi_dsi_detach(dsi); 191 if (err < 0) 192 dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); 193 194 drm_panel_remove(&tdo_tl070wsh30->base); 195 } 196 197 static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver = { 198 .driver = { 199 .name = "panel-tdo-tl070wsh30", 200 .of_match_table = tdo_tl070wsh30_of_match, 201 }, 202 .probe = tdo_tl070wsh30_panel_probe, 203 .remove = tdo_tl070wsh30_panel_remove, 204 }; 205 module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver); 206 207 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 208 MODULE_DESCRIPTION("TDO TL070WSH30 panel driver"); 209 MODULE_LICENSE("GPL v2"); 210