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