All of lore.kernel.org
 help / color / mirror / Atom feed
From: Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	 Maxime Ripard <mripard@kernel.org>,
	Thomas Zimmermann <tzimmermann@suse.de>,
	 David Airlie <airlied@gmail.com>,
	Simona Vetter <simona@ffwll.ch>,
	 Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org
Subject: [PATCH v2 1/4] drm/fl2000: Add USB driver with register and I2C access
Date: Wed, 11 Nov 2026 22:34:08 -0500	[thread overview]
Message-ID: <20261111-fl2000-upstream-v2-1-201f87708cb0@gmail.com> (raw)
In-Reply-To: <20261111-fl2000-upstream-v2-0-201f87708cb0@gmail.com>

The Fresco Logic FL2000DX is a USB 3.0 display controller used in
inexpensive USB to VGA and USB to HDMI adapters (USB ID 1d5c:2000). The
FL2000 itself outputs parallel RGB/VGA; HDMI adapters pair it with an
ITE IT66121 HDMI transmitter that sits on the FL2000's I2C bus.

This first patch adds the USB side of the driver: it matches interface
0 and claims the other two FL2000 interfaces from its probe, selects the
altsetting that has the bulk endpoint, describes the FL2000 registers,
provides a regmap on top of the vendor control transfers that access
them, and registers an I2C adapter backed by the FL2000 I2C engine. The
engine only transfers aligned 32-bit words, so byte accesses are done as
read-modify-write of the containing word and the adapter advertises the
matching quirks.

Everything hangs off the binding of interface 0 and goes away with it;
unbinding any of the interfaces takes the others along.

The driver is based on the out-of-tree fl2000_drm driver by Artem
Mygaiev, which carries code from the Fresco Logic reference driver.

Signed-off-by: Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
---
 MAINTAINERS                               |   7 +
 drivers/gpu/drm/Kconfig                   |   1 +
 drivers/gpu/drm/Makefile                  |   1 +
 drivers/gpu/drm/fl2000/Kconfig            |  19 ++
 drivers/gpu/drm/fl2000/Makefile           |   8 +
 drivers/gpu/drm/fl2000/fl2000.h           |  89 +++++++++
 drivers/gpu/drm/fl2000/fl2000_drv.c       | 163 ++++++++++++++++
 drivers/gpu/drm/fl2000/fl2000_i2c.c       | 179 +++++++++++++++++
 drivers/gpu/drm/fl2000/fl2000_registers.c | 315 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/fl2000/fl2000_registers.h | 276 ++++++++++++++++++++++++++
 10 files changed, 1058 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c10342aa3..6e74cb478 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8324,6 +8324,13 @@ F:	include/linux/aperture.h
 F:	include/linux/sysfb.h
 F:	include/video/nomodeset.h
 
+DRM DRIVER FOR FRESCO LOGIC FL2000 USB DISPLAY ADAPTERS
+M:	Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	drivers/gpu/drm/fl2000/
+
 DRM DRIVER FOR GENERIC EDP PANELS
 R:	Douglas Anderson <dianders@chromium.org>
 F:	Documentation/devicetree/bindings/display/panel/panel-edp.yaml
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 61111d50b..6ff093830 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -313,6 +313,7 @@ source "drivers/gpu/drm/atmel-hlcdc/Kconfig"
 source "drivers/gpu/drm/bridge/Kconfig"
 source "drivers/gpu/drm/etnaviv/Kconfig"
 source "drivers/gpu/drm/exynos/Kconfig"
+source "drivers/gpu/drm/fl2000/Kconfig"
 source "drivers/gpu/drm/fsl-dcu/Kconfig"
 source "drivers/gpu/drm/gma500/Kconfig"
 source "drivers/gpu/drm/gud/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2c6bc093a..a4a8a958a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -190,6 +190,7 @@ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
 obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
 obj-$(CONFIG_DRM_GMA500) += gma500/
+obj-$(CONFIG_DRM_FL2000) += fl2000/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
 obj-$(CONFIG_DRM_ARMADA) += armada/
