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

drm/panel: samsung-s6e88a0-ams427ap24: Add initial driver

This initial part of the panel driver was mostly generated by the
"linux-mdss-dsi-panel-driver-generator" tool [1], reading downstream
Android kernel file "dsi_panel_S6E88A0_AMS427AP24_qhd_octa_video.dtsi" [2].

On top of the generic output of the tool, there were a couple of changes
applied:
- Added mipi_dsi_dcs_set_display_on() to function s6e88a0_ams427ap24_on(),
otherwise the display does not show up.
- In functions s6e88a0_ams427ap24_on() and s6e88a0_ams427ap24_off()
changed DSI commands to multi context and used "accum_err" returns.
- In functions s6e88a0_ams427ap24_on() and s6e88a0_ams427ap24_off() replaced
msleep() by mipi_dsi_msleep().
- The function s6e88a0_ams427ap24_get_modes() was changed to make use of
drm_connector_helper_get_modes_fixed(). This also required to include
drm/drm_probe_helper.h.
- In function s6e88a0_ams427ap24_probe() registring the regulators was changed
to devm_regulator_bulk_get_const(). This required to change supplies in struct
s6e88a0_ams427ap24 to a pointer.
- Removed bool "prepared" from struct s6e88a0_ams427ap24 and according parts in
functions s6e88a0_ams427ap24_prepare() and s6e88a0_ams427ap24_unprepare().
- Removed include <linux/of.h>, it's not needed.
- Added comments to the mipi_dsi_dcs_write_seq_multi() lines in function
s6e88a0_ams427ap24_on().

[1] https://github.com/msm8916-mainline/linux-mdss-dsi-panel-driver-generator
[2] https://github.com/msm8916-mainline/linux-downstream/blob/GT-I9195I/drivers/video/msm/mdss/samsung/S6E88A0_AMS427AP24/dsi_panel_S6E88A0_AMS427AP24_qhd_octa_video.dtsi

Signed-off-by: Jakob Hauser <jahau@rocketmail.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Link: https://lore.kernel.org/r/d92ef0036b66520bb6d1ec908165e776cf30c303.1730070570.git.jahau@rocketmail.com
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/d92ef0036b66520bb6d1ec908165e776cf30c303.1730070570.git.jahau@rocketmail.com

authored by

Jakob Hauser and committed by
Neil Armstrong
d5658db2 4998d53d

