mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/2] Add support for st75156 in st7571 display driver
@ 2026-10-22 15:31 Lars Rask
  2026-10-22 15:31 ` [PATCH 1/2] drm/sitronix/st7571-spi: Add support for ST75156 controller Lars Rask
  2026-10-22 15:31 ` [PATCH 2/2] dt-bindings: display: sitronix,st75156: add example for SPI Lars Rask
  0 siblings, 2 replies; 3+ messages in thread
From: Lars Rask @ 2026-10-22 15:31 UTC (permalink / raw)
  To: Marcus Folkesson, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: dri-devel, linux-kernel, devicetree, kernel, Lars Rask

To: Marcus Folkesson <marcus.folkesson@gmail.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: kernel@axis.com

The Sitronix st7571 driver previously supported the st7567 and st7571
chips. This commit adds support for the st75156 chip used in SPI 4 wire mode.
In order to use the st75156 chip, you need to specify
CONFIG_DRM_ST7571=m and CONFIG_DRM_ST7571_SPI=m in the .config file, and
also use the compatible string "sitronix,st75156" in the device tree.

Signed-off-by: Lars Rask <lars.rask@axis.com>
---
Lars Rask (2):
      drm/sitronix/st7571-spi: Add support for ST75156 controller
      dt-bindings: display: sitronix,st75156: add example for SPI

 .../bindings/display/sitronix,st75156.yaml         |  67 ++++
 MAINTAINERS                                        |   1 +
 drivers/gpu/drm/sitronix/st7571-spi.c              | 118 +++++-
 drivers/gpu/drm/sitronix/st7571.c                  | 420 ++++++++++++++++++++-
 drivers/gpu/drm/sitronix/st7571.h                  |  20 +
 5 files changed, 617 insertions(+), 9 deletions(-)
---
base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
change-id: 20261015-add_st75156_support-00ec2c7f63a6

Best regards,
-- 
Lars Rask <lars.rask@axis.com>


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] drm/sitronix/st7571-spi: Add support for ST75156 controller
  2026-10-22 15:31 [PATCH 0/2] Add support for st75156 in st7571 display driver Lars Rask
@ 2026-10-22 15:31 ` Lars Rask
  2026-10-22 15:31 ` [PATCH 2/2] dt-bindings: display: sitronix,st75156: add example for SPI Lars Rask
  1 sibling, 0 replies; 3+ messages in thread
From: Lars Rask @ 2026-10-22 15:31 UTC (permalink / raw)
  To: Marcus Folkesson, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: dri-devel, linux-kernel, devicetree, kernel, Lars Rask

From: Lars Rask <lars.rask@axis.com>

Made additions to st7571 display driver to support st75156.

Signed-off-by: Lars Rask <lars.rask@axis.com>
---
 drivers/gpu/drm/sitronix/st7571-spi.c | 118 +++++++++-
 drivers/gpu/drm/sitronix/st7571.c     | 420 +++++++++++++++++++++++++++++++++-
 drivers/gpu/drm/sitronix/st7571.h     |  20 ++
 3 files changed, 549 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/sitronix/st7571-spi.c b/drivers/gpu/drm/sitronix/st7571-spi.c
index 0206e9162f1c..724c0eb5e7da 100644
--- a/drivers/gpu/drm/sitronix/st7571-spi.c
+++ b/drivers/gpu/drm/sitronix/st7571-spi.c
@@ -17,23 +17,133 @@ static const struct regmap_config st7571_spi_regmap_config = {
 	.can_multi_write = true,
 };
 
