]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
drm/panel: add samsung s6e3fa7 panel driver
authorRichard Acayan <mailingradian@gmail.com>
Fri, 9 Feb 2024 00:16:43 +0000 (19:16 -0500)
committerNeil Armstrong <neil.armstrong@linaro.org>
Thu, 29 Feb 2024 08:48:57 +0000 (09:48 +0100)
The S6E3FA7 display controller is enabled in every Pixel 3a (non-XL)
variant. Add the driver for it, generated by
linux-mdss-dsi-panel-driver-generator.

There are other panels connected to the same S6E3FA7 display controller,
such as the AMS604NL01 panel, which are incompatible with this driver.
Name the device tree compatible after the panel model according to
iFixit.

Link: https://github.com/msm8916-mainline/linux-mdss-dsi-panel-driver-generator
Link: https://android.googlesource.com/kernel/msm/+/7fda1cd7b64710dafac5f34899611c6d35eb4cd2/arch/arm64/boot/dts/google/dsi-panel-s6e3fa7-1080p-cmd.dtsi
Link: https://github.com/msm8953-mainline/linux/blob/v6.6.12-r0/drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c
Link: https://www.ifixit.com/Guide/Image/meta/muyjtLQTHu6MDkhK
Signed-off-by: Richard Acayan <mailingradian@gmail.com>
Reviewed-by: Jessica Zhang <quic_jesszhan@quicinc.com>
Link: https://lore.kernel.org/r/20240209001639.387374-8-mailingradian@gmail.com
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
drivers/gpu/drm/panel/Kconfig
drivers/gpu/drm/panel/Makefile
drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c [new file with mode: 0644]

index d037b3b8b9993458a8f1615902363a6663a94052..6dc451f58a3e39a4cae1f1f64d6f3fc7f87558d8 100644 (file)
@@ -586,6 +586,15 @@ config DRM_PANEL_SAMSUNG_LD9040
        depends on BACKLIGHT_CLASS_DEVICE
        select VIDEOMODE_HELPERS
 
+config DRM_PANEL_SAMSUNG_S6E3FA7
+       tristate "Samsung S6E3FA7 panel driver"
+       depends on OF
+       depends on DRM_MIPI_DSI
+       depends on BACKLIGHT_CLASS_DEVICE
+       help
+         Say Y here if you want to enable support for the Samsung S6E3FA7
+         1920x2220 panel.
+
 config DRM_PANEL_SAMSUNG_S6D16D0
        tristate "Samsung S6D16D0 DSI video mode panel"
        depends on OF
