drm: bridge: anx7625: implement minimal Type-C support

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.

Reviewed-by: Xin Ji <xji@analogixsemi.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://patch.msgid.link/20260121-anx7625-typec-v2-2-d14f31256a17@oss.qualcomm.com
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
This commit is contained in:
Dmitry Baryshkov 2026-01-21 12:15:46 +02:00
parent 1d7532444a
commit f81455b2d3
3 changed files with 168 additions and 10 deletions

View File

@ -34,6 +34,7 @@ config DRM_ANALOGIX_ANX7625
tristate "Analogix Anx7625 MIPI to DP interface support" tristate "Analogix Anx7625 MIPI to DP interface support"
depends on DRM depends on DRM
depends on OF depends on OF
depends on TYPEC || !TYPEC
select DRM_DISPLAY_DP_HELPER select DRM_DISPLAY_DP_HELPER
select DRM_DISPLAY_HDCP_HELPER select DRM_DISPLAY_HDCP_HELPER
select DRM_DISPLAY_HELPER select DRM_DISPLAY_HELPER

View File

@ -3,6 +3,7 @@
* Copyright(c) 2020, Analogix Semiconductor. All rights reserved. * Copyright(c) 2020, Analogix Semiconductor. All rights reserved.
* *
*/ */
#include <linux/cleanup.h>
#include <linux/gcd.h> #include <linux/gcd.h>
#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h>
#include <linux/i2c.h> #include <linux/i2c.h>
@ -15,6 +16,9 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/types.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/workqueue.h>
#include <linux/of_graph.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) static void anx7625_disable_pd_protocol(struct anx7625_data *ctx)
{ {
struct device *dev = ctx->dev; struct device *dev = ctx->dev;
int ret, val; int ret;
/* Reset main ocm */ /* Reset main ocm */
ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, 0x88, 0x40); 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"); DRM_DEV_DEBUG_DRIVER(dev, "disable PD feature fail.\n");
else else
DRM_DEV_DEBUG_DRIVER(dev, "disable PD feature succeeded.\n"); 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 * 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) if ((ret & FLASH_LOAD_STA_CHK) != FLASH_LOAD_STA_CHK)
return -ENODEV; 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,", DRM_DEV_DEBUG_DRIVER(dev, "Firmware ver %02x%02x,",
anx7625_reg_read(ctx, anx7625_reg_read(ctx,
@ -1472,6 +1483,107 @@ static void anx7625_start_dp_work(struct anx7625_data *ctx)
DRM_DEV_DEBUG_DRIVER(dev, "Secure OCM version=%02x\n", ret); 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_set_status(struct anx7625_data *ctx,
unsigned int intr_status,
unsigned int intr_vector)
{
if (intr_vector & CC_STATUS)
anx7625_typec_set_orientation(ctx);
if (intr_vector & DATA_ROLE_STATUS) {
enum typec_data_role data_role = (intr_status & DATA_ROLE_STATUS) ?
TYPEC_HOST : TYPEC_DEVICE;
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, data_role);
ctx->typec_data_role = data_role;
}
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) =
device_get_named_child_node(ctx->dev, "connector");
u32 val;
int ret;
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_status(ctx, val,
CC_STATUS | DATA_ROLE_STATUS |
VBUS_STATUS | VCONN_STATUS);
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_set_status(struct anx7625_data *ctx,
unsigned int intr_status,
unsigned int intr_vector)
{
}
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) static int anx7625_read_hpd_status_p0(struct anx7625_data *ctx)
{ {
return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, SYSTEM_STSTUS); return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, SYSTEM_STSTUS);
@ -1566,7 +1678,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; int intr_vector, status;
struct device *dev = ctx->dev; struct device *dev = ctx->dev;
@ -1593,9 +1705,6 @@ static int anx7625_hpd_change_detect(struct anx7625_data *ctx)
return status; return status;
} }
if (!(intr_vector & HPD_STATUS_CHANGE))
return -ENOENT;
status = anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, status = anx7625_reg_read(ctx, ctx->i2c.rx_p0_client,
SYSTEM_STSTUS); SYSTEM_STSTUS);
if (status < 0) { if (status < 0) {
@ -1604,6 +1713,12 @@ static int anx7625_hpd_change_detect(struct anx7625_data *ctx)
} }
DRM_DEV_DEBUG_DRIVER(dev, "0x7e:0x45=%x\n", status); DRM_DEV_DEBUG_DRIVER(dev, "0x7e:0x45=%x\n", status);
anx7625_typec_set_status(ctx, status, intr_vector);
if (!(intr_vector & HPD_STATUS))
return -ENOENT;
dp_hpd_change_handler(ctx, status & HPD_STATUS); dp_hpd_change_handler(ctx, status & HPD_STATUS);
return 0; return 0;
@ -1622,7 +1737,7 @@ static void anx7625_work_func(struct work_struct *work)
return; return;
} }
event = anx7625_hpd_change_detect(ctx); event = anx7625_intr_status(ctx);
mutex_unlock(&ctx->lock); mutex_unlock(&ctx->lock);
@ -2741,11 +2856,29 @@ static int anx7625_i2c_probe(struct i2c_client *client)
} }
if (!platform->pdata.low_power_mode) { 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); pm_runtime_get_sync(dev);
_anx7625_hpd_polling(platform, 5000 * 100); _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 */ /* Add work function */
if (platform->pdata.intp_irq) { if (platform->pdata.intp_irq) {
enable_irq(platform->pdata.intp_irq); enable_irq(platform->pdata.intp_irq);
@ -2759,6 +2892,10 @@ static int anx7625_i2c_probe(struct i2c_client *client)
return 0; return 0;
pm_suspend:
if (!platform->pdata.low_power_mode)
pm_runtime_put_sync_suspend(&client->dev);
free_wq: free_wq:
if (platform->workqueue) if (platform->workqueue)
destroy_workqueue(platform->workqueue); destroy_workqueue(platform->workqueue);
@ -2774,6 +2911,8 @@ static void anx7625_i2c_remove(struct i2c_client *client)
{ {
struct anx7625_data *platform = i2c_get_clientdata(client); struct anx7625_data *platform = i2c_get_clientdata(client);
anx7625_typec_unregister(platform);
drm_bridge_remove(&platform->bridge); drm_bridge_remove(&platform->bridge);
if (platform->pdata.intp_irq) if (platform->pdata.intp_irq)

View File

@ -51,9 +51,21 @@
#define INTR_RECEIVED_MSG BIT(5) #define INTR_RECEIVED_MSG BIT(5)
#define SYSTEM_STSTUS 0x45 #define SYSTEM_STSTUS 0x45
#define INTERFACE_CHANGE_INT_MASK 0x43
#define INTERFACE_CHANGE_INT 0x44 #define INTERFACE_CHANGE_INT 0x44
#define HPD_STATUS_CHANGE 0x80 #define VCONN_STATUS BIT(2)
#define HPD_STATUS 0x80 #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 ********/ /******** END of I2C Address 0x58 ********/
@ -447,9 +459,15 @@ struct anx7625_i2c_client {
struct i2c_client *tcpc_client; struct i2c_client *tcpc_client;
}; };
struct typec_port;
struct usb_role_switch;
struct anx7625_data { struct anx7625_data {
struct anx7625_platform_data pdata; struct anx7625_platform_data pdata;
struct platform_device *audio_pdev; struct platform_device *audio_pdev;
struct typec_port *typec_port;
struct usb_role_switch *role_sw;
int typec_data_role;
int hpd_status; int hpd_status;
int hpd_high_cnt; int hpd_high_cnt;
int dp_en; int dp_en;