Add INNO common api so that it can be used by vendor
drivers which implement vendor specific extensions to Innosilicon HDMI.

Signed-off-by: keith <keith.z...@starfivetech.com>
---
 MAINTAINERS                                   |   2 +
 drivers/gpu/drm/bridge/Kconfig                |   2 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/innosilicon/Kconfig    |   6 +
 drivers/gpu/drm/bridge/innosilicon/Makefile   |   2 +
 .../gpu/drm/bridge/innosilicon/inno-hdmi.c    | 587 ++++++++++++++++++
 .../gpu/drm/bridge/innosilicon/inno-hdmi.h    |  97 +++
 include/drm/bridge/inno_hdmi.h                |  69 ++
 8 files changed, 766 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/innosilicon/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/innosilicon/Makefile
 create mode 100644 drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c
 create mode 100644 drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h
 create mode 100644 include/drm/bridge/inno_hdmi.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 8881652bb8b6..cf2d66f88a83 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7155,6 +7155,8 @@ S:        Maintained
 T:     git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:     
Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml
 F:     Documentation/devicetree/bindings/display/starfive/
+F:     drivers/gpu/drm/bridge/innosilicon/
+F:     include/drm/bridge/inno_hdmi.h
 
 DRM DRIVER FOR SYNAPTICS R63353 PANELS
 M:     Michael Trimarchi <mich...@amarulasolutions.com>
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index c621be1a99a8..59fcdb810125 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -415,6 +415,8 @@ source "drivers/gpu/drm/bridge/cadence/Kconfig"
 
 source "drivers/gpu/drm/bridge/imx/Kconfig"
 
+source "drivers/gpu/drm/bridge/innosilicon/Kconfig"
+
 source "drivers/gpu/drm/bridge/synopsys/Kconfig"
 
 endmenu
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 7df87b582dca..cca7400566b2 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -40,4 +40,5 @@ obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o
 obj-y += analogix/
 obj-y += cadence/
 obj-y += imx/
+obj-y += innosilicon/
 obj-y += synopsys/