+261
+9
drivers/gpu/drm/panel/Kconfig
··· 632 632 Say Y or M here if you want to enable support for the 633 633 Samsung AMS639RQ08 FHD Plus (2340x1080@60Hz) CMD mode panel. 634 634 635 + config DRM_PANEL_SAMSUNG_S6E88A0_AMS427AP24 636 + tristate "Samsung AMS427AP24 panel with S6E88A0 controller" 637 + depends on GPIOLIB && OF && REGULATOR 638 + depends on DRM_MIPI_DSI 639 + help 640 + Say Y here if you want to enable support for Samsung AMS427AP24 panel 641 + with S6E88A0 controller (found in Samsung Galaxy S4 Mini Value Edition 642 + GT-I9195I). To compile this driver as a module, choose M here. 643 + 635 644 config DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01 636 645 tristate "Samsung AMS452EF01 panel with S6E88A0 DSI video mode controller" 637 646 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 77 77 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o 78 78 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0_SPI) += panel-samsung-s6e63m0-spi.o 79 79 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0_DSI) += panel-samsung-s6e63m0-dsi.o 80 + obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS427AP24) += panel-samsung-s6e88a0-ams427ap24.o 80 81 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01) += panel-samsung-s6e88a0-ams452ef01.o 81 82 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o 82 83 obj-$(CONFIG_DRM_PANEL_SAMSUNG_SOFEF00) += panel-samsung-sofef00.o
+251
drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams427ap24.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Samsung AMS427AP24 panel with S6E88A0 controller 4 + * Copyright (c) 2024 Jakob Hauser <jahau@rocketmail.com> 5 + */ 6 + 7 + #include <linux/delay.h> 8 + #include <linux/gpio/consumer.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 s6e88a0_ams427ap24 { 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 s6e88a0_ams427ap24_supplies[] = { 27 + { .supply = "vdd3" }, 28 + { .supply = "vci" }, 29 + }; 30 + 31 + static inline 32 + struct s6e88a0_ams427ap24 *to_s6e88a0_ams427ap24(struct drm_panel *panel) 33 + { 34 + return container_of(panel, struct s6e88a0_ams427ap24, panel); 35 + } 36 + 37 + static void s6e88a0_ams427ap24_reset(struct s6e88a0_ams427ap24 *ctx) 38 + { 39 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 40 + usleep_range(5000, 6000); 41 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 42 + usleep_range(1000, 2000); 43 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 44 + usleep_range(18000, 19000); 45 + } 46 + 47 + static int s6e88a0_ams427ap24_on(struct s6e88a0_ams427ap24 *ctx) 48 + { 49 + struct mipi_dsi_device *dsi = ctx->dsi; 50 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 51 + 52 + dsi->mode_flags |= MIPI_DSI_MODE_LPM; 53 + 54 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x5a, 0x5a); // level 1 key on 55 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x5a, 0x5a); // level 2 key on 56 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x11); // src latch set global 1 57 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfd, 0x11); // src latch set 1 58 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x13); // src latch set global 2 59 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfd, 0x18); // src latch set 2 60 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x02); // avdd set 1 61 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x30); // avdd set 2 62 + 63 + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 64 + mipi_dsi_msleep(&dsi_ctx, 20); 65 + 66 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf1, 0x5a, 0x5a); // level 3 key on 67 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcc, 0x4c); // pixel clock divider pol. 68 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf2, 0x03, 0x0d); // unknown 69 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf1, 0xa5, 0xa5); // level 3 key off 70 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, 71 + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x80, 72 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 73 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 74 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 75 + 0x80, 0x80, 0x00, 0x00, 0x00); // set gamma 76 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 77 + 0x40, 0x08, 0x20, 0x00, 0x08); // set aid 78 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x28, 0x0b); // set elvss 79 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf7, 0x03); // gamma update 80 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x00); // acl off 81 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xa5, 0xa5); // level 1 key off 82 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0xa5, 0xa5); // level 2 key off 83 + 84 + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 85 + 86 + return dsi_ctx.accum_err; 87 + } 88 + 89 + static int s6e88a0_ams427ap24_off(struct s6e88a0_ams427ap24 *ctx) 90 + { 91 + struct mipi_dsi_device *dsi = ctx->dsi; 92 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 93 + 94 + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 95 + 96 + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 97 + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 98 + mipi_dsi_msleep(&dsi_ctx, 120); 99 + 100 + return dsi_ctx.accum_err; 101 + } 102 + 103 + static int s6e88a0_ams427ap24_prepare(struct drm_panel *panel) 104 + { 105 + struct s6e88a0_ams427ap24 *ctx = to_s6e88a0_ams427ap24(panel); 106 + struct device *dev = &ctx->dsi->dev; 107 + int ret; 108 + 109 + ret = regulator_bulk_enable(ARRAY_SIZE(s6e88a0_ams427ap24_supplies), 110 + ctx->supplies); 111 + if (ret < 0) { 112 + dev_err(dev, "Failed to enable regulators: %d\n", ret); 113 + return ret; 114 + } 115 + 116 + s6e88a0_ams427ap24_reset(ctx); 117 + 118 + ret = s6e88a0_ams427ap24_on(ctx); 119 + if (ret < 0) { 120 + dev_err(dev, "Failed to initialize panel: %d\n", ret); 121 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 122 + regulator_bulk_disable(ARRAY_SIZE(s6e88a0_ams427ap24_supplies), 123 + ctx->supplies); 124 + return ret; 125 + } 126 + 127 + return 0; 128 + } 129 + 130 + static int s6e88a0_ams427ap24_unprepare(struct drm_panel *panel) 131 + { 132 + struct s6e88a0_ams427ap24 *ctx = to_s6e88a0_ams427ap24(panel); 133 + struct device *dev = &ctx->dsi->dev; 134 + int ret; 135 + 136 + ret = s6e88a0_ams427ap24_off(ctx); 137 + if (ret < 0) 138 + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); 139 + 140 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 141 + regulator_bulk_disable(ARRAY_SIZE(s6e88a0_ams427ap24_supplies), 142 + ctx->supplies); 143 + 144 + return 0; 145 + } 146 + 147 + static const struct drm_display_mode s6e88a0_ams427ap24_mode = { 148 + .clock = (540 + 94 + 4 + 18) * (960 + 12 + 1 + 3) * 60 / 1000, 149 + .hdisplay = 540, 150 + .hsync_start = 540 + 94, 151 + .hsync_end = 540 + 94 + 4, 152 + .htotal = 540 + 94 + 4 + 18, 153 + .vdisplay = 960, 154 + .vsync_start = 960 + 12, 155 + .vsync_end = 960 + 12 + 1, 156 + .vtotal = 960 + 12 + 1 + 3, 157 + .width_mm = 55, 158 + .height_mm = 95, 159 + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, 160 + }; 161 + 162 + static int s6e88a0_ams427ap24_get_modes(struct drm_panel *panel, 163 + struct drm_connector *connector) 164 + { 165 + return drm_connector_helper_get_modes_fixed(connector, 166 + &s6e88a0_ams427ap24_mode); 167 + } 168 + 169 + static const struct drm_panel_funcs s6e88a0_ams427ap24_panel_funcs = { 170 + .prepare = s6e88a0_ams427ap24_prepare, 171 + .unprepare = s6e88a0_ams427ap24_unprepare, 172 + .get_modes = s6e88a0_ams427ap24_get_modes, 173 + }; 174 + 175 + static int s6e88a0_ams427ap24_probe(struct mipi_dsi_device *dsi) 176 + { 177 + struct device *dev = &dsi->dev; 178 + struct s6e88a0_ams427ap24 *ctx; 179 + int ret; 180 + 181 + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 182 + if (!ctx) 183 + return -ENOMEM; 184 + 185 + ret = devm_regulator_bulk_get_const(dev, 186 + ARRAY_SIZE(s6e88a0_ams427ap24_supplies), 187 + s6e88a0_ams427ap24_supplies, 188 + &ctx->supplies); 189 + if (ret < 0) 190 + return ret; 191 + 192 + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 193 + if (IS_ERR(ctx->reset_gpio)) 194 + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 195 + "Failed to get reset-gpios\n"); 196 + 197 + ctx->dsi = dsi; 198 + mipi_dsi_set_drvdata(dsi, ctx); 199 + 200 + dsi->lanes = 2; 201 + dsi->format = MIPI_DSI_FMT_RGB888; 202 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | 203 + MIPI_DSI_MODE_NO_EOT_PACKET; 204 + 205 + drm_panel_init(&ctx->panel, dev, &s6e88a0_ams427ap24_panel_funcs, 206 + DRM_MODE_CONNECTOR_DSI); 207 + ctx->panel.prepare_prev_first = true; 208 + 209 + drm_panel_add(&ctx->panel); 210 + 211 + ret = mipi_dsi_attach(dsi); 212 + if (ret < 0) { 213 + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 214 + drm_panel_remove(&ctx->panel); 215 + return ret; 216 + } 217 + 218 + return 0; 219 + } 220 + 221 + static void s6e88a0_ams427ap24_remove(struct mipi_dsi_device *dsi) 222 + { 223 + struct s6e88a0_ams427ap24 *ctx = mipi_dsi_get_drvdata(dsi); 224 + int ret; 225 + 226 + ret = mipi_dsi_detach(dsi); 227 + if (ret < 0) 228 + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 229 + 230 + drm_panel_remove(&ctx->panel); 231 + } 232 + 233 + static const struct of_device_id s6e88a0_ams427ap24_of_match[] = { 234 + { .compatible = "samsung,s6e88a0-ams427ap24" }, 235 + { /* sentinel */ }, 236 + }; 237 + MODULE_DEVICE_TABLE(of, s6e88a0_ams427ap24_of_match); 238 + 239 + static struct mipi_dsi_driver s6e88a0_ams427ap24_driver = { 240 + .probe = s6e88a0_ams427ap24_probe, 241 + .remove = s6e88a0_ams427ap24_remove, 242 + .driver = { 243 + .name = "panel-s6e88a0-ams427ap24", 244 + .of_match_table = s6e88a0_ams427ap24_of_match, 245 + }, 246 + }; 247 + module_mipi_dsi_driver(s6e88a0_ams427ap24_driver); 248 + 249 + MODULE_AUTHOR("Jakob Hauser <jahau@rocketmail.com>"); 250 + MODULE_DESCRIPTION("Samsung AMS427AP24 panel with S6E88A0 controller"); 251 + MODULE_LICENSE("GPL v2");