+struct spi_device_data {
+	struct spi_device *spi;
+	struct gpio_desc *dc_gpio;
+};
+
+static int regmap_spi_write(void *context, const void *data, size_t count);
+static int regmap_spi_read(void *context, const void *reg, size_t reg_size,
+			   void *val, size_t val_size);
+
+static const struct regmap_bus regmap_spi_dc_bus = {
+	.write = regmap_spi_write,
+	.read = regmap_spi_read,
+};
+
+static int regmap_spi_write(void *context, const void *data, size_t count)
+{
+	struct spi_device_data *spi_data = context;
+	const u8 *buf = data;
+	u8 mode;
+	size_t num_data_bytes;
+	const u8 *data_bytes;
+	int write_result;
+	const u8 *cmdbuf;
+	const u8 *databuf;
+
+	/*
+	 * count is required to be at least two bytes. The first byte is mode,
+	 * which can be either ST7571_COMMAND_MODE or ST7571_DATA_MODE. If the
+	 * first byte is ST7571_COMMAND_MODE, the second byte is a command
+	 * (register address). If the first byte is ST7571_DATA_MODE the second
+	 * byte is a data byte.
+	 */
+
+	if (count < 2)
+		return -EINVAL;
+
+	mode = buf[0];
+
+	if (mode == ST7571_COMMAND_MODE) {
+		gpiod_set_value_cansleep(spi_data->dc_gpio, 0);
+
+		cmdbuf = kmemdup(&buf[1], 1, GFP_KERNEL);
+		if (!cmdbuf)
+			return -ENOMEM;
+
+		write_result = spi_write(spi_data->spi, cmdbuf, 1);
+		kfree(cmdbuf);
+
+		if (write_result != 0)
+			return write_result;
+
+		num_data_bytes = count - 2;
+
+		if (num_data_bytes > 0) {
+			gpiod_set_value_cansleep(spi_data->dc_gpio, 1);
+			data_bytes = &buf[2];
+			databuf = kmemdup(data_bytes, num_data_bytes, GFP_KERNEL);
+
+			write_result = spi_write(spi_data->spi, databuf, num_data_bytes);
+			kfree(databuf);
+
+			if (write_result != 0)
+				return write_result;
+		}
+	} else if (mode == ST7571_DATA_MODE) {
+		num_data_bytes = count - 1;
+
+		if (num_data_bytes > 0) {
+			gpiod_set_value_cansleep(spi_data->dc_gpio, 1);
+			data_bytes = &buf[1];
+			databuf = kmemdup(data_bytes, num_data_bytes, GFP_KERNEL);
+			write_result = spi_write(spi_data->spi, databuf, num_data_bytes);
+			kfree(databuf);
+
+			if (write_result != 0)
+				return write_result;
+		}
+
+	} else {
+		/* Unsupported mode */
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* The st7571 driver does not read registers but regmap expects a .read */
+static int regmap_spi_read(void *context, const void *reg, size_t reg_size,
+			   void *val, size_t val_size)
+{
+	return -EOPNOTSUPP;
+}
+
 static int st7571_spi_probe(struct spi_device *spi)
 {
 	struct st7571_device *st7571;
 	struct regmap *regmap;
+	struct spi_device_data *spi_data;
+
+	spi_data = devm_kzalloc(&spi->dev, sizeof(*spi_data), GFP_KERNEL);
+	if (IS_ERR(spi_data))
+		return dev_err_probe(&spi->dev, PTR_ERR(regmap),
+				     "Failed to initialize spi data\n");
 
-	regmap = devm_regmap_init_spi(spi, &st7571_spi_regmap_config);
-	if (IS_ERR(regmap)) {
+	spi_data->spi = spi;
+	spi_data->dc_gpio = devm_gpiod_get(&spi->dev, "dc", GPIOD_OUT_LOW);
+
+	if (IS_ERR(spi_data->dc_gpio))
+		/* init regmap for 3 line SPI */
+		regmap = devm_regmap_init_spi(spi, &st7571_spi_regmap_config);
+	else
+		/* init regmap for 4 line SPI */
+		regmap = devm_regmap_init(&spi->dev, &regmap_spi_dc_bus,
+					  spi_data, &st7571_spi_regmap_config);
+
+	if (IS_ERR(regmap))
 		return dev_err_probe(&spi->dev, PTR_ERR(regmap),
 				     "Failed to initialize regmap\n");
-	}
 
 	st7571 = st7571_probe(&spi->dev, regmap);
 	if (IS_ERR(st7571))
 		return dev_err_probe(&spi->dev, PTR_ERR(st7571),
 				     "Failed to initialize regmap\n");
 
+	st7571->spi_data = spi_data;
 	spi_set_drvdata(spi, st7571);
+
 	return 0;
 }
 
@@ -47,6 +157,7 @@ static void st7571_spi_remove(struct spi_device *spi)
 static const struct of_device_id st7571_of_match[] = {
 	{ .compatible = "sitronix,st7567", .data = &st7567_config },
 	{ .compatible = "sitronix,st7571", .data = &st7571_config },
+	{ .compatible = "sitronix,st75156", .data = &st75156_config },
 	{},
 };
 MODULE_DEVICE_TABLE(of, st7571_of_match);
@@ -54,6 +165,7 @@ MODULE_DEVICE_TABLE(of, st7571_of_match);
 static const struct spi_device_id st7571_spi_id[] = {
 	{ "st7567", 0 },
 	{ "st7571", 0 },
+	{ "st75156", 0 },
 	{ }
 };
 MODULE_DEVICE_TABLE(spi, st7571_spi_id);
diff --git a/drivers/gpu/drm/sitronix/st7571.c b/drivers/gpu/drm/sitronix/st7571.c
index 20954c33eca9..2dd52f2214d2 100644
--- a/drivers/gpu/drm/sitronix/st7571.c
+++ b/drivers/gpu/drm/sitronix/st7571.c
@@ -11,6 +11,7 @@
 #include <linux/i2c.h>
 #include <linux/module.h>
 #include <linux/regmap.h>
+#include <linux/backlight.h>
 
 #include <drm/clients/drm_client_setup.h>
 #include <drm/drm_atomic.h>
@@ -37,9 +38,6 @@
 
 #include "st7571.h"
 
-#define ST7571_COMMAND_MODE			(0x00)
-#define ST7571_DATA_MODE			(0x40)
-
 /* Normal mode command set */
 #define ST7571_DISPLAY_OFF			(0xae)
 #define ST7571_DISPLAY_ON			(0xaf)
@@ -73,6 +71,46 @@
 /* ST7567 commands */
 #define ST7567_SET_LCD_BIAS(m) (0xa2 | FIELD_PREP(GENMASK(0, 0), (m)))
 
+/* ST75156 commands */
+#define ST75156_EXTENSION_COMMAND_1			(0x30)
+#define ST75156_EXTENSION_COMMAND_2			(0x31)
+
+/* ST75156 Extension command 1 */
+#define ST75156_DISPLAY_OFF					(0xae)
+#define ST75156_DISPLAY_ON					(0xaf)
+#define ST75156_SLEEP_OUT					(0x94)
+#define ST75156_POWER_CONTROL				(0x20)
+#define ST75156_SET_VOP						(0x81)
+#define ST75156_DISPLAY_MODE				(0xf0)
+#define ST75156_DISPLAY_CONTROL				(0xca)
+#define ST75156_DATA_SCAN_DIRECTION			(0xbc)
+#define ST75156_NORMAL_DISPLAY				(0xa6)
+#define ST75156_COLUMN_ADDRESS_SETTING		(0x15)
+#define ST75156_PAGE_ADDRESS_SETTING		(0x75)
+#define ST75156_ENTER_WRITE_DDRAM_MODE		(0x5c)
+
+/* ST75156 Extension command 2 */
+#define ST75156_SET_AUTO_READ_INSTRUCTION	(0xd7)
+#define ST75156_DISABLE_AUTO_READ			(0x9f)
+#define ST75156_OTP_WR_RD_CONTROL			(0xe0)
+#define ST75156_OTP_UPLOAD					(0xe3)
+#define ST75156_OTP_CONTROL_OUT				(0xe1)
+#define ST75156_ANALOG_CIRCUIT_SET			(0x32)
+#define ST75156_BOOSTER_LEVEL_X10			(0x51)
+#define ST75156_INTERAL_POWER_SUPPLY		(0x40)
+
+/* Special command used to indicate to the st75156_send_command_list
+ * function that we should sleep for a number of milliseconds before
+ * the next command when sending the init sequence for ST75156.
+ * NOTE: This command is not part of the ST75156 instruction set.
+ */
+#define ST75156_SLEEP_MS					(0x00)
+
+/* ST75156 Command buffer */
+#define ST75156_COMMAND_LEN 1
+#define ST75156_MAX_PARAMETERS 3
+#define ST75156_COMMAND_BUFFER_LEN (ST75156_COMMAND_LEN + ST75156_MAX_PARAMETERS)
+
 #define ST7571_PAGE_HEIGHT 8
 
 #define DRIVER_NAME "st7571"
@@ -99,6 +137,89 @@ static int st7571_send_command_list(struct st7571_device *st7571,
 	return ret;
 }
 
+static int st75156_send_command_list(struct st7571_device *st75156,
+				     const u8 *cmd_list, size_t len)
+{
+	/* Each command in the command list is formatted using the following format:
+	 *
+	 * +---------+--------------------------+-------------+------+-------------+
+	 * | command | number of parameters (n) | parameter 1 | .... | parameter n |
+	 * +---------+--------------------------+-------------+------+-------------+
+	 *
+	 * For example, if a command has 2 parameters it is stored like this:
+	 *
+	 * +---------+------+-------------+--------------+
+	 * | command | 0x02 | parameter 1 |  parameter 2 |
+	 * +---------+------+-------------+--------------+
+	 *
+	 * If a command does not have any parameters it is stored like this:
+	 *
+	 * +---------+------+
+	 * | command | 0x00 |
+	 * +---------+------+
+	 *
+	 * NOTE: The command ST75156_SLEEP_MS (0x00) is a special command defined like this:
+	 *
+	 * +------------------+------+----+
+	 * | ST75156_SLEEP_MS | 0x01 | ms |
+	 * +------------------+------+----+
+	 *
+	 * When this command is sent, the code will sleep for ms milliseconds before
+	 * sending the next command in the command list.
+	 */
+
+	int ret;
+	int i = 0;
+
+	while (i < len) {
+		int command = cmd_list[i];
+		int num_params = cmd_list[i + 1];
+		int current_param = 0;
+		int param;
+		int bufpos = 0;
+		u8 *buf = st75156->commandbuf;
+
+		i = i + 2;
+
+		if (command == ST75156_SLEEP_MS) {
+			msleep(cmd_list[i]);
+		} else {
+			/* Add command to command buffer */
+			buf[bufpos] = command;
+			bufpos++;
+
+			if (bufpos >= ST75156_COMMAND_BUFFER_LEN)
+				return -EINVAL;
+
+			if (num_params > 0) {
+				if (num_params > ST75156_MAX_PARAMETERS)
+					return -EINVAL;
+
+				for (param = 0; param < num_params; param++) {
+					current_param = cmd_list[i + param];
+
+					/* Add parameter to command buffer */
+
+					if (bufpos >= ST75156_COMMAND_BUFFER_LEN)
+						return -EINVAL;
+
+					buf[bufpos] = current_param;
+					bufpos++;
+				}
+			}
+
+			/* Write command buffer */
+			ret = regmap_bulk_write(st75156->regmap, ST7571_COMMAND_MODE, buf, bufpos);
+			if (ret < 0)
+				return ret;
+		}
+
+		i = i + num_params;
+	}
+
+	return 0;
+}
+
 static inline u8 st7571_transform_xy(const char *p, int x, int y, u8 bpp)
 {
 	int xrest = x % 8;
@@ -127,6 +248,37 @@ static inline u8 st7571_transform_xy(const char *p, int x, int y, u8 bpp)
 	return result;
 }
 
+static inline u8 st75156_transform_xy(struct st7571_device *st75156, int x, int y)
+{
+	int xrest = x % 8;
+	u8 result = 0;
+	u8 row_len = (st75156->ncols / 8) * st75156->bpp;
+	int i;
+	const char *p = st75156->hwbuf;
+
+	/*
+	 * Transforms an (x, y) pixel coordinate into a vertical 8-bit
+	 * column from the framebuffer. It calculates the corresponding byte in the
+	 * framebuffer, extracts the bit at the given x position across 8 consecutive
+	 * rows, and packs those bits into a single byte.
+	 *
+	 * Return an 8-bit value representing a vertical column of pixels.
+	 */
+	x = x / 8;
+	y = (y / 8) * 8;
+
+	for (i = 0; i < 8; i++) {
+		int row_idx = y + i;
+		u8 byte = p[row_idx * row_len + x];
+		/* LSB in input is left most pixel */
+		u8 bit = (byte >> xrest) & 1;
+		/* LSB in output is 7th row */
+		result |= (bit << (7 - i));
+	}
+
+	return result;
+}
+
 static int st7571_set_position(struct st7571_device *st7571, int x, int y)
 {
 	u8 cmd_list[] = {
@@ -138,6 +290,23 @@ static int st7571_set_position(struct st7571_device *st7571, int x, int y)
 	return st7571_send_command_list(st7571, cmd_list, ARRAY_SIZE(cmd_list));
 }
 
+static int st75156_set_position(struct st7571_device *st75156, int x, int y)
+{
+	u8 xs_param = st75156->startcol + x;
+	u8 xe_param = st75156->startcol + st75156->ncols - 1;
+	u8 ys_param = y / ST7571_PAGE_HEIGHT;
+	u8 ye_param = ys_param;
+
+	u8 cmd_list[] = {
+		ST75156_EXTENSION_COMMAND_1, 0x00,
+		ST75156_COLUMN_ADDRESS_SETTING, 0x02, xs_param, xe_param,
+		ST75156_PAGE_ADDRESS_SETTING, 0x02, ys_param, ye_param,
+		ST75156_ENTER_WRITE_DDRAM_MODE, 0x00,
+	};
+
+	return st75156_send_command_list(st75156, cmd_list, ARRAY_SIZE(cmd_list));
+}
+
 static int st7571_fb_clear_screen(struct st7571_device *st7571)
 {
 	u32 npixels = st7571->ncols * round_up(st7571->nlines, ST7571_PAGE_HEIGHT) * st7571->bpp;
@@ -150,6 +319,21 @@ static int st7571_fb_clear_screen(struct st7571_device *st7571)
 	return 0;
 }
 
+static int st75156_fb_clear_screen(struct st7571_device *st75156)
+{
+	u32 npixels = st75156->ncols * st75156->bpp;
+	int y;
+
+	memset(st75156->row, 0, npixels);
+
+	for (y = 0; y < st75156->nlines; y += ST7571_PAGE_HEIGHT) {
+		st75156_set_position(st75156, 0, y);
+		regmap_bulk_write(st75156->regmap, ST7571_DATA_MODE, st75156->row, npixels);
+	}
+
+	return 0;
+}
+
 static void st7571_prepare_buffer_monochrome(struct st7571_device *st7571,
 					     const struct iosys_map *vmap,
 					     struct drm_framebuffer *fb,
@@ -284,6 +468,37 @@ static int st7571_fb_update_rect_grayscale(struct drm_framebuffer *fb, struct dr
 	return 0;
 }
 
+static int st75156_fb_update_rect_monochrome(struct drm_framebuffer *fb, struct drm_rect *rect)
+{
+	struct st7571_device *st75156 = drm_to_st7571(fb->dev);
+	struct device *dev = st75156->dev;
+
+	char *row = st75156->row;
+	int x;
+	int y;
+	int num_bytes;
+	int ret;
+
+	/* Align y to display page boundaries */
+	rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT);
+	rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st75156->nlines);
+
+	for (y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) {
+		for (x = rect->x1; x < rect->x2; x++)
+			row[x] = st75156_transform_xy(st75156, x, y);
+
+		st75156_set_position(st75156, rect->x1, y);
+
+		num_bytes = (rect->x2) - (rect->x1);
+		ret = regmap_bulk_write(st75156->regmap, ST7571_DATA_MODE,
+					row + rect->x1, num_bytes);
+		if (ret < 0)
+			dev_err(dev, "Failed to write data to display (error code: %i", ret);
+	}
+
+	return 0;
+}
+
 static int st7571_connector_get_modes(struct drm_connector *conn)
 {
 	struct st7571_device *st7571 = drm_to_st7571(conn->dev);
@@ -318,6 +533,17 @@ static const struct st7571_panel_format st7571_grayscale = {
 	.nformats = 3,
 };
 
+static const struct st7571_panel_format st75156_monochrome = {
+	.prepare_buffer = st7571_prepare_buffer_monochrome,
+	.update_rect = st75156_fb_update_rect_monochrome,
+	.mode = ST7571_COLOR_MODE_BLACKWHITE,
+	.formats = {
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_R1,
+	},
+	.nformats = 2,
+};
+
 static const u64 st7571_primary_plane_fmtmods[] = {
 	DRM_FORMAT_MOD_LINEAR,
 	DRM_FORMAT_MOD_INVALID
@@ -388,7 +614,7 @@ static void st7571_primary_plane_helper_atomic_disable(struct drm_plane *plane,
 	if (!drm_dev_enter(drm, &idx))
 		return;
 
-	st7571_fb_clear_screen(st7571);
+	st7571->pdata->fb_clear_screen(st7571);
 	drm_dev_exit(idx);
 }
 
@@ -444,6 +670,13 @@ static void st7571_encoder_atomic_enable(struct drm_encoder *encoder,
 	u8 command = ST7571_DISPLAY_ON;
 	int ret;
 
+	/* ST75156 needs init function to be run earlier than ST7571
+	 * so we need to call it in the probe function instead of
+	 * the encoder_atomic_enable function.
+	 */
+	if (st7571->device_type == DEVICE_TYPE_ST75156)
+		return;
+
 	ret = st7571->pdata->init(st7571);
 	if (ret)
 		return;
@@ -458,6 +691,9 @@ static void st7571_encoder_atomic_disable(struct drm_encoder *encoder,
 	struct st7571_device *st7571 = drm_to_st7571(drm);
 	u8 command = ST7571_DISPLAY_OFF;
 
+	if (st7571->device_type == DEVICE_TYPE_ST75156)
+		return;
+
 	st7571_send_command_list(st7571, &command, 1);
 }
 
@@ -672,6 +908,8 @@ static int st7567_parse_dt(struct st7571_device *st7567)
 	st7567->nlines = dt.vactive.typ;
 	st7567->ncols = dt.hactive.typ;
 
+	st7567->device_type = DEVICE_TYPE_ST7567;
+
 	return 0;
 }
 
@@ -710,6 +948,52 @@ static int st7571_parse_dt(struct st7571_device *st7571)
 		return dev_err_probe(dev, PTR_ERR(st7571->reset),
 				     "Failed to get reset gpio\n");
 
+	st7571->device_type = DEVICE_TYPE_ST7571;
+
+	return 0;
+}
+
+static int st75156_parse_dt(struct st7571_device *st75156)
+{
+	struct device *dev = st75156->dev;
+	struct device_node *np = dev->of_node;
+	struct display_timing dt;
+	int ret;
+
+	ret = of_get_display_timing(np, "panel-timing", &dt);
+
+	if (ret) {
+		dev_err(dev, "Failed to get display timing from DT\n");
+		return ret;
+	}
+
+	of_property_read_u32(np, "width-mm", &st75156->width_mm);
+	of_property_read_u32(np, "height-mm", &st75156->height_mm);
+	st75156->inverted = of_property_read_bool(np, "sitronix,inverted");
+
+	st75156->pformat = &st75156_monochrome;
+	st75156->bpp = 1;
+
+	st75156->startline = dt.vback_porch.typ;
+	st75156->startcol = dt.hback_porch.typ;
+
+	st75156->nlines = dt.vactive.typ;
+	st75156->ncols = dt.hactive.typ;
+
+	st75156->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+
+	if (IS_ERR(st75156->reset)) {
+		return dev_err_probe(dev, PTR_ERR(st75156->reset),
+				     "Failed to get reset gpio from DT\n");
+	}
+
+	st75156->device_type = DEVICE_TYPE_ST75156;
+
+	st75156->backlight = devm_of_find_backlight(dev);
+	if (IS_ERR(st75156->backlight))
+		return dev_err_probe(dev, PTR_ERR(st75156->backlight),
+			"Failed to get backlight from DT\n");
+
 	return 0;
 }
 
@@ -720,6 +1004,14 @@ static void st7571_reset(struct st7571_device *st7571)
 	gpiod_set_value_cansleep(st7571->reset, 0);
 }
 
+static void st75156_reset(struct st7571_device *st7571)
+{
+	gpiod_set_value_cansleep(st7571->reset, 0);
+	usleep_range(20, 10000);
+	gpiod_set_value_cansleep(st7571->reset, 1);
+	msleep(120);
+}
+
 static int st7567_lcd_init(struct st7571_device *st7567)
 {
 	/*
@@ -799,8 +1091,86 @@ static int st7571_lcd_init(struct st7571_device *st7571)
 	return st7571_send_command_list(st7571, commands, ARRAY_SIZE(commands));
 }
 
-struct st7571_device *st7571_probe(struct device *dev,
-				   struct regmap *regmap)
+static int st75156_lcd_init(struct st7571_device *st75156)
+{
+	int ret;
+
+	/* Each command in the command list is formatted using the following format:
+	 *
+	 * +---------+--------------------------+-------------+------+-------------+
+	 * | command | number of parameters (n) | parameter 1 | .... | parameter n |
+	 * +---------+--------------------------+-------------+------+-------------+
+	 *
+	 * For example, if a command has 2 parameters it is stored like this:
+	 *
+	 * +---------+------+-------------+--------------+
+	 * | command | 0x02 | parameter 1 |  parameter 2 |
+	 * +---------+------+-------------+--------------+
+	 *
+	 * If a command does not have any parameters it is stored like this:
+	 *
+	 * +---------+------+
+	 * | command | 0x00 |
+	 * +---------+------+
+	 *
+	 * NOTE: The command ST75156_SLEEP_MS (0x00) is a special command defined like this:
+	 *
+	 * +------------------+------+----+
+	 * | ST75156_SLEEP_MS | 0x01 | ms |
+	 * +------------------+------+----+
+	 *
+	 * When this command is sent, the code will sleep for ms milliseconds before
+	 * sending the next command in the command list.
+	 */
+
+	u8 commands[] = {
+		ST75156_EXTENSION_COMMAND_2, 0x00,
+		ST75156_SET_AUTO_READ_INSTRUCTION, 0x01, ST75156_DISABLE_AUTO_READ,
+		ST75156_OTP_WR_RD_CONTROL, 0x01, 0x10,
+		ST75156_SLEEP_MS, 0x01, 0x0a,
+		ST75156_OTP_UPLOAD, 0x00,
+		ST75156_SLEEP_MS, 0x01, 0x14,
+		ST75156_OTP_CONTROL_OUT, 0x00,
+		ST75156_EXTENSION_COMMAND_1, 0x00,
+		ST75156_SLEEP_OUT, 0x00,
+		ST75156_DISPLAY_OFF, 0x00,
+		ST75156_SLEEP_MS, 0x01, 0x32,
+		ST75156_POWER_CONTROL, 0x01, 0x0b,
+		ST75156_SLEEP_MS, 0x01, 0x64,
+		ST75156_SET_VOP, 0x02, 0x06, 0x03,
+		ST75156_EXTENSION_COMMAND_2, 0x00,
+		ST75156_ANALOG_CIRCUIT_SET, 0x03, 0x00, 0x01, 0x05,
+		ST75156_BOOSTER_LEVEL_X10, 0x01, 0xfb,
+		ST75156_EXTENSION_COMMAND_1, 0x00,
+		ST75156_DISPLAY_MODE, 0x01, 0x10,
+		ST75156_DISPLAY_CONTROL, 0x03, 0x00, 0x3f, 0x00,
+		ST75156_DATA_SCAN_DIRECTION, 0x01, 0x02,
+		ST75156_NORMAL_DISPLAY, 0x00,
+		ST75156_EXTENSION_COMMAND_2, 0x00,
+		ST75156_INTERAL_POWER_SUPPLY, 0x00,
+		ST75156_EXTENSION_COMMAND_1, 0x00,
+		ST75156_COLUMN_ADDRESS_SETTING, 0x02, 0x00, 0xef,
+		ST75156_PAGE_ADDRESS_SETTING, 0x02, 0x00, 0x07,
+		ST75156_DISPLAY_ON, 0x00,
+	};
+
+	/* Perform a reset before initializing the controller */
+	st75156_reset(st75156);
+
+	ret = st75156_send_command_list(st75156, commands, ARRAY_SIZE(commands));
+	if (ret < 0)
+		return ret;
+
+	/* Clear the screen after the controller has been initialized */
+	st75156_fb_clear_screen(st75156);
+
+	/* Enable backlight */
+	backlight_enable(st75156->backlight);
+
+	return 0;
+}
+
+struct st7571_device *st7571_probe(struct device *dev, struct regmap *regmap)
 {
 	struct st7571_device *st7571;
 	struct drm_device *drm;
@@ -832,12 +1202,22 @@ struct st7571_device *st7571_probe(struct device *dev,
 	if (!st7571->hwbuf)
 		return ERR_PTR(-ENOMEM);
 
+	memset(st7571->hwbuf, 0,  (st7571->nlines * st7571->ncols * st7571->bpp) / 8);
+
 	st7571->row = devm_kzalloc(st7571->dev,
 				   (st7571->ncols * st7571->bpp),
 				   GFP_KERNEL);
 	if (!st7571->row)
 		return ERR_PTR(-ENOMEM);
 
+	memset(st7571->row, 0, (st7571->ncols * st7571->bpp));
+
+	st7571->commandbuf = devm_kzalloc(st7571->dev,
+					  ST75156_COMMAND_BUFFER_LEN,
+							GFP_KERNEL);
+	if (!st7571->commandbuf)
+		return ERR_PTR(-ENOMEM);
+
 	ret = st7571_mode_config_init(st7571);
 	if (ret) {
 		dev_err(st7571->dev, "Failed to initialize mode config\n");
@@ -868,6 +1248,18 @@ struct st7571_device *st7571_probe(struct device *dev,
 		return ERR_PTR(ret);
 	}
 
+	if (st7571->device_type == DEVICE_TYPE_ST75156) {
+		/* ST75156 needs init function to be run earlier than ST7571
+		 * so we need to call it in the probe function instead of
+		 * the encoder_atomic_enable function.
+		 */
+		ret = st7571->pdata->init(st7571);
+		if (ret) {
+			dev_err(st7571->dev, "Failed to init display\n");
+			return ERR_PTR(ret);
+		}
+	}
+
 	drm_mode_config_reset(drm);
 
 	ret = drm_dev_register(drm, 0);
@@ -890,6 +1282,7 @@ EXPORT_SYMBOL_GPL(st7571_remove);
 const struct st7571_panel_data st7567_config = {
 	.init = st7567_lcd_init,
 	.parse_dt = st7567_parse_dt,
+	.fb_clear_screen = st7571_fb_clear_screen,
 	.constraints = {
 		.min_nlines = 1,
 		.max_nlines = 64,
@@ -903,6 +1296,7 @@ EXPORT_SYMBOL_NS_GPL(st7567_config, "DRM_ST7571");
 const struct st7571_panel_data st7571_config = {
 	.init = st7571_lcd_init,
 	.parse_dt = st7571_parse_dt,
+	.fb_clear_screen = st7571_fb_clear_screen,
 	.constraints = {
 		.min_nlines = 1,
 		.max_nlines = 128,
@@ -913,6 +1307,20 @@ const struct st7571_panel_data st7571_config = {
 };
 EXPORT_SYMBOL_NS_GPL(st7571_config, "DRM_ST7571");
 
+const struct st7571_panel_data st75156_config = {
+	.init = st75156_lcd_init,
+	.parse_dt = st75156_parse_dt,
+	.fb_clear_screen = st75156_fb_clear_screen,
+	.constraints = {
+		.min_nlines = 1,
+		.max_nlines = 64,
+		.min_ncols = 240,
+		.max_ncols = 240,
+		.support_grayscale = false,
+	},
+};
+EXPORT_SYMBOL_NS_GPL(st75156_config, "DRM_ST7571");
+
 MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>");
 MODULE_DESCRIPTION("DRM Driver for Sitronix ST7571 LCD controller");
 MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sitronix/st7571.h b/drivers/gpu/drm/sitronix/st7571.h
index af264f2e2ea4..6e3e70f12c2d 100644
--- a/drivers/gpu/drm/sitronix/st7571.h
+++ b/drivers/gpu/drm/sitronix/st7571.h
@@ -17,11 +17,20 @@
 
 #include <linux/regmap.h>
 
+#define ST7571_COMMAND_MODE			(0x00)
+#define ST7571_DATA_MODE			(0x40)
+
 enum st7571_color_mode {
 	ST7571_COLOR_MODE_GRAY = 0,
 	ST7571_COLOR_MODE_BLACKWHITE = 1,
 };
 
+enum DeviceType {
+	DEVICE_TYPE_ST7567,
+	DEVICE_TYPE_ST7571,
+	DEVICE_TYPE_ST75156
+};
+
 struct st7571_device;
 
 struct st7571_panel_constraints {
@@ -35,6 +44,7 @@ struct st7571_panel_constraints {
 struct st7571_panel_data {
 	int (*init)(struct st7571_device *st7571);
 	int (*parse_dt)(struct st7571_device *st7571);
+	int (*fb_clear_screen)(struct st7571_device *st7571);
 	struct st7571_panel_constraints constraints;
 };
 
@@ -65,12 +75,15 @@ struct st7571_device {
 	const struct st7571_panel_data *pdata;
 	struct gpio_desc *reset;
 	struct regmap *regmap;
+	struct spi_device_data *spi_data;
+	struct backlight_device *backlight;
 
 	bool grayscale;
 	bool inverted;
 	u32 height_mm;
 	u32 width_mm;
 	u32 startline;
+	u32 startcol;
 	u32 nlines;
 	u32 ncols;
 	u32 bpp;
@@ -80,10 +93,17 @@ struct st7571_device {
 
 	/* Row of (transformed) pixels ready to be written to the display */
 	u8 *row;
+
+	/* Buffer containing current command and parameters */
+	u8 *commandbuf;
+
+	/* Type of device */
+	enum DeviceType device_type;
 };
 
 extern const struct st7571_panel_data st7567_config;
 extern const struct st7571_panel_data st7571_config;
+extern const struct st7571_panel_data st75156_config;
 
 struct st7571_device *st7571_probe(struct device *dev, struct regmap *regmap);
 void st7571_remove(struct st7571_device *st7571);

-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 2/2] dt-bindings: display: sitronix,st75156: add example for SPI
  2026-10-22 15:31 [PATCH 0/2] Add support for st75156 in st7571 display driver Lars Rask
  2026-10-22 15:31 ` [PATCH 1/2] drm/sitronix/st7571-spi: Add support for ST75156 controller Lars Rask
@ 2026-10-22 15:31 ` Lars Rask
  1 sibling, 0 replies; 3+ messages in thread
From: Lars Rask @ 2026-10-22 15:31 UTC (permalink / raw)
  To: Marcus Folkesson, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: dri-devel, linux-kernel, devicetree, kernel, Lars Rask

From: Lars Rask <lars.rask@axis.com>

Add example for using st75156 with SPI.

Signed-off-by: Lars Rask <lars.rask@axis.com>
---
 .../bindings/display/sitronix,st75156.yaml         | 67 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 68 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/sitronix,st75156.yaml b/Documentation/devicetree/bindings/display/sitronix,st75156.yaml
new file mode 100644
index 000000000000..a3897ddc5e38
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sitronix,st75156.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/sitronix,st75156.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sitronix ST75156 Display Controller
+
+maintainers:
+  - Marcus Folkesson <marcus.folkesson@gmail.com>
+
+description:
+  Sitronix ST75156 is a monochrome dot matrix LCD panel used in SPI 4-wire mode.
+
+allOf:
+  - $ref: panel/panel-common.yaml#
+
+properties:
+  compatible:
+    const: sitronix,st75156
+
+  reg:
+    maxItems: 1
+
+  reset-gpios: true
+  width-mm: true
+  height-mm: true
+  panel-timing: true
+
+required:
+  - compatible
+  - reg
+  - reset-gpios
+  - width-mm
+  - height-mm
+  - panel-timing
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    spi {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      display@0 {
+        compatible = "sitronix,st75156";
+        reg = <0>;
+        reset-gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
+        width-mm = <37>;
+        height-mm = <27>;
+
+        panel-timing {
+          hactive = <128>;
+          vactive = <96>;
+          hback-porch = <0>;
+          vback-porch = <0>;
+          clock-frequency = <0>;
+          hfront-porch = <0>;
+          hsync-len = <0>;
+          vfront-porch = <0>;
+          vsync-len = <0>;
+        };
+      };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f8253e..9219916d8876 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8432,6 +8432,7 @@ F:	drivers/gpu/drm/sitronix/st7586.c
 DRM DRIVER FOR SITRONIX ST7571 PANELS
 M:	Marcus Folkesson <marcus.folkesson@gmail.com>
 S:	Maintained
+F:	Documentation/devicetree/bindings/display/sitronix,st75156.yaml
 F:	Documentation/devicetree/bindings/display/sitronix,st7567.yaml
 F:	Documentation/devicetree/bindings/display/sitronix,st7571.yaml
 F:	drivers/gpu/drm/sitronix/st7571-i2c.c

-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-22 15:31 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 15:31 [PATCH 0/2] Add support for st75156 in st7571 display driver Lars Rask
2026-10-22 15:31 ` [PATCH 1/2] drm/sitronix/st7571-spi: Add support for ST75156 controller Lars Rask
2026-10-22 15:31 ` [PATCH 2/2] dt-bindings: display: sitronix,st75156: add example for SPI Lars Rask

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®