drm: bridge: cadence: Add CEC driver for cdns mhdp hdmi
Add cec driver for cdns mhdp hdmi. Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
This commit is contained in:
parent
efb3962858
commit
18252d211f
|
@ -14,3 +14,6 @@ config DRM_CDNS_DP
|
|||
|
||||
config DRM_CDNS_AUDIO
|
||||
tristate "Cadence MHDP Audio driver"
|
||||
|
||||
config DRM_CDNS_HDMI_CEC
|
||||
tristate "Cadence MHDP HDMI CEC driver"
|
||||
|
|
|
@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp-common.o cdns-mhdp-hdmi.o
|
|||
obj-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o
|
||||
obj-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o
|
||||
obj-$(CONFIG_DRM_CDNS_AUDIO) += cdns-mhdp-audio.o
|
||||
obj-$(CONFIG_DRM_CDNS_HDMI_CEC) += cdns-mhdp-cec.o
|
||||
|
|
|
@ -515,6 +515,11 @@ __cdns_hdmi_probe(struct platform_device *pdev,
|
|||
/* register audio driver */
|
||||
cdns_mhdp_register_audio_driver(dev);
|
||||
|
||||
/* register cec driver */
|
||||
#ifdef CONFIG_DRM_CDNS_HDMI_CEC
|
||||
cdns_mhdp_register_cec_driver(dev);
|
||||
#endif
|
||||
|
||||
return hdmi;
|
||||
|
||||
err_out:
|
||||
|
@ -524,6 +529,10 @@ err_out:
|
|||
|
||||
static void __cdns_hdmi_remove(struct cdns_mhdp_device *mhdp)
|
||||
{
|
||||
/* unregister cec driver */
|
||||
#ifdef CONFIG_DRM_CDNS_HDMI_CEC
|
||||
cdns_mhdp_unregister_cec_driver(mhdp->dev);
|
||||
#endif
|
||||
cdns_mhdp_unregister_audio_driver(mhdp->dev);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Copyright 2019 NXP
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <drm/bridge/cdns-mhdp-common.h>
|
||||
|
||||
#define CEC_NAME "cdns-mhdp-cec"
|
||||
|
||||
#define REG_ADDR_OFF 4
|
||||
#define MAX_LA_IDX 4
|
||||
#define MAX_LA_VAL 15
|
||||
|
||||
/* regsiter define */
|
||||
#define TX_MSG_HEADER 0x33800
|
||||
#define TX_MSG_LENGTH 0x33840
|
||||
#define TX_MSG_CMD 0x33844
|
||||
#define RX_MSG_CMD 0x33850
|
||||
#define RX_CLEAR_BUF 0x33854
|
||||
#define LOGICAL_ADDRESS_LA0 0x33858
|
||||
|
||||
#define CLK_DIV_MSB 0x3386c
|
||||
#define CLK_DIV_LSB 0x33870
|
||||
#define RX_MSG_DATA1 0x33900
|
||||
#define RX_MSG_LENGTH 0x33940
|
||||
#define RX_MSG_STATUS 0x33944
|
||||
#define NUM_OF_MSG_RX_BUF 0x33948
|
||||
#define TX_MSG_STATUS 0x3394c
|
||||
#define DB_L_TIMER 0x33980
|
||||
|
||||
/**
|
||||
* CEC Transceiver operation.
|
||||
*/
|
||||
enum {
|
||||
CEC_TX_STOP,
|
||||
CEC_TX_TRANSMIT,
|
||||
CEC_TX_ABORT,
|
||||
CEC_TX_ABORT_AND_TRANSMIT
|
||||
};
|
||||
|
||||
/**
|
||||
* CEC Transceiver status.
|
||||
*/
|
||||
enum {
|
||||
CEC_STS_IDLE,
|
||||
CEC_STS_BUSY,
|
||||
CEC_STS_SUCCESS,
|
||||
CEC_STS_ERROR
|
||||
};
|
||||
|
||||
/**
|
||||
* CEC Receiver operation.
|
||||
*/
|
||||
enum {
|
||||
CEC_RX_STOP,
|
||||
CEC_RX_READ,
|
||||
CEC_RX_DISABLE,
|
||||
CEC_RX_ABORT_AND_CLR_FIFO
|
||||
};
|
||||
/**
|
||||
* Maximum number of Messages in the RX Buffers.
|
||||
*/
|
||||
#define CEC_MAX_RX_MSGS 2
|
||||
|
||||
static u32 mhdp_cec_read(struct cdns_mhdp_cec *cec, u32 offset)
|
||||
{
|
||||
struct cdns_mhdp_device *mhdp =
|
||||
container_of(cec, struct cdns_mhdp_device, hdmi.cec);
|
||||
return cdns_mhdp_bus_read(mhdp, offset);
|
||||
}
|
||||
|
||||
static void mhdp_cec_write(struct cdns_mhdp_cec *cec, u32 offset, u32 val)
|
||||
{
|
||||
struct cdns_mhdp_device *mhdp =
|
||||
container_of(cec, struct cdns_mhdp_device, hdmi.cec);
|
||||
cdns_mhdp_bus_write(val, mhdp, offset);
|
||||
}
|
||||
|
||||
static void mhdp_cec_clear_rx_buffer(struct cdns_mhdp_cec *cec)
|
||||
{
|
||||
mhdp_cec_write(cec, RX_CLEAR_BUF, 1);
|
||||
mhdp_cec_write(cec, RX_CLEAR_BUF, 0);
|
||||
}
|
||||
|
||||
static void mhdp_cec_set_divider(struct cdns_mhdp_cec *cec)
|
||||
{
|
||||
struct cdns_mhdp_device *mhdp =
|
||||
container_of(cec, struct cdns_mhdp_device, hdmi.cec);
|
||||
u32 clk_div;
|
||||
|
||||
/* Set clock divider */
|
||||
clk_div = cdns_mhdp_get_fw_clk(mhdp) * 10;
|
||||
|
||||
mhdp_cec_write(cec, CLK_DIV_MSB,
|
||||
(clk_div >> 8) & 0xFF);
|
||||
mhdp_cec_write(cec, CLK_DIV_LSB, clk_div & 0xFF);
|
||||
}
|
||||
|
||||
static u32 mhdp_cec_read_message(struct cdns_mhdp_cec *cec)
|
||||
{
|
||||
struct cec_msg *msg = &cec->msg;
|
||||
int len;
|
||||
int i;
|
||||
|
||||
mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_READ);
|
||||
|
||||
len = mhdp_cec_read(cec, RX_MSG_LENGTH);
|
||||
msg->len = len + 1;
|
||||
dev_dbg(cec->dev, "RX MSG len =%d\n", len);
|
||||
|
||||
/* Read RX MSG bytes */
|
||||
for (i = 0; i < msg->len; ++i) {
|
||||
msg->msg[i] = (u8) mhdp_cec_read(cec, RX_MSG_DATA1 + (i * REG_ADDR_OFF));
|
||||
dev_dbg(cec->dev, "RX MSG[%d]=0x%x\n", i, msg->msg[i]);
|
||||
}
|
||||
|
||||
mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_STOP);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static u32 mhdp_cec_write_message(struct cdns_mhdp_cec *cec, struct cec_msg *msg)
|
||||
{
|
||||
u8 i;
|
||||
|
||||
mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
|
||||
|
||||
if (msg->len > CEC_MAX_MSG_SIZE) {
|
||||
dev_err(cec->dev, "Invalid MSG size!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < msg->len; ++i)
|
||||
printk("msg[%d]=0x%x\n",i, msg->msg[i]);
|
||||
|
||||
/* Write Message to register */
|
||||
for (i = 0; i < msg->len; ++i) {
|
||||
mhdp_cec_write(cec, TX_MSG_HEADER + (i * REG_ADDR_OFF),
|
||||
msg->msg[i]);
|
||||
}
|
||||
/* Write Message Length (payload + opcode) */
|
||||
mhdp_cec_write(cec, TX_MSG_LENGTH, msg->len - 1);
|
||||
|
||||
mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_TRANSMIT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//static void cec_abort_tx_transfer(struct cdns_mhdp_cec *cec)
|
||||
//{
|
||||
// cec_write(cec, TX_MSG_CMD, CEC_TX_ABORT);
|
||||
// cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
|
||||
//}
|
||||
|
||||
static int mhdp_cec_set_logical_addr(struct cdns_mhdp_cec *cec, u32 la)
|
||||
{
|
||||
u8 i;
|
||||
u8 la_reg;
|
||||
|
||||
if (la >= MAX_LA_VAL) {
|
||||
dev_err(cec->dev, "Error logical Addr\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_LA_IDX; ++i) {
|
||||
la_reg =
|
||||
mhdp_cec_read(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF));
|
||||
|
||||
if (la_reg & 0x10)
|
||||
continue;
|
||||
|
||||
if ((la_reg & 0xF) == la) {
|
||||
dev_warn(cec->dev, "Warning. LA already in use.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
la = (la & 0xF) | (1 << 4);
|
||||
|
||||
mhdp_cec_write(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF), la);
|
||||
return true;
|
||||
}
|
||||
|
||||
dev_warn(cec->dev, "All LA in use\n");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int mhdp_cec_poll_worker(void *_cec)
|
||||
{
|
||||
struct cdns_mhdp_cec *cec = (struct cdns_mhdp_cec *)_cec;
|
||||
int num_rx_msgs, i;
|
||||
int sts;
|
||||
|
||||
set_freezable();
|
||||
|
||||
for (;;) {
|
||||
if (kthread_freezable_should_stop(NULL))
|
||||
break;
|
||||
|
||||
/* Check TX State */
|
||||
sts = mhdp_cec_read(cec, TX_MSG_STATUS);
|
||||
switch (sts) {
|
||||
case CEC_STS_SUCCESS:
|
||||
cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0,
|
||||
0);
|
||||
mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
|
||||
break;
|
||||
case CEC_STS_ERROR:
|
||||
mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
|
||||
cec_transmit_done(cec->adap,
|
||||
CEC_TX_STATUS_MAX_RETRIES |
|
||||
CEC_TX_STATUS_NACK, 0, 1, 0, 0);
|
||||
break;
|
||||
case CEC_STS_BUSY:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check RX State */
|
||||
sts = mhdp_cec_read(cec, RX_MSG_STATUS);
|
||||
num_rx_msgs = mhdp_cec_read(cec, NUM_OF_MSG_RX_BUF);
|
||||
switch (sts) {
|
||||
case CEC_STS_SUCCESS:
|
||||
if (num_rx_msgs == 0xf)
|
||||
num_rx_msgs = CEC_MAX_RX_MSGS;
|
||||
|
||||
if (num_rx_msgs > CEC_MAX_RX_MSGS) {
|
||||
dev_err(cec->dev, "Error rx msg num %d\n",
|
||||
num_rx_msgs);
|
||||
mhdp_cec_clear_rx_buffer(cec);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Rx FIFO Depth 2 RX MSG */
|
||||
for (i = 0; i < num_rx_msgs; i++) {
|
||||
mhdp_cec_read_message(cec);
|
||||
cec->msg.rx_status = CEC_RX_STATUS_OK;
|
||||
cec_received_msg(cec->adap, &cec->msg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!kthread_should_stop())
|
||||
schedule_timeout_idle(20);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mhdp_cec_adap_enable(struct cec_adapter *adap, bool enable)
|
||||
{
|
||||
struct cdns_mhdp_cec *cec = adap->priv;
|
||||
|
||||
if (enable) {
|
||||
mhdp_cec_write(cec, DB_L_TIMER, 0x10);
|
||||
mhdp_cec_set_divider(cec);
|
||||
} else
|
||||
mhdp_cec_set_divider(cec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mhdp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
|
||||
{
|
||||
struct cdns_mhdp_cec *cec = adap->priv;
|
||||
|
||||
return mhdp_cec_set_logical_addr(cec, addr);
|
||||
}
|
||||
|
||||
static int mhdp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
|
||||
u32 signal_free_time, struct cec_msg *msg)
|
||||
{
|
||||
struct cdns_mhdp_cec *cec = adap->priv;
|
||||
|
||||
mhdp_cec_write_message(cec, msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct cec_adap_ops cdns_mhdp_cec_adap_ops = {
|
||||
.adap_enable = mhdp_cec_adap_enable,
|
||||
.adap_log_addr = mhdp_cec_adap_log_addr,
|
||||
.adap_transmit = mhdp_cec_adap_transmit,
|
||||
};
|
||||
|
||||
int cdns_mhdp_register_cec_driver(struct device *dev)
|
||||
{
|
||||
struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
|
||||
struct cdns_mhdp_cec *cec = &mhdp->hdmi.cec;
|
||||
int ret;
|
||||
|
||||
cec->adap = cec_allocate_adapter(&cdns_mhdp_cec_adap_ops, cec,
|
||||
CEC_NAME,
|
||||
CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS |
|
||||
CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH
|
||||
| CEC_CAP_RC, 1);
|
||||
ret = PTR_ERR_OR_ZERO(cec->adap);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = cec_register_adapter(cec->adap, dev);
|
||||
if (ret) {
|
||||
cec_delete_adapter(cec->adap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cec->dev = dev;
|
||||
|
||||
cec->cec_worker = kthread_create(mhdp_cec_poll_worker, cec, "cdns-mhdp-cec");
|
||||
if (IS_ERR(cec->cec_worker))
|
||||
dev_err(cec->dev, "failed create hdp cec thread\n");
|
||||
|
||||
wake_up_process(cec->cec_worker);
|
||||
|
||||
dev_dbg(dev, "CEC successfuly probed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cdns_mhdp_unregister_cec_driver(struct device *dev)
|
||||
{
|
||||
struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
|
||||
struct cdns_mhdp_cec *cec = &mhdp->hdmi.cec;
|
||||
|
||||
if (cec->cec_worker) {
|
||||
kthread_stop(cec->cec_worker);
|
||||
cec->cec_worker = NULL;
|
||||
}
|
||||
cec_unregister_adapter(cec->adap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Sandor.Yu@NXP.com");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("NXP CDNS MHDP CEC driver");
|
|
@ -21,7 +21,7 @@
|
|||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_dp_mst_helper.h>
|
||||
|
||||
#include <media/cec.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#define ADDR_IMEM 0x10000
|
||||
|
@ -605,6 +605,17 @@ struct cdns_mhdp_connector {
|
|||
struct cdns_mhdp_bridge *bridge;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DRM_CDNS_HDMI_CEC
|
||||
struct cdns_mhdp_cec {
|
||||
struct cec_adapter *adap;
|
||||
struct device *dev;
|
||||
struct mutex lock;
|
||||
|
||||
struct cec_msg msg;
|
||||
struct task_struct *cec_worker;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct cdns_mhdp_device {
|
||||
void __iomem *regs;
|
||||
|
||||
|
@ -633,7 +644,7 @@ struct cdns_mhdp_device {
|
|||
bool plugged;
|
||||
|
||||
union {
|
||||
struct cdn_dp_data {
|
||||
struct _dp_data {
|
||||
struct drm_dp_link link;
|
||||
struct drm_dp_aux aux;
|
||||
struct cdns_mhdp_host host;
|
||||
|
@ -645,6 +656,9 @@ struct cdns_mhdp_device {
|
|||
u32 num_lanes;
|
||||
} dp;
|
||||
struct _hdmi_data {
|
||||
#ifdef CONFIG_DRM_CDNS_HDMI_CEC
|
||||
struct cdns_mhdp_cec cec;
|
||||
#endif
|
||||
u32 char_rate;
|
||||
u32 hdmi_type;
|
||||
} hdmi;
|
||||
|
@ -713,4 +727,10 @@ int cdns_hdmi_disable_gcp(struct cdns_mhdp_device *mhdp);
|
|||
int cdns_hdmi_enable_gcp(struct cdns_mhdp_device *mhdp);
|
||||
|
||||
bool cdns_mhdp_check_alive(struct cdns_mhdp_device *mhdp);
|
||||
/* CEC */
|
||||
#ifdef CONFIG_DRM_CDNS_HDMI_CEC
|
||||
int cdns_mhdp_register_cec_driver(struct device *dev);
|
||||
int cdns_mhdp_unregister_cec_driver(struct device *dev);
|
||||
#endif
|
||||
|
||||
#endif /* CDNS_MHDP_COMMON_H_ */
|
||||
|
|
Loading…
Reference in New Issue