/*++
* linux/drivers/video/wmt/dev-cec.c
* WonderMedia video post processor (VPP) driver
*
* Copyright c 2013 WonderMedia Technologies, Inc.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* WonderMedia Technologies, Inc.
* 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
--*/
#define DEV_CEC_C
/* #define DEBUG */
#include
#include
#include
#include
#include
#include "cec.h"
#include "vpp.h"
#define DRIVER_NAME "wmt-cec"
#define TXNORSP 0x8000 /* Tx no response interrupt */
#define TXLSARB 0x2000 /* Tx lose arbitration */
#define TXDONE 0x0100 /* Tx done */
#define RXFAIL 0x0002 /* Rx fail, details in Rx10 */
#define RXDONE 0x0001 /* Rx done */
#define MAX_RETRY 3
#define MAX_TIMEOUT 5000
static int tx_state;
static struct cdev *wmt_cec_cdev;
static DEFINE_SEMAPHORE(wmt_cec_sem);
struct wmt_cec_msg recv_msg;
static void wmt_cec_do_rx_notify(struct work_struct *ptr)
{
vpp_netlink_notify(USER_PID, DEVICE_RX_DATA, (int)&recv_msg);
}
DECLARE_DELAYED_WORK(wmt_cec_rx_work, wmt_cec_do_rx_notify);
/* receive message interrupt handling */
static void wmt_cec_do_recv(void)
{
memset(&recv_msg, 0, sizeof(recv_msg));
/* get received data */
recv_msg.msglen = wmt_cec_rx_data(recv_msg.msgdata);
if (recv_msg.msglen > MAX_MSG_BYTE)
recv_msg.msglen = MAX_MSG_BYTE;
DBGMSG("read a received byte! msglen: %d\n", recv_msg.msglen);
/* clear receive blockage */
/* notify AP the received message, let AP decide what to response */
schedule_delayed_work(&wmt_cec_rx_work, HZ / 10);
}
/* check if logic address is valid */
static int bvalidaddr(char addr)
{
if (addr > 15)
return 0;
return 1;
}
/* make sure cec line is not busy */
static int tx_get_cecline(void)
{
int timeout = 400;
while (timeout-- > 0) {
/* if not busy */
if (1)
return 0;
msleep(1);
}
return -ETIMEDOUT;
}
/* transfer a time*/
DECLARE_COMPLETION(txcomp);
static int wmt_cec_do_xfer_one(char *msgdata, int msglen)
{
int ret;
unsigned long jiffies;
ret = tx_get_cecline();
if (ret != 0)
ret = -EAGAIN;
wmt_cec_tx_data(msgdata, msglen);
jiffies = msecs_to_jiffies((unsigned int)MAX_TIMEOUT);
jiffies = wait_for_completion_timeout(&txcomp, jiffies);
if (jiffies == 0)
return -EAGAIN;
if (tx_state == TXLSARB)
return -EAGAIN;
return tx_state;
}
/* transfer a message, including retransmission if needed*/
static int wmt_cec_do_xfer(char *msgdata, int msglen)
{
int retry;
int ret;
char srcaddr, tgtaddr;
if (msglen < 1) {
DPRINT("[CEC] xfer: invalid message, msglen is less than 1.\n");
return -1;
}
srcaddr = (msgdata[0] & 0xf0) >> 4;
tgtaddr = (msgdata[0] & 0x0f);
if (!bvalidaddr(srcaddr) || !bvalidaddr(tgtaddr)) {
DPRINT("[CEC] xfer: invalid logic address in msg data.\n");
return -1;
}
for (retry = 0; retry < MAX_RETRY; retry++) {
ret = wmt_cec_do_xfer_one(msgdata, msglen);
if (ret != -EAGAIN)
goto out;
DPRINT("[CEC] Retrying transmission (%d)\n", retry);
udelay(100);
}
return -EAGAIN;
out:
/* if polling message ret is no-response, set logical address register,
and enable slave mode */
if (srcaddr == tgtaddr && msglen == 1) {
if (ret == TXNORSP) {
DBGMSG("[CEC] logic addr register is 0x%x\n", tgtaddr);
return 0;
} else
return -1;
} else if (ret == TXDONE)
return 0;
else
return -1;
}
/* irq handling function */
irqreturn_t wmt_cec_do_irq(int irq, void *dev_id)
{
int sts;
sts = wmt_cec_get_int();
wmt_cec_clr_int(sts);
if (sts & BIT0) { /* tx done */
DBGMSG("[CEC] write ok int\n");
complete(&txcomp);
tx_state = TXDONE;
}
if (sts & BIT1) { /* rx done */
DBGMSG("[CEC] read ok int\n");
wmt_cec_do_recv();
}
if (sts & BIT2) { /* rx error */
DBGMSG("[CEC] read error int\n");
}
if (sts & BIT3) { /* tx arb fail */
DBGMSG("[CEC] wr arb fail int\n");
complete(&txcomp);
tx_state = TXLSARB;
}
if (sts & BIT4) { /* tx no ack */
/* DBGMSG("[CEC] write no ack int(addr not match)\n"); */
complete(&txcomp);
tx_state = TXNORSP;
}
return IRQ_HANDLED;
}
static int wmt_cec_open(
struct inode *inode,
struct file *filp
)
{
int ret = 0;
DBGMSG("[CEC] wmt_cec_open\n");
down(&wmt_cec_sem);
wmt_cec_rx_enable(1);
up(&wmt_cec_sem);
return ret;
} /* End of videodecoder_open() */
static int wmt_cec_release(
struct inode *inode,
struct file *filp
)
{
int ret = 0;
DBGMSG("[CEC] wmt_cec_release\n");
down(&wmt_cec_sem);
wmt_cec_rx_enable(0);
up(&wmt_cec_sem);
return ret;
} /* End of videodecoder_release() */
static long wmt_cec_ioctl(
struct file *filp,
unsigned int cmd,
unsigned long arg
)
{
int ret = -EINVAL;
DBGMSG("[CEC] wmt_cec_ioctl 0x%x,0x%x\n", cmd, arg);
/* check type and number, if fail return ENOTTY */
if (_IOC_TYPE(cmd) != WMT_CEC_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > WMT_CEC_IOC_MAXNR)
return -ENOTTY;
/* check argument area */
if (_IOC_DIR(cmd) & _IOC_READ)
ret = !access_ok(VERIFY_WRITE,
(void __user *) arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
ret = !access_ok(VERIFY_READ,
(void __user *) arg, _IOC_SIZE(cmd));
else
ret = 0;
if (ret)
return -EFAULT;
down(&wmt_cec_sem);
switch (cmd) {
case CECIO_TX_DATA:
{
struct wmt_cec_msg msg;
ret = copy_from_user((void *) &msg, (const void *)arg,
sizeof(struct wmt_cec_msg));
wmt_cec_do_xfer(msg.msgdata, msg.msglen);
}
break;
case CECIO_TX_LOGADDR:
wmt_cec_set_logical_addr((arg & 0xFF00) >> 8, arg & 0xFF, 1);
break;
case CECIO_RX_PHYADDR:
{
wmt_phy_addr_t parm;
parm.phy_addr = edid_get_hdmi_phy_addr();
ret = copy_to_user((void *)arg, (void *)&parm,
sizeof(wmt_phy_addr_t));
}
break;
default:
DBGMSG("[CEC] *W* wmt_cec_ioctl cmd 0x%x\n", cmd);
break;
}
up(&wmt_cec_sem);
return ret;
} /* End of videodecoder_ioctl() */
static int wmt_cec_mmap(
struct file *filp,
struct vm_area_struct *vma
)
{
int ret = -EINVAL;
down(&wmt_cec_sem);
up(&wmt_cec_sem);
return ret;
}
static ssize_t wmt_cec_read(
struct file *filp,
char __user *buf,
size_t count,
loff_t *f_pos
)
{
ssize_t ret = 0;
down(&wmt_cec_sem);
up(&wmt_cec_sem);
return ret;
} /* videodecoder_read */
static ssize_t wmt_cec_write(
struct file *filp,
const char __user *buf,
size_t count,
loff_t *f_pos
)
{
ssize_t ret = 0;
down(&wmt_cec_sem);
up(&wmt_cec_sem);
return ret;
} /* End of videodecoder_write() */
const struct file_operations wmt_cec_fops = {
.owner = THIS_MODULE,
.open = wmt_cec_open,
.release = wmt_cec_release,
.read = wmt_cec_read,
.write = wmt_cec_write,
.unlocked_ioctl = wmt_cec_ioctl,
.mmap = wmt_cec_mmap,
};
static int __init wmt_cec_probe
(
struct platform_device *dev /*!<; // a pointer to struct device */
)
{
dev_t dev_no;
int ret;
DBGMSG("[CEC] Enter wmt_cec_probe\n");
wmt_cec_init_hw();
wmt_cec_set_logical_addr(4, 0xf, 1); /* default set boardcast address */
wmt_cec_rx_enable(0);
/* wmt_cec_enable_loopback(1); */
dev_no = MKDEV(CEC_MAJOR, 0);
ret = register_chrdev_region(dev_no, 1, "wmtcec");
if (ret < 0) {
DPRINT("can't get %s dev major %d\n", DRIVER_NAME, CEC_MAJOR);
return ret;
}
/* register char device */
wmt_cec_cdev = cdev_alloc();
if (!wmt_cec_cdev) {
DPRINT("alloc dev error.\n");
return -ENOMEM;
}
cdev_init(wmt_cec_cdev, &wmt_cec_fops);
ret = cdev_add(wmt_cec_cdev, dev_no, 1);
if (ret) {
DPRINT("reg char dev error(%d).\n", ret);
cdev_del(wmt_cec_cdev);
return ret;
}
if (vpp_request_irq(IRQ_VPP_IRQ20, wmt_cec_do_irq,
SA_INTERRUPT, "cec", (void *) 0))
DPRINT("*E* request CEC ISR fail\n");
cec_regs->int_enable = 0x1f;
DBGMSG("[CEC] Exit wmt_cec_probe(0x%x)\n", dev_no);
return 0;
} /* End of wmt_cec_probe */
static int wmt_cec_remove
(
struct platform_device *dev /*!<; // a pointer point to struct device */
)
{
return 0;
} /* End of wmt_cec_remove */
#ifdef CONFIG_PM
static int wmt_cec_suspend
(
struct platform_device *pDev, /*!<; // a pointer to struct device */
pm_message_t state /*!<; // suspend state */
)
{
DPRINT("Enter wmt_cec_suspend\n");
wmt_cec_do_suspend();
return 0;
} /* End of wmt_cec_suspend */
static int wmt_cec_resume
(
struct platform_device *pDev /*!<; // a pointer to struct device */
)
{
DPRINT("Enter wmt_cec_resume\n");
wmt_cec_do_resume();
return 0;
} /* End of wmt_cec_resume */
#else
#define wmt_cec_suspend NULL
#define wmt_cec_resume NULL
#endif
/***************************************************************************
device driver struct define
****************************************************************************/
static struct platform_driver wmt_cec_driver = {
.driver.name = "wmtcec", /* equal to platform device name. */
.driver.bus = &platform_bus_type,
.probe = wmt_cec_probe,
.remove = wmt_cec_remove,
.suspend = wmt_cec_suspend,
.resume = wmt_cec_resume,
};
/***************************************************************************
platform device struct define
****************************************************************************/
static u64 wmt_cec_dma_mask = 0xffffffffUL;
static struct platform_device wmt_cec_device = {
.name = "wmtcec",
.dev = {
.dma_mask = &wmt_cec_dma_mask,
.coherent_dma_mask = ~0,
},
#if 0
.id = 0,
.dev = {
.release = wmt_cec_platform_release,
},
.num_resources = 0, /* ARRAY_SIZE(wmt_cec_resources), */
.resource = NULL, /* wmt_cec_resources, */
#endif
};
static int __init wmt_cec_init(void)
{
int ret;
char buf[100];
int varlen = 100;
unsigned int cec_enable = 0;
if (wmt_getsyspara("wmt.display.cec", buf, &varlen) == 0)
vpp_parse_param(buf, &cec_enable, 1, 0);
if (cec_enable == 0)
return 0;
DBGMSG(KERN_ALERT "Enter wmt_cec_init\n");
ret = platform_driver_register(&wmt_cec_driver);
if (!ret) {
ret = platform_device_register(&wmt_cec_device);
if (ret)
platform_driver_unregister(&wmt_cec_driver);
}
return ret;
}
static void __exit wmt_cec_exit(void)
{
dev_t dev_no;
DBGMSG(KERN_ALERT "Enter wmt_cec_exit\n");
platform_driver_unregister(&wmt_cec_driver);
platform_device_unregister(&wmt_cec_device);
dev_no = MKDEV(CEC_MAJOR, 0);
unregister_chrdev_region(dev_no, 1);
return;
}
module_init(wmt_cec_init);
module_exit(wmt_cec_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("WMT CEC driver");
MODULE_AUTHOR("WMT TECH");
MODULE_VERSION("1.0.0");