[PATCH 2/2] drm/panel: lms397kf04: Add driver for LMS397KF04

Linus Walleij linus.walleij at linaro.org
Mon Apr 5 23:47:13 UTC 2021


This adds a new driver for the Samsung LMS397KF04
DPI display controlled over SPI.

Signed-off-by: Linus Walleij <linus.walleij at linaro.org>
---
 MAINTAINERS                                   |   7 +
 drivers/gpu/drm/panel/Kconfig                 |   8 +
 drivers/gpu/drm/panel/Makefile                |   1 +
 .../gpu/drm/panel/panel-samsung-lms397kf04.c  | 421 ++++++++++++++++++
 4 files changed, 437 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-samsung-lms397kf04.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d92f85ca831d..6d32432adebe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5731,6 +5731,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/display/panel/raydium,rm67191.yaml
 F:	drivers/gpu/drm/panel/panel-raydium-rm67191.c
 
+DRM DRIVER FOR SAMSUNG LMS397KF04 PANELS
+M:	Linus Walleij <linus.walleij at linaro.org>
+S:	Maintained
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+F:	Documentation/devicetree/bindings/display/panel/samsung,lms397kf04.yaml
+F:	drivers/gpu/drm/panel/panel-samsung-lms397kf04.c
+
 DRM DRIVER FOR SITRONIX ST7703 PANELS
 M:	Guido Günther <agx at sigxcpu.org>
 R:	Purism Kernel Team <kernel at puri.sm>
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 4894913936e9..8e24a92ad9ce 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -348,6 +348,14 @@ config DRM_PANEL_SAMSUNG_S6D16D0
 	depends on DRM_MIPI_DSI
 	select VIDEOMODE_HELPERS
 
+config DRM_PANEL_SAMSUNG_LMS397KF04
+	tristate "Samsung LMS397KF04 DPI panel"
+	depends on OF && SPI && GPIOLIB
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y here if you want to enable support for the Samsung
+	  LMS397KF04 480x800 DPI panel.
+
 config DRM_PANEL_SAMSUNG_S6E3HA2
 	tristate "Samsung S6E3HA2 DSI video mode panel"
 	depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index cae4d976c069..de23d00937db 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen
 obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o
 obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o
 obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o
+obj-$(CONFIG_DRM_PANEL_SAMSUNG_LMS397KF04) += panel-samsung-lms397kf04.o
 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_S6E3HA2) += panel-samsung-s6e3ha2.o
