linux_dsm_epyc7002/drivers/media/platform/vivid/vivid-cec.c
Hans Verkuil 4f88b0de78 media: vivid: add CEC pin monitoring emulation
Add support to emulate CEC pin monitoring. There are few hardware devices
that support this, so being able to emulate it here helps developing
software for this.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-08-26 08:35:49 -04:00

293 lines
8.2 KiB
C

/*
* vivid-cec.c - A Virtual Video Test Driver, cec emulation
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <media/cec.h>
#include "vivid-core.h"
#include "vivid-cec.h"
#define CEC_TIM_START_BIT_TOTAL 4500
#define CEC_TIM_START_BIT_LOW 3700
#define CEC_TIM_START_BIT_HIGH 800
#define CEC_TIM_DATA_BIT_TOTAL 2400
#define CEC_TIM_DATA_BIT_0_LOW 1500
#define CEC_TIM_DATA_BIT_0_HIGH 900
#define CEC_TIM_DATA_BIT_1_LOW 600
#define CEC_TIM_DATA_BIT_1_HIGH 1800
void vivid_cec_bus_free_work(struct vivid_dev *dev)
{
spin_lock(&dev->cec_slock);
while (!list_empty(&dev->cec_work_list)) {
struct vivid_cec_work *cw =
list_first_entry(&dev->cec_work_list,
struct vivid_cec_work, list);
spin_unlock(&dev->cec_slock);
cancel_delayed_work_sync(&cw->work);
spin_lock(&dev->cec_slock);
list_del(&cw->list);
cec_transmit_attempt_done(cw->adap, CEC_TX_STATUS_LOW_DRIVE);
kfree(cw);
}
spin_unlock(&dev->cec_slock);
}
static bool vivid_cec_find_dest_adap(struct vivid_dev *dev,
struct cec_adapter *adap, u8 dest)
{
unsigned int i;
if (dest >= 0xf)
return false;
if (adap != dev->cec_rx_adap && dev->cec_rx_adap &&
dev->cec_rx_adap->is_configured &&
cec_has_log_addr(dev->cec_rx_adap, dest))
return true;
for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) {
if (adap == dev->cec_tx_adap[i])
continue;
if (!dev->cec_tx_adap[i]->is_configured)
continue;
if (cec_has_log_addr(dev->cec_tx_adap[i], dest))
return true;
}
return false;
}
static void vivid_cec_pin_adap_events(struct cec_adapter *adap, ktime_t ts,
const struct cec_msg *msg, bool nacked)
{
unsigned int len = nacked ? 1 : msg->len;
unsigned int i;
bool bit;
if (adap == NULL)
return;
ts = ktime_sub_us(ts, (CEC_TIM_START_BIT_TOTAL +
len * 10 * CEC_TIM_DATA_BIT_TOTAL));
cec_queue_pin_cec_event(adap, false, ts);
ts = ktime_add_us(ts, CEC_TIM_START_BIT_LOW);
cec_queue_pin_cec_event(adap, true, ts);
ts = ktime_add_us(ts, CEC_TIM_START_BIT_HIGH);
for (i = 0; i < 10 * len; i++) {
switch (i % 10) {
case 0 ... 7:
bit = msg->msg[i / 10] & (0x80 >> (i % 10));
break;
case 8: /* EOM */
bit = i / 10 == msg->len - 1;
break;
case 9: /* ACK */
bit = cec_msg_is_broadcast(msg) ^ nacked;
break;
}
cec_queue_pin_cec_event(adap, false, ts);
if (bit)
ts = ktime_add_us(ts, CEC_TIM_DATA_BIT_1_LOW);
else
ts = ktime_add_us(ts, CEC_TIM_DATA_BIT_0_LOW);
cec_queue_pin_cec_event(adap, true, ts);
if (bit)
ts = ktime_add_us(ts, CEC_TIM_DATA_BIT_1_HIGH);
else
ts = ktime_add_us(ts, CEC_TIM_DATA_BIT_0_HIGH);
}
}
static void vivid_cec_pin_events(struct vivid_dev *dev,
const struct cec_msg *msg, bool nacked)
{
ktime_t ts = ktime_get();
unsigned int i;
vivid_cec_pin_adap_events(dev->cec_rx_adap, ts, msg, nacked);
for (i = 0; i < MAX_OUTPUTS; i++)
vivid_cec_pin_adap_events(dev->cec_tx_adap[i], ts, msg, nacked);
}
static void vivid_cec_xfer_done_worker(struct work_struct *work)
{
struct vivid_cec_work *cw =
container_of(work, struct vivid_cec_work, work.work);
struct vivid_dev *dev = cw->dev;
struct cec_adapter *adap = cw->adap;
u8 dest = cec_msg_destination(&cw->msg);
bool valid_dest;
unsigned int i;
valid_dest = cec_msg_is_broadcast(&cw->msg);
if (!valid_dest)
valid_dest = vivid_cec_find_dest_adap(dev, adap, dest);
cw->tx_status = valid_dest ? CEC_TX_STATUS_OK : CEC_TX_STATUS_NACK;
spin_lock(&dev->cec_slock);
dev->cec_xfer_time_jiffies = 0;
dev->cec_xfer_start_jiffies = 0;
list_del(&cw->list);
spin_unlock(&dev->cec_slock);
vivid_cec_pin_events(dev, &cw->msg, !valid_dest);
cec_transmit_attempt_done(cw->adap, cw->tx_status);
/* Broadcast message */
if (adap != dev->cec_rx_adap)
cec_received_msg(dev->cec_rx_adap, &cw->msg);
for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++)
if (adap != dev->cec_tx_adap[i])
cec_received_msg(dev->cec_tx_adap[i], &cw->msg);
kfree(cw);
}
static void vivid_cec_xfer_try_worker(struct work_struct *work)
{
struct vivid_cec_work *cw =
container_of(work, struct vivid_cec_work, work.work);
struct vivid_dev *dev = cw->dev;
spin_lock(&dev->cec_slock);
if (dev->cec_xfer_time_jiffies) {
list_del(&cw->list);
spin_unlock(&dev->cec_slock);
cec_transmit_attempt_done(cw->adap, CEC_TX_STATUS_ARB_LOST);
kfree(cw);
} else {
INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_done_worker);
dev->cec_xfer_start_jiffies = jiffies;
dev->cec_xfer_time_jiffies = usecs_to_jiffies(cw->usecs);
spin_unlock(&dev->cec_slock);
schedule_delayed_work(&cw->work, dev->cec_xfer_time_jiffies);
}
}
static int vivid_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
adap->cec_pin_is_high = true;
return 0;
}
static int vivid_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
{
return 0;
}
/*
* One data bit takes 2400 us, each byte needs 10 bits so that's 24000 us
* per byte.
*/
#define USECS_PER_BYTE 24000
static int vivid_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct vivid_dev *dev = cec_get_drvdata(adap);
struct vivid_cec_work *cw = kzalloc(sizeof(*cw), GFP_KERNEL);
long delta_jiffies = 0;
if (cw == NULL)
return -ENOMEM;
cw->dev = dev;
cw->adap = adap;
cw->usecs = CEC_FREE_TIME_TO_USEC(signal_free_time) +
msg->len * USECS_PER_BYTE;
cw->msg = *msg;
spin_lock(&dev->cec_slock);
list_add(&cw->list, &dev->cec_work_list);
if (dev->cec_xfer_time_jiffies == 0) {
INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_done_worker);
dev->cec_xfer_start_jiffies = jiffies;
dev->cec_xfer_time_jiffies = usecs_to_jiffies(cw->usecs);
delta_jiffies = dev->cec_xfer_time_jiffies;
} else {
INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_try_worker);
delta_jiffies = dev->cec_xfer_start_jiffies +
dev->cec_xfer_time_jiffies - jiffies;
}
spin_unlock(&dev->cec_slock);
schedule_delayed_work(&cw->work, delta_jiffies < 0 ? 0 : delta_jiffies);
return 0;
}
static int vivid_received(struct cec_adapter *adap, struct cec_msg *msg)
{
struct vivid_dev *dev = cec_get_drvdata(adap);
struct cec_msg reply;
u8 dest = cec_msg_destination(msg);
u8 disp_ctl;
char osd[14];
if (cec_msg_is_broadcast(msg))
dest = adap->log_addrs.log_addr[0];
cec_msg_init(&reply, dest, cec_msg_initiator(msg));
switch (cec_msg_opcode(msg)) {
case CEC_MSG_SET_OSD_STRING:
if (!cec_is_sink(adap))
return -ENOMSG;
cec_ops_set_osd_string(msg, &disp_ctl, osd);
switch (disp_ctl) {
case CEC_OP_DISP_CTL_DEFAULT:
strcpy(dev->osd, osd);
dev->osd_jiffies = jiffies;
break;
case CEC_OP_DISP_CTL_UNTIL_CLEARED:
strcpy(dev->osd, osd);
dev->osd_jiffies = 0;
break;
case CEC_OP_DISP_CTL_CLEAR:
dev->osd[0] = 0;
dev->osd_jiffies = 0;
break;
default:
cec_msg_feature_abort(&reply, cec_msg_opcode(msg),
CEC_OP_ABORT_INVALID_OP);
cec_transmit_msg(adap, &reply, false);
break;
}
break;
default:
return -ENOMSG;
}
return 0;
}
static const struct cec_adap_ops vivid_cec_adap_ops = {
.adap_enable = vivid_cec_adap_enable,
.adap_log_addr = vivid_cec_adap_log_addr,
.adap_transmit = vivid_cec_adap_transmit,
.received = vivid_received,
};
struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev,
unsigned int idx,
bool is_source)
{
char name[sizeof(dev->vid_out_dev.name) + 2];
u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN;
snprintf(name, sizeof(name), "%s%d",
is_source ? dev->vid_out_dev.name : dev->vid_cap_dev.name,
idx);
return cec_allocate_adapter(&vivid_cec_adap_ops, dev,
name, caps, 1);
}