diff --git a/drivers/gpu/drm/fl2000/Kconfig b/drivers/gpu/drm/fl2000/Kconfig
new file mode 100644
index 000000000..bf1badc3e
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_FL2000
+	tristate "Fresco Logic FL2000 USB display adapters"
+	depends on DRM && USB && MMU
+	depends on USB_ARCH_HAS_HCD
+	select DRM_BRIDGE_CONNECTOR
+	select DRM_CLIENT_SELECTION
+	select DRM_DISPLAY_HELPER
+	select DRM_GEM_SHMEM_HELPER
+	select DRM_KMS_HELPER
+	select I2C
+	select REGMAP
+	select REGMAP_I2C
+	help
+	  This is a KMS driver for USB display adapters based on the
+	  Fresco Logic FL2000DX USB 3.0 to VGA controller, paired with an
+	  ITE IT66121 HDMI transmitter (USB ID 1d5c:2000).
+
+	  If M is selected the module will be called fl2000.
diff --git a/drivers/gpu/drm/fl2000/Makefile b/drivers/gpu/drm/fl2000/Makefile
new file mode 100644
index 000000000..2b3a2f151
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+fl2000-y := \
+	fl2000_drv.o \
+	fl2000_i2c.o \
+	fl2000_registers.o
+
+obj-$(CONFIG_DRM_FL2000) += fl2000.o
diff --git a/drivers/gpu/drm/fl2000/fl2000.h b/drivers/gpu/drm/fl2000/fl2000.h
new file mode 100644
index 000000000..9b903e68c
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2020, Artem Mygaiev
+ */
+
+#ifndef FL2000_H
+#define FL2000_H
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+
+#include "fl2000_registers.h"
+
+/* Known USB interfaces of FL2000 */
+enum fl2000_interface {
+	FL2000_USBIF_AVCONTROL = 0,
+	FL2000_USBIF_STREAMING = 1,
+	FL2000_USBIF_INTERRUPT = 2,
+	FL2000_USBIF_NUM,
+};
+
+struct fl2000_timings {
+	u32 hactive;
+	u32 htotal;
+	u32 hsync_width;
+	u32 hstart;
+	u32 vactive;
+	u32 vtotal;
+	u32 vsync_width;
+	u32 vstart;
+};
+
+struct fl2000_pll {
+	u32 prescaler;
+	u32 multiplier;
+	u32 divisor;
+	u32 function;
+};
+
+/* Polling interval and timeout in us for I2C read/write operations */
+#define I2C_RDWR_INTERVAL 200
+#define I2C_RDWR_TIMEOUT  (256 * 1000)
+
+/* I2C adapter interface creation */
+struct i2c_adapter *fl2000_i2c_init(struct usb_interface *interface, struct regmap *regmap);
+int fl2000_ddc_read_block(struct i2c_adapter *adapter, u8 *buf, unsigned int block, size_t len);
+
+/* Register map creation */
+struct regmap *fl2000_regmap_init(struct device *dev, struct usb_device *usb_dev);
+void fl2000_regmap_drop_cache(struct regmap *regmap);
+
+/* Registers interface */
+int fl2000_reset(struct regmap *regmap);
+int fl2000_usb_magic(struct regmap *regmap);
+int fl2000_afe_magic(struct regmap *regmap);
+int fl2000_set_transfers(struct regmap *regmap);
+int fl2000_set_pixfmt(struct regmap *regmap, u32 bytes_pix);
+int fl2000_set_timings(struct regmap *regmap, struct fl2000_timings *timings);
+int fl2000_set_pll(struct regmap *regmap, struct fl2000_pll *pll);
+int fl2000_force_connect(struct regmap *regmap, bool force);
+bool fl2000_force_connect_active(struct regmap *regmap);
+int fl2000_enable_sink_interrupts(struct regmap *regmap);
+int fl2000_stream_interrupts(struct regmap *regmap, bool enable);
+int fl2000_read_status(struct regmap *regmap, u32 *status);
+int fl2000_i2c_dword(struct regmap *regmap, bool read, u16 addr, u8 offset, u32 *data);
+
+#endif /* FL2000_H */
diff --git a/drivers/gpu/drm/fl2000/fl2000_drv.c b/drivers/gpu/drm/fl2000/fl2000_drv.c
new file mode 100644
index 000000000..8e41de8f5
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_drv.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2020, Artem Mygaiev
+ */
+
+#include "fl2000.h"
+
+#define USB_VENDOR_FRESCO_LOGIC 0x1D5C
+#define USB_PRODUCT_FL2000	0x2000
+
+static struct usb_driver fl2000_driver;
+
+/*
+ * The three interfaces of the display function only work together, and the driver binds to the
+ * device through the first one: it matches interface 0 and claims the others from its probe, as
+ * other multi-interface USB drivers do. Everything else hangs off the binding of interface 0 and
+ * goes away with it, whatever the reason.
+ */
+static const struct usb_device_id fl2000_id_table[] = {
+	{
+		.match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_CLASS |
+			       USB_DEVICE_ID_MATCH_INT_NUMBER,
+		.idVendor = USB_VENDOR_FRESCO_LOGIC,
+		.idProduct = USB_PRODUCT_FL2000,
+		.bInterfaceClass = USB_CLASS_AUDIO_VIDEO,
+		.bInterfaceNumber = FL2000_USBIF_AVCONTROL,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(usb, fl2000_id_table);
+
+struct fl2000_devs {
+	struct regmap *regmap;
+	struct i2c_adapter *adapter;
+};
+
+/* Interface 0 is what the driver is probing: claim the others */
+static int fl2000_claim_interfaces(struct usb_device *usb_dev, struct fl2000_devs *devs)
+{
+	int ret;
+
+	for (int i = FL2000_USBIF_AVCONTROL + 1; i < FL2000_USBIF_NUM; i++) {
+		ret = usb_driver_claim_interface(&fl2000_driver, usb_ifnum_to_if(usb_dev, i), devs);
+		if (ret) {
+			dev_err(&usb_dev->dev, "Cannot claim interface %d (%d)\n", i, ret);
+			while (--i > FL2000_USBIF_AVCONTROL) {
+				struct usb_interface *other = usb_ifnum_to_if(usb_dev, i);
+
+				usb_set_intfdata(other, NULL);
+				usb_driver_release_interface(&fl2000_driver, other);
+			}
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/* Let go of all interfaces but @interface, which is being unbound if it is one of them */
+static void fl2000_release_interfaces(struct usb_device *usb_dev, struct usb_interface *interface)
+{
+	for (int i = 0; i < FL2000_USBIF_NUM; i++) {
+		struct usb_interface *other = usb_ifnum_to_if(usb_dev, i);
+
+		if (other != interface)
+			usb_driver_release_interface(&fl2000_driver, other);
+	}
+}
+
+static int fl2000_probe(struct usb_interface *interface, const struct usb_device_id *usb_dev_id)
+{
+	int ret;
+	struct usb_device *usb_dev = interface_to_usbdev(interface);
+	struct fl2000_devs *devs;
+
+	if (usb_dev->speed < USB_SPEED_HIGH) {
+		dev_err(&interface->dev, "USB 1.1 is not supported\n");
+		return -ENODEV;
+	}
+
+	/* Frames go to the bulk endpoint of altsetting 1, and the chip has always been used so */
+	ret = usb_set_interface(usb_dev, FL2000_USBIF_AVCONTROL, 1);
+	if (ret) {
+		dev_err(&interface->dev, "Cannot set streaming interface for bulk transfers\n");
+		return ret;
+	}
+
+	devs = devm_kzalloc(&interface->dev, sizeof(*devs), GFP_KERNEL);
+	if (!devs)
+		return -ENOMEM;
+
+	devs->regmap = fl2000_regmap_init(&interface->dev, usb_dev);
+	if (IS_ERR(devs->regmap)) {
+		dev_err(&interface->dev, "Cannot initialize regmap (%pe)\n", devs->regmap);
+		return PTR_ERR(devs->regmap);
+	}
+
+	devs->adapter = fl2000_i2c_init(interface, devs->regmap);
+	if (IS_ERR(devs->adapter)) {
+		dev_err(&interface->dev, "Cannot initialize I2C adapter (%pe)\n", devs->adapter);
+		return PTR_ERR(devs->adapter);
+	}
+
+	ret = fl2000_claim_interfaces(usb_dev, devs);
+	if (ret)
+		return ret;
+
+	usb_set_intfdata(interface, devs);
+
+	return 0;
+}
+
+static void fl2000_disconnect(struct usb_interface *interface)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(interface);
+	struct fl2000_devs *devs = usb_get_intfdata(interface);
+
+	/* This interface was only let go of, by the disconnect of another one */
+	if (!devs)
+		return;
+
+	for (int i = 0; i < FL2000_USBIF_NUM; i++)
+		usb_set_intfdata(usb_ifnum_to_if(usb_dev, i), NULL);
+
+	/* Last, as devs goes with interface 0, which may be one of these */
+	fl2000_release_interfaces(usb_dev, interface);
+}
+
+static int fl2000_suspend(struct usb_interface *interface, pm_message_t message)
+{
+	/*
+	 * Nothing to save yet. Providing the callback keeps the USB core from
+	 * unbinding the driver across system sleep.
+	 */
+	dev_dbg(&interface->dev, "suspend\n");
+
+	return 0;
+}
+
+static int fl2000_resume(struct usb_interface *interface)
+{
+	dev_dbg(&interface->dev, "resume\n");
+
+	return 0;
+}
+
+static struct usb_driver fl2000_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = fl2000_probe,
+	.disconnect = fl2000_disconnect,
+	.suspend = fl2000_suspend,
+	.resume = fl2000_resume,
+	.id_table = fl2000_id_table,
+	.supports_autosuspend = false,
+	.disable_hub_initiated_lpm = true,
+};
+
+module_usb_driver(fl2000_driver);
+
+MODULE_AUTHOR("Artem Mygaiev");
+MODULE_DESCRIPTION("FL2000 USB display driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/fl2000/fl2000_i2c.c b/drivers/gpu/drm/fl2000/fl2000_i2c.c
new file mode 100644
index 000000000..97c8cf573
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_i2c.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2019, Artem Mygaiev
+ */
+
+#include <linux/unaligned.h>
+
+#include <drm/drm_edid.h>
+
+#include "fl2000.h"
+
+/*
+ * The I2C controller requires a mandatory 8-bit sub-address for any read/write operation. Each
+ * read or write operates on 8-bit data. Every exchange shall consist of 2 messages (sub-address +
+ * data) combined. The USB transfer always aligns the address to a 4-byte boundary.
+ */
+#define I2C_CMESSAGES_NUM  2
+#define I2C_REG_ADDR_SIZE  (sizeof(u8))
+#define I2C_REG_DATA_SIZE  (sizeof(u8))
+#define I2C_XFER_ADDR_MASK (~0x3ul)
+
+static inline int fl2000_i2c_read_dword(struct regmap *regmap, u16 addr, u8 offset, u32 *data)
+{
+	return fl2000_i2c_dword(regmap, true, addr, offset, data);
+}
+
+static inline int fl2000_i2c_write_dword(struct regmap *regmap, u16 addr, u8 offset, u32 *data)
+{
+	return fl2000_i2c_dword(regmap, false, addr, offset, data);
+}
+
+static int fl2000_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
+{
+	int ret;
+	bool read;
+	u16 addr;
+	u8 idx;
+	u8 offset;
+	u32 data;
+
+	/*
+	 * The adapter quirks only let through:
+	 * - 2 messages of 1 byte each, first write then read
+	 * - 1 message of at most 2 bytes
+	 */
+	if (num == 2)
+		read = true;
+	else if (msgs[0].len == 2 && !(msgs[0].flags & I2C_M_RD))
+		read = false;
+	else
+		return -EOPNOTSUPP;
+
+	addr = msgs[0].addr;
+	offset = msgs[0].buf[0] & I2C_XFER_ADDR_MASK;
+	idx = msgs[0].buf[0] - offset;
+
+	/*
+	 * The FL2000 I2C engine only transfers aligned dwords, so the offset is aligned down to a
+	 * 4-byte boundary and the requested byte is picked out of (or merged into) the dword.
+	 */
+	if (read) {
+		ret = fl2000_i2c_read_dword(adapter->algo_data, addr, offset, &data);
+		if (ret)
+			return ret;
+
+		msgs[1].buf[0] = data >> (idx * 8);
+	} else {
+		/*
+		 * Since the FL2000 I2C engine always operates on 4-byte messages, we need to read
+		 * before write in order not to corrupt the unrelated registers of the same dword.
+		 */
+		ret = fl2000_i2c_read_dword(adapter->algo_data, addr, offset, &data);
+		if (ret)
+			return ret;
+
+		data &= ~(0xffU << (idx * 8));
+		data |= (u32)msgs[0].buf[1] << (idx * 8);
+
+		ret = fl2000_i2c_write_dword(adapter->algo_data, addr, offset, &data);
+		if (ret)
+			return ret;
+	}
+
+	return num;
+}
+
+/*
+ * Monitor DDC on the FL2000 bus. The bridge's own EDID command does not work behind this engine,
+ * and the VGA connector has no bridge at all, so EDID is read with the FL2000 I2C engine at address
+ * 0x50, 4 bytes per operation with an 8-bit offset, which covers EDID blocks 0 and 1.
+ */
+#define FL2000_DDC_ADDR 0x50
+
+/**
+ * fl2000_ddc_read_block() - read one EDID block through the FL2000 I2C engine
+ * @adapter: the FL2000 I2C adapter
+ * @buf: destination, @len bytes
+ * @block: EDID block number (0 or 1)
+ * @len: bytes to read, at most EDID_LENGTH
+ *
+ * The signature matches what drm_edid_read_custom() expects, with the adapter as context.
+ *
+ * Return: 0 on success, a negative error code otherwise
+ */
+int fl2000_ddc_read_block(struct i2c_adapter *adapter, u8 *buf, unsigned int block, size_t len)
+{
+	struct regmap *regmap = adapter->algo_data;
+	unsigned int start = block * EDID_LENGTH;
+	int ret = 0;
+
+	if (block > 1 || len > EDID_LENGTH)
+		return -EOPNOTSUPP;
+
+	/* One operation of the engine is a sequence of register accesses: keep others out */
+	i2c_lock_bus(adapter, I2C_LOCK_SEGMENT);
+	for (unsigned int i = 0; i < len; i += 4) {
+		u32 data;
+
+		ret = fl2000_i2c_dword(regmap, true, FL2000_DDC_ADDR, start + i, &data);
+		if (ret)
+			break;
+
+		put_unaligned_le32(data, buf + i);
+	}
+	i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);
+
+	return ret;
+}
+
+static u32 fl2000_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm fl2000_i2c_algorithm = {
+	.xfer = fl2000_i2c_xfer,
+	.functionality = fl2000_i2c_func,
+};
+
+static const struct i2c_adapter_quirks fl2000_i2c_quirks = {
+	/* Combined transfers only: an address write, then a read, on the same address */
+	.flags = I2C_AQ_COMB_WRITE_THEN_READ | I2C_AQ_NO_ZERO_LEN,
+	.max_num_msgs = I2C_CMESSAGES_NUM,
+	.max_write_len = 2 * I2C_REG_DATA_SIZE,
+	.max_read_len = I2C_REG_DATA_SIZE,
+	.max_comb_1st_msg_len = I2C_REG_ADDR_SIZE,
+	.max_comb_2nd_msg_len = I2C_REG_DATA_SIZE,
+};
+
+struct i2c_adapter *fl2000_i2c_init(struct usb_interface *interface, struct regmap *regmap)
+{
+	int ret;
+	struct i2c_adapter *adapter;
+	struct usb_device *usb_dev = interface_to_usbdev(interface);
+	u8 usb_path[32];
+
+	/* Adapter must be allocated before anything else */
+	adapter = devm_kzalloc(&interface->dev, sizeof(*adapter), GFP_KERNEL);
+	if (!adapter)
+		return ERR_PTR(-ENOMEM);
+
+	adapter->owner = THIS_MODULE;
+	adapter->algo = &fl2000_i2c_algorithm;
+	adapter->quirks = &fl2000_i2c_quirks;
+	adapter->algo_data = regmap;
+	adapter->dev.parent = &interface->dev;
+	strscpy(adapter->name, "FL2000 bridge I2C bus", sizeof(adapter->name));
+
+	ret = devm_i2c_add_adapter(&interface->dev, adapter);
+	if (ret)
+		return ERR_PTR(ret);
+
+	usb_make_path(usb_dev, usb_path, sizeof(usb_path));
+	dev_dbg(&interface->dev, "Created FL2000 bridge I2C bus %d at interface %s\n",
+		i2c_adapter_id(adapter), usb_path);
+
+	return adapter;
+}
diff --git a/drivers/gpu/drm/fl2000/fl2000_registers.c b/drivers/gpu/drm/fl2000/fl2000_registers.c
new file mode 100644
index 000000000..f1cd28434
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_registers.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2019, Artem Mygaiev
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+
+#include "fl2000.h"
+
+#define CONTROL_MSG_READ  64
+#define CONTROL_MSG_WRITE 65
+
+#define FL2000_HW_RST_MDELAY 10
+
+static bool fl2000_reg_precious(struct device *dev, unsigned int reg)
+{
+	return FL2000_REG_PRECIOUS(reg);
+}
+
+static bool fl2000_reg_volatile(struct device *dev, unsigned int reg)
+{
+	return FL2000_REG_VOLATILE(reg);
+}
+
+/* Protected by internal regmap mutex */
+static int fl2000_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct usb_device *usb_dev = context;
+	__le32 data;
+	int ret;
+
+	ret = usb_control_msg_recv(usb_dev, 0, CONTROL_MSG_READ, USB_DIR_IN | USB_TYPE_VENDOR, 0,
+				   reg, &data, sizeof(data), USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	*val = le32_to_cpu(data);
+
+	return 0;
+}
+
+/* Protected by internal regmap mutex */
+static int fl2000_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct usb_device *usb_dev = context;
+	__le32 data = cpu_to_le32(val);
+
+	return usb_control_msg_send(usb_dev, 0, CONTROL_MSG_WRITE, USB_DIR_OUT | USB_TYPE_VENDOR, 0,
+				    reg, &data, sizeof(data), USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
+}
+
+/*
+ * We do not use default values as per documentation because:
+ *  a) somehow they differ from real HW
+ *  b) on SW reset not all of them are cleared
+ */
+static const struct regmap_config fl2000_regmap_config = {
+	.val_bits = 32,
+	.reg_bits = 16,
+	.reg_stride = 4,
+	.max_register = 0xFFFF,
+
+	.cache_type = REGCACHE_MAPLE,
+
+	.precious_reg = fl2000_reg_precious,
+	.volatile_reg = fl2000_reg_volatile,
+
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+
+	.reg_read = fl2000_reg_read,
+	.reg_write = fl2000_reg_write,
+
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+int fl2000_set_pll(struct regmap *regmap, struct fl2000_pll *pll)
+{
+	u32 val;
+
+	val = FIELD_PREP(FL2000_VGA_PLL_PRESCALER, pll->prescaler) |
+	      FIELD_PREP(FL2000_VGA_PLL_MULTIPLIER, pll->multiplier) |
+	      FIELD_PREP(FL2000_VGA_PLL_DIVISOR, pll->divisor) |
+	      FIELD_PREP(FL2000_VGA_PLL_FUNCTION, pll->function);
+	regmap_write(regmap, FL2000_VGA_PLL_REG, val);
+
+	val = FL2000_VGA_CTRL_ACLK_FORCE_PLL_UP;
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, val, val);
+
+	return 0;
+}
+
+/*
+ * Make the chip believe a monitor is connected whatever its own sense says. The HDMI path has
+ * always run with this set, so it stays set while HDMI is driven. It must be clear while only VGA
+ * is driven: it hides the cable being unplugged, and with it clear the chip powers its DAC and PLL
+ * by itself whenever it senses a monitor.
+ */
+int fl2000_force_connect(struct regmap *regmap, bool force)
+{
+	return regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK,
+				 FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT,
+				 force ? FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT : 0);
+}
+
+/* Is the chip's monitor sense forced on: then it does not follow any cable */
+bool fl2000_force_connect_active(struct regmap *regmap)
+{
+	return regmap_test_bits(regmap, FL2000_VGA_CTRL_REG_ACLK,
+				FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT) > 0;
+}
+
+int fl2000_set_timings(struct regmap *regmap, struct fl2000_timings *timings)
+{
+	regmap_write(regmap, FL2000_VGA_HSYNC_REG1,
+		     FIELD_PREP(FL2000_VGA_HSYNC1_HACTIVE, timings->hactive) |
+		     FIELD_PREP(FL2000_VGA_HSYNC1_HTOTAL, timings->htotal));
+
+	regmap_write(regmap, FL2000_VGA_HSYNC_REG2,
+		     FIELD_PREP(FL2000_VGA_HSYNC2_HSYNC_WIDTH, timings->hsync_width) |
+		     FIELD_PREP(FL2000_VGA_HSYNC2_HSTART, timings->hstart));
+
+	regmap_write(regmap, FL2000_VGA_VSYNC_REG1,
+		     FIELD_PREP(FL2000_VGA_VSYNC1_VACTIVE, timings->vactive) |
+		     FIELD_PREP(FL2000_VGA_VSYNC1_VTOTAL, timings->vtotal));
+
+	regmap_write(regmap, FL2000_VGA_VSYNC_REG2,
+		     FIELD_PREP(FL2000_VGA_VSYNC2_VSYNC_WIDTH, timings->vsync_width) |
+		     FIELD_PREP(FL2000_VGA_VSYNC2_VSTART, timings->vstart) |
+		     FIELD_PREP(FL2000_VGA_VSYNC2_START_LATENCY, timings->vstart));
+
+	return 0;
+}
+
+int fl2000_set_pixfmt(struct regmap *regmap, u32 bytes_pix)
+{
+	u32 mask, val;
+
+	mask = FL2000_VGA_CTRL_PXCLK_DAC_OUTPUT_EN | FL2000_VGA_CTRL_PXCLK_DROP_CNT |
+	       FL2000_VGA_CTRL_PXCLK_VGA565_MODE | FL2000_VGA_CTRL_PXCLK_VGA332_MODE |
+	       FL2000_VGA_CTRL_PXCLK_VGA555_MODE | FL2000_VGA_CTRL_PXCLK_VGA_COMPRESS |
+	       FL2000_VGA_CTRL_PXCLK_CLEAR_WATERMARK;
+	val = FL2000_VGA_CTRL_PXCLK_DAC_OUTPUT_EN | FL2000_VGA_CTRL_PXCLK_CLEAR_WATERMARK;
+	if (bytes_pix == 2)
+		val |= FL2000_VGA_CTRL_PXCLK_VGA565_MODE;
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_PXCLK, mask, val);
+
+	return 0;
+}
+
+int fl2000_set_transfers(struct regmap *regmap)
+{
+	u32 mask;
+
+	mask = FL2000_VGA_CTRL_ACLK_USE_PKT_PENDING | FL2000_VGA_CTRL_ACLK_USE_ZERO_TD |
+	       FL2000_VGA_CTRL_ACLK_USE_ZERO_PKT_LEN;
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, mask,
+			  FL2000_VGA_CTRL_ACLK_USE_ZERO_PKT_LEN);
+
+	regmap_write_bits(regmap, FL2000_VGA_ISOCH_REG, FL2000_VGA_ISOCH_MFRAME_CNT, 0);
+
+	return 0;
+}
+
+int fl2000_reset(struct regmap *regmap)
+{
+	regmap_write_bits(regmap, FL2000_RST_CTRL_REG, FL2000_RST_CTRL_SW_RESET,
+			  FL2000_RST_CTRL_SW_RESET);
+
+	msleep(FL2000_HW_RST_MDELAY);
+
+	return 0;
+}
+
+int fl2000_afe_magic(struct regmap *regmap)
+{
+	regmap_write_bits(regmap, FL2000_USB_LPM_REG, FL2000_USB_LPM_MAGIC, FL2000_USB_LPM_MAGIC);
+
+	return 0;
+}
+
+int fl2000_usb_magic(struct regmap *regmap)
+{
+	u32 val;
+
+	/* Writing DONE as 0 would start an I2C operation, see fl2000_i2c_dword() */
+	val = FL2000_VGA_I2C_SC_MONITOR_DETECT | FL2000_VGA_I2C_SC_EDID_DETECT |
+	      FL2000_VGA_I2C_SC_DONE;
+	regmap_write_bits(regmap, FL2000_VGA_I2C_SC_REG, val, val);
+
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_3, FL2000_VGA_CTRL3_WAKEUP_CLR_EN, 0);
+
+	val = FL2000_USB_LPM_U1_REJECT | FL2000_USB_LPM_U2_REJECT;
+	regmap_write_bits(regmap, FL2000_USB_LPM_REG, val, val);
+
+	regmap_write_bits(regmap, FL2000_USB_CTRL_REG, FL2000_USB_CTRL_WAKE_NRDY, 0);
+
+	return 0;
+}
+
+/* The interrupts about monitors: they matter from the moment the device is up, mode or no mode */
+int fl2000_enable_sink_interrupts(struct regmap *regmap)
+{
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, FL2000_VGA_CTRL_ACLK_EDID_MON_INT_EN,
+			  FL2000_VGA_CTRL_ACLK_EDID_MON_INT_EN);
+
+	regmap_write_bits(regmap, FL2000_VGA_CTRL2_REG_ACLK, FL2000_VGA_CTRL2_ACLK_HDMI_INT_EN,
+			  FL2000_VGA_CTRL2_ACLK_HDMI_INT_EN);
+
+	return 0;
+}
+
+/* The interrupts about the frames being sent */
+int fl2000_stream_interrupts(struct regmap *regmap, bool enable)
+{
+	u32 mask, val;
+
+	val = FL2000_VGA_CTRL_ACLK_VGA_ERR_INT_EN | FL2000_VGA_CTRL_ACLK_LBUF_ERR_INT_EN;
+	mask = val | FL2000_VGA_CTRL_ACLK_FEEDBACK_INT_EN;
+
+	return regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, mask, enable ? val : 0);
+}
+
+/*
+ * Read the status register. Its event bits clear on read, so whoever reads it owns the events it
+ * returns: callers must not drop them.
+ */
+int fl2000_read_status(struct regmap *regmap, u32 *status)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(regmap, FL2000_VGA_STATUS_REG, &val);
+	if (ret)
+		return ret;
+
+	/*
+	 * LBUF issues are recoverable: write their bits back. regmap_write_bits() would read the
+	 * register once more and lose any event raised since the read above.
+	 */
+	if (val & (FL2000_VGA_STATUS_LBUF_OVERFLOW | FL2000_VGA_STATUS_LBUF_UNDERFLOW))
+		regmap_write(regmap, FL2000_VGA_STATUS_REG, val & ~FL2000_VGA_STATUS_SELF_CLEAR);
+
+	/* lbuf_halt and vga_error are not handled: no recovery sequence is known */
+
+	*status = val;
+
+	return 0;
+}
+
+int fl2000_i2c_dword(struct regmap *regmap, bool read, u16 addr, u8 offset, u32 *data)
+{
+	int ret;
+	unsigned int val;
+	u32 mask;
+
+	if (!read) {
+		ret = regmap_write(regmap, FL2000_VGA_I2C_WR_REG, *data);
+		if (ret)
+			return -EIO;
+	}
+
+	/*
+	 * The detection bits always read back as 0, so they need to be set again on every write.
+	 * It is not clear whether the monitor detection circuit is needed at all for the HDMI use
+	 * case.
+	 */
+	mask = FL2000_VGA_I2C_SC_MONITOR_DETECT | FL2000_VGA_I2C_SC_EDID_DETECT |
+	       FL2000_VGA_I2C_SC_STATUS | FL2000_VGA_I2C_SC_ADDR | FL2000_VGA_I2C_SC_CMD |
+	       FL2000_VGA_I2C_SC_OFFSET | FL2000_VGA_I2C_SC_DONE;
+	val = FL2000_VGA_I2C_SC_MONITOR_DETECT | FL2000_VGA_I2C_SC_EDID_DETECT |
+	      FIELD_PREP(FL2000_VGA_I2C_SC_ADDR, addr) |
+	      FIELD_PREP(FL2000_VGA_I2C_SC_CMD, read) |
+	      FIELD_PREP(FL2000_VGA_I2C_SC_OFFSET, offset);
+
+	ret = regmap_write_bits(regmap, FL2000_VGA_I2C_SC_REG, mask, val);
+	if (ret)
+		return -EIO;
+
+	ret = regmap_read_poll_timeout(regmap, FL2000_VGA_I2C_SC_REG, val,
+				       val & FL2000_VGA_I2C_SC_DONE, I2C_RDWR_INTERVAL,
+				       I2C_RDWR_TIMEOUT);
+	/*
+	 * This shouldn't normally happen: there's internal 256ms HW timeout on I2C operations and
+	 * USB must be always available so no I/O errors. But if it happens we are probably facing
+	 * an irreversible HW issue.
+	 */
+	if (ret || FIELD_GET(FL2000_VGA_I2C_SC_STATUS, val))
+		return -EIO;
+
+	if (read) {
+		ret = regmap_read(regmap, FL2000_VGA_I2C_RD_REG, data);
+		if (ret)
+			return -EIO;
+	}
+
+	return 0;
+}
+
+/*
+ * The chip may have lost power (system sleep): forget the cached registers, so that the
+ * read-modify-write updates that follow read the chip instead of skipping writes it still needs.
+ */
+void fl2000_regmap_drop_cache(struct regmap *regmap)
+{
+	regcache_drop_region(regmap, 0, fl2000_regmap_config.max_register);
+}
+
+struct regmap *fl2000_regmap_init(struct device *dev, struct usb_device *usb_dev)
+{
+	return devm_regmap_init(dev, NULL, usb_dev, &fl2000_regmap_config);
+}
diff --git a/drivers/gpu/drm/fl2000/fl2000_registers.h b/drivers/gpu/drm/fl2000/fl2000_registers.h
new file mode 100644
index 000000000..f17431e38
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_registers.h
@@ -0,0 +1,276 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Based on the official registers description from Fresco Logic
+ *
+ * (C) Copyright 2019, Artem Mygaiev
+ */
+
+#ifndef FL2000_REGISTERS_H
+#define FL2000_REGISTERS_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+/* #### USB Control Registers Bank #### */
+/* Some unknown & undocumented FL2000 USB settings */
+
+#define FL2000_USB_CONTROL_OFFSET 0x0000
+
+#define FL2000_USB_LPM_REG (FL2000_USB_CONTROL_OFFSET + 0x70)
+#define  FL2000_USB_LPM_MAGIC				BIT(13)
+#define  FL2000_USB_LPM_U2_REJECT			BIT(19)
+#define  FL2000_USB_LPM_U1_REJECT			BIT(20)
+
+#define FL2000_USB_CTRL_REG (FL2000_USB_CONTROL_OFFSET + 0x78)
+#define  FL2000_USB_CTRL_WAKE_NRDY			BIT(17)
+
+/* #### VGA Control Registers Bank #### */
+/* Taken from the 'FL200DX Memory Mapped Address Space Registers' spec */
+
+#define FL2000_VGA_CONTROL_OFFSET 0x8000
+
+#define FL2000_VGA_STATUS_REG (FL2000_VGA_CONTROL_OFFSET + 0x00)
+#define  FL2000_VGA_STATUS_VGA_STATUS			BIT(0)
+#define  FL2000_VGA_STATUS_VGA_ERROR			BIT(1) /* read self clear */
+#define  FL2000_VGA_STATUS_LBUF_HALT			BIT(2)
+#define  FL2000_VGA_STATUS_ISO_ACK			BIT(3) /* read self clear */
+#define  FL2000_VGA_STATUS_TD_DROP			BIT(4) /* read self clear */
+#define  FL2000_VGA_STATUS_IRQ_PENDING			BIT(5) /* read self clear */
+#define  FL2000_VGA_STATUS_PLL_STATUS			BIT(6)
+#define  FL2000_VGA_STATUS_DAC_STATUS			BIT(7)
+#define  FL2000_VGA_STATUS_LBUF_OVERFLOW		BIT(8)
+#define  FL2000_VGA_STATUS_LBUF_UNDERFLOW		BIT(9)
+#define  FL2000_VGA_STATUS_FRAME_CNT			GENMASK(25, 10)
+#define  FL2000_VGA_STATUS_HDMI_EVENT			BIT(26) /* read self clear */
+#define  FL2000_VGA_STATUS_HDMI_STATUS			BIT(27)
+#define  FL2000_VGA_STATUS_EDID_STATUS			BIT(28)
+#define  FL2000_VGA_STATUS_MONITOR_STATUS		BIT(29)
+#define  FL2000_VGA_STATUS_MONITOR_EVENT		BIT(30) /* read self clear */
+#define  FL2000_VGA_STATUS_EDID_EVENT			BIT(31) /* read self clear */
+#define  FL2000_VGA_STATUS_SELF_CLEAR \
+	(FL2000_VGA_STATUS_VGA_ERROR | FL2000_VGA_STATUS_ISO_ACK | FL2000_VGA_STATUS_TD_DROP | \
+	 FL2000_VGA_STATUS_IRQ_PENDING | FL2000_VGA_STATUS_HDMI_EVENT | \
+	 FL2000_VGA_STATUS_MONITOR_EVENT | FL2000_VGA_STATUS_EDID_EVENT)
+
+#define FL2000_VGA_CTRL_REG_PXCLK (FL2000_VGA_CONTROL_OFFSET + 0x04)
+#define  FL2000_VGA_CTRL_PXCLK_CLEAR_WATERMARK		BIT(0)
+#define  FL2000_VGA_CTRL_PXCLK_FRAME_SYNC		BIT(1)
+#define  FL2000_VGA_CTRL_PXCLK_HSYNC_POLARITY		BIT(2)
+#define  FL2000_VGA_CTRL_PXCLK_VSYNC_POLARITY		BIT(3)
+#define  FL2000_VGA_CTRL_PXCLK_DE_POLARITY		BIT(4)
+#define  FL2000_VGA_CTRL_PXCLK_MIRROR_MODE		BIT(5)
+#define  FL2000_VGA_CTRL_PXCLK_VGA565_MODE		BIT(6)
+#define  FL2000_VGA_CTRL_PXCLK_DAC_OUTPUT_EN		BIT(7)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_TIMING_EN		BIT(8)
+#define  FL2000_VGA_CTRL_PXCLK_USE_NEW_PKT_RETRY	BIT(9)
+#define  FL2000_VGA_CTRL_PXCLK_REF_SELECT		BIT(10)
+#define  FL2000_VGA_CTRL_PXCLK_DAC_PX_CLK_INVERT	BIT(11)
+#define  FL2000_VGA_CTRL_PXCLK_CLEAR_LBUF_STATUS	BIT(12)
+#define  FL2000_VGA_CTRL_PXCLK_DROP_CNT			BIT(13)
+#define  FL2000_VGA_CTRL_PXCLK_USE_VDI_ITP_CNT		BIT(14)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_COMPRESS		BIT(24)
+#define  FL2000_VGA_CTRL_PXCLK_VGA332_MODE		BIT(25)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_COLOR_PALETTE_EN	BIT(26)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_FIRST_BT_ENC_EN	BIT(27)
+#define  FL2000_VGA_CTRL_PXCLK_CLEAR_125US_CNT		BIT(28)
+#define  FL2000_VGA_CTRL_PXCLK_DISABLE_HALT		BIT(29)
+#define  FL2000_VGA_CTRL_PXCLK_FORCE_DE_EN		BIT(30)
+#define  FL2000_VGA_CTRL_PXCLK_VGA555_MODE		BIT(31)
+
+#define FL2000_VGA_HSYNC_REG1 (FL2000_VGA_CONTROL_OFFSET + 0x08)
+#define  FL2000_VGA_HSYNC1_HTOTAL			GENMASK(11, 0)
+#define  FL2000_VGA_HSYNC1_HACTIVE			GENMASK(27, 16)
+
+#define FL2000_VGA_HSYNC_REG2 (FL2000_VGA_CONTROL_OFFSET + 0x0C)
+#define  FL2000_VGA_HSYNC2_HSTART			GENMASK(11, 0)
+#define  FL2000_VGA_HSYNC2_HSYNC_WIDTH			GENMASK(23, 16)
+
+#define FL2000_VGA_VSYNC_REG1 (FL2000_VGA_CONTROL_OFFSET + 0x10)
+#define  FL2000_VGA_VSYNC1_VTOTAL			GENMASK(11, 0)
+#define  FL2000_VGA_VSYNC1_VACTIVE			GENMASK(27, 16)
+
+#define FL2000_VGA_VSYNC_REG2 (FL2000_VGA_CONTROL_OFFSET + 0x14)
+#define  FL2000_VGA_VSYNC2_VSTART			GENMASK(11, 0)
+#define  FL2000_VGA_VSYNC2_VSYNC_WIDTH			GENMASK(18, 16)
+#define  FL2000_VGA_VSYNC2_START_LATENCY		GENMASK(29, 20)
+#define  FL2000_VGA_VSYNC2_BUF_ERROR_EN			BIT(31)
+
+#define FL2000_VGA_TEST_REG  (FL2000_VGA_CONTROL_OFFSET + 0x18)
+#define FL2000_VGA_ISOCH_REG (FL2000_VGA_CONTROL_OFFSET + 0x1C)
+#define  FL2000_VGA_ISOCH_START_MFRAME_CNT		GENMASK(13, 0)
+#define  FL2000_VGA_ISOCH_USE_MFRAME_MATCH		BIT(14)
+#define  FL2000_VGA_ISOCH_USE_ZERO_LEN_FRAME		BIT(15)
+#define  FL2000_VGA_ISOCH_MFRAME_CNT			GENMASK(29, 16)
+#define  FL2000_VGA_ISOCH_MFRAME_CNT_UPDATE		BIT(30)
+
+#define FL2000_VGA_I2C_SC_REG (FL2000_VGA_CONTROL_OFFSET + 0x20)
+#define  FL2000_VGA_I2C_SC_ADDR				GENMASK(6, 0)
+#define  FL2000_VGA_I2C_SC_CMD				BIT(7)
+#define  FL2000_VGA_I2C_SC_OFFSET			GENMASK(15, 8)
+#define  FL2000_VGA_I2C_SC_VGA_STATUS			GENMASK(23, 16)
+#define  FL2000_VGA_I2C_SC_STATUS			GENMASK(27, 24)
+#define  FL2000_VGA_I2C_SC_MONITOR_DETECT		BIT(28)
+#define  FL2000_VGA_I2C_SC_READY			BIT(29)
+#define  FL2000_VGA_I2C_SC_EDID_DETECT			BIT(30)
+#define  FL2000_VGA_I2C_SC_DONE				BIT(31)
+
+#define FL2000_VGA_I2C_RD_REG (FL2000_VGA_CONTROL_OFFSET + 0x24)
+#define FL2000_VGA_I2C_WR_REG (FL2000_VGA_CONTROL_OFFSET + 0x28)
+
+#define FL2000_VGA_PLL_REG (FL2000_VGA_CONTROL_OFFSET + 0x2C)
+#define  FL2000_VGA_PLL_DIVISOR				GENMASK(7, 0)
+#define  FL2000_VGA_PLL_PRESCALER			GENMASK(9, 8)
+#define  FL2000_VGA_PLL_FUNCTION			GENMASK(14, 13)
+#define  FL2000_VGA_PLL_MULTIPLIER			GENMASK(23, 16)
+#define  FL2000_VGA_PLL_TEST_IO				BIT(24)
+#define  FL2000_VGA_PLL_CFG_DAC_PWRDOWN			BIT(25)
+#define  FL2000_VGA_PLL_FORCE_DAC_PWRUP			BIT(26)
+
+#define FL2000_VGA_LBUF_REG (FL2000_VGA_CONTROL_OFFSET + 0x30)
+#define  FL2000_VGA_LBUF_WATERMARK_ASSERT_RDY		GENMASK(14, 0)
+
+#define FL2000_VGA_HI_MARK (FL2000_VGA_CONTROL_OFFSET + 0x34)
+#define  FL2000_VGA_HI_MARK_HIGH_WATERMARK		GENMASK(16, 0)
+
+#define FL2000_VGA_LO_MARK (FL2000_VGA_CONTROL_OFFSET + 0x38)
+#define  FL2000_VGA_LO_MARK_LOW_WATERMARK		GENMASK(16, 0)
+
+#define FL2000_VGA_CTRL_REG_ACLK (FL2000_VGA_CONTROL_OFFSET + 0x3C)
+#define  FL2000_VGA_CTRL_ACLK_CFG_TIMING_RESET_N	BIT(0)
+#define  FL2000_VGA_CTRL_ACLK_PLH_BLOCK_EN		BIT(1)
+#define  FL2000_VGA_CTRL_ACLK_EDID_MON_INT_EN		BIT(2)
+#define  FL2000_VGA_CTRL_ACLK_EXT_MON_INT_EN		BIT(3)
+#define  FL2000_VGA_CTRL_ACLK_VGA_STATUS_SELF_CLEAR	BIT(4)
+#define  FL2000_VGA_CTRL_ACLK_PLL_LOCK_TIME		GENMASK(9, 5)
+#define  FL2000_VGA_CTRL_ACLK_PLL_FAST_TIMEOUT_EN	BIT(10)
+#define  FL2000_VGA_CTRL_ACLK_PPE_BLOCK_EM		BIT(11)
+#define  FL2000_VGA_CTRL_ACLK_PLL_TIMER_EN		BIT(12)
+#define  FL2000_VGA_CTRL_ACLK_FEEDBACK_INT_EN		BIT(13)
+#define  FL2000_VGA_CTRL_ACLK_CLR_125US_COUNTER		BIT(14)
+#define  FL2000_VGA_CTRL_ACLK_CCS_PD_DIS		BIT(15)
+#define  FL2000_VGA_CTRL_ACLK_STANDBY_EN		BIT(16)
+#define  FL2000_VGA_CTRL_ACLK_FORCE_LOOPBACK		BIT(17)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_DROP_FRAME_EN	BIT(18)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_VDE_RST_EN		BIT(19)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_SW_RST		BIT(20)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_ERR_INT_EN		BIT(21)
+#define  FL2000_VGA_CTRL_ACLK_BIAC_EN			BIT(22)
+#define  FL2000_VGA_CTRL_ACLK_PXCLK_IN_EN		BIT(23)
+#define  FL2000_VGA_CTRL_ACLK_VGA_ERR_INT_EN		BIT(24)
+#define  FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT		BIT(25)
+#define  FL2000_VGA_CTRL_ACLK_FORCE_PLL_UP		BIT(26)
+#define  FL2000_VGA_CTRL_ACLK_USE_ZERO_TD		BIT(27)
+#define  FL2000_VGA_CTRL_ACLK_USE_ZERO_PKT_LEN		BIT(28)
+#define  FL2000_VGA_CTRL_ACLK_USE_PKT_PENDING		BIT(29)
+#define  FL2000_VGA_CTRL_ACLK_PLL_DAC_PD_USBP3_EN	BIT(30)
+#define  FL2000_VGA_CTRL_ACLK_PLL_DAC_PD_NOVGA_EN	BIT(31)
+
+#define FL2000_VGA_PXCLK_CNT_REG (FL2000_VGA_CONTROL_OFFSET + 0x40)
+#define  FL2000_VGA_PXCLK_CNT_PIX_CLOCK_COUNT		GENMASK(27, 0)
+
+#define FL2000_VGA_VCNT_REG (FL2000_VGA_CONTROL_OFFSET + 0x44)
+#define  FL2000_VGA_VCNT_MAX_ACLK_COUNT			GENMASK(14, 0)
+#define  FL2000_VGA_VCNT_MAX_ACLK_COUNT_HIT		BIT(15)
+#define  FL2000_VGA_VCNT_MAX_LBUF_ACCUMULATE		GENMASK(31, 16)
+
+#define FL2000_RST_CTRL_REG (FL2000_VGA_CONTROL_OFFSET + 0x48)
+#define  FL2000_RST_CTRL_DIS_HOT_RST2_PORT		BIT(0)
+#define  FL2000_RST_CTRL_DIS_WARM_RST2_PORT		BIT(1)
+#define  FL2000_RST_CTRL_DIS_HOT_RESET_PIPE		BIT(2)
+#define  FL2000_RST_CTRL_DIS_WARM_RESET_PIPE		BIT(3)
+#define  FL2000_RST_CTRL_DIS_HOT_RESET_PIX		BIT(4)
+#define  FL2000_RST_CTRL_DIS_WARM_RESET_PIX		BIT(5)
+#define  FL2000_RST_CTRL_DIS_USB2_RESET_PIX		BIT(6)
+#define  FL2000_RST_CTRL_DIS_PLL_RESET_PIX		BIT(7)
+#define  FL2000_RST_CTRL_DIS_SW_RESET_PIX		BIT(8)
+#define  FL2000_RST_CTRL_DIS_USB2_RESET_BUF		BIT(9)
+#define  FL2000_RST_CTRL_DIS_SW_RESET_BUF		BIT(10)
+#define  FL2000_RST_CTRL_DIS_LBUF_RESET_PIX		BIT(11)
+#define  FL2000_RST_CTRL_DIS_HOT_RESET_PORT		BIT(12)
+#define  FL2000_RST_CTRL_DIS_WARM_RESET_PORT		BIT(13)
+#define  FL2000_RST_CTRL_SET_SLOW_CLK_PREDFT		BIT(14)
+#define  FL2000_RST_CTRL_SW_RESET			BIT(15)
+#define  FL2000_RST_CTRL_FRAME_FIRST_ITP_WL		GENMASK(31, 16)
+
+#define FL2000_BIAC_CTRL1_REG (FL2000_VGA_CONTROL_OFFSET + 0x4C)
+#define  FL2000_BIAC_CTRL1_CTRL_LO8			GENMASK(7, 0)
+#define  FL2000_BIAC_CTRL1_FRAME_MULT			GENMASK(15, 8)
+#define  FL2000_BIAC_CTRL1_125US_MULT			GENMASK(31, 16)
+
+#define FL2000_BIAC_CTRL2_REG (FL2000_VGA_CONTROL_OFFSET + 0x50)
+#define  FL2000_BIAC_CTRL2_CTRL_HI16			GENMASK(15, 0)
+
+#define FL2000_BIAC_STATUS_REG (FL2000_VGA_CONTROL_OFFSET + 0x54)
+#define  FL2000_BIAC_STATUS_CURRENT_STATUS		GENMASK(15, 0)
+#define  FL2000_BIAC_STATUS_CURRENT_VALUE		GENMASK(31, 16)
+
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x58) */
+
+#define FL2000_VGA_PLT_REG_PXCLK (FL2000_VGA_CONTROL_OFFSET + 0x5C)
+#define  FL2000_VGA_PLT_PXCLK_PALETTE_RAM_WR_DATA	GENMASK(23, 0)
+
+#define FL2000_VGA_PLT_RADDR_REG_PXCLK (FL2000_VGA_CONTROL_OFFSET + 0x60)
+#define  FL2000_VGA_PLT_RADDR_PALETTE_RAM_RD_ADDR	GENMASK(7, 0)
+#define  FL2000_VGA_PLT_RADDR_LAST_FRAME_LBUF_WATERMARK	GENMASK(23, 8)
+
+#define FL2000_VGA_CTRL2_REG_ACLK (FL2000_VGA_CONTROL_OFFSET + 0x64)
+#define  FL2000_VGA_CTRL2_ACLK_PLL_POWERDOWN_DETECT_EN	BIT(0)
+#define  FL2000_VGA_CTRL2_ACLK_MSTOR_BLKSIZE_PTR_WIDTH	GENMASK(3, 1)
+#define  FL2000_VGA_CTRL2_ACLK_MSTOR_BLK_COUNT		GENMASK(15, 4)
+#define  FL2000_VGA_CTRL2_ACLK_SPI_WR_EN		BIT(16)
+#define  FL2000_VGA_CTRL2_ACLK_DETECT_PINS_DEBOUNCE_EN	BIT(17)
+#define  FL2000_VGA_CTRL2_ACLK_HDMI_INT_EN		BIT(18)
+#define  FL2000_VGA_CTRL2_ACLK_HDMI_INT_ACTIVE_HIGH	BIT(19)
+#define  FL2000_VGA_CTRL2_ACLK_SPI_EN			BIT(20)
+#define  FL2000_VGA_CTRL2_ACLK_SW_PROD_REV		GENMASK(30, 23)
+
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x68) */
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x6C) */
+
+#define FL2000_TEST_CNTL_REG1 (FL2000_VGA_CONTROL_OFFSET + 0x70)
+#define FL2000_TEST_CNTL_REG2 (FL2000_VGA_CONTROL_OFFSET + 0x74)
+#define FL2000_TEST_CNTL_REG3 (FL2000_VGA_CONTROL_OFFSET + 0x78)
+#define FL2000_TEST_STAT1     (FL2000_VGA_CONTROL_OFFSET + 0x7C)
+#define FL2000_TEST_STAT2     (FL2000_VGA_CONTROL_OFFSET + 0x80)
+#define FL2000_TEST_STAT3     (FL2000_VGA_CONTROL_OFFSET + 0x84)
+
+#define FL2000_VGA_CTRL_REG_3 (FL2000_VGA_CONTROL_OFFSET + 0x88)
+#define  FL2000_VGA_CTRL3_WAKEUP_CLR_EN			BIT(10)
+
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x8C) */
+
+/*
+ * List of registers that shall not be read automatically (e.g. for cache update) due to presence
+ * of self-clear bits
+ */
+static inline bool FL2000_REG_PRECIOUS(u32 reg)
+{
+	return reg == FL2000_VGA_STATUS_REG;
+}
+
+/* List of volatile registers that shall not be cached */
+static inline bool FL2000_REG_VOLATILE(u32 reg)
+{
+	switch (reg) {
+	case FL2000_VGA_STATUS_REG:
+	case FL2000_VGA_CTRL_REG_PXCLK:
+	case FL2000_VGA_ISOCH_REG:
+	case FL2000_VGA_I2C_SC_REG:
+	case FL2000_VGA_I2C_RD_REG:
+	case FL2000_VGA_PXCLK_CNT_REG:
+	case FL2000_VGA_VCNT_REG:
+	case FL2000_RST_CTRL_REG:
+	case FL2000_BIAC_STATUS_REG:
+	case FL2000_VGA_PLT_RADDR_REG_PXCLK:
+	case FL2000_TEST_CNTL_REG1:
+	case FL2000_TEST_CNTL_REG2:
+	case FL2000_TEST_STAT1:
+	case FL2000_TEST_STAT2:
+	case FL2000_TEST_STAT3:
+		return true;
+	default:
+		return false;
+	}
+}
+
+#endif /* FL2000_REGISTERS_H */

