![Hans Verkuil](/assets/img/avatar_default.png)
The cec_allocate_adapter function doesn't need the parent device, only the cec_register_adapter function needs it. Drop the cec_devnode parent field, since devnode.dev.parent can be used instead. This change makes the framework consistent with other frameworks where the parent device is not used until the device is registered. Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
231 lines
6.4 KiB
C
231 lines
6.4 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"
|
|
|
|
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_done(cw->adap, CEC_TX_STATUS_LOW_DRIVE, 0, 0, 1, 0);
|
|
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_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);
|
|
cec_transmit_done(cw->adap, cw->tx_status, 0, valid_dest ? 0 : 1, 0, 0);
|
|
|
|
/* 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_done(cw->adap, CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0);
|
|
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)
|
|
{
|
|
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 = adap->priv;
|
|
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 = adap->priv;
|
|
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_TRANSMIT | CEC_CAP_LOG_ADDRS |
|
|
CEC_CAP_PASSTHROUGH | CEC_CAP_RC | CEC_CAP_MONITOR_ALL;
|
|
|
|
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);
|
|
}
|