mirror of
https://github.com/torvalds/linux.git
synced 2026-06-07 22:14:04 +02:00
media: rockchip: hdmirx: add cec support
Signed-off-by: Shunqing Chen <csq@rock-chips.com> Change-Id: Idcfb5be184e6b1aa4818421626b7c24f1409e18d
This commit is contained in:
parent
eaa4aee939
commit
9d47d3aef2
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
rockchip-hdmirx-objs := rk_hdmirx.o
|
||||
rockchip-hdmirx-objs := rk_hdmirx.o rk_hdmirx_cec.o
|
||||
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_HDMIRX) += rockchip-hdmirx.o
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
#include <linux/reset.h>
|
||||
#include <linux/v4l2-dv-timings.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <media/cec.h>
|
||||
#include <media/cec-notifier.h>
|
||||
#include <media/v4l2-common.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-device.h>
|
||||
|
|
@ -34,6 +36,7 @@
|
|||
#include <media/videobuf2-v4l2.h>
|
||||
#include <sound/hdmi-codec.h>
|
||||
#include "rk_hdmirx.h"
|
||||
#include "rk_hdmirx_cec.h"
|
||||
|
||||
static int debug;
|
||||
module_param(debug, int, 0644);
|
||||
|
|
@ -174,6 +177,8 @@ struct rk_hdmirx_dev {
|
|||
struct hdmirx_audiostate audio_state;
|
||||
hdmi_codec_plugged_cb plugged_cb;
|
||||
struct device *codec_dev;
|
||||
struct hdmirx_cec *cec;
|
||||
struct cec_notifier *cec_notifier;
|
||||
};
|
||||
|
||||
static bool tx_5v_power_present(struct rk_hdmirx_dev *hdmirx_dev);
|
||||
|
|
@ -2418,6 +2423,11 @@ static irqreturn_t hdmirx_5v_det_irq_handler(int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct hdmirx_cec_ops hdmirx_cec_ops = {
|
||||
.write = hdmirx_writel,
|
||||
.read = hdmirx_readl,
|
||||
};
|
||||
|
||||
static int hdmirx_parse_dt(struct rk_hdmirx_dev *hdmirx_dev)
|
||||
{
|
||||
struct device *dev = hdmirx_dev->dev;
|
||||
|
|
@ -2598,6 +2608,7 @@ static int hdmirx_probe(struct platform_device *pdev)
|
|||
struct v4l2_ctrl_handler *hdl;
|
||||
struct resource *res;
|
||||
int ret, irq;
|
||||
struct hdmirx_cec_data cec_data;
|
||||
|
||||
hdmirx_dev = devm_kzalloc(dev, sizeof(*hdmirx_dev), GFP_KERNEL);
|
||||
if (!hdmirx_dev)
|
||||
|
|
@ -2724,6 +2735,28 @@ static int hdmirx_probe(struct platform_device *pdev)
|
|||
|
||||
schedule_delayed_work(&hdmirx_dev->delayed_work_hotplug,
|
||||
msecs_to_jiffies(2000));
|
||||
|
||||
hdmirx_dev->cec_notifier = cec_notifier_conn_register(dev, NULL, NULL);
|
||||
if (!hdmirx_dev->cec_notifier) {
|
||||
ret = -ENOMEM;
|
||||
goto err_hdl;
|
||||
}
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "cec");
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "get hdmi cec irq failed!\n");
|
||||
cec_notifier_conn_unregister(hdmirx_dev->cec_notifier);
|
||||
ret = irq;
|
||||
goto err_hdl;
|
||||
}
|
||||
|
||||
cec_data.hdmirx = hdmirx_dev;
|
||||
cec_data.dev = hdmirx_dev->dev;
|
||||
cec_data.ops = &hdmirx_cec_ops;
|
||||
cec_data.irq = irq;
|
||||
cec_data.edid = edid_init_data;
|
||||
hdmirx_dev->cec = rk_hdmirx_cec_register(&cec_data);
|
||||
|
||||
dev_info(dev, "%s driver probe ok!\n", dev_name(dev));
|
||||
|
||||
return 0;
|
||||
|
|
@ -2757,6 +2790,11 @@ static int hdmirx_remove(struct platform_device *pdev)
|
|||
clk_bulk_disable_unprepare(hdmirx_dev->num_clks, hdmirx_dev->clks);
|
||||
reset_control_assert(hdmirx_dev->reset);
|
||||
|
||||
if (hdmirx_dev->cec)
|
||||
rk_hdmirx_cec_unregister(hdmirx_dev->cec);
|
||||
if (hdmirx_dev->cec_notifier)
|
||||
cec_notifier_conn_unregister(hdmirx_dev->cec_notifier);
|
||||
|
||||
video_unregister_device(&hdmirx_dev->stream.vdev);
|
||||
v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
|
||||
v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
|
||||
|
|
|
|||
|
|
@ -261,7 +261,19 @@
|
|||
#define VMON_STATUS7 0x1598
|
||||
#define VMON_ILACE_DETECT BIT(4)
|
||||
|
||||
#define CEC_TX_CONTROL 0x2000
|
||||
#define CEC_STATUS 0x2004
|
||||
#define CEC_CONFIG 0x2008
|
||||
#define RX_AUTO_DRIVE_ACKNOWLEDGE BIT(9)
|
||||
#define CEC_ADDR 0x200c
|
||||
#define CEC_TX_COUNT 0x2020
|
||||
#define CEC_TX_DATA3_0 0x2024
|
||||
#define CEC_RX_COUNT_STATUS 0x2040
|
||||
#define CEC_RX_DATA3_0 0x2044
|
||||
#define CEC_LOCK_CONTROL 0x2054
|
||||
#define CEC_RXQUAL_BITTIME_CONFIG 0x2060
|
||||
#define CEC_RX_BITTIME_CONFIG 0x2064
|
||||
#define CEC_TX_BITTIME_CONFIG 0x2068
|
||||
|
||||
#define DMA_CONFIG1 0x4400
|
||||
#define UV_WID_MASK GENMASK(31, 28)
|
||||
|
|
@ -324,6 +336,17 @@
|
|||
|
||||
#define MAINUNIT_INTVEC_INDEX 0x5000
|
||||
#define MAINUNIT_0_INT_STATUS 0x5010
|
||||
#define CECRX_NOTIFY_ERR BIT(12)
|
||||
#define CECRX_EOM BIT(11)
|
||||
#define CECTX_DRIVE_ERR BIT(10)
|
||||
#define CECRX_BUSY BIT(9)
|
||||
#define CECTX_BUSY BIT(8)
|
||||
#define CECTX_FRAME_DISCARDED BIT(5)
|
||||
#define CECTX_NRETRANSMIT_FAIL BIT(4)
|
||||
#define CECTX_LINE_ERR BIT(3)
|
||||
#define CECTX_ARBLOST BIT(2)
|
||||
#define CECTX_NACK BIT(1)
|
||||
#define CECTX_DONE BIT(0)
|
||||
#define MAINUNIT_0_INT_MASK_N 0x5014
|
||||
#define MAINUNIT_0_INT_CLEAR 0x5018
|
||||
#define MAINUNIT_0_INT_FORCE 0x501c
|
||||
|
|
|
|||
288
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_cec.c
Normal file
288
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_cec.c
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Shunqing Chen <csq@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <media/cec.h>
|
||||
#include <media/cec-notifier.h>
|
||||
|
||||
#include "rk_hdmirx.h"
|
||||
#include "rk_hdmirx_cec.h"
|
||||
|
||||
static void hdmirx_cec_write(struct hdmirx_cec *cec, int reg, u32 val)
|
||||
{
|
||||
cec->ops->write(cec->hdmirx, reg, val);
|
||||
}
|
||||
|
||||
static u32 hdmirx_cec_read(struct hdmirx_cec *cec, int reg)
|
||||
{
|
||||
return cec->ops->read(cec->hdmirx, reg);
|
||||
}
|
||||
|
||||
static void hdmirx_cec_update_bits(struct hdmirx_cec *cec, int reg, u32 mask,
|
||||
u32 data)
|
||||
{
|
||||
u32 val = hdmirx_cec_read(cec, reg) & ~mask;
|
||||
|
||||
val |= (data & mask);
|
||||
hdmirx_cec_write(cec, reg, val);
|
||||
}
|
||||
|
||||
static int hdmirx_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
|
||||
{
|
||||
struct hdmirx_cec *cec = cec_get_drvdata(adap);
|
||||
|
||||
if (logical_addr == CEC_LOG_ADDR_INVALID)
|
||||
cec->addresses = 0;
|
||||
else
|
||||
cec->addresses |= BIT(logical_addr) | BIT(15);
|
||||
|
||||
hdmirx_cec_write(cec, CEC_ADDR, cec->addresses);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hdmirx_cec_transmit(struct cec_adapter *adap, u8 attempts,
|
||||
u32 signal_free_time, struct cec_msg *msg)
|
||||
{
|
||||
struct hdmirx_cec *cec = cec_get_drvdata(adap);
|
||||
u32 data[4] = {0};
|
||||
int i, data_len, msg_len;
|
||||
|
||||
msg_len = msg->len;
|
||||
if (msg->len > 16)
|
||||
msg_len = 16;
|
||||
if (msg_len <= 0)
|
||||
return 0;
|
||||
|
||||
hdmirx_cec_write(cec, CEC_TX_COUNT, msg_len - 1);
|
||||
for (i = 0; i < msg_len; i++)
|
||||
data[i / 4] |= msg->msg[i] << (i % 4) * 8;
|
||||
|
||||
data_len = msg_len / 4 + 1;
|
||||
for (i = 0; i < data_len; i++)
|
||||
hdmirx_cec_write(cec, CEC_TX_DATA3_0 + i * 4, data[i]);
|
||||
|
||||
hdmirx_cec_write(cec, CEC_TX_CONTROL, 0x1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t hdmirx_cec_hardirq(int irq, void *data)
|
||||
{
|
||||
struct cec_adapter *adap = data;
|
||||
struct hdmirx_cec *cec = cec_get_drvdata(adap);
|
||||
u32 stat = hdmirx_cec_read(cec, CEC_INT_STATUS);
|
||||
irqreturn_t ret = IRQ_HANDLED;
|
||||
u32 val;
|
||||
|
||||
if (stat == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
hdmirx_cec_write(cec, CEC_INT_CLEAR, stat);
|
||||
|
||||
if (stat & CECTX_LINE_ERR) {
|
||||
cec->tx_status = CEC_TX_STATUS_ERROR;
|
||||
cec->tx_done = true;
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
} else if (stat & CECTX_DONE) {
|
||||
cec->tx_status = CEC_TX_STATUS_OK;
|
||||
cec->tx_done = true;
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
} else if (stat & CECTX_NACK) {
|
||||
cec->tx_status = CEC_TX_STATUS_NACK;
|
||||
cec->tx_done = true;
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
if (stat & CECRX_EOM) {
|
||||
unsigned int len, i;
|
||||
|
||||
val = hdmirx_cec_read(cec, CEC_RX_COUNT_STATUS);
|
||||
/* rxbuffer locked status */
|
||||
if ((val & 0x80))
|
||||
return ret;
|
||||
|
||||
len = (val & 0xf) + 1;
|
||||
if (len > sizeof(cec->rx_msg.msg))
|
||||
len = sizeof(cec->rx_msg.msg);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i % 4 == 0)
|
||||
val = hdmirx_cec_read(cec, CEC_RX_DATA3_0 + i / 4 * 4);
|
||||
cec->rx_msg.msg[i] = (val >> ((i % 4) * 8)) & 0xff;
|
||||
}
|
||||
|
||||
cec->rx_msg.len = len;
|
||||
smp_wmb(); /* receive RX msg */
|
||||
cec->rx_done = true;
|
||||
hdmirx_cec_write(cec, CEC_LOCK_CONTROL, 0x1);
|
||||
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t hdmirx_cec_thread(int irq, void *data)
|
||||
{
|
||||
struct cec_adapter *adap = data;
|
||||
struct hdmirx_cec *cec = cec_get_drvdata(adap);
|
||||
|
||||
if (cec->tx_done) {
|
||||
cec->tx_done = false;
|
||||
cec_transmit_attempt_done(adap, cec->tx_status);
|
||||
}
|
||||
if (cec->rx_done) {
|
||||
cec->rx_done = false;
|
||||
smp_rmb(); /* RX msg has been received */
|
||||
cec_received_msg(adap, &cec->rx_msg);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int hdmirx_cec_enable(struct cec_adapter *adap, bool enable)
|
||||
{
|
||||
struct hdmirx_cec *cec = cec_get_drvdata(adap);
|
||||
|
||||
if (!enable) {
|
||||
hdmirx_cec_write(cec, CEC_INT_MASK_N, 0);
|
||||
hdmirx_cec_write(cec, CEC_INT_CLEAR, 0);
|
||||
if (cec->ops->disable)
|
||||
cec->ops->disable(cec->hdmirx);
|
||||
} else {
|
||||
unsigned int irqs;
|
||||
|
||||
hdmirx_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID);
|
||||
if (cec->ops->enable)
|
||||
cec->ops->enable(cec->hdmirx);
|
||||
hdmirx_cec_update_bits(cec, GLOBAL_SWENABLE, CEC_ENABLE, CEC_ENABLE);
|
||||
|
||||
irqs = CECTX_LINE_ERR | CECTX_NACK | CECRX_EOM | CECTX_DONE;
|
||||
hdmirx_cec_write(cec, CEC_INT_MASK_N, irqs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct cec_adap_ops hdmirx_cec_ops = {
|
||||
.adap_enable = hdmirx_cec_enable,
|
||||
.adap_log_addr = hdmirx_cec_log_addr,
|
||||
.adap_transmit = hdmirx_cec_transmit,
|
||||
};
|
||||
|
||||
static void hdmirx_cec_del(void *data)
|
||||
{
|
||||
struct hdmirx_cec *cec = data;
|
||||
|
||||
cec_delete_adapter(cec->adap);
|
||||
}
|
||||
|
||||
struct hdmirx_cec *rk_hdmirx_cec_register(struct hdmirx_cec_data *data)
|
||||
{
|
||||
struct hdmirx_cec *cec;
|
||||
int ret;
|
||||
|
||||
unsigned int irqs;
|
||||
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Our device is just a convenience - we want to link to the real
|
||||
* hardware device here, so that userspace can see the association
|
||||
* between the HDMI hardware and its associated CEC chardev.
|
||||
*/
|
||||
cec = devm_kzalloc(data->dev, sizeof(*cec), GFP_KERNEL);
|
||||
if (!cec)
|
||||
return NULL;
|
||||
|
||||
cec->dev = data->dev;
|
||||
cec->irq = data->irq;
|
||||
cec->ops = data->ops;
|
||||
cec->hdmirx = data->hdmirx;
|
||||
cec->edid = (struct edid *)data->edid;
|
||||
|
||||
hdmirx_cec_update_bits(cec, GLOBAL_SWENABLE, CEC_ENABLE, CEC_ENABLE);
|
||||
hdmirx_cec_update_bits(cec, CEC_CONFIG, RX_AUTO_DRIVE_ACKNOWLEDGE,
|
||||
RX_AUTO_DRIVE_ACKNOWLEDGE);
|
||||
|
||||
hdmirx_cec_write(cec, CEC_TX_COUNT, 0);
|
||||
hdmirx_cec_write(cec, CEC_INT_MASK_N, 0);
|
||||
hdmirx_cec_write(cec, CEC_INT_CLEAR, ~0);
|
||||
|
||||
cec->adap = cec_allocate_adapter(&hdmirx_cec_ops, cec, "rk-hdmirx",
|
||||
CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
|
||||
CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
|
||||
CEC_MAX_LOG_ADDRS);
|
||||
if (IS_ERR(cec->adap)) {
|
||||
dev_err(cec->dev, "cec adap allocate failed!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* override the module pointer */
|
||||
cec->adap->owner = THIS_MODULE;
|
||||
|
||||
ret = devm_add_action(cec->dev, hdmirx_cec_del, cec);
|
||||
if (ret) {
|
||||
cec_delete_adapter(cec->adap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cec->notify = cec_notifier_cec_adap_register(cec->dev,
|
||||
NULL, cec->adap);
|
||||
if (!cec->notify) {
|
||||
dev_err(cec->dev, "cec notify register failed!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = cec_register_adapter(cec->adap, cec->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(cec->dev, "cec register adapter failed!\n");
|
||||
cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cec_s_phys_addr_from_edid(cec->adap, cec->edid);
|
||||
|
||||
ret = devm_request_threaded_irq(cec->dev, cec->irq,
|
||||
hdmirx_cec_hardirq,
|
||||
hdmirx_cec_thread, IRQF_ONESHOT,
|
||||
"rk_hdmirx_cec", cec->adap);
|
||||
if (ret) {
|
||||
dev_err(cec->dev, "cec irq request failed!\n");
|
||||
cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
|
||||
dev_err(cec->dev, "request cec irq thread failed!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
irqs = CECTX_LINE_ERR | CECTX_NACK | CECRX_EOM | CECTX_DONE;
|
||||
hdmirx_cec_write(cec, CEC_INT_MASK_N, irqs);
|
||||
|
||||
/*
|
||||
* CEC documentation says we must not call cec_delete_adapter
|
||||
* after a successful call to cec_register_adapter().
|
||||
*/
|
||||
devm_remove_action(cec->dev, hdmirx_cec_del, cec);
|
||||
|
||||
return cec;
|
||||
}
|
||||
|
||||
void rk_hdmirx_cec_unregister(struct hdmirx_cec *cec)
|
||||
{
|
||||
if (!cec)
|
||||
return;
|
||||
|
||||
cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
|
||||
cec_unregister_adapter(cec->adap);
|
||||
}
|
||||
46
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_cec.h
Normal file
46
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_cec.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Shunqing Chen <csq@rock-chips.com>
|
||||
*/
|
||||
|
||||
#ifndef __RK_HDMIRX_CEC_H__
|
||||
#define __RK_HDMIRX_CEC_H__
|
||||
|
||||
struct rk_hdmirx_dev;
|
||||
|
||||
struct hdmirx_cec_ops {
|
||||
void (*write)(struct rk_hdmirx_dev *hdmirx_dev, int reg, u32 val);
|
||||
u32 (*read)(struct rk_hdmirx_dev *hdmirx_dev, int reg);
|
||||
void (*enable)(struct rk_hdmirx_dev *hdmirx);
|
||||
void (*disable)(struct rk_hdmirx_dev *hdmirx);
|
||||
};
|
||||
|
||||
struct hdmirx_cec_data {
|
||||
struct rk_hdmirx_dev *hdmirx;
|
||||
const struct hdmirx_cec_ops *ops;
|
||||
struct device *dev;
|
||||
int irq;
|
||||
u8 *edid;
|
||||
};
|
||||
|
||||
struct hdmirx_cec {
|
||||
struct rk_hdmirx_dev *hdmirx;
|
||||
struct device *dev;
|
||||
const struct hdmirx_cec_ops *ops;
|
||||
u32 addresses;
|
||||
struct cec_adapter *adap;
|
||||
struct cec_msg rx_msg;
|
||||
unsigned int tx_status;
|
||||
bool tx_done;
|
||||
bool rx_done;
|
||||
struct cec_notifier *notify;
|
||||
int irq;
|
||||
struct edid *edid;
|
||||
};
|
||||
|
||||
struct hdmirx_cec *rk_hdmirx_cec_register(struct hdmirx_cec_data *data);
|
||||
void rk_hdmirx_cec_unregister(struct hdmirx_cec *cec);
|
||||
|
||||
#endif /* __DW_HDMI_RX_CEC_H__ */
|
||||
Loading…
Reference in New Issue
Block a user