-- 
2.56.0


WARNING: multiple messages have this Message-ID (diff)
From: Angel Gabriel Crispin Valdivia via B4 Relay <devnull+angelcrispinvaldivia.gmail.com@kernel.org>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	 Maxime Ripard <mripard@kernel.org>,
	Thomas Zimmermann <tzimmermann@suse.de>,
	 David Airlie <airlied@gmail.com>,
	Simona Vetter <simona@ffwll.ch>,
	 Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org
Subject: [PATCH v2 1/4] drm/fl2000: Add USB driver with register and I2C access
Date: Wed, 11 Nov 2026 22:34:08 -0500	[thread overview]
Message-ID: <20261111-fl2000-upstream-v2-1-201f87708cb0@gmail.com> (raw)
In-Reply-To: <20261111-fl2000-upstream-v2-0-201f87708cb0@gmail.com>

From: Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>

The Fresco Logic FL2000DX is a USB 3.0 display controller used in
inexpensive USB to VGA and USB to HDMI adapters (USB ID 1d5c:2000). The
FL2000 itself outputs parallel RGB/VGA; HDMI adapters pair it with an
ITE IT66121 HDMI transmitter that sits on the FL2000's I2C bus.

This first patch adds the USB side of the driver: it matches interface
0 and claims the other two FL2000 interfaces from its probe, selects the
altsetting that has the bulk endpoint, describes the FL2000 registers,
provides a regmap on top of the vendor control transfers that access
them, and registers an I2C adapter backed by the FL2000 I2C engine. The
engine only transfers aligned 32-bit words, so byte accesses are done as
read-modify-write of the containing word and the adapter advertises the
matching quirks.

