Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/panel: Add Samsung s6e88a0-ams452ef01 panel driver

Add driver for Samsung AMS452EF01 AMOLED panel, which makes
use of their S6E88A0 controller.

Signed-off-by: Michael Srba <Michael.Srba@seznam.cz>
Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
[fixed checkpatch warnings, added changelog]
Link: https://patchwork.freedesktop.org/patch/msgid/20200130203555.316-2-michael.srba@seznam.cz

authored by

Michael Srba and committed by
Sam Ravnborg
6d598a32 1dff44e1

+300
+6
drivers/gpu/drm/panel/Kconfig
··· 293 293 Say Y here if you want to enable support for Samsung S6E63M0 294 294 AMOLED LCD panel. 295 295 296 + config DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01 297 + tristate "Samsung AMS452EF01 panel with S6E88A0 DSI video mode controller" 298 + depends on OF 299 + select DRM_MIPI_DSI 300 + select VIDEOMODE_HELPERS 301 + 296 302 config DRM_PANEL_SAMSUNG_S6E8AA0 297 303 tristate "Samsung S6E8AA0 DSI video mode panel" 298 304 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 30 30 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o 31 31 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o 32 32 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o 33 + obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01) += panel-samsung-s6e88a0-ams452ef01.o 33 34 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o 34 35 obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o 35 36 obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
+293
drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + // Copyright (C) 2019, Michael Srba 3 + 4 + #include <linux/delay.h> 5 + #include <linux/gpio/consumer.h> 6 + #include <linux/module.h> 7 + #include <linux/of.h> 8 + #include <linux/regulator/consumer.h> 9 + 10 + #include <video/mipi_display.h> 11 + 12 + #include <drm/drm_mipi_dsi.h> 13 + #include <drm/drm_modes.h> 14 + #include <drm/drm_panel.h> 15 + 16 + struct s6e88a0_ams452ef01 { 17 + struct drm_panel panel; 18 + struct mipi_dsi_device *dsi; 19 + struct regulator_bulk_data supplies[2]; 20 + struct gpio_desc *reset_gpio; 21 + 22 + bool prepared; 23 + }; 24 + 25 + static inline struct 26 + s6e88a0_ams452ef01 *to_s6e88a0_ams452ef01(struct drm_panel *panel) 27 + { 28 + return container_of(panel, struct s6e88a0_ams452ef01, panel); 29 + } 30 + 31 + #define dsi_dcs_write_seq(dsi, seq...) do { \ 32 + static const u8 d[] = { seq }; \ 33 + int ret; \ 34 + ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ 35 + if (ret < 0) \ 36 + return ret; \ 37 + } while (0) 38 + 39 + static void s6e88a0_ams452ef01_reset(struct s6e88a0_ams452ef01 *ctx) 40 + { 41 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 42 + usleep_range(5000, 6000); 43 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 44 + usleep_range(1000, 2000); 45 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 46 + usleep_range(10000, 11000); 47 + } 48 + 49 + static int s6e88a0_ams452ef01_on(struct s6e88a0_ams452ef01 *ctx) 50 + { 51 + struct mipi_dsi_device *dsi = ctx->dsi; 52 + struct device *dev = &dsi->dev; 53 + int ret; 54 + 55 + dsi->mode_flags |= MIPI_DSI_MODE_LPM; 56 + 57 + dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a); // enable LEVEL2 commands 58 + dsi_dcs_write_seq(dsi, 0xcc, 0x4c); // set Pixel Clock Divider polarity 59 + 60 + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 61 + if (ret < 0) { 62 + dev_err(dev, "Failed to exit sleep mode: %d\n", ret); 63 + return ret; 64 + } 65 + msleep(120); 66 + 67 + // set default brightness/gama 68 + dsi_dcs_write_seq(dsi, 0xca, 69 + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, // V255 RR,GG,BB 70 + 0x80, 0x80, 0x80, // V203 R,G,B 71 + 0x80, 0x80, 0x80, // V151 R,G,B 72 + 0x80, 0x80, 0x80, // V87 R,G,B 73 + 0x80, 0x80, 0x80, // V51 R,G,B 74 + 0x80, 0x80, 0x80, // V35 R,G,B 75 + 0x80, 0x80, 0x80, // V23 R,G,B 76 + 0x80, 0x80, 0x80, // V11 R,G,B 77 + 0x6b, 0x68, 0x71, // V3 R,G,B 78 + 0x00, 0x00, 0x00); // V1 R,G,B 79 + // set default Amoled Off Ratio 80 + dsi_dcs_write_seq(dsi, 0xb2, 0x40, 0x0a, 0x17, 0x00, 0x0a); 81 + dsi_dcs_write_seq(dsi, 0xb6, 0x2c, 0x0b); // set default elvss voltage 82 + dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); 83 + dsi_dcs_write_seq(dsi, 0xf7, 0x03); // gamma/aor update 84 + dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5); // disable LEVEL2 commands 85 + 86 + ret = mipi_dsi_dcs_set_display_on(dsi); 87 + if (ret < 0) { 88 + dev_err(dev, "Failed to set display on: %d\n", ret); 89 + return ret; 90 + } 91 + 92 + return 0; 93 + } 94 + 95 + static int s6e88a0_ams452ef01_off(struct s6e88a0_ams452ef01 *ctx) 96 + { 97 + struct mipi_dsi_device *dsi = ctx->dsi; 98 + struct device *dev = &dsi->dev; 99 + int ret; 100 + 101 + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 102 + 103 + ret = mipi_dsi_dcs_set_display_off(dsi); 104 + if (ret < 0) { 105 + dev_err(dev, "Failed to set display off: %d\n", ret); 106 + return ret; 107 + } 108 + msleep(35); 109 + 110 + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 111 + if (ret < 0) { 112 + dev_err(dev, "Failed to enter sleep mode: %d\n", ret); 113 + return ret; 114 + } 115 + msleep(120); 116 + 117 + return 0; 118 + } 119 + 120 + static int s6e88a0_ams452ef01_prepare(struct drm_panel *panel) 121 + { 122 + struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel); 123 + struct device *dev = &ctx->dsi->dev; 124 + int ret; 125 + 126 + if (ctx->prepared) 127 + return 0; 128 + 129 + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 130 + if (ret < 0) { 131 + dev_err(dev, "Failed to enable regulators: %d\n", ret); 132 + return ret; 133 + } 134 + 135 + s6e88a0_ams452ef01_reset(ctx); 136 + 137 + ret = s6e88a0_ams452ef01_on(ctx); 138 + if (ret < 0) { 139 + dev_err(dev, "Failed to initialize panel: %d\n", ret); 140 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 141 + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), 142 + ctx->supplies); 143 + return ret; 144 + } 145 + 146 + ctx->prepared = true; 147 + return 0; 148 + } 149 + 150 + static int s6e88a0_ams452ef01_unprepare(struct drm_panel *panel) 151 + { 152 + struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel); 153 + struct device *dev = &ctx->dsi->dev; 154 + int ret; 155 + 156 + if (!ctx->prepared) 157 + return 0; 158 + 159 + ret = s6e88a0_ams452ef01_off(ctx); 160 + if (ret < 0) 161 + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); 162 + 163 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 164 + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 165 + 166 + ctx->prepared = false; 167 + return 0; 168 + } 169 + 170 + static const struct drm_display_mode s6e88a0_ams452ef01_mode = { 171 + .clock = (540 + 88 + 4 + 20) * (960 + 14 + 2 + 8) * 60 / 1000, 172 + .hdisplay = 540, 173 + .hsync_start = 540 + 88, 174 + .hsync_end = 540 + 88 + 4, 175 + .htotal = 540 + 88 + 4 + 20, 176 + .vdisplay = 960, 177 + .vsync_start = 960 + 14, 178 + .vsync_end = 960 + 14 + 2, 179 + .vtotal = 960 + 14 + 2 + 8, 180 + .vrefresh = 60, 181 + .width_mm = 56, 182 + .height_mm = 100, 183 + }; 184 + 185 + static int s6e88a0_ams452ef01_get_modes(struct drm_panel *panel, 186 + struct drm_connector *connector) 187 + { 188 + struct drm_display_mode *mode; 189 + 190 + mode = drm_mode_duplicate(connector->dev, &s6e88a0_ams452ef01_mode); 191 + if (!mode) 192 + return -ENOMEM; 193 + 194 + drm_mode_set_name(mode); 195 + 196 + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 197 + connector->display_info.width_mm = mode->width_mm; 198 + connector->display_info.height_mm = mode->height_mm; 199 + drm_mode_probed_add(connector, mode); 200 + 201 + return 1; 202 + } 203 + 204 + static const struct drm_panel_funcs s6e88a0_ams452ef01_panel_funcs = { 205 + .unprepare = s6e88a0_ams452ef01_unprepare, 206 + .prepare = s6e88a0_ams452ef01_prepare, 207 + .get_modes = s6e88a0_ams452ef01_get_modes, 208 + }; 209 + 210 + static int s6e88a0_ams452ef01_probe(struct mipi_dsi_device *dsi) 211 + { 212 + struct device *dev = &dsi->dev; 213 + struct s6e88a0_ams452ef01 *ctx; 214 + int ret; 215 + 216 + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 217 + if (!ctx) 218 + return -ENOMEM; 219 + 220 + ctx->supplies[0].supply = "vdd3"; 221 + ctx->supplies[1].supply = "vci"; 222 + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), 223 + ctx->supplies); 224 + if (ret < 0) { 225 + dev_err(dev, "Failed to get regulators: %d\n", ret); 226 + return ret; 227 + } 228 + 229 + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 230 + if (IS_ERR(ctx->reset_gpio)) { 231 + ret = PTR_ERR(ctx->reset_gpio); 232 + dev_err(dev, "Failed to get reset-gpios: %d\n", ret); 233 + return ret; 234 + } 235 + 236 + ctx->dsi = dsi; 237 + mipi_dsi_set_drvdata(dsi, ctx); 238 + 239 + dsi->lanes = 2; 240 + dsi->format = MIPI_DSI_FMT_RGB888; 241 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST; 242 + 243 + drm_panel_init(&ctx->panel, dev, &s6e88a0_ams452ef01_panel_funcs, 244 + DRM_MODE_CONNECTOR_DSI); 245 + 246 + ret = drm_panel_add(&ctx->panel); 247 + if (ret < 0) { 248 + dev_err(dev, "Failed to add panel: %d\n", ret); 249 + return ret; 250 + } 251 + 252 + ret = mipi_dsi_attach(dsi); 253 + if (ret < 0) { 254 + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 255 + return ret; 256 + } 257 + 258 + return 0; 259 + } 260 + 261 + static int s6e88a0_ams452ef01_remove(struct mipi_dsi_device *dsi) 262 + { 263 + struct s6e88a0_ams452ef01 *ctx = mipi_dsi_get_drvdata(dsi); 264 + int ret; 265 + 266 + ret = mipi_dsi_detach(dsi); 267 + if (ret < 0) 268 + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 269 + 270 + drm_panel_remove(&ctx->panel); 271 + 272 + return 0; 273 + } 274 + 275 + static const struct of_device_id s6e88a0_ams452ef01_of_match[] = { 276 + { .compatible = "samsung,s6e88a0-ams452ef01" }, 277 + { /* sentinel */ }, 278 + }; 279 + MODULE_DEVICE_TABLE(of, s6e88a0_ams452ef01_of_match); 280 + 281 + static struct mipi_dsi_driver s6e88a0_ams452ef01_driver = { 282 + .probe = s6e88a0_ams452ef01_probe, 283 + .remove = s6e88a0_ams452ef01_remove, 284 + .driver = { 285 + .name = "panel-s6e88a0-ams452ef01", 286 + .of_match_table = s6e88a0_ams452ef01_of_match, 287 + }, 288 + }; 289 + module_mipi_dsi_driver(s6e88a0_ams452ef01_driver); 290 + 291 + MODULE_AUTHOR("Michael Srba <Michael.Srba@seznam.cz>"); 292 + MODULE_DESCRIPTION("MIPI-DSI based Panel Driver for AMS452EF01 AMOLED LCD with a S6E88A0 controller"); 293 + MODULE_LICENSE("GPL v2");