linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] Novena laptop: LVDS-to-eDP bridge
@ 2020-05-09 11:17 srk
  2020-05-09 11:17 ` [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 " srk
  2020-05-09 11:17 ` [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver srk
  0 siblings, 2 replies; 7+ messages in thread
From: srk @ 2020-05-09 11:17 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Neil Armstrong, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	David Airlie, Daniel Vetter, Rob Herring, Lubomir Rintel,
	dri-devel, devicetree, linux-kernel, Richard Marko, Marek Vasut,
	Sean Cross

From: Richard Marko <srk@48.io>

Contains updated patches I've received from Marek with the following
changes:

- yaml docs migration
- extended documentation

Tested on a Kosagi Novena laptop with imx6 display controller.

Based on v5.7-rc2, applies to drm-misc-next 5e6ed29d72d2

CC: Marek Vasut <marex@denx.de>
Cc: Sean Cross <xobs@kosagi.com>

Marek Vasut (2):
  dt-bindings: it6251: add bindings for IT6251 LVDS-to-eDP bridge
  drm/bridge: Add ITE IT6251 bridge driver

 .../bindings/display/bridge/ite,it6251.yaml   |  97 +++
 drivers/gpu/drm/bridge/Kconfig                |  12 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/ite-it6251.c           | 582 ++++++++++++++++++
 4 files changed, 692 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
 create mode 100644 drivers/gpu/drm/bridge/ite-it6251.c


base-commit: ae83d0b416db002fe95601e7f97f64b59514d936
-- 
2.25.1


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

* [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 LVDS-to-eDP bridge
  2020-05-09 11:17 [PATCH 0/2] Novena laptop: LVDS-to-eDP bridge srk
@ 2020-05-09 11:17 ` srk
  2020-05-09 13:44   ` Laurent Pinchart
  2020-05-11 10:15   ` Sam Ravnborg
  2020-05-09 11:17 ` [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver srk
  1 sibling, 2 replies; 7+ messages in thread
From: srk @ 2020-05-09 11:17 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Neil Armstrong, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	David Airlie, Daniel Vetter, Rob Herring, Lubomir Rintel,
	dri-devel, devicetree, linux-kernel, Marek Vasut, Richard Marko,
	Sean Cross

From: Marek Vasut <marex@denx.de>

Add DT bindings for ITE IT6251 LVDS-to-eDP bridge.

Signed-off-by: Marek Vasut <marex@denx.de>
Signed-off-by: Richard Marko <srk@48.io>
Cc: Daniel Vetter <daniel@ffwll.ch>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Sean Cross <xobs@kosagi.com>
Cc: devicetree@vger.kernel.org
To: dri-devel@lists.freedesktop.org
---
 .../bindings/display/bridge/ite,it6251.yaml   | 97 +++++++++++++++++++
 1 file changed, 97 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml b/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
new file mode 100644
index 000000000000..8daa44a30fa1
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
@@ -0,0 +1,97 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/ite,it6251.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ITE IT6251 LVDS-to-eDP bridge bindings
+
+maintainers:
+  - Marek Vasut <marex@denx.de>
+  - Richard Marko <srk@48.io>
+
+description: |
+  The IT6251 is a high-performance single-chip
+  De-SSC LVDS to DisplayPort converter.
+  Combined with LVDS receiver and DisplayPort Transmitter,
+  the IT6251 supports LVDS input and DisplayPort 1.1a
+  output by conversion function.
+
+properties:
+  compatible:
+    const: ite,it6251
+
+  reg:
+    items:
+      - description: I2C address of the bridge
+      - description: I2C address of the LVDS part
+
+  reg-names:
+    items:
+      - const: bridge
+      - const: lvds
+
+  ports:
+    type: object
+
+    properties:
+      "#address-cells":
+        const: 1
+
+      "#size-cells":
+        const: 0
+
+      port@0:
+        type: object
+        description: |
+         Video port for eDP output (typically panel).
+
+      port@1:
+        type: object
+        description: |
+          Video port for LVDS input.
+
+    required:
+      - port@0
+      - port@1
+
+    additionalProperties: false
+
+  power-supply: true
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - power-supply
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    it6251@5c {
+      compatible = "ite,it6251";
+      reg = <0x5c>, <0x5e>;
+      reg-names = "bridge", "lvds";
+      power-supply = <&reg_display>;
+
+      ports {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        port@0 {
+          reg = <0>;
+          bridge_out_edp0: endpoint {
+            remote-endpoint = <&panel_in_edp0>;
+          };
+        };
+
+        port@1 {
+          reg = <1>;
+          bridge_in_lvds0: endpoint {
+            remote-endpoint = <&lvds0_out>;
+          };
+        };
+      };
+    };
-- 
2.25.1


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

* [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver
  2020-05-09 11:17 [PATCH 0/2] Novena laptop: LVDS-to-eDP bridge srk
  2020-05-09 11:17 ` [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 " srk
@ 2020-05-09 11:17 ` srk
  2020-05-09 13:48   ` Laurent Pinchart
  2020-05-11 10:29   ` Sam Ravnborg
  1 sibling, 2 replies; 7+ messages in thread
From: srk @ 2020-05-09 11:17 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Neil Armstrong, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	David Airlie, Daniel Vetter, Rob Herring, Lubomir Rintel,
	dri-devel, devicetree, linux-kernel, Marek Vasut, Richard Marko,
	Sean Cross

From: Marek Vasut <marex@denx.de>

Add driver for the ITE IT6251 LVDS-to-eDP bridge.

Signed-off-by: Marek Vasut <marex@denx.de>
Signed-off-by: Richard Marko <srk@48.io>
Cc: Daniel Vetter <daniel@ffwll.ch>
Cc: Sean Cross <xobs@kosagi.com>
To: dri-devel@lists.freedesktop.org
---
 drivers/gpu/drm/bridge/Kconfig      |  12 +
 drivers/gpu/drm/bridge/Makefile     |   1 +
 drivers/gpu/drm/bridge/ite-it6251.c | 582 ++++++++++++++++++++++++++++
 3 files changed, 595 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ite-it6251.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index aaed2347ace9..934896a4ab2d 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -38,6 +38,18 @@ config DRM_DISPLAY_CONNECTOR
 	  on ARM-based platforms. Saying Y here when this driver is not needed
 	  will not cause any issue.
 
+config DRM_ITE_IT6251
+	tristate "ITE IT6251 LVDS/eDP bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	select REGMAP_I2C
+	help
+	  Driver for ITE IT6251 LVDS-eDP bridge chip driver. This is used
+	  in Novena open-hardware laptop with eDP based panel.
+	  IT6251 supports LVDS input and DisplayPort 1.1a output,
+	  resolution up to 1080P and 10-bit color depth.
+
 config DRM_LVDS_CODEC
 	tristate "Transparent LVDS encoders and decoders support"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 6fb062b5b0f0..4c195dc42fce 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
 obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
+obj-$(CONFIG_DRM_ITE_IT6251) += ite-it6251.o
 obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
 obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
diff --git a/drivers/gpu/drm/bridge/ite-it6251.c b/drivers/gpu/drm/bridge/ite-it6251.c
new file mode 100644
index 000000000000..b8534fb62c9d
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ite-it6251.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 Sean Cross <xobs@kosagi.com>
+ *
+ * Rework for mainline: Marek Vasut <marex@denx.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <drm/drm_panel.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+struct it6251_bridge {
+	struct i2c_client	*client;
+	struct i2c_client	*lvds_client;
+	struct regmap		*regmap;
+	struct regmap		*lvds_regmap;
+	struct regulator	*regulator;
+
+	struct drm_connector	connector;
+	struct drm_bridge	bridge;
+	struct drm_panel	*panel;
+};
+
+/* Register definitions */
+#define IT6251_VENDOR_ID_LOW			0x00
+#define IT6251_VENDOR_ID_HIGH			0x01
+#define IT6251_DEVICE_ID_LOW			0x02
+#define IT6251_DEVICE_ID_HIGH			0x03
+#define IT6251_SYSTEM_STATUS			0x0d
+#define IT6251_SYSTEM_STATUS_RINTSTATUS		BIT(0)
+#define IT6251_SYSTEM_STATUS_RHPDSTATUS		BIT(1)
+#define IT6251_SYSTEM_STATUS_RVIDEOSTABLE	BIT(2)
+#define IT6251_SYSTEM_STATUS_RPLL_IOLOCK	BIT(3)
+#define IT6251_SYSTEM_STATUS_RPLL_XPLOCK	BIT(4)
+#define IT6251_SYSTEM_STATUS_RPLL_SPLOCK	BIT(5)
+#define IT6251_SYSTEM_STATUS_RAUXFREQ_LOCK	BIT(6)
+#define IT6251_REF_STATE			0x0e
+#define IT6251_REF_STATE_MAIN_LINK_DISABLED	BIT(0)
+#define IT6251_REF_STATE_AUX_CHANNEL_READ	BIT(1)
+#define IT6251_REF_STATE_CR_PATTERN		BIT(2)
+#define IT6251_REF_STATE_EQ_PATTERN		BIT(3)
+#define IT6251_REF_STATE_NORMAL_OPERATION	BIT(4)
+#define IT6251_REF_STATE_MUTED			BIT(5)
+#define IT6251_RPCLK_CNT_LOW			0x13
+#define IT6251_RPCLK_CNT_HIGH			0x14
+#define IT6251_RPC_REQ				0x2b
+#define IT6251_RPC_REQ_RPC_FIFOFULL		BIT(6)
+#define IT6251_RPC_REQ_RPC_FIFOEMPTY		BIT(7)
+#define IT6251_PCLK_CNT_LOW			0x57
+#define IT6251_PCLK_CNT_HIGH			0x58
+#define IT6251_DPHDEW_LOW			0xa5
+#define IT6251_DPHDEW_HIGH			0xa6
+#define IT6251_DPVDEW_LOW			0xaf
+#define IT6251_DPVDEW_HIGH			0xb0
+#define IT6251_LVDS_PORT_ADDR			0xfd
+#define IT6251_LVDS_PORT_CTRL			0xfe
+#define IT6251_LVDS_PORT_CTRL_EN		BIT(0)
+
+/*
+ * Register programming sequences.
+ * NOTE: There is a lot of registers here which are completely undocumented
+ *       and/or their meaning is not clear from the little documentation
+ *       that is available for this chip. These values below just seem to
+ *       work well enough.
+ */
+static const struct reg_sequence it6251_lvds_rx_sequence[] = {
+	{ 0x05, 0x00 },
+
+	{ 0x3b, 0x42 },	/* reset LVDSRX PLL */
+	{ 0x3b, 0x43 },
+
+	{ 0x3c, 0x08 },	/* something with SSC PLL */
+	{ 0x0b, 0x88 },	/* don't swap links, writing reserved regs */
+
+	{ 0x2c, 0x01 },	/* JEIDA, 8-bit depth 0x11, original 0x42 */
+	{ 0x32, 0x04 },	/* "reserved" */
+	{ 0x35, 0xe0 },	/* "reserved" */
+	{ 0x2b, 0x24 },	/* "reserved" + clock delay */
+
+	{ 0x05, 0x02 },	/* reset LVDSRX pix clock */
+	{ 0x05, 0x00 },
+};
+
+static const struct reg_sequence it6251_edp_tx_sequence[] = {
+	/* two lane mode, normal operation, no swapping, no downspread */
+	{ 0x16, 0x02 },
+	{ 0x23, 0x40 },	/* some AUX channel EDID magic */
+	{ 0x5c, 0xf3 },	/* power down lanes 3-0 */
+	{ 0x5f, 0x06 },	/* enable DP scrambling, change EQ CR phase */
+	{ 0x60, 0x02 },	/* color mode RGB, pclk/2 */
+	{ 0x61, 0x04 },	/* dual pixel input mode, no EO swap, no RGB swap */
+	{ 0x62, 0x01 },	/* M444B24 video format */
+
+	/* vesa range / not interlace / vsync high / hsync high */
+	{ 0xa0, 0x0F },
+
+	{ 0xc9, 0xf5 },	/* hpd event timer set to 1.6-ish ms */
+
+	{ 0xca, 0x4d },	/* more reserved magic */
+	{ 0xcb, 0x37 },
+
+	/* enhanced framing mode, auto video fifo reset, video mute disable */
+	{ 0xd3, 0x03 },
+	{ 0xd4, 0x45 },	/* "vidstmp" and some reserved stuff */
+
+	{ 0xe7, 0xa0 },	/* queue number -- reserved */
+	{ 0xe8, 0x33 },	/* info frame packets and reserved */
+	{ 0xec, 0x00 },	/* more AVI stuff */
+
+	{ 0x23, 0x42 },	/* select PC master reg for aux channel? */
+
+	{ 0x24, 0x00 },	/* send PC request commands */
+	{ 0x25, 0x00 },
+	{ 0x26, 0x00 },
+
+	{ 0x2b, 0x00 },	/* native aux read */
+	{ 0x23, 0x40 },	/* back to internal */
+
+	{ 0x19, 0xff },	/* voltage swing level 3 */
+	{ 0x1a, 0xff },	/* pre-emphasis level 3 */
+
+	{ 0x17, 0x01 },	/* start link training */
+};
+
+static struct it6251_bridge *bridge_to_it6251(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct it6251_bridge, bridge);
+}
+
+static struct it6251_bridge *conn_to_it6251(struct drm_connector *connector)
+{
+	return container_of(connector, struct it6251_bridge, connector);
+}
+
+static int it6251_is_stable(struct it6251_bridge *it6251)
+{
+	unsigned int status, rpclkcnt, clkcnt, refstate, rpcreq;
+	u16 hactive;
+	u16 vactive;
+	u8 regs[2];
+	int ret;
+
+	ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &status);
+	if (ret)
+		return ret;
+	dev_dbg(&it6251->client->dev, "System status: 0x%02x\n", status);
+
+	if (!(status & IT6251_SYSTEM_STATUS_RVIDEOSTABLE))
+		return -EINVAL;
+
+	ret = regmap_bulk_read(it6251->regmap, IT6251_RPCLK_CNT_LOW, regs, 2);
+	if (ret)
+		return ret;
+	rpclkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
+	dev_dbg(&it6251->client->dev, "RPCLKCnt: %d\n", rpclkcnt);
+
+	ret = regmap_bulk_read(it6251->lvds_regmap, IT6251_PCLK_CNT_LOW,
+			       regs, 2);
+	if (ret)
+		return ret;
+	clkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
+	dev_dbg(&it6251->client->dev, "Clock: 0x%02x\n", clkcnt);
+
+	ret = regmap_read(it6251->lvds_regmap, IT6251_REF_STATE, &refstate);
+	if (ret)
+		return ret;
+	dev_dbg(&it6251->client->dev, "Ref Link State: 0x%02x\n", refstate);
+
+	ret = regmap_read(it6251->lvds_regmap, IT6251_RPC_REQ, &rpcreq);
+	if (ret)
+		return ret;
+	dev_dbg(&it6251->client->dev, "RPC Req: 0x%02x\n", rpcreq);
+
+	ret = regmap_bulk_read(it6251->regmap, IT6251_DPHDEW_LOW, regs, 2);
+	if (ret)
+		return ret;
+	hactive = (regs[0] & 0xff) | ((regs[1] & 0x1f) << 8);
+	dev_dbg(&it6251->client->dev, "hactive: %d\n", hactive);
+
+	ret = regmap_bulk_read(it6251->regmap, IT6251_DPVDEW_LOW, regs, 2);
+	if (ret)
+		return ret;
+	vactive = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
+	dev_dbg(&it6251->client->dev, "vactive: %d\n", vactive);
+
+	if ((refstate & 0x1f) != 0)
+		return -EINVAL;
+
+	if (rpcreq & IT6251_RPC_REQ_RPC_FIFOFULL) {
+		dev_err(&it6251->client->dev,
+			"RPC fifofull is set, might be an error\n");
+		return -EINVAL;
+	}
+
+	/* If video is muted, that's a failure */
+	if (refstate & IT6251_REF_STATE_MUTED)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int it6251_init(struct it6251_bridge *it6251)
+{
+	const struct reg_sequence it6251_reset_reg_sequence[] = {
+		{ 0x05, 0x00 },
+		{ IT6251_LVDS_PORT_ADDR, it6251->lvds_client->addr << 1 },
+		{ IT6251_LVDS_PORT_CTRL, IT6251_LVDS_PORT_CTRL_EN },
+	};
+
+	int ret, stable_delays;
+	unsigned int reg;
+
+	/*
+	 * Reset DisplayPort half. Setting bit 2 causes IT6251 to not
+	 * respond over i2c, which is considered "normal". This write
+	 * will report failure, but will actually succeed.
+	 */
+	regmap_write(it6251->regmap, 0x05, 0xff);
+
+	/* Un-reset DisplayPort half and configure LVDS receiver. */
+	ret = regmap_multi_reg_write(it6251->regmap, it6251_reset_reg_sequence,
+				     ARRAY_SIZE(it6251_reset_reg_sequence));
+	if (ret) {
+		dev_err(&it6251->client->dev, "cannot setup eDP half\n");
+		return ret;
+	}
+
+	/* LVDS RX */
+	regmap_write(it6251->lvds_regmap, 0x05, 0xff);
+	ret = regmap_multi_reg_write(it6251->lvds_regmap,
+				     it6251_lvds_rx_sequence,
+				     ARRAY_SIZE(it6251_lvds_rx_sequence));
+	if (ret) {
+		dev_err(&it6251->lvds_client->dev, "cannot setup LVDS RX\n");
+		return ret;
+	}
+
+	/* eDP TX */
+	ret = regmap_multi_reg_write(it6251->regmap,
+				     it6251_edp_tx_sequence,
+				     ARRAY_SIZE(it6251_edp_tx_sequence));
+	if (ret) {
+		dev_err(&it6251->client->dev, "cannot setup eDP TX\n");
+		return ret;
+	}
+
+	for (stable_delays = 0; stable_delays < 100; stable_delays++) {
+		ret = regmap_read(it6251->regmap, 0x0e, &reg);
+		if (ret || ((reg & 0x1f) != 0x10)) {
+			mdelay(2);
+			continue;
+		}
+
+		ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &reg);
+		if (ret || !(reg & IT6251_SYSTEM_STATUS_RVIDEOSTABLE)) {
+			mdelay(2);
+			continue;
+		}
+
+		break;
+	}
+
+	/*
+	 * If we couldn't stabilize, requeue and try again, because it means
+	 * that the LVDS channel isn't stable yet.
+	 */
+	ret = it6251_is_stable(it6251);
+	if (ret)
+		dev_err(&it6251->client->dev, "bridge is not stable\n");
+
+	return ret;
+}
+
+static int it6251_power_down(struct it6251_bridge *it6251)
+{
+	struct device *dev = &it6251->client->dev;
+	int ret = 0;
+
+	if (regulator_is_enabled(it6251->regulator)) {
+		ret = regulator_disable(it6251->regulator);
+		if (ret)
+			dev_err(dev, "unable to disable regulator\n");
+	}
+
+	return ret;
+}
+
+static int it6251_power_up(struct it6251_bridge *it6251)
+{
+	struct i2c_client *client = it6251->client;
+	u8 regs[4];
+	int i, ret;
+
+	ret = regulator_enable(it6251->regulator);
+	if (ret) {
+		dev_err(&client->dev, "unable to enable regulator\n");
+		return ret;
+	}
+
+	/* Sometimes it seems like multiple tries are needed */
+	for (i = 0; i < 5; i++) {
+		ret = regmap_bulk_read(it6251->regmap, IT6251_VENDOR_ID_LOW,
+				       regs, 4);
+		if (!ret && regs[0] && regs[1] && regs[2] && regs[3]) {
+			dev_info(&client->dev, "found ITE6251 [%04x:%04x]\n",
+				 (regs[1] << 8) | regs[0],
+				 (regs[3] << 8) | regs[2]);
+			return 0;
+		}
+
+		usleep_range(100000, 200000);
+	}
+
+	dev_err(&client->dev, "unable to read product id\n");
+	it6251_power_down(it6251);
+	return -EINVAL;
+}
+
+/* I2C driver functions */
+static void it6251_pre_enable(struct drm_bridge *bridge)
+{
+	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
+
+	if (drm_panel_prepare(it6251->panel)) {
+		DRM_ERROR("failed to prepare panel\n");
+		return;
+	}
+
+	it6251_power_up(it6251);
+}
+
+static void it6251_enable(struct drm_bridge *bridge)
+{
+	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
+	int tries, ret;
+
+	if (drm_panel_enable(it6251->panel)) {
+		DRM_ERROR("failed to enable panel\n");
+		return;
+	}
+
+	for (tries = 0; tries < 5; tries++) {
+		ret = it6251_init(it6251);
+		if (!ret)
+			return;
+
+		/* If the init failed, restart the chip */
+		it6251_power_down(it6251);
+		it6251_power_up(it6251);
+	}
+}
+
+static void it6251_disable(struct drm_bridge *bridge)
+{
+	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
+
+	if (drm_panel_disable(it6251->panel))
+		DRM_ERROR("failed to disable panel\n");
+}
+
+static void it6251_post_disable(struct drm_bridge *bridge)
+{
+	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
+
+	if (drm_panel_unprepare(it6251->panel))
+		DRM_ERROR("failed to unprepare panel\n");
+
+	it6251_power_down(it6251);
+}
+
+static int it6251_get_modes(struct drm_connector *connector)
+{
+	struct it6251_bridge *it6251 = conn_to_it6251(connector);
+
+	return drm_panel_get_modes(it6251->panel, connector);
+}
+
+static const struct drm_connector_helper_funcs it6251_connector_helper_funcs = {
+	.get_modes	= it6251_get_modes,
+};
+
+static const struct drm_connector_funcs it6251_connector_funcs = {
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= drm_connector_cleanup,
+	.reset			= drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+};
+
+static int it6251_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
+{
+	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	it6251->connector.polled = DRM_CONNECTOR_POLL_HPD;
+	ret = drm_connector_init(bridge->dev, &it6251->connector,
+				 &it6251_connector_funcs,
+				 DRM_MODE_CONNECTOR_eDP);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	drm_atomic_helper_connector_reset(&it6251->connector);
+	drm_connector_helper_add(&it6251->connector,
+				 &it6251_connector_helper_funcs);
+	drm_connector_attach_encoder(&it6251->connector, bridge->encoder);
+
+	if (it6251->panel)
+		drm_panel_attach(it6251->panel, &it6251->connector);
+
+	drm_helper_hpd_irq_event(it6251->connector.dev);
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs it6251_bridge_funcs = {
+	.pre_enable	= it6251_pre_enable,
+	.enable		= it6251_enable,
+	.disable	= it6251_disable,
+	.post_disable	= it6251_post_disable,
+	.attach		= it6251_attach,
+};
+
+static const struct regmap_config it6251_regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+	.max_register	= 0xff,
+	.cache_type	= REGCACHE_NONE,
+};
+
+static int
+it6251_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct it6251_bridge *it6251;
+	struct device_node *endpoint, *panel_node;
+	int ret;
+
+	it6251 = devm_kzalloc(dev, sizeof(*it6251), GFP_KERNEL);
+	if (!it6251)
+		return -ENOMEM;
+
+	endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+	if (endpoint) {
+		panel_node = of_graph_get_remote_port_parent(endpoint);
+		if (panel_node) {
+			it6251->panel = of_drm_find_panel(panel_node);
+			of_node_put(panel_node);
+			if (!it6251->panel)
+				return -EPROBE_DEFER;
+		}
+	}
+
+	it6251->client = client;
+
+	it6251->regmap = devm_regmap_init_i2c(client, &it6251_regmap_config);
+	if (IS_ERR(it6251->regmap)) {
+		dev_err(dev, "cannot init i2c regmap for IT6251\n");
+		return PTR_ERR(it6251->regmap);
+	}
+
+	it6251->regulator = devm_regulator_get(dev, "power");
+	if (IS_ERR(it6251->regulator)) {
+		dev_err(dev, "no power regulator found for IT6251\n");
+		return PTR_ERR(it6251->regulator);
+	}
+
+	/* The LVDS-half of the chip shows up at address 0x5e */
+	it6251->lvds_client = i2c_new_ancillary_device(client, "lvds", 0x5e);
+	if (IS_ERR(it6251->lvds_client)) {
+		dev_err(dev, "cannot create I2C device for IT6251 LVDS\n");
+		return PTR_ERR(it6251->lvds_client);
+	}
+
+	it6251->lvds_regmap = regmap_init_i2c(it6251->lvds_client,
+					      &it6251_regmap_config);
+	if (IS_ERR(it6251->lvds_regmap)) {
+		dev_err(dev, "cannot init i2c regmap for IT6251 LVDS\n");
+		ret = PTR_ERR(it6251->lvds_regmap);
+		goto err_lvds_regmap;
+	}
+
+	i2c_set_clientdata(client, it6251);
+
+	it6251->bridge.funcs = &it6251_bridge_funcs;
+	it6251->bridge.of_node = dev->of_node;
+	drm_bridge_add(&it6251->bridge);
+
+	return 0;
+
+err_lvds_regmap:
+	i2c_unregister_device(it6251->lvds_client);
+	return ret;
+}
+
+static int it6251_remove(struct i2c_client *client)
+{
+	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
+	int ret;
+
+	ret = it6251_power_down(it6251);
+	if (ret)
+		return ret;
+
+	regmap_exit(it6251->lvds_regmap);
+	i2c_unregister_device(it6251->lvds_client);
+
+	return 0;
+}
+
+static int it6251_pm_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
+
+	return it6251_power_down(it6251);
+}
+
+static int it6251_pm_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
+
+	return it6251_power_up(it6251);
+}
+
+static const struct dev_pm_ops it6251_dev_pm_ops = {
+	.suspend = it6251_pm_suspend,
+	.resume = it6251_pm_resume,
+	.restore = it6251_pm_resume,
+};
+
+static struct i2c_device_id it6251_ids[] = {
+	{ "it6251", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, it6251_ids);
+
+static const struct of_device_id it6251_of_match[] = {
+	{ .compatible = "ite,it6251", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, it6251_of_match);
+
+static struct i2c_driver it6251_driver = {
+	.driver = {
+		.name		= "it6251",
+		.pm		= &it6251_dev_pm_ops,
+		.of_match_table	= it6251_of_match,
+	},
+	.probe		= it6251_probe,
+	.remove		= it6251_remove,
+	.id_table	= it6251_ids,
+};
+
+module_i2c_driver(it6251_driver);
+
+/* Module initialization */
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("ITE Tech 6251 LVDS to DisplayPort encoder");
+MODULE_LICENSE("GPL");
-- 
2.25.1


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

* Re: [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 LVDS-to-eDP bridge
  2020-05-09 11:17 ` [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 " srk
@ 2020-05-09 13:44   ` Laurent Pinchart
  2020-05-11 10:15   ` Sam Ravnborg
  1 sibling, 0 replies; 7+ messages in thread
From: Laurent Pinchart @ 2020-05-09 13:44 UTC (permalink / raw)
  To: srk
  Cc: Andrzej Hajda, Neil Armstrong, Jonas Karlman, Jernej Skrabec,
	David Airlie, Daniel Vetter, Rob Herring, Lubomir Rintel,
	dri-devel, devicetree, linux-kernel, Marek Vasut, Sean Cross

Hi Richard,

Thank you for the patch.

On Sat, May 09, 2020 at 01:17:31PM +0200, srk@48.io wrote:
> From: Marek Vasut <marex@denx.de>
> 
> Add DT bindings for ITE IT6251 LVDS-to-eDP bridge.
> 
> Signed-off-by: Marek Vasut <marex@denx.de>
> Signed-off-by: Richard Marko <srk@48.io>
> Cc: Daniel Vetter <daniel@ffwll.ch>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: Sean Cross <xobs@kosagi.com>
> Cc: devicetree@vger.kernel.org
> To: dri-devel@lists.freedesktop.org
> ---
>  .../bindings/display/bridge/ite,it6251.yaml   | 97 +++++++++++++++++++
>  1 file changed, 97 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml b/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
> new file mode 100644
> index 000000000000..8daa44a30fa1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
> @@ -0,0 +1,97 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/bridge/ite,it6251.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ITE IT6251 LVDS-to-eDP bridge bindings
> +
> +maintainers:
> +  - Marek Vasut <marex@denx.de>
> +  - Richard Marko <srk@48.io>
> +
> +description: |
> +  The IT6251 is a high-performance single-chip
> +  De-SSC LVDS to DisplayPort converter.
> +  Combined with LVDS receiver and DisplayPort Transmitter,
> +  the IT6251 supports LVDS input and DisplayPort 1.1a
> +  output by conversion function.

You could reflow this to the 80 columns limit. It also sounds like
marketing material copied from the datasheet, with "by conversion
function" likely a bad translation.

> +
> +properties:
> +  compatible:
> +    const: ite,it6251
> +
> +  reg:
> +    items:
> +      - description: I2C address of the bridge
> +      - description: I2C address of the LVDS part
> +
> +  reg-names:
> +    items:
> +      - const: bridge
> +      - const: lvds
> +
> +  ports:
> +    type: object
> +
> +    properties:
> +      "#address-cells":
> +        const: 1
> +
> +      "#size-cells":
> +        const: 0
> +
> +      port@0:
> +        type: object
> +        description: |
> +         Video port for eDP output (typically panel).
> +
> +      port@1:
> +        type: object
> +        description: |
> +          Video port for LVDS input.

We traditionally use port 0 for the input and port 1 for the output. I'd
rather do the same here to remain consistent, and allow generic code to
operate on this device.

> +
> +    required:
> +      - port@0
> +      - port@1
> +
> +    additionalProperties: false
> +
> +  power-supply: true

There are multiple power supplies for the chip, shouldn't we have
multiple properties ? I would also name the properties according to the
supply name.

The chip has an HPD input. I would recommend already documenting an
optional boolean no-hpd property to report when the HPD input is not
connected.

> +
> +required:
> +  - compatible
> +  - reg
> +  - reg-names
> +  - power-supply
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    it6251@5c {
> +      compatible = "ite,it6251";
> +      reg = <0x5c>, <0x5e>;
> +      reg-names = "bridge", "lvds";
> +      power-supply = <&reg_display>;
> +
> +      ports {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        port@0 {
> +          reg = <0>;
> +          bridge_out_edp0: endpoint {
> +            remote-endpoint = <&panel_in_edp0>;
> +          };
> +        };
> +
> +        port@1 {
> +          reg = <1>;
> +          bridge_in_lvds0: endpoint {
> +            remote-endpoint = <&lvds0_out>;
> +          };
> +        };
> +      };
> +    };

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver
  2020-05-09 11:17 ` [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver srk
@ 2020-05-09 13:48   ` Laurent Pinchart
  2020-05-11 10:29   ` Sam Ravnborg
  1 sibling, 0 replies; 7+ messages in thread
From: Laurent Pinchart @ 2020-05-09 13:48 UTC (permalink / raw)
  To: srk
  Cc: Andrzej Hajda, Neil Armstrong, Jonas Karlman, Jernej Skrabec,
	David Airlie, Daniel Vetter, Rob Herring, Lubomir Rintel,
	dri-devel, devicetree, linux-kernel, Marek Vasut, Sean Cross

Hi Richard,

Thank you for the patch.

On Sat, May 09, 2020 at 01:17:32PM +0200, srk@48.io wrote:
> From: Marek Vasut <marex@denx.de>
> 
> Add driver for the ITE IT6251 LVDS-to-eDP bridge.
> 
> Signed-off-by: Marek Vasut <marex@denx.de>
> Signed-off-by: Richard Marko <srk@48.io>
> Cc: Daniel Vetter <daniel@ffwll.ch>
> Cc: Sean Cross <xobs@kosagi.com>
> To: dri-devel@lists.freedesktop.org
> ---
>  drivers/gpu/drm/bridge/Kconfig      |  12 +
>  drivers/gpu/drm/bridge/Makefile     |   1 +
>  drivers/gpu/drm/bridge/ite-it6251.c | 582 ++++++++++++++++++++++++++++
>  3 files changed, 595 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ite-it6251.c
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index aaed2347ace9..934896a4ab2d 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -38,6 +38,18 @@ config DRM_DISPLAY_CONNECTOR
>  	  on ARM-based platforms. Saying Y here when this driver is not needed
>  	  will not cause any issue.
>  
> +config DRM_ITE_IT6251
> +	tristate "ITE IT6251 LVDS/eDP bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	select REGMAP_I2C
> +	help
> +	  Driver for ITE IT6251 LVDS-eDP bridge chip driver. This is used
> +	  in Novena open-hardware laptop with eDP based panel.
> +	  IT6251 supports LVDS input and DisplayPort 1.1a output,
> +	  resolution up to 1080P and 10-bit color depth.
> +
>  config DRM_LVDS_CODEC
>  	tristate "Transparent LVDS encoders and decoders support"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 6fb062b5b0f0..4c195dc42fce 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
> +obj-$(CONFIG_DRM_ITE_IT6251) += ite-it6251.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
>  obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> diff --git a/drivers/gpu/drm/bridge/ite-it6251.c b/drivers/gpu/drm/bridge/ite-it6251.c
> new file mode 100644
> index 000000000000..b8534fb62c9d
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ite-it6251.c
> @@ -0,0 +1,582 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2014 Sean Cross <xobs@kosagi.com>
> + *
> + * Rework for mainline: Marek Vasut <marex@denx.de>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <drm/drm_panel.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +struct it6251_bridge {
> +	struct i2c_client	*client;
> +	struct i2c_client	*lvds_client;
> +	struct regmap		*regmap;
> +	struct regmap		*lvds_regmap;
> +	struct regulator	*regulator;
> +
> +	struct drm_connector	connector;
> +	struct drm_bridge	bridge;
> +	struct drm_panel	*panel;
> +};
> +
> +/* Register definitions */
> +#define IT6251_VENDOR_ID_LOW			0x00
> +#define IT6251_VENDOR_ID_HIGH			0x01
> +#define IT6251_DEVICE_ID_LOW			0x02
> +#define IT6251_DEVICE_ID_HIGH			0x03
> +#define IT6251_SYSTEM_STATUS			0x0d
> +#define IT6251_SYSTEM_STATUS_RINTSTATUS		BIT(0)
> +#define IT6251_SYSTEM_STATUS_RHPDSTATUS		BIT(1)
> +#define IT6251_SYSTEM_STATUS_RVIDEOSTABLE	BIT(2)
> +#define IT6251_SYSTEM_STATUS_RPLL_IOLOCK	BIT(3)
> +#define IT6251_SYSTEM_STATUS_RPLL_XPLOCK	BIT(4)
> +#define IT6251_SYSTEM_STATUS_RPLL_SPLOCK	BIT(5)
> +#define IT6251_SYSTEM_STATUS_RAUXFREQ_LOCK	BIT(6)
> +#define IT6251_REF_STATE			0x0e
> +#define IT6251_REF_STATE_MAIN_LINK_DISABLED	BIT(0)
> +#define IT6251_REF_STATE_AUX_CHANNEL_READ	BIT(1)
> +#define IT6251_REF_STATE_CR_PATTERN		BIT(2)
> +#define IT6251_REF_STATE_EQ_PATTERN		BIT(3)
> +#define IT6251_REF_STATE_NORMAL_OPERATION	BIT(4)
> +#define IT6251_REF_STATE_MUTED			BIT(5)
> +#define IT6251_RPCLK_CNT_LOW			0x13
> +#define IT6251_RPCLK_CNT_HIGH			0x14
> +#define IT6251_RPC_REQ				0x2b
> +#define IT6251_RPC_REQ_RPC_FIFOFULL		BIT(6)
> +#define IT6251_RPC_REQ_RPC_FIFOEMPTY		BIT(7)
> +#define IT6251_PCLK_CNT_LOW			0x57
> +#define IT6251_PCLK_CNT_HIGH			0x58
> +#define IT6251_DPHDEW_LOW			0xa5
> +#define IT6251_DPHDEW_HIGH			0xa6
> +#define IT6251_DPVDEW_LOW			0xaf
> +#define IT6251_DPVDEW_HIGH			0xb0
> +#define IT6251_LVDS_PORT_ADDR			0xfd
> +#define IT6251_LVDS_PORT_CTRL			0xfe
> +#define IT6251_LVDS_PORT_CTRL_EN		BIT(0)
> +
> +/*
> + * Register programming sequences.
> + * NOTE: There is a lot of registers here which are completely undocumented
> + *       and/or their meaning is not clear from the little documentation
> + *       that is available for this chip. These values below just seem to
> + *       work well enough.
> + */
> +static const struct reg_sequence it6251_lvds_rx_sequence[] = {
> +	{ 0x05, 0x00 },
> +
> +	{ 0x3b, 0x42 },	/* reset LVDSRX PLL */
> +	{ 0x3b, 0x43 },
> +
> +	{ 0x3c, 0x08 },	/* something with SSC PLL */
> +	{ 0x0b, 0x88 },	/* don't swap links, writing reserved regs */
> +
> +	{ 0x2c, 0x01 },	/* JEIDA, 8-bit depth 0x11, original 0x42 */
> +	{ 0x32, 0x04 },	/* "reserved" */
> +	{ 0x35, 0xe0 },	/* "reserved" */
> +	{ 0x2b, 0x24 },	/* "reserved" + clock delay */
> +
> +	{ 0x05, 0x02 },	/* reset LVDSRX pix clock */
> +	{ 0x05, 0x00 },
> +};
> +
> +static const struct reg_sequence it6251_edp_tx_sequence[] = {
> +	/* two lane mode, normal operation, no swapping, no downspread */
> +	{ 0x16, 0x02 },
> +	{ 0x23, 0x40 },	/* some AUX channel EDID magic */
> +	{ 0x5c, 0xf3 },	/* power down lanes 3-0 */
> +	{ 0x5f, 0x06 },	/* enable DP scrambling, change EQ CR phase */
> +	{ 0x60, 0x02 },	/* color mode RGB, pclk/2 */
> +	{ 0x61, 0x04 },	/* dual pixel input mode, no EO swap, no RGB swap */
> +	{ 0x62, 0x01 },	/* M444B24 video format */
> +
> +	/* vesa range / not interlace / vsync high / hsync high */
> +	{ 0xa0, 0x0F },
> +
> +	{ 0xc9, 0xf5 },	/* hpd event timer set to 1.6-ish ms */
> +
> +	{ 0xca, 0x4d },	/* more reserved magic */
> +	{ 0xcb, 0x37 },
> +
> +	/* enhanced framing mode, auto video fifo reset, video mute disable */
> +	{ 0xd3, 0x03 },
> +	{ 0xd4, 0x45 },	/* "vidstmp" and some reserved stuff */
> +
> +	{ 0xe7, 0xa0 },	/* queue number -- reserved */
> +	{ 0xe8, 0x33 },	/* info frame packets and reserved */
> +	{ 0xec, 0x00 },	/* more AVI stuff */
> +
> +	{ 0x23, 0x42 },	/* select PC master reg for aux channel? */
> +
> +	{ 0x24, 0x00 },	/* send PC request commands */
> +	{ 0x25, 0x00 },
> +	{ 0x26, 0x00 },
> +
> +	{ 0x2b, 0x00 },	/* native aux read */
> +	{ 0x23, 0x40 },	/* back to internal */
> +
> +	{ 0x19, 0xff },	/* voltage swing level 3 */
> +	{ 0x1a, 0xff },	/* pre-emphasis level 3 */
> +
> +	{ 0x17, 0x01 },	/* start link training */
> +};
> +
> +static struct it6251_bridge *bridge_to_it6251(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct it6251_bridge, bridge);
> +}
> +
> +static struct it6251_bridge *conn_to_it6251(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct it6251_bridge, connector);
> +}
> +
> +static int it6251_is_stable(struct it6251_bridge *it6251)
> +{
> +	unsigned int status, rpclkcnt, clkcnt, refstate, rpcreq;
> +	u16 hactive;
> +	u16 vactive;
> +	u8 regs[2];
> +	int ret;
> +
> +	ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &status);
> +	if (ret)
> +		return ret;
> +	dev_dbg(&it6251->client->dev, "System status: 0x%02x\n", status);
> +
> +	if (!(status & IT6251_SYSTEM_STATUS_RVIDEOSTABLE))
> +		return -EINVAL;
> +
> +	ret = regmap_bulk_read(it6251->regmap, IT6251_RPCLK_CNT_LOW, regs, 2);
> +	if (ret)
> +		return ret;
> +	rpclkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
> +	dev_dbg(&it6251->client->dev, "RPCLKCnt: %d\n", rpclkcnt);
> +
> +	ret = regmap_bulk_read(it6251->lvds_regmap, IT6251_PCLK_CNT_LOW,
> +			       regs, 2);
> +	if (ret)
> +		return ret;
> +	clkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
> +	dev_dbg(&it6251->client->dev, "Clock: 0x%02x\n", clkcnt);
> +
> +	ret = regmap_read(it6251->lvds_regmap, IT6251_REF_STATE, &refstate);
> +	if (ret)
> +		return ret;
> +	dev_dbg(&it6251->client->dev, "Ref Link State: 0x%02x\n", refstate);
> +
> +	ret = regmap_read(it6251->lvds_regmap, IT6251_RPC_REQ, &rpcreq);
> +	if (ret)
> +		return ret;
> +	dev_dbg(&it6251->client->dev, "RPC Req: 0x%02x\n", rpcreq);
> +
> +	ret = regmap_bulk_read(it6251->regmap, IT6251_DPHDEW_LOW, regs, 2);
> +	if (ret)
> +		return ret;
> +	hactive = (regs[0] & 0xff) | ((regs[1] & 0x1f) << 8);
> +	dev_dbg(&it6251->client->dev, "hactive: %d\n", hactive);
> +
> +	ret = regmap_bulk_read(it6251->regmap, IT6251_DPVDEW_LOW, regs, 2);
> +	if (ret)
> +		return ret;
> +	vactive = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
> +	dev_dbg(&it6251->client->dev, "vactive: %d\n", vactive);
> +
> +	if ((refstate & 0x1f) != 0)
> +		return -EINVAL;
> +
> +	if (rpcreq & IT6251_RPC_REQ_RPC_FIFOFULL) {
> +		dev_err(&it6251->client->dev,
> +			"RPC fifofull is set, might be an error\n");
> +		return -EINVAL;
> +	}
> +
> +	/* If video is muted, that's a failure */
> +	if (refstate & IT6251_REF_STATE_MUTED)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int it6251_init(struct it6251_bridge *it6251)
> +{
> +	const struct reg_sequence it6251_reset_reg_sequence[] = {
> +		{ 0x05, 0x00 },
> +		{ IT6251_LVDS_PORT_ADDR, it6251->lvds_client->addr << 1 },
> +		{ IT6251_LVDS_PORT_CTRL, IT6251_LVDS_PORT_CTRL_EN },
> +	};
> +
> +	int ret, stable_delays;
> +	unsigned int reg;
> +
> +	/*
> +	 * Reset DisplayPort half. Setting bit 2 causes IT6251 to not
> +	 * respond over i2c, which is considered "normal". This write
> +	 * will report failure, but will actually succeed.
> +	 */
> +	regmap_write(it6251->regmap, 0x05, 0xff);
> +
> +	/* Un-reset DisplayPort half and configure LVDS receiver. */
> +	ret = regmap_multi_reg_write(it6251->regmap, it6251_reset_reg_sequence,
> +				     ARRAY_SIZE(it6251_reset_reg_sequence));
> +	if (ret) {
> +		dev_err(&it6251->client->dev, "cannot setup eDP half\n");
> +		return ret;
> +	}
> +
> +	/* LVDS RX */
> +	regmap_write(it6251->lvds_regmap, 0x05, 0xff);
> +	ret = regmap_multi_reg_write(it6251->lvds_regmap,
> +				     it6251_lvds_rx_sequence,
> +				     ARRAY_SIZE(it6251_lvds_rx_sequence));
> +	if (ret) {
> +		dev_err(&it6251->lvds_client->dev, "cannot setup LVDS RX\n");
> +		return ret;
> +	}
> +
> +	/* eDP TX */
> +	ret = regmap_multi_reg_write(it6251->regmap,
> +				     it6251_edp_tx_sequence,
> +				     ARRAY_SIZE(it6251_edp_tx_sequence));
> +	if (ret) {
> +		dev_err(&it6251->client->dev, "cannot setup eDP TX\n");
> +		return ret;
> +	}
> +
> +	for (stable_delays = 0; stable_delays < 100; stable_delays++) {
> +		ret = regmap_read(it6251->regmap, 0x0e, &reg);
> +		if (ret || ((reg & 0x1f) != 0x10)) {
> +			mdelay(2);
> +			continue;
> +		}
> +
> +		ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &reg);
> +		if (ret || !(reg & IT6251_SYSTEM_STATUS_RVIDEOSTABLE)) {
> +			mdelay(2);
> +			continue;
> +		}
> +
> +		break;
> +	}
> +
> +	/*
> +	 * If we couldn't stabilize, requeue and try again, because it means
> +	 * that the LVDS channel isn't stable yet.
> +	 */
> +	ret = it6251_is_stable(it6251);
> +	if (ret)
> +		dev_err(&it6251->client->dev, "bridge is not stable\n");
> +
> +	return ret;
> +}
> +
> +static int it6251_power_down(struct it6251_bridge *it6251)
> +{
> +	struct device *dev = &it6251->client->dev;
> +	int ret = 0;
> +
> +	if (regulator_is_enabled(it6251->regulator)) {
> +		ret = regulator_disable(it6251->regulator);
> +		if (ret)
> +			dev_err(dev, "unable to disable regulator\n");
> +	}
> +
> +	return ret;
> +}
> +
> +static int it6251_power_up(struct it6251_bridge *it6251)
> +{
> +	struct i2c_client *client = it6251->client;
> +	u8 regs[4];
> +	int i, ret;
> +
> +	ret = regulator_enable(it6251->regulator);
> +	if (ret) {
> +		dev_err(&client->dev, "unable to enable regulator\n");
> +		return ret;
> +	}
> +
> +	/* Sometimes it seems like multiple tries are needed */
> +	for (i = 0; i < 5; i++) {
> +		ret = regmap_bulk_read(it6251->regmap, IT6251_VENDOR_ID_LOW,
> +				       regs, 4);
> +		if (!ret && regs[0] && regs[1] && regs[2] && regs[3]) {
> +			dev_info(&client->dev, "found ITE6251 [%04x:%04x]\n",
> +				 (regs[1] << 8) | regs[0],
> +				 (regs[3] << 8) | regs[2]);
> +			return 0;
> +		}
> +
> +		usleep_range(100000, 200000);
> +	}
> +
> +	dev_err(&client->dev, "unable to read product id\n");
> +	it6251_power_down(it6251);
> +	return -EINVAL;
> +}
> +
> +/* I2C driver functions */
> +static void it6251_pre_enable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +
> +	if (drm_panel_prepare(it6251->panel)) {
> +		DRM_ERROR("failed to prepare panel\n");
> +		return;
> +	}
> +
> +	it6251_power_up(it6251);
> +}
> +
> +static void it6251_enable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +	int tries, ret;
> +
> +	if (drm_panel_enable(it6251->panel)) {
> +		DRM_ERROR("failed to enable panel\n");
> +		return;
> +	}
> +
> +	for (tries = 0; tries < 5; tries++) {
> +		ret = it6251_init(it6251);
> +		if (!ret)
> +			return;
> +
> +		/* If the init failed, restart the chip */
> +		it6251_power_down(it6251);
> +		it6251_power_up(it6251);
> +	}
> +}
> +
> +static void it6251_disable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +
> +	if (drm_panel_disable(it6251->panel))
> +		DRM_ERROR("failed to disable panel\n");
> +}
> +
> +static void it6251_post_disable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +
> +	if (drm_panel_unprepare(it6251->panel))
> +		DRM_ERROR("failed to unprepare panel\n");
> +
> +	it6251_power_down(it6251);
> +}
> +
> +static int it6251_get_modes(struct drm_connector *connector)
> +{
> +	struct it6251_bridge *it6251 = conn_to_it6251(connector);
> +
> +	return drm_panel_get_modes(it6251->panel, connector);
> +}
> +
> +static const struct drm_connector_helper_funcs it6251_connector_helper_funcs = {
> +	.get_modes	= it6251_get_modes,
> +};
> +
> +static const struct drm_connector_funcs it6251_connector_funcs = {
> +	.fill_modes		= drm_helper_probe_single_connector_modes,
> +	.destroy		= drm_connector_cleanup,
> +	.reset			= drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int it6251_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +	int ret;
> +

New bridge drivers need to support the DRM_BRIDGE_ATTACH_NO_CONNECTOR
flag, and implement the connector-related drm_bridge_funcs operations.
See the "[PATCH v2 0/21] drm/bridge: support drm bridge connector helper
+ panel updates" patch series on the dri-devel mailing list for
examples.

> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	it6251->connector.polled = DRM_CONNECTOR_POLL_HPD;
> +	ret = drm_connector_init(bridge->dev, &it6251->connector,
> +				 &it6251_connector_funcs,
> +				 DRM_MODE_CONNECTOR_eDP);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_atomic_helper_connector_reset(&it6251->connector);
> +	drm_connector_helper_add(&it6251->connector,
> +				 &it6251_connector_helper_funcs);
> +	drm_connector_attach_encoder(&it6251->connector, bridge->encoder);
> +
> +	if (it6251->panel)
> +		drm_panel_attach(it6251->panel, &it6251->connector);
> +
> +	drm_helper_hpd_irq_event(it6251->connector.dev);
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs it6251_bridge_funcs = {
> +	.pre_enable	= it6251_pre_enable,
> +	.enable		= it6251_enable,
> +	.disable	= it6251_disable,
> +	.post_disable	= it6251_post_disable,
> +	.attach		= it6251_attach,
> +};
> +
> +static const struct regmap_config it6251_regmap_config = {
> +	.reg_bits	= 8,
> +	.val_bits	= 8,
> +	.max_register	= 0xff,
> +	.cache_type	= REGCACHE_NONE,
> +};
> +
> +static int
> +it6251_probe(struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct device *dev = &client->dev;
> +	struct it6251_bridge *it6251;
> +	struct device_node *endpoint, *panel_node;
> +	int ret;
> +
> +	it6251 = devm_kzalloc(dev, sizeof(*it6251), GFP_KERNEL);
> +	if (!it6251)
> +		return -ENOMEM;
> +
> +	endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
> +	if (endpoint) {
> +		panel_node = of_graph_get_remote_port_parent(endpoint);
> +		if (panel_node) {
> +			it6251->panel = of_drm_find_panel(panel_node);
> +			of_node_put(panel_node);
> +			if (!it6251->panel)
> +				return -EPROBE_DEFER;
> +		}
> +	}
> +
> +	it6251->client = client;
> +
> +	it6251->regmap = devm_regmap_init_i2c(client, &it6251_regmap_config);
> +	if (IS_ERR(it6251->regmap)) {
> +		dev_err(dev, "cannot init i2c regmap for IT6251\n");
> +		return PTR_ERR(it6251->regmap);
> +	}
> +
> +	it6251->regulator = devm_regulator_get(dev, "power");
> +	if (IS_ERR(it6251->regulator)) {
> +		dev_err(dev, "no power regulator found for IT6251\n");
> +		return PTR_ERR(it6251->regulator);
> +	}
> +
> +	/* The LVDS-half of the chip shows up at address 0x5e */
> +	it6251->lvds_client = i2c_new_ancillary_device(client, "lvds", 0x5e);
> +	if (IS_ERR(it6251->lvds_client)) {
> +		dev_err(dev, "cannot create I2C device for IT6251 LVDS\n");
> +		return PTR_ERR(it6251->lvds_client);
> +	}
> +
> +	it6251->lvds_regmap = regmap_init_i2c(it6251->lvds_client,
> +					      &it6251_regmap_config);
> +	if (IS_ERR(it6251->lvds_regmap)) {
> +		dev_err(dev, "cannot init i2c regmap for IT6251 LVDS\n");
> +		ret = PTR_ERR(it6251->lvds_regmap);
> +		goto err_lvds_regmap;
> +	}
> +
> +	i2c_set_clientdata(client, it6251);
> +
> +	it6251->bridge.funcs = &it6251_bridge_funcs;
> +	it6251->bridge.of_node = dev->of_node;
> +	drm_bridge_add(&it6251->bridge);
> +
> +	return 0;
> +
> +err_lvds_regmap:
> +	i2c_unregister_device(it6251->lvds_client);
> +	return ret;
> +}
> +
> +static int it6251_remove(struct i2c_client *client)
> +{
> +	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
> +	int ret;
> +
> +	ret = it6251_power_down(it6251);
> +	if (ret)
> +		return ret;
> +
> +	regmap_exit(it6251->lvds_regmap);
> +	i2c_unregister_device(it6251->lvds_client);
> +
> +	return 0;
> +}
> +
> +static int it6251_pm_suspend(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
> +
> +	return it6251_power_down(it6251);
> +}
> +
> +static int it6251_pm_resume(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
> +
> +	return it6251_power_up(it6251);
> +}
> +
> +static const struct dev_pm_ops it6251_dev_pm_ops = {
> +	.suspend = it6251_pm_suspend,
> +	.resume = it6251_pm_resume,
> +	.restore = it6251_pm_resume,
> +};
> +
> +static struct i2c_device_id it6251_ids[] = {
> +	{ "it6251", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, it6251_ids);
> +
> +static const struct of_device_id it6251_of_match[] = {
> +	{ .compatible = "ite,it6251", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, it6251_of_match);
> +
> +static struct i2c_driver it6251_driver = {
> +	.driver = {
> +		.name		= "it6251",
> +		.pm		= &it6251_dev_pm_ops,
> +		.of_match_table	= it6251_of_match,
> +	},
> +	.probe		= it6251_probe,
> +	.remove		= it6251_remove,
> +	.id_table	= it6251_ids,
> +};
> +
> +module_i2c_driver(it6251_driver);
> +
> +/* Module initialization */
> +MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
> +MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
> +MODULE_DESCRIPTION("ITE Tech 6251 LVDS to DisplayPort encoder");
> +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 LVDS-to-eDP bridge
  2020-05-09 11:17 ` [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 " srk
  2020-05-09 13:44   ` Laurent Pinchart
@ 2020-05-11 10:15   ` Sam Ravnborg
  1 sibling, 0 replies; 7+ messages in thread
From: Sam Ravnborg @ 2020-05-11 10:15 UTC (permalink / raw)
  To: srk
  Cc: Andrzej Hajda, Marek Vasut, devicetree, Jernej Skrabec,
	Jonas Karlman, David Airlie, Neil Armstrong, linux-kernel,
	dri-devel, Lubomir Rintel, Rob Herring, Laurent Pinchart,
	Sean Cross

Hi Richard/Marek.

On Sat, May 09, 2020 at 01:17:31PM +0200, srk@48.io wrote:
> From: Marek Vasut <marex@denx.de>
> 
> Add DT bindings for ITE IT6251 LVDS-to-eDP bridge.

Looks good, just a few comments in the following.

	Sam
> 
> Signed-off-by: Marek Vasut <marex@denx.de>
> Signed-off-by: Richard Marko <srk@48.io>
> Cc: Daniel Vetter <daniel@ffwll.ch>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: Sean Cross <xobs@kosagi.com>
> Cc: devicetree@vger.kernel.org
> To: dri-devel@lists.freedesktop.org
> ---
>  .../bindings/display/bridge/ite,it6251.yaml   | 97 +++++++++++++++++++
>  1 file changed, 97 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml b/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
> new file mode 100644
> index 000000000000..8daa44a30fa1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/ite,it6251.yaml
> @@ -0,0 +1,97 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/bridge/ite,it6251.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ITE IT6251 LVDS-to-eDP bridge bindings
> +
> +maintainers:
> +  - Marek Vasut <marex@denx.de>
> +  - Richard Marko <srk@48.io>
> +
> +description: |
> +  The IT6251 is a high-performance single-chip
> +  De-SSC LVDS to DisplayPort converter.
> +  Combined with LVDS receiver and DisplayPort Transmitter,
> +  the IT6251 supports LVDS input and DisplayPort 1.1a
> +  output by conversion function.
> +
> +properties:
> +  compatible:
> +    const: ite,it6251
> +
> +  reg:
> +    items:
> +      - description: I2C address of the bridge
> +      - description: I2C address of the LVDS part
> +
> +  reg-names:
> +    items:
> +      - const: bridge
> +      - const: lvds
> +
> +  ports:
> +    type: object
> +
> +    properties:
> +      "#address-cells":
> +        const: 1
> +
> +      "#size-cells":
> +        const: 0
> +
> +      port@0:
> +        type: object
> +        description: |
> +         Video port for eDP output (typically panel).
port@0 is in most (all?) cases input today.
Take for example a look at: toshiba,tc358768.yaml
Could we do the same here?

> +
> +      port@1:
> +        type: object
> +        description: |
> +          Video port for LVDS input.
> +
> +    required:
> +      - port@0
> +      - port@1
> +
> +    additionalProperties: false
> +
> +  power-supply: true
> +
> +required:
> +  - compatible
> +  - reg
> +  - reg-names
> +  - power-supply
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
Add an extra level named "i2c" like we do in other bridge examples.
> +    it6251@5c {
My personal preference is 4 spaces for indent.
But 2 spaces is also fine.

> +      compatible = "ite,it6251";
> +      reg = <0x5c>, <0x5e>;
> +      reg-names = "bridge", "lvds";
> +      power-supply = <&reg_display>;
> +
> +      ports {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        port@0 {
> +          reg = <0>;
> +          bridge_out_edp0: endpoint {
> +            remote-endpoint = <&panel_in_edp0>;
> +          };
> +        };
> +
> +        port@1 {
> +          reg = <1>;
> +          bridge_in_lvds0: endpoint {
> +            remote-endpoint = <&lvds0_out>;
> +          };
> +        };
> +      };
> +    };

End with a line containing only "..."

This is an optional end-marker.
See lvds-codec.yaml for an example.

	Sam

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

* Re: [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver
  2020-05-09 11:17 ` [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver srk
  2020-05-09 13:48   ` Laurent Pinchart
@ 2020-05-11 10:29   ` Sam Ravnborg
  1 sibling, 0 replies; 7+ messages in thread
From: Sam Ravnborg @ 2020-05-11 10:29 UTC (permalink / raw)
  To: srk
  Cc: Andrzej Hajda, Marek Vasut, devicetree, Jernej Skrabec,
	Jonas Karlman, David Airlie, Neil Armstrong, linux-kernel,
	dri-devel, Lubomir Rintel, Rob Herring, Laurent Pinchart,
	Sean Cross

Hi Richard.

On Sat, May 09, 2020 at 01:17:32PM +0200, srk@48.io wrote:
> From: Marek Vasut <marex@denx.de>
> 
> Add driver for the ITE IT6251 LVDS-to-eDP bridge.

I assume this is the same driver submitted here:
https://lore.kernel.org/dri-devel/f2892d7b-a60b-4e15-faa7-97ccb20e874e@codeaurora.org/

I miss a changelog, that identified what was addressed of the review
from last submission.

In general we are moving over to chained bridges and this driver shall
comply to this.
It is not so difficult:
1) Respect flag in the attach operation, so connector creation become
optional.
2) Implement relevant bridge operations.

You can see examples in following thread:
https://lore.kernel.org/dri-devel/20200427081850.17512-1-sam@ravnborg.org/

I get the impression we have several similar ITE based bridge drivers
in flight at the moment. And I have lost the overview which are
duplicates and which are unique.
Plese check if anyone else have submitted a bridge driver for a similar
IC recently.

I only took a quick view of the code today, it triggered a few comments
in the following.

	Sam

> 
> Signed-off-by: Marek Vasut <marex@denx.de>
> Signed-off-by: Richard Marko <srk@48.io>
> Cc: Daniel Vetter <daniel@ffwll.ch>
> Cc: Sean Cross <xobs@kosagi.com>
> To: dri-devel@lists.freedesktop.org
> ---
>  drivers/gpu/drm/bridge/Kconfig      |  12 +
>  drivers/gpu/drm/bridge/Makefile     |   1 +
>  drivers/gpu/drm/bridge/ite-it6251.c | 582 ++++++++++++++++++++++++++++
>  3 files changed, 595 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ite-it6251.c
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index aaed2347ace9..934896a4ab2d 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -38,6 +38,18 @@ config DRM_DISPLAY_CONNECTOR
>  	  on ARM-based platforms. Saying Y here when this driver is not needed
>  	  will not cause any issue.
>  
> +config DRM_ITE_IT6251
> +	tristate "ITE IT6251 LVDS/eDP bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	select REGMAP_I2C
> +	help
> +	  Driver for ITE IT6251 LVDS-eDP bridge chip driver. This is used
> +	  in Novena open-hardware laptop with eDP based panel.
> +	  IT6251 supports LVDS input and DisplayPort 1.1a output,
> +	  resolution up to 1080P and 10-bit color depth.
The driver is hopefully in no way Novena specific.
In other words, we would like to be able to use this bridge driver for
other cases, and the wording above shall reflect that.

> +
>  config DRM_LVDS_CODEC
>  	tristate "Transparent LVDS encoders and decoders support"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 6fb062b5b0f0..4c195dc42fce 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
> +obj-$(CONFIG_DRM_ITE_IT6251) += ite-it6251.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
>  obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> diff --git a/drivers/gpu/drm/bridge/ite-it6251.c b/drivers/gpu/drm/bridge/ite-it6251.c
> new file mode 100644
> index 000000000000..b8534fb62c9d
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ite-it6251.c
> @@ -0,0 +1,582 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2014 Sean Cross <xobs@kosagi.com>
2020?
> + *
> + * Rework for mainline: Marek Vasut <marex@denx.de>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <drm/drm_panel.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>

Please group drm_* headers together and in alphabetical order.
drm_panel.h needs to be moved.

> +
> +struct it6251_bridge {
> +	struct i2c_client	*client;
> +	struct i2c_client	*lvds_client;
> +	struct regmap		*regmap;
> +	struct regmap		*lvds_regmap;
> +	struct regulator	*regulator;
> +
> +	struct drm_connector	connector;
> +	struct drm_bridge	bridge;
> +	struct drm_panel	*panel;

Use bridge panel - see patchset referred above.

> +};
> +
> +/* Register definitions */
> +#define IT6251_VENDOR_ID_LOW			0x00
> +#define IT6251_VENDOR_ID_HIGH			0x01
> +#define IT6251_DEVICE_ID_LOW			0x02
> +#define IT6251_DEVICE_ID_HIGH			0x03
> +#define IT6251_SYSTEM_STATUS			0x0d
> +#define IT6251_SYSTEM_STATUS_RINTSTATUS		BIT(0)
> +#define IT6251_SYSTEM_STATUS_RHPDSTATUS		BIT(1)
> +#define IT6251_SYSTEM_STATUS_RVIDEOSTABLE	BIT(2)
> +#define IT6251_SYSTEM_STATUS_RPLL_IOLOCK	BIT(3)
> +#define IT6251_SYSTEM_STATUS_RPLL_XPLOCK	BIT(4)
> +#define IT6251_SYSTEM_STATUS_RPLL_SPLOCK	BIT(5)
> +#define IT6251_SYSTEM_STATUS_RAUXFREQ_LOCK	BIT(6)
> +#define IT6251_REF_STATE			0x0e
> +#define IT6251_REF_STATE_MAIN_LINK_DISABLED	BIT(0)
> +#define IT6251_REF_STATE_AUX_CHANNEL_READ	BIT(1)
> +#define IT6251_REF_STATE_CR_PATTERN		BIT(2)
> +#define IT6251_REF_STATE_EQ_PATTERN		BIT(3)
> +#define IT6251_REF_STATE_NORMAL_OPERATION	BIT(4)
> +#define IT6251_REF_STATE_MUTED			BIT(5)
> +#define IT6251_RPCLK_CNT_LOW			0x13
> +#define IT6251_RPCLK_CNT_HIGH			0x14
> +#define IT6251_RPC_REQ				0x2b
> +#define IT6251_RPC_REQ_RPC_FIFOFULL		BIT(6)
> +#define IT6251_RPC_REQ_RPC_FIFOEMPTY		BIT(7)
> +#define IT6251_PCLK_CNT_LOW			0x57
> +#define IT6251_PCLK_CNT_HIGH			0x58
> +#define IT6251_DPHDEW_LOW			0xa5
> +#define IT6251_DPHDEW_HIGH			0xa6
> +#define IT6251_DPVDEW_LOW			0xaf
> +#define IT6251_DPVDEW_HIGH			0xb0
> +#define IT6251_LVDS_PORT_ADDR			0xfd
> +#define IT6251_LVDS_PORT_CTRL			0xfe
> +#define IT6251_LVDS_PORT_CTRL_EN		BIT(0)
> +
> +/*
> + * Register programming sequences.
> + * NOTE: There is a lot of registers here which are completely undocumented
> + *       and/or their meaning is not clear from the little documentation
> + *       that is available for this chip. These values below just seem to
> + *       work well enough.
> + */
> +static const struct reg_sequence it6251_lvds_rx_sequence[] = {
> +	{ 0x05, 0x00 },
> +
> +	{ 0x3b, 0x42 },	/* reset LVDSRX PLL */
> +	{ 0x3b, 0x43 },
> +
> +	{ 0x3c, 0x08 },	/* something with SSC PLL */
> +	{ 0x0b, 0x88 },	/* don't swap links, writing reserved regs */
> +
> +	{ 0x2c, 0x01 },	/* JEIDA, 8-bit depth 0x11, original 0x42 */
> +	{ 0x32, 0x04 },	/* "reserved" */
> +	{ 0x35, 0xe0 },	/* "reserved" */
> +	{ 0x2b, 0x24 },	/* "reserved" + clock delay */
> +
> +	{ 0x05, 0x02 },	/* reset LVDSRX pix clock */
> +	{ 0x05, 0x00 },
> +};
> +
> +static const struct reg_sequence it6251_edp_tx_sequence[] = {
> +	/* two lane mode, normal operation, no swapping, no downspread */
> +	{ 0x16, 0x02 },
> +	{ 0x23, 0x40 },	/* some AUX channel EDID magic */
> +	{ 0x5c, 0xf3 },	/* power down lanes 3-0 */
> +	{ 0x5f, 0x06 },	/* enable DP scrambling, change EQ CR phase */
> +	{ 0x60, 0x02 },	/* color mode RGB, pclk/2 */
> +	{ 0x61, 0x04 },	/* dual pixel input mode, no EO swap, no RGB swap */
> +	{ 0x62, 0x01 },	/* M444B24 video format */
> +
> +	/* vesa range / not interlace / vsync high / hsync high */
> +	{ 0xa0, 0x0F },
> +
> +	{ 0xc9, 0xf5 },	/* hpd event timer set to 1.6-ish ms */
> +
> +	{ 0xca, 0x4d },	/* more reserved magic */
> +	{ 0xcb, 0x37 },
> +
> +	/* enhanced framing mode, auto video fifo reset, video mute disable */
> +	{ 0xd3, 0x03 },
> +	{ 0xd4, 0x45 },	/* "vidstmp" and some reserved stuff */
> +
> +	{ 0xe7, 0xa0 },	/* queue number -- reserved */
> +	{ 0xe8, 0x33 },	/* info frame packets and reserved */
> +	{ 0xec, 0x00 },	/* more AVI stuff */
> +
> +	{ 0x23, 0x42 },	/* select PC master reg for aux channel? */
> +
> +	{ 0x24, 0x00 },	/* send PC request commands */
> +	{ 0x25, 0x00 },
> +	{ 0x26, 0x00 },
> +
> +	{ 0x2b, 0x00 },	/* native aux read */
> +	{ 0x23, 0x40 },	/* back to internal */
> +
> +	{ 0x19, 0xff },	/* voltage swing level 3 */
> +	{ 0x1a, 0xff },	/* pre-emphasis level 3 */
> +
> +	{ 0x17, 0x01 },	/* start link training */
> +};
> +
> +static struct it6251_bridge *bridge_to_it6251(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct it6251_bridge, bridge);
> +}
> +
> +static struct it6251_bridge *conn_to_it6251(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct it6251_bridge, connector);
> +}
> +
> +static int it6251_is_stable(struct it6251_bridge *it6251)
> +{
> +	unsigned int status, rpclkcnt, clkcnt, refstate, rpcreq;
> +	u16 hactive;
> +	u16 vactive;
> +	u8 regs[2];
> +	int ret;
> +
> +	ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &status);
> +	if (ret)
> +		return ret;
> +	dev_dbg(&it6251->client->dev, "System status: 0x%02x\n", status);
> +
> +	if (!(status & IT6251_SYSTEM_STATUS_RVIDEOSTABLE))
> +		return -EINVAL;
> +
> +	ret = regmap_bulk_read(it6251->regmap, IT6251_RPCLK_CNT_LOW, regs, 2);
> +	if (ret)
> +		return ret;
> +	rpclkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
> +	dev_dbg(&it6251->client->dev, "RPCLKCnt: %d\n", rpclkcnt);
> +
> +	ret = regmap_bulk_read(it6251->lvds_regmap, IT6251_PCLK_CNT_LOW,
> +			       regs, 2);
> +	if (ret)
> +		return ret;
> +	clkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
> +	dev_dbg(&it6251->client->dev, "Clock: 0x%02x\n", clkcnt);
> +
> +	ret = regmap_read(it6251->lvds_regmap, IT6251_REF_STATE, &refstate);
> +	if (ret)
> +		return ret;
> +	dev_dbg(&it6251->client->dev, "Ref Link State: 0x%02x\n", refstate);
> +
> +	ret = regmap_read(it6251->lvds_regmap, IT6251_RPC_REQ, &rpcreq);
> +	if (ret)
> +		return ret;
> +	dev_dbg(&it6251->client->dev, "RPC Req: 0x%02x\n", rpcreq);
> +
> +	ret = regmap_bulk_read(it6251->regmap, IT6251_DPHDEW_LOW, regs, 2);
> +	if (ret)
> +		return ret;
> +	hactive = (regs[0] & 0xff) | ((regs[1] & 0x1f) << 8);
> +	dev_dbg(&it6251->client->dev, "hactive: %d\n", hactive);
> +
> +	ret = regmap_bulk_read(it6251->regmap, IT6251_DPVDEW_LOW, regs, 2);
> +	if (ret)
> +		return ret;
> +	vactive = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8);
> +	dev_dbg(&it6251->client->dev, "vactive: %d\n", vactive);
> +
> +	if ((refstate & 0x1f) != 0)
> +		return -EINVAL;
> +
> +	if (rpcreq & IT6251_RPC_REQ_RPC_FIFOFULL) {
> +		dev_err(&it6251->client->dev,
> +			"RPC fifofull is set, might be an error\n");
> +		return -EINVAL;
> +	}
> +
> +	/* If video is muted, that's a failure */
> +	if (refstate & IT6251_REF_STATE_MUTED)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int it6251_init(struct it6251_bridge *it6251)
> +{
> +	const struct reg_sequence it6251_reset_reg_sequence[] = {
> +		{ 0x05, 0x00 },
> +		{ IT6251_LVDS_PORT_ADDR, it6251->lvds_client->addr << 1 },
> +		{ IT6251_LVDS_PORT_CTRL, IT6251_LVDS_PORT_CTRL_EN },
> +	};
> +
> +	int ret, stable_delays;
> +	unsigned int reg;
> +
> +	/*
> +	 * Reset DisplayPort half. Setting bit 2 causes IT6251 to not
> +	 * respond over i2c, which is considered "normal". This write
> +	 * will report failure, but will actually succeed.
> +	 */
> +	regmap_write(it6251->regmap, 0x05, 0xff);
> +
> +	/* Un-reset DisplayPort half and configure LVDS receiver. */
> +	ret = regmap_multi_reg_write(it6251->regmap, it6251_reset_reg_sequence,
> +				     ARRAY_SIZE(it6251_reset_reg_sequence));
> +	if (ret) {
> +		dev_err(&it6251->client->dev, "cannot setup eDP half\n");
> +		return ret;
> +	}
> +
> +	/* LVDS RX */
> +	regmap_write(it6251->lvds_regmap, 0x05, 0xff);
> +	ret = regmap_multi_reg_write(it6251->lvds_regmap,
> +				     it6251_lvds_rx_sequence,
> +				     ARRAY_SIZE(it6251_lvds_rx_sequence));
> +	if (ret) {
> +		dev_err(&it6251->lvds_client->dev, "cannot setup LVDS RX\n");
> +		return ret;
> +	}
> +
> +	/* eDP TX */
> +	ret = regmap_multi_reg_write(it6251->regmap,
> +				     it6251_edp_tx_sequence,
> +				     ARRAY_SIZE(it6251_edp_tx_sequence));
> +	if (ret) {
> +		dev_err(&it6251->client->dev, "cannot setup eDP TX\n");
> +		return ret;
> +	}
> +
> +	for (stable_delays = 0; stable_delays < 100; stable_delays++) {
> +		ret = regmap_read(it6251->regmap, 0x0e, &reg);
> +		if (ret || ((reg & 0x1f) != 0x10)) {
> +			mdelay(2);
> +			continue;
> +		}
> +
> +		ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &reg);
> +		if (ret || !(reg & IT6251_SYSTEM_STATUS_RVIDEOSTABLE)) {
> +			mdelay(2);
> +			continue;
> +		}
> +
> +		break;
> +	}
> +
> +	/*
> +	 * If we couldn't stabilize, requeue and try again, because it means
> +	 * that the LVDS channel isn't stable yet.
> +	 */
> +	ret = it6251_is_stable(it6251);
> +	if (ret)
> +		dev_err(&it6251->client->dev, "bridge is not stable\n");
> +
> +	return ret;
> +}
> +
> +static int it6251_power_down(struct it6251_bridge *it6251)
> +{
> +	struct device *dev = &it6251->client->dev;
> +	int ret = 0;
> +
> +	if (regulator_is_enabled(it6251->regulator)) {
> +		ret = regulator_disable(it6251->regulator);
> +		if (ret)
> +			dev_err(dev, "unable to disable regulator\n");
> +	}
> +
> +	return ret;
> +}
> +
> +static int it6251_power_up(struct it6251_bridge *it6251)
> +{
> +	struct i2c_client *client = it6251->client;
> +	u8 regs[4];
> +	int i, ret;
> +
> +	ret = regulator_enable(it6251->regulator);
> +	if (ret) {
> +		dev_err(&client->dev, "unable to enable regulator\n");
> +		return ret;
> +	}
> +
> +	/* Sometimes it seems like multiple tries are needed */
> +	for (i = 0; i < 5; i++) {
> +		ret = regmap_bulk_read(it6251->regmap, IT6251_VENDOR_ID_LOW,
> +				       regs, 4);
> +		if (!ret && regs[0] && regs[1] && regs[2] && regs[3]) {
> +			dev_info(&client->dev, "found ITE6251 [%04x:%04x]\n",
> +				 (regs[1] << 8) | regs[0],
> +				 (regs[3] << 8) | regs[2]);
> +			return 0;
> +		}
> +
> +		usleep_range(100000, 200000);
> +	}
> +
> +	dev_err(&client->dev, "unable to read product id\n");
> +	it6251_power_down(it6251);
> +	return -EINVAL;
> +}
> +
> +/* I2C driver functions */
> +static void it6251_pre_enable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +
> +	if (drm_panel_prepare(it6251->panel)) {
> +		DRM_ERROR("failed to prepare panel\n");
> +		return;
> +	}
> +
> +	it6251_power_up(it6251);
> +}
> +
> +static void it6251_enable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +	int tries, ret;
> +
> +	if (drm_panel_enable(it6251->panel)) {
> +		DRM_ERROR("failed to enable panel\n");
> +		return;
> +	}
> +
> +	for (tries = 0; tries < 5; tries++) {
> +		ret = it6251_init(it6251);
> +		if (!ret)
> +			return;
> +
> +		/* If the init failed, restart the chip */
> +		it6251_power_down(it6251);
> +		it6251_power_up(it6251);
> +	}
> +}
> +
> +static void it6251_disable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +
> +	if (drm_panel_disable(it6251->panel))
> +		DRM_ERROR("failed to disable panel\n");
> +}
> +
> +static void it6251_post_disable(struct drm_bridge *bridge)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +
> +	if (drm_panel_unprepare(it6251->panel))
> +		DRM_ERROR("failed to unprepare panel\n");
> +
> +	it6251_power_down(it6251);
> +}
> +
> +static int it6251_get_modes(struct drm_connector *connector)
> +{
> +	struct it6251_bridge *it6251 = conn_to_it6251(connector);
> +
> +	return drm_panel_get_modes(it6251->panel, connector);
> +}
> +
> +static const struct drm_connector_helper_funcs it6251_connector_helper_funcs = {
> +	.get_modes	= it6251_get_modes,
> +};
> +
> +static const struct drm_connector_funcs it6251_connector_funcs = {
> +	.fill_modes		= drm_helper_probe_single_connector_modes,
> +	.destroy		= drm_connector_cleanup,
> +	.reset			= drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int it6251_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
> +{
> +	struct it6251_bridge *it6251 = bridge_to_it6251(bridge);
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	it6251->connector.polled = DRM_CONNECTOR_POLL_HPD;
> +	ret = drm_connector_init(bridge->dev, &it6251->connector,
> +				 &it6251_connector_funcs,
> +				 DRM_MODE_CONNECTOR_eDP);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_atomic_helper_connector_reset(&it6251->connector);
> +	drm_connector_helper_add(&it6251->connector,
> +				 &it6251_connector_helper_funcs);
> +	drm_connector_attach_encoder(&it6251->connector, bridge->encoder);
> +
> +	if (it6251->panel)
> +		drm_panel_attach(it6251->panel, &it6251->connector);
> +
> +	drm_helper_hpd_irq_event(it6251->connector.dev);
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs it6251_bridge_funcs = {
> +	.pre_enable	= it6251_pre_enable,
> +	.enable		= it6251_enable,
> +	.disable	= it6251_disable,
> +	.post_disable	= it6251_post_disable,
> +	.attach		= it6251_attach,
> +};
> +
> +static const struct regmap_config it6251_regmap_config = {
> +	.reg_bits	= 8,
> +	.val_bits	= 8,
> +	.max_register	= 0xff,
> +	.cache_type	= REGCACHE_NONE,
> +};
> +
> +static int
> +it6251_probe(struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct device *dev = &client->dev;
> +	struct it6251_bridge *it6251;
> +	struct device_node *endpoint, *panel_node;
> +	int ret;
> +
> +	it6251 = devm_kzalloc(dev, sizeof(*it6251), GFP_KERNEL);
> +	if (!it6251)
> +		return -ENOMEM;
> +
> +	endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
> +	if (endpoint) {
> +		panel_node = of_graph_get_remote_port_parent(endpoint);
> +		if (panel_node) {
> +			it6251->panel = of_drm_find_panel(panel_node);
> +			of_node_put(panel_node);
> +			if (!it6251->panel)
> +				return -EPROBE_DEFER;
> +		}
> +	}
> +
> +	it6251->client = client;
> +
> +	it6251->regmap = devm_regmap_init_i2c(client, &it6251_regmap_config);
> +	if (IS_ERR(it6251->regmap)) {
> +		dev_err(dev, "cannot init i2c regmap for IT6251\n");
> +		return PTR_ERR(it6251->regmap);
> +	}
> +
> +	it6251->regulator = devm_regulator_get(dev, "power");
> +	if (IS_ERR(it6251->regulator)) {
> +		dev_err(dev, "no power regulator found for IT6251\n");
> +		return PTR_ERR(it6251->regulator);
> +	}
> +
> +	/* The LVDS-half of the chip shows up at address 0x5e */
> +	it6251->lvds_client = i2c_new_ancillary_device(client, "lvds", 0x5e);
> +	if (IS_ERR(it6251->lvds_client)) {
> +		dev_err(dev, "cannot create I2C device for IT6251 LVDS\n");
> +		return PTR_ERR(it6251->lvds_client);
> +	}
> +
> +	it6251->lvds_regmap = regmap_init_i2c(it6251->lvds_client,
> +					      &it6251_regmap_config);
> +	if (IS_ERR(it6251->lvds_regmap)) {
> +		dev_err(dev, "cannot init i2c regmap for IT6251 LVDS\n");
> +		ret = PTR_ERR(it6251->lvds_regmap);
> +		goto err_lvds_regmap;
> +	}
> +
> +	i2c_set_clientdata(client, it6251);
> +
> +	it6251->bridge.funcs = &it6251_bridge_funcs;
> +	it6251->bridge.of_node = dev->of_node;
> +	drm_bridge_add(&it6251->bridge);
> +
> +	return 0;
> +
> +err_lvds_regmap:
> +	i2c_unregister_device(it6251->lvds_client);
> +	return ret;
> +}
> +
> +static int it6251_remove(struct i2c_client *client)
> +{
> +	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
> +	int ret;
> +
> +	ret = it6251_power_down(it6251);
> +	if (ret)
> +		return ret;
> +
> +	regmap_exit(it6251->lvds_regmap);
> +	i2c_unregister_device(it6251->lvds_client);
> +
> +	return 0;
> +}
> +
> +static int it6251_pm_suspend(struct device *dev)
__maybe_unused?
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
> +
> +	return it6251_power_down(it6251);
> +}
> +
> +static int it6251_pm_resume(struct device *dev)
__maybe_unused?
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct it6251_bridge *it6251 = i2c_get_clientdata(client);
> +
> +	return it6251_power_up(it6251);
> +}
> +
> +static const struct dev_pm_ops it6251_dev_pm_ops = {
> +	.suspend = it6251_pm_suspend,
> +	.resume = it6251_pm_resume,
> +	.restore = it6251_pm_resume,
> +};
SET_RUNTIME_PM_OPS?

