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