Everything hangs off the binding of interface 0 and goes away with it;
unbinding any of the interfaces takes the others along.

The driver is based on the out-of-tree fl2000_drm driver by Artem
Mygaiev, which carries code from the Fresco Logic reference driver.

Signed-off-by: Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
---
 MAINTAINERS                               |   7 +
 drivers/gpu/drm/Kconfig                   |   1 +
 drivers/gpu/drm/Makefile                  |   1 +
 drivers/gpu/drm/fl2000/Kconfig            |  19 ++
 drivers/gpu/drm/fl2000/Makefile           |   8 +
 drivers/gpu/drm/fl2000/fl2000.h           |  89 +++++++++
 drivers/gpu/drm/fl2000/fl2000_drv.c       | 163 ++++++++++++++++
 drivers/gpu/drm/fl2000/fl2000_i2c.c       | 179 +++++++++++++++++
 drivers/gpu/drm/fl2000/fl2000_registers.c | 315 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/fl2000/fl2000_registers.h | 276 ++++++++++++++++++++++++++
 10 files changed, 1058 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c10342aa3..6e74cb478 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8324,6 +8324,13 @@ F:	include/linux/aperture.h
 F:	include/linux/sysfb.h
 F:	include/video/nomodeset.h
 
+DRM DRIVER FOR FRESCO LOGIC FL2000 USB DISPLAY ADAPTERS
+M:	Angel Gabriel Crispin Valdivia <angelcrispinvaldivia@gmail.com>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	drivers/gpu/drm/fl2000/
+
 DRM DRIVER FOR GENERIC EDP PANELS
 R:	Douglas Anderson <dianders@chromium.org>
 F:	Documentation/devicetree/bindings/display/panel/panel-edp.yaml
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 61111d50b..6ff093830 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -313,6 +313,7 @@ source "drivers/gpu/drm/atmel-hlcdc/Kconfig"
 source "drivers/gpu/drm/bridge/Kconfig"
 source "drivers/gpu/drm/etnaviv/Kconfig"
 source "drivers/gpu/drm/exynos/Kconfig"