diff --git a/drivers/gpu/drm/panel/panel-samsung-lms397kf04.c b/drivers/gpu/drm/panel/panel-samsung-lms397kf04.c
new file mode 100644
index 000000000000..41290cadc351
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-lms397kf04.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Panel driver for the Samsung LMS397KF04 480x800 DPI RGB panel.
+ * According to the data sheet the display controller is called DB7430
+ * Linus Walleij <linus.walleij at linaro.org>
+ */
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+
+#define LMS397_MANUFACTURER_CMD		0xb0
+#define LMS397_UNKNOWN_B4		0xb4
+#define LMS397_USER_SELECT		0xb5
+#define LMS397_UNKNOWN_B7		0xb7
+#define LMS397_UNKNOWN_B8		0xb8
+#define LMS397_PANEL_DRIVING		0xc0
+#define LMS397_SOURCE_CONTROL		0xc1
+#define LMS397_GATE_INTERFACE		0xc4
+#define LMS397_DISPLAY_H_TIMING		0xc5
+#define LMS397_RGB_SYNC_OPTION		0xc6
+#define LMS397_GAMMA_SET_RED		0xc8
+#define LMS397_GAMMA_SET_GREEN		0xc9
+#define LMS397_GAMMA_SET_BLUE		0xca
+#define LMS397_BIAS_CURRENT_CTRL	0xd1
+#define LMS397_DDV_CTRL			0xd2
+#define LMS397_GAMMA_CTRL_REF		0xd3
+#define LMS397_UNKNOWN_D4		0xd4
+#define LMS397_DCDC_CTRL		0xd5
+#define LMS397_VCL_CTRL			0xd6
+#define LMS397_UNKNOWN_F8		0xf8
+#define LMS397_UNKNOWN_FC		0xfc
+
+#define DATA_MASK	0x100
+
+/**
+ * struct lms397kf04 - state container for the LMS397kf04 panel
+ */
+struct lms397kf04 {
+	/**
+	 * @dev: the container device
+	 */
+	struct device *dev;
+	/**
+	 * @spi: the corresponding SPI device
+	 */
+	struct spi_device *spi;
+	/**
+	 * @panel: the DRM panel instance for this device
+	 */
+	struct drm_panel panel;
+	/**
+	 * @width: the width of this panel in mm
+	 */
+	u32 width;
+	/**
+	 * @height: the height of this panel in mm
+	 */
+	u32 height;
+	/**
+	 * @reset: reset GPIO line
+	 */
+	struct gpio_desc *reset;
+	/**
+	 * @regulators: VCCIO and VIO supply regulators
+	 */
+	struct regulator_bulk_data regulators[2];
+};
+
+static const struct drm_display_mode lms397kf04_mode = {
+	/*
+	 * 31 ns period min (htotal*vtotal*vrefresh)/1000
+	 * gives a Vrefresh of ~71 Hz.
+	 */
+	.clock = 32258,
+	.hdisplay = 480,
+	.hsync_start = 480 + 10,
+	.hsync_end = 480 + 10 + 4,
+	.htotal = 480 + 10 + 4 + 40,
+	.vdisplay = 800,
+	.vsync_start = 800 + 6,
+	.vsync_end = 800 + 6 + 1,
+	.vtotal = 800 + 6 + 1 + 7,
+	.width_mm = 53,
+	.height_mm = 87,
+	.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static inline struct lms397kf04 *to_lms397kf04(struct drm_panel *panel)
+{
+	return container_of(panel, struct lms397kf04, panel);
+}
+
+static int lms397kf04_write_word(struct lms397kf04 *lms, u16 data)
+{
+	/* SPI buffers are always in CPU order */
+	return spi_write(lms->spi, &data, 2);
+}
+
+static int lms397kf04_dcs_write(struct lms397kf04 *lms, const u8 *data, size_t len)
+{
+	int ret;
+
+	dev_dbg(lms->dev, "SPI writing dcs seq: %*ph\n", (int)len, data);
+
+	/*
+	 * This sends 9 bits with the first bit (bit 8) set to 0
+	 * This indicates that this is a command. Anything after the
+	 * command is data.
+	 */
+	ret = lms397kf04_write_word(lms, *data);
+
+	while (!ret && --len) {
+		++data;
+		/* This sends 9 bits with the first bit (bit 8) set to 1 */
+		ret = lms397kf04_write_word(lms, *data | DATA_MASK);
+	}
+
+	if (ret) {
+		dev_err(lms->dev, "SPI error %d writing dcs seq: %*ph\n", ret,
+			(int)len, data);
+	}
+
+	return ret;
+}
+
+#define lms397kf04_dcs_write_seq_static(ctx, seq ...) \
+	({ \
+		static const u8 d[] = { seq }; \
+		lms397kf04_dcs_write(ctx, d, ARRAY_SIZE(d)); \
+	})
+
+static int lms397kf04_power_on(struct lms397kf04 *lms)
+{
+	int ret;
+
+	/* Power up */
+	ret = regulator_bulk_enable(ARRAY_SIZE(lms->regulators),
+				    lms->regulators);
+	if (ret) {
+		dev_err(lms->dev, "failed to enable regulators: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+	/* Assert reset >=1 ms */
+	gpiod_set_value_cansleep(lms->reset, 1);
+	msleep(1);
+	/* De-assert reset */
+	gpiod_set_value_cansleep(lms->reset, 0);
+	/* Wait >= 10 ms */
+	msleep(10);
+	dev_info(lms->dev, "de-asserted RESET\n");
+
+	/*
+	 * This is set to 0x0a (RGB/BGR order + horizontal flip) in order
+	 * to make the display behave normally. If this is not set the displays
+	 * normal output behaviour is horizontally flipped and BGR ordered. Do
+	 * it twice because the first message doesn't always "take".
+	 */
+	lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_ADDRESS_MODE, 0x0a);
+	lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_ADDRESS_MODE, 0x0a);
+	/* Called "Access protection off" in vendor code */
+	lms397kf04_dcs_write_seq_static(lms, LMS397_MANUFACTURER_CMD, 0x00);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_PANEL_DRIVING, 0x28, 0x08);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_SOURCE_CONTROL,
+					0x01, 0x30, 0x15, 0x05, 0x22);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_GATE_INTERFACE,
+					0x10, 0x01, 0x00);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_DISPLAY_H_TIMING,
+					0x06, 0x55, 0x03, 0x07, 0x0b,
+					0x33, 0x00, 0x01, 0x03);
+	/*
+	 * 0x00 in datasheet 0x01 in vendor code 0x00, it seems 0x01 means
+	 * DE active high and 0x00 means DE active low.
+	 */
+	lms397kf04_dcs_write_seq_static(lms, LMS397_RGB_SYNC_OPTION, 0x01);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_SET_RED,
+		/* R positive gamma */ 0x00,
+		0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62,
+		0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08,
+		/* R negative gamma */ 0x00,
+		0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62,
+		0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_SET_GREEN,
+		/* G positive gamma */ 0x00,
+		0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59,
+		0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A,
+		/* G negative gamma */ 0x00,
+		0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59,
+		0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_SET_BLUE,
+		/* B positive gamma */ 0x00,
+		0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D,
+		0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C,
+		/* B negative gamma */ 0x00,
+		0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D,
+		0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_BIAS_CURRENT_CTRL,
+					0x33, 0x13);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_DDV_CTRL,
+					0x11, 0x00, 0x00);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_CTRL_REF,
+					0x50, 0x50);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_DCDC_CTRL,
+					0x2f, 0x11, 0x1e, 0x46);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_VCL_CTRL,
+					0x11, 0x0a);
+
+	return 0;
+}
+
+static void lms397kf04_power_off(struct lms397kf04 *lms)
+{
+	/* Go into RESET and disable regulators */
+	gpiod_set_value_cansleep(lms->reset, 1);
+	regulator_bulk_disable(ARRAY_SIZE(lms->regulators),
+			       lms->regulators);
+}
+
+static int lms397kf04_unprepare(struct drm_panel *panel)
+{
+	struct lms397kf04 *lms = to_lms397kf04(panel);
+
+	lms397kf04_power_off(lms);
+
+	return 0;
+}
+
+static int lms397kf04_disable(struct drm_panel *panel)
+{
+	struct lms397kf04 *lms = to_lms397kf04(panel);
+
+	lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_DISPLAY_OFF);
+	msleep(25);
+	lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_ENTER_SLEEP_MODE);
+	msleep(120);
+
+	return 0;
+}
+
+static int lms397kf04_prepare(struct drm_panel *panel)
+{
+	struct lms397kf04 *lms = to_lms397kf04(panel);
+	int ret;
+
+	ret = lms397kf04_power_on(lms);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int lms397kf04_enable(struct drm_panel *panel)
+{
+	struct lms397kf04 *lms = to_lms397kf04(panel);
+
+	/* Exit sleep mode */
+	lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(20);
+
+	/* NVM (non-volatile memory) load sequence */
+	lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_D4,
+					0x52, 0x5e);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_F8,
+					0x01, 0xf5, 0xf2, 0x71, 0x44);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_FC,
+					0x00, 0x08);
+	msleep(150);
+
+	/* CABC turn on sequence (BC = backlight control) */
+	lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_B4,
+					0x0f, 0x00, 0x50);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_USER_SELECT, 0x80);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_B7, 0x24);
+	lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_B8, 0x01);
+
+	/* Turn on display */
+	lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_DISPLAY_ON);
+
+	/* Update brightness */
+
+	return 0;
+}
+
+/**
+ * lms397kf04_get_modes() - return the mode
+ * @panel: the panel to get the mode for
+ * @connector: reference to the central DRM connector control structure
+ */
+static int lms397kf04_get_modes(struct drm_panel *panel,
+			    struct drm_connector *connector)
+{
+	struct lms397kf04 *lms = to_lms397kf04(panel);
+	struct drm_display_mode *mode;
+	static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+	mode = drm_mode_duplicate(connector->dev, &lms397kf04_mode);
+	if (!mode) {
+		dev_err(lms->dev, "failed to add mode\n");
+		return -ENOMEM;
+	}
+
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+	connector->display_info.bus_flags =
+		DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+	drm_display_info_set_bus_formats(&connector->display_info,
+					 &bus_format, 1);
+
+	drm_mode_set_name(mode);
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	drm_mode_probed_add(connector, mode);
+
+	return 1;
+}
+
+static const struct drm_panel_funcs lms397kf04_drm_funcs = {
+	.disable = lms397kf04_disable,
+	.unprepare = lms397kf04_unprepare,
+	.prepare = lms397kf04_prepare,
+	.enable = lms397kf04_enable,
+	.get_modes = lms397kf04_get_modes,
+};
+
+static int lms397kf04_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct lms397kf04 *lms;
+	int ret;
+
+	lms = devm_kzalloc(dev, sizeof(*lms), GFP_KERNEL);
+	if (!lms)
+		return -ENOMEM;
+	lms->dev = dev;
+
+	/*
+	 * VCI   is the analog voltage supply
+	 * VCCIO is the digital I/O voltage supply
+	 */
+	lms->regulators[0].supply = "vci";
+	lms->regulators[1].supply = "vccio";
+	ret = devm_regulator_bulk_get(dev,
+				      ARRAY_SIZE(lms->regulators),
+				      lms->regulators);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+	/* This asserts the RESET signal, putting the display into reset */
+	lms->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(lms->reset)) {
+		dev_err(dev, "no RESET GPIO\n");
+		return -ENODEV;
+	}
+
+	spi->bits_per_word = 9;
+	/* Preserve e.g. SPI_3WIRE setting */
+	spi->mode |= SPI_MODE_3;
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(dev, "spi setup failed.\n");
+		return ret;
+	}
+	lms->spi = spi;
+
+	drm_panel_init(&lms->panel, dev, &lms397kf04_drm_funcs,
+		       DRM_MODE_CONNECTOR_DPI);
+
+	/* FIXME: if no external backlight, use internal backlight */
+	ret = drm_panel_of_backlight(&lms->panel);
+	if (ret) {
+		dev_info(dev, "failed to add backlight\n");
+		return ret;
+	}
+
+	spi_set_drvdata(spi, lms);
+
+	drm_panel_add(&lms->panel);
+	dev_info(dev, "added panel\n");
+
+	return 0;
+}
+
+static int lms397kf04_remove(struct spi_device *spi)
+{
+	struct lms397kf04 *lms = spi_get_drvdata(spi);
+
+	drm_panel_remove(&lms->panel);
+	return 0;
+}
+
+static const struct of_device_id lms397kf04_match[] = {
+	{ .compatible = "samsung,lms397kf04", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, lms397kf04_match);
+
+static struct spi_driver lms397kf04_driver = {
+	.probe		= lms397kf04_probe,
+	.remove		= lms397kf04_remove,
+	.driver		= {
+		.name	= "lms397kf04-panel",
+		.of_match_table = lms397kf04_match,
+	},
+};
+module_spi_driver(lms397kf04_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij at linaro.org>");
+MODULE_DESCRIPTION("Samsung LMS397KF04 panel driver");
+MODULE_LICENSE("GPL v2");
-- 
2.29.2



More information about the dri-devel mailing list