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

Configure Feed

Select the types of activity you want to include in your feed.

drm/panel: Add Samsung AMS581VF01 panel driver

Add the driver for Samsung AMS581VF01 SOFEF01-based 5.81" FHD Plus CMD
mode OLED panel support found in Google Pixel 4a (sm7150-google-sunfish)

Signed-off-by: Danila Tikhonov <danila@jiaxyga.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://lore.kernel.org/r/20241013212402.15624-3-danila@jiaxyga.com
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20241013212402.15624-3-danila@jiaxyga.com

authored by

Danila Tikhonov and committed by
Neil Armstrong
b330f3a0 dca22e99

+293
+9
drivers/gpu/drm/panel/Kconfig
··· 614 614 Say Y here if you want to enable support for Ronbo Electronics 615 615 RB070D30 1024x600 DSI panel. 616 616 617 + config DRM_PANEL_SAMSUNG_AMS581VF01 618 + tristate "Samsung AMS581VF01 panel" 619 + depends on OF 620 + depends on DRM_MIPI_DSI 621 + depends on BACKLIGHT_CLASS_DEVICE 622 + help 623 + Say Y or M here if you want to enable support for the 624 + Samsung AMS581VF01 FHD Plus (2340x1080@60Hz) CMD mode panel. 625 + 617 626 config DRM_PANEL_SAMSUNG_AMS639RQ08 618 627 tristate "Samsung AMS639RQ08 panel" 619 628 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 62 62 obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM692E5) += panel-raydium-rm692e5.o 63 63 obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM69380) += panel-raydium-rm69380.o 64 64 obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o 65 + obj-$(CONFIG_DRM_PANEL_SAMSUNG_AMS581VF01) += panel-samsung-ams581vf01.o 65 66 obj-$(CONFIG_DRM_PANEL_SAMSUNG_AMS639RQ08) += panel-samsung-ams639rq08.o 66 67 obj-$(CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20) += panel-samsung-atna33xc20.o 67 68 obj-$(CONFIG_DRM_PANEL_SAMSUNG_DB7430) += panel-samsung-db7430.o
+283
drivers/gpu/drm/panel/panel-samsung-ams581vf01.c
··· 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");