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: include/linux/spi/cc2520.h
|
||||||
F: Documentation/devicetree/bindings/net/ieee802154/cc2520.txt
|
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
|
CELL BROADBAND ENGINE ARCHITECTURE
|
||||||
M: Arnd Bergmann <arnd@arndb.de>
|
M: Arnd Bergmann <arnd@arndb.de>
|
||||||
L: linuxppc-dev@lists.ozlabs.org
|
L: linuxppc-dev@lists.ozlabs.org
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
#include <linux/of_irq.h>
|
#include <linux/of_irq.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
|
||||||
#include "sde_kms.h"
|
#include "sde_kms.h"
|
||||||
#include "sde_connector.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)
|
static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
struct sde_hdmi *sde_hdmi = 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)
|
if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported)
|
||||||
hdmi_hdcp_ctrl_irq(hdmi->hdcp_ctrl);
|
hdmi_hdcp_ctrl_irq(hdmi->hdcp_ctrl);
|
||||||
|
|
||||||
/* TODO audio.. */
|
/* Process CEC: */
|
||||||
|
_sde_hdmi_cec_irq(sde_hdmi);
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
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 sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
@ -2071,6 +2108,13 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
|
||||||
goto error;
|
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;
|
enc->bridge = hdmi->bridge;
|
||||||
priv->bridges[priv->num_bridges++] = hdmi->bridge;
|
priv->bridges[priv->num_bridges++] = hdmi->bridge;
|
||||||
|
|
||||||
|
@ -2096,6 +2140,9 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (display->irq_domain)
|
||||||
|
irq_domain_remove(display->irq_domain);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,7 @@ struct sde_hdmi_ctrl {
|
||||||
* @hpd_work: HPD work structure.
|
* @hpd_work: HPD work structure.
|
||||||
* @codec_ready: If audio codec is ready.
|
* @codec_ready: If audio codec is ready.
|
||||||
* @client_notify_pending: If there is client notification pending.
|
* @client_notify_pending: If there is client notification pending.
|
||||||
|
* @irq_domain: IRQ domain structure.
|
||||||
* @root: Debug fs root entry.
|
* @root: Debug fs root entry.
|
||||||
*/
|
*/
|
||||||
struct sde_hdmi {
|
struct sde_hdmi {
|
||||||
|
@ -114,6 +115,8 @@ struct sde_hdmi {
|
||||||
bool codec_ready;
|
bool codec_ready;
|
||||||
bool client_notify_pending;
|
bool client_notify_pending;
|
||||||
|
|
||||||
|
struct irq_domain *irq_domain;
|
||||||
|
|
||||||
/* DEBUG FS */
|
/* DEBUG FS */
|
||||||
struct dentry *root;
|
struct dentry *root;
|
||||||
};
|
};
|
||||||
|
|
|
@ -80,6 +80,25 @@ config MEDIA_RC_SUPPORT
|
||||||
|
|
||||||
Say Y when you have a TV or an IR device.
|
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
|
# Media controller
|
||||||
# Selectable only for webcam/grabbers, as other drivers don't use it
|
# Selectable only for webcam/grabbers, as other drivers don't use it
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
# Makefile for the kernel multimedia device drivers.
|
# 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
|
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_defs.h
|
||||||
header-y += cciss_ioctl.h
|
header-y += cciss_ioctl.h
|
||||||
header-y += cdrom.h
|
header-y += cdrom.h
|
||||||
|
header-y += cec.h
|
||||||
|
header-y += cec-funcs.h
|
||||||
header-y += cgroupstats.h
|
header-y += cgroupstats.h
|
||||||
header-y += chio.h
|
header-y += chio.h
|
||||||
header-y += cm4000_cs.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