media: synopsys: hdmirx: Detect broken interrupt

Downstream version of RK3588 U-Boot uses customized TF-A that remaps
HDMIRX hardware interrupt, routing it via firmware that isn't supported
by upstream driver.

Detect broken interrupt and print a clarifying error message about a need
to use open-source TF-A with this driver.

Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Signed-off-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
This commit is contained in:
Dmitry Osipenko
2025-12-10 19:00:06 +03:00
committed by Hans Verkuil
parent 863f50d583
commit a813338d91
2 changed files with 90 additions and 2 deletions

View File

@@ -132,12 +132,14 @@ struct snps_hdmirx_dev {
struct delayed_work delayed_work_hotplug;
struct delayed_work delayed_work_res_change;
struct hdmirx_cec *cec;
struct mutex phy_rw_lock; /* to protect phy r/w configuration */
struct mutex stream_lock; /* to lock video stream capture */
struct mutex work_lock; /* to lock the critical section of hotplug event */
struct reset_control_bulk_data resets[HDMIRX_NUM_RST];
struct clk_bulk_data *clks;
struct regmap *grf;
struct regmap *vo1_grf;
struct completion cr_read_done;
struct completion cr_write_done;
struct completion timer_base_lock;
struct completion avi_pkt_rcv;
@@ -796,11 +798,50 @@ static int wait_reg_bit_status(struct snps_hdmirx_dev *hdmirx_dev, u32 reg,
return 0;
}
static int hdmirx_phy_register_read(struct snps_hdmirx_dev *hdmirx_dev,
u32 phy_reg, u32 *val)
{
struct device *dev = hdmirx_dev->dev;
u32 status;
guard(mutex)(&hdmirx_dev->phy_rw_lock);
reinit_completion(&hdmirx_dev->cr_read_done);
/* clear irq status */
hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
/* en irq */
hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N,
PHYCREG_CR_READ_DONE, PHYCREG_CR_READ_DONE);
/* write phy reg addr */
hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG1, phy_reg);
/* config read enable */
hdmirx_writel(hdmirx_dev, PHYCREG_CONTROL, PHYCREG_CR_PARA_READ_P);
if (!wait_for_completion_timeout(&hdmirx_dev->cr_read_done,
msecs_to_jiffies(20))) {
dev_err(dev, "%s wait cr read done failed\n", __func__);
return -ETIMEDOUT;
}
/* read phy reg value */
status = hdmirx_readl(hdmirx_dev, PHYCREG_STATUS);
if (!(status & PHYCREG_CR_PARA_DATAVALID)) {
dev_err(dev, "%s cr read failed\n", __func__);
return -EINVAL;
}
*val = status & PHYCREG_CR_PARA_RD_DATA_MASK;
return 0;
}
static int hdmirx_phy_register_write(struct snps_hdmirx_dev *hdmirx_dev,
u32 phy_reg, u32 val)
{
struct device *dev = hdmirx_dev->dev;
guard(mutex)(&hdmirx_dev->phy_rw_lock);
reinit_completion(&hdmirx_dev->cr_write_done);
/* clear irq status */
hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
@@ -1814,6 +1855,13 @@ static void mainunit_2_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
*handled = true;
}
if (status & PHYCREG_CR_READ_DONE) {
hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N,
PHYCREG_CR_READ_DONE, 0);
complete(&hdmirx_dev->cr_read_done);
*handled = true;
}
if (status & TMDSVALID_STABLE_CHG) {
process_signal_change(hdmirx_dev);
v4l2_dbg(2, debug, v4l2_dev, "%s: TMDSVALID_STABLE_CHG\n", __func__);
@@ -2313,18 +2361,51 @@ static void hdmirx_disable_all_interrupts(struct snps_hdmirx_dev *hdmirx_dev)
hdmirx_clear_interrupt(hdmirx_dev, CEC_INT_CLEAR, 0xffffffff);
}
static void hdmirx_init(struct snps_hdmirx_dev *hdmirx_dev)
static int hdmirx_detect_broken_interrupt(struct snps_hdmirx_dev *hdmirx_dev)
{
int ret;
u32 val;
enable_irq(hdmirx_dev->hdmi_irq);
hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG0, 0x3);
ret = hdmirx_phy_register_read(hdmirx_dev,
HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_FSM_CONFIG,
&val);
disable_irq(hdmirx_dev->hdmi_irq);
return ret;
}
static int hdmirx_init(struct snps_hdmirx_dev *hdmirx_dev)
{
int ret;
hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, PHY_RESET | PHY_PDDQ, 0);
regmap_write(hdmirx_dev->vo1_grf, VO1_GRF_VO1_CON2,
(HDMIRX_SDAIN_MSK | HDMIRX_SCLIN_MSK) |
((HDMIRX_SDAIN_MSK | HDMIRX_SCLIN_MSK) << 16));
/*
* RK3588 downstream version of TF-A remaps HDMIRX interrupt and
* requires use of a vendor-specific FW API that we don't support
* in this driver.
*/
ret = hdmirx_detect_broken_interrupt(hdmirx_dev);
if (ret)
dev_err_probe(hdmirx_dev->dev, ret,
"interrupt not functioning, open-source TF-A is required by this driver\n");
/*
* Some interrupts are enabled by default, so we disable
* all interrupts and clear interrupts status first.
*/
hdmirx_disable_all_interrupts(hdmirx_dev);
return ret;
}
/* hdmi-4k-300mhz EDID produced by v4l2-ctl tool */
@@ -2606,10 +2687,12 @@ static int hdmirx_probe(struct platform_device *pdev)
return dev_err_probe(dev, PTR_ERR(hdmirx_dev->regs),
"failed to remap regs resource\n");
mutex_init(&hdmirx_dev->phy_rw_lock);
mutex_init(&hdmirx_dev->stream_lock);
mutex_init(&hdmirx_dev->work_lock);
spin_lock_init(&hdmirx_dev->rst_lock);
init_completion(&hdmirx_dev->cr_read_done);
init_completion(&hdmirx_dev->cr_write_done);
init_completion(&hdmirx_dev->timer_base_lock);
init_completion(&hdmirx_dev->avi_pkt_rcv);
@@ -2623,7 +2706,10 @@ static int hdmirx_probe(struct platform_device *pdev)
hdmirx_dev->timings = cea640x480;
hdmirx_enable(dev);
hdmirx_init(hdmirx_dev);
ret = hdmirx_init(hdmirx_dev);
if (ret)
goto err_pm;
v4l2_dev = &hdmirx_dev->v4l2_dev;
strscpy(v4l2_dev->name, dev_name(dev), sizeof(v4l2_dev->name));

View File

@@ -116,6 +116,8 @@
#define PHYCREG_CR_PARA_WRITE_P BIT(1)
#define PHYCREG_CR_PARA_READ_P BIT(0)
#define PHYCREG_STATUS 0x00f4
#define PHYCREG_CR_PARA_DATAVALID BIT(24)
#define PHYCREG_CR_PARA_RD_DATA_MASK GENMASK(15, 0)
#define MAINUNIT_STATUS 0x0150
#define TMDSVALID_STABLE_ST BIT(1)