1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Driver for the Samsung S6E3FA7 panel. 4 * 5 * Copyright (c) 2022-2024, The Linux Foundation. All rights reserved. 6 * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: 7 * Copyright (c) 2013, The Linux Foundation. All rights reserved. 8 */ 9 10 #include <linux/backlight.h> 11 #include <linux/delay.h> 12 #include <linux/gpio/consumer.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 16 #include <video/mipi_display.h> 17 18 #include <drm/drm_mipi_dsi.h> 19 #include <drm/drm_modes.h> 20 #include <drm/drm_panel.h> 21 22 struct s6e3fa7_panel { 23 struct drm_panel panel; 24 struct mipi_dsi_device *dsi; 25 struct gpio_desc *reset_gpio; 26 }; 27 28 static inline struct s6e3fa7_panel *to_s6e3fa7_panel(struct drm_panel *panel) 29 { 30 return container_of(panel, struct s6e3fa7_panel, panel); 31 } 32 33 static void s6e3fa7_panel_reset(struct s6e3fa7_panel *ctx) 34 { 35 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 36 usleep_range(1000, 2000); 37 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 38 usleep_range(10000, 11000); 39 } 40 41 static int s6e3fa7_panel_on(struct s6e3fa7_panel *ctx) 42 { 43 struct mipi_dsi_device *dsi = ctx->dsi; 44 struct device *dev = &dsi->dev; 45 int ret; 46 47 ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 48 if (ret < 0) { 49 dev_err(dev, "Failed to exit sleep mode: %d\n", ret); 50 return ret; 51 } 52 msleep(120); 53 54 ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 55 if (ret < 0) { 56 dev_err(dev, "Failed to set tear on: %d\n", ret); 57 return ret; 58 } 59 60 mipi_dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a); 61 mipi_dsi_dcs_write_seq(dsi, 0xf4, 62 0xbb, 0x23, 0x19, 0x3a, 0x9f, 0x0f, 0x09, 0xc0, 63 0x00, 0xb4, 0x37, 0x70, 0x79, 0x69); 64 mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5); 65 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); 66 67 ret = mipi_dsi_dcs_set_display_on(dsi); 68 if (ret < 0) { 69 dev_err(dev, "Failed to set display on: %d\n", ret); 70 return ret; 71 } 72 73 return 0; 74 } 75 76 static int s6e3fa7_panel_prepare(struct drm_panel *panel) 77 { 78 struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel); 79 struct device *dev = &ctx->dsi->dev; 80 int ret; 81 82 s6e3fa7_panel_reset(ctx); 83 84 ret = s6e3fa7_panel_on(ctx); 85 if (ret < 0) { 86 dev_err(dev, "Failed to initialize panel: %d\n", ret); 87 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 88 return ret; 89 } 90 91 return 0; 92 } 93 94 static int s6e3fa7_panel_unprepare(struct drm_panel *panel) 95 { 96 struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel); 97 98 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 99 100 return 0; 101 } 102 103 static int s6e3fa7_panel_disable(struct drm_panel *panel) 104 { 105 struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel); 106 struct mipi_dsi_device *dsi = ctx->dsi; 107 struct device *dev = &dsi->dev; 108 int ret; 109 110 ret = mipi_dsi_dcs_set_display_off(dsi); 111 if (ret < 0) { 112 dev_err(dev, "Failed to set display off: %d\n", ret); 113 return ret; 114 } 115 116 ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 117 if (ret < 0) { 118 dev_err(dev, "Failed to enter sleep mode: %d\n", ret); 119 return ret; 120 } 121 msleep(120); 122 123 return 0; 124 } 125 126 static const struct drm_display_mode s6e3fa7_panel_mode = { 127 .clock = (1080 + 32 + 32 + 78) * (2220 + 32 + 4 + 78) * 60 / 1000, 128 .hdisplay = 1080, 129 .hsync_start = 1080 + 32, 130 .hsync_end = 1080 + 32 + 32, 131 .htotal = 1080 + 32 + 32 + 78, 132 .vdisplay = 2220, 133 .vsync_start = 2220 + 32, 134 .vsync_end = 2220 + 32 + 4, 135 .vtotal = 2220 + 32 + 4 + 78, 136 .width_mm = 62, 137 .height_mm = 127, 138 }; 139 140 static int s6e3fa7_panel_get_modes(struct drm_panel *panel, 141 struct drm_connector *connector) 142 { 143 struct drm_display_mode *mode; 144 145 mode = drm_mode_duplicate(connector->dev, &s6e3fa7_panel_mode); 146 if (!mode) 147 return -ENOMEM; 148 149 drm_mode_set_name(mode); 150 151 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 152 connector->display_info.width_mm = mode->width_mm; 153 connector->display_info.height_mm = mode->height_mm; 154 drm_mode_probed_add(connector, mode); 155 156 return 1; 157 } 158 159 static const struct drm_panel_funcs s6e3fa7_panel_funcs = { 160 .prepare = s6e3fa7_panel_prepare, 161 .unprepare = s6e3fa7_panel_unprepare, 162 .disable = s6e3fa7_panel_disable, 163 .get_modes = s6e3fa7_panel_get_modes, 164 }; 165 166 static int s6e3fa7_panel_bl_update_status(struct backlight_device *bl) 167 { 168 struct mipi_dsi_device *dsi = bl_get_data(bl); 169 u16 brightness = backlight_get_brightness(bl); 170 int ret; 171 172 ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); 173 if (ret < 0) 174 return ret; 175 176 return 0; 177 } 178 179 static int s6e3fa7_panel_bl_get_brightness(struct backlight_device *bl) 180 { 181 struct mipi_dsi_device *dsi = bl_get_data(bl); 182 u16 brightness; 183 int ret; 184 185 ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); 186 if (ret < 0) 187 return ret; 188 189 return brightness; 190 } 191 192 static const struct backlight_ops s6e3fa7_panel_bl_ops = { 193 .update_status = s6e3fa7_panel_bl_update_status, 194 .get_brightness = s6e3fa7_panel_bl_get_brightness, 195 }; 196 197 static struct backlight_device * 198 s6e3fa7_panel_create_backlight(struct mipi_dsi_device *dsi) 199 { 200 struct device *dev = &dsi->dev; 201 const struct backlight_properties props = { 202 .type = BACKLIGHT_RAW, 203 .brightness = 1023, 204 .max_brightness = 1023, 205 }; 206 207 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 208 &s6e3fa7_panel_bl_ops, &props); 209 } 210 211 static int s6e3fa7_panel_probe(struct mipi_dsi_device *dsi) 212 { 213 struct device *dev = &dsi->dev; 214 struct s6e3fa7_panel *ctx; 215 int ret; 216 217 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 218 if (!ctx) 219 return -ENOMEM; 220 221 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 222 if (IS_ERR(ctx->reset_gpio)) 223 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 224 "Failed to get reset-gpios\n"); 225 226 ctx->dsi = dsi; 227 mipi_dsi_set_drvdata(dsi, ctx); 228 229 dsi->lanes = 4; 230 dsi->format = MIPI_DSI_FMT_RGB888; 231 dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | 232 MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; 233 234 drm_panel_init(&ctx->panel, dev, &s6e3fa7_panel_funcs, 235 DRM_MODE_CONNECTOR_DSI); 236 ctx->panel.prepare_prev_first = true; 237 238 ctx->panel.backlight = s6e3fa7_panel_create_backlight(dsi); 239 if (IS_ERR(ctx->panel.backlight)) 240 return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 241 "Failed to create backlight\n"); 242 243 drm_panel_add(&ctx->panel); 244 245 ret = mipi_dsi_attach(dsi); 246 if (ret < 0) { 247 dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 248 drm_panel_remove(&ctx->panel); 249 return ret; 250 } 251 252 return 0; 253 } 254 255 static void s6e3fa7_panel_remove(struct mipi_dsi_device *dsi) 256 { 257 struct s6e3fa7_panel *ctx = mipi_dsi_get_drvdata(dsi); 258 int ret; 259 260 ret = mipi_dsi_detach(dsi); 261 if (ret < 0) 262 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 263 264 drm_panel_remove(&ctx->panel); 265 } 266 267 static const struct of_device_id s6e3fa7_panel_of_match[] = { 268 { .compatible = "samsung,s6e3fa7-ams559nk06" }, 269 { /* sentinel */ } 270 }; 271 MODULE_DEVICE_TABLE(of, s6e3fa7_panel_of_match); 272 273 static struct mipi_dsi_driver s6e3fa7_panel_driver = { 274 .probe = s6e3fa7_panel_probe, 275 .remove = s6e3fa7_panel_remove, 276 .driver = { 277 .name = "panel-samsung-s6e3fa7", 278 .of_match_table = s6e3fa7_panel_of_match, 279 }, 280 }; 281 module_mipi_dsi_driver(s6e3fa7_panel_driver); 282 283 MODULE_AUTHOR("Richard Acayan <mailingradian@gmail.com>"); 284 MODULE_DESCRIPTION("DRM driver for Samsung S6E3FA7 command mode DSI panel"); 285 MODULE_LICENSE("GPL"); 286