1 // SPDX-License-Identifier: GPL-2.0-only 2 // Copyright (c) 2024 Linaro Limited 3 4 #include <linux/backlight.h> 5 #include <linux/delay.h> 6 #include <linux/gpio/consumer.h> 7 #include <linux/regulator/consumer.h> 8 #include <linux/mod_devicetable.h> 9 #include <linux/module.h> 10 11 #include <drm/display/drm_dsc.h> 12 #include <drm/display/drm_dsc_helper.h> 13 #include <drm/drm_mipi_dsi.h> 14 #include <drm/drm_modes.h> 15 #include <drm/drm_panel.h> 16 #include <drm/drm_probe_helper.h> 17 18 #include <video/mipi_display.h> 19 20 struct novatek_nt37801 { 21 struct drm_panel panel; 22 struct mipi_dsi_device *dsi; 23 struct drm_dsc_config dsc; 24 struct gpio_desc *reset_gpio; 25 struct regulator_bulk_data *supplies; 26 }; 27 28 static const struct regulator_bulk_data novatek_nt37801_supplies[] = { 29 { .supply = "vddio" }, 30 { .supply = "vci" }, 31 { .supply = "vdd" }, 32 }; 33 34 static inline struct novatek_nt37801 *to_novatek_nt37801(struct drm_panel *panel) 35 { 36 return container_of(panel, struct novatek_nt37801, panel); 37 } 38 39 static void novatek_nt37801_reset(struct novatek_nt37801 *ctx) 40 { 41 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 42 usleep_range(10000, 21000); 43 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 44 usleep_range(10000, 21000); 45 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 46 usleep_range(10000, 21000); 47 } 48 49 #define NT37801_DCS_SWITCH_PAGE 0xf0 50 51 #define novatek_nt37801_switch_page(dsi_ctx, page) \ 52 mipi_dsi_dcs_write_seq_multi((dsi_ctx), NT37801_DCS_SWITCH_PAGE, \ 53 0x55, 0xaa, 0x52, 0x08, (page)) 54 55 static int novatek_nt37801_on(struct novatek_nt37801 *ctx) 56 { 57 struct mipi_dsi_device *dsi = ctx->dsi; 58 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 59 60 dsi->mode_flags |= MIPI_DSI_MODE_LPM; 61 62 novatek_nt37801_switch_page(&dsi_ctx, 0x01); 63 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01); 64 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc5, 0x0b, 0x0b, 0x0b); 65 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80); 66 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02); 67 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf5, 0x10); 68 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1b); 69 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x55); 70 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x18); 71 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x19); 72 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0f); 73 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x00); 74 mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x059f); 75 mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0c7f); 76 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03, 0x03); 77 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, 78 0x89, 0x28, 0x00, 0x28, 0xc2, 0x00, 0x02, 79 0x68, 0x04, 0x6c, 0x00, 0x0a, 0x02, 0x77, 80 0x01, 0xe9, 0x10, 0xf0); 81 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); 82 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x23); 83 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 84 0x00, 0x01, 0x00, 0x11, 0x33, 0x33, 0x33, 85 0x55, 0x57, 0xd0, 0x00, 0x00, 0x44, 0x56, 86 0x77, 0x78, 0x9a, 0xbc, 0xdd, 0xf0); 87 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x06); 88 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0xdc); 89 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x00); 90 mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 91 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x00, 0x18, 0x00, 0x10); 92 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 93 0x20); 94 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 95 0x07, 0xff, 0x07, 0xff, 0x0f, 0xff); 96 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x01); 97 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); 98 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9c, 0x01); 99 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); 100 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x00); 101 102 novatek_nt37801_switch_page(&dsi_ctx, 0x01); 103 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x55, 0x01, 0xff, 0x03); 104 mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 105 mipi_dsi_msleep(&dsi_ctx, 120); 106 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 107 mipi_dsi_msleep(&dsi_ctx, 20); 108 109 return dsi_ctx.accum_err; 110 } 111 112 static int novatek_nt37801_off(struct novatek_nt37801 *ctx) 113 { 114 struct mipi_dsi_device *dsi = ctx->dsi; 115 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 116 117 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 118 119 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 120 mipi_dsi_msleep(&dsi_ctx, 20); 121 122 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 123 mipi_dsi_msleep(&dsi_ctx, 120); 124 125 return dsi_ctx.accum_err; 126 } 127 128 static int novatek_nt37801_prepare(struct drm_panel *panel) 129 { 130 struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); 131 struct device *dev = &ctx->dsi->dev; 132 struct drm_dsc_picture_parameter_set pps; 133 int ret; 134 135 ret = regulator_bulk_enable(ARRAY_SIZE(novatek_nt37801_supplies), 136 ctx->supplies); 137 if (ret < 0) 138 return ret; 139 140 novatek_nt37801_reset(ctx); 141 142 ret = novatek_nt37801_on(ctx); 143 if (ret < 0) 144 goto err; 145 146 drm_dsc_pps_payload_pack(&pps, &ctx->dsc); 147 148 ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps); 149 if (ret < 0) { 150 dev_err(panel->dev, "failed to transmit PPS: %d\n", ret); 151 goto err; 152 } 153 154 ret = mipi_dsi_compression_mode(ctx->dsi, true); 155 if (ret < 0) { 156 dev_err(dev, "failed to enable compression mode: %d\n", ret); 157 goto err; 158 } 159 160 msleep(28); 161 162 return 0; 163 164 err: 165 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 166 regulator_bulk_disable(ARRAY_SIZE(novatek_nt37801_supplies), 167 ctx->supplies); 168 169 return ret; 170 } 171 172 static int novatek_nt37801_unprepare(struct drm_panel *panel) 173 { 174 struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); 175 struct device *dev = &ctx->dsi->dev; 176 int ret; 177 178 ret = novatek_nt37801_off(ctx); 179 if (ret < 0) 180 dev_err(dev, "Failed to un-initialize panel: %d\n", ret); 181 182 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 183 184 regulator_bulk_disable(ARRAY_SIZE(novatek_nt37801_supplies), 185 ctx->supplies); 186 187 return 0; 188 } 189 190 static const struct drm_display_mode novatek_nt37801_mode = { 191 .clock = (1440 + 20 + 4 + 20) * (3200 + 20 + 2 + 18) * 120 / 1000, 192 .hdisplay = 1440, 193 .hsync_start = 1440 + 20, 194 .hsync_end = 1440 + 20 + 4, 195 .htotal = 1440 + 20 + 4 + 20, 196 .vdisplay = 3200, 197 .vsync_start = 3200 + 20, 198 .vsync_end = 3200 + 20 + 2, 199 .vtotal = 3200 + 20 + 2 + 18, 200 .type = DRM_MODE_TYPE_DRIVER, 201 }; 202 203 static int novatek_nt37801_get_modes(struct drm_panel *panel, 204 struct drm_connector *connector) 205 { 206 return drm_connector_helper_get_modes_fixed(connector, 207 &novatek_nt37801_mode); 208 } 209 210 static const struct drm_panel_funcs novatek_nt37801_panel_funcs = { 211 .prepare = novatek_nt37801_prepare, 212 .unprepare = novatek_nt37801_unprepare, 213 .get_modes = novatek_nt37801_get_modes, 214 }; 215 216 static int novatek_nt37801_bl_update_status(struct backlight_device *bl) 217 { 218 struct mipi_dsi_device *dsi = bl_get_data(bl); 219 u16 brightness = backlight_get_brightness(bl); 220 int ret; 221 222 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 223 224 ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); 225 if (ret < 0) 226 return ret; 227 228 dsi->mode_flags |= MIPI_DSI_MODE_LPM; 229 230 return 0; 231 } 232 233 static const struct backlight_ops novatek_nt37801_bl_ops = { 234 .update_status = novatek_nt37801_bl_update_status, 235 }; 236 237 static struct backlight_device * 238 novatek_nt37801_create_backlight(struct mipi_dsi_device *dsi) 239 { 240 struct device *dev = &dsi->dev; 241 const struct backlight_properties props = { 242 .type = BACKLIGHT_RAW, 243 .brightness = 4095, 244 .max_brightness = 4095, 245 }; 246 247 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 248 &novatek_nt37801_bl_ops, &props); 249 } 250 251 static int novatek_nt37801_probe(struct mipi_dsi_device *dsi) 252 { 253 struct device *dev = &dsi->dev; 254 struct novatek_nt37801 *ctx; 255 int ret; 256 257 ctx = devm_drm_panel_alloc(dev, struct novatek_nt37801, panel, 258 &novatek_nt37801_panel_funcs, 259 DRM_MODE_CONNECTOR_DSI); 260 if (IS_ERR(ctx)) 261 return PTR_ERR(ctx); 262 263 ret = devm_regulator_bulk_get_const(dev, 264 ARRAY_SIZE(novatek_nt37801_supplies), 265 novatek_nt37801_supplies, 266 &ctx->supplies); 267 if (ret < 0) 268 return ret; 269 270 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 271 if (IS_ERR(ctx->reset_gpio)) 272 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 273 "Failed to get reset-gpios\n"); 274 275 ctx->dsi = dsi; 276 mipi_dsi_set_drvdata(dsi, ctx); 277 278 dsi->lanes = 4; 279 dsi->format = MIPI_DSI_FMT_RGB888; 280 dsi->mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS; 281 282 ctx->panel.prepare_prev_first = true; 283 ctx->panel.backlight = novatek_nt37801_create_backlight(dsi); 284 if (IS_ERR(ctx->panel.backlight)) 285 return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 286 "Failed to create backlight\n"); 287 288 drm_panel_add(&ctx->panel); 289 290 /* This panel only supports DSC; unconditionally enable it */ 291 dsi->dsc = &ctx->dsc; 292 ctx->dsc.dsc_version_major = 1; 293 ctx->dsc.dsc_version_minor = 1; 294 ctx->dsc.slice_height = 40; 295 ctx->dsc.slice_width = 720; 296 ctx->dsc.slice_count = 1440 / ctx->dsc.slice_width; 297 ctx->dsc.bits_per_component = 8; 298 ctx->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ 299 ctx->dsc.block_pred_enable = true; 300 301 ret = mipi_dsi_attach(dsi); 302 if (ret < 0) { 303 drm_panel_remove(&ctx->panel); 304 return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); 305 } 306 307 return 0; 308 } 309 310 static void novatek_nt37801_remove(struct mipi_dsi_device *dsi) 311 { 312 struct novatek_nt37801 *ctx = mipi_dsi_get_drvdata(dsi); 313 int ret; 314 315 ret = mipi_dsi_detach(dsi); 316 if (ret < 0) 317 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 318 319 drm_panel_remove(&ctx->panel); 320 } 321 322 static const struct of_device_id novatek_nt37801_of_match[] = { 323 { .compatible = "novatek,nt37801" }, 324 {} 325 }; 326 MODULE_DEVICE_TABLE(of, novatek_nt37801_of_match); 327 328 static struct mipi_dsi_driver novatek_nt37801_driver = { 329 .probe = novatek_nt37801_probe, 330 .remove = novatek_nt37801_remove, 331 .driver = { 332 .name = "panel-novatek-nt37801", 333 .of_match_table = novatek_nt37801_of_match, 334 }, 335 }; 336 module_mipi_dsi_driver(novatek_nt37801_driver); 337 338 MODULE_AUTHOR("Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>"); 339 MODULE_DESCRIPTION("Panel driver for the Novatek NT37801/NT37810 AMOLED DSI panel"); 340 MODULE_LICENSE("GPL"); 341