+source "drivers/gpu/drm/fl2000/Kconfig"
 source "drivers/gpu/drm/fsl-dcu/Kconfig"
 source "drivers/gpu/drm/gma500/Kconfig"
 source "drivers/gpu/drm/gud/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2c6bc093a..a4a8a958a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -190,6 +190,7 @@ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
 obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
 obj-$(CONFIG_DRM_GMA500) += gma500/
+obj-$(CONFIG_DRM_FL2000) += fl2000/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
 obj-$(CONFIG_DRM_ARMADA) += armada/
diff --git a/drivers/gpu/drm/fl2000/Kconfig b/drivers/gpu/drm/fl2000/Kconfig
new file mode 100644
index 000000000..bf1badc3e
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_FL2000
+	tristate "Fresco Logic FL2000 USB display adapters"
+	depends on DRM && USB && MMU
+	depends on USB_ARCH_HAS_HCD
+	select DRM_BRIDGE_CONNECTOR
+	select DRM_CLIENT_SELECTION
+	select DRM_DISPLAY_HELPER
+	select DRM_GEM_SHMEM_HELPER
+	select DRM_KMS_HELPER
+	select I2C
+	select REGMAP
+	select REGMAP_I2C
+	help
+	  This is a KMS driver for USB display adapters based on the
+	  Fresco Logic FL2000DX USB 3.0 to VGA controller, paired with an
+	  ITE IT66121 HDMI transmitter (USB ID 1d5c:2000).
+
+	  If M is selected the module will be called fl2000.
diff --git a/drivers/gpu/drm/fl2000/Makefile b/drivers/gpu/drm/fl2000/Makefile
new file mode 100644
index 000000000..2b3a2f151
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+fl2000-y := \
+	fl2000_drv.o \
+	fl2000_i2c.o \
+	fl2000_registers.o
+
+obj-$(CONFIG_DRM_FL2000) += fl2000.o
diff --git a/drivers/gpu/drm/fl2000/fl2000.h b/drivers/gpu/drm/fl2000/fl2000.h
new file mode 100644
index 000000000..9b903e68c
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2020, Artem Mygaiev
+ */
+
+#ifndef FL2000_H
+#define FL2000_H
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+
+#include "fl2000_registers.h"
+
+/* Known USB interfaces of FL2000 */
+enum fl2000_interface {
+	FL2000_USBIF_AVCONTROL = 0,
+	FL2000_USBIF_STREAMING = 1,
+	FL2000_USBIF_INTERRUPT = 2,
+	FL2000_USBIF_NUM,
+};
+
+struct fl2000_timings {
+	u32 hactive;
+	u32 htotal;
+	u32 hsync_width;
+	u32 hstart;
+	u32 vactive;
+	u32 vtotal;
+	u32 vsync_width;
+	u32 vstart;
+};
+
+struct fl2000_pll {
+	u32 prescaler;
+	u32 multiplier;
+	u32 divisor;
+	u32 function;
+};
+
+/* Polling interval and timeout in us for I2C read/write operations */
+#define I2C_RDWR_INTERVAL 200
+#define I2C_RDWR_TIMEOUT  (256 * 1000)
+
+/* I2C adapter interface creation */
+struct i2c_adapter *fl2000_i2c_init(struct usb_interface *interface, struct regmap *regmap);
+int fl2000_ddc_read_block(struct i2c_adapter *adapter, u8 *buf, unsigned int block, size_t len);
+
+/* Register map creation */
+struct regmap *fl2000_regmap_init(struct device *dev, struct usb_device *usb_dev);
+void fl2000_regmap_drop_cache(struct regmap *regmap);
+
+/* Registers interface */
+int fl2000_reset(struct regmap *regmap);
+int fl2000_usb_magic(struct regmap *regmap);
+int fl2000_afe_magic(struct regmap *regmap);
+int fl2000_set_transfers(struct regmap *regmap);
+int fl2000_set_pixfmt(struct regmap *regmap, u32 bytes_pix);
+int fl2000_set_timings(struct regmap *regmap, struct fl2000_timings *timings);
+int fl2000_set_pll(struct regmap *regmap, struct fl2000_pll *pll);
+int fl2000_force_connect(struct regmap *regmap, bool force);
+bool fl2000_force_connect_active(struct regmap *regmap);
+int fl2000_enable_sink_interrupts(struct regmap *regmap);
+int fl2000_stream_interrupts(struct regmap *regmap, bool enable);
+int fl2000_read_status(struct regmap *regmap, u32 *status);
+int fl2000_i2c_dword(struct regmap *regmap, bool read, u16 addr, u8 offset, u32 *data);
+
+#endif /* FL2000_H */
diff --git a/drivers/gpu/drm/fl2000/fl2000_drv.c b/drivers/gpu/drm/fl2000/fl2000_drv.c
new file mode 100644
index 000000000..8e41de8f5
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_drv.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2020, Artem Mygaiev
+ */
+
+#include "fl2000.h"
+
+#define USB_VENDOR_FRESCO_LOGIC 0x1D5C
+#define USB_PRODUCT_FL2000	0x2000
+
+static struct usb_driver fl2000_driver;
+
+/*
+ * The three interfaces of the display function only work together, and the driver binds to the
+ * device through the first one: it matches interface 0 and claims the others from its probe, as
+ * other multi-interface USB drivers do. Everything else hangs off the binding of interface 0 and
+ * goes away with it, whatever the reason.
+ */
+static const struct usb_device_id fl2000_id_table[] = {
+	{
+		.match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_CLASS |
+			       USB_DEVICE_ID_MATCH_INT_NUMBER,
+		.idVendor = USB_VENDOR_FRESCO_LOGIC,
+		.idProduct = USB_PRODUCT_FL2000,
+		.bInterfaceClass = USB_CLASS_AUDIO_VIDEO,
+		.bInterfaceNumber = FL2000_USBIF_AVCONTROL,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(usb, fl2000_id_table);
+
+struct fl2000_devs {
+	struct regmap *regmap;
+	struct i2c_adapter *adapter;
+};
+
+/* Interface 0 is what the driver is probing: claim the others */
+static int fl2000_claim_interfaces(struct usb_device *usb_dev, struct fl2000_devs *devs)
+{
+	int ret;
+
+	for (int i = FL2000_USBIF_AVCONTROL + 1; i < FL2000_USBIF_NUM; i++) {
+		ret = usb_driver_claim_interface(&fl2000_driver, usb_ifnum_to_if(usb_dev, i), devs);
+		if (ret) {
+			dev_err(&usb_dev->dev, "Cannot claim interface %d (%d)\n", i, ret);
+			while (--i > FL2000_USBIF_AVCONTROL) {
+				struct usb_interface *other = usb_ifnum_to_if(usb_dev, i);
+
+				usb_set_intfdata(other, NULL);
+				usb_driver_release_interface(&fl2000_driver, other);
+			}
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/* Let go of all interfaces but @interface, which is being unbound if it is one of them */
+static void fl2000_release_interfaces(struct usb_device *usb_dev, struct usb_interface *interface)
+{
+	for (int i = 0; i < FL2000_USBIF_NUM; i++) {
+		struct usb_interface *other = usb_ifnum_to_if(usb_dev, i);
+
+		if (other != interface)
+			usb_driver_release_interface(&fl2000_driver, other);
+	}
+}
+
+static int fl2000_probe(struct usb_interface *interface, const struct usb_device_id *usb_dev_id)
+{
+	int ret;
+	struct usb_device *usb_dev = interface_to_usbdev(interface);
+	struct fl2000_devs *devs;
+
+	if (usb_dev->speed < USB_SPEED_HIGH) {
+		dev_err(&interface->dev, "USB 1.1 is not supported\n");
+		return -ENODEV;
+	}
+
+	/* Frames go to the bulk endpoint of altsetting 1, and the chip has always been used so */
+	ret = usb_set_interface(usb_dev, FL2000_USBIF_AVCONTROL, 1);
+	if (ret) {
+		dev_err(&interface->dev, "Cannot set streaming interface for bulk transfers\n");
+		return ret;
+	}
+
+	devs = devm_kzalloc(&interface->dev, sizeof(*devs), GFP_KERNEL);
+	if (!devs)
+		return -ENOMEM;
+
+	devs->regmap = fl2000_regmap_init(&interface->dev, usb_dev);
+	if (IS_ERR(devs->regmap)) {
+		dev_err(&interface->dev, "Cannot initialize regmap (%pe)\n", devs->regmap);
+		return PTR_ERR(devs->regmap);
+	}
+
+	devs->adapter = fl2000_i2c_init(interface, devs->regmap);
+	if (IS_ERR(devs->adapter)) {
+		dev_err(&interface->dev, "Cannot initialize I2C adapter (%pe)\n", devs->adapter);
+		return PTR_ERR(devs->adapter);
+	}
+
+	ret = fl2000_claim_interfaces(usb_dev, devs);
+	if (ret)
+		return ret;
+
+	usb_set_intfdata(interface, devs);
+
+	return 0;
+}
+
+static void fl2000_disconnect(struct usb_interface *interface)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(interface);
+	struct fl2000_devs *devs = usb_get_intfdata(interface);
+
+	/* This interface was only let go of, by the disconnect of another one */
+	if (!devs)
+		return;
+
+	for (int i = 0; i < FL2000_USBIF_NUM; i++)
+		usb_set_intfdata(usb_ifnum_to_if(usb_dev, i), NULL);
+
+	/* Last, as devs goes with interface 0, which may be one of these */
+	fl2000_release_interfaces(usb_dev, interface);
+}
+
+static int fl2000_suspend(struct usb_interface *interface, pm_message_t message)
+{
+	/*
+	 * Nothing to save yet. Providing the callback keeps the USB core from
+	 * unbinding the driver across system sleep.
+	 */
+	dev_dbg(&interface->dev, "suspend\n");
+
+	return 0;
+}
+
+static int fl2000_resume(struct usb_interface *interface)
+{
+	dev_dbg(&interface->dev, "resume\n");
+
+	return 0;
+}
+
+static struct usb_driver fl2000_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = fl2000_probe,
+	.disconnect = fl2000_disconnect,
+	.suspend = fl2000_suspend,
+	.resume = fl2000_resume,
+	.id_table = fl2000_id_table,
+	.supports_autosuspend = false,
+	.disable_hub_initiated_lpm = true,
+};
+
+module_usb_driver(fl2000_driver);
+
+MODULE_AUTHOR("Artem Mygaiev");
+MODULE_DESCRIPTION("FL2000 USB display driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/fl2000/fl2000_i2c.c b/drivers/gpu/drm/fl2000/fl2000_i2c.c
new file mode 100644
index 000000000..97c8cf573
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_i2c.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2019, Artem Mygaiev
+ */
+
+#include <linux/unaligned.h>
+
+#include <drm/drm_edid.h>
+
+#include "fl2000.h"
+
+/*
+ * The I2C controller requires a mandatory 8-bit sub-address for any read/write operation. Each
+ * read or write operates on 8-bit data. Every exchange shall consist of 2 messages (sub-address +
+ * data) combined. The USB transfer always aligns the address to a 4-byte boundary.
+ */
+#define I2C_CMESSAGES_NUM  2
+#define I2C_REG_ADDR_SIZE  (sizeof(u8))
+#define I2C_REG_DATA_SIZE  (sizeof(u8))
+#define I2C_XFER_ADDR_MASK (~0x3ul)
+
+static inline int fl2000_i2c_read_dword(struct regmap *regmap, u16 addr, u8 offset, u32 *data)
+{
+	return fl2000_i2c_dword(regmap, true, addr, offset, data);
+}
+
+static inline int fl2000_i2c_write_dword(struct regmap *regmap, u16 addr, u8 offset, u32 *data)
+{
+	return fl2000_i2c_dword(regmap, false, addr, offset, data);
+}
+
+static int fl2000_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
+{
+	int ret;
+	bool read;
+	u16 addr;
+	u8 idx;
+	u8 offset;
+	u32 data;
+
+	/*
+	 * The adapter quirks only let through:
+	 * - 2 messages of 1 byte each, first write then read
+	 * - 1 message of at most 2 bytes
+	 */
+	if (num == 2)
+		read = true;
+	else if (msgs[0].len == 2 && !(msgs[0].flags & I2C_M_RD))
+		read = false;
+	else
+		return -EOPNOTSUPP;
+
+	addr = msgs[0].addr;
+	offset = msgs[0].buf[0] & I2C_XFER_ADDR_MASK;
+	idx = msgs[0].buf[0] - offset;
+
+	/*
+	 * The FL2000 I2C engine only transfers aligned dwords, so the offset is aligned down to a
+	 * 4-byte boundary and the requested byte is picked out of (or merged into) the dword.
+	 */
+	if (read) {
+		ret = fl2000_i2c_read_dword(adapter->algo_data, addr, offset, &data);
+		if (ret)
+			return ret;
+
+		msgs[1].buf[0] = data >> (idx * 8);
+	} else {
+		/*
+		 * Since the FL2000 I2C engine always operates on 4-byte messages, we need to read
+		 * before write in order not to corrupt the unrelated registers of the same dword.
+		 */
+		ret = fl2000_i2c_read_dword(adapter->algo_data, addr, offset, &data);
+		if (ret)
+			return ret;
+
+		data &= ~(0xffU << (idx * 8));
+		data |= (u32)msgs[0].buf[1] << (idx * 8);
+
+		ret = fl2000_i2c_write_dword(adapter->algo_data, addr, offset, &data);
+		if (ret)
+			return ret;
+	}
+
+	return num;
+}
+
+/*
+ * Monitor DDC on the FL2000 bus. The bridge's own EDID command does not work behind this engine,
+ * and the VGA connector has no bridge at all, so EDID is read with the FL2000 I2C engine at address
+ * 0x50, 4 bytes per operation with an 8-bit offset, which covers EDID blocks 0 and 1.
+ */
+#define FL2000_DDC_ADDR 0x50
+
+/**
+ * fl2000_ddc_read_block() - read one EDID block through the FL2000 I2C engine
+ * @adapter: the FL2000 I2C adapter
+ * @buf: destination, @len bytes
+ * @block: EDID block number (0 or 1)
+ * @len: bytes to read, at most EDID_LENGTH
+ *
+ * The signature matches what drm_edid_read_custom() expects, with the adapter as context.
+ *
+ * Return: 0 on success, a negative error code otherwise
+ */
+int fl2000_ddc_read_block(struct i2c_adapter *adapter, u8 *buf, unsigned int block, size_t len)
+{
+	struct regmap *regmap = adapter->algo_data;
+	unsigned int start = block * EDID_LENGTH;
+	int ret = 0;
+
+	if (block > 1 || len > EDID_LENGTH)
+		return -EOPNOTSUPP;
+
+	/* One operation of the engine is a sequence of register accesses: keep others out */
+	i2c_lock_bus(adapter, I2C_LOCK_SEGMENT);
+	for (unsigned int i = 0; i < len; i += 4) {
+		u32 data;
+
+		ret = fl2000_i2c_dword(regmap, true, FL2000_DDC_ADDR, start + i, &data);
+		if (ret)
+			break;
+
+		put_unaligned_le32(data, buf + i);
+	}
+	i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);
+
+	return ret;
+}
+
+static u32 fl2000_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm fl2000_i2c_algorithm = {
+	.xfer = fl2000_i2c_xfer,
+	.functionality = fl2000_i2c_func,
+};
+
+static const struct i2c_adapter_quirks fl2000_i2c_quirks = {
+	/* Combined transfers only: an address write, then a read, on the same address */
+	.flags = I2C_AQ_COMB_WRITE_THEN_READ | I2C_AQ_NO_ZERO_LEN,
+	.max_num_msgs = I2C_CMESSAGES_NUM,
+	.max_write_len = 2 * I2C_REG_DATA_SIZE,
+	.max_read_len = I2C_REG_DATA_SIZE,
+	.max_comb_1st_msg_len = I2C_REG_ADDR_SIZE,
+	.max_comb_2nd_msg_len = I2C_REG_DATA_SIZE,
+};
+
+struct i2c_adapter *fl2000_i2c_init(struct usb_interface *interface, struct regmap *regmap)
+{
+	int ret;
+	struct i2c_adapter *adapter;
+	struct usb_device *usb_dev = interface_to_usbdev(interface);
+	u8 usb_path[32];
+
+	/* Adapter must be allocated before anything else */
+	adapter = devm_kzalloc(&interface->dev, sizeof(*adapter), GFP_KERNEL);
+	if (!adapter)
+		return ERR_PTR(-ENOMEM);
+
+	adapter->owner = THIS_MODULE;
+	adapter->algo = &fl2000_i2c_algorithm;
+	adapter->quirks = &fl2000_i2c_quirks;
+	adapter->algo_data = regmap;
+	adapter->dev.parent = &interface->dev;
+	strscpy(adapter->name, "FL2000 bridge I2C bus", sizeof(adapter->name));
+
+	ret = devm_i2c_add_adapter(&interface->dev, adapter);
+	if (ret)
+		return ERR_PTR(ret);
+
+	usb_make_path(usb_dev, usb_path, sizeof(usb_path));
+	dev_dbg(&interface->dev, "Created FL2000 bridge I2C bus %d at interface %s\n",
+		i2c_adapter_id(adapter), usb_path);
+
+	return adapter;
+}
diff --git a/drivers/gpu/drm/fl2000/fl2000_registers.c b/drivers/gpu/drm/fl2000/fl2000_registers.c
new file mode 100644
index 000000000..f1cd28434
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_registers.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2017, Fresco Logic, Incorporated.
+ * (C) Copyright 2018-2019, Artem Mygaiev
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+
+#include "fl2000.h"
+
+#define CONTROL_MSG_READ  64
+#define CONTROL_MSG_WRITE 65
+
+#define FL2000_HW_RST_MDELAY 10
+
+static bool fl2000_reg_precious(struct device *dev, unsigned int reg)
+{
+	return FL2000_REG_PRECIOUS(reg);
+}
+
+static bool fl2000_reg_volatile(struct device *dev, unsigned int reg)
+{
+	return FL2000_REG_VOLATILE(reg);
+}
+
+/* Protected by internal regmap mutex */
+static int fl2000_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct usb_device *usb_dev = context;
+	__le32 data;
+	int ret;
+
+	ret = usb_control_msg_recv(usb_dev, 0, CONTROL_MSG_READ, USB_DIR_IN | USB_TYPE_VENDOR, 0,
+				   reg, &data, sizeof(data), USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	*val = le32_to_cpu(data);
+
+	return 0;
+}
+
+/* Protected by internal regmap mutex */
+static int fl2000_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct usb_device *usb_dev = context;
+	__le32 data = cpu_to_le32(val);
+
+	return usb_control_msg_send(usb_dev, 0, CONTROL_MSG_WRITE, USB_DIR_OUT | USB_TYPE_VENDOR, 0,
+				    reg, &data, sizeof(data), USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
+}
+
+/*
+ * We do not use default values as per documentation because:
+ *  a) somehow they differ from real HW
+ *  b) on SW reset not all of them are cleared
+ */
+static const struct regmap_config fl2000_regmap_config = {
+	.val_bits = 32,
+	.reg_bits = 16,
+	.reg_stride = 4,
+	.max_register = 0xFFFF,
+
+	.cache_type = REGCACHE_MAPLE,
+
+	.precious_reg = fl2000_reg_precious,
+	.volatile_reg = fl2000_reg_volatile,
+
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+
+	.reg_read = fl2000_reg_read,
+	.reg_write = fl2000_reg_write,
+
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+int fl2000_set_pll(struct regmap *regmap, struct fl2000_pll *pll)
+{
+	u32 val;
+
+	val = FIELD_PREP(FL2000_VGA_PLL_PRESCALER, pll->prescaler) |
+	      FIELD_PREP(FL2000_VGA_PLL_MULTIPLIER, pll->multiplier) |
+	      FIELD_PREP(FL2000_VGA_PLL_DIVISOR, pll->divisor) |
+	      FIELD_PREP(FL2000_VGA_PLL_FUNCTION, pll->function);
+	regmap_write(regmap, FL2000_VGA_PLL_REG, val);
+
+	val = FL2000_VGA_CTRL_ACLK_FORCE_PLL_UP;
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, val, val);
+
+	return 0;
+}
+
+/*
+ * Make the chip believe a monitor is connected whatever its own sense says. The HDMI path has
+ * always run with this set, so it stays set while HDMI is driven. It must be clear while only VGA
+ * is driven: it hides the cable being unplugged, and with it clear the chip powers its DAC and PLL
+ * by itself whenever it senses a monitor.
+ */
+int fl2000_force_connect(struct regmap *regmap, bool force)
+{
+	return regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK,
+				 FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT,
+				 force ? FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT : 0);
+}
+
+/* Is the chip's monitor sense forced on: then it does not follow any cable */
+bool fl2000_force_connect_active(struct regmap *regmap)
+{
+	return regmap_test_bits(regmap, FL2000_VGA_CTRL_REG_ACLK,
+				FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT) > 0;
+}
+
+int fl2000_set_timings(struct regmap *regmap, struct fl2000_timings *timings)
+{
+	regmap_write(regmap, FL2000_VGA_HSYNC_REG1,
+		     FIELD_PREP(FL2000_VGA_HSYNC1_HACTIVE, timings->hactive) |
+		     FIELD_PREP(FL2000_VGA_HSYNC1_HTOTAL, timings->htotal));
+
+	regmap_write(regmap, FL2000_VGA_HSYNC_REG2,
+		     FIELD_PREP(FL2000_VGA_HSYNC2_HSYNC_WIDTH, timings->hsync_width) |
+		     FIELD_PREP(FL2000_VGA_HSYNC2_HSTART, timings->hstart));
+
+	regmap_write(regmap, FL2000_VGA_VSYNC_REG1,
+		     FIELD_PREP(FL2000_VGA_VSYNC1_VACTIVE, timings->vactive) |
+		     FIELD_PREP(FL2000_VGA_VSYNC1_VTOTAL, timings->vtotal));
+
+	regmap_write(regmap, FL2000_VGA_VSYNC_REG2,
+		     FIELD_PREP(FL2000_VGA_VSYNC2_VSYNC_WIDTH, timings->vsync_width) |
+		     FIELD_PREP(FL2000_VGA_VSYNC2_VSTART, timings->vstart) |
+		     FIELD_PREP(FL2000_VGA_VSYNC2_START_LATENCY, timings->vstart));
+
+	return 0;
+}
+
+int fl2000_set_pixfmt(struct regmap *regmap, u32 bytes_pix)
+{
+	u32 mask, val;
+
+	mask = FL2000_VGA_CTRL_PXCLK_DAC_OUTPUT_EN | FL2000_VGA_CTRL_PXCLK_DROP_CNT |
+	       FL2000_VGA_CTRL_PXCLK_VGA565_MODE | FL2000_VGA_CTRL_PXCLK_VGA332_MODE |
+	       FL2000_VGA_CTRL_PXCLK_VGA555_MODE | FL2000_VGA_CTRL_PXCLK_VGA_COMPRESS |
+	       FL2000_VGA_CTRL_PXCLK_CLEAR_WATERMARK;
+	val = FL2000_VGA_CTRL_PXCLK_DAC_OUTPUT_EN | FL2000_VGA_CTRL_PXCLK_CLEAR_WATERMARK;
+	if (bytes_pix == 2)
+		val |= FL2000_VGA_CTRL_PXCLK_VGA565_MODE;
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_PXCLK, mask, val);
+
+	return 0;
+}
+
+int fl2000_set_transfers(struct regmap *regmap)
+{
+	u32 mask;
+
+	mask = FL2000_VGA_CTRL_ACLK_USE_PKT_PENDING | FL2000_VGA_CTRL_ACLK_USE_ZERO_TD |
+	       FL2000_VGA_CTRL_ACLK_USE_ZERO_PKT_LEN;
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, mask,
+			  FL2000_VGA_CTRL_ACLK_USE_ZERO_PKT_LEN);
+
+	regmap_write_bits(regmap, FL2000_VGA_ISOCH_REG, FL2000_VGA_ISOCH_MFRAME_CNT, 0);
+
+	return 0;
+}
+
+int fl2000_reset(struct regmap *regmap)
+{
+	regmap_write_bits(regmap, FL2000_RST_CTRL_REG, FL2000_RST_CTRL_SW_RESET,
+			  FL2000_RST_CTRL_SW_RESET);
+
+	msleep(FL2000_HW_RST_MDELAY);
+
+	return 0;
+}
+
+int fl2000_afe_magic(struct regmap *regmap)
+{
+	regmap_write_bits(regmap, FL2000_USB_LPM_REG, FL2000_USB_LPM_MAGIC, FL2000_USB_LPM_MAGIC);
+
+	return 0;
+}
+
+int fl2000_usb_magic(struct regmap *regmap)
+{
+	u32 val;
+
+	/* Writing DONE as 0 would start an I2C operation, see fl2000_i2c_dword() */
+	val = FL2000_VGA_I2C_SC_MONITOR_DETECT | FL2000_VGA_I2C_SC_EDID_DETECT |
+	      FL2000_VGA_I2C_SC_DONE;
+	regmap_write_bits(regmap, FL2000_VGA_I2C_SC_REG, val, val);
+
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_3, FL2000_VGA_CTRL3_WAKEUP_CLR_EN, 0);
+
+	val = FL2000_USB_LPM_U1_REJECT | FL2000_USB_LPM_U2_REJECT;
+	regmap_write_bits(regmap, FL2000_USB_LPM_REG, val, val);
+
+	regmap_write_bits(regmap, FL2000_USB_CTRL_REG, FL2000_USB_CTRL_WAKE_NRDY, 0);
+
+	return 0;
+}
+
+/* The interrupts about monitors: they matter from the moment the device is up, mode or no mode */
+int fl2000_enable_sink_interrupts(struct regmap *regmap)
+{
+	regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, FL2000_VGA_CTRL_ACLK_EDID_MON_INT_EN,
+			  FL2000_VGA_CTRL_ACLK_EDID_MON_INT_EN);
+
+	regmap_write_bits(regmap, FL2000_VGA_CTRL2_REG_ACLK, FL2000_VGA_CTRL2_ACLK_HDMI_INT_EN,
+			  FL2000_VGA_CTRL2_ACLK_HDMI_INT_EN);
+
+	return 0;
+}
+
+/* The interrupts about the frames being sent */
+int fl2000_stream_interrupts(struct regmap *regmap, bool enable)
+{
+	u32 mask, val;
+
+	val = FL2000_VGA_CTRL_ACLK_VGA_ERR_INT_EN | FL2000_VGA_CTRL_ACLK_LBUF_ERR_INT_EN;
+	mask = val | FL2000_VGA_CTRL_ACLK_FEEDBACK_INT_EN;
+
+	return regmap_write_bits(regmap, FL2000_VGA_CTRL_REG_ACLK, mask, enable ? val : 0);
+}
+
+/*
+ * Read the status register. Its event bits clear on read, so whoever reads it owns the events it
+ * returns: callers must not drop them.
+ */
+int fl2000_read_status(struct regmap *regmap, u32 *status)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(regmap, FL2000_VGA_STATUS_REG, &val);
+	if (ret)
+		return ret;
+
+	/*
+	 * LBUF issues are recoverable: write their bits back. regmap_write_bits() would read the
+	 * register once more and lose any event raised since the read above.
+	 */
+	if (val & (FL2000_VGA_STATUS_LBUF_OVERFLOW | FL2000_VGA_STATUS_LBUF_UNDERFLOW))
+		regmap_write(regmap, FL2000_VGA_STATUS_REG, val & ~FL2000_VGA_STATUS_SELF_CLEAR);
+
+	/* lbuf_halt and vga_error are not handled: no recovery sequence is known */
+
+	*status = val;
+
+	return 0;
+}
+
+int fl2000_i2c_dword(struct regmap *regmap, bool read, u16 addr, u8 offset, u32 *data)
+{
+	int ret;
+	unsigned int val;
+	u32 mask;
+
+	if (!read) {
+		ret = regmap_write(regmap, FL2000_VGA_I2C_WR_REG, *data);
+		if (ret)
+			return -EIO;
+	}
+
+	/*
+	 * The detection bits always read back as 0, so they need to be set again on every write.
+	 * It is not clear whether the monitor detection circuit is needed at all for the HDMI use
+	 * case.
+	 */
+	mask = FL2000_VGA_I2C_SC_MONITOR_DETECT | FL2000_VGA_I2C_SC_EDID_DETECT |
+	       FL2000_VGA_I2C_SC_STATUS | FL2000_VGA_I2C_SC_ADDR | FL2000_VGA_I2C_SC_CMD |
+	       FL2000_VGA_I2C_SC_OFFSET | FL2000_VGA_I2C_SC_DONE;
+	val = FL2000_VGA_I2C_SC_MONITOR_DETECT | FL2000_VGA_I2C_SC_EDID_DETECT |
+	      FIELD_PREP(FL2000_VGA_I2C_SC_ADDR, addr) |
+	      FIELD_PREP(FL2000_VGA_I2C_SC_CMD, read) |
+	      FIELD_PREP(FL2000_VGA_I2C_SC_OFFSET, offset);
+
+	ret = regmap_write_bits(regmap, FL2000_VGA_I2C_SC_REG, mask, val);
+	if (ret)
+		return -EIO;
+
+	ret = regmap_read_poll_timeout(regmap, FL2000_VGA_I2C_SC_REG, val,
+				       val & FL2000_VGA_I2C_SC_DONE, I2C_RDWR_INTERVAL,
+				       I2C_RDWR_TIMEOUT);
+	/*
+	 * This shouldn't normally happen: there's internal 256ms HW timeout on I2C operations and
+	 * USB must be always available so no I/O errors. But if it happens we are probably facing
+	 * an irreversible HW issue.
+	 */
+	if (ret || FIELD_GET(FL2000_VGA_I2C_SC_STATUS, val))
+		return -EIO;
+
+	if (read) {
+		ret = regmap_read(regmap, FL2000_VGA_I2C_RD_REG, data);
+		if (ret)
+			return -EIO;
+	}
+
+	return 0;
+}
+
+/*
+ * The chip may have lost power (system sleep): forget the cached registers, so that the
+ * read-modify-write updates that follow read the chip instead of skipping writes it still needs.
+ */
+void fl2000_regmap_drop_cache(struct regmap *regmap)
+{
+	regcache_drop_region(regmap, 0, fl2000_regmap_config.max_register);
+}
+
+struct regmap *fl2000_regmap_init(struct device *dev, struct usb_device *usb_dev)
+{
+	return devm_regmap_init(dev, NULL, usb_dev, &fl2000_regmap_config);
+}
diff --git a/drivers/gpu/drm/fl2000/fl2000_registers.h b/drivers/gpu/drm/fl2000/fl2000_registers.h
new file mode 100644
index 000000000..f17431e38
--- /dev/null
+++ b/drivers/gpu/drm/fl2000/fl2000_registers.h
@@ -0,0 +1,276 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Based on the official registers description from Fresco Logic
+ *
+ * (C) Copyright 2019, Artem Mygaiev
+ */
+
+#ifndef FL2000_REGISTERS_H
+#define FL2000_REGISTERS_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+/* #### USB Control Registers Bank #### */
+/* Some unknown & undocumented FL2000 USB settings */
+
+#define FL2000_USB_CONTROL_OFFSET 0x0000
+
+#define FL2000_USB_LPM_REG (FL2000_USB_CONTROL_OFFSET + 0x70)
+#define  FL2000_USB_LPM_MAGIC				BIT(13)
+#define  FL2000_USB_LPM_U2_REJECT			BIT(19)
+#define  FL2000_USB_LPM_U1_REJECT			BIT(20)
+
+#define FL2000_USB_CTRL_REG (FL2000_USB_CONTROL_OFFSET + 0x78)
+#define  FL2000_USB_CTRL_WAKE_NRDY			BIT(17)
+
+/* #### VGA Control Registers Bank #### */
+/* Taken from the 'FL200DX Memory Mapped Address Space Registers' spec */
+
+#define FL2000_VGA_CONTROL_OFFSET 0x8000
+
+#define FL2000_VGA_STATUS_REG (FL2000_VGA_CONTROL_OFFSET + 0x00)
+#define  FL2000_VGA_STATUS_VGA_STATUS			BIT(0)
+#define  FL2000_VGA_STATUS_VGA_ERROR			BIT(1) /* read self clear */
+#define  FL2000_VGA_STATUS_LBUF_HALT			BIT(2)
+#define  FL2000_VGA_STATUS_ISO_ACK			BIT(3) /* read self clear */
+#define  FL2000_VGA_STATUS_TD_DROP			BIT(4) /* read self clear */
+#define  FL2000_VGA_STATUS_IRQ_PENDING			BIT(5) /* read self clear */
+#define  FL2000_VGA_STATUS_PLL_STATUS			BIT(6)
+#define  FL2000_VGA_STATUS_DAC_STATUS			BIT(7)
+#define  FL2000_VGA_STATUS_LBUF_OVERFLOW		BIT(8)
+#define  FL2000_VGA_STATUS_LBUF_UNDERFLOW		BIT(9)
+#define  FL2000_VGA_STATUS_FRAME_CNT			GENMASK(25, 10)
+#define  FL2000_VGA_STATUS_HDMI_EVENT			BIT(26) /* read self clear */
+#define  FL2000_VGA_STATUS_HDMI_STATUS			BIT(27)
+#define  FL2000_VGA_STATUS_EDID_STATUS			BIT(28)
+#define  FL2000_VGA_STATUS_MONITOR_STATUS		BIT(29)
+#define  FL2000_VGA_STATUS_MONITOR_EVENT		BIT(30) /* read self clear */
+#define  FL2000_VGA_STATUS_EDID_EVENT			BIT(31) /* read self clear */
+#define  FL2000_VGA_STATUS_SELF_CLEAR \
+	(FL2000_VGA_STATUS_VGA_ERROR | FL2000_VGA_STATUS_ISO_ACK | FL2000_VGA_STATUS_TD_DROP | \
+	 FL2000_VGA_STATUS_IRQ_PENDING | FL2000_VGA_STATUS_HDMI_EVENT | \
+	 FL2000_VGA_STATUS_MONITOR_EVENT | FL2000_VGA_STATUS_EDID_EVENT)
+
+#define FL2000_VGA_CTRL_REG_PXCLK (FL2000_VGA_CONTROL_OFFSET + 0x04)
+#define  FL2000_VGA_CTRL_PXCLK_CLEAR_WATERMARK		BIT(0)
+#define  FL2000_VGA_CTRL_PXCLK_FRAME_SYNC		BIT(1)
+#define  FL2000_VGA_CTRL_PXCLK_HSYNC_POLARITY		BIT(2)
+#define  FL2000_VGA_CTRL_PXCLK_VSYNC_POLARITY		BIT(3)
+#define  FL2000_VGA_CTRL_PXCLK_DE_POLARITY		BIT(4)
+#define  FL2000_VGA_CTRL_PXCLK_MIRROR_MODE		BIT(5)
+#define  FL2000_VGA_CTRL_PXCLK_VGA565_MODE		BIT(6)
+#define  FL2000_VGA_CTRL_PXCLK_DAC_OUTPUT_EN		BIT(7)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_TIMING_EN		BIT(8)
+#define  FL2000_VGA_CTRL_PXCLK_USE_NEW_PKT_RETRY	BIT(9)
+#define  FL2000_VGA_CTRL_PXCLK_REF_SELECT		BIT(10)
+#define  FL2000_VGA_CTRL_PXCLK_DAC_PX_CLK_INVERT	BIT(11)
+#define  FL2000_VGA_CTRL_PXCLK_CLEAR_LBUF_STATUS	BIT(12)
+#define  FL2000_VGA_CTRL_PXCLK_DROP_CNT			BIT(13)
+#define  FL2000_VGA_CTRL_PXCLK_USE_VDI_ITP_CNT		BIT(14)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_COMPRESS		BIT(24)
+#define  FL2000_VGA_CTRL_PXCLK_VGA332_MODE		BIT(25)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_COLOR_PALETTE_EN	BIT(26)
+#define  FL2000_VGA_CTRL_PXCLK_VGA_FIRST_BT_ENC_EN	BIT(27)
+#define  FL2000_VGA_CTRL_PXCLK_CLEAR_125US_CNT		BIT(28)
+#define  FL2000_VGA_CTRL_PXCLK_DISABLE_HALT		BIT(29)
+#define  FL2000_VGA_CTRL_PXCLK_FORCE_DE_EN		BIT(30)
+#define  FL2000_VGA_CTRL_PXCLK_VGA555_MODE		BIT(31)
+
+#define FL2000_VGA_HSYNC_REG1 (FL2000_VGA_CONTROL_OFFSET + 0x08)
+#define  FL2000_VGA_HSYNC1_HTOTAL			GENMASK(11, 0)
+#define  FL2000_VGA_HSYNC1_HACTIVE			GENMASK(27, 16)
+
+#define FL2000_VGA_HSYNC_REG2 (FL2000_VGA_CONTROL_OFFSET + 0x0C)
+#define  FL2000_VGA_HSYNC2_HSTART			GENMASK(11, 0)
+#define  FL2000_VGA_HSYNC2_HSYNC_WIDTH			GENMASK(23, 16)
+
+#define FL2000_VGA_VSYNC_REG1 (FL2000_VGA_CONTROL_OFFSET + 0x10)
+#define  FL2000_VGA_VSYNC1_VTOTAL			GENMASK(11, 0)
+#define  FL2000_VGA_VSYNC1_VACTIVE			GENMASK(27, 16)
+
+#define FL2000_VGA_VSYNC_REG2 (FL2000_VGA_CONTROL_OFFSET + 0x14)
+#define  FL2000_VGA_VSYNC2_VSTART			GENMASK(11, 0)
+#define  FL2000_VGA_VSYNC2_VSYNC_WIDTH			GENMASK(18, 16)
+#define  FL2000_VGA_VSYNC2_START_LATENCY		GENMASK(29, 20)
+#define  FL2000_VGA_VSYNC2_BUF_ERROR_EN			BIT(31)
+
+#define FL2000_VGA_TEST_REG  (FL2000_VGA_CONTROL_OFFSET + 0x18)
+#define FL2000_VGA_ISOCH_REG (FL2000_VGA_CONTROL_OFFSET + 0x1C)
+#define  FL2000_VGA_ISOCH_START_MFRAME_CNT		GENMASK(13, 0)
+#define  FL2000_VGA_ISOCH_USE_MFRAME_MATCH		BIT(14)
+#define  FL2000_VGA_ISOCH_USE_ZERO_LEN_FRAME		BIT(15)
+#define  FL2000_VGA_ISOCH_MFRAME_CNT			GENMASK(29, 16)
+#define  FL2000_VGA_ISOCH_MFRAME_CNT_UPDATE		BIT(30)
+
+#define FL2000_VGA_I2C_SC_REG (FL2000_VGA_CONTROL_OFFSET + 0x20)
+#define  FL2000_VGA_I2C_SC_ADDR				GENMASK(6, 0)
+#define  FL2000_VGA_I2C_SC_CMD				BIT(7)
+#define  FL2000_VGA_I2C_SC_OFFSET			GENMASK(15, 8)
+#define  FL2000_VGA_I2C_SC_VGA_STATUS			GENMASK(23, 16)
+#define  FL2000_VGA_I2C_SC_STATUS			GENMASK(27, 24)
+#define  FL2000_VGA_I2C_SC_MONITOR_DETECT		BIT(28)
+#define  FL2000_VGA_I2C_SC_READY			BIT(29)
+#define  FL2000_VGA_I2C_SC_EDID_DETECT			BIT(30)
+#define  FL2000_VGA_I2C_SC_DONE				BIT(31)
+
+#define FL2000_VGA_I2C_RD_REG (FL2000_VGA_CONTROL_OFFSET + 0x24)
+#define FL2000_VGA_I2C_WR_REG (FL2000_VGA_CONTROL_OFFSET + 0x28)
+
+#define FL2000_VGA_PLL_REG (FL2000_VGA_CONTROL_OFFSET + 0x2C)
+#define  FL2000_VGA_PLL_DIVISOR				GENMASK(7, 0)
+#define  FL2000_VGA_PLL_PRESCALER			GENMASK(9, 8)
+#define  FL2000_VGA_PLL_FUNCTION			GENMASK(14, 13)
+#define  FL2000_VGA_PLL_MULTIPLIER			GENMASK(23, 16)
+#define  FL2000_VGA_PLL_TEST_IO				BIT(24)
+#define  FL2000_VGA_PLL_CFG_DAC_PWRDOWN			BIT(25)
+#define  FL2000_VGA_PLL_FORCE_DAC_PWRUP			BIT(26)
+
+#define FL2000_VGA_LBUF_REG (FL2000_VGA_CONTROL_OFFSET + 0x30)
+#define  FL2000_VGA_LBUF_WATERMARK_ASSERT_RDY		GENMASK(14, 0)
+
+#define FL2000_VGA_HI_MARK (FL2000_VGA_CONTROL_OFFSET + 0x34)
+#define  FL2000_VGA_HI_MARK_HIGH_WATERMARK		GENMASK(16, 0)
+
+#define FL2000_VGA_LO_MARK (FL2000_VGA_CONTROL_OFFSET + 0x38)
+#define  FL2000_VGA_LO_MARK_LOW_WATERMARK		GENMASK(16, 0)
+
+#define FL2000_VGA_CTRL_REG_ACLK (FL2000_VGA_CONTROL_OFFSET + 0x3C)
+#define  FL2000_VGA_CTRL_ACLK_CFG_TIMING_RESET_N	BIT(0)
+#define  FL2000_VGA_CTRL_ACLK_PLH_BLOCK_EN		BIT(1)
+#define  FL2000_VGA_CTRL_ACLK_EDID_MON_INT_EN		BIT(2)
+#define  FL2000_VGA_CTRL_ACLK_EXT_MON_INT_EN		BIT(3)
+#define  FL2000_VGA_CTRL_ACLK_VGA_STATUS_SELF_CLEAR	BIT(4)
+#define  FL2000_VGA_CTRL_ACLK_PLL_LOCK_TIME		GENMASK(9, 5)
+#define  FL2000_VGA_CTRL_ACLK_PLL_FAST_TIMEOUT_EN	BIT(10)
+#define  FL2000_VGA_CTRL_ACLK_PPE_BLOCK_EM		BIT(11)
+#define  FL2000_VGA_CTRL_ACLK_PLL_TIMER_EN		BIT(12)
+#define  FL2000_VGA_CTRL_ACLK_FEEDBACK_INT_EN		BIT(13)
+#define  FL2000_VGA_CTRL_ACLK_CLR_125US_COUNTER		BIT(14)
+#define  FL2000_VGA_CTRL_ACLK_CCS_PD_DIS		BIT(15)
+#define  FL2000_VGA_CTRL_ACLK_STANDBY_EN		BIT(16)
+#define  FL2000_VGA_CTRL_ACLK_FORCE_LOOPBACK		BIT(17)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_DROP_FRAME_EN	BIT(18)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_VDE_RST_EN		BIT(19)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_SW_RST		BIT(20)
+#define  FL2000_VGA_CTRL_ACLK_LBUF_ERR_INT_EN		BIT(21)
+#define  FL2000_VGA_CTRL_ACLK_BIAC_EN			BIT(22)
+#define  FL2000_VGA_CTRL_ACLK_PXCLK_IN_EN		BIT(23)
+#define  FL2000_VGA_CTRL_ACLK_VGA_ERR_INT_EN		BIT(24)
+#define  FL2000_VGA_CTRL_ACLK_FORCE_VGA_CONNECT		BIT(25)
+#define  FL2000_VGA_CTRL_ACLK_FORCE_PLL_UP		BIT(26)
+#define  FL2000_VGA_CTRL_ACLK_USE_ZERO_TD		BIT(27)
+#define  FL2000_VGA_CTRL_ACLK_USE_ZERO_PKT_LEN		BIT(28)
+#define  FL2000_VGA_CTRL_ACLK_USE_PKT_PENDING		BIT(29)
+#define  FL2000_VGA_CTRL_ACLK_PLL_DAC_PD_USBP3_EN	BIT(30)
+#define  FL2000_VGA_CTRL_ACLK_PLL_DAC_PD_NOVGA_EN	BIT(31)
+
+#define FL2000_VGA_PXCLK_CNT_REG (FL2000_VGA_CONTROL_OFFSET + 0x40)
+#define  FL2000_VGA_PXCLK_CNT_PIX_CLOCK_COUNT		GENMASK(27, 0)
+
+#define FL2000_VGA_VCNT_REG (FL2000_VGA_CONTROL_OFFSET + 0x44)
+#define  FL2000_VGA_VCNT_MAX_ACLK_COUNT			GENMASK(14, 0)
+#define  FL2000_VGA_VCNT_MAX_ACLK_COUNT_HIT		BIT(15)
+#define  FL2000_VGA_VCNT_MAX_LBUF_ACCUMULATE		GENMASK(31, 16)
+
+#define FL2000_RST_CTRL_REG (FL2000_VGA_CONTROL_OFFSET + 0x48)
+#define  FL2000_RST_CTRL_DIS_HOT_RST2_PORT		BIT(0)
+#define  FL2000_RST_CTRL_DIS_WARM_RST2_PORT		BIT(1)
+#define  FL2000_RST_CTRL_DIS_HOT_RESET_PIPE		BIT(2)
+#define  FL2000_RST_CTRL_DIS_WARM_RESET_PIPE		BIT(3)
+#define  FL2000_RST_CTRL_DIS_HOT_RESET_PIX		BIT(4)
+#define  FL2000_RST_CTRL_DIS_WARM_RESET_PIX		BIT(5)
+#define  FL2000_RST_CTRL_DIS_USB2_RESET_PIX		BIT(6)
+#define  FL2000_RST_CTRL_DIS_PLL_RESET_PIX		BIT(7)
+#define  FL2000_RST_CTRL_DIS_SW_RESET_PIX		BIT(8)
+#define  FL2000_RST_CTRL_DIS_USB2_RESET_BUF		BIT(9)
+#define  FL2000_RST_CTRL_DIS_SW_RESET_BUF		BIT(10)
+#define  FL2000_RST_CTRL_DIS_LBUF_RESET_PIX		BIT(11)
+#define  FL2000_RST_CTRL_DIS_HOT_RESET_PORT		BIT(12)
+#define  FL2000_RST_CTRL_DIS_WARM_RESET_PORT		BIT(13)
+#define  FL2000_RST_CTRL_SET_SLOW_CLK_PREDFT		BIT(14)
+#define  FL2000_RST_CTRL_SW_RESET			BIT(15)
+#define  FL2000_RST_CTRL_FRAME_FIRST_ITP_WL		GENMASK(31, 16)
+
+#define FL2000_BIAC_CTRL1_REG (FL2000_VGA_CONTROL_OFFSET + 0x4C)
+#define  FL2000_BIAC_CTRL1_CTRL_LO8			GENMASK(7, 0)
+#define  FL2000_BIAC_CTRL1_FRAME_MULT			GENMASK(15, 8)
+#define  FL2000_BIAC_CTRL1_125US_MULT			GENMASK(31, 16)
+
+#define FL2000_BIAC_CTRL2_REG (FL2000_VGA_CONTROL_OFFSET + 0x50)
+#define  FL2000_BIAC_CTRL2_CTRL_HI16			GENMASK(15, 0)
+
+#define FL2000_BIAC_STATUS_REG (FL2000_VGA_CONTROL_OFFSET + 0x54)
+#define  FL2000_BIAC_STATUS_CURRENT_STATUS		GENMASK(15, 0)
+#define  FL2000_BIAC_STATUS_CURRENT_VALUE		GENMASK(31, 16)
+
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x58) */
+
+#define FL2000_VGA_PLT_REG_PXCLK (FL2000_VGA_CONTROL_OFFSET + 0x5C)
+#define  FL2000_VGA_PLT_PXCLK_PALETTE_RAM_WR_DATA	GENMASK(23, 0)
+
+#define FL2000_VGA_PLT_RADDR_REG_PXCLK (FL2000_VGA_CONTROL_OFFSET + 0x60)
+#define  FL2000_VGA_PLT_RADDR_PALETTE_RAM_RD_ADDR	GENMASK(7, 0)
+#define  FL2000_VGA_PLT_RADDR_LAST_FRAME_LBUF_WATERMARK	GENMASK(23, 8)
+
+#define FL2000_VGA_CTRL2_REG_ACLK (FL2000_VGA_CONTROL_OFFSET + 0x64)
+#define  FL2000_VGA_CTRL2_ACLK_PLL_POWERDOWN_DETECT_EN	BIT(0)
+#define  FL2000_VGA_CTRL2_ACLK_MSTOR_BLKSIZE_PTR_WIDTH	GENMASK(3, 1)
+#define  FL2000_VGA_CTRL2_ACLK_MSTOR_BLK_COUNT		GENMASK(15, 4)
+#define  FL2000_VGA_CTRL2_ACLK_SPI_WR_EN		BIT(16)
+#define  FL2000_VGA_CTRL2_ACLK_DETECT_PINS_DEBOUNCE_EN	BIT(17)
+#define  FL2000_VGA_CTRL2_ACLK_HDMI_INT_EN		BIT(18)
+#define  FL2000_VGA_CTRL2_ACLK_HDMI_INT_ACTIVE_HIGH	BIT(19)
+#define  FL2000_VGA_CTRL2_ACLK_SPI_EN			BIT(20)
+#define  FL2000_VGA_CTRL2_ACLK_SW_PROD_REV		GENMASK(30, 23)
+
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x68) */
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x6C) */
+
+#define FL2000_TEST_CNTL_REG1 (FL2000_VGA_CONTROL_OFFSET + 0x70)
+#define FL2000_TEST_CNTL_REG2 (FL2000_VGA_CONTROL_OFFSET + 0x74)
+#define FL2000_TEST_CNTL_REG3 (FL2000_VGA_CONTROL_OFFSET + 0x78)
+#define FL2000_TEST_STAT1     (FL2000_VGA_CONTROL_OFFSET + 0x7C)
+#define FL2000_TEST_STAT2     (FL2000_VGA_CONTROL_OFFSET + 0x80)
+#define FL2000_TEST_STAT3     (FL2000_VGA_CONTROL_OFFSET + 0x84)
+
+#define FL2000_VGA_CTRL_REG_3 (FL2000_VGA_CONTROL_OFFSET + 0x88)
+#define  FL2000_VGA_CTRL3_WAKEUP_CLR_EN			BIT(10)
+
+/* undefined				(FL2000_VGA_CONTROL_OFFSET + 0x8C) */
+
+/*
+ * List of registers that shall not be read automatically (e.g. for cache update) due to presence
+ * of self-clear bits
+ */
+static inline bool FL2000_REG_PRECIOUS(u32 reg)
+{
+	return reg == FL2000_VGA_STATUS_REG;
+}
+
+/* List of volatile registers that shall not be cached */
+static inline bool FL2000_REG_VOLATILE(u32 reg)
+{
+	switch (reg) {
+	case FL2000_VGA_STATUS_REG:
+	case FL2000_VGA_CTRL_REG_PXCLK:
+	case FL2000_VGA_ISOCH_REG:
+	case FL2000_VGA_I2C_SC_REG:
+	case FL2000_VGA_I2C_RD_REG:
+	case FL2000_VGA_PXCLK_CNT_REG:
+	case FL2000_VGA_VCNT_REG:
+	case FL2000_RST_CTRL_REG:
+	case FL2000_BIAC_STATUS_REG:
+	case FL2000_VGA_PLT_RADDR_REG_PXCLK:
+	case FL2000_TEST_CNTL_REG1:
+	case FL2000_TEST_CNTL_REG2:
+	case FL2000_TEST_STAT1:
+	case FL2000_TEST_STAT2:
+	case FL2000_TEST_STAT3:
+		return true;
+	default:
+		return false;
+	}
+}
+
+#endif /* FL2000_REGISTERS_H */