diff --git a/drivers/gpu/drm/bridge/innosilicon/Kconfig 
b/drivers/gpu/drm/bridge/innosilicon/Kconfig
new file mode 100644
index 000000000000..73dbed3b1c4d
--- /dev/null
+++ b/drivers/gpu/drm/bridge/innosilicon/Kconfig
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_INNO_HDMI
+       tristate
+       help
+         Support the common interface which is part of the INNO
+         Designware HDMI block.
diff --git a/drivers/gpu/drm/bridge/innosilicon/Makefile 
b/drivers/gpu/drm/bridge/innosilicon/Makefile
new file mode 100644
index 000000000000..3b3a961ab9fb
--- /dev/null
+++ b/drivers/gpu/drm/bridge/innosilicon/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o
diff --git a/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c 
b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c
new file mode 100644
index 000000000000..2378ce06a876
--- /dev/null
+++ b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ *    Zheng Yang <zhengy...@rock-chips.com>
+ *    Yakir Yang <y...@rock-chips.com>
+ * Copyright (C) 2024 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#include <drm/bridge/inno_hdmi.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "inno-hdmi.h"
+
+struct inno_hdmi_i2c {
+       struct i2c_adapter adap;
+
+       u8 ddc_addr;
+       u8 segment_addr;
+
+       struct mutex lock; /* For i2c operation. */
+       struct completion cmp;
+};
+
+struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector)
+{
+       return container_of(connector, struct inno_hdmi, connector);
+}
+EXPORT_SYMBOL_GPL(connector_to_inno_hdmi);
+
+u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset)
+{
+       return readl_relaxed(hdmi->regs + (offset) * 0x04);
+}
+EXPORT_SYMBOL_GPL(hdmi_readb);
+
+void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val)
+{
+       writel_relaxed(val, hdmi->regs + (offset) * 0x04);
+}
+EXPORT_SYMBOL_GPL(hdmi_writeb);
+
+void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, u32 msk, u32 val)
+{
+       u8 temp = hdmi_readb(hdmi, offset) & ~msk;
+
+       temp |= val & msk;
+       hdmi_writeb(hdmi, offset, temp);
+}
+EXPORT_SYMBOL_GPL(hdmi_modb);
+
+void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate)
+{
+       unsigned long long ddc_bus_freq = rate >> 2;
+
+       do_div(ddc_bus_freq, HDMI_SCL_RATE);
+
+       hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
+       hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
+
+       /* Clear the EDID interrupt flag and mute the interrupt */
+       hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
+       hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+}
+EXPORT_SYMBOL_GPL(inno_hdmi_i2c_init);
+
+void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable)
+{
+       if (enable)
+               hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON);
+       else
+               hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF);
+}
+EXPORT_SYMBOL_GPL(inno_hdmi_sys_power);
+
+static enum drm_connector_status
+inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+       struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
+
+       return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
+               connector_status_connected : connector_status_disconnected;
+}
+
+static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+       struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
+       const struct drm_edid *drm_edid;
+       int ret = 0;
+
+       if (!hdmi->ddc)
+               return 0;
+
+       drm_edid = drm_edid_read_ddc(connector, hdmi->ddc);
+       drm_edid_connector_update(connector, drm_edid);
+       ret = drm_edid_connector_add_modes(connector);
+       drm_edid_free(drm_edid);
+
+       return ret;
+}
+
+static enum drm_mode_status
+inno_hdmi_connector_mode_valid(struct drm_connector *connector, struct 
drm_display_mode *mode)
+{
+       struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
+
+       const struct inno_hdmi_plat_data *pdata = hdmi->plat_data;
+       enum drm_mode_status mode_status = MODE_OK;
+
+       if (pdata->mode_valid)
+               mode_status = pdata->mode_valid(connector, mode);
+
+       return mode_status;
+}
+
+static void
+inno_hdmi_connector_destroy_state(struct drm_connector *connector,
+                                 struct drm_connector_state *state)
+{
+       struct inno_hdmi_connector_state *inno_conn_state =
+                                               to_inno_hdmi_conn_state(state);
+
+       __drm_atomic_helper_connector_destroy_state(&inno_conn_state->base);
+       kfree(inno_conn_state);
+}
+
+static void inno_hdmi_connector_reset(struct drm_connector *connector)
+{
+       struct inno_hdmi_connector_state *inno_conn_state;
+
+       if (connector->state) {
+               inno_hdmi_connector_destroy_state(connector, connector->state);
+               connector->state = NULL;
+       }
+
+       inno_conn_state = kzalloc(sizeof(*inno_conn_state), GFP_KERNEL);
+       if (!inno_conn_state)
+               return;
+
+       __drm_atomic_helper_connector_reset(connector, &inno_conn_state->base);
+
+       inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709;
+       inno_conn_state->enc_out_format = HDMI_COLORSPACE_RGB;
+       inno_conn_state->rgb_limited_range = false;
+}
+
+static struct drm_connector_state *
+inno_hdmi_connector_duplicate_state(struct drm_connector *connector)
+{
+       struct inno_hdmi_connector_state *inno_conn_state;
+
+       if (WARN_ON(!connector->state))
+               return NULL;
+
+       inno_conn_state = kmemdup(to_inno_hdmi_conn_state(connector->state),
+                                 sizeof(*inno_conn_state), GFP_KERNEL);
+
+       if (!inno_conn_state)
+               return NULL;
+
+       __drm_atomic_helper_connector_duplicate_state(connector,
+                                                     &inno_conn_state->base);
+
+       return &inno_conn_state->base;
+}
+
+static const struct drm_connector_funcs inno_hdmi_connector_funcs = {
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .detect = inno_hdmi_connector_detect,
+       .reset = inno_hdmi_connector_reset,
+       .atomic_duplicate_state = inno_hdmi_connector_duplicate_state,
+       .atomic_destroy_state = inno_hdmi_connector_destroy_state,
+};
+
+static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
+       .get_modes = inno_hdmi_connector_get_modes,
+       .mode_valid = inno_hdmi_connector_mode_valid,
+};
+
+static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi,
+                             struct drm_encoder *encoder)
+{
+       struct device *dev = hdmi->dev;
+       const struct inno_hdmi_plat_data *pdata = hdmi->plat_data;
+
+       if (RK3128_HDMI == pdata->soc_type || RK3036_HDMI == pdata->soc_type)
+               drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+       encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+
+       /*
+        * If we failed to find the CRTC(s) which this encoder is
+        * supposed to be connected to, it's because the CRTC has
+        * not been registered yet.  Defer probing, and hope that
+        * the required CRTC is added later.
+        */
+       if (encoder->possible_crtcs == 0)
+               return -EPROBE_DEFER;
+
+       drm_encoder_helper_add(encoder, pdata->helper_private);
+
+       hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+       drm_connector_helper_add(&hdmi->connector,
+                                &inno_hdmi_connector_helper_funcs);
+
+       drmm_connector_init(drm, &hdmi->connector,
+                           &inno_hdmi_connector_funcs,
+                           DRM_MODE_CONNECTOR_HDMIA,
+                           hdmi->ddc);
+
+       drm_connector_attach_encoder(&hdmi->connector, encoder);
+
+       return 0;
+}
+
+static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi)
+{
+       struct inno_hdmi_i2c *i2c = hdmi->i2c;
+       u8 stat;
+
+       stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1);
+       if (!(stat & m_INT_EDID_READY))
+               return IRQ_NONE;
+
+       /* Clear HDMI EDID interrupt flag */
+       hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+
+       complete(&i2c->cmp);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id)
+{
+       struct inno_hdmi *hdmi = dev_id;
+       irqreturn_t ret = IRQ_NONE;
+       u8 interrupt;
+
+       if (hdmi->i2c)
+               ret = inno_hdmi_i2c_irq(hdmi);
+
+       interrupt = hdmi_readb(hdmi, HDMI_STATUS);
+       if (interrupt & m_INT_HOTPLUG) {
+               hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG);
+               ret = IRQ_WAKE_THREAD;
+       }
+
+       return ret;
+}
+
+static irqreturn_t inno_hdmi_irq(int irq, void *dev_id)
+{
+       struct inno_hdmi *hdmi = dev_id;
+
+       drm_helper_hpd_irq_event(hdmi->connector.dev);
+
+       return IRQ_HANDLED;
+}
+
+static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
+{
+       int length = msgs->len;
+       u8 *buf = msgs->buf;
+       int ret;
+
+       ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10);
+       if (!ret)
+               return -EAGAIN;
+
+       while (length--)
+               *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR);
+
+       return 0;
+}
+
+static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
+{
+       /*
+        * The DDC module only support read EDID message, so
+        * we assume that each word write to this i2c adapter
+        * should be the offset of EDID word address.
+        */
+       if (msgs->len != 1 || (msgs->addr != DDC_ADDR && msgs->addr != 
DDC_SEGMENT_ADDR))
+               return -EINVAL;
+
+       reinit_completion(&hdmi->i2c->cmp);
+
+       if (msgs->addr == DDC_SEGMENT_ADDR)
+               hdmi->i2c->segment_addr = msgs->buf[0];
+       if (msgs->addr == DDC_ADDR)
+               hdmi->i2c->ddc_addr = msgs->buf[0];
+
+       /* Set edid fifo first addr */
+       hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00);
+
+       /* Set edid word address 0x00/0x80 */
+       hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
+
+       /* Set edid segment pointer */
+       hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
+
+       return 0;
+}
+
+static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap,
+                             struct i2c_msg *msgs, int num)
+{
+       struct inno_hdmi *hdmi = i2c_get_adapdata(adap);
+       struct inno_hdmi_i2c *i2c = hdmi->i2c;
+       int i, ret = 0;
+
+       mutex_lock(&i2c->lock);
+
+       /* Clear the EDID interrupt flag and unmute the interrupt */
+       hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY);
+       hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+
+       for (i = 0; i < num; i++) {
+               DRM_DEV_DEBUG(hdmi->dev,
+                             "xfer: num: %d/%d, len: %d, flags: %#x\n",
+                             i + 1, num, msgs[i].len, msgs[i].flags);
+
+               if (msgs[i].flags & I2C_M_RD)
+                       ret = inno_hdmi_i2c_read(hdmi, &msgs[i]);
+               else
+                       ret = inno_hdmi_i2c_write(hdmi, &msgs[i]);
+
+               if (ret < 0)
+                       break;
+       }
+
+       if (!ret)
+               ret = num;
+
+       /* Mute HDMI EDID interrupt */
+       hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
+
+       mutex_unlock(&i2c->lock);
+
+       return ret;
+}
+
+static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm inno_hdmi_algorithm = {
+       .master_xfer    = inno_hdmi_i2c_xfer,
+       .functionality  = inno_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi)
+{
+       struct i2c_adapter *adap;
+       struct inno_hdmi_i2c *i2c;
+       int ret;
+
+       i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+       if (!i2c)
+               return ERR_PTR(-ENOMEM);
+
+       mutex_init(&i2c->lock);
+       init_completion(&i2c->cmp);
+
+       adap = &i2c->adap;
+       adap->owner = THIS_MODULE;
+       adap->dev.parent = hdmi->dev;
+       adap->dev.of_node = hdmi->dev->of_node;
+       adap->algo = &inno_hdmi_algorithm;
+       strscpy(adap->name, "Inno HDMI", sizeof(adap->name));
+       i2c_set_adapdata(adap, hdmi);
+
+       ret = i2c_add_adapter(adap);
+       if (ret) {
+               dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+               devm_kfree(hdmi->dev, i2c);
+               return ERR_PTR(ret);
+       }
+
+       hdmi->i2c = i2c;
+
+       DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+       return adap;
+}
+
+int inno_hdmi_bind(struct drm_device *drm, struct inno_hdmi *hdmi, struct 
drm_encoder  *encoder)
+{
+       int ret, irq;
+       struct platform_device *pdev = to_platform_device(hdmi->dev);
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               ret = irq;
+               return ret;
+       }
+
+       hdmi->ddc = inno_hdmi_i2c_adapter(hdmi);
+       if (IS_ERR(hdmi->ddc)) {
+               ret = PTR_ERR(hdmi->ddc);
+               hdmi->ddc = NULL;
+               return ret;
+       }
+
+       ret = inno_hdmi_register(drm, hdmi, encoder);
+       if (ret)
+               goto err_put_adapter;
+
+       /* Unmute hotplug interrupt */
+       hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1));
+
+       ret = devm_request_threaded_irq(hdmi->dev, irq, inno_hdmi_hardirq,
+                                       inno_hdmi_irq, IRQF_SHARED,
+                                       dev_name(hdmi->dev), hdmi);
+       if (ret)
+               goto err_put_adapter;
+
+       return ret;
+err_put_adapter:
+       i2c_put_adapter(hdmi->ddc);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(inno_hdmi_bind);
+
+static void inno_hdmi_disable_frame(struct inno_hdmi *hdmi,
+                                   enum hdmi_infoframe_type type)
+{
+       struct drm_connector *connector = &hdmi->connector;
+
+       if (type != HDMI_INFOFRAME_TYPE_AVI) {
+               drm_err(connector->dev,
+                       "Unsupported infoframe type: %u\n", type);
+               return;
+       }
+
+       hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, INFOFRAME_AVI);
+}
+
+static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi,
+                                 union hdmi_infoframe *frame, enum 
hdmi_infoframe_type type)
+{
+       struct drm_connector *connector = &hdmi->connector;
+       u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
+       ssize_t rc, i;
+
+       if (type != HDMI_INFOFRAME_TYPE_AVI) {
+               drm_err(connector->dev,
+                       "Unsupported infoframe type: %u\n", type);
+               return 0;
+       }
+
+       inno_hdmi_disable_frame(hdmi, type);
+
+       rc = hdmi_infoframe_pack(frame, packed_frame,
+                                sizeof(packed_frame));
+       if (rc < 0)
+               return rc;
+
+       for (i = 0; i < rc; i++)
+               hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i,
+                           packed_frame[i]);
+
+       return 0;
+}
+
+int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi, struct drm_display_mode 
*mode)
+{
+       struct drm_connector *connector = &hdmi->connector;
+       struct drm_connector_state *conn_state = connector->state;
+       struct inno_hdmi_connector_state *inno_conn_state =
+                                       to_inno_hdmi_conn_state(conn_state);
+       union hdmi_infoframe frame;
+       int rc;
+
+       rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
+                                                     &hdmi->connector,
+                                                     mode);
+       if (rc) {
+               inno_hdmi_disable_frame(hdmi, HDMI_INFOFRAME_TYPE_AVI);
+               return rc;
+       }
+
+       if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444)
+               frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
+       else if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV422)
+               frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
+       else
+               frame.avi.colorspace = HDMI_COLORSPACE_RGB;
+
+       if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_RGB) {
+               drm_hdmi_avi_infoframe_quant_range(&frame.avi,
+                                                  connector, mode,
+                                                  
inno_conn_state->rgb_limited_range ?
+                                                  
HDMI_QUANTIZATION_RANGE_LIMITED :
+                                                  
HDMI_QUANTIZATION_RANGE_FULL);
+       } else {
+               frame.avi.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
+               frame.avi.ycc_quantization_range =
+                       HDMI_YCC_QUANTIZATION_RANGE_LIMITED;
+       }
+
+       return inno_hdmi_upload_frame(hdmi, &frame, HDMI_INFOFRAME_TYPE_AVI);
+}
+EXPORT_SYMBOL_GPL(inno_hdmi_config_video_avi);
+
+int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, struct 
drm_display_mode *mode)
+{
+       int value;
+
+       /* Set detail external video timing polarity and interlace mode */
+       value = v_EXTERANL_VIDEO(1);
+       if (hdmi->plat_data->soc_type == STARFIVE_HDMI) {
+               value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+                       v_HSYNC_POLARITY_SF(1) : v_HSYNC_POLARITY_SF(0);
+               value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+                       v_VSYNC_POLARITY_SF(1) : v_VSYNC_POLARITY_SF(0);
+       } else {
+               value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+                       v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0);
+               value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+                       v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0);
+       }
+       value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
+        v_INETLACE(1) : v_INETLACE(0);
+       hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value);
+
+       /* Set detail external video timing */
+       value = mode->htotal;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF);
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF);
+
+       value = mode->htotal - mode->hdisplay;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF);
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF);
+
+       value = mode->htotal - mode->hsync_start;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF);
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF);
+
+       value = mode->hsync_end - mode->hsync_start;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF);
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF);
+
+       value = mode->vtotal;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF);
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF);
+
+       value = mode->vtotal - mode->vdisplay;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF);
+
+       value = mode->vtotal - mode->vsync_start;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF);
+
+       value = mode->vsync_end - mode->vsync_start;
+       hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF);
+
+       if (hdmi->plat_data->soc_type == STARFIVE_HDMI)
+               return 0;
+
+       hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e);
+       hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c);
+       hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(inno_hdmi_config_video_timing);
+
+MODULE_DESCRIPTION("INNO HDMI transmitter driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:inno-hdmi");
diff --git a/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h 
b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h
new file mode 100644
index 000000000000..233077cfb136
--- /dev/null
+++ b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ *    Zheng Yang <zhengy...@rock-chips.com>
+ *    Yakir Yang <y...@rock-chips.com>
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ */
+
+#ifndef __INNO_H__
+#define __INNO_H__
+
+#define DDC_SEGMENT_ADDR               0x30
+
+#define HDMI_SCL_RATE                  (100 * 1000)
+#define DDC_BUS_FREQ_L                 0x4b
+#define DDC_BUS_FREQ_H                 0x4c
+
+#define HDMI_SYS_CTRL                  0x00
+#define m_RST_ANALOG                   (1 << 6)
+#define v_RST_ANALOG                   (0 << 6)
+#define v_NOT_RST_ANALOG               (1 << 6)
+#define m_RST_DIGITAL                  (1 << 5)
+#define v_RST_DIGITAL                  (0 << 5)
+#define v_NOT_RST_DIGITAL              (1 << 5)
+#define m_REG_CLK_INV                  (1 << 4)
+#define v_REG_CLK_NOT_INV              (0 << 4)
+#define v_REG_CLK_INV                  (1 << 4)
+#define m_VCLK_INV                     (1 << 3)
+#define v_VCLK_NOT_INV                 (0 << 3)
+#define v_VCLK_INV                     (1 << 3)
+#define m_REG_CLK_SOURCE               (1 << 2)
+#define v_REG_CLK_SOURCE_TMDS          (0 << 2)
+#define v_REG_CLK_SOURCE_SYS           (1 << 2)
+#define m_POWER                                (1 << 1)
+#define v_PWR_ON                       (0 << 1)
+#define v_PWR_OFF                      (1 << 1)
+#define m_INT_POL                      (1 << 0)
+#define v_INT_POL_HIGH                 1
+#define v_INT_POL_LOW                  0
+
+#define HDMI_VIDEO_TIMING_CTL          0x08
+#define v_HSYNC_POLARITY(n)            ((n) << 3)
+#define v_VSYNC_POLARITY(n)            ((n) << 2)
+#define v_VSYNC_POLARITY_SF(n)         ((n) << 3)
+#define v_HSYNC_POLARITY_SF(n)         ((n) << 2)
+#define v_INETLACE(n)                  ((n) << 1)
+#define v_EXTERANL_VIDEO(n)            ((n) << 0)
+
+#define HDMI_VIDEO_EXT_HTOTAL_L                0x09
+#define HDMI_VIDEO_EXT_HTOTAL_H                0x0a
+#define HDMI_VIDEO_EXT_HBLANK_L                0x0b
+#define HDMI_VIDEO_EXT_HBLANK_H                0x0c
+#define HDMI_VIDEO_EXT_HDELAY_L                0x0d
+#define HDMI_VIDEO_EXT_HDELAY_H                0x0e
+#define HDMI_VIDEO_EXT_HDURATION_L     0x0f
+#define HDMI_VIDEO_EXT_HDURATION_H     0x10
+#define HDMI_VIDEO_EXT_VTOTAL_L                0x11
+#define HDMI_VIDEO_EXT_VTOTAL_H                0x12
+#define HDMI_VIDEO_EXT_VBLANK          0x13
+#define HDMI_VIDEO_EXT_VDELAY          0x14
+#define HDMI_VIDEO_EXT_VDURATION       0x15
+
+#define HDMI_EDID_SEGMENT_POINTER      0x4d
+#define HDMI_EDID_WORD_ADDR            0x4e
+#define HDMI_EDID_FIFO_OFFSET          0x4f
+#define HDMI_EDID_FIFO_ADDR            0x50
+
+#define HDMI_INTERRUPT_MASK1           0xc0
+#define HDMI_INTERRUPT_STATUS1         0xc1
+#define        m_INT_ACTIVE_VSYNC              (1 << 5)
+#define m_INT_EDID_READY               (1 << 2)
+
+#define HDMI_CONTROL_PACKET_BUF_INDEX  0x9f
+enum {
+       INFOFRAME_VSI = 0x05,
+       INFOFRAME_AVI = 0x06,
+       INFOFRAME_AAI = 0x08,
+};
+
+#define HDMI_CONTROL_PACKET_ADDR       0xa0
+#define HDMI_MAXIMUM_INFO_FRAME_SIZE   0x11
+
+#define HDMI_STATUS                    0xc8
+#define m_HOTPLUG                      (1 << 7)
+#define m_MASK_INT_HOTPLUG             (1 << 5)
+#define m_INT_HOTPLUG                  (1 << 1)
+#define v_MASK_INT_HOTPLUG(n)          (((n) & 0x1) << 5)
+
+#define HDMI_PHY_FEEDBACK_DIV_RATIO_LOW                0xe7
+#define v_FEEDBACK_DIV_LOW(n)                  ((n) & 0xff)
+#define HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH       0xe8
+#define v_FEEDBACK_DIV_HIGH(n)                 ((n) & 1)
+
+#define HDMI_PHY_PRE_DIV_RATIO         0xed
+#define v_PRE_DIV_RATIO(n)             ((n) & 0x1f)
+
+#endif /* __INNO_H__ */
diff --git a/include/drm/bridge/inno_hdmi.h b/include/drm/bridge/inno_hdmi.h
new file mode 100644
index 000000000000..bc865f5343ce
--- /dev/null
+++ b/include/drm/bridge/inno_hdmi.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ */
+
+#ifndef __INNO_COMMON_HDMI__
+#define __INNO_COMMON_HDMI__
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+struct inno_hdmi_connector_state {
+       struct drm_connector_state      base;
+       unsigned int                    enc_out_format;
+       unsigned int                    colorimetry;
+       bool                            rgb_limited_range;
+};
+
+enum inno_hdmi_devtype {
+       RK3036_HDMI,
+       RK3128_HDMI,
+       STARFIVE_HDMI,
+};
+
+#define to_inno_hdmi_conn_state(conn_state) \
+       container_of_const(conn_state, struct inno_hdmi_connector_state, base)
+
+struct inno_hdmi_phy_config {
+       unsigned long pixelclock;
+       u8 pre_emphasis;
+       u8 voltage_level_control;
+};
+
+struct inno_hdmi_plat_data {
+       enum inno_hdmi_devtype soc_type;
+
+       /* Platform-specific mode validation*/
+       enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
+                                          struct drm_display_mode *mode);
+       /* Platform-specific encoder helper funcs*/
+       const struct drm_encoder_helper_funcs *helper_private;
+
+       /* Platform-specific phy_configs Optional*/
+       struct inno_hdmi_phy_config *phy_configs;
+       struct inno_hdmi_phy_config *default_phy_config;
+};
+
+struct inno_hdmi {
+       struct device *dev;
+       void __iomem *regs;
+
+       struct drm_connector    connector;
+       struct inno_hdmi_i2c    *i2c;
+       struct i2c_adapter      *ddc;
+       const struct inno_hdmi_plat_data *plat_data;
+};
+
+u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset);
+void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val);
+void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, u32 msk, u32 val);
+
+struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector);
+void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate);
+int inno_hdmi_bind(struct drm_device *drm, struct inno_hdmi *hdmi, struct 
drm_encoder *encoder);
+void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable);
+int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi, struct drm_display_mode 
*mode);
+int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, struct 
drm_display_mode *mode);
+
+#endif /* __INNO_COMMON_HDMI__ */
-- 
2.27.0

Reply via email to