ANX7625 can be used as a USB-C controller, handling USB and DP data
streams. Provide minimal Type-C support necessary for ANX7625 to
register the Type-C port device and properly respond to data / power
role events from the Type-C partner.

While ANX7625 provides TCPCI interface, using it would circumvent the
on-chip running firmware. Analogix recommended using the higher-level
interface instead of TCPCI.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
 drivers/gpu/drm/bridge/analogix/Kconfig   |   1 +
 drivers/gpu/drm/bridge/analogix/anx7625.c | 163 ++++++++++++++++++++++++++++--
 drivers/gpu/drm/bridge/analogix/anx7625.h |  21 +++-
 3 files changed, 175 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig 
b/drivers/gpu/drm/bridge/analogix/Kconfig
index 
4846b2e9be7c2a5da18f6a3cdec53ef5766455e0..f3448b0631fea42e7e7ab10368777a93ce33cee7
 100644
--- a/drivers/gpu/drm/bridge/analogix/Kconfig
+++ b/drivers/gpu/drm/bridge/analogix/Kconfig
@@ -34,6 +34,7 @@ config DRM_ANALOGIX_ANX7625
        tristate "Analogix Anx7625 MIPI to DP interface support"
        depends on DRM
        depends on OF
+       depends on TYPEC || !TYPEC
        select DRM_DISPLAY_DP_HELPER
        select DRM_DISPLAY_HDCP_HELPER
        select DRM_DISPLAY_HELPER
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c 
b/drivers/gpu/drm/bridge/analogix/anx7625.c
index 
6f3fdcb6afdb9d785bc4515300676cf3988c5807..a44405db739669dfd2907b0afd41293a7b173035
 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.c
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.c
@@ -3,6 +3,7 @@
  * Copyright(c) 2020, Analogix Semiconductor. All rights reserved.
  *
  */
+#include <linux/cleanup.h>
 #include <linux/gcd.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
@@ -15,6 +16,9 @@
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 #include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/role.h>
 #include <linux/workqueue.h>
 
 #include <linux/of_graph.h>
@@ -1325,7 +1329,7 @@ static int anx7625_read_hpd_gpio_config_status(struct 
anx7625_data *ctx)
 static void anx7625_disable_pd_protocol(struct anx7625_data *ctx)
 {
        struct device *dev = ctx->dev;
-       int ret, val;
+       int ret;
 
        /* Reset main ocm */
        ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, 0x88, 0x40);
@@ -1339,6 +1343,11 @@ static void anx7625_disable_pd_protocol(struct 
anx7625_data *ctx)
                DRM_DEV_DEBUG_DRIVER(dev, "disable PD feature fail.\n");
        else
                DRM_DEV_DEBUG_DRIVER(dev, "disable PD feature succeeded.\n");