index f156d7fa0bccccf9ff088645d714ac8c447d65c9..24a02655d72667064822df1946934f32151ff131 100644 (file)
@@ -62,6 +62,7 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D16D0) += panel-samsung-s6d16d0.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D27A1) += panel-samsung-s6d27a1.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0) += panel-samsung-s6d7aa0.o
+obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3FA7) += panel-samsung-s6e3fa7.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o
diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c b/drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c
new file mode 100644 (file)
index 0000000..10bc8fb
--- /dev/null
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Samsung S6E3FA7 panel.
+ *
+ * Copyright (c) 2022-2024, The Linux Foundation. All rights reserved.
+ * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree:
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+struct s6e3fa7_panel {
+       struct drm_panel panel;
+       struct mipi_dsi_device *dsi;
+       struct gpio_desc *reset_gpio;
+};
+
+static inline struct s6e3fa7_panel *to_s6e3fa7_panel(struct drm_panel *panel)
+{
+       return container_of(panel, struct s6e3fa7_panel, panel);
+}
+
+static void s6e3fa7_panel_reset(struct s6e3fa7_panel *ctx)
+{
+       gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+       usleep_range(1000, 2000);
+       gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+       usleep_range(10000, 11000);
+}
+
+static int s6e3fa7_panel_on(struct s6e3fa7_panel *ctx)
+{
+       struct mipi_dsi_device *dsi = ctx->dsi;
+       struct device *dev = &dsi->dev;
+       int ret;
+
+       ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+       if (ret < 0) {
+               dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+               return ret;
+       }
+       msleep(120);
+
+       ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+       if (ret < 0) {
+               dev_err(dev, "Failed to set tear on: %d\n", ret);
+               return ret;
+       }
+
+       mipi_dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a);
+       mipi_dsi_dcs_write_seq(dsi, 0xf4,
+                              0xbb, 0x23, 0x19, 0x3a, 0x9f, 0x0f, 0x09, 0xc0,
+                              0x00, 0xb4, 0x37, 0x70, 0x79, 0x69);
+       mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5);
+       mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
+
+       ret = mipi_dsi_dcs_set_display_on(dsi);
+       if (ret < 0) {
+               dev_err(dev, "Failed to set display on: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int s6e3fa7_panel_prepare(struct drm_panel *panel)
+{
+       struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel);
+       struct device *dev = &ctx->dsi->dev;
+       int ret;
+
+       s6e3fa7_panel_reset(ctx);
+
+       ret = s6e3fa7_panel_on(ctx);
+       if (ret < 0) {
+               dev_err(dev, "Failed to initialize panel: %d\n", ret);
+               gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int s6e3fa7_panel_unprepare(struct drm_panel *panel)
+{
+       struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel);
+
+       gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+
+       return 0;
+}
+
+static int s6e3fa7_panel_disable(struct drm_panel *panel)
+{
+       struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel);
+       struct mipi_dsi_device *dsi = ctx->dsi;
+       struct device *dev = &dsi->dev;
+       int ret;
+
+       ret = mipi_dsi_dcs_set_display_off(dsi);
+       if (ret < 0) {
+               dev_err(dev, "Failed to set display off: %d\n", ret);
+               return ret;
+       }
+
+       ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+       if (ret < 0) {
+               dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+               return ret;
+       }
+       msleep(120);
+
+       return 0;
+}
+
+static const struct drm_display_mode s6e3fa7_panel_mode = {
+       .clock = (1080 + 32 + 32 + 78) * (2220 + 32 + 4 + 78) * 60 / 1000,
+       .hdisplay = 1080,
+       .hsync_start = 1080 + 32,
+       .hsync_end = 1080 + 32 + 32,
+       .htotal = 1080 + 32 + 32 + 78,
+       .vdisplay = 2220,
+       .vsync_start = 2220 + 32,
+       .vsync_end = 2220 + 32 + 4,
+       .vtotal = 2220 + 32 + 4 + 78,
+       .width_mm = 62,
+       .height_mm = 127,
+};
+
+static int s6e3fa7_panel_get_modes(struct drm_panel *panel,
+                                struct drm_connector *connector)
+{
+       struct drm_display_mode *mode;
+
+       mode = drm_mode_duplicate(connector->dev, &s6e3fa7_panel_mode);
+       if (!mode)
+               return -ENOMEM;
+
+       drm_mode_set_name(mode);
+
+       mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+       connector->display_info.width_mm = mode->width_mm;
+       connector->display_info.height_mm = mode->height_mm;
+       drm_mode_probed_add(connector, mode);
+
+       return 1;
+}
+
+static const struct drm_panel_funcs s6e3fa7_panel_funcs = {
+       .prepare = s6e3fa7_panel_prepare,
+       .unprepare = s6e3fa7_panel_unprepare,
+       .disable = s6e3fa7_panel_disable,
+       .get_modes = s6e3fa7_panel_get_modes,
+};
+
+static int s6e3fa7_panel_bl_update_status(struct backlight_device *bl)
+{
+       struct mipi_dsi_device *dsi = bl_get_data(bl);
+       u16 brightness = backlight_get_brightness(bl);
+       int ret;
+
+       ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int s6e3fa7_panel_bl_get_brightness(struct backlight_device *bl)
+{
+       struct mipi_dsi_device *dsi = bl_get_data(bl);
+       u16 brightness;
+       int ret;
+
+       ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness);
+       if (ret < 0)
+               return ret;
+
+       return brightness;
+}
+
+static const struct backlight_ops s6e3fa7_panel_bl_ops = {
+       .update_status = s6e3fa7_panel_bl_update_status,
+       .get_brightness = s6e3fa7_panel_bl_get_brightness,
+};
+
+static struct backlight_device *
+s6e3fa7_panel_create_backlight(struct mipi_dsi_device *dsi)
+{
+       struct device *dev = &dsi->dev;
+       const struct backlight_properties props = {
+               .type = BACKLIGHT_RAW,
+               .brightness = 1023,
+               .max_brightness = 1023,
+       };
+
+       return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
+                                             &s6e3fa7_panel_bl_ops, &props);
+}
+
+static int s6e3fa7_panel_probe(struct mipi_dsi_device *dsi)
+{
+       struct device *dev = &dsi->dev;
+       struct s6e3fa7_panel *ctx;
+       int ret;
+
+       ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+
+       ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+       if (IS_ERR(ctx->reset_gpio))
+               return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
+                                    "Failed to get reset-gpios\n");
+
+       ctx->dsi = dsi;
+       mipi_dsi_set_drvdata(dsi, ctx);
+
+       dsi->lanes = 4;
+       dsi->format = MIPI_DSI_FMT_RGB888;
+       dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST |
+                         MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM;
+
+       drm_panel_init(&ctx->panel, dev, &s6e3fa7_panel_funcs,
+                      DRM_MODE_CONNECTOR_DSI);
+       ctx->panel.prepare_prev_first = true;
+
+       ctx->panel.backlight = s6e3fa7_panel_create_backlight(dsi);
+       if (IS_ERR(ctx->panel.backlight))
+               return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
+                                    "Failed to create backlight\n");
+
+       drm_panel_add(&ctx->panel);
+
+       ret = mipi_dsi_attach(dsi);
+       if (ret < 0) {
+               dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
+               drm_panel_remove(&ctx->panel);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void s6e3fa7_panel_remove(struct mipi_dsi_device *dsi)
+{
+       struct s6e3fa7_panel *ctx = mipi_dsi_get_drvdata(dsi);
+       int ret;
+
+       ret = mipi_dsi_detach(dsi);
+       if (ret < 0)
+               dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
+
+       drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id s6e3fa7_panel_of_match[] = {
+       { .compatible = "samsung,s6e3fa7-ams559nk06" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, s6e3fa7_panel_of_match);
+
+static struct mipi_dsi_driver s6e3fa7_panel_driver = {
+       .probe = s6e3fa7_panel_probe,
+       .remove = s6e3fa7_panel_remove,
+       .driver = {
+               .name = "panel-samsung-s6e3fa7",
+               .of_match_table = s6e3fa7_panel_of_match,
+       },
+};
+module_mipi_dsi_driver(s6e3fa7_panel_driver);
+
+MODULE_AUTHOR("Richard Acayan <mailingradian@gmail.com>");
+MODULE_DESCRIPTION("DRM driver for Samsung S6E3FA7 command mode DSI panel");
+MODULE_LICENSE("GPL");