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