Blob Blame History Raw
From a620c11db01d9a33bdb807ebd557ab62db1c2118 Mon Sep 17 00:00:00 2001
From: Linus Walleij <linus.walleij@linaro.org>
Date: Mon, 3 Jan 2022 12:38:20 +0100
Subject: drm/panel: Rename Sony ACX424 to Novatek NT35560
Git-commit: 994ea402c767e54af60f1d01f0c16520480466ed
Patch-mainline: v5.18-rc1
References: jsc#PED-1166 jsc#PED-1168 jsc#PED-1170 jsc#PED-1218 jsc#PED-1220 jsc#PED-1222 jsc#PED-1223 jsc#PED-1225

A code drop from Sony Mobile reveals that the ACX424 panels are
built around the Novatek NT35560 panel controllers so just bite
the bullet and rename the driver and all basic symbols so that
we can modify this driver to cover any other panels also using
the Novatek NT35560 display controller.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20220103113822.654592-1-linus.walleij@linaro.org
Acked-by: Patrik Jakobsson <pjakobsson@suse.de>
---
 drivers/gpu/drm/panel/Kconfig                 |  23 +-
 drivers/gpu/drm/panel/Makefile                |   2 +-
 drivers/gpu/drm/panel/panel-novatek-nt35560.c | 493 ++++++++++++++++++
 drivers/gpu/drm/panel/panel-sony-acx424akp.c  | 490 -----------------
 4 files changed, 506 insertions(+), 502 deletions(-)
 create mode 100644 drivers/gpu/drm/panel/panel-novatek-nt35560.c
 delete mode 100644 drivers/gpu/drm/panel/panel-sony-acx424akp.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 9989a316fe88..ddf5f38e8731 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -294,6 +294,18 @@ config DRM_PANEL_NOVATEK_NT35510
 	  around the Novatek NT35510 display controller, such as some
 	  Hydis panels.
 
+config DRM_PANEL_NOVATEK_NT35560
+	tristate "Novatek NT35560 DSI command mode panel"
+	depends on OF
+	depends on DRM_MIPI_DSI
+	depends on BACKLIGHT_CLASS_DEVICE
+	select VIDEOMODE_HELPERS
+	help
+	  Say Y here if you want to enable the Novatek NT35560 display
+	  controller. This panel supports DSI in both command and video
+	  mode. This supports several panels such as Sony ACX424AKM and
+	  ACX424AKP.
+
 config DRM_PANEL_NOVATEK_NT35950
 	tristate "Novatek NT35950 DSI panel"
 	depends on OF
@@ -594,17 +606,6 @@ config DRM_PANEL_SITRONIX_ST7789V
 	  Say Y here if you want to enable support for the Sitronix
 	  ST7789V controller for 240x320 LCD panels
 
-config DRM_PANEL_SONY_ACX424AKP
-	tristate "Sony ACX424AKP DSI command mode panel"
-	depends on OF
-	depends on DRM_MIPI_DSI
-	depends on BACKLIGHT_CLASS_DEVICE
-	select VIDEOMODE_HELPERS
-	help
-	  Say Y here if you want to enable the Sony ACX424 display
-	  panel. This panel supports DSI in both command and video
-	  mode.
-
 config DRM_PANEL_SONY_ACX565AKM
 	tristate "Sony ACX565AKM panel"
 	depends on GPIOLIB && OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index d99fbbce49d1..5740911f637c 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
 obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35510) += panel-novatek-nt35510.o
+obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35560) += panel-novatek-nt35560.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o
@@ -60,7 +61,6 @@ obj-$(CONFIG_DRM_PANEL_SHARP_LS060T1SX01) += panel-sharp-ls060t1sx01.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7703) += panel-sitronix-st7703.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
-obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o
 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
 obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.o
 obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o
diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35560.c b/drivers/gpu/drm/panel/panel-novatek-nt35560.c
new file mode 100644
index 000000000000..620876225384
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt35560.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MIPI-DSI Novatek NT35560-based panel controller.
+ *
+ * Supported panels include:
+ * Sony ACX424AKM - a 480x854 AMOLED DSI panel
+ * Sony ACX424AKP - a 480x864 AMOLED DSI panel
+ *
+ * Copyright (C) Linaro Ltd. 2019-2021
+ * Author: Linus Walleij
+ * Based on code and know-how from Marcus Lorentzon
+ * Copyright (C) ST-Ericsson SA 2010
+ */
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define NT35560_DCS_READ_ID1		0xDA
+#define NT35560_DCS_READ_ID2		0xDB
+#define NT35560_DCS_READ_ID3		0xDC
+#define NT35560_DCS_SET_MDDI		0xAE
+
+/*
+ * Sony seems to use vendor ID 0x81
+ */
+#define DISPLAY_SONY_ACX424AKP_ID1	0x811b
+#define DISPLAY_SONY_ACX424AKP_ID2	0x811a
+/*
+ * The third ID looks like a bug, vendor IDs begin at 0x80
+ * and panel 00 ... seems like default values.
+ */
+#define DISPLAY_SONY_ACX424AKP_ID3	0x8000
+
+struct nt35560 {
+	struct drm_panel panel;
+	struct device *dev;
+	struct regulator *supply;
+	struct gpio_desc *reset_gpio;
+	bool video_mode;
+};
+
+static const struct drm_display_mode sony_acx424akp_vid_mode = {
+	.clock = 27234,
+	.hdisplay = 480,
+	.hsync_start = 480 + 15,
+	.hsync_end = 480 + 15 + 0,
+	.htotal = 480 + 15 + 0 + 15,
+	.vdisplay = 864,
+	.vsync_start = 864 + 14,
+	.vsync_end = 864 + 14 + 1,
+	.vtotal = 864 + 14 + 1 + 11,
+	.width_mm = 48,
+	.height_mm = 84,
+	.flags = DRM_MODE_FLAG_PVSYNC,
+};
+
+/*
+ * The timings are not very helpful as the display is used in
+ * command mode using the maximum HS frequency.
+ */
+static const struct drm_display_mode sony_acx424akp_cmd_mode = {
+	.clock = 35478,
+	.hdisplay = 480,
+	.hsync_start = 480 + 154,
+	.hsync_end = 480 + 154 + 16,
+	.htotal = 480 + 154 + 16 + 32,
+	.vdisplay = 864,
+	.vsync_start = 864 + 1,
+	.vsync_end = 864 + 1 + 1,
+	.vtotal = 864 + 1 + 1 + 1,
+	/*
+	 * Some desired refresh rate, experiments at the maximum "pixel"
+	 * clock speed (HS clock 420 MHz) yields around 117Hz.
+	 */
+	.width_mm = 48,
+	.height_mm = 84,
+};
+
+static inline struct nt35560 *panel_to_nt35560(struct drm_panel *panel)
+{
+	return container_of(panel, struct nt35560, panel);
+}
+
+#define FOSC			20 /* 20Mhz */
+#define SCALE_FACTOR_NS_DIV_MHZ	1000
+
+static int nt35560_set_brightness(struct backlight_device *bl)
+{
+	struct nt35560 *nt = bl_get_data(bl);
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev);
+	int period_ns = 1023;
+	int duty_ns = bl->props.brightness;
+	u8 pwm_ratio;
+	u8 pwm_div;
+	u8 par;
+	int ret;
+
+	if (backlight_is_blank(bl)) {
+		/* Disable backlight */
+		par = 0x00;
+		ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+					 &par, 1);
+		if (ret) {
+			dev_err(nt->dev, "failed to disable display backlight (%d)\n", ret);
+			return ret;
+		}
+		return 0;
+	}
+
+	/* Calculate the PWM duty cycle in n/256's */
+	pwm_ratio = max(((duty_ns * 256) / period_ns) - 1, 1);
+	pwm_div = max(1,
+		      ((FOSC * period_ns) / 256) /
+		      SCALE_FACTOR_NS_DIV_MHZ);
+
+	/* Set up PWM dutycycle ONE byte (differs from the standard) */
+	dev_dbg(nt->dev, "calculated duty cycle %02x\n", pwm_ratio);
+	ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+				 &pwm_ratio, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to set display PWM ratio (%d)\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Sequence to write PWMDIV:
+	 *	address		data
+	 *	0xF3		0xAA   CMD2 Unlock
+	 *	0x00		0x01   Enter CMD2 page 0
+	 *	0X7D		0x01   No reload MTP of CMD2 P1
+	 *	0x22		PWMDIV
+	 *	0x7F		0xAA   CMD2 page 1 lock
+	 */
+	par = 0xaa;
+	ret = mipi_dsi_dcs_write(dsi, 0xf3, &par, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to unlock CMD 2 (%d)\n", ret);
+		return ret;
+	}
+	par = 0x01;
+	ret = mipi_dsi_dcs_write(dsi, 0x00, &par, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to enter page 1 (%d)\n", ret);
+		return ret;
+	}
+	par = 0x01;
+	ret = mipi_dsi_dcs_write(dsi, 0x7d, &par, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to disable MTP reload (%d)\n", ret);
+		return ret;
+	}
+	ret = mipi_dsi_dcs_write(dsi, 0x22, &pwm_div, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to set PWM divisor (%d)\n", ret);
+		return ret;
+	}
+	par = 0xaa;
+	ret = mipi_dsi_dcs_write(dsi, 0x7f, &par, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to lock CMD 2 (%d)\n", ret);
+		return ret;
+	}
+
+	/* Enable backlight */
+	par = 0x24;
+	ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+				 &par, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to enable display backlight (%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct backlight_ops nt35560_bl_ops = {
+	.update_status = nt35560_set_brightness,
+};
+
+static const struct backlight_properties nt35560_bl_props = {
+	.type = BACKLIGHT_RAW,
+	.brightness = 512,
+	.max_brightness = 1023,
+};
+
+static int nt35560_read_id(struct nt35560 *nt)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev);
+	u8 vendor, version, panel;
+	u16 val;
+	int ret;
+
+	ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID1, &vendor, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "could not vendor ID byte\n");
+		return ret;
+	}
+	ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID2, &version, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "could not read device version byte\n");
+		return ret;
+	}
+	ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID3, &panel, 1);
+	if (ret < 0) {
+		dev_err(nt->dev, "could not read panel ID byte\n");
+		return ret;
+	}
+
+	if (vendor == 0x00) {
+		dev_err(nt->dev, "device vendor ID is zero\n");
+		return -ENODEV;
+	}
+
+	val = (vendor << 8) | panel;
+	switch (val) {
+	case DISPLAY_SONY_ACX424AKP_ID1:
+	case DISPLAY_SONY_ACX424AKP_ID2:
+	case DISPLAY_SONY_ACX424AKP_ID3:
+		dev_info(nt->dev, "MTP vendor: %02x, version: %02x, panel: %02x\n",
+			 vendor, version, panel);
+		break;
+	default:
+		dev_info(nt->dev, "unknown vendor: %02x, version: %02x, panel: %02x\n",
+			 vendor, version, panel);
+		break;
+	}
+
+	return 0;
+}
+
+static int nt35560_power_on(struct nt35560 *nt)
+{
+	int ret;
+
+	ret = regulator_enable(nt->supply);
+	if (ret) {
+		dev_err(nt->dev, "failed to enable supply (%d)\n", ret);
+		return ret;
+	}
+
+	/* Assert RESET */
+	gpiod_set_value_cansleep(nt->reset_gpio, 1);
+	udelay(20);
+	/* De-assert RESET */
+	gpiod_set_value_cansleep(nt->reset_gpio, 0);
+	usleep_range(11000, 20000);
+
+	return 0;
+}
+
+static void nt35560_power_off(struct nt35560 *nt)
+{
+	/* Assert RESET */
+	gpiod_set_value_cansleep(nt->reset_gpio, 1);
+	usleep_range(11000, 20000);
+
+	regulator_disable(nt->supply);
+}
+
+static int nt35560_prepare(struct drm_panel *panel)
+{
+	struct nt35560 *nt = panel_to_nt35560(panel);
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev);
+	const u8 mddi = 3;
+	int ret;
+
+	ret = nt35560_power_on(nt);
+	if (ret)
+		return ret;
+
+	ret = nt35560_read_id(nt);
+	if (ret) {
+		dev_err(nt->dev, "failed to read panel ID (%d)\n", ret);
+		goto err_power_off;
+	}
+
+	/* Enabe tearing mode: send TE (tearing effect) at VBLANK */
+	ret = mipi_dsi_dcs_set_tear_on(dsi,
+				       MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+	if (ret) {
+		dev_err(nt->dev, "failed to enable vblank TE (%d)\n", ret);
+		goto err_power_off;
+	}
+
+	/*
+	 * Set MDDI
+	 *
+	 * This presumably deactivates the Qualcomm MDDI interface and
+	 * selects DSI, similar code is found in other drivers such as the
+	 * Sharp LS043T1LE01 which makes us suspect that this panel may be
+	 * using a Novatek NT35565 or similar display driver chip that shares
+	 * this command. Due to the lack of documentation we cannot know for
+	 * sure.
+	 */
+	ret = mipi_dsi_dcs_write(dsi, NT35560_DCS_SET_MDDI,
+				 &mddi, sizeof(mddi));
+	if (ret < 0) {
+		dev_err(nt->dev, "failed to set MDDI (%d)\n", ret);
+		goto err_power_off;
+	}
+
+	/* Exit sleep mode */
+	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+	if (ret) {
+		dev_err(nt->dev, "failed to exit sleep mode (%d)\n", ret);
+		goto err_power_off;
+	}
+	msleep(140);
+
+	ret = mipi_dsi_dcs_set_display_on(dsi);
+	if (ret) {
+		dev_err(nt->dev, "failed to turn display on (%d)\n", ret);
+		goto err_power_off;
+	}
+	if (nt->video_mode) {
+		/* In video mode turn peripheral on */
+		ret = mipi_dsi_turn_on_peripheral(dsi);
+		if (ret) {
+			dev_err(nt->dev, "failed to turn on peripheral\n");
+			goto err_power_off;
+		}
+	}
+
+	return 0;
+
+err_power_off:
+	nt35560_power_off(nt);
+	return ret;
+}
+
+static int nt35560_unprepare(struct drm_panel *panel)
+{
+	struct nt35560 *nt = panel_to_nt35560(panel);
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev);
+	int ret;
+
+	ret = mipi_dsi_dcs_set_display_off(dsi);
+	if (ret) {
+		dev_err(nt->dev, "failed to turn display off (%d)\n", ret);
+		return ret;
+	}
+
+	/* Enter sleep mode */
+	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+	if (ret) {
+		dev_err(nt->dev, "failed to enter sleep mode (%d)\n", ret);
+		return ret;
+	}
+	msleep(85);
+
+	nt35560_power_off(nt);
+
+	return 0;
+}
+
+
+static int nt35560_get_modes(struct drm_panel *panel,
+			     struct drm_connector *connector)
+{
+	struct nt35560 *nt = panel_to_nt35560(panel);
+	struct drm_display_mode *mode;
+
+	if (nt->video_mode)
+		mode = drm_mode_duplicate(connector->dev,
+					  &sony_acx424akp_vid_mode);
+	else
+		mode = drm_mode_duplicate(connector->dev,
+					  &sony_acx424akp_cmd_mode);
+	if (!mode) {
+		dev_err(panel->dev, "bad mode or failed to add mode\n");
+		return -EINVAL;
+	}
+	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; /* Number of modes */
+}
+
+static const struct drm_panel_funcs nt35560_drm_funcs = {
+	.unprepare = nt35560_unprepare,
+	.prepare = nt35560_prepare,
+	.get_modes = nt35560_get_modes,
+};
+
+static int nt35560_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct nt35560 *nt;
+	int ret;
+
+	nt = devm_kzalloc(dev, sizeof(struct nt35560), GFP_KERNEL);
+	if (!nt)
+		return -ENOMEM;
+	nt->video_mode = of_property_read_bool(dev->of_node,
+						"enforce-video-mode");
+
+	mipi_dsi_set_drvdata(dsi, nt);
+	nt->dev = dev;
+
+	dsi->lanes = 2;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	/*
+	 * FIXME: these come from the ST-Ericsson vendor driver for the
+	 * HREF520 and seems to reflect limitations in the PLLs on that
+	 * platform, if you have the datasheet, please cross-check the
+	 * actual max rates.
+	 */
+	dsi->lp_rate = 19200000;
+	dsi->hs_rate = 420160000;
+
+	if (nt->video_mode)
+		/* Burst mode using event for sync */
+		dsi->mode_flags =
+			MIPI_DSI_MODE_VIDEO |
+			MIPI_DSI_MODE_VIDEO_BURST;
+	else
+		dsi->mode_flags =
+			MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+	nt->supply = devm_regulator_get(dev, "vddi");
+	if (IS_ERR(nt->supply))
+		return PTR_ERR(nt->supply);
+
+	/* This asserts RESET by default */
+	nt->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						 GPIOD_OUT_HIGH);
+	if (IS_ERR(nt->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(nt->reset_gpio),
+				     "failed to request GPIO\n");
+
+	drm_panel_init(&nt->panel, dev, &nt35560_drm_funcs,
+		       DRM_MODE_CONNECTOR_DSI);
+
+	nt->panel.backlight = devm_backlight_device_register(dev, "nt35560", dev, nt,
+					&nt35560_bl_ops, &nt35560_bl_props);
+	if (IS_ERR(nt->panel.backlight))
+		return dev_err_probe(dev, PTR_ERR(nt->panel.backlight),
+				     "failed to register backlight device\n");
+
+	drm_panel_add(&nt->panel);
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret < 0) {
+		drm_panel_remove(&nt->panel);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int nt35560_remove(struct mipi_dsi_device *dsi)
+{
+	struct nt35560 *nt = mipi_dsi_get_drvdata(dsi);
+
+	mipi_dsi_detach(dsi);
+	drm_panel_remove(&nt->panel);
+
+	return 0;
+}
+
+static const struct of_device_id nt35560_of_match[] = {
+	{ .compatible = "sony,acx424akp" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, nt35560_of_match);
+
+static struct mipi_dsi_driver nt35560_driver = {
+	.probe = nt35560_probe,
+	.remove = nt35560_remove,
+	.driver = {
+		.name = "panel-novatek-nt35560",
+		.of_match_table = nt35560_of_match,
+	},
+};
+module_mipi_dsi_driver(nt35560_driver);
+
+MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("MIPI-DSI Novatek NT35560 Panel Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-sony-acx424akp.c b/drivers/gpu/drm/panel/panel-sony-acx424akp.c
deleted file mode 100644
index 9536d56a94a5..000000000000
--- a/drivers/gpu/drm/panel/panel-sony-acx424akp.c
+++ /dev/null
@@ -1,490 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * MIPI-DSI Sony ACX424AKP panel driver. This is a 480x864
- * AMOLED panel with a command-only DSI interface.
- *
- * Copyright (C) Linaro Ltd. 2019
- * Author: Linus Walleij
- * Based on code and know-how from Marcus Lorentzon
- * Copyright (C) ST-Ericsson SA 2010
- */
-#include <linux/backlight.h>
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/regulator/consumer.h>
-
-#include <video/mipi_display.h>
-
-#include <drm/drm_mipi_dsi.h>
-#include <drm/drm_modes.h>
-#include <drm/drm_panel.h>
-
-#define ACX424_DCS_READ_ID1		0xDA
-#define ACX424_DCS_READ_ID2		0xDB
-#define ACX424_DCS_READ_ID3		0xDC
-#define ACX424_DCS_SET_MDDI		0xAE
-
-/*
- * Sony seems to use vendor ID 0x81
- */
-#define DISPLAY_SONY_ACX424AKP_ID1	0x811b
-#define DISPLAY_SONY_ACX424AKP_ID2	0x811a
-/*
- * The third ID looks like a bug, vendor IDs begin at 0x80
- * and panel 00 ... seems like default values.
- */
-#define DISPLAY_SONY_ACX424AKP_ID3	0x8000
-
-struct acx424akp {
-	struct drm_panel panel;
-	struct device *dev;
-	struct regulator *supply;
-	struct gpio_desc *reset_gpio;
-	bool video_mode;
-};
-
-static const struct drm_display_mode sony_acx424akp_vid_mode = {
-	.clock = 27234,
-	.hdisplay = 480,
-	.hsync_start = 480 + 15,
-	.hsync_end = 480 + 15 + 0,
-	.htotal = 480 + 15 + 0 + 15,
-	.vdisplay = 864,
-	.vsync_start = 864 + 14,
-	.vsync_end = 864 + 14 + 1,
-	.vtotal = 864 + 14 + 1 + 11,
-	.width_mm = 48,
-	.height_mm = 84,
-	.flags = DRM_MODE_FLAG_PVSYNC,
-};
-
-/*
- * The timings are not very helpful as the display is used in
- * command mode using the maximum HS frequency.
- */
-static const struct drm_display_mode sony_acx424akp_cmd_mode = {
-	.clock = 35478,
-	.hdisplay = 480,
-	.hsync_start = 480 + 154,
-	.hsync_end = 480 + 154 + 16,
-	.htotal = 480 + 154 + 16 + 32,
-	.vdisplay = 864,
-	.vsync_start = 864 + 1,
-	.vsync_end = 864 + 1 + 1,
-	.vtotal = 864 + 1 + 1 + 1,
-	/*
-	 * Some desired refresh rate, experiments at the maximum "pixel"
-	 * clock speed (HS clock 420 MHz) yields around 117Hz.
-	 */
-	.width_mm = 48,
-	.height_mm = 84,
-};
-
-static inline struct acx424akp *panel_to_acx424akp(struct drm_panel *panel)
-{
-	return container_of(panel, struct acx424akp, panel);
-}
-
-#define FOSC			20 /* 20Mhz */
-#define SCALE_FACTOR_NS_DIV_MHZ	1000
-
-static int acx424akp_set_brightness(struct backlight_device *bl)
-{
-	struct acx424akp *acx = bl_get_data(bl);
-	struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev);
-	int period_ns = 1023;
-	int duty_ns = bl->props.brightness;
-	u8 pwm_ratio;
-	u8 pwm_div;
-	u8 par;
-	int ret;
-
-	if (backlight_is_blank(bl)) {
-		/* Disable backlight */
-		par = 0x00;
-		ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
-					 &par, 1);
-		if (ret) {
-			dev_err(acx->dev, "failed to disable display backlight (%d)\n", ret);
-			return ret;
-		}
-		return 0;
-	}
-
-	/* Calculate the PWM duty cycle in n/256's */
-	pwm_ratio = max(((duty_ns * 256) / period_ns) - 1, 1);
-	pwm_div = max(1,
-		      ((FOSC * period_ns) / 256) /
-		      SCALE_FACTOR_NS_DIV_MHZ);
-
-	/* Set up PWM dutycycle ONE byte (differs from the standard) */
-	dev_dbg(acx->dev, "calculated duty cycle %02x\n", pwm_ratio);
-	ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
-				 &pwm_ratio, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to set display PWM ratio (%d)\n", ret);
-		return ret;
-	}
-
-	/*
-	 * Sequence to write PWMDIV:
-	 *	address		data
-	 *	0xF3		0xAA   CMD2 Unlock
-	 *	0x00		0x01   Enter CMD2 page 0
-	 *	0X7D		0x01   No reload MTP of CMD2 P1
-	 *	0x22		PWMDIV
-	 *	0x7F		0xAA   CMD2 page 1 lock
-	 */
-	par = 0xaa;
-	ret = mipi_dsi_dcs_write(dsi, 0xf3, &par, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to unlock CMD 2 (%d)\n", ret);
-		return ret;
-	}
-	par = 0x01;
-	ret = mipi_dsi_dcs_write(dsi, 0x00, &par, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to enter page 1 (%d)\n", ret);
-		return ret;
-	}
-	par = 0x01;
-	ret = mipi_dsi_dcs_write(dsi, 0x7d, &par, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to disable MTP reload (%d)\n", ret);
-		return ret;
-	}
-	ret = mipi_dsi_dcs_write(dsi, 0x22, &pwm_div, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to set PWM divisor (%d)\n", ret);
-		return ret;
-	}
-	par = 0xaa;
-	ret = mipi_dsi_dcs_write(dsi, 0x7f, &par, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to lock CMD 2 (%d)\n", ret);
-		return ret;
-	}
-
-	/* Enable backlight */
-	par = 0x24;
-	ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
-				 &par, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to enable display backlight (%d)\n", ret);
-		return ret;
-	}
-
-	return 0;
-}
-
-static const struct backlight_ops acx424akp_bl_ops = {
-	.update_status = acx424akp_set_brightness,
-};
-
-static const struct backlight_properties acx424akp_bl_props = {
-	.type = BACKLIGHT_RAW,
-	.brightness = 512,
-	.max_brightness = 1023,
-};
-
-static int acx424akp_read_id(struct acx424akp *acx)
-{
-	struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev);
-	u8 vendor, version, panel;
-	u16 val;
-	int ret;
-
-	ret = mipi_dsi_dcs_read(dsi, ACX424_DCS_READ_ID1, &vendor, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "could not vendor ID byte\n");
-		return ret;
-	}
-	ret = mipi_dsi_dcs_read(dsi, ACX424_DCS_READ_ID2, &version, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "could not read device version byte\n");
-		return ret;
-	}
-	ret = mipi_dsi_dcs_read(dsi, ACX424_DCS_READ_ID3, &panel, 1);
-	if (ret < 0) {
-		dev_err(acx->dev, "could not read panel ID byte\n");
-		return ret;
-	}
-
-	if (vendor == 0x00) {
-		dev_err(acx->dev, "device vendor ID is zero\n");
-		return -ENODEV;
-	}
-
-	val = (vendor << 8) | panel;
-	switch (val) {
-	case DISPLAY_SONY_ACX424AKP_ID1:
-	case DISPLAY_SONY_ACX424AKP_ID2:
-	case DISPLAY_SONY_ACX424AKP_ID3:
-		dev_info(acx->dev, "MTP vendor: %02x, version: %02x, panel: %02x\n",
-			 vendor, version, panel);
-		break;
-	default:
-		dev_info(acx->dev, "unknown vendor: %02x, version: %02x, panel: %02x\n",
-			 vendor, version, panel);
-		break;
-	}
-
-	return 0;
-}
-
-static int acx424akp_power_on(struct acx424akp *acx)
-{
-	int ret;
-
-	ret = regulator_enable(acx->supply);
-	if (ret) {
-		dev_err(acx->dev, "failed to enable supply (%d)\n", ret);
-		return ret;
-	}
-
-	/* Assert RESET */
-	gpiod_set_value_cansleep(acx->reset_gpio, 1);
-	udelay(20);
-	/* De-assert RESET */
-	gpiod_set_value_cansleep(acx->reset_gpio, 0);
-	usleep_range(11000, 20000);
-
-	return 0;
-}
-
-static void acx424akp_power_off(struct acx424akp *acx)
-{
-	/* Assert RESET */
-	gpiod_set_value_cansleep(acx->reset_gpio, 1);
-	usleep_range(11000, 20000);
-
-	regulator_disable(acx->supply);
-}
-
-static int acx424akp_prepare(struct drm_panel *panel)
-{
-	struct acx424akp *acx = panel_to_acx424akp(panel);
-	struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev);
-	const u8 mddi = 3;
-	int ret;
-
-	ret = acx424akp_power_on(acx);
-	if (ret)
-		return ret;
-
-	ret = acx424akp_read_id(acx);
-	if (ret) {
-		dev_err(acx->dev, "failed to read panel ID (%d)\n", ret);
-		goto err_power_off;
-	}
-
-	/* Enabe tearing mode: send TE (tearing effect) at VBLANK */
-	ret = mipi_dsi_dcs_set_tear_on(dsi,
-				       MIPI_DSI_DCS_TEAR_MODE_VBLANK);
-	if (ret) {
-		dev_err(acx->dev, "failed to enable vblank TE (%d)\n", ret);
-		goto err_power_off;
-	}
-
-	/*
-	 * Set MDDI
-	 *
-	 * This presumably deactivates the Qualcomm MDDI interface and
-	 * selects DSI, similar code is found in other drivers such as the
-	 * Sharp LS043T1LE01 which makes us suspect that this panel may be
-	 * using a Novatek NT35565 or similar display driver chip that shares
-	 * this command. Due to the lack of documentation we cannot know for
-	 * sure.
-	 */
-	ret = mipi_dsi_dcs_write(dsi, ACX424_DCS_SET_MDDI,
-				 &mddi, sizeof(mddi));
-	if (ret < 0) {
-		dev_err(acx->dev, "failed to set MDDI (%d)\n", ret);
-		goto err_power_off;
-	}
-
-	/* Exit sleep mode */
-	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
-	if (ret) {
-		dev_err(acx->dev, "failed to exit sleep mode (%d)\n", ret);
-		goto err_power_off;
-	}
-	msleep(140);
-
-	ret = mipi_dsi_dcs_set_display_on(dsi);
-	if (ret) {
-		dev_err(acx->dev, "failed to turn display on (%d)\n", ret);
-		goto err_power_off;
-	}
-	if (acx->video_mode) {
-		/* In video mode turn peripheral on */
-		ret = mipi_dsi_turn_on_peripheral(dsi);
-		if (ret) {
-			dev_err(acx->dev, "failed to turn on peripheral\n");
-			goto err_power_off;
-		}
-	}
-
-	return 0;
-
-err_power_off:
-	acx424akp_power_off(acx);
-	return ret;
-}
-
-static int acx424akp_unprepare(struct drm_panel *panel)
-{
-	struct acx424akp *acx = panel_to_acx424akp(panel);
-	struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev);
-	int ret;
-
-	ret = mipi_dsi_dcs_set_display_off(dsi);
-	if (ret) {
-		dev_err(acx->dev, "failed to turn display off (%d)\n", ret);
-		return ret;
-	}
-
-	/* Enter sleep mode */
-	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
-	if (ret) {
-		dev_err(acx->dev, "failed to enter sleep mode (%d)\n", ret);
-		return ret;
-	}
-	msleep(85);
-
-	acx424akp_power_off(acx);
-
-	return 0;
-}
-
-
-static int acx424akp_get_modes(struct drm_panel *panel,
-			       struct drm_connector *connector)
-{
-	struct acx424akp *acx = panel_to_acx424akp(panel);
-	struct drm_display_mode *mode;
-
-	if (acx->video_mode)
-		mode = drm_mode_duplicate(connector->dev,
-					  &sony_acx424akp_vid_mode);
-	else
-		mode = drm_mode_duplicate(connector->dev,
-					  &sony_acx424akp_cmd_mode);
-	if (!mode) {
-		dev_err(panel->dev, "bad mode or failed to add mode\n");
-		return -EINVAL;
-	}
-	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; /* Number of modes */
-}
-
-static const struct drm_panel_funcs acx424akp_drm_funcs = {
-	.unprepare = acx424akp_unprepare,
-	.prepare = acx424akp_prepare,
-	.get_modes = acx424akp_get_modes,
-};
-
-static int acx424akp_probe(struct mipi_dsi_device *dsi)
-{
-	struct device *dev = &dsi->dev;
-	struct acx424akp *acx;
-	int ret;
-
-	acx = devm_kzalloc(dev, sizeof(struct acx424akp), GFP_KERNEL);
-	if (!acx)
-		return -ENOMEM;
-	acx->video_mode = of_property_read_bool(dev->of_node,
-						"enforce-video-mode");
-
-	mipi_dsi_set_drvdata(dsi, acx);
-	acx->dev = dev;
-
-	dsi->lanes = 2;
-	dsi->format = MIPI_DSI_FMT_RGB888;
-	/*
-	 * FIXME: these come from the ST-Ericsson vendor driver for the
-	 * HREF520 and seems to reflect limitations in the PLLs on that
-	 * platform, if you have the datasheet, please cross-check the
-	 * actual max rates.
-	 */
-	dsi->lp_rate = 19200000;
-	dsi->hs_rate = 420160000;
-
-	if (acx->video_mode)
-		/* Burst mode using event for sync */
-		dsi->mode_flags =
-			MIPI_DSI_MODE_VIDEO |
-			MIPI_DSI_MODE_VIDEO_BURST;
-	else
-		dsi->mode_flags =
-			MIPI_DSI_CLOCK_NON_CONTINUOUS;
-
-	acx->supply = devm_regulator_get(dev, "vddi");
-	if (IS_ERR(acx->supply))
-		return PTR_ERR(acx->supply);
-
-	/* This asserts RESET by default */
-	acx->reset_gpio = devm_gpiod_get_optional(dev, "reset",
-						  GPIOD_OUT_HIGH);
-	if (IS_ERR(acx->reset_gpio))
-		return dev_err_probe(dev, PTR_ERR(acx->reset_gpio),
-				     "failed to request GPIO\n");
-
-	drm_panel_init(&acx->panel, dev, &acx424akp_drm_funcs,
-		       DRM_MODE_CONNECTOR_DSI);
-
-	acx->panel.backlight = devm_backlight_device_register(dev, "acx424akp", dev, acx,
-					&acx424akp_bl_ops, &acx424akp_bl_props);
-	if (IS_ERR(acx->panel.backlight))
-		return dev_err_probe(dev, PTR_ERR(acx->panel.backlight),
-				     "failed to register backlight device\n");
-
-	drm_panel_add(&acx->panel);
-
-	ret = mipi_dsi_attach(dsi);
-	if (ret < 0) {
-		drm_panel_remove(&acx->panel);
-		return ret;
-	}
-
-	return 0;
-}
-
-static int acx424akp_remove(struct mipi_dsi_device *dsi)
-{
-	struct acx424akp *acx = mipi_dsi_get_drvdata(dsi);
-
-	mipi_dsi_detach(dsi);
-	drm_panel_remove(&acx->panel);
-
-	return 0;
-}
-
-static const struct of_device_id acx424akp_of_match[] = {
-	{ .compatible = "sony,acx424akp" },
-	{ /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, acx424akp_of_match);
-
-static struct mipi_dsi_driver acx424akp_driver = {
-	.probe = acx424akp_probe,
-	.remove = acx424akp_remove,
-	.driver = {
-		.name = "panel-sony-acx424akp",
-		.of_match_table = acx424akp_of_match,
-	},
-};
-module_mipi_dsi_driver(acx424akp_driver);
-
-MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>");
-MODULE_DESCRIPTION("MIPI-DSI Sony acx424akp Panel Driver");
-MODULE_LICENSE("GPL v2");
-- 
2.38.1