diff --git a/arch/arm/boot/dts/amlogic/meson8b.dtsi b/arch/arm/boot/dts/amlogic/meson8b.dtsi index 92a398b89563..e205d43c4ba7 100644 --- a/arch/arm/boot/dts/amlogic/meson8b.dtsi +++ b/arch/arm/boot/dts/amlogic/meson8b.dtsi @@ -527,6 +527,13 @@ function = "i2c_mst_ao"; }; }; + + hdmi_cec_1: hdmi_cec_1 { + mux { + groups = "hdmi_cec_1"; + function = "hdmi_cec"; + }; + }; }; dwc2_b { compatible = "amlogic,dwc2"; diff --git a/arch/arm/boot/dts/amlogic/meson8b_m200.dts b/arch/arm/boot/dts/amlogic/meson8b_m200.dts index 05f296009324..353ff97e1f9a 100644 --- a/arch/arm/boot/dts/amlogic/meson8b_m200.dts +++ b/arch/arm/boot/dts/amlogic/meson8b_m200.dts @@ -228,6 +228,22 @@ status = "okay"; }; }; + + aocec: aocec{ + compatible = "amlogic, amlogic-aocec"; + device_name = "aocec"; + status = "okay"; + vendor_id = <0x000000>; + cec_osd_string = "MBox"; /* Max Chars: 14 */ + cec_version = <6>; /* 5: 1.4, 6: 2.0 */ + port_num = <1>; + arc_port_mask = <0x0>; + interrupts = <0 151 1>; + interrupt-names = "hdmi_aocec"; + pinctrl-names = "hdmi_cec_1"; + pinctrl-0=<&hdmi_cec_1>; + reg = <0xc8100000 0x200>; + }; }; &uart_AO { diff --git a/arch/arm/configs/meson32_defconfig b/arch/arm/configs/meson32_defconfig index 36af2163f80d..54ac3618fe7b 100644 --- a/arch/arm/configs/meson32_defconfig +++ b/arch/arm/configs/meson32_defconfig @@ -66,6 +66,8 @@ CONFIG_AMLOGIC_INPUT=y CONFIG_AMLOGIC_SARADC=y CONFIG_AMLOGIC_MMC=y CONFIG_AMLOGIC_M8B_MMC=y +CONFIG_AMLOGIC_CEC=y +CONFIG_AMLOGIC_M8B_CEC=y CONFIG_AMLOGIC_POWER=y CONFIG_AMLOGIC_PMU_OF=y CONFIG_AMLOGIC_PMU=y diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index d82afc7ff21f..5c07f195dc7d 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -233,6 +233,7 @@ CONFIG_AMLOGIC_MMC=y CONFIG_AMLOGIC_NAND=y CONFIG_AMLOGIC_VRTC=y CONFIG_AMLOGIC_SMARTCARD=y +CONFIG_AMLOGIC_CEC=y CONFIG_AMLOGIC_AO_CEC=y CONFIG_AMLOGIC_SECURITY_KEY=y CONFIG_AMLOGIC_KEY_MANAGE=y diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index 320de5b6df0a..3cf1f6ed04d8 100644 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -58,7 +58,7 @@ obj-$(CONFIG_AMLOGIC_VRTC) += vrtc/ obj-$(CONFIG_AMLOGIC_SMARTCARD) += smartcard/ -obj-$(CONFIG_AMLOGIC_AO_CEC) += cec/ +obj-$(CONFIG_AMLOGIC_CEC) += cec/ obj-$(CONFIG_AMLOGIC_SECURITY_KEY) += securitykey/ diff --git a/drivers/amlogic/cec/Kconfig b/drivers/amlogic/cec/Kconfig index 3f5dbf3186fa..76b8a7b5d8ae 100644 --- a/drivers/amlogic/cec/Kconfig +++ b/drivers/amlogic/cec/Kconfig @@ -1,9 +1,28 @@ -menu "AO CEC Support" +menuconfig AMLOGIC_CEC + bool "AMLOGIC CEC Support" + depends on AMLOGIC_DRIVER + default n + help + HDMI cec driver provide cec support on Amlogic SOC chips, + you can use this driver to implement cec features on TV/MBOX + +choice + prompt "Amlogic cec chip driver select" + depends on AMLOGIC_CEC + default AMLOGIC_AO_CEC config AMLOGIC_AO_CEC - bool "HDMI AO cec driver support" + bool "HDMI AO cec driver support for gx/gxl/gxm/txl/txlx" + depends on AMLOGIC_CEC help - HDMI AO cec driver provide cec support on Amlogic SOC chips, you can - use this driver to implement cec features on TV/MBOX + HDMI cec driver provide cec support for gx/gxl/gxm/txl/txlx chips + select if your chip belongs -endmenu +config AMLOGIC_M8B_CEC + bool "HDMI CEC driver support for m8/m8b" + depends on MACH_MESON8B && AMLOGIC_CEC + help + HDMI CEC driver support for m8/m8b chips + Select if your chip belongs + +endchoice #AMLOGIC_CEC diff --git a/drivers/amlogic/cec/Makefile b/drivers/amlogic/cec/Makefile index 26488a150816..8b8548c6d0e1 100644 --- a/drivers/amlogic/cec/Makefile +++ b/drivers/amlogic/cec/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_AMLOGIC_AO_CEC) += hdmi_ao_cec.o +obj-$(CONFIG_AMLOGIC_M8B_CEC) += m8_ao_cec.o diff --git a/drivers/amlogic/cec/m8_ao_cec.c b/drivers/amlogic/cec/m8_ao_cec.c new file mode 100644 index 000000000000..0e231d5bcc38 --- /dev/null +++ b/drivers/amlogic/cec/m8_ao_cec.c @@ -0,0 +1,1978 @@ +/* + * drivers/amlogic/cec/m8_ao_cec.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include "hdmi_ao_cec.h" +#include +#include +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif +#include +#include +#include + +#include + +#define DEFAULT_DEBUG_EN 0 + +#define DBG_BUF_SIZE 80 +#define MAX_INT 0x7ffffff + +/* global struct for tx and rx */ +struct ao_cec_dev { + unsigned char rx_msg[MAX_MSG]; + /* bit filed for flags */ + struct { + unsigned char my_log_addr : 4; + unsigned char port_num : 4; /* first bytes */ + unsigned char cec_tx_result : 2; + unsigned char power_status : 2; + unsigned char rx_len : 4; /* second bytes */ + #ifdef CONFIG_PM + unsigned char cec_suspend : 2; + #endif + unsigned char dev_type : 3; + unsigned char cec_version : 3; /* third bytes */ + /* remaining bool flags */ + bool new_msg : 1; + bool phy_addr_test : 1; + bool wake_ok : 1; + bool cec_msg_dbg_en : 1; + bool hal_control : 1; + /* + * pin_status: tv support cec check, for box + * hpd_state: hdmi cable connected status + * + * hpd_state pin_status | TV support cec + * 0 x | N/A + * 1 0 | 0 + * 1 1 | 1 + */ + bool pin_status : 1; + bool hpd_state : 1; + }; + + /* hardware config */ + unsigned short arc_port; + unsigned short phy_addr; + unsigned short irq_cec; + unsigned short cec_line_cnt; + unsigned short hal_flag; + unsigned int port_seq; + + /* miscellaneous */ + unsigned int menu_lang; + unsigned int open_count; + dev_t dev_no; + + /* vendor related */ + unsigned int vendor_id; + char *osd_name; + char *product; + const char *pin_name; + + /* resource of register */ + void __iomem *cec_reg; + + /* kernel resource */ + struct input_dev *remote_cec_dev; + struct notifier_block hdmitx_nb; + struct workqueue_struct *cec_thread; + struct device *dbg_dev; + struct delayed_work cec_work; + struct completion rx_ok; + struct completion tx_ok; + spinlock_t cec_reg_lock; + struct mutex cec_mutex; + struct hrtimer start_bit_check; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend aocec_suspend_handler; +#endif +}; + +static struct ao_cec_dev *cec_dev; + +#define CEC_ERR(format, args...) \ + {if (cec_dev->dbg_dev) \ + dev_err(cec_dev->dbg_dev, format, ##args); \ + } + +#define CEC_INFO(format, args...) \ + {if (cec_dev->cec_msg_dbg_en && cec_dev->dbg_dev) \ + dev_info(cec_dev->dbg_dev, format, ##args); \ + } + + +#define waiting_aocec_free() \ + do {\ + unsigned long cnt = 0;\ + while (readl(cec_dev->cec_reg + AO_CEC_RW_REG) & (1<<23)) {\ + if (cnt++ >= 3500) { \ + pr_info("waiting aocec free time out.\n");\ + break;\ + } \ + } \ + } while (0) + +static void cec_set_reg_bits(unsigned int addr, unsigned int value, + unsigned int offset, unsigned int len) +{ + unsigned int data32 = 0; + + data32 = readl(cec_dev->cec_reg + addr); + data32 &= ~(((1 << len) - 1) << offset); + data32 |= (value & ((1 << len) - 1)) << offset; + writel(data32, cec_dev->cec_reg + addr); +} + +static unsigned int aocec_rd_reg(unsigned long addr) +{ + unsigned int data32; + + waiting_aocec_free(); + spin_lock(&cec_dev->cec_reg_lock); + data32 = 0; + data32 |= 0 << 16; /* [16] cec_reg_wr */ + data32 |= 0 << 8; /* [15:8] cec_reg_wrdata */ + data32 |= addr << 0; /* [7:0] cec_reg_addr */ + writel(data32, cec_dev->cec_reg + AO_CEC_RW_REG); + + waiting_aocec_free(); + data32 = ((readl(cec_dev->cec_reg + AO_CEC_RW_REG)) >> 24) & 0xff; + spin_unlock(&cec_dev->cec_reg_lock); + return data32; +} /* aocec_rd_reg */ + +static void aocec_wr_reg(unsigned long addr, unsigned long data) +{ + unsigned long data32; + + waiting_aocec_free(); + spin_lock(&cec_dev->cec_reg_lock); + data32 = 0; + data32 |= 1 << 16; /* [16] cec_reg_wr */ + data32 |= data << 8; /* [15:8] cec_reg_wrdata */ + data32 |= addr << 0; /* [7:0] cec_reg_addr */ + writel(data32, cec_dev->cec_reg + AO_CEC_RW_REG); + spin_unlock(&cec_dev->cec_reg_lock); +} /* aocec_wr_only_reg */ + +static void cec_hw_buf_clear(void) +{ + aocec_wr_reg(CEC_RX_MSG_CMD, RX_DISABLE); + aocec_wr_reg(CEC_TX_MSG_CMD, TX_ABORT); + aocec_wr_reg(CEC_RX_CLEAR_BUF, 1); + aocec_wr_reg(CEC_TX_CLEAR_BUF, 1); + udelay(100); + aocec_wr_reg(CEC_RX_CLEAR_BUF, 0); + aocec_wr_reg(CEC_TX_CLEAR_BUF, 0); + udelay(100); + aocec_wr_reg(CEC_RX_MSG_CMD, RX_NO_OP); + aocec_wr_reg(CEC_TX_MSG_CMD, TX_NO_OP); +} + +static void cec_logicaddr_set(int l_add) +{ + /* save logical address for suspend/wake up */ + cec_set_reg_bits(AO_DEBUG_REG1, l_add, 16, 4); + aocec_wr_reg(CEC_LOGICAL_ADDR0, 0); + cec_hw_buf_clear(); + aocec_wr_reg(CEC_LOGICAL_ADDR0, (l_add & 0xf)); + udelay(100); + aocec_wr_reg(CEC_LOGICAL_ADDR0, (0x1 << 4) | (l_add & 0xf)); + if (cec_dev->cec_msg_dbg_en) + CEC_INFO("set logical addr:0x%x\n", + aocec_rd_reg(CEC_LOGICAL_ADDR0)); +} + +static void cec_enable_irq(void) +{ + cec_set_reg_bits(AO_CEC_INTR_MASKN, 0x6, 0, 3); + CEC_INFO("enable:int mask:0x%x\n", + readl(cec_dev->cec_reg + AO_CEC_INTR_MASKN)); +} + +static void cec_arbit_bit_time_set(unsigned int bit_set, + unsigned int time_set, unsigned int flag) +{ /* 11bit:bit[10:0] */ + if (flag) { + CEC_INFO("bit_set:0x%x;time_set:0x%x\n", + bit_set, time_set); + } + + switch (bit_set) { + case 3: + /* 3 bit */ + if (flag) { + CEC_INFO("read 3 bit:0x%x%x\n", + aocec_rd_reg(AO_CEC_TXTIME_4BIT_BIT10_8), + aocec_rd_reg(AO_CEC_TXTIME_4BIT_BIT7_0)); + } + aocec_wr_reg(AO_CEC_TXTIME_4BIT_BIT7_0, time_set & 0xff); + aocec_wr_reg(AO_CEC_TXTIME_4BIT_BIT10_8, (time_set >> 8) & 0x7); + if (flag) { + CEC_INFO("write 3 bit:0x%x%x\n", + aocec_rd_reg(AO_CEC_TXTIME_4BIT_BIT10_8), + aocec_rd_reg(AO_CEC_TXTIME_4BIT_BIT7_0)); + } + break; + /* 5 bit */ + case 5: + if (flag) { + CEC_INFO("read 5 bit:0x%x%x\n", + aocec_rd_reg(AO_CEC_TXTIME_2BIT_BIT10_8), + aocec_rd_reg(AO_CEC_TXTIME_2BIT_BIT7_0)); + } + aocec_wr_reg(AO_CEC_TXTIME_2BIT_BIT7_0, time_set & 0xff); + aocec_wr_reg(AO_CEC_TXTIME_2BIT_BIT10_8, (time_set >> 8) & 0x7); + if (flag) { + CEC_INFO("write 5 bit:0x%x%x\n", + aocec_rd_reg(AO_CEC_TXTIME_2BIT_BIT10_8), + aocec_rd_reg(AO_CEC_TXTIME_2BIT_BIT7_0)); + } + break; + /* 7 bit */ + case 7: + if (flag) { + CEC_INFO("read 7 bit:0x%x%x\n", + aocec_rd_reg(AO_CEC_TXTIME_17MS_BIT10_8), + aocec_rd_reg(AO_CEC_TXTIME_17MS_BIT7_0)); + } + aocec_wr_reg(AO_CEC_TXTIME_17MS_BIT7_0, time_set & 0xff); + aocec_wr_reg(AO_CEC_TXTIME_17MS_BIT10_8, (time_set >> 8) & 0x7); + if (flag) { + CEC_INFO("write 7 bit:0x%x%x\n", + aocec_rd_reg(AO_CEC_TXTIME_17MS_BIT10_8), + aocec_rd_reg(AO_CEC_TXTIME_17MS_BIT7_0)); + } + break; + default: + break; + } +} + +static void cec_hw_reset(void) +{ + writel(0x1, cec_dev->cec_reg + AO_CEC_GEN_CNTL); + /* Enable gated clock (Normal mode). */ + cec_set_reg_bits(AO_CEC_GEN_CNTL, 1, 1, 1); + /* Release SW reset */ + udelay(100); + cec_set_reg_bits(AO_CEC_GEN_CNTL, 0, 0, 1); + + /* Enable all AO_CEC interrupt sources */ + cec_set_reg_bits(AO_CEC_INTR_MASKN, 0x6, 0, 3); + + cec_logicaddr_set(cec_dev->my_log_addr); + + /* Cec arbitration 3/5/7 bit time set. */ + cec_arbit_bit_time_set(3, 0x118, 0); + cec_arbit_bit_time_set(5, 0x000, 0); + cec_arbit_bit_time_set(7, 0x2aa, 0); + + CEC_INFO("hw reset :logical addr:0x%x\n", + aocec_rd_reg(CEC_LOGICAL_ADDR0)); +} + +static void cec_rx_buf_clear(void) +{ + aocec_wr_reg(CEC_RX_CLEAR_BUF, 0x1); + aocec_wr_reg(CEC_RX_CLEAR_BUF, 0x0); +} + +static int cec_rx_buf_check(void) +{ + unsigned int rx_num_msg; + + rx_num_msg = aocec_rd_reg(CEC_RX_NUM_MSG); + if (rx_num_msg) + CEC_INFO("rx msg num:0x%02x\n", rx_num_msg); + + return rx_num_msg; +} + +static void format_msg_str(const char *msg, char len, char *prefix, char *buf) +{ + int size = 0, i; + + size = sprintf(buf + size, "%s %2d:", prefix, len); + for (i = 0; i < len; i++) + size += sprintf(buf + size, " %02x", msg[i]); + buf[size] = '\0'; + WARN_ON(size >= DBG_BUF_SIZE); +} + +int cec_ll_rx(unsigned char *msg) +{ + int i; + int ret = -1; + int rx_stat; + int len; + + rx_stat = aocec_rd_reg(CEC_RX_MSG_STATUS); + if ((rx_stat != RX_DONE) || (aocec_rd_reg(CEC_RX_NUM_MSG) != 1)) { + CEC_INFO("rx status:%x\n", rx_stat); + writel((1 << 2), cec_dev->cec_reg + AO_CEC_INTR_CLR); + aocec_wr_reg(CEC_RX_MSG_CMD, RX_ACK_CURRENT); + aocec_wr_reg(CEC_RX_MSG_CMD, RX_NO_OP); + cec_rx_buf_clear(); + return ret; + } + + len = aocec_rd_reg(CEC_RX_MSG_LENGTH) + 1; + cec_dev->rx_len = len; + + for (i = 0; i < len && i < MAX_MSG; i++) + msg[i] = aocec_rd_reg(CEC_RX_MSG_0_HEADER + i); + + ret = rx_stat; + + if (cec_dev->cec_msg_dbg_en) { + char buf[DBG_BUF_SIZE] = {}; + + format_msg_str(msg, len, "CEC rx msg", buf); + pr_info("%s\n", buf); + } + + writel((1 << 2), cec_dev->cec_reg + AO_CEC_INTR_CLR); + aocec_wr_reg(CEC_RX_MSG_CMD, RX_ACK_CURRENT); + aocec_wr_reg(CEC_RX_MSG_CMD, RX_NO_OP); + cec_rx_buf_clear(); + cec_dev->pin_status = 1; + return ret; +} + +/************************ cec arbitration cts code **************************/ +/* using the cec pin as fiq gpi to assist the bus arbitration */ + +/* return value: 1: successful 0: error */ +static int cec_ll_trigle_tx(const unsigned char *msg, int len) +{ + int i; + int reg; + unsigned int j = 40; + unsigned int tx_stat; + int cec_timeout_cnt = 1; + + while (1) { + tx_stat = aocec_rd_reg(CEC_TX_MSG_STATUS); + if (tx_stat != TX_BUSY) + break; + + if (!(j--)) { + CEC_INFO("waiting busy timeout\n"); + aocec_wr_reg(CEC_TX_MSG_CMD, TX_ABORT); + cec_timeout_cnt++; + if (cec_timeout_cnt > 0x08) + cec_hw_reset(); + break; + } + msleep(20); + } + + reg = aocec_rd_reg(CEC_TX_MSG_STATUS); + if (reg == TX_IDLE || reg == TX_DONE) { + for (i = 0; i < len; i++) + aocec_wr_reg(CEC_TX_MSG_0_HEADER + i, msg[i]); + + aocec_wr_reg(CEC_TX_MSG_LENGTH, len-1); + aocec_wr_reg(CEC_TX_MSG_CMD, TX_REQ_CURRENT); + if (cec_dev->cec_msg_dbg_en) { + char buf[DBG_BUF_SIZE] = {}; + + format_msg_str(msg, len, "CEC tx msg", buf); + pr_info("%s\n", buf); + } + return 0; + } + return -1; +} + +static void tx_irq_handle(void) +{ + unsigned int tx_status = aocec_rd_reg(CEC_TX_MSG_STATUS); + + switch (tx_status) { + case TX_DONE: + aocec_wr_reg(CEC_TX_MSG_CMD, TX_NO_OP); + cec_dev->cec_tx_result = CEC_FAIL_NONE; + break; + + case TX_BUSY: + CEC_ERR("TX_BUSY\n"); + cec_dev->cec_tx_result = CEC_FAIL_BUSY; + break; + + case TX_ERROR: + if (cec_dev->cec_msg_dbg_en == 1) + CEC_ERR("TX ERROR!!!\n"); + if (aocec_rd_reg(CEC_RX_MSG_STATUS) == RX_ERROR) + cec_hw_reset(); + else + aocec_wr_reg(CEC_TX_MSG_CMD, TX_NO_OP); + cec_dev->cec_tx_result = CEC_FAIL_NACK; + break; + + case TX_IDLE: + CEC_ERR("TX_IDLE\n"); + cec_dev->cec_tx_result = CEC_FAIL_OTHER; + break; + default: + cec_dev->cec_tx_result = CEC_FAIL_OTHER; + break; + } + writel((1 << 1), cec_dev->cec_reg + AO_CEC_INTR_CLR); + complete(&cec_dev->tx_ok); +} + +static int get_line(void) +{ + int reg, cpu_type, ret = -EINVAL; + + reg = readl(cec_dev->cec_reg + AO_GPIO_I); + cpu_type = get_cpu_type(); + if (cpu_type <= MESON_CPU_MAJOR_ID_GXBB) + ret = (reg & (1 << 12)); + else + ret = 1; + return ret; +} + +static enum hrtimer_restart cec_line_check(struct hrtimer *timer) +{ + if (get_line() == 0) + cec_dev->cec_line_cnt++; + hrtimer_forward_now(timer, HR_DELAY(1)); + return HRTIMER_RESTART; +} + +static int check_confilct(void) +{ + int i; + + for (i = 0; i < 50; i++) { + /* + * sleep 20ms and using hrtimer to check cec line every 1ms + */ + cec_dev->cec_line_cnt = 0; + hrtimer_start(&cec_dev->start_bit_check, + HR_DELAY(1), HRTIMER_MODE_REL); + msleep(20); + hrtimer_cancel(&cec_dev->start_bit_check); + if (cec_dev->cec_line_cnt == 0) + break; + CEC_INFO("line busy:%d\n", cec_dev->cec_line_cnt); + } + if (i >= 50) + return -EBUSY; + else + return 0; +} + +static inline bool physical_addr_valid(void) +{ + if (cec_dev->dev_type == DEV_TYPE_TV) + return 1; + if (cec_dev->phy_addr_test) + return 1; + if (cec_dev->phy_addr == INVALID_PHY_ADDR) + return 0; + return 1; +} + +static int cec_hdmi_tx_notify_handler(struct notifier_block *nb, + unsigned long value, void *p) +{ + int ret = 0; + int phy_addr = 0; + + switch (value) { + case HDMITX_PLUG: + cec_dev->hpd_state = 1; + CEC_INFO("%s, HDMITX_PLUG\n", __func__); + break; + + case HDMITX_UNPLUG: + cec_dev->hpd_state = 0; + CEC_INFO("%s, HDMITX_UNPLUG\n", __func__); + break; + + case HDMITX_PHY_ADDR_VALID: + phy_addr = *((int *)p); + cec_dev->phy_addr = phy_addr & 0xffff; + cec_dev->hpd_state = 1; + CEC_INFO("%s, phy_addr %x ok\n", __func__, cec_dev->phy_addr); + break; + + default: + CEC_INFO("unsupported hdmitx notify:%ld, arg:%p\n", value, p); + ret = -EINVAL; + break; + } + return ret; +} + +static bool check_physical_addr_valid(int timeout) +{ + while (timeout > 0) { + if (!physical_addr_valid()) { + msleep(100); + timeout--; + } else + break; + } + if (timeout <= 0) + return false; + return true; +} + +/* Return value: < 0: fail, > 0: success */ +int cec_ll_tx(const unsigned char *msg, unsigned char len) +{ + int ret = -1; + int t = msecs_to_jiffies(2000); + int retry = 2; + + if (len == 0) + return CEC_FAIL_NONE; + + mutex_lock(&cec_dev->cec_mutex); + /* make sure physical address is valid before send */ + if (len >= 2 && msg[1] == CEC_OC_REPORT_PHYSICAL_ADDRESS) + check_physical_addr_valid(MAX_INT); + +try_again: + reinit_completion(&cec_dev->tx_ok); + /* + * CEC controller won't ack message if it is going to send + * state. If we detect cec line is low during waiting signal + * free time, that means a send is already started by other + * device, we should wait it finished. + */ + if (check_confilct()) { + CEC_ERR("bus confilct too long\n"); + mutex_unlock(&cec_dev->cec_mutex); + return CEC_FAIL_BUSY; + } + + ret = cec_ll_trigle_tx(msg, len); + if (ret < 0) { + /* we should increase send idx if busy */ + CEC_INFO("tx busy\n"); + if (retry > 0) { + retry--; + msleep(100 + (prandom_u32() & 0x07) * 10); + goto try_again; + } + mutex_unlock(&cec_dev->cec_mutex); + return CEC_FAIL_BUSY; + } + cec_dev->cec_tx_result = CEC_FAIL_OTHER; + ret = wait_for_completion_timeout(&cec_dev->tx_ok, t); + if (ret <= 0) { + /* timeout or interrupt */ + if (ret == 0) { + CEC_ERR("tx timeout\n"); + cec_hw_reset(); + } + ret = CEC_FAIL_OTHER; + } else { + ret = cec_dev->cec_tx_result; + } + if (ret != CEC_FAIL_NONE && ret != CEC_FAIL_NACK) { + if (retry > 0) { + retry--; + msleep(100 + (prandom_u32() & 0x07) * 10); + goto try_again; + } + } + mutex_unlock(&cec_dev->cec_mutex); + + return ret; +} + +/* -------------------------------------------------------------------------- */ +/* AO CEC0 config */ +/* -------------------------------------------------------------------------- */ +static void ao_cec_init(void) +{ + unsigned long data32; + + data32 = 0; + data32 |= 0 << 1; /* [2:1] cntl_clk: */ + /* 0=Disable clk (Power-off mode); */ + /* 1=Enable gated clock (Normal mode); */ + /* 2=Enable free-run clk (Debug mode). */ + data32 |= 1 << 0; /* [0] sw_reset: 1=Reset */ + writel(data32, cec_dev->cec_reg + AO_CEC_GEN_CNTL); + /* Enable gated clock (Normal mode). */ + cec_set_reg_bits(AO_CEC_GEN_CNTL, 1, 1, 1); + /* Release SW reset */ + cec_set_reg_bits(AO_CEC_GEN_CNTL, 0, 0, 1); + + /* Enable all AO_CEC interrupt sources */ + cec_enable_irq(); +} + +static unsigned int ao_cec_intr_stat(void) +{ + return readl(cec_dev->cec_reg + AO_CEC_INTR_STAT); +} + +static unsigned int cec_intr_stat(void) +{ + return ao_cec_intr_stat(); +} + +/* + *wr_flag: 1 write; value valid + * 0 read; value invalid + */ +static unsigned int cec_config(unsigned int value, bool wr_flag) +{ + if (wr_flag) + cec_set_reg_bits(AO_DEBUG_REG0, value, 0, 8); + + return readl(cec_dev->cec_reg + AO_DEBUG_REG0) & 0xff; +} + +/* + *wr_flag:1 write; value valid + * 0 read; value invalid + */ +static unsigned int cec_phyaddr_config(unsigned int value, bool wr_flag) +{ + if (wr_flag) + cec_set_reg_bits(AO_DEBUG_REG1, value, 0, 16); + + return readl(cec_dev->cec_reg + AO_DEBUG_REG1); +} + +static void cec_keep_reset(void) +{ + writel(0x1, cec_dev->cec_reg + AO_CEC_GEN_CNTL); +} +/* + * cec hw module init before allocate logical address + */ +static void cec_pre_init(void) +{ + unsigned int reg = readl(cec_dev->cec_reg + AO_RTI_STATUS_REG1); + + ao_cec_init(); + + cec_arbit_bit_time_set(3, 0x118, 0); + cec_arbit_bit_time_set(5, 0x000, 0); + cec_arbit_bit_time_set(7, 0x2aa, 0); + reg &= 0xfffff; + if ((reg & 0xffff) == 0xffff) + cec_dev->wake_ok = 0; + pr_info("cec: wake up flag:%x\n", reg); +} + +static int cec_late_check_rx_buffer(void) +{ + int ret; + struct delayed_work *dwork = &cec_dev->cec_work; + + ret = cec_rx_buf_check(); + if (!ret) + return 0; + /* + * start another check if rx buffer is full + */ + if ((-1) == cec_ll_rx(cec_dev->rx_msg)) { + CEC_INFO("buffer got unrecorgnized msg\n"); + cec_rx_buf_clear(); + ret = 0; + } else { + mod_delayed_work(cec_dev->cec_thread, dwork, 0); + ret = 1; + } + return ret; +} + +static void cec_key_report(int suspend) +{ + input_event(cec_dev->remote_cec_dev, EV_KEY, KEY_POWER, 1); + input_sync(cec_dev->remote_cec_dev); + input_event(cec_dev->remote_cec_dev, EV_KEY, KEY_POWER, 0); + input_sync(cec_dev->remote_cec_dev); + if (!suspend) + CEC_INFO("== WAKE UP BY CEC ==\n") + else + CEC_INFO("== SLEEP by CEC==\n") +} + +static void cec_give_version(unsigned int dest) +{ + unsigned char index = cec_dev->my_log_addr; + unsigned char msg[3]; + + if (dest != 0xf) { + msg[0] = ((index & 0xf) << 4) | dest; + msg[1] = CEC_OC_CEC_VERSION; + msg[2] = cec_dev->cec_version; + cec_ll_tx(msg, 3); + } +} + +static void cec_report_physical_address_smp(void) +{ + unsigned char msg[5]; + unsigned char index = cec_dev->my_log_addr; + unsigned char phy_addr_ab, phy_addr_cd; + + phy_addr_ab = (cec_dev->phy_addr >> 8) & 0xff; + phy_addr_cd = (cec_dev->phy_addr >> 0) & 0xff; + msg[0] = ((index & 0xf) << 4) | CEC_BROADCAST_ADDR; + msg[1] = CEC_OC_REPORT_PHYSICAL_ADDRESS; + msg[2] = phy_addr_ab; + msg[3] = phy_addr_cd; + msg[4] = cec_dev->dev_type; + + cec_ll_tx(msg, 5); +} + +static void cec_device_vendor_id(void) +{ + unsigned char index = cec_dev->my_log_addr; + unsigned char msg[5]; + unsigned int vendor_id; + + vendor_id = cec_dev->vendor_id; + msg[0] = ((index & 0xf) << 4) | CEC_BROADCAST_ADDR; + msg[1] = CEC_OC_DEVICE_VENDOR_ID; + msg[2] = (vendor_id >> 16) & 0xff; + msg[3] = (vendor_id >> 8) & 0xff; + msg[4] = (vendor_id >> 0) & 0xff; + + cec_ll_tx(msg, 5); +} + +static void cec_give_deck_status(unsigned int dest) +{ + unsigned char index = cec_dev->my_log_addr; + unsigned char msg[3]; + + msg[0] = ((index & 0xf) << 4) | dest; + msg[1] = CEC_OC_DECK_STATUS; + msg[2] = 0x1a; + cec_ll_tx(msg, 3); +} + +static void cec_menu_status_smp(int dest, int status) +{ + unsigned char msg[3]; + unsigned char index = cec_dev->my_log_addr; + + msg[0] = ((index & 0xf) << 4) | dest; + msg[1] = CEC_OC_MENU_STATUS; + if (status == DEVICE_MENU_ACTIVE) + msg[2] = DEVICE_MENU_ACTIVE; + else + msg[2] = DEVICE_MENU_INACTIVE; + cec_ll_tx(msg, 3); +} + +static void cec_inactive_source(int dest) +{ + unsigned char index = cec_dev->my_log_addr; + unsigned char msg[4]; + unsigned char phy_addr_ab, phy_addr_cd; + + phy_addr_ab = (cec_dev->phy_addr >> 8) & 0xff; + phy_addr_cd = (cec_dev->phy_addr >> 0) & 0xff; + msg[0] = ((index & 0xf) << 4) | dest; + msg[1] = CEC_OC_INACTIVE_SOURCE; + msg[2] = phy_addr_ab; + msg[3] = phy_addr_cd; + + cec_ll_tx(msg, 4); +} + +static void cec_set_osd_name(int dest) +{ + unsigned char index = cec_dev->my_log_addr; + unsigned char osd_len = strlen(cec_dev->osd_name); + unsigned char msg[16]; + + if (dest != 0xf) { + msg[0] = ((index & 0xf) << 4) | dest; + msg[1] = CEC_OC_SET_OSD_NAME; + memcpy(&msg[2], cec_dev->osd_name, osd_len); + + cec_ll_tx(msg, 2 + osd_len); + } +} + +static void cec_active_source_smp(void) +{ + unsigned char msg[4]; + unsigned char index = cec_dev->my_log_addr; + unsigned char phy_addr_ab; + unsigned char phy_addr_cd; + + phy_addr_ab = (cec_dev->phy_addr >> 8) & 0xff; + phy_addr_cd = (cec_dev->phy_addr >> 0) & 0xff; + msg[0] = ((index & 0xf) << 4) | CEC_BROADCAST_ADDR; + msg[1] = CEC_OC_ACTIVE_SOURCE; + msg[2] = phy_addr_ab; + msg[3] = phy_addr_cd; + cec_ll_tx(msg, 4); +} + +static void cec_request_active_source(void) +{ + unsigned char msg[2]; + unsigned char index = cec_dev->my_log_addr; + + msg[0] = ((index & 0xf) << 4) | CEC_BROADCAST_ADDR; + msg[1] = CEC_OC_REQUEST_ACTIVE_SOURCE; + cec_ll_tx(msg, 2); +} + +static void cec_set_stream_path(unsigned char *msg) +{ + unsigned int phy_addr_active; + + phy_addr_active = (unsigned int)(msg[2] << 8 | msg[3]); + if (phy_addr_active == cec_dev->phy_addr) { + cec_active_source_smp(); + /* + * some types of TV such as panasonic need to send menu status, + * otherwise it will not send remote key event to control + * device's menu + */ + cec_menu_status_smp(msg[0] >> 4, DEVICE_MENU_ACTIVE); + } +} + +static void cec_report_power_status(int dest, int status) +{ + unsigned char index = cec_dev->my_log_addr; + unsigned char msg[3]; + + msg[0] = ((index & 0xf) << 4) | dest; + msg[1] = CEC_OC_REPORT_POWER_STATUS; + msg[2] = status; + cec_ll_tx(msg, 3); +} + +static void cec_rx_process(void) +{ + int len = cec_dev->rx_len; + int initiator, follower; + int opcode; + unsigned char msg[MAX_MSG] = {}; + int dest_phy_addr; + + if (len < 2 || !cec_dev->new_msg) /* ignore ping message */ + return; + + memcpy(msg, cec_dev->rx_msg, len); + initiator = ((msg[0] >> 4) & 0xf); + follower = msg[0] & 0xf; + if (follower != 0xf && follower != cec_dev->my_log_addr) { + CEC_ERR("wrong rx message of bad follower:%x", follower); + return; + } + opcode = msg[1]; + switch (opcode) { + case CEC_OC_ACTIVE_SOURCE: + if (cec_dev->wake_ok == 0) { + int phy_addr = msg[2] << 8 | msg[3]; + + if (phy_addr == INVALID_PHY_ADDR) + break; + cec_dev->wake_ok = 1; + phy_addr |= (initiator << 16); + writel(phy_addr, cec_dev->cec_reg + AO_RTI_STATUS_REG1); + CEC_INFO("found wake up source:%x", phy_addr); + } + break; + + case CEC_OC_ROUTING_CHANGE: + dest_phy_addr = msg[4] << 8 | msg[5]; + if (dest_phy_addr == cec_dev->phy_addr) { + CEC_INFO("wake up by ROUTING_CHANGE\n"); + cec_key_report(0); + } + break; + + case CEC_OC_GET_CEC_VERSION: + cec_give_version(initiator); + break; + + case CEC_OC_GIVE_DECK_STATUS: + cec_give_deck_status(initiator); + break; + + case CEC_OC_GIVE_PHYSICAL_ADDRESS: + cec_report_physical_address_smp(); + break; + + case CEC_OC_GIVE_DEVICE_VENDOR_ID: + cec_device_vendor_id(); + break; + + case CEC_OC_GIVE_OSD_NAME: + cec_set_osd_name(initiator); + break; + + case CEC_OC_STANDBY: + cec_inactive_source(initiator); + cec_menu_status_smp(initiator, DEVICE_MENU_INACTIVE); + break; + + case CEC_OC_SET_STREAM_PATH: + cec_set_stream_path(msg); + /* wake up if in early suspend */ + if (cec_dev->cec_suspend == CEC_EARLY_SUSPEND) + cec_key_report(0); + break; + + case CEC_OC_REQUEST_ACTIVE_SOURCE: + if (!cec_dev->cec_suspend) + cec_active_source_smp(); + break; + + case CEC_OC_GIVE_DEVICE_POWER_STATUS: + if (cec_dev->cec_suspend) + cec_report_power_status(initiator, POWER_STANDBY); + else + cec_report_power_status(initiator, POWER_ON); + break; + + case CEC_OC_USER_CONTROL_PRESSED: + /* wake up by key function */ + if (cec_dev->cec_suspend == CEC_EARLY_SUSPEND) { + if (msg[2] == 0x40 || msg[2] == 0x6d) + cec_key_report(0); + } + break; + + case CEC_OC_MENU_REQUEST: + if (cec_dev->cec_suspend) + cec_menu_status_smp(initiator, DEVICE_MENU_INACTIVE); + else + cec_menu_status_smp(initiator, DEVICE_MENU_ACTIVE); + break; + + default: + CEC_ERR("unsupported command:%x\n", opcode); + break; + } + cec_dev->new_msg = 0; +} + +static bool cec_service_suspended(void) +{ + /* service is not enabled */ + if (!(cec_dev->hal_flag & (1 << HDMI_OPTION_SERVICE_FLAG))) + return false; + if (!(cec_dev->hal_flag & (1 << HDMI_OPTION_SYSTEM_CEC_CONTROL))) + return true; + return false; +} + +static void cec_task(struct work_struct *work) +{ + struct delayed_work *dwork; + + dwork = &cec_dev->cec_work; + if (cec_dev && (!cec_dev->wake_ok || cec_service_suspended())) + cec_rx_process(); + + if (!cec_late_check_rx_buffer()) + queue_delayed_work(cec_dev->cec_thread, dwork, CEC_FRAME_DELAY); +} + +static irqreturn_t cec_isr_handler(int irq, void *dev_instance) +{ + unsigned int intr_stat = 0; + struct delayed_work *dwork; + + dwork = &cec_dev->cec_work; + intr_stat = cec_intr_stat(); + if (intr_stat & (1<<1)) { /* aocec tx intr */ + tx_irq_handle(); + return IRQ_HANDLED; + } + if ((-1) == cec_ll_rx(cec_dev->rx_msg)) + return IRQ_HANDLED; + + complete(&cec_dev->rx_ok); + /* check rx buffer is full */ + cec_dev->new_msg = 1; + mod_delayed_work(cec_dev->cec_thread, dwork, 0); + return IRQ_HANDLED; +} + +static void check_wake_up(void) +{ + if (cec_dev->wake_ok == 0) + cec_request_active_source(); +} + +/******************** cec class interface *************************/ +static ssize_t device_type_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", cec_dev->dev_type); +} + +static ssize_t device_type_store(struct class *cla, + struct class_attribute *attr, const char *buf, size_t count) +{ + unsigned long type; + + if (kstrtol(buf, 10, &type)) + return -EINVAL; + + cec_dev->dev_type = type; + CEC_ERR("set dev_type to %ld\n", type); + return count; +} + +static ssize_t menu_language_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + char a, b, c; + + a = ((cec_dev->menu_lang >> 16) & 0xff); + b = ((cec_dev->menu_lang >> 8) & 0xff); + c = ((cec_dev->menu_lang >> 0) & 0xff); + return sprintf(buf, "%c%c%c\n", a, b, c); +} + +static ssize_t menu_language_store(struct class *cla, + struct class_attribute *attr, const char *buf, size_t count) +{ + char a, b, c; + + if (sscanf(buf, "%c%c%c", &a, &b, &c) != 3) + return -EINVAL; + + cec_dev->menu_lang = (a << 16) | (b << 8) | c; + CEC_ERR("set menu_language to %s\n", buf); + return count; +} + +static ssize_t vendor_id_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%x\n", cec_dev->vendor_id); +} + +static ssize_t vendor_id_store(struct class *cla, struct class_attribute *attr, + const char *buf, size_t count) +{ + int id; + + if (kstrtoint(buf, 16, &id)) + return -EINVAL; + cec_dev->vendor_id = id; + return count; +} + +static ssize_t port_num_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", cec_dev->port_num); +} + +static const char * const cec_reg_name1[] = { + "CEC_TX_MSG_LENGTH", + "CEC_TX_MSG_CMD", + "CEC_TX_WRITE_BUF", + "CEC_TX_CLEAR_BUF", + "CEC_RX_MSG_CMD", + "CEC_RX_CLEAR_BUF", + "CEC_LOGICAL_ADDR0", + "CEC_LOGICAL_ADDR1", + "CEC_LOGICAL_ADDR2", + "CEC_LOGICAL_ADDR3", + "CEC_LOGICAL_ADDR4", + "CEC_CLOCK_DIV_H", + "CEC_CLOCK_DIV_L" +}; + +static const char * const cec_reg_name2[] = { + "CEC_RX_MSG_LENGTH", + "CEC_RX_MSG_STATUS", + "CEC_RX_NUM_MSG", + "CEC_TX_MSG_STATUS", + "CEC_TX_NUM_MSG" +}; + +static ssize_t dump_reg_show(struct class *cla, + struct class_attribute *attr, char *b) +{ + int i, s = 0; + + s += sprintf(b + s, "TX buffer:\n"); + for (i = 0; i <= CEC_TX_MSG_F_OP14; i++) + s += sprintf(b + s, "%2d:%2x\n", i, aocec_rd_reg(i)); + + for (i = 0; i < ARRAY_SIZE(cec_reg_name1); i++) { + s += sprintf(b + s, "%s:%2x\n", + cec_reg_name1[i], aocec_rd_reg(i + 0x10)); + } + + s += sprintf(b + s, "RX buffer:\n"); + for (i = 0; i <= CEC_TX_MSG_F_OP14; i++) + s += sprintf(b + s, "%2d:%2x\n", i, aocec_rd_reg(i + 0x80)); + + for (i = 0; i < ARRAY_SIZE(cec_reg_name2); i++) { + s += sprintf(b + s, "%s:%2x\n", + cec_reg_name2[i], aocec_rd_reg(i + 0x90)); + } + return s; +} + +static ssize_t arc_port_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%x\n", cec_dev->arc_port); +} + +static ssize_t osd_name_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", cec_dev->osd_name); +} + +static ssize_t port_seq_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%x\n", cec_dev->port_seq); +} + +static ssize_t port_status_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + unsigned int tmp; + unsigned int tx_hpd; + + tx_hpd = cec_dev->hpd_state; + if (cec_dev->dev_type == DEV_TYPE_PLAYBACK) { + tmp = tx_hpd; + return sprintf(buf, "%x\n", tmp); + } + + tmp = hdmirx_rd_top(TOP_HPD_PWR5V); + CEC_INFO("TOP_HPD_PWR5V:%x\n", tmp); + tmp >>= 20; + tmp &= 0xf; + tmp |= (tx_hpd << 16); + return sprintf(buf, "%x\n", tmp); +} + +static ssize_t pin_status_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + unsigned int tx_hpd = 0; + char p; + + tx_hpd = cec_dev->hpd_state; + if (cec_dev->dev_type == DEV_TYPE_PLAYBACK) { + if (!tx_hpd) { + cec_dev->pin_status = 0; + return sprintf(buf, "%s\n", "disconnected"); + } + if (cec_dev->pin_status == 0) { + p = (cec_dev->my_log_addr << 4) | CEC_TV_ADDR; + if (cec_ll_tx(&p, 1) == CEC_FAIL_NONE) + return sprintf(buf, "%s\n", "ok"); + else + return sprintf(buf, "%s\n", "fail"); + } else + return sprintf(buf, "%s\n", "ok"); + } else { + return sprintf(buf, "%s\n", + cec_dev->pin_status ? "ok" : "fail"); + } +} + +static ssize_t physical_addr_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + unsigned int tmp = cec_dev->phy_addr; + + return sprintf(buf, "%04x\n", tmp); +} + +static ssize_t physical_addr_store(struct class *cla, + struct class_attribute *attr, + const char *buf, size_t count) +{ + int addr; + + if (kstrtoint(buf, 16, &addr)) + return -EINVAL; + + if (addr > INVALID_PHY_ADDR || addr < 0) { + CEC_ERR("invalid input:%s\n", buf); + cec_dev->phy_addr_test = 0; + return -EINVAL; + } + cec_dev->phy_addr = addr; + cec_dev->phy_addr_test = 1; + return count; +} + +static ssize_t dbg_en_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%x\n", cec_dev->cec_msg_dbg_en); +} + +static ssize_t dbg_en_store(struct class *cla, struct class_attribute *attr, + const char *buf, size_t count) +{ + int en; + + if (kstrtoint(buf, 10, &en)) + return -EINVAL; + + cec_dev->cec_msg_dbg_en = en ? 1 : 0; + return count; +} + +static ssize_t cmd_store(struct class *cla, struct class_attribute *attr, + const char *bu, size_t count) +{ + char buf[16] = {}; + int cnt; + + cnt = sscanf(bu, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + (int *)&buf[0], (int *)&buf[1], (int *)&buf[2], + (int *)&buf[3], (int *)&buf[4], (int *)&buf[5], + (int *)&buf[6], (int *)&buf[7], (int *)&buf[8], + (int *)&buf[9], (int *)&buf[10], (int *)&buf[11], + (int *)&buf[12], (int *)&buf[13], (int *)&buf[14], + (int *)&buf[15]); + if (cnt < 0) + return -EINVAL; + cec_ll_tx(buf, cnt); + return count; +} + +static ssize_t wake_up_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + unsigned int reg = readl(cec_dev->cec_reg + AO_RTI_STATUS_REG1); + + return sprintf(buf, "%x\n", reg & 0xfffff); +} + +static ssize_t fun_cfg_store(struct class *cla, struct class_attribute *attr, + const char *bu, size_t count) +{ + int cnt, val; + + cnt = kstrtoint(bu, 16, &val); + if (cnt < 0 || val > 0xff) + return -EINVAL; + cec_config(val, 1); + if (val == 0) + cec_keep_reset(); + else + cec_pre_init(); + return count; +} + +static ssize_t fun_cfg_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + unsigned int reg = cec_config(0, 0); + + return sprintf(buf, "0x%x\n", reg & 0xff); +} + +static ssize_t log_addr_store(struct class *cla, struct class_attribute *attr, + const char *bu, size_t count) +{ + int cnt, val; + + cnt = kstrtoint(bu, 16, &val); + if (cnt < 0 || val > 0xf) + return -EINVAL; + cec_logicaddr_set(val); + /* add by hal, to init some data structure */ + cec_dev->my_log_addr = val; + cec_dev->power_status = POWER_ON; + + return count; +} + +static ssize_t log_addr_show(struct class *cla, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%x\n", cec_dev->my_log_addr); +} + +static struct class_attribute aocec_class_attr[] = { + __ATTR_WO(cmd), + __ATTR_RO(port_num), + __ATTR_RO(port_seq), + __ATTR_RO(osd_name), + __ATTR_RO(dump_reg), + __ATTR_RO(port_status), + __ATTR_RO(pin_status), + __ATTR_RO(arc_port), + __ATTR_RO(wake_up), + __ATTR(physical_addr, 0664, physical_addr_show, physical_addr_store), + __ATTR(vendor_id, 0664, vendor_id_show, vendor_id_store), + __ATTR(menu_language, 0664, menu_language_show, menu_language_store), + __ATTR(device_type, 0664, device_type_show, device_type_store), + __ATTR(dbg_en, 0664, dbg_en_show, dbg_en_store), + __ATTR(fun_cfg, 0664, fun_cfg_show, fun_cfg_store), + __ATTR(log_addr, 0664, log_addr_show, log_addr_store), + __ATTR_NULL +}; + +/******************** cec hal interface ***************************/ +static int hdmitx_cec_open(struct inode *inode, struct file *file) +{ + cec_dev->open_count++; + if (cec_dev->open_count) { + cec_dev->hal_control = 1; + /* set default logical addr flag for uboot */ + cec_set_reg_bits(AO_DEBUG_REG1, 0xf, 16, 4); + } + return 0; +} + +static int hdmitx_cec_release(struct inode *inode, struct file *file) +{ + cec_dev->open_count--; + if (!cec_dev->open_count) + cec_dev->hal_control = 0; + return 0; +} + +static ssize_t hdmitx_cec_read(struct file *f, char __user *buf, + size_t size, loff_t *p) +{ + int ret; + + if ((cec_dev->hal_flag & (1 << HDMI_OPTION_SYSTEM_CEC_CONTROL))) + cec_dev->rx_len = 0; + ret = wait_for_completion_timeout(&cec_dev->rx_ok, CEC_FRAME_DELAY); + if (ret <= 0) + return ret; + if (cec_dev->rx_len == 0) + return 0; + + if (copy_to_user(buf, cec_dev->rx_msg, cec_dev->rx_len)) + return -EINVAL; + return cec_dev->rx_len; +} + +static ssize_t hdmitx_cec_write(struct file *f, const char __user *buf, + size_t size, loff_t *p) +{ + unsigned char tempbuf[16] = {}; + int ret; + + if (size > 16) + size = 16; + if (size <= 0) + return -EINVAL; + + if (copy_from_user(tempbuf, buf, size)) + return -EINVAL; + + ret = cec_ll_tx(tempbuf, size); + return ret; +} + +static void init_cec_port_info(struct hdmi_port_info *port, + struct ao_cec_dev *cec_dev) +{ + unsigned int a, b, c; + unsigned int phy_head = 0xf000, phy_app = 0x1000, phy_addr; + + /* physical address for TV or repeator */ + if (cec_dev->dev_type == DEV_TYPE_TV) + phy_addr = 0; + else if (cec_dev->phy_addr != INVALID_PHY_ADDR) + phy_addr = cec_dev->phy_addr; + else + phy_addr = 0; + + /* found physical address append for repeator */ + for (a = 0; a < 4; a++) { + if (phy_addr & phy_head) { + phy_head >>= 4; + phy_app >>= 4; + } else + break; + } + if (cec_dev->dev_type == DEV_TYPE_TUNER) + b = cec_dev->port_num - 1; + else + b = cec_dev->port_num; + + /* init for port info */ + for (a = 0; a < b; a++) { + port[a].type = HDMI_INPUT; + port[a].port_id = a + 1; + port[a].cec_supported = 1; + /* set ARC feature according mask */ + if (cec_dev->arc_port & (1 << a)) + port[a].arc_supported = 1; + else + port[a].arc_supported = 0; + + /* set port physical address according port sequence */ + if (cec_dev->port_seq) { + c = (cec_dev->port_seq >> (4 * a)) & 0xf; + port[a].physical_address = (c + 1) * phy_app + phy_addr; + } else { + /* asending order if port_seq is not set */ + port[a].physical_address = (a + 1) * phy_app + phy_addr; + } + } + + if (cec_dev->dev_type == DEV_TYPE_TUNER) { + /* last port is for tx in mixed tx/rx */ + port[a].type = HDMI_OUTPUT; + port[a].port_id = a + 1; + port[a].cec_supported = 1; + port[a].arc_supported = 0; + port[a].physical_address = phy_addr; + } +} + +static long hdmitx_cec_ioctl(struct file *f, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned long tmp = 0, i; + struct hdmi_port_info *port; + int tx_hpd = 0; + + switch (cmd) { + case CEC_IOC_GET_PHYSICAL_ADDR: + check_physical_addr_valid(20); + if (cec_dev->dev_type == DEV_TYPE_PLAYBACK && + !cec_dev->phy_addr_test) { + /* physical address for mbox */ + if (cec_dev->phy_addr == INVALID_PHY_ADDR) + return -EINVAL; + tmp = cec_dev->phy_addr; + } else { + if (cec_dev->dev_type == DEV_TYPE_TV) + tmp = 0; + /* for repeator */ + else if (cec_dev->phy_addr != INVALID_PHY_ADDR) + tmp = cec_dev->phy_addr; + else + tmp = 0; + } + if (!cec_dev->phy_addr_test) { + cec_dev->phy_addr = tmp; + cec_phyaddr_config(tmp, 1); + } else + tmp = cec_dev->phy_addr; + + if (copy_to_user(argp, &tmp, _IOC_SIZE(cmd))) + return -EINVAL; + break; + + case CEC_IOC_GET_VERSION: + tmp = cec_dev->cec_version; + if (copy_to_user(argp, &tmp, _IOC_SIZE(cmd))) + return -EINVAL; + break; + + case CEC_IOC_GET_VENDOR_ID: + tmp = cec_dev->vendor_id; + if (copy_to_user(argp, &tmp, _IOC_SIZE(cmd))) + return -EINVAL; + break; + + case CEC_IOC_GET_PORT_NUM: + tmp = cec_dev->port_num; + if (copy_to_user(argp, &tmp, _IOC_SIZE(cmd))) + return -EINVAL; + break; + + case CEC_IOC_GET_PORT_INFO: + port = kcalloc(cec_dev->port_num, sizeof(*port), GFP_KERNEL); + if (!port) { + CEC_ERR("no memory\n"); + return -EINVAL; + } + if (cec_dev->dev_type == DEV_TYPE_PLAYBACK) { + /* for tx only 1 port */ + tmp = cec_dev->phy_addr; + port->type = HDMI_OUTPUT; + port->port_id = 1; + port->cec_supported = 1; + /* not support arc for tx */ + port->arc_supported = 0; + port->physical_address = tmp & INVALID_PHY_ADDR; + if (copy_to_user(argp, port, sizeof(*port))) { + kfree(port); + return -EINVAL; + } + } else { + tmp = cec_dev->port_num; + init_cec_port_info(port, cec_dev); + if (copy_to_user(argp, port, sizeof(*port) * tmp)) { + kfree(port); + return -EINVAL; + } + } + kfree(port); + break; + + case CEC_IOC_SET_OPTION_WAKEUP: + tmp = cec_config(0, 0); + tmp &= ~(1 << AUTO_POWER_ON_MASK); + tmp |= (arg << AUTO_POWER_ON_MASK); + cec_config(tmp, 1); + break; + + case CEC_IOC_SET_AUTO_DEVICE_OFF: + tmp = cec_config(0, 0); + tmp &= ~(1 << ONE_TOUCH_STANDBY_MASK); + tmp |= (arg << ONE_TOUCH_STANDBY_MASK); + cec_config(tmp, 1); + break; + + case CEC_IOC_SET_OPTION_ENALBE_CEC: + tmp = (1 << HDMI_OPTION_ENABLE_CEC); + if (arg) { + cec_dev->hal_flag |= tmp; + cec_config(0x2f, 1); + cec_pre_init(); + } else { + cec_dev->hal_flag &= ~(tmp); + CEC_INFO("disable CEC\n"); + cec_config(0x0, 1); + cec_keep_reset(); + } + break; + + case CEC_IOC_SET_OPTION_SYS_CTRL: + tmp = (1 << HDMI_OPTION_SYSTEM_CEC_CONTROL); + if (arg) { + cec_dev->hal_flag |= tmp; + cec_config(0x2f, 1); + } else + cec_dev->hal_flag &= ~(tmp); + cec_dev->hal_flag |= (1 << HDMI_OPTION_SERVICE_FLAG); + break; + + case CEC_IOC_SET_OPTION_SET_LANG: + cec_dev->menu_lang = arg; + break; + + case CEC_IOC_GET_CONNECT_STATUS: + tx_hpd = cec_dev->hpd_state; + if (cec_dev->dev_type == DEV_TYPE_PLAYBACK) + tmp = tx_hpd; + else { + if (copy_from_user(&i, argp, _IOC_SIZE(cmd))) + return -EINVAL; + if (cec_dev->port_num == i && + cec_dev->dev_type == DEV_TYPE_TUNER) + tmp = tx_hpd; + else { /* mixed for rx */ + tmp = (hdmirx_rd_top(TOP_HPD_PWR5V) >> 20); + if (tmp & (1 << (i - 1))) + tmp = 1; + else + tmp = 0; + } + } + if (copy_to_user(argp, &tmp, _IOC_SIZE(cmd))) + return -EINVAL; + break; + + case CEC_IOC_ADD_LOGICAL_ADDR: + tmp = arg & 0xf; + cec_logicaddr_set(tmp); + /* add by hal, to init some data structure */ + cec_dev->my_log_addr = tmp; + if (cec_dev->dev_type != DEV_TYPE_PLAYBACK) + check_wake_up(); + break; + + case CEC_IOC_CLR_LOGICAL_ADDR: + /* TODO: clear global info */ + break; + + case CEC_IOC_SET_DEV_TYPE: + if (arg < DEV_TYPE_TV && arg > DEV_TYPE_VIDEO_PROCESSOR) + return -EINVAL; + cec_dev->dev_type = arg; + break; + + case CEC_IOC_SET_ARC_ENABLE: + /* select arc according arg */ + if (arg) + hdmirx_wr_top(TOP_ARCTX_CNTL, 0x03); + else + hdmirx_wr_top(TOP_ARCTX_CNTL, 0x00); + CEC_INFO("set arc en:%ld, reg:%lx\n", + arg, hdmirx_rd_top(TOP_ARCTX_CNTL)); + break; + + default: + break; + } + return 0; +} + +#ifdef CONFIG_COMPAT +static long hdmitx_cec_compat_ioctl(struct file *f, + unsigned int cmd, unsigned long arg) +{ + arg = (unsigned long)compat_ptr(arg); + return hdmitx_cec_ioctl(f, cmd, arg); +} +#endif + +/* for improve rw permission */ +static char *aml_cec_class_devnode(struct device *dev, umode_t *mode) +{ + if (mode) + *mode = 0666; + CEC_INFO("mode is %x\n", *mode); + return NULL; +} + +static struct class aocec_class = { + .name = CEC_DEV_NAME, + .class_attrs = aocec_class_attr, + .devnode = aml_cec_class_devnode, +}; + + +static const struct file_operations hdmitx_cec_fops = { + .owner = THIS_MODULE, + .open = hdmitx_cec_open, + .read = hdmitx_cec_read, + .write = hdmitx_cec_write, + .release = hdmitx_cec_release, + .unlocked_ioctl = hdmitx_cec_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = hdmitx_cec_compat_ioctl, +#endif +}; + +/************************ cec high level code *****************************/ +#ifdef CONFIG_HAS_EARLYSUSPEND +static void aocec_early_suspend(struct early_suspend *h) +{ + cec_dev->cec_suspend = CEC_EARLY_SUSPEND; + CEC_INFO("%s, suspend:%d\n", __func__, cec_dev->cec_suspend); +} + +static void aocec_late_resume(struct early_suspend *h) +{ + cec_dev->cec_suspend = 0; + CEC_INFO("%s, suspend:%d\n", __func__, cec_dev->cec_suspend); + +} +#endif + +static __init int aml_cec_probe(struct platform_device *pdev) +{ + struct device *cdev; +#ifdef CONFIG_OF + struct device_node *node = pdev->dev.of_node; + unsigned int tmp; + int irq_idx = 0, r, num; + const char *irq_name = NULL; + struct pinctrl *p; + struct resource *res; + resource_size_t *base; +#endif + + cec_dev = kzalloc(sizeof(struct ao_cec_dev), GFP_KERNEL); + if (!cec_dev) + return -ENOMEM; + cec_dev->cec_msg_dbg_en = DEFAULT_DEBUG_EN; + cec_dev->dev_type = DEV_TYPE_PLAYBACK; + cec_dev->dbg_dev = &pdev->dev; + cec_dev->wake_ok = 1; + cec_dev->phy_addr = INVALID_PHY_ADDR; + + /* cdev registe */ + r = class_register(&aocec_class); + if (r) { + CEC_ERR("regist class failed\n"); + return -EINVAL; + } + pdev->dev.class = &aocec_class; + cec_dev->dev_no = register_chrdev(0, CEC_DEV_NAME, + &hdmitx_cec_fops); + if (cec_dev->dev_no < 0) { + CEC_ERR("alloc chrdev failed\n"); + return -EINVAL; + } + CEC_INFO("alloc chrdev %x\n", cec_dev->dev_no); + cdev = device_create(&aocec_class, &pdev->dev, + MKDEV(cec_dev->dev_no, 0), + NULL, CEC_DEV_NAME); + if (IS_ERR(cdev)) { + CEC_ERR("create chrdev failed, dev:%p\n", cdev); + unregister_chrdev(cec_dev->dev_no, + CEC_DEV_NAME); + return -EINVAL; + } + + init_completion(&cec_dev->rx_ok); + init_completion(&cec_dev->tx_ok); + mutex_init(&cec_dev->cec_mutex); + spin_lock_init(&cec_dev->cec_reg_lock); + cec_dev->cec_thread = create_workqueue("cec_work"); + if (cec_dev->cec_thread == NULL) { + CEC_INFO("create work queue failed\n"); + return -EFAULT; + } + INIT_DELAYED_WORK(&cec_dev->cec_work, cec_task); + cec_dev->remote_cec_dev = input_allocate_device(); + if (!cec_dev->remote_cec_dev) + CEC_INFO("No enough memory\n"); + + cec_dev->remote_cec_dev->name = "cec_input"; + + cec_dev->remote_cec_dev->evbit[0] = BIT_MASK(EV_KEY); + cec_dev->remote_cec_dev->keybit[BIT_WORD(BTN_0)] = + BIT_MASK(BTN_0); + cec_dev->remote_cec_dev->id.bustype = BUS_ISA; + cec_dev->remote_cec_dev->id.vendor = 0x1b8e; + cec_dev->remote_cec_dev->id.product = 0x0cec; + cec_dev->remote_cec_dev->id.version = 0x0001; + + set_bit(KEY_POWER, cec_dev->remote_cec_dev->keybit); + + if (input_register_device(cec_dev->remote_cec_dev)) { + CEC_INFO("Failed to register device\n"); + input_free_device(cec_dev->remote_cec_dev); + } + +#ifdef CONFIG_OF + /* pinmux set */ + if (of_get_property(node, "pinctrl-names", NULL)) { + r = of_property_read_string(node, + "pinctrl-names", + &cec_dev->pin_name); + if (!r) + p = devm_pinctrl_get_select(&pdev->dev, + cec_dev->pin_name); + } + + /* irq set */ + irq_idx = of_irq_get(node, 0); + cec_dev->irq_cec = irq_idx; + if (of_get_property(node, "interrupt-names", NULL)) { + r = of_property_read_string(node, "interrupt-names", &irq_name); + if (!r) { + r = request_irq(irq_idx, &cec_isr_handler, IRQF_SHARED, + irq_name, (void *)cec_dev); + } + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + base = ioremap(res->start, res->end - res->start); + cec_dev->cec_reg = (void *)base; + } else { + CEC_ERR("no CEC reg resource\n"); + cec_dev->cec_reg = NULL; + } + + r = of_property_read_u32(node, "port_num", &num); + if (r) { + CEC_ERR("not find 'port_num'\n"); + cec_dev->port_num = 1; + } else + cec_dev->port_num = num; + r = of_property_read_u32(node, "arc_port_mask", &num); + if (r) { + CEC_ERR("not find 'arc_port_mask'\n"); + cec_dev->arc_port = 0; + } else + cec_dev->arc_port = num; + + r = of_property_read_u32(node, "vendor_id", &(cec_dev->vendor_id)); + if (r) + CEC_INFO("not find vendor id\n"); + + r = of_property_read_string(node, "cec_osd_string", + (const char **)&(cec_dev->osd_name)); + if (r) { + CEC_INFO("not find cec osd string\n"); + cec_dev->osd_name = "Amlogic"; + } + + r = of_property_read_u32(node, "cec_version", + &(tmp)); + if (r) { + /* default set to 2.0 */ + CEC_INFO("not find cec_version\n"); + cec_dev->cec_version = CEC_VERSION_20; + } else + cec_dev->cec_version = tmp; + + /* get port sequence */ + node = of_find_node_by_path("/hdmirx"); + if (node == NULL) { + CEC_ERR("can't find hdmirx\n"); + cec_dev->port_seq = 0; + } else { + r = of_property_read_u32(node, "rx_port_maps", + &(cec_dev->port_seq)); + if (r) + CEC_INFO("not find rx_port_maps\n"); + } +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + aocec_suspend_handler.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN - 20; + aocec_suspend_handler.suspend = aocec_early_suspend; + aocec_suspend_handler.resume = aocec_late_resume; + aocec_suspend_handler.param = cec_dev; + register_early_suspend(&aocec_suspend_handler); +#endif + hrtimer_init(&cec_dev->start_bit_check, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + cec_dev->start_bit_check.function = cec_line_check; + cec_dev->hdmitx_nb.notifier_call = cec_hdmi_tx_notify_handler; + hdmitx_event_notifier_regist(&cec_dev->hdmitx_nb); + /* for init */ + cec_pre_init(); + queue_delayed_work(cec_dev->cec_thread, &cec_dev->cec_work, 0); + return 0; +} + +static __exit int aml_cec_remove(struct platform_device *pdev) +{ + CEC_INFO("cec uninit!\n"); + free_irq(cec_dev->irq_cec, (void *)cec_dev); + + hdmitx_event_notifier_unregist(&cec_dev->hdmitx_nb); + if (cec_dev->cec_thread) { + cancel_delayed_work_sync(&cec_dev->cec_work); + destroy_workqueue(cec_dev->cec_thread); + } + input_unregister_device(cec_dev->remote_cec_dev); + unregister_chrdev(cec_dev->dev_no, CEC_DEV_NAME); + class_unregister(&aocec_class); + kfree(cec_dev); + return 0; +} + +#ifdef CONFIG_PM +static int aml_cec_pm_prepare(struct device *dev) +{ + cec_dev->cec_suspend = CEC_DEEP_SUSPEND; + CEC_INFO("%s, cec_suspend:%d\n", __func__, cec_dev->cec_suspend); + return 0; +} +static const struct dev_pm_ops aml_cec_pm = { + .prepare = aml_cec_pm_prepare, +}; +#endif + +#ifdef CONFIG_OF +static const struct of_device_id aml_cec_dt_match[] __initconst = { + { + .compatible = "amlogic, amlogic-aocec", + }, + {}, +}; +#endif + +static struct platform_driver aml_cec_driver = { + .driver = { + .name = "cectx", + .owner = THIS_MODULE, + #ifdef CONFIG_PM + .pm = &aml_cec_pm, + #endif + #ifdef CONFIG_OF + .of_match_table = aml_cec_dt_match, + #endif + }, + .probe = aml_cec_probe, + .remove = aml_cec_remove, +}; + +static int __init cec_init(void) +{ + int ret; + + ret = platform_driver_register(&aml_cec_driver); + return ret; +} + +static void __exit cec_uninit(void) +{ + platform_driver_unregister(&aml_cec_driver); +} + + +module_init(cec_init); +module_exit(cec_uninit); +MODULE_DESCRIPTION("AMLOGIC HDMI TX CEC driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h b/include/linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h index a80512cc81b7..02b65816b493 100644 --- a/include/linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h +++ b/include/linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h @@ -489,7 +489,7 @@ extern void hdmitx_event_notify(unsigned long state, void *arg); #else static inline struct hdmitx_dev *get_hdmitx_device(void) { -urn NULL; + return NULL; } static inline int get_hpd_state(void) {