diff options
author | Muralidhar Dixit <murali.dixit@ti.com> | 2012-05-10 15:53:54 +0530 |
---|---|---|
committer | Ziyan <jaraidaniel@gmail.com> | 2015-04-17 20:37:16 +0200 |
commit | b19d7fc175a9b7c1b3c5b623dde5daafbd59cdab (patch) | |
tree | 2f1c8f57d32ebf954d31e449c7c8f1876936cd67 /drivers/video/hdmi_ti_4xxx_ip.c | |
parent | 991cb73d31e0473a4053ab7d97137a92d0d59dea (diff) | |
download | kernel_samsung_tuna-b19d7fc175a9b7c1b3c5b623dde5daafbd59cdab.zip kernel_samsung_tuna-b19d7fc175a9b7c1b3c5b623dde5daafbd59cdab.tar.gz kernel_samsung_tuna-b19d7fc175a9b7c1b3c5b623dde5daafbd59cdab.tar.bz2 |
OMAPDSS: CEC: Adding support for CEC in HDMI driver
Adding support for CEC functionality in OMAP4 HDMI IP
specific file
Change-Id: I333e8ae484e0e56e0c597bca754f0276614d90ee
Signed-off-by: Muralidhar Dixit <murali.dixit@ti.com>
Conflicts:
include/video/hdmi_ti_4xxx_ip.h
Diffstat (limited to 'drivers/video/hdmi_ti_4xxx_ip.c')
-rw-r--r-- | drivers/video/hdmi_ti_4xxx_ip.c | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/drivers/video/hdmi_ti_4xxx_ip.c b/drivers/video/hdmi_ti_4xxx_ip.c index cca4fe2..fdb9b7b 100644 --- a/drivers/video/hdmi_ti_4xxx_ip.c +++ b/drivers/video/hdmi_ti_4xxx_ip.c @@ -37,6 +37,10 @@ #endif #include "hdmi_ti_4xxx_ip.h" +#define CEC_MAX_NUM_OPERANDS 14 +#define HDMI_CORE_CEC_RETRY 200 +#define HDMI_CEC_TX_CMD_RETRY 400 + #if defined(CONFIG_OMAP_REMOTE_PROC_IPU) && defined(CONFIG_RPMSG) static bool hdmi_acrwa_registered; struct omap_chip_id audio_must_use_tclk; @@ -220,6 +224,12 @@ static inline void __iomem *hdmi_core_sys_base(struct hdmi_ip_data *ip_data) (ip_data->base_wp + ip_data->hdmi_core_sys_offset); } +static inline void __iomem *hdmi_core_cec_base(struct hdmi_ip_data *ip_data) +{ + return (void __iomem *) + (ip_data->base_wp + ip_data->hdmi_cec_offset); +} + static inline int hdmi_wait_for_bit_change(void __iomem *base_addr, const struct hdmi_reg idx, int b2, int b1, u32 val) @@ -1548,6 +1558,421 @@ int hdmi_ti_4xx_check_aksv_data(struct hdmi_ip_data *ip_data) } EXPORT_SYMBOL(hdmi_ti_4xx_check_aksv_data); +int hdmi_ti_4xx_cec_read_rx_cmd(struct hdmi_ip_data *ip_data, + struct cec_rx_data *rx_data) +{ + int rx_byte_cnt; + int temp; + int i; + int cec_cmd_cnt = 0; + int r = 0; + + cec_cmd_cnt = REG_GET(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_COUNT, 6, 4); + + if (cec_cmd_cnt > 0) { + /*TODO:Check the RX error*/ + /*Get the initiator and destination id*/ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_CMD_HEADER); + rx_data->init_device_id = FLD_GET(temp, 7, 4); + rx_data->dest_device_id = FLD_GET(temp, 3, 0); + + /*get the command*/ + r = hdmi_ti_4xx_cec_get_rx_cmd(ip_data, &rx_data->rx_cmd); + if (r) { + pr_err(KERN_ERR "RX Error in reading cmd\n"); + goto error_exit; + } + + /*Get the rx command operands*/ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_COUNT); + rx_byte_cnt = FLD_GET(temp, 3, 0); + rx_data->rx_count = rx_byte_cnt; + if (rx_data->rx_count > CEC_MAX_NUM_OPERANDS) { + pr_err(KERN_ERR "RX wrong num of operands\n"); + r = -EINVAL; + } else { + for (i = 0; i < rx_byte_cnt; i++) { + temp = RD_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_OPERAND + (i * 4)); + rx_data->rx_operand[i] = FLD_GET(temp, 7, 0); + } + } + + /* Clear the just read command */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_RX_CONTROL, + 1, 0, 0); + + } else { + /*No cmd in the FIFO return error*/ + r = -EINVAL ; + } + +error_exit: + return r; + +} +EXPORT_SYMBOL(hdmi_ti_4xx_cec_read_rx_cmd); + +int hdmi_ti_4xx_cec_get_rx_cmd(struct hdmi_ip_data *ip_data, + char *rx_cmd) +{ + int temp; + int cec_cmd_cnt = 0; + int r = 0; + + if (!ip_data) + return -ENODEV; + + cec_cmd_cnt = REG_GET(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_COUNT, 6, 4); + + if (cec_cmd_cnt > 0) { + /*get the command*/ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_COMMAND); + *rx_cmd = FLD_GET(temp, 7, 0); + r = 0; + } else + r = -EINVAL; + + return r; + +} +EXPORT_SYMBOL(hdmi_ti_4xx_cec_get_rx_cmd); +int hdmi_ti_4xx_cec_transmit_cmd(struct hdmi_ip_data *ip_data, + struct cec_tx_data *data, int *cmd_acked) +{ + int r = EINVAL; + u32 retry = HDMI_CORE_CEC_RETRY; + u32 temp, i = 0; + + /* 1. Flush TX FIFO - required as change of initiator ID / destination + ID while TX is in progress could result in courrupted message. + 2. Clear interrupt status registers for TX. + 3. Set initiator Address, set retry count + 4. Set Destination Address + 5. Clear TX interrupt flags - if required + 6. Set the command + 7. Transmit + 8. Check for NACK / ACK - report the same. */ + + + /* Clear TX FIFO */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_DBG_3, 1, 7, 7); + + while (retry) { + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_DBG_3); + if (FLD_GET(temp, 7, 7) == 0) + break; + udelay(10); + retry--; + } + if (retry == 0x0) { + pr_err(KERN_ERR "Could not clear TX FIFO"); + pr_err(KERN_ERR "\n FIFO Reset - retry : %d - was %d\n", + retry, HDMI_CORE_CEC_RETRY); + goto error_exit; + } + + /* Clear TX interrupts */ + hdmi_write_reg(hdmi_core_cec_base(ip_data), HDMI_CEC_INT_STATUS_0, + HDMI_CEC_TX_FIFO_INT_MASK); + + hdmi_write_reg(hdmi_core_cec_base(ip_data), HDMI_CEC_INT_STATUS_1, + HDMI_CEC_RETRANSMIT_CNT_INT_MASK); + + /* Set the initiator addresses */ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_TX_INIT); + temp = FLD_MOD(temp, data->initiator_device_id, 3, 0); + hdmi_write_reg(hdmi_core_cec_base(ip_data), HDMI_CEC_TX_INIT, + temp); + + /*Set destination id*/ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_TX_DEST); + temp = FLD_MOD(temp, data->dest_device_id, 3, 0); + hdmi_write_reg(hdmi_core_cec_base(ip_data), HDMI_CEC_TX_DEST, + temp); + + + /* Set the retry count */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_DBG_3, + data->retry_count, 6, 4); + + if (data->send_ping) + goto send_ping; + + + /* Setup command and arguments for the command */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_TX_COMMAND, + data->tx_cmd, 7, 0); + + + for (i = 0; i < data->tx_count; i++) { + temp = RD_REG_32(hdmi_core_cec_base(ip_data), + (HDMI_CEC_TX_OPERAND + (i * 4))); + temp = FLD_MOD(temp, data->tx_operand[i], 7, 0); + WR_REG_32(hdmi_core_cec_base(ip_data), + (HDMI_CEC_TX_OPERAND + (i * 4)), temp); + } + + /* Operand count */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_TRANSMIT_DATA, + data->tx_count, 3, 0); + /* Transmit */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_TRANSMIT_DATA, + 0x1, 4, 4); + + goto wait_for_ack_nack; + +send_ping: + + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_TX_DEST, 0x1, 7, 7); + /*Wait for reset*/ + retry = HDMI_CORE_CEC_RETRY; + while (retry) { + if (!REG_GET(hdmi_core_cec_base(ip_data), HDMI_CEC_TX_DEST, + 7, 7)) + break; + udelay(10); + retry--; + } + if (retry == 0) { + pr_err(KERN_ERR "\nCould not send ping\n"); + goto error_exit; + } + +wait_for_ack_nack: + pr_debug("cec_transmit_cmd wait for ack\n"); + retry = HDMI_CEC_TX_CMD_RETRY; + *cmd_acked = -1; + do { + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_0); + /* Look for TX change event */ + if (FLD_GET(temp, 5, 5) != 0) { + *cmd_acked = 1; + /* Clear interrupt status */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_0, 0x1, 5, 5); + if (FLD_GET(temp, 2, 2) != 0) { + /* Clear interrupt status */ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_0, 0x1, 2, 2); + } + + r = 0; + break; + } + /* Wait for re-transmits to expire */ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_1); + if (FLD_GET(temp, 1, 1) == 0) { + /* Wait for 7 mSecs - As per CEC protocol + nominal bit period is ~3 msec + delay of >= 3 bit period before next attempt + */ + mdelay(10); + + } else { + /* Nacked ensure to clear the status */ + temp = FLD_MOD(0x0, 1, 1, 1); + hdmi_write_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_1, temp); + *cmd_acked = 0; + r = 0; + break; + } + retry--; + } while (retry); + + if (retry == 0x0) { + pr_err(KERN_ERR "\nCould not send\n"); + pr_err(KERN_ERR "\nNo ack / nack sensed\n"); + pr_err(KERN_ERR "\nResend did not complete in : %d\n", + ((HDMI_CEC_TX_CMD_RETRY - retry) * 10)); + } + +error_exit: + + return r; +} +EXPORT_SYMBOL(hdmi_ti_4xx_cec_transmit_cmd); + +int hdmi_ti_4xxx_power_on_cec(struct hdmi_ip_data *ip_data) +{ + int temp; + int r = 0; + int retry = HDMI_CORE_CEC_RETRY; + + /*Clear TX FIFO*/ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_DBG_3, 0x1, 7, 7); + while (retry) { + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_DBG_3); + if (FLD_GET(temp, 7, 7) == 0) + break; + retry--; + } + if (retry == 0x0) { + pr_err(KERN_ERR "Could not clear TX FIFO"); + r = -EBUSY; + goto error_exit; + } + + /*Clear RX FIFO*/ + hdmi_write_reg(hdmi_core_cec_base(ip_data), HDMI_CEC_RX_CONTROL, 0x3); + retry = HDMI_CORE_CEC_RETRY; + while (retry) { + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_RX_CONTROL); + if (FLD_GET(temp, 1, 0) == 0) + break; + retry--; + } + if (retry == 0x0) { + pr_err(KERN_ERR "Could not clear RX FIFO"); + r = -EBUSY; + goto error_exit; + } + + /*Clear CEC interrupts*/ + hdmi_write_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_1, + hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_1)); + hdmi_write_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_0, + hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_0)); + + /*Enable HDMI core interrupts*/ + + REG_FLD_MOD(hdmi_wp_base(ip_data), HDMI_WP_IRQENABLE_SET, 0x1, + 0, 0); + + + REG_FLD_MOD(hdmi_core_sys_base(ip_data), HDMI_CORE_SYS_UMASK4, 0x1, 3, + 3); + + /*Enable CEC interrupts*/ + /*command being received event*/ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_INT_ENABLE_0, 0x1, 0, + 0); + + /*RX fifo not empty event*/ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_INT_ENABLE_0, 0x1, 1, + 1); + + + /*Initialize CEC clock divider*/ + /*CEC needs 2MHz clock hence set the devider to 24 to get + 48/24=2MHz clock*/ + REG_FLD_MOD(hdmi_wp_base(ip_data), HDMI_WP_WP_CLK, 0x18, 5, 0); + + /*Remove BYpass mode*/ + + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_SETUP); + if (FLD_GET(temp, 4, 4) != 0) { + temp = FLD_MOD(temp, 0, 4, 4); + hdmi_write_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_SETUP, temp); + + /* If we enabled CEC in middle of a CEC messages on CEC n/w, + we could have start bit irregularity and/or short + pulse event. Clear them now */ + temp = hdmi_read_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_1); + temp = FLD_MOD(0x0, 0x5, 2, 0); + hdmi_write_reg(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_1, temp); + } + return 0; +error_exit: + return r; +} +EXPORT_SYMBOL(hdmi_ti_4xxx_power_on_cec); + +int hdmi_ti_4xxx_cec_get_rx_int(struct hdmi_ip_data *ip_data) +{ + u32 cec_rx = 0; + + cec_rx = REG_GET(hdmi_core_cec_base(ip_data), + HDMI_CEC_INT_STATUS_0, 1, 0); + return cec_rx; +} +EXPORT_SYMBOL(hdmi_ti_4xxx_cec_get_rx_int); + +int hdmi_ti_4xxx_cec_clr_rx_int(struct hdmi_ip_data *ip_data, int cec_rx) +{ + /*clear CEC RX interrupts*/ + REG_FLD_MOD(hdmi_core_cec_base(ip_data), HDMI_CEC_INT_STATUS_0, cec_rx, + 1, 0); + return 0; +} +EXPORT_SYMBOL(hdmi_ti_4xxx_cec_clr_rx_int); + +int hdmi_ti_4xxx_cec_get_listening_mask(struct hdmi_ip_data *ip_data) +{ + int dev_mask = 0; + + /*Store current device listning ids*/ + dev_mask = RD_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_CA_15_8); + dev_mask <<= 8; + dev_mask |= RD_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_CA_7_0); + return dev_mask; + +} +EXPORT_SYMBOL(hdmi_ti_4xxx_cec_get_listening_mask); + +int hdmi_ti_4xxx_cec_add_listening_device(struct hdmi_ip_data *ip_data, + int device_id, int clear) +{ + u32 temp, regis_reg, shift_cnt; + + /* Register to receive messages intended for this device + and broad cast messages */ + regis_reg = HDMI_CEC_CA_7_0; + shift_cnt = device_id; + temp = 0; + if (device_id > 0x7) { + regis_reg = HDMI_CEC_CA_15_8; + shift_cnt -= 0x7; + } + if (clear == 0x1) { + WR_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_CA_7_0, 0); + WR_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_CA_15_8, 0); + } else { + temp = RD_REG_32(hdmi_core_cec_base(ip_data), regis_reg); + } + temp |= 0x1 << shift_cnt; + WR_REG_32(hdmi_core_cec_base(ip_data), regis_reg, + temp); + return 0; + +} +EXPORT_SYMBOL(hdmi_ti_4xxx_cec_add_listening_device); + +int hdmi_ti_4xxx_cec_set_listening_mask(struct hdmi_ip_data *ip_data, int mask) +{ + WR_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_CA_15_8, (mask >> 8) & 0xff); + WR_REG_32(hdmi_core_cec_base(ip_data), + HDMI_CEC_CA_7_0, mask & 0xff); + return 0; +} +EXPORT_SYMBOL(hdmi_ti_4xxx_cec_set_listening_mask); + static int __init hdmi_ti_4xxx_init(void) { #if defined(CONFIG_OMAP_REMOTE_PROC_IPU) && defined(CONFIG_RPMSG) |