1 // SPDX-License-Identifier: GPL-2.0-only 2 // Copyright (c) 2024 Barnabas Czeman <barnabas.czeman@mainlining.org> 3 // Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: 4 // Copyright (c) 2013, The Linux Foundation. All rights reserved. 5 6 #include <linux/delay.h> 7 #include <linux/gpio/consumer.h> 8 #include <linux/module.h> 9 #include <linux/regulator/consumer.h> 10 11 #include <video/mipi_display.h> 12 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 struct boe_td4320 { 19 struct drm_panel panel; 20 struct mipi_dsi_device *dsi; 21 struct regulator_bulk_data *supplies; 22 struct gpio_desc *reset_gpio; 23 }; 24 25 static const struct regulator_bulk_data boe_td4320_supplies[] = { 26 { .supply = "iovcc" }, 27 { .supply = "vsn" }, 28 { .supply = "vsp" }, 29 }; 30 31 static inline struct boe_td4320 *to_boe_td4320(struct drm_panel *panel) 32 { 33 return container_of(panel, struct boe_td4320, panel); 34 } 35 36 static void boe_td4320_reset(struct boe_td4320 *ctx) 37 { 38 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 39 usleep_range(1000, 2000); 40 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 41 usleep_range(5000, 6000); 42 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 43 msleep(30); 44 } 45 46 static int boe_td4320_on(struct boe_td4320 *ctx) 47 { 48 struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; 49 50 ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; 51 52 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x04); 53 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd6, 0x00); 54 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb8, 55 0x19, 0x55, 0x00, 0xbe, 0x00, 0x00, 56 0x00); 57 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb9, 58 0x4d, 0x55, 0x05, 0xe6, 0x00, 0x02, 59 0x03); 60 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, 61 0x9b, 0x5b, 0x07, 0xe6, 0x00, 0x13, 62 0x00); 63 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf9, 64 0x44, 0x3f, 0x00, 0x8d, 0xbf); 65 mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xce, 66 0x5d, 0x00, 0x0f, 0x1f, 0x2f, 0x3f, 67 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, 0x9f, 68 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, 69 0x04, 0x00, 0x02, 0x02, 0x42, 0x01, 70 0x69, 0x5a, 0x40, 0x40, 0x00, 0x00, 71 0x04, 0xfa, 0x00); 72 mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x00b8); 73 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 74 0x2c); 75 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); 76 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x03); 77 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x00); 78 mipi_dsi_msleep(&dsi_ctx, 96); 79 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x00); 80 mipi_dsi_msleep(&dsi_ctx, 20); 81 82 return dsi_ctx.accum_err; 83 } 84 85 static int boe_td4320_off(struct boe_td4320 *ctx) 86 { 87 struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; 88 89 ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 90 91 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 92 mipi_dsi_msleep(&dsi_ctx, 20); 93 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 94 mipi_dsi_msleep(&dsi_ctx, 120); 95 96 return dsi_ctx.accum_err; 97 } 98 99 static int boe_td4320_prepare(struct drm_panel *panel) 100 { 101 struct boe_td4320 *ctx = to_boe_td4320(panel); 102 struct device *dev = &ctx->dsi->dev; 103 int ret; 104 105 ret = regulator_bulk_enable(ARRAY_SIZE(boe_td4320_supplies), ctx->supplies); 106 if (ret < 0) { 107 dev_err(dev, "Failed to enable regulators: %d\n", ret); 108 return ret; 109 } 110 111 boe_td4320_reset(ctx); 112 113 ret = boe_td4320_on(ctx); 114 if (ret < 0) { 115 dev_err(dev, "Failed to initialize panel: %d\n", ret); 116 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 117 regulator_bulk_disable(ARRAY_SIZE(boe_td4320_supplies), ctx->supplies); 118 return ret; 119 } 120 121 return 0; 122 } 123 124 static int boe_td4320_unprepare(struct drm_panel *panel) 125 { 126 struct boe_td4320 *ctx = to_boe_td4320(panel); 127 struct device *dev = &ctx->dsi->dev; 128 int ret; 129 130 ret = boe_td4320_off(ctx); 131 if (ret < 0) 132 dev_err(dev, "Failed to un-initialize panel: %d\n", ret); 133 134 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 135 regulator_bulk_disable(ARRAY_SIZE(boe_td4320_supplies), ctx->supplies); 136 137 return 0; 138 } 139 140 static const struct drm_display_mode boe_td4320_mode = { 141 .clock = (1080 + 86 + 2 + 100) * (2340 + 4 + 4 + 60) * 60 / 1000, 142 .hdisplay = 1080, 143 .hsync_start = 1080 + 86, 144 .hsync_end = 1080 + 86 + 2, 145 .htotal = 1080 + 86 + 2 + 100, 146 .vdisplay = 2340, 147 .vsync_start = 2340 + 4, 148 .vsync_end = 2340 + 4 + 4, 149 .vtotal = 2340 + 4 + 4 + 60, 150 .width_mm = 67, 151 .height_mm = 145, 152 .type = DRM_MODE_TYPE_DRIVER, 153 }; 154 155 static int boe_td4320_get_modes(struct drm_panel *panel, 156 struct drm_connector *connector) 157 { 158 return drm_connector_helper_get_modes_fixed(connector, &boe_td4320_mode); 159 } 160 161 static const struct drm_panel_funcs boe_td4320_panel_funcs = { 162 .prepare = boe_td4320_prepare, 163 .unprepare = boe_td4320_unprepare, 164 .get_modes = boe_td4320_get_modes, 165 }; 166 167 static int boe_td4320_probe(struct mipi_dsi_device *dsi) 168 { 169 struct device *dev = &dsi->dev; 170 struct boe_td4320 *ctx; 171 int ret; 172 173 ctx = devm_drm_panel_alloc(dev, struct boe_td4320, panel, 174 &boe_td4320_panel_funcs, 175 DRM_MODE_CONNECTOR_DSI); 176 if (IS_ERR(ctx)) 177 return PTR_ERR(ctx); 178 179 ret = devm_regulator_bulk_get_const(dev, 180 ARRAY_SIZE(boe_td4320_supplies), 181 boe_td4320_supplies, 182 &ctx->supplies); 183 if (ret < 0) 184 return ret; 185 186 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 187 if (IS_ERR(ctx->reset_gpio)) 188 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 189 "Failed to get reset-gpios\n"); 190 191 ctx->dsi = dsi; 192 mipi_dsi_set_drvdata(dsi, ctx); 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 | 197 MIPI_DSI_CLOCK_NON_CONTINUOUS; 198 199 ctx->panel.prepare_prev_first = true; 200 201 ret = drm_panel_of_backlight(&ctx->panel); 202 if (ret) 203 return dev_err_probe(dev, ret, "Failed to get backlight\n"); 204 205 drm_panel_add(&ctx->panel); 206 207 ret = mipi_dsi_attach(dsi); 208 if (ret < 0) { 209 drm_panel_remove(&ctx->panel); 210 return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); 211 } 212 213 return 0; 214 } 215 216 static void boe_td4320_remove(struct mipi_dsi_device *dsi) 217 { 218 struct boe_td4320 *ctx = mipi_dsi_get_drvdata(dsi); 219 int ret; 220 221 ret = mipi_dsi_detach(dsi); 222 if (ret < 0) 223 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 224 225 drm_panel_remove(&ctx->panel); 226 } 227 228 static const struct of_device_id boe_td4320_of_match[] = { 229 { .compatible = "boe,td4320" }, 230 { /* sentinel */ } 231 }; 232 MODULE_DEVICE_TABLE(of, boe_td4320_of_match); 233 234 static struct mipi_dsi_driver boe_td4320_driver = { 235 .probe = boe_td4320_probe, 236 .remove = boe_td4320_remove, 237 .driver = { 238 .name = "panel-boe-td4320", 239 .of_match_table = boe_td4320_of_match, 240 }, 241 }; 242 module_mipi_dsi_driver(boe_td4320_driver); 243 244 MODULE_AUTHOR("Barnabas Czeman <barnabas.czeman@mainlining.org>"); 245 MODULE_DESCRIPTION("DRM driver for boe td4320 fhdplus video mode dsi panel"); 246 MODULE_LICENSE("GPL"); 247