1 // SPDX-License-Identifier: GPL-2.0-only 2 // 3 // Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: 4 // Copyright (c) 2013, The Linux Foundation. All rights reserved. 5 // Copyright (c) 2024 Dzmitry Sankouski <dsankouski@gmail.com> 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 <drm/display/drm_dsc.h> 14 #include <drm/display/drm_dsc_helper.h> 15 #include <drm/drm_mipi_dsi.h> 16 #include <drm/drm_probe_helper.h> 17 #include <drm/drm_panel.h> 18 19 struct s6e3ha8 { 20 struct drm_panel panel; 21 struct mipi_dsi_device *dsi; 22 struct drm_dsc_config dsc; 23 struct gpio_desc *reset_gpio; 24 struct regulator_bulk_data *supplies; 25 }; 26 27 const struct regulator_bulk_data s6e3ha8_supplies[] = { 28 { .supply = "vdd3" }, 29 { .supply = "vci" }, 30 { .supply = "vddr" }, 31 }; 32 33 static inline 34 struct s6e3ha8 *to_s6e3ha8_amb577px01_wqhd(struct drm_panel *panel) 35 { 36 return container_of(panel, struct s6e3ha8, panel); 37 } 38 39 #define s6e3ha8_test_key_on_lvl2(ctx) \ 40 mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) 41 #define s6e3ha8_test_key_off_lvl2(ctx) \ 42 mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) 43 #define s6e3ha8_test_key_on_lvl3(ctx) \ 44 mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) 45 #define s6e3ha8_test_key_off_lvl3(ctx) \ 46 mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) 47 #define s6e3ha8_test_key_on_lvl1(ctx) \ 48 mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5) 49 #define s6e3ha8_test_key_off_lvl1(ctx) \ 50 mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a) 51 #define s6e3ha8_afc_off(ctx) \ 52 mipi_dsi_dcs_write_seq_multi(ctx, 0xe2, 0x00, 0x00) 53 54 static void s6e3ha8_amb577px01_wqhd_reset(struct s6e3ha8 *priv) 55 { 56 gpiod_set_value_cansleep(priv->reset_gpio, 1); 57 usleep_range(5000, 6000); 58 gpiod_set_value_cansleep(priv->reset_gpio, 0); 59 usleep_range(5000, 6000); 60 gpiod_set_value_cansleep(priv->reset_gpio, 1); 61 usleep_range(5000, 6000); 62 } 63 64 static int s6e3ha8_amb577px01_wqhd_on(struct s6e3ha8 *priv) 65 { 66 struct mipi_dsi_device *dsi = priv->dsi; 67 struct mipi_dsi_multi_context ctx = { .dsi = dsi }; 68 69 dsi->mode_flags |= MIPI_DSI_MODE_LPM; 70 71 s6e3ha8_test_key_on_lvl1(&ctx); 72 73 s6e3ha8_test_key_on_lvl2(&ctx); 74 mipi_dsi_compression_mode_multi(&ctx, true); 75 s6e3ha8_test_key_off_lvl2(&ctx); 76 77 mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); 78 usleep_range(5000, 6000); 79 80 s6e3ha8_test_key_on_lvl2(&ctx); 81 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13); 82 s6e3ha8_test_key_off_lvl2(&ctx); 83 usleep_range(10000, 11000); 84 85 s6e3ha8_test_key_on_lvl2(&ctx); 86 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13); 87 s6e3ha8_test_key_off_lvl2(&ctx); 88 89 /* OMOK setting 1 (Initial setting) - Scaler Latch Setting Guide */ 90 s6e3ha8_test_key_on_lvl2(&ctx); 91 mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x07); 92 /* latch setting 1 : Scaler on/off & address setting & PPS setting -> Image update latch */ 93 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x3c, 0x10); 94 mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x0b); 95 /* latch setting 2 : Ratio change mode -> Image update latch */ 96 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x30); 97 /* OMOK setting 2 - Seamless setting guide : WQHD */ 98 mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x00, 0x00, 0x05, 0x9f); /* CASET */ 99 mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00, 0x00, 0x0b, 0x8f); /* PASET */ 100 mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x01); /* scaler setup : scaler off */ 101 s6e3ha8_test_key_off_lvl2(&ctx); 102 103 mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x00); /* TE Vsync ON */ 104 105 s6e3ha8_test_key_on_lvl2(&ctx); 106 mipi_dsi_dcs_write_seq_multi(&ctx, 0xed, 0x4c); /* ERR_FG */ 107 s6e3ha8_test_key_off_lvl2(&ctx); 108 109 s6e3ha8_test_key_on_lvl3(&ctx); 110 /* FFC Setting 897.6Mbps */ 111 mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x0d, 0x10, 0xb4, 0x3e, 0x01); 112 s6e3ha8_test_key_off_lvl3(&ctx); 113 114 s6e3ha8_test_key_on_lvl2(&ctx); 115 mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 116 0x00, 0xb0, 0x81, 0x09, 0x00, 0x00, 0x00, 117 0x11, 0x03); /* TSP HSYNC Setting */ 118 s6e3ha8_test_key_off_lvl2(&ctx); 119 120 s6e3ha8_test_key_on_lvl2(&ctx); 121 mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x03); 122 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf6, 0x43); 123 s6e3ha8_test_key_off_lvl2(&ctx); 124 125 s6e3ha8_test_key_on_lvl2(&ctx); 126 /* Brightness condition set */ 127 mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 128 0x07, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 129 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 130 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 131 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 132 0x80, 0x80, 0x80, 0x00, 0x00, 0x00); 133 mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0x0c); /* AID Set : 0% */ 134 mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 135 0x19, 0xdc, 0x16, 0x01, 0x34, 0x67, 0x9a, 136 0xcd, 0x01, 0x22, 0x33, 0x44, 0x00, 0x00, 137 0x05, 0x55, 0xcc, 0x0c, 0x01, 0x11, 0x11, 138 0x10); /* MPS/ELVSS Setting */ 139 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf4, 0xeb, 0x28); /* VINT */ 140 mipi_dsi_dcs_write_seq_multi(&ctx, 0xf7, 0x03); /* Gamma, LTPS(AID) update */ 141 s6e3ha8_test_key_off_lvl2(&ctx); 142 143 s6e3ha8_test_key_off_lvl1(&ctx); 144 145 return ctx.accum_err; 146 } 147 148 static int s6e3ha8_enable(struct drm_panel *panel) 149 { 150 struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); 151 struct mipi_dsi_device *dsi = priv->dsi; 152 struct mipi_dsi_multi_context ctx = { .dsi = dsi }; 153 154 s6e3ha8_test_key_on_lvl1(&ctx); 155 mipi_dsi_dcs_set_display_on_multi(&ctx); 156 s6e3ha8_test_key_off_lvl1(&ctx); 157 158 return ctx.accum_err; 159 } 160 161 static int s6e3ha8_disable(struct drm_panel *panel) 162 { 163 struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); 164 struct mipi_dsi_device *dsi = priv->dsi; 165 struct mipi_dsi_multi_context ctx = { .dsi = dsi }; 166 167 s6e3ha8_test_key_on_lvl1(&ctx); 168 mipi_dsi_dcs_set_display_off_multi(&ctx); 169 s6e3ha8_test_key_off_lvl1(&ctx); 170 mipi_dsi_msleep(&ctx, 20); 171 172 s6e3ha8_test_key_on_lvl2(&ctx); 173 s6e3ha8_afc_off(&ctx); 174 s6e3ha8_test_key_off_lvl2(&ctx); 175 176 mipi_dsi_msleep(&ctx, 160); 177 178 return ctx.accum_err; 179 } 180 181 static int s6e3ha8_amb577px01_wqhd_prepare(struct drm_panel *panel) 182 { 183 struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); 184 struct mipi_dsi_device *dsi = priv->dsi; 185 struct mipi_dsi_multi_context ctx = { .dsi = dsi }; 186 struct drm_dsc_picture_parameter_set pps; 187 int ret; 188 189 ret = regulator_bulk_enable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies); 190 if (ret < 0) 191 return ret; 192 mipi_dsi_msleep(&ctx, 120); 193 s6e3ha8_amb577px01_wqhd_reset(priv); 194 195 ret = s6e3ha8_amb577px01_wqhd_on(priv); 196 if (ret < 0) { 197 gpiod_set_value_cansleep(priv->reset_gpio, 1); 198 goto err; 199 } 200 201 drm_dsc_pps_payload_pack(&pps, &priv->dsc); 202 203 s6e3ha8_test_key_on_lvl1(&ctx); 204 mipi_dsi_picture_parameter_set_multi(&ctx, &pps); 205 s6e3ha8_test_key_off_lvl1(&ctx); 206 207 mipi_dsi_msleep(&ctx, 28); 208 209 return ctx.accum_err; 210 err: 211 regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies); 212 return ret; 213 } 214 215 static int s6e3ha8_amb577px01_wqhd_unprepare(struct drm_panel *panel) 216 { 217 struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); 218 219 return regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies); 220 } 221 222 static const struct drm_display_mode s6e3ha8_amb577px01_wqhd_mode = { 223 .clock = (1440 + 116 + 44 + 120) * (2960 + 120 + 80 + 124) * 60 / 1000, 224 .hdisplay = 1440, 225 .hsync_start = 1440 + 116, 226 .hsync_end = 1440 + 116 + 44, 227 .htotal = 1440 + 116 + 44 + 120, 228 .vdisplay = 2960, 229 .vsync_start = 2960 + 120, 230 .vsync_end = 2960 + 120 + 80, 231 .vtotal = 2960 + 120 + 80 + 124, 232 .width_mm = 64, 233 .height_mm = 132, 234 }; 235 236 static int s6e3ha8_amb577px01_wqhd_get_modes(struct drm_panel *panel, 237 struct drm_connector *connector) 238 { 239 return drm_connector_helper_get_modes_fixed(connector, &s6e3ha8_amb577px01_wqhd_mode); 240 } 241 242 static const struct drm_panel_funcs s6e3ha8_amb577px01_wqhd_panel_funcs = { 243 .prepare = s6e3ha8_amb577px01_wqhd_prepare, 244 .unprepare = s6e3ha8_amb577px01_wqhd_unprepare, 245 .get_modes = s6e3ha8_amb577px01_wqhd_get_modes, 246 .enable = s6e3ha8_enable, 247 .disable = s6e3ha8_disable, 248 }; 249 250 static int s6e3ha8_amb577px01_wqhd_probe(struct mipi_dsi_device *dsi) 251 { 252 struct device *dev = &dsi->dev; 253 struct s6e3ha8 *priv; 254 int ret; 255 256 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 257 if (!priv) 258 return -ENOMEM; 259 260 ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(s6e3ha8_supplies), 261 s6e3ha8_supplies, 262 &priv->supplies); 263 if (ret < 0) { 264 dev_err(dev, "failed to get regulators: %d\n", ret); 265 return ret; 266 } 267 268 priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 269 if (IS_ERR(priv->reset_gpio)) 270 return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), 271 "Failed to get reset-gpios\n"); 272 273 priv->dsi = dsi; 274 mipi_dsi_set_drvdata(dsi, priv); 275 276 dsi->lanes = 4; 277 dsi->format = MIPI_DSI_FMT_RGB888; 278 dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | 279 MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP | 280 MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET; 281 282 drm_panel_init(&priv->panel, dev, &s6e3ha8_amb577px01_wqhd_panel_funcs, 283 DRM_MODE_CONNECTOR_DSI); 284 priv->panel.prepare_prev_first = true; 285 286 drm_panel_add(&priv->panel); 287 288 /* This panel only supports DSC; unconditionally enable it */ 289 dsi->dsc = &priv->dsc; 290 291 priv->dsc.dsc_version_major = 1; 292 priv->dsc.dsc_version_minor = 1; 293 294 priv->dsc.slice_height = 40; 295 priv->dsc.slice_width = 720; 296 WARN_ON(1440 % priv->dsc.slice_width); 297 priv->dsc.slice_count = 1440 / priv->dsc.slice_width; 298 priv->dsc.bits_per_component = 8; 299 priv->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ 300 priv->dsc.block_pred_enable = true; 301 302 ret = mipi_dsi_attach(dsi); 303 if (ret < 0) { 304 dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 305 drm_panel_remove(&priv->panel); 306 return ret; 307 } 308 309 return 0; 310 } 311 312 static void s6e3ha8_amb577px01_wqhd_remove(struct mipi_dsi_device *dsi) 313 { 314 struct s6e3ha8 *priv = mipi_dsi_get_drvdata(dsi); 315 int ret; 316 317 ret = mipi_dsi_detach(dsi); 318 if (ret < 0) 319 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 320 321 drm_panel_remove(&priv->panel); 322 } 323 324 static const struct of_device_id s6e3ha8_amb577px01_wqhd_of_match[] = { 325 { .compatible = "samsung,s6e3ha8" }, 326 { /* sentinel */ } 327 }; 328 MODULE_DEVICE_TABLE(of, s6e3ha8_amb577px01_wqhd_of_match); 329 330 static struct mipi_dsi_driver s6e3ha8_amb577px01_wqhd_driver = { 331 .probe = s6e3ha8_amb577px01_wqhd_probe, 332 .remove = s6e3ha8_amb577px01_wqhd_remove, 333 .driver = { 334 .name = "panel-s6e3ha8", 335 .of_match_table = s6e3ha8_amb577px01_wqhd_of_match, 336 }, 337 }; 338 module_mipi_dsi_driver(s6e3ha8_amb577px01_wqhd_driver); 339 340 MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>"); 341 MODULE_DESCRIPTION("DRM driver for S6E3HA8 panel"); 342 MODULE_LICENSE("GPL"); 343