-- 
2.56.0



  reply	other threads:[~2026-11-12  3:34 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-12  3:34 [PATCH v2 0/4] drm: Add Fresco Logic FL2000 USB display driver Angel Gabriel Crispin Valdivia
2026-11-12  3:34 ` Angel Gabriel Crispin Valdivia via B4 Relay
2026-11-12  3:34 ` Angel Gabriel Crispin Valdivia [this message]
2026-11-12  3:34   ` [PATCH v2 1/4] drm/fl2000: Add USB driver with register and I2C access Angel Gabriel Crispin Valdivia via B4 Relay
2026-11-12  3:58   ` sashiko-bot
2026-11-12  3:34 ` [PATCH v2 2/4] drm/fl2000: Add ITE IT66121 HDMI transmitter support Angel Gabriel Crispin Valdivia
2026-11-12  3:34   ` Angel Gabriel Crispin Valdivia via B4 Relay
2026-11-12  3:58   ` sashiko-bot
2026-11-12  3:34 ` [PATCH v2 3/4] drm/fl2000: Add frame streaming Angel Gabriel Crispin Valdivia
2026-11-12  3:34   ` Angel Gabriel Crispin Valdivia via B4 Relay
2026-11-12  3:54   ` sashiko-bot
2026-11-12  3:34 ` [PATCH v2 4/4] drm/fl2000: Add KMS display pipeline Angel Gabriel Crispin Valdivia
2026-11-12  3:34   ` Angel Gabriel Crispin Valdivia via B4 Relay
2026-11-12  4:00   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261111-fl2000-upstream-v2-1-201f87708cb0@gmail.com \
    --to=angelcrispinvaldivia@gmail.com \
    --cc=airlied@gmail.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=tzimmermann@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.