+}
+
+static void anx7625_configure_hpd(struct anx7625_data *ctx)
+{
+       int val;
 
        /*
         * Make sure the HPD GPIO already be configured after OCM release before
@@ -1369,7 +1378,9 @@ static int anx7625_ocm_loading_check(struct anx7625_data 
*ctx)
        if ((ret & FLASH_LOAD_STA_CHK) != FLASH_LOAD_STA_CHK)
                return -ENODEV;
 
-       anx7625_disable_pd_protocol(ctx);
+       if (!ctx->typec_port)
+               anx7625_disable_pd_protocol(ctx);
+       anx7625_configure_hpd(ctx);
 
        DRM_DEV_DEBUG_DRIVER(dev, "Firmware ver %02x%02x,",
                             anx7625_reg_read(ctx,
@@ -1472,6 +1483,115 @@ static void anx7625_start_dp_work(struct anx7625_data 
*ctx)
        DRM_DEV_DEBUG_DRIVER(dev, "Secure OCM version=%02x\n", ret);
 }
 
+#if IS_REACHABLE(CONFIG_TYPEC)
+static void anx7625_typec_set_orientation(struct anx7625_data *ctx)
+{
+       u32 val = anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, SYSTEM_STSTUS);
+
+       if (val & (CC1_RP | CC1_RD))
+               typec_set_orientation(ctx->typec_port, 
TYPEC_ORIENTATION_NORMAL);
+       else if (val & (CC2_RP | CC2_RD))
+               typec_set_orientation(ctx->typec_port, 
TYPEC_ORIENTATION_REVERSE);
+       else
+               typec_set_orientation(ctx->typec_port, TYPEC_ORIENTATION_NONE);
+}
+
+static void anx7625_typec_isr(struct anx7625_data *ctx,
+                             unsigned int intr_vector,
+                             unsigned int intr_status)
+{
+       if (intr_vector & CC_STATUS)
+               anx7625_typec_set_orientation(ctx);
+       if (intr_vector & DATA_ROLE_STATUS) {
+               usb_role_switch_set_role(ctx->role_sw,
+                                        (intr_status & DATA_ROLE_STATUS) ?
+                                        USB_ROLE_HOST : USB_ROLE_DEVICE);
+               typec_set_data_role(ctx->typec_port,
+                                   (intr_status & DATA_ROLE_STATUS) ?
+                                   TYPEC_HOST : TYPEC_DEVICE);
+       }
+       if (intr_vector & VBUS_STATUS)
+               typec_set_pwr_role(ctx->typec_port,
+                                  (intr_status & VBUS_STATUS) ?
+                                  TYPEC_SOURCE : TYPEC_SINK);
+       if (intr_vector & VCONN_STATUS)
+               typec_set_vconn_role(ctx->typec_port,
+                                    (intr_status & VCONN_STATUS) ?
+                                    TYPEC_SOURCE : TYPEC_SINK);
+}
+
+static int anx7625_typec_register(struct anx7625_data *ctx)
+{
+       struct typec_capability typec_cap = { };
+       struct fwnode_handle *fwnode __free(fwnode_handle) = NULL;
+       u32 val;
+       int ret;
+
+       fwnode = device_get_named_child_node(ctx->dev, "connector");
+       if (!fwnode)
+               return 0;
+
+       ret = typec_get_fw_cap(&typec_cap, fwnode);
+       if (ret < 0)
+               return ret;
+
+       typec_cap.revision = 0x0120;
+       typec_cap.pd_revision = 0x0300;
+       typec_cap.usb_capability = USB_CAPABILITY_USB2 | USB_CAPABILITY_USB3;
+       typec_cap.orientation_aware = true;
+
+       typec_cap.driver_data = ctx;
+
+       ctx->typec_port = typec_register_port(ctx->dev, &typec_cap);
+       if (IS_ERR(ctx->typec_port))
+               return PTR_ERR(ctx->typec_port);
+
+       ctx->role_sw = fwnode_usb_role_switch_get(fwnode);
+       if (IS_ERR(ctx->role_sw)) {
+               typec_unregister_port(ctx->typec_port);
+               return PTR_ERR(ctx->role_sw);
+       }
+
+       val = anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, SYSTEM_STSTUS);
+       anx7625_typec_set_orientation(ctx);
+       usb_role_switch_set_role(ctx->role_sw,
+                                (val & DATA_ROLE_STATUS) ?
+                                USB_ROLE_HOST : USB_ROLE_DEVICE);
+       typec_set_data_role(ctx->typec_port,
+                           (val & DATA_ROLE_STATUS) ?
+                           TYPEC_HOST : TYPEC_DEVICE);
+       typec_set_pwr_role(ctx->typec_port,
+                           (val & VBUS_STATUS) ?
+                           TYPEC_SOURCE : TYPEC_SINK);
+       typec_set_vconn_role(ctx->typec_port,
+                            (val & VCONN_STATUS) ?
+                            TYPEC_SOURCE : TYPEC_SINK);
+
+       return 0;
+}
+
+static void anx7625_typec_unregister(struct anx7625_data *ctx)
+{
+       usb_role_switch_put(ctx->role_sw);
+       typec_unregister_port(ctx->typec_port);
+}
+#else
+static void anx7625_typec_isr(struct anx7625_data *ctx,
+                             unsigned int intr_vector,
+                             unsigned int intr_status)
+{
+}
+
+static int anx7625_typec_register(struct anx7625_data *ctx)
+{
+       return 0;
+}
+
+static void anx7625_typec_unregister(struct anx7625_data *ctx)
+{
+}
+#endif
+
 static int anx7625_read_hpd_status_p0(struct anx7625_data *ctx)
 {
        return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, SYSTEM_STSTUS);
@@ -1566,7 +1686,7 @@ static void dp_hpd_change_handler(struct anx7625_data 
*ctx, bool on)
        }
 }
 
-static int anx7625_hpd_change_detect(struct anx7625_data *ctx)
+static int anx7625_intr_status(struct anx7625_data *ctx)
 {
        int intr_vector, status;
        struct device *dev = ctx->dev;
@@ -1593,9 +1713,6 @@ static int anx7625_hpd_change_detect(struct anx7625_data 
*ctx)
                return status;
        }
 
-       if (!(intr_vector & HPD_STATUS_CHANGE))
-               return -ENOENT;
-
        status = anx7625_reg_read(ctx, ctx->i2c.rx_p0_client,
                                  SYSTEM_STSTUS);
        if (status < 0) {
@@ -1604,6 +1721,12 @@ static int anx7625_hpd_change_detect(struct anx7625_data 
*ctx)
        }
 
        DRM_DEV_DEBUG_DRIVER(dev, "0x7e:0x45=%x\n", status);
+
+       anx7625_typec_isr(ctx, intr_vector, status);
+
+       if (!(intr_vector & HPD_STATUS))
+               return -ENOENT;
+
        dp_hpd_change_handler(ctx, status & HPD_STATUS);
 
        return 0;
@@ -1622,7 +1745,7 @@ static void anx7625_work_func(struct work_struct *work)
                return;
        }
 
-       event = anx7625_hpd_change_detect(ctx);
+       event = anx7625_intr_status(ctx);
 
        mutex_unlock(&ctx->lock);
 
@@ -2741,11 +2864,29 @@ static int anx7625_i2c_probe(struct i2c_client *client)
        }
 
        if (!platform->pdata.low_power_mode) {
-               anx7625_disable_pd_protocol(platform);
+               struct fwnode_handle *fwnode;
+
+               fwnode = device_get_named_child_node(dev, "connector");
+               if (fwnode)
+                       fwnode_handle_put(fwnode);
+               else
+                       anx7625_disable_pd_protocol(platform);
+
+               anx7625_configure_hpd(platform);
+
                pm_runtime_get_sync(dev);
                _anx7625_hpd_polling(platform, 5000 * 100);
        }
 
+       if (platform->pdata.intp_irq)
+               anx7625_reg_write(platform, platform->i2c.rx_p0_client,
+                                 INTERFACE_CHANGE_INT_MASK, 0);
+
+       /* After getting runtime handle */
+       ret = anx7625_typec_register(platform);
+       if (ret)
+               goto pm_suspend;
+
        /* Add work function */
        if (platform->pdata.intp_irq) {
                enable_irq(platform->pdata.intp_irq);
@@ -2759,6 +2900,10 @@ static int anx7625_i2c_probe(struct i2c_client *client)
 
        return 0;
 
+pm_suspend:
+       if (!platform->pdata.low_power_mode)
+               pm_runtime_put_sync_suspend(&client->dev);
+
 free_wq:
        if (platform->workqueue)
                destroy_workqueue(platform->workqueue);
@@ -2774,6 +2919,8 @@ static void anx7625_i2c_remove(struct i2c_client *client)
 {
        struct anx7625_data *platform = i2c_get_clientdata(client);
 
+       anx7625_typec_unregister(platform);
+
        drm_bridge_remove(&platform->bridge);
 
        if (platform->pdata.intp_irq)
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h 
b/drivers/gpu/drm/bridge/analogix/anx7625.h
index 
eb5580f1ab2f86b48b6f2df4fa4d6c3be603ad48..f9570cd6d22e55fd70a12c15960714cbb783d059
 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.h
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.h
@@ -51,9 +51,21 @@
 #define INTR_RECEIVED_MSG BIT(5)
 
 #define SYSTEM_STSTUS 0x45
+#define INTERFACE_CHANGE_INT_MASK 0x43
 #define INTERFACE_CHANGE_INT 0x44
-#define HPD_STATUS_CHANGE 0x80
-#define HPD_STATUS 0x80
+#define VCONN_STATUS   BIT(2)
+#define VBUS_STATUS    BIT(3)
+#define CC_STATUS      BIT(4)
+#define DATA_ROLE_STATUS       BIT(5)
+#define HPD_STATUS     BIT(7)
+
+#define NEW_CC_STATUS 0x46
+#define CC1_RD                  BIT(0)
+#define CC1_RA                  BIT(1)
+#define CC1_RP                 (BIT(2) | BIT(3))
+#define CC2_RD                  BIT(4)
+#define CC2_RA                  BIT(5)
+#define CC2_RP                 (BIT(6) | BIT(7))
 
 /******** END of I2C Address 0x58 ********/
 
@@ -447,9 +459,14 @@ struct anx7625_i2c_client {
        struct i2c_client *tcpc_client;
 };
 
+struct typec_port;
+struct usb_role_switch;
+
 struct anx7625_data {
        struct anx7625_platform_data pdata;
        struct platform_device *audio_pdev;
+       struct typec_port *typec_port;
+       struct usb_role_switch *role_sw;
        int hpd_status;
        int hpd_high_cnt;
        int dp_en;

-- 
2.47.3

Reply via email to