> +
> +static struct i2c_device_id it6251_ids[] = {
> +	{ "it6251", 0 },
> +	{ }
	{ /* sentinel */ },?

> +};
> +MODULE_DEVICE_TABLE(i2c, it6251_ids);
> +
> +static const struct of_device_id it6251_of_match[] = {
> +	{ .compatible = "ite,it6251", },
> +	{ }
	{ /* sentinel */ },?
> +};
> +MODULE_DEVICE_TABLE(of, it6251_of_match);
> +
> +static struct i2c_driver it6251_driver = {
> +	.driver = {
> +		.name		= "it6251",
> +		.pm		= &it6251_dev_pm_ops,
> +		.of_match_table	= it6251_of_match,
> +	},
> +	.probe		= it6251_probe,
> +	.remove		= it6251_remove,
> +	.id_table	= it6251_ids,
> +};
> +
> +module_i2c_driver(it6251_driver);
> +
> +/* Module initialization */
> +MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
> +MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
> +MODULE_DESCRIPTION("ITE Tech 6251 LVDS to DisplayPort encoder");
> +MODULE_LICENSE("GPL");
> -- 
> 2.25.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

end of thread, other threads:[~2020-05-11 10:29 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-05-09 11:17 [PATCH 0/2] Novena laptop: LVDS-to-eDP bridge srk
2020-05-09 11:17 ` [PATCH 1/2] dt-bindings: it6251: add bindings for IT6251 " srk
2020-05-09 13:44   ` Laurent Pinchart
2020-05-11 10:15   ` Sam Ravnborg
2020-05-09 11:17 ` [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver srk
2020-05-09 13:48   ` Laurent Pinchart
2020-05-11 10:29   ` Sam Ravnborg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).