From a0aa53714949b6bb5a1a730cc63d8849c58eb120 Mon Sep 17 00:00:00 2001 From: Robert Huang Date: Wed, 12 Feb 2025 05:06:33 +0000 Subject: [PATCH] misc: cec: add wake up support. This change adds SC7 wake support to cec driver. CEC engine can detect TV packets in SC7 state and can wake up when particular pattern detected. In resume functionality, the total arrived packets on FIFO needs to read and reported back to user-space. This change adds - 1. Program CEC engine to activate for SC7 wake. 2. Program required opcode and physical address. 3. Register for wake and read packets in resume path. 4. Provide sysfs control to program physical address. Verification of SC7 wake is done for below cases - 1. TV turn on and off is tested. 2. TV source input change is tested. 3. CEC Analyzer set-stream is tested. Bug 4974679 Change-Id: Ia1ceeb13b89614c96d0b6d42f2bb58419865414b Signed-off-by: Robert Huang Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3300821 GVS: buildbot_gerritrpt Reviewed-by: Bitan Biswas Reviewed-by: Prafull Suryawanshi --- drivers/misc/tegra-cec/tegra_cec.c | 133 ++++++++++++++++++++++++++--- drivers/misc/tegra-cec/tegra_cec.h | 7 +- 2 files changed, 125 insertions(+), 15 deletions(-) diff --git a/drivers/misc/tegra-cec/tegra_cec.c b/drivers/misc/tegra-cec/tegra_cec.c index 588f9d66..03f00715 100644 --- a/drivers/misc/tegra-cec/tegra_cec.c +++ b/drivers/misc/tegra-cec/tegra_cec.c @@ -65,6 +65,15 @@ static ssize_t cec_logical_addr_show(struct device *dev, static DEVICE_ATTR(cec_logical_addr_config, S_IWUSR | S_IRUGO, cec_logical_addr_show, cec_logical_addr_store); +static ssize_t cec_physical_addr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t cec_physical_addr_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static DEVICE_ATTR(cec_physical_addr_config, S_IWUSR | S_IRUGO, + cec_physical_addr_show, cec_physical_addr_store); + /* keeping this for debug support to track register read/writes */ struct tegra_cec *tegra_cec_global; @@ -213,8 +222,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,11 +235,22 @@ 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)) - return -EFAULT; + if (cec->rx_fifo_data != 0) { + count = sizeof(cec->rx_fifo[0]) * (cec->rx_fifo_data); + if (copy_to_user(buffer, &(cec->rx_fifo_data), count)) + return -EFAULT; + + dev_dbg(cec->dev, "%s: %*phC", __func__, (int)count, + &(cec->rx_fifo[0])); + } else { + count = sizeof(cec->rx_buffer); + if (copy_to_user(buffer, &(cec->rx_buffer), count)) + return -EFAULT; + + dev_dbg(cec->dev, "%s: %*phC", __func__, (int)count, + &(cec->rx_buffer)); + } - dev_dbg(cec->dev, "%s: %*phC", __func__, (int)count, - &(cec->rx_buffer)); cec->rx_buffer = 0x0; cec->rx_wake = 0; return count; @@ -242,7 +260,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); @@ -316,9 +334,26 @@ static irqreturn_t tegra_cec_irq_handler(int irq, void *data) } else if (status & TEGRA_CEC_INT_STAT_RX_REGISTER_FULL) { 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); + if (cec->is_tegra_cec_suspended) { + /* + 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); + } + pm_wakeup_event(dev, 0); + } else { + cec->rx_buffer = readw(cec->cec_base + TEGRA_CEC_RX_REGISTER); + cec->rx_fifo_data = 0; + } + cec->rx_wake = 1; + wake_up_interruptible(&cec->rx_waitq); } out: @@ -462,6 +497,8 @@ static int tegra_cec_send_one_touch_play(struct tegra_cec *cec) return 0; } + cec->physical_addr = (phy_address[1] << 8) | (phy_address[0]); + active_source_command[2] = phy_address[0]; active_source_command[3] = phy_address[1]; @@ -511,8 +548,6 @@ static void tegra_cec_init(struct tegra_cec *cec) tegra_cec_writel(TEGRA_CEC_HWCTRL_RX_LADDR(cec->logical_addr), cec->cec_base + TEGRA_CEC_HW_CONTROL); - tegra_cec_writel(0x1, cec->cec_base + TEGRA_CEC_MESSAGE_FILTER_CTRL); - state = (0xff << TEGRA_CEC_RX_TIMING_1_RX_DATA_BIT_MAX_LO_TIME_MASK) | (0x22 << TEGRA_CEC_RX_TIMING_1_RX_DATA_BIT_SAMPLE_TIME_MASK) | (0xe0 << TEGRA_CEC_RX_TIMING_1_RX_DATA_BIT_MAX_DURATION_MASK) | @@ -559,6 +594,43 @@ static void tegra_cec_init_worker(struct work_struct *work) tegra_cec_init(cec); } +static ssize_t cec_physical_addr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tegra_cec *cec = dev_get_drvdata(dev); + + if (!atomic_read(&cec->init_done)) + return -EAGAIN; + + if (buf) + return sprintf(buf, "0x%x\n", (u32)cec->physical_addr); + return 1; +} + +static ssize_t cec_physical_addr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret; + u16 addr; + struct tegra_cec *cec; + + if (!buf || !count) + return -EINVAL; + + cec = dev_get_drvdata(dev); + if (!atomic_read(&cec->init_done)) + return -EAGAIN; + + ret = kstrtou16(buf, 0, &addr); + if (ret) + return ret; + + dev_info(dev, "set physical address: 0x%x\n", (u32)addr); + cec->physical_addr = addr; + + return count; +} + static ssize_t cec_logical_addr_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -742,6 +814,14 @@ static int tegra_cec_probe(struct platform_device *pdev) goto cec_error; } + ret = sysfs_create_file( + &pdev->dev.kobj, &dev_attr_cec_physical_addr_config.attr); + dev_info(&pdev->dev, "cec_add_sysfs ret=%d\n", ret); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to add sysfs: %d\n", ret); + goto cec_error; + } + dev_notice(&pdev->dev, "probed\n"); return 0; @@ -763,7 +843,7 @@ static void tegra_cec_remove(struct platform_device *pdev) static int tegra_cec_suspend(struct platform_device *pdev, pm_message_t state) { struct tegra_cec *cec = platform_get_drvdata(pdev); - + u16 physical_addr; atomic_set(&cec->init_cancel, 1); wmb(); @@ -778,7 +858,29 @@ static int tegra_cec_suspend(struct platform_device *pdev, pm_message_t state) * Wakeup from SC7 on CEC is broken for T234 in HW. * Don't do anything for this while going to suspend. */ + + /* exchange higher and lower byte in physical address */ + physical_addr = + ((cec->physical_addr & 0xFF00) >> 8) | ((cec->physical_addr & 0x00FF) << 8); + /* Re-set the filter control */ + tegra_cec_writel(0x0, cec->cec_base + TEGRA_CEC_MESSAGE_FILTER_CTRL_0); + tegra_cec_writel(0x1, cec->cec_base + TEGRA_CEC_MESSAGE_FILTER_CTRL_0); + /* Set wake mask enable only for packet filter*/ + tegra_cec_writel(0x40000, cec->cec_base + TEGRA_CEC_WAKE_MASK_0); + /* Clear wake stat */ + tegra_cec_writel(0xFFFFFFFF, cec->cec_base + TEGRA_CEC_WAKE_STAT_0); + /* write opcode which needs to filter for wake up - set stream path */ + tegra_cec_writel(0x80000086, cec->cec_base + TEGRA_CEC_RX_OPCODE_0); + /* write opcode which needs to filter for wake up - set routing change */ + tegra_cec_writel(0x80000080, cec->cec_base + TEGRA_CEC_RX_OPCODE_1); + /* Program physical address - Write higher byte first and then lower byte */ + tegra_cec_writel(physical_addr, cec->cec_base + TEGRA_CEC_RX_PHYSICAL_ADDR_0); + /* Increase FIFO depth to receive as many as packet till cec driver resumes */ + tegra_cec_writel(0x40, cec->cec_base + TEGRA_CEC_RX_BUFFER_AFULL_CFG_0); + enable_irq_wake(cec->tegra_cec_irq); + dev_notice(&pdev->dev, "suspended\n"); + cec->is_tegra_cec_suspended = true; return 0; } @@ -788,18 +890,21 @@ static int tegra_cec_resume(struct platform_device *pdev) dev_notice(&pdev->dev, "Resuming\n"); + disable_irq_wake(cec->tegra_cec_irq); + /* * Wakeup from SC7 on CEC is broken for T234 in HW. * Don't do anything after exiting suspend. */ schedule_work(&cec->work); - + cec->is_tegra_cec_suspended = false; return 0; } #endif static struct of_device_id tegra_cec_of_match[] = { { .compatible = "nvidia,tegra234-cec"}, + { .compatible = "nvidia,tegra239-cec"}, {}, }; diff --git a/drivers/misc/tegra-cec/tegra_cec.h b/drivers/misc/tegra-cec/tegra_cec.h index c46708c8..b666f7b4 100644 --- a/drivers/misc/tegra-cec/tegra_cec.h +++ b/drivers/misc/tegra-cec/tegra_cec.h @@ -11,6 +11,7 @@ #include #define TEGRA_CEC_FRAME_MAX_LENGTH 16 +#define TEGRA_CEC_RX_FIFO_LENGTH 64 struct tegra_cec { struct device *dev; @@ -29,6 +30,7 @@ struct tegra_cec { atomic_t init_cancel; #endif u16 logical_addr; + u16 physical_addr; struct work_struct work; unsigned int rx_wake; unsigned int tx_wake; @@ -38,6 +40,9 @@ struct tegra_cec { u8 tx_buf_cur; u8 tx_buf_cnt; struct reset_control *reset; + bool is_tegra_cec_suspended; + u16 rx_fifo[TEGRA_CEC_RX_FIFO_LENGTH]; + u16 rx_fifo_data; }; #define TEGRA_CEC_LADDR_BROADCAST 0xF @@ -69,7 +74,7 @@ struct tegra_cec { #define TEGRA_CEC_RX_BUFFER_AFULL_CFG_0 0x04c #define TEGRA_CEC_RX_BUFFER_STAT_0 0x050 #define TEGRA_CEC_RX_TIMING_3_0 0x054 -#define TEGRA_CEC_MESSAGE_FILTER_CTRL 0X058 +#define TEGRA_CEC_MESSAGE_FILTER_CTRL_0 0X058 #define TEGRA_CEC_RX_PHYSICAL_ADDR_0 0X05C #define TEGRA_CEC_RX_OPCODE_0 0x060 #define TEGRA_CEC_RX_OPCODE_1 0x064