/*
* drivers/misc/tegra-cec/tegra_cec.c
*
- * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2012-2014, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
struct miscdevice *miscdev = file->private_data;
struct tegra_cec *cec = container_of(miscdev,
struct tegra_cec, misc_dev);
+ int ret = 0;
+
dev_dbg(cec->dev, "%s\n", __func__);
- wait_event_interruptible(cec->init_waitq,
+ ret = wait_event_interruptible(cec->init_waitq,
atomic_read(&cec->init_done) == 1);
+ if (ret)
+ return ret;
file->private_data = cec;
- return 0;
+ return ret;
}
int tegra_cec_release(struct inode *inode, struct file *file)
return 0;
}
-ssize_t tegra_cec_write(struct file *file, const char __user *buffer,
+static inline void tegra_cec_native_tx(const struct tegra_cec *cec, u32 block)
+{
+ writel(block, cec->cec_base + TEGRA_CEC_TX_REGISTER);
+ writel(TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY,
+ cec->cec_base + TEGRA_CEC_INT_STAT);
+}
+
+static
+int tegra_cec_native_write_l(struct tegra_cec *cec, const u8 *buf, size_t cnt)
+{
+ int ret;
+ size_t i;
+ u32 start, mode, eom;
+ u32 mask;
+
+ /*
+ * In case previous transmission was interrupted by signal,
+ * driver will try to complete the frame anyway. However,
+ * this means we have to wait for it to finish before beginning
+ * subsequent transmission.
+ */
+ ret = wait_event_interruptible(cec->tx_waitq, cec->tx_wake == 1);
+ if (ret)
+ return ret;
+
+ mode = TEGRA_CEC_LADDR_MODE(buf[0]) << TEGRA_CEC_TX_REG_ADDR_MODE_SHIFT;
+
+ cec->tx_wake = 0;
+ cec->tx_error = 0;
+ cec->tx_buf_cur = 0;
+ cec->tx_buf_cnt = cnt;
+
+ for (i = 0; i < cnt; i++) {
+ start = i == 0 ? (1 << TEGRA_CEC_TX_REG_START_BIT_SHIFT) : 0;
+ eom = i == cnt-1 ? (1 << TEGRA_CEC_TX_REG_EOM_SHIFT) : 0;
+ cec->tx_buf[i] = start | mode | eom | buf[i];
+ }
+
+ mask = readl(cec->cec_base + TEGRA_CEC_INT_MASK);
+ writel(mask | TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY,
+ cec->cec_base + TEGRA_CEC_INT_MASK);
+
+ ret = wait_event_interruptible(cec->tx_waitq, cec->tx_wake == 1);
+ if (!ret)
+ ret = cec->tx_error;
+
+ return ret;
+}
+
+ssize_t tegra_cec_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
+ u8 tx_buf[TEGRA_CEC_FRAME_MAX_LENGTH];
struct tegra_cec *cec = file->private_data;
- unsigned long write_buff;
+ ssize_t ret;
- count = 4;
+ if (count == 0 || count > TEGRA_CEC_FRAME_MAX_LENGTH)
+ return -EMSGSIZE;
- wait_event_interruptible(cec->init_waitq,
+ ret = wait_event_interruptible(cec->init_waitq,
atomic_read(&cec->init_done) == 1);
+ if (ret)
+ return ret;
- if (copy_from_user(&write_buff, buffer, count))
+ if (copy_from_user(tx_buf, buf, count))
return -EFAULT;
- writel((TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY |
- TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN |
- TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD |
- TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED |
- TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED |
- TEGRA_CEC_INT_MASK_RX_REGISTER_FULL |
- TEGRA_CEC_INT_MASK_RX_REGISTER_OVERRUN),
- cec->cec_base + TEGRA_CEC_INT_MASK);
-
- wait_event_interruptible(cec->tx_waitq, cec->tx_wake == 1);
- writel(write_buff, cec->cec_base + TEGRA_CEC_TX_REGISTER);
- cec->tx_wake = 0;
-
- writel((TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN |
- TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD |
- TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED |
- TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED |
- TEGRA_CEC_INT_MASK_RX_REGISTER_FULL |
- TEGRA_CEC_INT_MASK_RX_REGISTER_OVERRUN),
- cec->cec_base + TEGRA_CEC_INT_MASK);
-
- write_buff = 0x00;
- return count;
+ mutex_lock(&cec->tx_lock);
+ ret = tegra_cec_native_write_l(cec, tx_buf, count);
+ mutex_unlock(&cec->tx_lock);
+ if (ret)
+ return ret;
+ else
+ return count;
}
ssize_t tegra_cec_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct tegra_cec *cec = file->private_data;
- count = 2;
+ ssize_t ret;
- wait_event_interruptible(cec->init_waitq,
+ count = sizeof(cec->rx_buffer);
+
+ ret = wait_event_interruptible(cec->init_waitq,
atomic_read(&cec->init_done) == 1);
+ if (ret)
+ return ret;
if (cec->rx_wake == 0)
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
- wait_event_interruptible(cec->rx_waitq, cec->rx_wake == 1);
+ ret = wait_event_interruptible(cec->rx_waitq, cec->rx_wake == 1);
+ if (ret)
+ return ret;
if (copy_to_user(buffer, &(cec->rx_buffer), count))
return -EFAULT;
return count;
}
+static inline void tegra_cec_error_recovery(struct tegra_cec *cec)
+{
+ u32 hw_ctrl;
+
+ hw_ctrl = readl(cec->cec_base + TEGRA_CEC_HW_CONTROL);
+ writel(0x0, cec->cec_base + TEGRA_CEC_HW_CONTROL);
+ writel(0xFFFFFFFF, cec->cec_base + TEGRA_CEC_INT_STAT);
+ writel(hw_ctrl, cec->cec_base + TEGRA_CEC_HW_CONTROL);
+}
+
static irqreturn_t tegra_cec_irq_handler(int irq, void *data)
{
struct device *dev = data;
struct tegra_cec *cec = dev_get_drvdata(dev);
- unsigned long status;
+ u32 status, mask;
status = readl(cec->cec_base + TEGRA_CEC_INT_STAT);
+ mask = readl(cec->cec_base + TEGRA_CEC_INT_MASK);
+
+ status &= mask;
if (!status)
- return IRQ_HANDLED;
+ goto out;
+
+ if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN) {
+ dev_err(dev, "tegra_cec: TX underrun, interrupt timing issue!\n");
+
+ tegra_cec_error_recovery(cec);
+ writel(mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY,
+ cec->cec_base + TEGRA_CEC_INT_MASK);
+
+ cec->tx_error = -EIO;
+ cec->tx_wake = 1;
+
+ wake_up_interruptible(&cec->tx_waitq);
+
+ goto out;
+ } else if ((status & TEGRA_CEC_INT_STAT_TX_ARBITRATION_FAILED) ||
+ (status & TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED)) {
+ tegra_cec_error_recovery(cec);
+ writel(mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY,
+ cec->cec_base + TEGRA_CEC_INT_MASK);
+
+ cec->tx_error = -ECOMM;
+ cec->tx_wake = 1;
+
+ wake_up_interruptible(&cec->tx_waitq);
+
+ goto out;
+ } else if (status & TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED) {
+ writel((TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED),
+ cec->cec_base + TEGRA_CEC_INT_STAT);
- if ((status & TEGRA_CEC_INT_STAT_RX_REGISTER_OVERRUN) ||
- (status & TEGRA_CEC_INT_STAT_RX_BUS_ANOMALY_DETECTED) ||
- (status & TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED) ||
- (status & TEGRA_CEC_INT_STAT_RX_BUS_ERROR_DETECTED)) {
+ if (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD) {
+ tegra_cec_error_recovery(cec);
+
+ cec->tx_error = TEGRA_CEC_LADDR_MODE(cec->tx_buf[0]) ?
+ -ECONNRESET : -EHOSTUNREACH;
+ }
+ cec->tx_wake = 1;
+
+ wake_up_interruptible(&cec->tx_waitq);
+
+ goto out;
+ } else if (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD)
+ dev_warn(dev, "tegra_cec: TX NAKed on the fly!\n");
+
+ if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY) {
+ if (cec->tx_buf_cur == cec->tx_buf_cnt)
+ writel(mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY,
+ cec->cec_base + TEGRA_CEC_INT_MASK);
+ else
+ tegra_cec_native_tx(cec,
+ cec->tx_buf[cec->tx_buf_cur++]);
+ }
+
+ if (status & (TEGRA_CEC_INT_STAT_RX_REGISTER_OVERRUN |
+ TEGRA_CEC_INT_STAT_RX_BUS_ANOMALY_DETECTED |
+ TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED |
+ TEGRA_CEC_INT_STAT_RX_BUS_ERROR_DETECTED)) {
writel((TEGRA_CEC_INT_STAT_RX_REGISTER_OVERRUN |
TEGRA_CEC_INT_STAT_RX_BUS_ANOMALY_DETECTED |
TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED |
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) {
- writel((TEGRA_CEC_INT_STAT_RX_REGISTER_FULL),
+ writel(TEGRA_CEC_INT_STAT_RX_REGISTER_FULL,
cec->cec_base + TEGRA_CEC_INT_STAT);
cec->rx_buffer = readw(cec->cec_base + TEGRA_CEC_RX_REGISTER);
cec->rx_wake = 1;
wake_up_interruptible(&cec->rx_waitq);
- } else if ((status & TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN) ||
- (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD) ||
- (status & TEGRA_CEC_INT_STAT_TX_ARBITRATION_FAILED) ||
- (status & TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED)) {
- writel((TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN |
- TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD |
- TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY |
- TEGRA_CEC_INT_STAT_TX_ARBITRATION_FAILED |
- TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED),
- cec->cec_base + TEGRA_CEC_INT_STAT);
- } else if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY) {
- cec->tx_wake = 1;
- wake_up_interruptible(&cec->tx_waitq);
- writel((TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY),
- cec->cec_base + TEGRA_CEC_INT_STAT);
- } else if (status & TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED) {
- writel((TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED),
- cec->cec_base + TEGRA_CEC_INT_STAT);
}
+out:
return IRQ_HANDLED;
}
static void tegra_cec_init(struct tegra_cec *cec)
{
+ cec->rx_wake = 0;
+ cec->tx_wake = 1;
+ cec->tx_buf_cnt = 0;
+ cec->tx_buf_cur = 0;
+ cec->tx_error = 0;
dev_notice(cec->dev, "%s started\n", __func__);
writel(0x00, cec->cec_base + TEGRA_CEC_SW_CONTROL);
- writel((
- (cec->logical_addr << TEGRA_CEC_HW_CONTROL_RX_LOGICAL_ADDRS_MASK) &
- (~TEGRA_CEC_HW_CONTROL_RX_SNOOP) &
- (~TEGRA_CEC_HW_CONTROL_RX_NAK_MODE) &
- (~TEGRA_CEC_HW_CONTROL_TX_NAK_MODE) &
- (~TEGRA_CEC_HW_CONTROL_FAST_SIM_MODE)) |
- (TEGRA_CEC_HW_CONTROL_TX_RX_MODE),
- cec->cec_base + TEGRA_CEC_HW_CONTROL);
+ cec->logical_addr = TEGRA_CEC_HWCTRL_RX_LADDR_UNREG;
+ writel(TEGRA_CEC_HWCTRL_RX_LADDR(cec->logical_addr) |
+ TEGRA_CEC_HWCTRL_TX_NAK_MODE |
+ TEGRA_CEC_HWCTRL_TX_RX_MODE,
+ cec->cec_base + TEGRA_CEC_HW_CONTROL);
writel(0x00, cec->cec_base + TEGRA_CEC_INPUT_FILTER);
cec->cec_base + TEGRA_CEC_TX_TIMING_1);
writel((0x07 << TEGRA_CEC_TX_TIMING_2_BUS_IDLE_TIME_ADDITIONAL_FRAME_MASK) |
- (0x05 << TEGRA_CEC_TX_TIMING_2_TX_BUS_IDLE_TIME_NEW_FRAME_MASK) |
- (0x03 << TEGRA_CEC_TX_TIMING_2_TX_BUS_IDLE_TIME_RETRY_FRAME_MASK),
+ (0x05 << TEGRA_CEC_TX_TIMING_2_BUS_IDLE_TIME_NEW_FRAME_MASK) |
+ (0x03 << TEGRA_CEC_TX_TIMING_2_BUS_IDLE_TIME_RETRY_FRAME_MASK),
cec->cec_base + TEGRA_CEC_TX_TIMING_2);
- writel((TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN |
- TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD |
- TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED |
- TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED |
- TEGRA_CEC_INT_MASK_RX_REGISTER_FULL |
- TEGRA_CEC_INT_MASK_RX_REGISTER_OVERRUN),
- cec->cec_base + TEGRA_CEC_INT_MASK);
+ writel(TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN |
+ TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD |
+ TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED |
+ TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED |
+ TEGRA_CEC_INT_MASK_TX_FRAME_TRANSMITTED |
+ TEGRA_CEC_INT_MASK_RX_REGISTER_FULL |
+ TEGRA_CEC_INT_MASK_RX_REGISTER_OVERRUN,
+ cec->cec_base + TEGRA_CEC_INT_MASK);
atomic_set(&cec->init_done, 1);
wake_up_interruptible(&cec->init_waitq);
{
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->logical_addr);
return 1;
}
atomic_set(&cec->init_done, 0);
- cec->logical_addr = TEGRA_CEC_LOGICAL_ADDR;
+ mutex_init(&cec->tx_lock);
cec->clk = clk_get(&pdev->dev, "cec");
/* set context info. */
cec->dev = &pdev->dev;
- cec->rx_wake = 0;
- cec->tx_wake = 0;
init_waitqueue_head(&cec->rx_waitq);
init_waitqueue_head(&cec->tx_waitq);
init_waitqueue_head(&cec->init_waitq);
}
ret = devm_request_irq(&pdev->dev, cec->tegra_cec_irq,
- tegra_cec_irq_handler, IRQF_DISABLED, "cec_irq", &pdev->dev);
+ tegra_cec_irq_handler, 0x0, "cec_irq", &pdev->dev);
if (ret) {
dev_err(&pdev->dev,
/*
* drivers/misc/tegra-cec/tegra_cec.h
*
- * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2012-2014, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#ifndef TEGRA_CEC_H
+#define TEGRA_CEC_H
+
#include <linux/pm.h>
#include <asm/atomic.h>
+#define TEGRA_CEC_FRAME_MAX_LENGTH 16
struct tegra_cec {
struct device *dev;
struct miscdevice misc_dev;
struct clk *clk;
+ struct mutex tx_lock;
void __iomem *cec_base;
int tegra_cec_irq;
wait_queue_head_t rx_waitq;
wait_queue_head_t tx_waitq;
wait_queue_head_t init_waitq;
- unsigned int rx_wake;
- unsigned int tx_wake;
- unsigned short rx_buffer;
- atomic_t init_done;
+ atomic_t init_done;
u16 logical_addr;
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;
};
static int tegra_cec_remove(struct platform_device *pdev);
+#define TEGRA_CEC_LADDR_BROADCAST 0xF
+#define TEGRA_CEC_LADDR_MASK 0xF
+#define TEGRA_CEC_LADDR_WIDTH 4
+#define TEGRA_CEC_LADDR_MODE(blk) \
+ ((blk & TEGRA_CEC_LADDR_MASK) == TEGRA_CEC_LADDR_BROADCAST)
+
/*CEC Timing registers*/
#define TEGRA_CEC_SW_CONTROL 0X000
#define TEGRA_CEC_HW_CONTROL 0X004
#define TEGRA_CEC_HW_DEBUG_RX 0X038
#define TEGRA_CEC_HW_DEBUG_TX 0X03C
-#define TEGRA_CEC_LOGICAL_ADDR 0x10
-#define TEGRA_CEC_HWCTRL_RX_LADDR_MASK 0xFFFF
-#define TEGRA_CEC_HWCTRL_RX_LADDR(x) (x<<0)
-
-#define TEGRA_CEC_HW_CONTROL_RX_LOGICAL_ADDRS_MASK 0
-#define TEGRA_CEC_HW_CONTROL_RX_SNOOP (1<<15)
-#define TEGRA_CEC_HW_CONTROL_RX_NAK_MODE (1<<16)
-#define TEGRA_CEC_HW_CONTROL_TX_NAK_MODE (1<<24)
-#define TEGRA_CEC_HW_CONTROL_FAST_SIM_MODE (1<<30)
-#define TEGRA_CEC_HW_CONTROL_TX_RX_MODE (1<<31)
-
-#define TEGRA_CEC_INPUT_FILTER_MODE (1<<31)
+#define TEGRA_CEC_MAX_LOGICAL_ADDR 15
+#define TEGRA_CEC_HWCTRL_RX_LADDR_UNREG 0x0
+#define TEGRA_CEC_HWCTRL_RX_LADDR_MASK 0x7FFF
+#define TEGRA_CEC_HWCTRL_RX_LADDR(x) \
+ ((x<<0) & TEGRA_CEC_HWCTRL_RX_LADDR_MASK)
+#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_FAST_SIM_MODE (1<<30)
+#define TEGRA_CEC_HWCTRL_TX_RX_MODE (1<<31)
+
+#define TEGRA_CEC_INPUT_FILTER_MODE (1<<31)
#define TEGRA_CEC_INPUT_FILTER_FIFO_LENGTH_MASK 0
-#define TEGRA_CEC_TX_REGISTER_DATA_MASK 0
-#define TEGRA_CEC_TX_REGISTER_EOM_MASK 8
-#define TEGRA_CEC_TX_REGISTER_ADDRESS_MODE_MASK 12
-#define TEGRA_CEC_TX_REGISTER_GENERATE_START_BIT_MASK 16
-#define TEGRA_CEC_TX_REGISTER_RETRY_FRAME_MASK 17
+#define TEGRA_CEC_TX_REG_DATA_SHIFT 0
+#define TEGRA_CEC_TX_REG_EOM_SHIFT 8
+#define TEGRA_CEC_TX_REG_ADDR_MODE_SHIFT 12
+#define TEGRA_CEC_TX_REG_START_BIT_SHIFT 16
+#define TEGRA_CEC_TX_REG_RETRY_BIT_SHIFT 17
#define TEGRA_CEC_RX_REGISTER_MASK 0
#define TEGRA_CEC_RX_REGISTER_EOM (1<<8)
#define TEGRA_CEC_TX_TIMING_1_TX_ACK_NAK_BIT_SAMPLE_TIME_MASK 24
#define TEGRA_CEC_TX_TIMING_2_BUS_IDLE_TIME_ADDITIONAL_FRAME_MASK 0
-#define TEGRA_CEC_TX_TIMING_2_TX_BUS_IDLE_TIME_NEW_FRAME_MASK 4
-#define TEGRA_CEC_TX_TIMING_2_TX_BUS_IDLE_TIME_RETRY_FRAME_MASK 8
+#define TEGRA_CEC_TX_TIMING_2_BUS_IDLE_TIME_NEW_FRAME_MASK 4
+#define TEGRA_CEC_TX_TIMING_2_BUS_IDLE_TIME_RETRY_FRAME_MASK 8
#define TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY (1<<0)
#define TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN (1<<1)
#define TEGRA_CEC_HW_DEBUG_TX_TXDATABIT_SAMPLE_TIMER (1<<26)
#define TEGRA_CEC_NAME "tegra_cec"
+
+#endif /* TEGRA_CEC_H */