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 bool prepared; 29 }; 30 31 static inline 32 struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel) 33 { 34 return container_of(panel, struct tdo_tl070wsh30_panel, base); 35 } 36 37 static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel) 38 { 39 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); 40 int err; 41 42 if (tdo_tl070wsh30->prepared) 43 return 0; 44 45 err = regulator_enable(tdo_tl070wsh30->supply); 46 if (err < 0) 47 return err; 48 49 usleep_range(10000, 11000); 50 51 gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 1); 52 53 usleep_range(10000, 11000); 54 55 gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 0); 56 57 msleep(200); 58 59 err = mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30->link); 60 if (err < 0) { 61 dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); 62 regulator_disable(tdo_tl070wsh30->supply); 63 return err; 64 } 65 66 msleep(200); 67 68 err = mipi_dsi_dcs_set_display_on(tdo_tl070wsh30->link); 69 if (err < 0) { 70 dev_err(panel->dev, "failed to set display on: %d\n", err); 71 regulator_disable(tdo_tl070wsh30->supply); 72 return err; 73 } 74 75 msleep(20); 76 77 tdo_tl070wsh30->prepared = true; 78 79 return 0; 80 } 81 82 static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel) 83 { 84 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); 85 int err; 86 87 if (!tdo_tl070wsh30->prepared) 88 return 0; 89 90 err = mipi_dsi_dcs_set_display_off(tdo_tl070wsh30->link); 91 if (err < 0) 92 dev_err(panel->dev, "failed to set display off: %d\n", err); 93 94 usleep_range(10000, 11000); 95 96 err = mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30->link); 97 if (err < 0) { 98 dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); 99 return err; 100 } 101 102 usleep_range(10000, 11000); 103 104 regulator_disable(tdo_tl070wsh30->supply); 105 106 tdo_tl070wsh30->prepared = false; 107 108 return 0; 109 } 110 111 static const struct drm_display_mode default_mode = { 112 .clock = 47250, 113 .hdisplay = 1024, 114 .hsync_start = 1024 + 46, 115 .hsync_end = 1024 + 46 + 80, 116 .htotal = 1024 + 46 + 80 + 100, 117 .vdisplay = 600, 118 .vsync_start = 600 + 5, 119 .vsync_end = 600 + 5 + 5, 120 .vtotal = 600 + 5 + 5 + 20, 121 .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, 122 }; 123 124 static int tdo_tl070wsh30_panel_get_modes(struct drm_panel *panel, 125 struct drm_connector *connector) 126 { 127 struct drm_display_mode *mode; 128 129 mode = drm_mode_duplicate(connector->dev, &default_mode); 130 if (!mode) { 131 dev_err(panel->dev, "failed to add mode %ux%u@%u\n", 132 default_mode.hdisplay, default_mode.vdisplay, 133 drm_mode_vrefresh(&default_mode)); 134 return -ENOMEM; 135 } 136 137 drm_mode_set_name(mode); 138 139 drm_mode_probed_add(connector, mode); 140 141 connector->display_info.width_mm = 154; 142 connector->display_info.height_mm = 85; 143 connector->display_info.bpc = 8; 144 145 return 1; 146 } 147 148 static const struct drm_panel_funcs tdo_tl070wsh30_panel_funcs = { 149 .unprepare = tdo_tl070wsh30_panel_unprepare, 150 .prepare = tdo_tl070wsh30_panel_prepare, 151 .get_modes = tdo_tl070wsh30_panel_get_modes, 152 }; 153 154 static const struct of_device_id tdo_tl070wsh30_of_match[] = { 155 { .compatible = "tdo,tl070wsh30", }, 156 { /* sentinel */ } 157 }; 158 MODULE_DEVICE_TABLE(of, tdo_tl070wsh30_of_match); 159 160 static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel *tdo_tl070wsh30) 161 { 162 struct device *dev = &tdo_tl070wsh30->link->dev; 163 int err; 164 165 tdo_tl070wsh30->supply = devm_regulator_get(dev, "power"); 166 if (IS_ERR(tdo_tl070wsh30->supply)) 167 return PTR_ERR(tdo_tl070wsh30->supply); 168 169 tdo_tl070wsh30->reset_gpio = devm_gpiod_get(dev, "reset", 170 GPIOD_OUT_LOW); 171 if (IS_ERR(tdo_tl070wsh30->reset_gpio)) { 172 err = PTR_ERR(tdo_tl070wsh30->reset_gpio); 173 dev_dbg(dev, "failed to get reset gpio: %d\n", err); 174 return err; 175 } 176 177 drm_panel_init(&tdo_tl070wsh30->base, &tdo_tl070wsh30->link->dev, 178 &tdo_tl070wsh30_panel_funcs, DRM_MODE_CONNECTOR_DSI); 179 180 err = drm_panel_of_backlight(&tdo_tl070wsh30->base); 181 if (err) 182 return err; 183 184 drm_panel_add(&tdo_tl070wsh30->base); 185 186 return 0; 187 } 188 189 static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device *dsi) 190 { 191 struct tdo_tl070wsh30_panel *tdo_tl070wsh30; 192 int err; 193 194 dsi->lanes = 4; 195 dsi->format = MIPI_DSI_FMT_RGB888; 196 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM; 197 198 tdo_tl070wsh30 = devm_kzalloc(&dsi->dev, sizeof(*tdo_tl070wsh30), 199 GFP_KERNEL); 200 if (!tdo_tl070wsh30) 201 return -ENOMEM; 202 203 mipi_dsi_set_drvdata(dsi, tdo_tl070wsh30); 204 tdo_tl070wsh30->link = dsi; 205 206 err = tdo_tl070wsh30_panel_add(tdo_tl070wsh30); 207 if (err < 0) 208 return err; 209 210 return mipi_dsi_attach(dsi); 211 } 212 213 static int tdo_tl070wsh30_panel_remove(struct mipi_dsi_device *dsi) 214 { 215 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi); 216 int err; 217 218 err = mipi_dsi_detach(dsi); 219 if (err < 0) 220 dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); 221 222 drm_panel_remove(&tdo_tl070wsh30->base); 223 drm_panel_disable(&tdo_tl070wsh30->base); 224 drm_panel_unprepare(&tdo_tl070wsh30->base); 225 226 return 0; 227 } 228 229 static void tdo_tl070wsh30_panel_shutdown(struct mipi_dsi_device *dsi) 230 { 231 struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi); 232 233 drm_panel_disable(&tdo_tl070wsh30->base); 234 drm_panel_unprepare(&tdo_tl070wsh30->base); 235 } 236 237 static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver = { 238 .driver = { 239 .name = "panel-tdo-tl070wsh30", 240 .of_match_table = tdo_tl070wsh30_of_match, 241 }, 242 .probe = tdo_tl070wsh30_panel_probe, 243 .remove = tdo_tl070wsh30_panel_remove, 244 .shutdown = tdo_tl070wsh30_panel_shutdown, 245 }; 246 module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver); 247 248 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 249 MODULE_DESCRIPTION("TDO TL070WSH30 panel driver"); 250 MODULE_LICENSE("GPL v2"); 251