1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2024, Danila Tikhonov <danila@jiaxyga.com> 4 */ 5 6 #include <linux/backlight.h> 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 <video/mipi_display.h> 14 15 #include <drm/drm_mipi_dsi.h> 16 #include <drm/drm_modes.h> 17 #include <drm/drm_panel.h> 18 #include <drm/drm_probe_helper.h> 19 20 /* Manufacturer Command Set */ 21 #define MCS_ACCESS_PROT_OFF 0xb0 22 #define MCS_PASSWD 0xf0 23 24 struct ams581vf01 { 25 struct drm_panel panel; 26 struct mipi_dsi_device *dsi; 27 struct gpio_desc *reset_gpio; 28 struct regulator_bulk_data *supplies; 29 }; 30 31 static const struct regulator_bulk_data ams581vf01_supplies[] = { 32 { .supply = "vdd3p3" }, 33 { .supply = "vddio" }, 34 { .supply = "vsn" }, 35 { .supply = "vsp" }, 36 }; 37 38 static inline struct ams581vf01 *to_ams581vf01(struct drm_panel *panel) 39 { 40 return container_of(panel, struct ams581vf01, panel); 41 } 42 43 static void ams581vf01_reset(struct ams581vf01 *ctx) 44 { 45 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 46 usleep_range(10000, 11000); 47 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 48 usleep_range(10000, 11000); 49 } 50 51 static int ams581vf01_on(struct ams581vf01 *ctx) 52 { 53 struct mipi_dsi_device *dsi = ctx->dsi; 54 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 55 56 /* Sleep Out, Wait 10ms */ 57 mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 58 usleep_range(10000, 11000); 59 60 /* TE On */ 61 mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 62 63 /* MIC Setting */ 64 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */ 65 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xeb, 0x17, 66 0x41, 0x92, 67 0x0e, 0x10, 68 0x82, 0x5a); 69 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */ 70 71 /* Column & Page Address Setting */ 72 mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437); 73 mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923); 74 75 /* Brightness Setting */ 76 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); 77 78 /* Horizontal & Vertical sync Setting */ 79 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */ 80 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x09); 81 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x11, 0x30); 82 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */ 83 mipi_dsi_msleep(&dsi_ctx, 110); 84 85 /* Display On */ 86 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 87 88 return dsi_ctx.accum_err; 89 } 90 91 static void ams581vf01_off(struct ams581vf01 *ctx) 92 { 93 struct mipi_dsi_device *dsi = ctx->dsi; 94 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 95 96 /* Display Off & Sleep In */ 97 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 98 mipi_dsi_msleep(&dsi_ctx, 20); 99 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 100 101 /* VCI operating mode change */ 102 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */ 103 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x05); 104 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x01); 105 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */ 106 107 mipi_dsi_msleep(&dsi_ctx, 120); 108 } 109 110 static int ams581vf01_prepare(struct drm_panel *panel) 111 { 112 struct ams581vf01 *ctx = to_ams581vf01(panel); 113 int ret; 114 115 ret = regulator_bulk_enable(ARRAY_SIZE(ams581vf01_supplies), 116 ctx->supplies); 117 if (ret < 0) 118 return ret; 119 120 ams581vf01_reset(ctx); 121 122 ret = ams581vf01_on(ctx); 123 if (ret < 0) { 124 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 125 regulator_bulk_disable(ARRAY_SIZE(ams581vf01_supplies), 126 ctx->supplies); 127 return ret; 128 } 129 130 return 0; 131 } 132 133 static int ams581vf01_unprepare(struct drm_panel *panel) 134 { 135 struct ams581vf01 *ctx = to_ams581vf01(panel); 136 137 ams581vf01_off(ctx); 138 139 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 140 regulator_bulk_disable(ARRAY_SIZE(ams581vf01_supplies), 141 ctx->supplies); 142 143 return 0; 144 } 145 146 static const struct drm_display_mode ams581vf01_mode = { 147 .clock = (1080 + 32 + 73 + 98) * (2340 + 8 + 1 + 8) * 60 / 1000, 148 .hdisplay = 1080, 149 .hsync_start = 1080 + 32, 150 .hsync_end = 1080 + 32 + 73, 151 .htotal = 1080 + 32 + 73 + 98, 152 .vdisplay = 2340, 153 .vsync_start = 2340 + 8, 154 .vsync_end = 2340 + 8 + 1, 155 .vtotal = 2340 + 8 + 1 + 8, 156 .width_mm = 62, 157 .height_mm = 134, 158 .type = DRM_MODE_TYPE_DRIVER, 159 }; 160 161 static int ams581vf01_get_modes(struct drm_panel *panel, 162 struct drm_connector *connector) 163 { 164 return drm_connector_helper_get_modes_fixed(connector, &ams581vf01_mode); 165 } 166 167 static const struct drm_panel_funcs ams581vf01_panel_funcs = { 168 .prepare = ams581vf01_prepare, 169 .unprepare = ams581vf01_unprepare, 170 .get_modes = ams581vf01_get_modes, 171 }; 172 173 static int ams581vf01_bl_update_status(struct backlight_device *bl) 174 { 175 struct mipi_dsi_device *dsi = bl_get_data(bl); 176 u16 brightness = backlight_get_brightness(bl); 177 int ret; 178 179 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 180 181 ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); 182 if (ret < 0) 183 return ret; 184 185 dsi->mode_flags |= MIPI_DSI_MODE_LPM; 186 187 return 0; 188 } 189 190 static const struct backlight_ops ams581vf01_bl_ops = { 191 .update_status = ams581vf01_bl_update_status, 192 }; 193 194 static struct backlight_device * 195 ams581vf01_create_backlight(struct mipi_dsi_device *dsi) 196 { 197 struct device *dev = &dsi->dev; 198 const struct backlight_properties props = { 199 .type = BACKLIGHT_RAW, 200 .brightness = 511, 201 .max_brightness = 1023, 202 }; 203 204 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 205 &ams581vf01_bl_ops, &props); 206 } 207 208 static int ams581vf01_probe(struct mipi_dsi_device *dsi) 209 { 210 struct device *dev = &dsi->dev; 211 struct ams581vf01 *ctx; 212 int ret; 213 214 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 215 if (!ctx) 216 return -ENOMEM; 217 218 ret = devm_regulator_bulk_get_const(&dsi->dev, 219 ARRAY_SIZE(ams581vf01_supplies), 220 ams581vf01_supplies, 221 &ctx->supplies); 222 if (ret < 0) 223 return dev_err_probe(dev, ret, "Failed to get regulators\n"); 224 225 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 226 if (IS_ERR(ctx->reset_gpio)) 227 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 228 "Failed to get reset-gpios\n"); 229 230 ctx->dsi = dsi; 231 mipi_dsi_set_drvdata(dsi, ctx); 232 233 dsi->lanes = 4; 234 dsi->format = MIPI_DSI_FMT_RGB888; 235 dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | 236 MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; 237 238 drm_panel_init(&ctx->panel, dev, &ams581vf01_panel_funcs, 239 DRM_MODE_CONNECTOR_DSI); 240 ctx->panel.prepare_prev_first = true; 241 242 ctx->panel.backlight = ams581vf01_create_backlight(dsi); 243 if (IS_ERR(ctx->panel.backlight)) 244 return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 245 "Failed to create backlight\n"); 246 247 drm_panel_add(&ctx->panel); 248 249 ret = devm_mipi_dsi_attach(dev, dsi); 250 if (ret < 0) { 251 drm_panel_remove(&ctx->panel); 252 return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); 253 } 254 255 return 0; 256 } 257 258 static void ams581vf01_remove(struct mipi_dsi_device *dsi) 259 { 260 struct ams581vf01 *ctx = mipi_dsi_get_drvdata(dsi); 261 262 drm_panel_remove(&ctx->panel); 263 } 264 265 static const struct of_device_id ams581vf01_of_match[] = { 266 { .compatible = "samsung,ams581vf01" }, 267 { /* sentinel */ } 268 }; 269 MODULE_DEVICE_TABLE(of, ams581vf01_of_match); 270 271 static struct mipi_dsi_driver ams581vf01_driver = { 272 .probe = ams581vf01_probe, 273 .remove = ams581vf01_remove, 274 .driver = { 275 .name = "panel-samsung-ams581vf01", 276 .of_match_table = ams581vf01_of_match, 277 }, 278 }; 279 module_mipi_dsi_driver(ams581vf01_driver); 280 281 MODULE_AUTHOR("Danila Tikhonov <danila@jiaxyga.com>"); 282 MODULE_DESCRIPTION("DRM driver for SAMSUNG AMS581VF01 cmd mode dsi panel"); 283 MODULE_LICENSE("GPL"); 284