From 785d9aa288b9c1107c9d5361c8030e859ceb6e62 Mon Sep 17 00:00:00 2001 From: Ken Chang Date: Tue, 18 Mar 2025 20:25:47 +0800 Subject: [PATCH] tegra-cec: poll WR_LOCK for TX data sync After programming the TX Register, it takes 3-4 clock cycles for TX data synchronization to the CEC core domain. The TX_EMPTY interrupt should not be cleared until this data synchronization is complete. Enable CEC HW feature to automatically clears the TX_EMPTY interrupt after TX data synchronization is complete. It takes time for HW to clear the interrupt and TX_EMPTY can still appear high, hence SW needs to poll WR_LOCK until it goes to 0. This will avoid SW to attempt the next TX block during the same TX_EMPTY interrupt. Also read RX_REGISTER based on the buffer occupancy which is indicated by the CEC_RX_BUFFER_STAT_0 register. Bug 4954851 Change-Id: I3ec3792c9ae3b8a00c800c921cf4e4d09369e6b9 Signed-off-by: Ken Chang Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3322519 (cherry picked from commit 94cf6106402dc833dbaa4305c3e1a8f85fd80d0e) Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3336992 Reviewed-by: Bitan Biswas Reviewed-by: Prafull Suryawanshi GVS: buildbot_gerritrpt --- drivers/misc/tegra-cec/tegra_cec.c | 44 ++++++++++++++++++++++-------- drivers/misc/tegra-cec/tegra_cec.h | 7 +++-- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/drivers/misc/tegra-cec/tegra_cec.c b/drivers/misc/tegra-cec/tegra_cec.c index bcb361b0..a77f94a7 100644 --- a/drivers/misc/tegra-cec/tegra_cec.c +++ b/drivers/misc/tegra-cec/tegra_cec.c @@ -110,10 +110,16 @@ static int tegra_cec_release(struct inode *inode, struct file *file) static inline void tegra_cec_native_tx(const struct tegra_cec *cec, u32 block) { - tegra_cec_writel(block, cec->cec_base + TEGRA_CEC_TX_REGISTER); - tegra_cec_writel(TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY, - cec->cec_base + TEGRA_CEC_INT_STAT); + u32 tx_reg, retry; + tegra_cec_writel(block, cec->cec_base + TEGRA_CEC_TX_REGISTER); + + tx_reg = tegra_cec_readl(cec->cec_base + TEGRA_CEC_TX_REGISTER); + retry = 10; + while ((tx_reg & 0x80000000) && retry--) { + udelay(31); // one clock cycle = 30.5us + tx_reg = tegra_cec_readl(cec->cec_base + TEGRA_CEC_TX_REGISTER); + } } static inline void tegra_cec_error_recovery(struct tegra_cec *cec) @@ -213,8 +219,6 @@ static ssize_t tegra_cec_read(struct file *file, char __user *buffer, struct tegra_cec *cec = file->private_data; ssize_t ret; - count = sizeof(cec->rx_buffer); - ret = wait_event_interruptible(cec->init_waitq, atomic_read(&cec->init_done) == 1); if (ret) @@ -228,13 +232,16 @@ static ssize_t tegra_cec_read(struct file *file, char __user *buffer, if (ret) return ret; - if (copy_to_user(buffer, &(cec->rx_buffer), count)) + count = sizeof(cec->rx_fifo[0]) * (cec->rx_fifo_data); + if (copy_to_user(buffer, &(cec->rx_fifo[0]), count)) return -EFAULT; dev_dbg(cec->dev, "%s: %*phC", __func__, (int)count, - &(cec->rx_buffer)); - cec->rx_buffer = 0x0; + &(cec->rx_fifo[0])); + + memset(&(cec->rx_fifo[0]), 0, count); cec->rx_wake = 0; + cec->rx_fifo_data = 0; return count; } @@ -242,7 +249,7 @@ static irqreturn_t tegra_cec_irq_handler(int irq, void *data) { struct device *dev = data; struct tegra_cec *cec = dev_get_drvdata(dev); - u32 status, mask; + u32 status, mask, i; status = tegra_cec_readl(cec->cec_base + TEGRA_CEC_INT_STAT); mask = tegra_cec_readl(cec->cec_base + TEGRA_CEC_INT_MASK); @@ -314,9 +321,21 @@ static irqreturn_t tegra_cec_irq_handler(int irq, void *data) TEGRA_CEC_INT_STAT_RX_BUS_ERROR_DETECTED), cec->cec_base + TEGRA_CEC_INT_STAT); } else if (status & TEGRA_CEC_INT_STAT_RX_REGISTER_FULL) { + /* + Read TEGRA_CEC_RX_BUFFER_STAT_0 which have total number of blocks, + then read every block continuously from TEGRA_CEC_RX_REGISTER. + TEGRA_CEC_INT_STAT_RX_REGISTER_FULL sets only once and not for + every block if there are more than 1 block in FIFO. + */ + cec->rx_fifo_data = + tegra_cec_readl(cec->cec_base + TEGRA_CEC_RX_BUFFER_STAT_0); + for (i = 0; i < cec->rx_fifo_data; i++) { + cec->rx_fifo[i] = + readw(cec->cec_base + TEGRA_CEC_RX_REGISTER); + } + tegra_cec_writel(TEGRA_CEC_INT_STAT_RX_REGISTER_FULL, cec->cec_base + TEGRA_CEC_INT_STAT); - cec->rx_buffer = tegra_cec_readl(cec->cec_base + TEGRA_CEC_RX_REGISTER); cec->rx_wake = 1; wake_up_interruptible(&cec->rx_waitq); } @@ -508,8 +527,9 @@ static void tegra_cec_init(struct tegra_cec *cec) cec->logical_addr = TEGRA_CEC_HWCTRL_RX_LADDR_UNREG; - tegra_cec_writel(TEGRA_CEC_HWCTRL_RX_LADDR(cec->logical_addr), - cec->cec_base + TEGRA_CEC_HW_CONTROL); + state = TEGRA_CEC_HWCTRL_AUTO_CLR_TX_EMPTY_INTR | + TEGRA_CEC_HWCTRL_RX_LADDR(cec->logical_addr); + tegra_cec_writel(state, cec->cec_base + TEGRA_CEC_HW_CONTROL); tegra_cec_writel(0x1, cec->cec_base + TEGRA_CEC_MESSAGE_FILTER_CTRL); diff --git a/drivers/misc/tegra-cec/tegra_cec.h b/drivers/misc/tegra-cec/tegra_cec.h index 1cc1aeb0..6694df5b 100644 --- a/drivers/misc/tegra-cec/tegra_cec.h +++ b/drivers/misc/tegra-cec/tegra_cec.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved. +// Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved. #ifndef TEGRA_CEC_H #define TEGRA_CEC_H @@ -9,6 +9,7 @@ #include #define TEGRA_CEC_FRAME_MAX_LENGTH 16 +#define TEGRA_CEC_RX_FIFO_LENGTH 64 struct tegra_cec { struct device *dev; @@ -30,12 +31,13 @@ struct tegra_cec { struct work_struct work; unsigned int rx_wake; unsigned int tx_wake; - u16 rx_buffer; long tx_error; u32 tx_buf[TEGRA_CEC_FRAME_MAX_LENGTH]; u8 tx_buf_cur; u8 tx_buf_cnt; struct reset_control *reset; + u16 rx_fifo[TEGRA_CEC_RX_FIFO_LENGTH]; + u16 rx_fifo_data; }; #define TEGRA_CEC_LADDR_BROADCAST 0xF @@ -83,6 +85,7 @@ struct tegra_cec { #define TEGRA_CEC_HWCTRL_RX_SNOOP (1<<15) #define TEGRA_CEC_HWCTRL_RX_NAK_MODE (1<<16) #define TEGRA_CEC_HWCTRL_TX_NAK_MODE (1<<24) +#define TEGRA_CEC_HWCTRL_AUTO_CLR_TX_EMPTY_INTR (1<<29) #define TEGRA_CEC_HWCTRL_FAST_SIM_MODE (1<<30) #define TEGRA_CEC_HWCTRL_TX_RX_MODE (1<<31)