Merge "drm/msm: add irq mapping for cec in hdmi driver"
This commit is contained in:
commit
a24cba4428
16 changed files with 6586 additions and 1 deletions
16
MAINTAINERS
16
MAINTAINERS
|
@ -2657,6 +2657,22 @@ F: drivers/net/ieee802154/cc2520.c
|
|||
F: include/linux/spi/cc2520.h
|
||||
F: Documentation/devicetree/bindings/net/ieee802154/cc2520.txt
|
||||
|
||||
CEC DRIVER
|
||||
M: Hans Verkuil <hans.verkuil@cisco.com>
|
||||
L: linux-media@vger.kernel.org
|
||||
T: git git://linuxtv.org/media_tree.git
|
||||
W: http://linuxtv.org
|
||||
S: Supported
|
||||
F: Documentation/cec.txt
|
||||
F: Documentation/DocBook/media/v4l/cec*
|
||||
F: drivers/staging/media/cec/
|
||||
F: drivers/media/cec-edid.c
|
||||
F: drivers/media/rc/keymaps/rc-cec.c
|
||||
F: include/media/cec.h
|
||||
F: include/media/cec-edid.h
|
||||
F: include/linux/cec.h
|
||||
F: include/linux/cec-funcs.h
|
||||
|
||||
CELL BROADBAND ENGINE ARCHITECTURE
|
||||
M: Arnd Bergmann <arnd@arndb.de>
|
||||
L: linuxppc-dev@lists.ozlabs.org
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/gpio.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/irqdomain.h>
|
||||
|
||||
#include "sde_kms.h"
|
||||
#include "sde_connector.h"
|
||||
|
@ -967,6 +968,18 @@ static void _sde_hdmi_connector_irq(struct sde_hdmi *sde_hdmi)
|
|||
}
|
||||
}
|
||||
|
||||
static void _sde_hdmi_cec_irq(struct sde_hdmi *sde_hdmi)
|
||||
{
|
||||
struct hdmi *hdmi = sde_hdmi->ctrl.ctrl;
|
||||
u32 cec_intr = hdmi_read(hdmi, REG_HDMI_CEC_INT);
|
||||
|
||||
/* Routing interrupt to external CEC drivers */
|
||||
if (cec_intr)
|
||||
generic_handle_irq(irq_find_mapping(
|
||||
sde_hdmi->irq_domain, 1));
|
||||
}
|
||||
|
||||
|
||||
static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct sde_hdmi *sde_hdmi = dev_id;
|
||||
|
@ -987,7 +1000,8 @@ static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
|
|||
if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported)
|
||||
hdmi_hdcp_ctrl_irq(hdmi->hdcp_ctrl);
|
||||
|
||||
/* TODO audio.. */
|
||||
/* Process CEC: */
|
||||
_sde_hdmi_cec_irq(sde_hdmi);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
@ -2017,6 +2031,29 @@ static struct platform_driver sde_hdmi_driver = {
|
|||
},
|
||||
};
|
||||
|
||||
static int sde_hdmi_irqdomain_map(struct irq_domain *domain,
|
||||
unsigned int irq, irq_hw_number_t hwirq)
|
||||
{
|
||||
struct sde_hdmi *display;
|
||||
int rc;
|
||||
|
||||
if (!domain || !domain->host_data) {
|
||||
pr_err("invalid parameters domain\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
display = domain->host_data;
|
||||
|
||||
irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_level_irq);
|
||||
rc = irq_set_chip_data(irq, display);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops sde_hdmi_irqdomain_ops = {
|
||||
.map = sde_hdmi_irqdomain_map,
|
||||
.xlate = irq_domain_xlate_onecell,
|
||||
};
|
||||
|
||||
int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
|
||||
{
|
||||
int rc = 0;
|
||||
|
@ -2071,6 +2108,13 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
|
|||
goto error;
|
||||
}
|
||||
|
||||
display->irq_domain = irq_domain_add_linear(pdev->dev.of_node, 8,
|
||||
&sde_hdmi_irqdomain_ops, display);
|
||||
if (!display->irq_domain) {
|
||||
SDE_ERROR("failed to create IRQ domain\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
enc->bridge = hdmi->bridge;
|
||||
priv->bridges[priv->num_bridges++] = hdmi->bridge;
|
||||
|
||||
|
@ -2096,6 +2140,9 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (display->irq_domain)
|
||||
irq_domain_remove(display->irq_domain);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ struct sde_hdmi_ctrl {
|
|||
* @hpd_work: HPD work structure.
|
||||
* @codec_ready: If audio codec is ready.
|
||||
* @client_notify_pending: If there is client notification pending.
|
||||
* @irq_domain: IRQ domain structure.
|
||||
* @root: Debug fs root entry.
|
||||
*/
|
||||
struct sde_hdmi {
|
||||
|
@ -114,6 +115,8 @@ struct sde_hdmi {
|
|||
bool codec_ready;
|
||||
bool client_notify_pending;
|
||||
|
||||
struct irq_domain *irq_domain;
|
||||
|
||||
/* DEBUG FS */
|
||||
struct dentry *root;
|
||||
};
|
||||
|
|
|
@ -80,6 +80,25 @@ config MEDIA_RC_SUPPORT
|
|||
|
||||
Say Y when you have a TV or an IR device.
|
||||
|
||||
config MEDIA_CEC_SUPPORT
|
||||
bool "HDMI CEC support"
|
||||
select MEDIA_CEC_EDID
|
||||
---help---
|
||||
Enable support for HDMI CEC (Consumer Electronics Control),
|
||||
which is an optional HDMI feature.
|
||||
|
||||
Say Y when you have an HDMI receiver, transmitter or a USB CEC
|
||||
adapter that supports HDMI CEC.
|
||||
|
||||
config MEDIA_CEC_DEBUG
|
||||
bool "HDMI CEC debugfs interface"
|
||||
depends on MEDIA_CEC_SUPPORT && DEBUG_FS
|
||||
---help---
|
||||
Turns on the DebugFS interface for CEC devices.
|
||||
|
||||
config MEDIA_CEC_EDID
|
||||
bool
|
||||
|
||||
#
|
||||
# Media controller
|
||||
# Selectable only for webcam/grabbers, as other drivers don't use it
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
# Makefile for the kernel multimedia device drivers.
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_MEDIA_CEC_EDID),y)
|
||||
obj-$(CONFIG_MEDIA_SUPPORT) += cec-edid.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_MEDIA_CEC_SUPPORT),y)
|
||||
obj-$(CONFIG_MEDIA_SUPPORT) += cec/
|
||||
endif
|
||||
|
||||
media-objs := media-device.o media-devnode.o media-entity.o
|
||||
|
||||
#
|
||||
|
|
171
drivers/media/cec-edid.c
Normal file
171
drivers/media/cec-edid.c
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* cec-edid - HDMI Consumer Electronics Control EDID & CEC helper functions
|
||||
*
|
||||
* 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 <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <media/cec-edid.h>
|
||||
|
||||
/*
|
||||
* This EDID is expected to be a CEA-861 compliant, which means that there are
|
||||
* at least two blocks and one or more of the extensions blocks are CEA-861
|
||||
* blocks.
|
||||
*
|
||||
* The returned location is guaranteed to be < size - 1.
|
||||
*/
|
||||
static unsigned int cec_get_edid_spa_location(const u8 *edid, unsigned int size)
|
||||
{
|
||||
unsigned int blocks = size / 128;
|
||||
unsigned int block;
|
||||
u8 d;
|
||||
|
||||
/* Sanity check: at least 2 blocks and a multiple of the block size */
|
||||
if (blocks < 2 || size % 128)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If there are fewer extension blocks than the size, then update
|
||||
* 'blocks'. It is allowed to have more extension blocks than the size,
|
||||
* since some hardware can only read e.g. 256 bytes of the EDID, even
|
||||
* though more blocks are present. The first CEA-861 extension block
|
||||
* should normally be in block 1 anyway.
|
||||
*/
|
||||
if (edid[0x7e] + 1 < blocks)
|
||||
blocks = edid[0x7e] + 1;
|
||||
|
||||
for (block = 1; block < blocks; block++) {
|
||||
unsigned int offset = block * 128;
|
||||
|
||||
/* Skip any non-CEA-861 extension blocks */
|
||||
if (edid[offset] != 0x02 || edid[offset + 1] != 0x03)
|
||||
continue;
|
||||
|
||||
/* search Vendor Specific Data Block (tag 3) */
|
||||
d = edid[offset + 2] & 0x7f;
|
||||
/* Check if there are Data Blocks */
|
||||
if (d <= 4)
|
||||
continue;
|
||||
if (d > 4) {
|
||||
unsigned int i = offset + 4;
|
||||
unsigned int end = offset + d;
|
||||
|
||||
/* Note: 'end' is always < 'size' */
|
||||
do {
|
||||
u8 tag = edid[i] >> 5;
|
||||
u8 len = edid[i] & 0x1f;
|
||||
|
||||
if (tag == 3 && len >= 5 && i + len <= end &&
|
||||
edid[i + 1] == 0x03 &&
|
||||
edid[i + 2] == 0x0c &&
|
||||
edid[i + 3] == 0x00)
|
||||
return i + 4;
|
||||
i += len + 1;
|
||||
} while (i < end);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u16 cec_get_edid_phys_addr(const u8 *edid, unsigned int size,
|
||||
unsigned int *offset)
|
||||
{
|
||||
unsigned int loc = cec_get_edid_spa_location(edid, size);
|
||||
|
||||
if (offset)
|
||||
*offset = loc;
|
||||
if (loc == 0)
|
||||
return CEC_PHYS_ADDR_INVALID;
|
||||
return (edid[loc] << 8) | edid[loc + 1];
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_get_edid_phys_addr);
|
||||
|
||||
void cec_set_edid_phys_addr(u8 *edid, unsigned int size, u16 phys_addr)
|
||||
{
|
||||
unsigned int loc = cec_get_edid_spa_location(edid, size);
|
||||
u8 sum = 0;
|
||||
unsigned int i;
|
||||
|
||||
if (loc == 0)
|
||||
return;
|
||||
edid[loc] = phys_addr >> 8;
|
||||
edid[loc + 1] = phys_addr & 0xff;
|
||||
loc &= ~0x7f;
|
||||
|
||||
/* update the checksum */
|
||||
for (i = loc; i < loc + 127; i++)
|
||||
sum += edid[i];
|
||||
edid[i] = 256 - sum;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_set_edid_phys_addr);
|
||||
|
||||
u16 cec_phys_addr_for_input(u16 phys_addr, u8 input)
|
||||
{
|
||||
/* Check if input is sane */
|
||||
if (WARN_ON(input == 0 || input > 0xf))
|
||||
return CEC_PHYS_ADDR_INVALID;
|
||||
|
||||
if (phys_addr == 0)
|
||||
return input << 12;
|
||||
|
||||
if ((phys_addr & 0x0fff) == 0)
|
||||
return phys_addr | (input << 8);
|
||||
|
||||
if ((phys_addr & 0x00ff) == 0)
|
||||
return phys_addr | (input << 4);
|
||||
|
||||
if ((phys_addr & 0x000f) == 0)
|
||||
return phys_addr | input;
|
||||
|
||||
/*
|
||||
* All nibbles are used so no valid physical addresses can be assigned
|
||||
* to the input.
|
||||
*/
|
||||
return CEC_PHYS_ADDR_INVALID;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_phys_addr_for_input);
|
||||
|
||||
int cec_phys_addr_validate(u16 phys_addr, u16 *parent, u16 *port)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (parent)
|
||||
*parent = phys_addr;
|
||||
if (port)
|
||||
*port = 0;
|
||||
if (phys_addr == CEC_PHYS_ADDR_INVALID)
|
||||
return 0;
|
||||
for (i = 0; i < 16; i += 4)
|
||||
if (phys_addr & (0xf << i))
|
||||
break;
|
||||
if (i == 16)
|
||||
return 0;
|
||||
if (parent)
|
||||
*parent = phys_addr & (0xfff0 << i);
|
||||
if (port)
|
||||
*port = (phys_addr >> i) & 0xf;
|
||||
for (i += 4; i < 16; i += 4)
|
||||
if ((phys_addr & (0xf << i)) == 0)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_phys_addr_validate);
|
||||
|
||||
MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
|
||||
MODULE_DESCRIPTION("CEC EDID helper functions");
|
||||
MODULE_LICENSE("GPL");
|
5
drivers/media/cec/Makefile
Normal file
5
drivers/media/cec/Makefile
Normal file
|
@ -0,0 +1,5 @@
|
|||
cec-objs := cec-core.o cec-adap.o cec-api.o
|
||||
|
||||
ifeq ($(CONFIG_MEDIA_CEC_SUPPORT),y)
|
||||
obj-$(CONFIG_MEDIA_SUPPORT) += cec.o
|
||||
endif
|
1880
drivers/media/cec/cec-adap.c
Normal file
1880
drivers/media/cec/cec-adap.c
Normal file
File diff suppressed because it is too large
Load diff
588
drivers/media/cec/cec-api.c
Normal file
588
drivers/media/cec/cec-api.c
Normal file
|
@ -0,0 +1,588 @@
|
|||
/*
|
||||
* cec-api.c - HDMI Consumer Electronics Control framework - API
|
||||
*
|
||||
* 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 <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "cec-priv.h"
|
||||
|
||||
static inline struct cec_devnode *cec_devnode_data(struct file *filp)
|
||||
{
|
||||
struct cec_fh *fh = filp->private_data;
|
||||
|
||||
return &fh->adap->devnode;
|
||||
}
|
||||
|
||||
/* CEC file operations */
|
||||
|
||||
static unsigned int cec_poll(struct file *filp,
|
||||
struct poll_table_struct *poll)
|
||||
{
|
||||
struct cec_devnode *devnode = cec_devnode_data(filp);
|
||||
struct cec_fh *fh = filp->private_data;
|
||||
struct cec_adapter *adap = fh->adap;
|
||||
unsigned int res = 0;
|
||||
|
||||
if (!devnode->registered)
|
||||
return POLLERR | POLLHUP;
|
||||
mutex_lock(&adap->lock);
|
||||
if (adap->is_configured &&
|
||||
adap->transmit_queue_sz < CEC_MAX_MSG_TX_QUEUE_SZ)
|
||||
res |= POLLOUT | POLLWRNORM;
|
||||
if (fh->queued_msgs)
|
||||
res |= POLLIN | POLLRDNORM;
|
||||
if (fh->pending_events)
|
||||
res |= POLLPRI;
|
||||
poll_wait(filp, &fh->wait, poll);
|
||||
mutex_unlock(&adap->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool cec_is_busy(const struct cec_adapter *adap,
|
||||
const struct cec_fh *fh)
|
||||
{
|
||||
bool valid_initiator = adap->cec_initiator && adap->cec_initiator == fh;
|
||||
bool valid_follower = adap->cec_follower && adap->cec_follower == fh;
|
||||
|
||||
/*
|
||||
* Exclusive initiators and followers can always access the CEC adapter
|
||||
*/
|
||||
if (valid_initiator || valid_follower)
|
||||
return false;
|
||||
/*
|
||||
* All others can only access the CEC adapter if there is no
|
||||
* exclusive initiator and they are in INITIATOR mode.
|
||||
*/
|
||||
return adap->cec_initiator ||
|
||||
fh->mode_initiator == CEC_MODE_NO_INITIATOR;
|
||||
}
|
||||
|
||||
static long cec_adap_g_caps(struct cec_adapter *adap,
|
||||
struct cec_caps __user *parg)
|
||||
{
|
||||
struct cec_caps caps = {};
|
||||
|
||||
strlcpy(caps.driver, adap->devnode.dev.parent->driver->name,
|
||||
sizeof(caps.driver));
|
||||
strlcpy(caps.name, adap->name, sizeof(caps.name));
|
||||
caps.available_log_addrs = adap->available_log_addrs;
|
||||
caps.capabilities = adap->capabilities;
|
||||
caps.version = LINUX_VERSION_CODE;
|
||||
if (copy_to_user(parg, &caps, sizeof(caps)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_adap_g_phys_addr(struct cec_adapter *adap,
|
||||
__u16 __user *parg)
|
||||
{
|
||||
u16 phys_addr;
|
||||
|
||||
mutex_lock(&adap->lock);
|
||||
phys_addr = adap->phys_addr;
|
||||
mutex_unlock(&adap->lock);
|
||||
if (copy_to_user(parg, &phys_addr, sizeof(phys_addr)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_adap_s_phys_addr(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
bool block, __u16 __user *parg)
|
||||
{
|
||||
u16 phys_addr;
|
||||
long err;
|
||||
|
||||
if (!(adap->capabilities & CEC_CAP_PHYS_ADDR))
|
||||
return -ENOTTY;
|
||||
if (copy_from_user(&phys_addr, parg, sizeof(phys_addr)))
|
||||
return -EFAULT;
|
||||
|
||||
err = cec_phys_addr_validate(phys_addr, NULL, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
mutex_lock(&adap->lock);
|
||||
if (cec_is_busy(adap, fh))
|
||||
err = -EBUSY;
|
||||
else
|
||||
__cec_s_phys_addr(adap, phys_addr, block);
|
||||
mutex_unlock(&adap->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static long cec_adap_g_log_addrs(struct cec_adapter *adap,
|
||||
struct cec_log_addrs __user *parg)
|
||||
{
|
||||
struct cec_log_addrs log_addrs;
|
||||
|
||||
mutex_lock(&adap->lock);
|
||||
log_addrs = adap->log_addrs;
|
||||
if (!adap->is_configured)
|
||||
memset(log_addrs.log_addr, CEC_LOG_ADDR_INVALID,
|
||||
sizeof(log_addrs.log_addr));
|
||||
mutex_unlock(&adap->lock);
|
||||
|
||||
if (copy_to_user(parg, &log_addrs, sizeof(log_addrs)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_adap_s_log_addrs(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
bool block, struct cec_log_addrs __user *parg)
|
||||
{
|
||||
struct cec_log_addrs log_addrs;
|
||||
long err = -EBUSY;
|
||||
|
||||
if (!(adap->capabilities & CEC_CAP_LOG_ADDRS))
|
||||
return -ENOTTY;
|
||||
if (copy_from_user(&log_addrs, parg, sizeof(log_addrs)))
|
||||
return -EFAULT;
|
||||
log_addrs.flags &= CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK |
|
||||
CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU |
|
||||
CEC_LOG_ADDRS_FL_CDC_ONLY;
|
||||
mutex_lock(&adap->lock);
|
||||
if (!adap->is_configuring &&
|
||||
(!log_addrs.num_log_addrs || !adap->is_configured) &&
|
||||
!cec_is_busy(adap, fh)) {
|
||||
err = __cec_s_log_addrs(adap, &log_addrs, block);
|
||||
if (!err)
|
||||
log_addrs = adap->log_addrs;
|
||||
}
|
||||
mutex_unlock(&adap->lock);
|
||||
if (err)
|
||||
return err;
|
||||
if (copy_to_user(parg, &log_addrs, sizeof(log_addrs)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_transmit(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
bool block, struct cec_msg __user *parg)
|
||||
{
|
||||
struct cec_msg msg = {};
|
||||
long err = 0;
|
||||
|
||||
if (!(adap->capabilities & CEC_CAP_TRANSMIT))
|
||||
return -ENOTTY;
|
||||
if (copy_from_user(&msg, parg, sizeof(msg)))
|
||||
return -EFAULT;
|
||||
|
||||
/* A CDC-Only device can only send CDC messages */
|
||||
if ((adap->log_addrs.flags & CEC_LOG_ADDRS_FL_CDC_ONLY) &&
|
||||
(msg.len == 1 || msg.msg[1] != CEC_MSG_CDC_MESSAGE))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&adap->lock);
|
||||
if (!adap->is_configured)
|
||||
err = -ENONET;
|
||||
else if (cec_is_busy(adap, fh))
|
||||
err = -EBUSY;
|
||||
else
|
||||
err = cec_transmit_msg_fh(adap, &msg, fh, block);
|
||||
mutex_unlock(&adap->lock);
|
||||
if (err)
|
||||
return err;
|
||||
if (copy_to_user(parg, &msg, sizeof(msg)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called by CEC_RECEIVE: wait for a message to arrive */
|
||||
static int cec_receive_msg(struct cec_fh *fh, struct cec_msg *msg, bool block)
|
||||
{
|
||||
u32 timeout = msg->timeout;
|
||||
int res;
|
||||
|
||||
do {
|
||||
mutex_lock(&fh->lock);
|
||||
/* Are there received messages queued up? */
|
||||
if (fh->queued_msgs) {
|
||||
/* Yes, return the first one */
|
||||
struct cec_msg_entry *entry =
|
||||
list_first_entry(&fh->msgs,
|
||||
struct cec_msg_entry, list);
|
||||
|
||||
list_del(&entry->list);
|
||||
*msg = entry->msg;
|
||||
kfree(entry);
|
||||
fh->queued_msgs--;
|
||||
mutex_unlock(&fh->lock);
|
||||
/* restore original timeout value */
|
||||
msg->timeout = timeout;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No, return EAGAIN in non-blocking mode or wait */
|
||||
mutex_unlock(&fh->lock);
|
||||
|
||||
/* Return when in non-blocking mode */
|
||||
if (!block)
|
||||
return -EAGAIN;
|
||||
|
||||
if (msg->timeout) {
|
||||
/* The user specified a timeout */
|
||||
res = wait_event_interruptible_timeout(fh->wait,
|
||||
fh->queued_msgs,
|
||||
msecs_to_jiffies(msg->timeout));
|
||||
if (res == 0)
|
||||
res = -ETIMEDOUT;
|
||||
else if (res > 0)
|
||||
res = 0;
|
||||
} else {
|
||||
/* Wait indefinitely */
|
||||
res = wait_event_interruptible(fh->wait,
|
||||
fh->queued_msgs);
|
||||
}
|
||||
/* Exit on error, otherwise loop to get the new message */
|
||||
} while (!res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static long cec_receive(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
bool block, struct cec_msg __user *parg)
|
||||
{
|
||||
struct cec_msg msg = {};
|
||||
long err = 0;
|
||||
|
||||
if (copy_from_user(&msg, parg, sizeof(msg)))
|
||||
return -EFAULT;
|
||||
mutex_lock(&adap->lock);
|
||||
if (!adap->is_configured && fh->mode_follower < CEC_MODE_MONITOR)
|
||||
err = -ENONET;
|
||||
mutex_unlock(&adap->lock);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = cec_receive_msg(fh, &msg, block);
|
||||
if (err)
|
||||
return err;
|
||||
msg.flags = 0;
|
||||
if (copy_to_user(parg, &msg, sizeof(msg)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_dqevent(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
bool block, struct cec_event __user *parg)
|
||||
{
|
||||
struct cec_event *ev = NULL;
|
||||
u64 ts = ~0ULL;
|
||||
unsigned int i;
|
||||
long err = 0;
|
||||
|
||||
mutex_lock(&fh->lock);
|
||||
while (!fh->pending_events && block) {
|
||||
mutex_unlock(&fh->lock);
|
||||
err = wait_event_interruptible(fh->wait, fh->pending_events);
|
||||
if (err)
|
||||
return err;
|
||||
mutex_lock(&fh->lock);
|
||||
}
|
||||
|
||||
/* Find the oldest event */
|
||||
for (i = 0; i < CEC_NUM_EVENTS; i++) {
|
||||
if (fh->pending_events & (1 << (i + 1)) &&
|
||||
fh->events[i].ts <= ts) {
|
||||
ev = &fh->events[i];
|
||||
ts = ev->ts;
|
||||
}
|
||||
}
|
||||
if (!ev) {
|
||||
err = -EAGAIN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (copy_to_user(parg, ev, sizeof(*ev))) {
|
||||
err = -EFAULT;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
fh->pending_events &= ~(1 << ev->event);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&fh->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static long cec_g_mode(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
u32 __user *parg)
|
||||
{
|
||||
u32 mode = fh->mode_initiator | fh->mode_follower;
|
||||
|
||||
if (copy_to_user(parg, &mode, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_s_mode(struct cec_adapter *adap, struct cec_fh *fh,
|
||||
u32 __user *parg)
|
||||
{
|
||||
u32 mode;
|
||||
u8 mode_initiator;
|
||||
u8 mode_follower;
|
||||
long err = 0;
|
||||
|
||||
if (copy_from_user(&mode, parg, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
if (mode & ~(CEC_MODE_INITIATOR_MSK | CEC_MODE_FOLLOWER_MSK))
|
||||
return -EINVAL;
|
||||
|
||||
mode_initiator = mode & CEC_MODE_INITIATOR_MSK;
|
||||
mode_follower = mode & CEC_MODE_FOLLOWER_MSK;
|
||||
|
||||
if (mode_initiator > CEC_MODE_EXCL_INITIATOR ||
|
||||
mode_follower > CEC_MODE_MONITOR_ALL)
|
||||
return -EINVAL;
|
||||
|
||||
if (mode_follower == CEC_MODE_MONITOR_ALL &&
|
||||
!(adap->capabilities & CEC_CAP_MONITOR_ALL))
|
||||
return -EINVAL;
|
||||
|
||||
/* Follower modes should always be able to send CEC messages */
|
||||
if ((mode_initiator == CEC_MODE_NO_INITIATOR ||
|
||||
!(adap->capabilities & CEC_CAP_TRANSMIT)) &&
|
||||
mode_follower >= CEC_MODE_FOLLOWER &&
|
||||
mode_follower <= CEC_MODE_EXCL_FOLLOWER_PASSTHRU)
|
||||
return -EINVAL;
|
||||
|
||||
/* Monitor modes require CEC_MODE_NO_INITIATOR */
|
||||
if (mode_initiator && mode_follower >= CEC_MODE_MONITOR)
|
||||
return -EINVAL;
|
||||
|
||||
/* Monitor modes require CAP_NET_ADMIN */
|
||||
if (mode_follower >= CEC_MODE_MONITOR && !capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
mutex_lock(&adap->lock);
|
||||
/*
|
||||
* You can't become exclusive follower if someone else already
|
||||
* has that job.
|
||||
*/
|
||||
if ((mode_follower == CEC_MODE_EXCL_FOLLOWER ||
|
||||
mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) &&
|
||||
adap->cec_follower && adap->cec_follower != fh)
|
||||
err = -EBUSY;
|
||||
/*
|
||||
* You can't become exclusive initiator if someone else already
|
||||
* has that job.
|
||||
*/
|
||||
if (mode_initiator == CEC_MODE_EXCL_INITIATOR &&
|
||||
adap->cec_initiator && adap->cec_initiator != fh)
|
||||
err = -EBUSY;
|
||||
|
||||
if (!err) {
|
||||
bool old_mon_all = fh->mode_follower == CEC_MODE_MONITOR_ALL;
|
||||
bool new_mon_all = mode_follower == CEC_MODE_MONITOR_ALL;
|
||||
|
||||
if (old_mon_all != new_mon_all) {
|
||||
if (new_mon_all)
|
||||
err = cec_monitor_all_cnt_inc(adap);
|
||||
else
|
||||
cec_monitor_all_cnt_dec(adap);
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
mutex_unlock(&adap->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (fh->mode_follower == CEC_MODE_FOLLOWER)
|
||||
adap->follower_cnt--;
|
||||
if (mode_follower == CEC_MODE_FOLLOWER)
|
||||
adap->follower_cnt++;
|
||||
if (mode_follower == CEC_MODE_EXCL_FOLLOWER ||
|
||||
mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) {
|
||||
adap->passthrough =
|
||||
mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU;
|
||||
adap->cec_follower = fh;
|
||||
} else if (adap->cec_follower == fh) {
|
||||
adap->passthrough = false;
|
||||
adap->cec_follower = NULL;
|
||||
}
|
||||
if (mode_initiator == CEC_MODE_EXCL_INITIATOR)
|
||||
adap->cec_initiator = fh;
|
||||
else if (adap->cec_initiator == fh)
|
||||
adap->cec_initiator = NULL;
|
||||
fh->mode_initiator = mode_initiator;
|
||||
fh->mode_follower = mode_follower;
|
||||
mutex_unlock(&adap->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct cec_devnode *devnode = cec_devnode_data(filp);
|
||||
struct cec_fh *fh = filp->private_data;
|
||||
struct cec_adapter *adap = fh->adap;
|
||||
bool block = !(filp->f_flags & O_NONBLOCK);
|
||||
void __user *parg = (void __user *)arg;
|
||||
|
||||
if (!devnode->registered)
|
||||
return -ENODEV;
|
||||
|
||||
switch (cmd) {
|
||||
case CEC_ADAP_G_CAPS:
|
||||
return cec_adap_g_caps(adap, parg);
|
||||
|
||||
case CEC_ADAP_G_PHYS_ADDR:
|
||||
return cec_adap_g_phys_addr(adap, parg);
|
||||
|
||||
case CEC_ADAP_S_PHYS_ADDR:
|
||||
return cec_adap_s_phys_addr(adap, fh, block, parg);
|
||||
|
||||
case CEC_ADAP_G_LOG_ADDRS:
|
||||
return cec_adap_g_log_addrs(adap, parg);
|
||||
|
||||
case CEC_ADAP_S_LOG_ADDRS:
|
||||
return cec_adap_s_log_addrs(adap, fh, block, parg);
|
||||
|
||||
case CEC_TRANSMIT:
|
||||
return cec_transmit(adap, fh, block, parg);
|
||||
|
||||
case CEC_RECEIVE:
|
||||
return cec_receive(adap, fh, block, parg);
|
||||
|
||||
case CEC_DQEVENT:
|
||||
return cec_dqevent(adap, fh, block, parg);
|
||||
|
||||
case CEC_G_MODE:
|
||||
return cec_g_mode(adap, fh, parg);
|
||||
|
||||
case CEC_S_MODE:
|
||||
return cec_s_mode(adap, fh, parg);
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static int cec_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct cec_devnode *devnode =
|
||||
container_of(inode->i_cdev, struct cec_devnode, cdev);
|
||||
struct cec_adapter *adap = to_cec_adapter(devnode);
|
||||
struct cec_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL);
|
||||
/*
|
||||
* Initial events that are automatically sent when the cec device is
|
||||
* opened.
|
||||
*/
|
||||
struct cec_event ev_state = {
|
||||
.event = CEC_EVENT_STATE_CHANGE,
|
||||
.flags = CEC_EVENT_FL_INITIAL_STATE,
|
||||
};
|
||||
int err;
|
||||
|
||||
if (!fh)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&fh->msgs);
|
||||
INIT_LIST_HEAD(&fh->xfer_list);
|
||||
mutex_init(&fh->lock);
|
||||
init_waitqueue_head(&fh->wait);
|
||||
|
||||
fh->mode_initiator = CEC_MODE_INITIATOR;
|
||||
fh->adap = adap;
|
||||
|
||||
err = cec_get_device(devnode);
|
||||
if (err) {
|
||||
kfree(fh);
|
||||
return err;
|
||||
}
|
||||
|
||||
filp->private_data = fh;
|
||||
|
||||
mutex_lock(&devnode->lock);
|
||||
/* Queue up initial state events */
|
||||
ev_state.state_change.phys_addr = adap->phys_addr;
|
||||
ev_state.state_change.log_addr_mask = adap->log_addrs.log_addr_mask;
|
||||
cec_queue_event_fh(fh, &ev_state, 0);
|
||||
|
||||
list_add(&fh->list, &devnode->fhs);
|
||||
mutex_unlock(&devnode->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Override for the release function */
|
||||
static int cec_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct cec_devnode *devnode = cec_devnode_data(filp);
|
||||
struct cec_adapter *adap = to_cec_adapter(devnode);
|
||||
struct cec_fh *fh = filp->private_data;
|
||||
|
||||
mutex_lock(&adap->lock);
|
||||
if (adap->cec_initiator == fh)
|
||||
adap->cec_initiator = NULL;
|
||||
if (adap->cec_follower == fh) {
|
||||
adap->cec_follower = NULL;
|
||||
adap->passthrough = false;
|
||||
}
|
||||
if (fh->mode_follower == CEC_MODE_FOLLOWER)
|
||||
adap->follower_cnt--;
|
||||
if (fh->mode_follower == CEC_MODE_MONITOR_ALL)
|
||||
cec_monitor_all_cnt_dec(adap);
|
||||
mutex_unlock(&adap->lock);
|
||||
|
||||
mutex_lock(&devnode->lock);
|
||||
list_del(&fh->list);
|
||||
mutex_unlock(&devnode->lock);
|
||||
|
||||
/* Unhook pending transmits from this filehandle. */
|
||||
mutex_lock(&adap->lock);
|
||||
while (!list_empty(&fh->xfer_list)) {
|
||||
struct cec_data *data =
|
||||
list_first_entry(&fh->xfer_list, struct cec_data, xfer_list);
|
||||
|
||||
data->blocking = false;
|
||||
data->fh = NULL;
|
||||
list_del(&data->xfer_list);
|
||||
}
|
||||
mutex_unlock(&adap->lock);
|
||||
while (!list_empty(&fh->msgs)) {
|
||||
struct cec_msg_entry *entry =
|
||||
list_first_entry(&fh->msgs, struct cec_msg_entry, list);
|
||||
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
kfree(fh);
|
||||
|
||||
cec_put_device(devnode);
|
||||
filp->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct file_operations cec_devnode_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = cec_open,
|
||||
.unlocked_ioctl = cec_ioctl,
|
||||
.release = cec_release,
|
||||
.poll = cec_poll,
|
||||
.llseek = no_llseek,
|
||||
};
|
413
drivers/media/cec/cec-core.c
Normal file
413
drivers/media/cec/cec-core.c
Normal file
|
@ -0,0 +1,413 @@
|
|||
/*
|
||||
* cec-core.c - HDMI Consumer Electronics Control framework - Core
|
||||
*
|
||||
* 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 <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "cec-priv.h"
|
||||
|
||||
#define CEC_NUM_DEVICES 256
|
||||
#define CEC_NAME "cec"
|
||||
|
||||
int cec_debug;
|
||||
module_param_named(debug, cec_debug, int, 0644);
|
||||
MODULE_PARM_DESC(debug, "debug level (0-2)");
|
||||
|
||||
static dev_t cec_dev_t;
|
||||
|
||||
/* Active devices */
|
||||
static DEFINE_MUTEX(cec_devnode_lock);
|
||||
static DECLARE_BITMAP(cec_devnode_nums, CEC_NUM_DEVICES);
|
||||
|
||||
static struct dentry *top_cec_dir;
|
||||
|
||||
/* dev to cec_devnode */
|
||||
#define to_cec_devnode(cd) container_of(cd, struct cec_devnode, dev)
|
||||
|
||||
int cec_get_device(struct cec_devnode *devnode)
|
||||
{
|
||||
/*
|
||||
* Check if the cec device is available. This needs to be done with
|
||||
* the devnode->lock held to prevent an open/unregister race:
|
||||
* without the lock, the device could be unregistered and freed between
|
||||
* the devnode->registered check and get_device() calls, leading to
|
||||
* a crash.
|
||||
*/
|
||||
mutex_lock(&devnode->lock);
|
||||
/*
|
||||
* return ENXIO if the cec device has been removed
|
||||
* already or if it is not registered anymore.
|
||||
*/
|
||||
if (!devnode->registered) {
|
||||
mutex_unlock(&devnode->lock);
|
||||
return -ENXIO;
|
||||
}
|
||||
/* and increase the device refcount */
|
||||
get_device(&devnode->dev);
|
||||
mutex_unlock(&devnode->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cec_put_device(struct cec_devnode *devnode)
|
||||
{
|
||||
put_device(&devnode->dev);
|
||||
}
|
||||
|
||||
/* Called when the last user of the cec device exits. */
|
||||
static void cec_devnode_release(struct device *cd)
|
||||
{
|
||||
struct cec_devnode *devnode = to_cec_devnode(cd);
|
||||
|
||||
mutex_lock(&cec_devnode_lock);
|
||||
/* Mark device node number as free */
|
||||
clear_bit(devnode->minor, cec_devnode_nums);
|
||||
mutex_unlock(&cec_devnode_lock);
|
||||
|
||||
cec_delete_adapter(to_cec_adapter(devnode));
|
||||
}
|
||||
|
||||
static struct bus_type cec_bus_type = {
|
||||
.name = CEC_NAME,
|
||||
};
|
||||
|
||||
/*
|
||||
* Register a cec device node
|
||||
*
|
||||
* The registration code assigns minor numbers and registers the new device node
|
||||
* with the kernel. An error is returned if no free minor number can be found,
|
||||
* or if the registration of the device node fails.
|
||||
*
|
||||
* Zero is returned on success.
|
||||
*
|
||||
* Note that if the cec_devnode_register call fails, the release() callback of
|
||||
* the cec_devnode structure is *not* called, so the caller is responsible for
|
||||
* freeing any data.
|
||||
*/
|
||||
static int __must_check cec_devnode_register(struct cec_devnode *devnode,
|
||||
struct module *owner)
|
||||
{
|
||||
int minor;
|
||||
int ret;
|
||||
|
||||
/* Initialization */
|
||||
INIT_LIST_HEAD(&devnode->fhs);
|
||||
mutex_init(&devnode->lock);
|
||||
|
||||
/* Part 1: Find a free minor number */
|
||||
mutex_lock(&cec_devnode_lock);
|
||||
minor = find_next_zero_bit(cec_devnode_nums, CEC_NUM_DEVICES, 0);
|
||||
if (minor == CEC_NUM_DEVICES) {
|
||||
mutex_unlock(&cec_devnode_lock);
|
||||
pr_err("could not get a free minor\n");
|
||||
return -ENFILE;
|
||||
}
|
||||
|
||||
set_bit(minor, cec_devnode_nums);
|
||||
mutex_unlock(&cec_devnode_lock);
|
||||
|
||||
devnode->minor = minor;
|
||||
devnode->dev.bus = &cec_bus_type;
|
||||
devnode->dev.devt = MKDEV(MAJOR(cec_dev_t), minor);
|
||||
devnode->dev.release = cec_devnode_release;
|
||||
dev_set_name(&devnode->dev, "cec%d", devnode->minor);
|
||||
device_initialize(&devnode->dev);
|
||||
|
||||
/* Part 2: Initialize and register the character device */
|
||||
cdev_init(&devnode->cdev, &cec_devnode_fops);
|
||||
devnode->cdev.kobj.parent = &devnode->dev.kobj;
|
||||
devnode->cdev.owner = owner;
|
||||
|
||||
ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: cdev_add failed\n", __func__);
|
||||
goto clr_bit;
|
||||
}
|
||||
|
||||
ret = device_add(&devnode->dev);
|
||||
if (ret)
|
||||
goto cdev_del;
|
||||
|
||||
devnode->registered = true;
|
||||
return 0;
|
||||
|
||||
cdev_del:
|
||||
cdev_del(&devnode->cdev);
|
||||
clr_bit:
|
||||
mutex_lock(&cec_devnode_lock);
|
||||
clear_bit(devnode->minor, cec_devnode_nums);
|
||||
mutex_unlock(&cec_devnode_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a cec device node
|
||||
*
|
||||
* This unregisters the passed device. Future open calls will be met with
|
||||
* errors.
|
||||
*
|
||||
* This function can safely be called if the device node has never been
|
||||
* registered or has already been unregistered.
|
||||
*/
|
||||
static void cec_devnode_unregister(struct cec_devnode *devnode)
|
||||
{
|
||||
struct cec_fh *fh;
|
||||
|
||||
mutex_lock(&devnode->lock);
|
||||
|
||||
/* Check if devnode was never registered or already unregistered */
|
||||
if (!devnode->registered || devnode->unregistered) {
|
||||
mutex_unlock(&devnode->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
list_for_each_entry(fh, &devnode->fhs, list)
|
||||
wake_up_interruptible(&fh->wait);
|
||||
|
||||
devnode->registered = false;
|
||||
devnode->unregistered = true;
|
||||
mutex_unlock(&devnode->lock);
|
||||
|
||||
device_del(&devnode->dev);
|
||||
cdev_del(&devnode->cdev);
|
||||
put_device(&devnode->dev);
|
||||
}
|
||||
|
||||
struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops,
|
||||
void *priv, const char *name, u32 caps,
|
||||
u8 available_las)
|
||||
{
|
||||
struct cec_adapter *adap;
|
||||
int res;
|
||||
|
||||
if (WARN_ON(!caps))
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (WARN_ON(!ops))
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (WARN_ON(!available_las || available_las > CEC_MAX_LOG_ADDRS))
|
||||
return ERR_PTR(-EINVAL);
|
||||
adap = kzalloc(sizeof(*adap), GFP_KERNEL);
|
||||
if (!adap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
strlcpy(adap->name, name, sizeof(adap->name));
|
||||
adap->phys_addr = CEC_PHYS_ADDR_INVALID;
|
||||
adap->log_addrs.cec_version = CEC_OP_CEC_VERSION_2_0;
|
||||
adap->log_addrs.vendor_id = CEC_VENDOR_ID_NONE;
|
||||
adap->capabilities = caps;
|
||||
adap->available_log_addrs = available_las;
|
||||
adap->sequence = 0;
|
||||
adap->ops = ops;
|
||||
adap->priv = priv;
|
||||
memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs));
|
||||
mutex_init(&adap->lock);
|
||||
INIT_LIST_HEAD(&adap->transmit_queue);
|
||||
INIT_LIST_HEAD(&adap->wait_queue);
|
||||
init_waitqueue_head(&adap->kthread_waitq);
|
||||
|
||||
adap->kthread = kthread_run(cec_thread_func, adap, "cec-%s", name);
|
||||
if (IS_ERR(adap->kthread)) {
|
||||
pr_err("cec-%s: kernel_thread() failed\n", name);
|
||||
res = PTR_ERR(adap->kthread);
|
||||
kfree(adap);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
|
||||
if (!(caps & CEC_CAP_RC))
|
||||
return adap;
|
||||
|
||||
#if IS_REACHABLE(CONFIG_RC_CORE)
|
||||
/* Prepare the RC input device */
|
||||
adap->rc = rc_allocate_device();
|
||||
if (!adap->rc) {
|
||||
pr_err("cec-%s: failed to allocate memory for rc_dev\n",
|
||||
name);
|
||||
kthread_stop(adap->kthread);
|
||||
kfree(adap);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
snprintf(adap->input_name, sizeof(adap->input_name),
|
||||
"RC for %s", name);
|
||||
snprintf(adap->input_phys, sizeof(adap->input_phys),
|
||||
"%s/input0", name);
|
||||
|
||||
adap->rc->input_name = adap->input_name;
|
||||
adap->rc->input_phys = adap->input_phys;
|
||||
adap->rc->input_id.bustype = BUS_CEC;
|
||||
adap->rc->input_id.vendor = 0;
|
||||
adap->rc->input_id.product = 0;
|
||||
adap->rc->input_id.version = 1;
|
||||
adap->rc->driver_type = RC_DRIVER_SCANCODE;
|
||||
adap->rc->driver_name = CEC_NAME;
|
||||
adap->rc->allowed_protocols = RC_BIT_CEC;
|
||||
adap->rc->priv = adap;
|
||||
adap->rc->map_name = RC_MAP_CEC;
|
||||
adap->rc->timeout = MS_TO_NS(100);
|
||||
#else
|
||||
adap->capabilities &= ~CEC_CAP_RC;
|
||||
#endif
|
||||
return adap;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_allocate_adapter);
|
||||
|
||||
int cec_register_adapter(struct cec_adapter *adap,
|
||||
struct device *parent)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (IS_ERR_OR_NULL(adap))
|
||||
return 0;
|
||||
|
||||
if (WARN_ON(!parent))
|
||||
return -EINVAL;
|
||||
|
||||
adap->owner = parent->driver->owner;
|
||||
adap->devnode.dev.parent = parent;
|
||||
|
||||
#if IS_REACHABLE(CONFIG_RC_CORE)
|
||||
adap->rc->dev.parent = parent;
|
||||
if (adap->capabilities & CEC_CAP_RC) {
|
||||
res = rc_register_device(adap->rc);
|
||||
|
||||
if (res) {
|
||||
pr_err("cec-%s: failed to prepare input device\n",
|
||||
adap->name);
|
||||
rc_free_device(adap->rc);
|
||||
adap->rc = NULL;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
res = cec_devnode_register(&adap->devnode, adap->owner);
|
||||
if (res) {
|
||||
#if IS_REACHABLE(CONFIG_RC_CORE)
|
||||
/* Note: rc_unregister also calls rc_free */
|
||||
rc_unregister_device(adap->rc);
|
||||
adap->rc = NULL;
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&adap->devnode.dev, adap);
|
||||
#ifdef CONFIG_MEDIA_CEC_DEBUG
|
||||
if (!top_cec_dir)
|
||||
return 0;
|
||||
|
||||
adap->cec_dir = debugfs_create_dir(dev_name(&adap->devnode.dev), top_cec_dir);
|
||||
if (IS_ERR_OR_NULL(adap->cec_dir)) {
|
||||
pr_warn("cec-%s: Failed to create debugfs dir\n", adap->name);
|
||||
return 0;
|
||||
}
|
||||
adap->status_file = debugfs_create_devm_seqfile(&adap->devnode.dev,
|
||||
"status", adap->cec_dir, cec_adap_status);
|
||||
if (IS_ERR_OR_NULL(adap->status_file)) {
|
||||
pr_warn("cec-%s: Failed to create status file\n", adap->name);
|
||||
debugfs_remove_recursive(adap->cec_dir);
|
||||
adap->cec_dir = NULL;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_register_adapter);
|
||||
|
||||
void cec_unregister_adapter(struct cec_adapter *adap)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(adap))
|
||||
return;
|
||||
|
||||
#if IS_REACHABLE(CONFIG_RC_CORE)
|
||||
/* Note: rc_unregister also calls rc_free */
|
||||
rc_unregister_device(adap->rc);
|
||||
adap->rc = NULL;
|
||||
#endif
|
||||
debugfs_remove_recursive(adap->cec_dir);
|
||||
cec_devnode_unregister(&adap->devnode);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_unregister_adapter);
|
||||
|
||||
void cec_delete_adapter(struct cec_adapter *adap)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(adap))
|
||||
return;
|
||||
mutex_lock(&adap->lock);
|
||||
__cec_s_phys_addr(adap, CEC_PHYS_ADDR_INVALID, false);
|
||||
mutex_unlock(&adap->lock);
|
||||
kthread_stop(adap->kthread);
|
||||
if (adap->kthread_config)
|
||||
kthread_stop(adap->kthread_config);
|
||||
#if IS_REACHABLE(CONFIG_RC_CORE)
|
||||
rc_free_device(adap->rc);
|
||||
#endif
|
||||
kfree(adap);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cec_delete_adapter);
|
||||
|
||||
/*
|
||||
* Initialise cec for linux
|
||||
*/
|
||||
static int __init cec_devnode_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_info("Linux cec interface: v0.10\n");
|
||||
ret = alloc_chrdev_region(&cec_dev_t, 0, CEC_NUM_DEVICES,
|
||||
CEC_NAME);
|
||||
if (ret < 0) {
|
||||
pr_warn("cec: unable to allocate major\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MEDIA_CEC_DEBUG
|
||||
top_cec_dir = debugfs_create_dir("cec", NULL);
|
||||
if (IS_ERR_OR_NULL(top_cec_dir)) {
|
||||
pr_warn("cec: Failed to create debugfs cec dir\n");
|
||||
top_cec_dir = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = bus_register(&cec_bus_type);
|
||||
if (ret < 0) {
|
||||
unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES);
|
||||
pr_warn("cec: bus_register failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit cec_devnode_exit(void)
|
||||
{
|
||||
debugfs_remove_recursive(top_cec_dir);
|
||||
bus_unregister(&cec_bus_type);
|
||||
unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES);
|
||||
}
|
||||
|
||||
subsys_initcall(cec_devnode_init);
|
||||
module_exit(cec_devnode_exit)
|
||||
|
||||
MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
|
||||
MODULE_DESCRIPTION("Device node registration for cec drivers");
|
||||
MODULE_LICENSE("GPL");
|
56
drivers/media/cec/cec-priv.h
Normal file
56
drivers/media/cec/cec-priv.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* cec-priv.h - HDMI Consumer Electronics Control internal header
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _CEC_PRIV_H
|
||||
#define _CEC_PRIV_H
|
||||
|
||||
#include <linux/cec-funcs.h>
|
||||
#include <media/cec.h>
|
||||
|
||||
#define dprintk(lvl, fmt, arg...) \
|
||||
do { \
|
||||
if (lvl <= cec_debug) \
|
||||
pr_info("cec-%s: " fmt, adap->name, ## arg); \
|
||||
} while (0)
|
||||
|
||||
/* devnode to cec_adapter */
|
||||
#define to_cec_adapter(node) container_of(node, struct cec_adapter, devnode)
|
||||
|
||||
/* cec-core.c */
|
||||
extern int cec_debug;
|
||||
int cec_get_device(struct cec_devnode *devnode);
|
||||
void cec_put_device(struct cec_devnode *devnode);
|
||||
|
||||
/* cec-adap.c */
|
||||
int cec_monitor_all_cnt_inc(struct cec_adapter *adap);
|
||||
void cec_monitor_all_cnt_dec(struct cec_adapter *adap);
|
||||
int cec_adap_status(struct seq_file *file, void *priv);
|
||||
int cec_thread_func(void *_adap);
|
||||
void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block);
|
||||
int __cec_s_log_addrs(struct cec_adapter *adap,
|
||||
struct cec_log_addrs *log_addrs, bool block);
|
||||
int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
|
||||
struct cec_fh *fh, bool block);
|
||||
void cec_queue_event_fh(struct cec_fh *fh,
|
||||
const struct cec_event *new_ev, u64 ts);
|
||||
|
||||
/* cec-api.c */
|
||||
extern const struct file_operations cec_devnode_fops;
|
||||
|
||||
#endif
|
104
include/media/cec-edid.h
Normal file
104
include/media/cec-edid.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* cec-edid - HDMI Consumer Electronics Control & EDID helpers
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MEDIA_CEC_EDID_H
|
||||
#define _MEDIA_CEC_EDID_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define CEC_PHYS_ADDR_INVALID 0xffff
|
||||
#define cec_phys_addr_exp(pa) \
|
||||
((pa) >> 12), ((pa) >> 8) & 0xf, ((pa) >> 4) & 0xf, (pa) & 0xf
|
||||
|
||||
/**
|
||||
* cec_get_edid_phys_addr() - find and return the physical address
|
||||
*
|
||||
* @edid: pointer to the EDID data
|
||||
* @size: size in bytes of the EDID data
|
||||
* @offset: If not %NULL then the location of the physical address
|
||||
* bytes in the EDID will be returned here. This is set to 0
|
||||
* if there is no physical address found.
|
||||
*
|
||||
* Return: the physical address or CEC_PHYS_ADDR_INVALID if there is none.
|
||||
*/
|
||||
u16 cec_get_edid_phys_addr(const u8 *edid, unsigned int size,
|
||||
unsigned int *offset);
|
||||
|
||||
/**
|
||||
* cec_set_edid_phys_addr() - find and set the physical address
|
||||
*
|
||||
* @edid: pointer to the EDID data
|
||||
* @size: size in bytes of the EDID data
|
||||
* @phys_addr: the new physical address
|
||||
*
|
||||
* This function finds the location of the physical address in the EDID
|
||||
* and fills in the given physical address and updates the checksum
|
||||
* at the end of the EDID block. It does nothing if the EDID doesn't
|
||||
* contain a physical address.
|
||||
*/
|
||||
void cec_set_edid_phys_addr(u8 *edid, unsigned int size, u16 phys_addr);
|
||||
|
||||
/**
|
||||
* cec_phys_addr_for_input() - calculate the PA for an input
|
||||
*
|
||||
* @phys_addr: the physical address of the parent
|
||||
* @input: the number of the input port, must be between 1 and 15
|
||||
*
|
||||
* This function calculates a new physical address based on the input
|
||||
* port number. For example:
|
||||
*
|
||||
* PA = 0.0.0.0 and input = 2 becomes 2.0.0.0
|
||||
*
|
||||
* PA = 3.0.0.0 and input = 1 becomes 3.1.0.0
|
||||
*
|
||||
* PA = 3.2.1.0 and input = 5 becomes 3.2.1.5
|
||||
*
|
||||
* PA = 3.2.1.3 and input = 5 becomes f.f.f.f since it maxed out the depth.
|
||||
*
|
||||
* Return: the new physical address or CEC_PHYS_ADDR_INVALID.
|
||||
*/
|
||||
u16 cec_phys_addr_for_input(u16 phys_addr, u8 input);
|
||||
|
||||
/**
|
||||
* cec_phys_addr_validate() - validate a physical address from an EDID
|
||||
*
|
||||
* @phys_addr: the physical address to validate
|
||||
* @parent: if not %NULL, then this is filled with the parents PA.
|
||||
* @port: if not %NULL, then this is filled with the input port.
|
||||
*
|
||||
* This validates a physical address as read from an EDID. If the
|
||||
* PA is invalid (such as 1.0.1.0 since '0' is only allowed at the end),
|
||||
* then it will return -EINVAL.
|
||||
*
|
||||
* The parent PA is passed into %parent and the input port is passed into
|
||||
* %port. For example:
|
||||
*
|
||||
* PA = 0.0.0.0: has parent 0.0.0.0 and input port 0.
|
||||
*
|
||||
* PA = 1.0.0.0: has parent 0.0.0.0 and input port 1.
|
||||
*
|
||||
* PA = 3.2.0.0: has parent 3.0.0.0 and input port 2.
|
||||
*
|
||||
* PA = f.f.f.f: has parent f.f.f.f and input port 0.
|
||||
*
|
||||
* Return: 0 if the PA is valid, -EINVAL if not.
|
||||
*/
|
||||
int cec_phys_addr_validate(u16 phys_addr, u16 *parent, u16 *port);
|
||||
|
||||
#endif /* _MEDIA_CEC_EDID_H */
|
239
include/media/cec.h
Normal file
239
include/media/cec.h
Normal file
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* cec - HDMI Consumer Electronics Control support header
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MEDIA_CEC_H
|
||||
#define _MEDIA_CEC_H
|
||||
|
||||
#include <linux/poll.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/cec-funcs.h>
|
||||
#include <media/rc-core.h>
|
||||
#include <media/cec-edid.h>
|
||||
|
||||
/**
|
||||
* struct cec_devnode - cec device node
|
||||
* @dev: cec device
|
||||
* @cdev: cec character device
|
||||
* @minor: device node minor number
|
||||
* @registered: the device was correctly registered
|
||||
* @unregistered: the device was unregistered
|
||||
* @fhs_lock: lock to control access to the filehandle list
|
||||
* @fhs: the list of open filehandles (cec_fh)
|
||||
*
|
||||
* This structure represents a cec-related device node.
|
||||
*
|
||||
* The @parent is a physical device. It must be set by core or device drivers
|
||||
* before registering the node.
|
||||
*/
|
||||
struct cec_devnode {
|
||||
/* sysfs */
|
||||
struct device dev;
|
||||
struct cdev cdev;
|
||||
|
||||
/* device info */
|
||||
int minor;
|
||||
bool registered;
|
||||
bool unregistered;
|
||||
struct list_head fhs;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
struct cec_adapter;
|
||||
struct cec_data;
|
||||
|
||||
struct cec_data {
|
||||
struct list_head list;
|
||||
struct list_head xfer_list;
|
||||
struct cec_adapter *adap;
|
||||
struct cec_msg msg;
|
||||
struct cec_fh *fh;
|
||||
struct delayed_work work;
|
||||
struct completion c;
|
||||
u8 attempts;
|
||||
bool new_initiator;
|
||||
bool blocking;
|
||||
bool completed;
|
||||
};
|
||||
|
||||
struct cec_msg_entry {
|
||||
struct list_head list;
|
||||
struct cec_msg msg;
|
||||
};
|
||||
|
||||
#define CEC_NUM_EVENTS CEC_EVENT_LOST_MSGS
|
||||
|
||||
struct cec_fh {
|
||||
struct list_head list;
|
||||
struct list_head xfer_list;
|
||||
struct cec_adapter *adap;
|
||||
u8 mode_initiator;
|
||||
u8 mode_follower;
|
||||
|
||||
/* Events */
|
||||
wait_queue_head_t wait;
|
||||
unsigned int pending_events;
|
||||
struct cec_event events[CEC_NUM_EVENTS];
|
||||
struct mutex lock;
|
||||
struct list_head msgs; /* queued messages */
|
||||
unsigned int queued_msgs;
|
||||
};
|
||||
|
||||
#define CEC_SIGNAL_FREE_TIME_RETRY 3
|
||||
#define CEC_SIGNAL_FREE_TIME_NEW_INITIATOR 5
|
||||
#define CEC_SIGNAL_FREE_TIME_NEXT_XFER 7
|
||||
|
||||
/* The nominal data bit period is 2.4 ms */
|
||||
#define CEC_FREE_TIME_TO_USEC(ft) ((ft) * 2400)
|
||||
|
||||
struct cec_adap_ops {
|
||||
/* Low-level callbacks */
|
||||
int (*adap_enable)(struct cec_adapter *adap, bool enable);
|
||||
int (*adap_monitor_all_enable)(struct cec_adapter *adap, bool enable);
|
||||
int (*adap_log_addr)(struct cec_adapter *adap, u8 logical_addr);
|
||||
int (*adap_transmit)(struct cec_adapter *adap, u8 attempts,
|
||||
u32 signal_free_time, struct cec_msg *msg);
|
||||
void (*adap_status)(struct cec_adapter *adap, struct seq_file *file);
|
||||
|
||||
/* High-level CEC message callback */
|
||||
int (*received)(struct cec_adapter *adap, struct cec_msg *msg);
|
||||
};
|
||||
|
||||
/*
|
||||
* The minimum message length you can receive (excepting poll messages) is 2.
|
||||
* With a transfer rate of at most 36 bytes per second this makes 18 messages
|
||||
* per second worst case.
|
||||
*
|
||||
* We queue at most 3 seconds worth of received messages. The CEC specification
|
||||
* requires that messages are replied to within a second, so 3 seconds should
|
||||
* give more than enough margin. Since most messages are actually more than 2
|
||||
* bytes, this is in practice a lot more than 3 seconds.
|
||||
*/
|
||||
#define CEC_MAX_MSG_RX_QUEUE_SZ (18 * 3)
|
||||
|
||||
/*
|
||||
* The transmit queue is limited to 1 second worth of messages (worst case).
|
||||
* Messages can be transmitted by userspace and kernel space. But for both it
|
||||
* makes no sense to have a lot of messages queued up. One second seems
|
||||
* reasonable.
|
||||
*/
|
||||
#define CEC_MAX_MSG_TX_QUEUE_SZ (18 * 1)
|
||||
|
||||
struct cec_adapter {
|
||||
struct module *owner;
|
||||
char name[32];
|
||||
struct cec_devnode devnode;
|
||||
struct mutex lock;
|
||||
struct rc_dev *rc;
|
||||
|
||||
struct list_head transmit_queue;
|
||||
unsigned int transmit_queue_sz;
|
||||
struct list_head wait_queue;
|
||||
struct cec_data *transmitting;
|
||||
|
||||
struct task_struct *kthread_config;
|
||||
struct completion config_completion;
|
||||
|
||||
struct task_struct *kthread;
|
||||
wait_queue_head_t kthread_waitq;
|
||||
wait_queue_head_t waitq;
|
||||
|
||||
const struct cec_adap_ops *ops;
|
||||
void *priv;
|
||||
u32 capabilities;
|
||||
u8 available_log_addrs;
|
||||
|
||||
u16 phys_addr;
|
||||
bool is_configuring;
|
||||
bool is_configured;
|
||||
u32 monitor_all_cnt;
|
||||
u32 follower_cnt;
|
||||
struct cec_fh *cec_follower;
|
||||
struct cec_fh *cec_initiator;
|
||||
bool passthrough;
|
||||
struct cec_log_addrs log_addrs;
|
||||
|
||||
struct dentry *cec_dir;
|
||||
struct dentry *status_file;
|
||||
|
||||
u16 phys_addrs[15];
|
||||
u32 sequence;
|
||||
|
||||
char input_name[32];
|
||||
char input_phys[32];
|
||||
char input_drv[32];
|
||||
};
|
||||
|
||||
static inline bool cec_has_log_addr(const struct cec_adapter *adap, u8 log_addr)
|
||||
{
|
||||
return adap->log_addrs.log_addr_mask & (1 << log_addr);
|
||||
}
|
||||
|
||||
static inline bool cec_is_sink(const struct cec_adapter *adap)
|
||||
{
|
||||
return adap->phys_addr == 0;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_MEDIA_CEC_SUPPORT)
|
||||
struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops,
|
||||
void *priv, const char *name, u32 caps, u8 available_las);
|
||||
int cec_register_adapter(struct cec_adapter *adap, struct device *parent);
|
||||
void cec_unregister_adapter(struct cec_adapter *adap);
|
||||
void cec_delete_adapter(struct cec_adapter *adap);
|
||||
|
||||
int cec_s_log_addrs(struct cec_adapter *adap, struct cec_log_addrs *log_addrs,
|
||||
bool block);
|
||||
void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr,
|
||||
bool block);
|
||||
int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg,
|
||||
bool block);
|
||||
|
||||
/* Called by the adapter */
|
||||
void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt,
|
||||
u8 nack_cnt, u8 low_drive_cnt, u8 error_cnt);
|
||||
void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg);
|
||||
|
||||
#else
|
||||
|
||||
static inline int cec_register_adapter(struct cec_adapter *adap,
|
||||
struct device *parent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void cec_unregister_adapter(struct cec_adapter *adap)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void cec_delete_adapter(struct cec_adapter *adap)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr,
|
||||
bool block)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* _MEDIA_CEC_H */
|
|
@ -86,6 +86,8 @@ header-y += capi.h
|
|||
header-y += cciss_defs.h
|
||||
header-y += cciss_ioctl.h
|
||||
header-y += cdrom.h
|
||||
header-y += cec.h
|
||||
header-y += cec-funcs.h
|
||||
header-y += cgroupstats.h
|
||||
header-y += chio.h
|
||||
header-y += cm4000_cs.h
|
||||
|
|
1969
include/uapi/linux/cec-funcs.h
Normal file
1969
include/uapi/linux/cec-funcs.h
Normal file
File diff suppressed because it is too large
Load diff
1065
include/uapi/linux/cec.h
Normal file
1065
include/uapi/linux/cec.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue