blob: 9b867e281bd74994c44c053070bbf6888635ddd8 [file] [log] [blame]
/*
* 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 <linux/version.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <asm/irq.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/spinlock_types.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/amlogic/pm.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_irq.h>
#include "hdmi_ao_cec.h"
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/of_address.h>
#include <linux/random.h>
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
#include <linux/amlogic/pm.h>
#endif
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/cec_common.h>
#include <linux/notifier.h>
#include <linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h>
#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;
};
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;
unsigned long flags;
waiting_aocec_free();
spin_lock_irqsave(&cec_dev->cec_reg_lock, flags);
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_irqrestore(&cec_dev->cec_reg_lock, flags);
return data32;
} /* aocec_rd_reg */
static void aocec_wr_reg(unsigned long addr, unsigned long data)
{
unsigned long data32;
unsigned long flags;
waiting_aocec_free();
spin_lock_irqsave(&cec_dev->cec_reg_lock, flags);
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_irqrestore(&cec_dev->cec_reg_lock, flags);
} /* 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 ceca_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)
ceca_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)
ceca_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(20);
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");
ceca_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);
pm_wakeup_event(cec_dev->dbg_dev, 2000);
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_AMLOGIC_LEGACY_EARLY_SUSPEND
struct early_suspend aocec_suspend_handler;
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 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_AMLOGIC_LEGACY_EARLY_SUSPEND
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
device_init_wakeup(cec_dev->dbg_dev, 1);
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[] = {
{
.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");