Hi Hans, Thanks for your comments and feedback! From: Hans Verkuil <hverkuil-cisco@xxxxxxxxx> Date: qua, jun 02, 2021 at 13:45:45 > On 02/06/2021 13:24, Nelson Costa wrote: > > This adds support for the optional HDMI CEC feature > > for the Synopsys DesignWare HDMI RX Controller. > > > > It uses the generic CEC framework interface. > > > > Signed-off-by: Jose Abreu <jose.abreu@xxxxxxxxxxxx> > > Signed-off-by: Nelson Costa <nelson.costa@xxxxxxxxxxxx> > > --- > > drivers/media/platform/dwc/Kconfig | 10 ++ > > drivers/media/platform/dwc/dw-hdmi-rx.c | 259 +++++++++++++++++++++++++++++++- > > drivers/media/platform/dwc/dw-hdmi-rx.h | 57 +++++++ > > 3 files changed, 324 insertions(+), 2 deletions(-) > > > > diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig > > index e915ca6..b487a92 100644 > > --- a/drivers/media/platform/dwc/Kconfig > > +++ b/drivers/media/platform/dwc/Kconfig > > @@ -11,3 +11,13 @@ config VIDEO_DWC_HDMI_RX > > > > To compile this driver as a module, choose M here. The module > > will be called dw-hdmi-rx. > > + > > +config VIDEO_DWC_HDMI_RX_CEC > > + bool "Synopsys DesignWare HDMI Receiver CEC support" > > + depends on VIDEO_DWC_HDMI_RX > > + select CEC_CORE > > + help > > + When selected the Synopsys DesignWare HDMI RX controller > > + will support the optional HDMI CEC feature. > > + > > + It uses the generic CEC framework interface. > > diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.c b/drivers/media/platform/dwc/dw-hdmi-rx.c > > index a468a93..5d6b1c0 100644 > > --- a/drivers/media/platform/dwc/dw-hdmi-rx.c > > +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c > > @@ -21,6 +21,7 @@ > > #include <linux/workqueue.h> > > #include <linux/rational.h> > > #include <linux/hdmi.h> > > +#include <media/cec.h> > > #include <media/v4l2-async.h> > > #include <media/v4l2-ctrls.h> > > #include <media/v4l2-device.h> > > @@ -36,6 +37,7 @@ > > #define DW_HDMI_JTAG_TAP_WRITE_CMD 1 > > #define DW_HDMI_JTAG_TAP_READ_CMD 3 > > #define DW_HDMI_AUDIO_FREQ_RANGE 1000 > > +#define DW_HDMI_CEC_MAX_LOG_ADDRS CEC_MAX_LOG_ADDRS > > > > /* EDID for HDMI RX */ > > static u32 dw_hdmi_edid[] = { > > @@ -164,6 +166,9 @@ struct dw_hdmi_dev { > > union hdmi_infoframe audioif; > > union hdmi_infoframe vsif; > > > > + /* CEC */ > > + struct cec_adapter *cec_adap; > > + > > /* v4l2 device */ > > struct v4l2_subdev sd; > > struct v4l2_ctrl_handler hdl; > > @@ -365,6 +370,20 @@ static void dw_hdmi_reset(struct dw_hdmi_dev *dw_dev) > > dw_hdmi_main_reset(dw_dev); > > > > dw_hdmi_disable_hpd(dw_dev); > > + > > + /* After a main reset try to re-enable the cec adapter in order to > > + * reconfigure the required cec registers. For this the physical address > > + * is invalidated and reconfigured, and with CEC_CAP_NEEDS_HPD allowing > > + * to re-enable the adapter. > > + */ > > + if (dw_dev->cec_adap) { > > + u16 phys_addr = dw_dev->cec_adap->phys_addr; > > + > > + cec_phys_addr_invalidate(dw_dev->cec_adap); > > + cec_s_phys_addr(dw_dev->cec_adap, phys_addr, false); > > + dev_dbg(dw_dev->dev, "%s: re-enable cec adapter\n", > > + __func__); > > + } > > } > > > > static inline bool is_off(struct dw_hdmi_dev *dw_dev) > > @@ -1460,6 +1479,184 @@ static u32 dw_hdmi_get_int_val(struct dw_hdmi_dev *dw_dev, u32 ists, u32 ien) > > return hdmi_readl(dw_dev, ists) & hdmi_readl(dw_dev, ien); > > } > > > > +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC) > > +static void dw_hdmi_cec_enable_ints(struct dw_hdmi_dev *dw_dev) > > +{ > > + u32 mask = DW_HDMI_DONE_ISTS | DW_HDMI_EOM_ISTS | > > + DW_HDMI_NACK_ISTS | DW_HDMI_ARBLST_ISTS | > > + DW_HDMI_ERROR_INIT_ISTS | DW_HDMI_ERROR_FOLL_ISTS; > > + > > + hdmi_writel(dw_dev, mask, DW_HDMI_AUD_CEC_IEN_SET); > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_MASK); > > +} > > + > > +static void dw_hdmi_cec_disable_ints(struct dw_hdmi_dev *dw_dev) > > +{ > > + hdmi_writel(dw_dev, ~0x0, DW_HDMI_AUD_CEC_IEN_CLR); > > + hdmi_writel(dw_dev, ~0x0, DW_HDMI_CEC_MASK); > > +} > > + > > +static void dw_hdmi_cec_clear_ints(struct dw_hdmi_dev *dw_dev) > > +{ > > + hdmi_writel(dw_dev, ~0x0, DW_HDMI_AUD_CEC_ICLR); > > +} > > + > > +static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat) > > +{ > > + if (hdmi_readl(dw_dev, DW_HDMI_CEC_CTRL) & DW_HDMI_SEND_MASK) { > > + dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__); > > + return; > > + } > > + > > + if (stat & DW_HDMI_ARBLST_ISTS) { > > + cec_transmit_attempt_done(dw_dev->cec_adap, > > + CEC_TX_STATUS_ARB_LOST); > > + return; > > + } > > + > > + if (stat & DW_HDMI_NACK_ISTS) { > > + cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_NACK); > > + return; > > + } > > + > > + if (stat & DW_HDMI_ERROR_INIT_ISTS) { > > + dev_dbg(dw_dev->dev, "%s: got low drive error\n", __func__); > > + cec_transmit_attempt_done(dw_dev->cec_adap, > > + CEC_TX_STATUS_LOW_DRIVE); > > + return; > > + } > > + > > + if (stat & DW_HDMI_DONE_ISTS) { > > + cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_OK); > > + return; > > + } > > +} > > + > > +static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev) > > +{ > > + struct cec_msg msg; > > + u8 i; > > + > > + msg.len = hdmi_readl(dw_dev, DW_HDMI_CEC_RX_CNT); > > + if (!msg.len || msg.len > DW_HDMI_CEC_RX_DATA_MAX) > > + return; /* it's an invalid/non-existent message */ > > + > > + for (i = 0; i < msg.len; i++) > > + msg.msg[i] = hdmi_readl(dw_dev, DW_HDMI_CEC_RX_DATA(i)); > > + > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_LOCK); > > + cec_received_msg(dw_dev->cec_adap, &msg); > > +} > > + > > +static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) > > +{ > > + struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap); > > + > > + dev_dbg(dw_dev->dev, "%s: enable=%d\n", __func__, enable); > > + > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_ADDR_L); > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_ADDR_H); > > + > > + if (enable) { > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_LOCK); > > + dw_hdmi_cec_clear_ints(dw_dev); > > + dw_hdmi_cec_enable_ints(dw_dev); > > + } else { > > + dw_hdmi_cec_disable_ints(dw_dev); > > + dw_hdmi_cec_clear_ints(dw_dev); > > + } > > + > > + return 0; > > +} > > + > > +static int dw_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) > > +{ > > + struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap); > > + u32 tmp; > > + > > + dev_dbg(dw_dev->dev, "%s: addr=%d\n", __func__, addr); > > + > > + if (addr == CEC_LOG_ADDR_INVALID) { > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_ADDR_L); > > + hdmi_writel(dw_dev, 0x0, DW_HDMI_CEC_ADDR_H); > > + return 0; > > + } > > + > > + if (addr >= 8) { > > + tmp = hdmi_readl(dw_dev, DW_HDMI_CEC_ADDR_H); > > + tmp |= BIT(addr - 8); > > + hdmi_writel(dw_dev, tmp, DW_HDMI_CEC_ADDR_H); > > + } else { > > + tmp = hdmi_readl(dw_dev, DW_HDMI_CEC_ADDR_L); > > + tmp |= BIT(addr); > > + hdmi_writel(dw_dev, tmp, DW_HDMI_CEC_ADDR_L); > > + } > > + > > + return 0; > > +} > > + > > +static int dw_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, > > + u32 signal_free_time, struct cec_msg *msg) > > +{ > > + struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap); > > + u8 len = msg->len; > > + u32 reg; > > + u8 i; > > + > > + dev_dbg(dw_dev->dev, "%s: len=%d\n", __func__, len); > > + > > + if (hdmi_readl(dw_dev, DW_HDMI_CEC_CTRL) & DW_HDMI_SEND_MASK) { > > + dev_err(dw_dev->dev, "%s: tx is busy\n", __func__); > > + return -EBUSY; > > + } > > + > > + for (i = 0; i < len; i++) > > + hdmi_writel(dw_dev, msg->msg[i], DW_HDMI_CEC_TX_DATA(i)); > > + > > + switch (signal_free_time) { > > + case CEC_SIGNAL_FREE_TIME_RETRY: > > + reg = 0x0; > > + break; > > + case CEC_SIGNAL_FREE_TIME_NEXT_XFER: > > + reg = 0x2; > > + break; > > + case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR: > > + default: > > + reg = 0x1; > > + break; > > + } > > + > > + hdmi_writel(dw_dev, len, DW_HDMI_CEC_TX_CNT); > > + hdmi_mask_writel(dw_dev, reg, DW_HDMI_CEC_CTRL, > > + DW_HDMI_FRAME_TYP_OFFSET, > > + DW_HDMI_FRAME_TYP_MASK); > > + hdmi_mask_writel(dw_dev, 0x1, DW_HDMI_CEC_CTRL, > > + DW_HDMI_SEND_OFFSET, > > + DW_HDMI_SEND_MASK); > > + return 0; > > +} > > + > > +static const struct cec_adap_ops dw_hdmi_cec_adap_ops = { > > + .adap_enable = dw_hdmi_cec_adap_enable, > > + .adap_log_addr = dw_hdmi_cec_adap_log_addr, > > + .adap_transmit = dw_hdmi_cec_adap_transmit, > > +}; > > + > > +static void dw_hdmi_cec_irq_handler(struct dw_hdmi_dev *dw_dev) > > +{ > > + u32 cec_ists = dw_hdmi_get_int_val(dw_dev, DW_HDMI_AUD_CEC_ISTS, > > + DW_HDMI_AUD_CEC_IEN); > > + > > + dw_hdmi_cec_clear_ints(dw_dev); > > + > > + if (cec_ists) { > > + dw_hdmi_cec_tx_raw_status(dw_dev, cec_ists); > > + if (cec_ists & DW_HDMI_EOM_ISTS) > > + dw_hdmi_cec_received_msg(dw_dev); > > + } > > +} > > +#endif /* CONFIG_VIDEO_DWC_HDMI_RX_CEC */ > > + > > static u8 dw_hdmi_get_curr_vic(struct dw_hdmi_dev *dw_dev, bool *is_hdmi_vic) > > { > > u8 vic = hdmi_mask_readl(dw_dev, DW_HDMI_PDEC_AVI_PB, > > @@ -2058,6 +2255,10 @@ static irqreturn_t dw_hdmi_irq_handler(int irq, void *dev_data) > > } > > } > > > > +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC) > > + dw_hdmi_cec_irq_handler(dw_dev); > > +#endif /* CONFIG_VIDEO_DWC_HDMI_RX_CEC */ > > + > > return IRQ_HANDLED; > > } > > > > @@ -2556,14 +2757,27 @@ static int dw_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) > > struct dw_hdmi_dev *dw_dev = to_dw_dev(sd); > > int input_count = dw_dev->config->phy->input_count; > > int size, ret; > > + u16 phys_addr; > > u32 *tmp; > > > > memset(edid->reserved, 0, sizeof(edid->reserved)); > > > > - if (edid->pad >= input_count || !edid->edid || !edid->blocks) > > + if (edid->pad >= input_count || !edid->edid) > > return -EINVAL; > > if (edid->start_block != 0) > > return -EINVAL; > > + if (!edid->blocks) { > > + phys_addr = CEC_PHYS_ADDR_INVALID; > > + goto set_phys_addr; > > + } > > + > > + /* get the source physical address (PA) from edid */ > > + phys_addr = cec_get_edid_phys_addr(edid->edid, edid->blocks * 128, > > + NULL); > > + /* get the own physical address getting the parent of Source PA */ > > + ret = v4l2_phys_addr_validate(phys_addr, &phys_addr, NULL); > > + if (ret) > > + return ret; > > Something I should have asked in my review for 6/9: if there are > multiple inputs, does each input have its own EDID area? > Yes, there is one EDID area for each input port. > If there are multiple inputs, are the CEC lines of each input part > of the same CEC bus? This is typically the case for e.g. TVs, and as > far as I can see it's the case for this receiver driver as well. > Yes. > > > > /* Clear old EDID */ > > size = dw_dev->curr_edid_blocks[edid->pad] * 128; > > @@ -2592,7 +2806,9 @@ static int dw_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) > > if (ret) > > return ret; > > > > +set_phys_addr: > > dw_dev->curr_edid_blocks[edid->pad] = edid->blocks; > > + cec_s_phys_addr(dw_dev->cec_adap, phys_addr, false); > > return 0; > > } > > > > @@ -2825,15 +3041,33 @@ static int dw_hdmi_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, > > static int dw_hdmi_registered(struct v4l2_subdev *sd) > > { > > struct dw_hdmi_dev *dw_dev = to_dw_dev(sd); > > + int ret; > > + > > + ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev); > > + if (ret) { > > + dev_err(dw_dev->dev, "failed to register CEC adapter\n"); > > + goto err_adapter; > > + } > > + cec_s_phys_addr(dw_dev->cec_adap, 0, false); > > This isn't right. When you set the EDID you will also set the physical > address, you don't do that when registering the CEC adapter, there is no > physical address yet. > As in the patch [6/9] we had the default edid "dw_hdmi_edid", that was being loaded in the driver probe, we also initialized the cec physical address "0" accordingly with the default edid. But following your comments in patch [6/9], the default edid must be removed and so this initial cec physical address "0" will be removed accordingly also as you are asking :). > > + if (dw_dev->cec_adap) > > + dev_info(dw_dev->dev, > > + "CEC adapter %s registered for HDMI input\n", > > + dev_name(&dw_dev->cec_adap->devnode.dev)); > > > > dw_dev->registered = true; > > return 0; > > + > > +err_adapter: > > + cec_delete_adapter(dw_dev->cec_adap); > > + return ret; > > } > > > > static void dw_hdmi_unregistered(struct v4l2_subdev *sd) > > { > > struct dw_hdmi_dev *dw_dev = to_dw_dev(sd); > > > > + cec_unregister_adapter(dw_dev->cec_adap); > > + > > dw_dev->registered = false; > > } > > > > @@ -3194,10 +3428,29 @@ static int dw_hdmi_rx_probe(struct platform_device *pdev) > > if (ret) > > goto err_phy_exit; > > > > + /* CEC */ > > +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC) > > + dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops, > > + dw_dev, dev_name(dev), > > + (CEC_CAP_DEFAULTS | > > + CEC_CAP_NEEDS_HPD | > > This cap makes no sense for a receiver. > This cap was added in order to overcome a scenario that we have when there was an hot plug. The driver performs a main reset that cause the loss of cec controller configuration. And in that case in order to reconfigure the cec this cap was added. Because whenever there is a reset the physical address is reconfigured (invalidated and set) and with this cap the cec configuration callback function (adap_enable) is called again allowing to reconfigure the cec. Does it make sense? If not, what should be the best approach in order to deal with this scenario? > > + CEC_CAP_CONNECTOR_INFO), > > This cap also should be removed: connector info is not (yet) supported > for receivers, only for transmitters. > I agree! Will be addressed in the next patch series. Thanks a lot, BR, Nelson Costa > Regards, > > Hans > > > + DW_HDMI_CEC_MAX_LOG_ADDRS); > > + ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap); > > + if (ret) { > > + dev_err(dev, "failed to allocate CEC adapter\n"); > > + goto err_cec; > > + } > > + > > + dev_info(dev, "CEC is enabled\n"); > > +#else > > + dev_info(dev, "CEC is disabled\n"); > > +#endif /* CONFIG_VIDEO_DWC_HDMI_RX_CEC */ > > + > > ret = v4l2_async_register_subdev(sd); > > if (ret) { > > dev_err(dev, "failed to register subdev\n"); > > - goto err_phy_exit; > > + goto err_cec; > > } > > > > /* Fill initial format settings */ > > @@ -3230,6 +3483,8 @@ static int dw_hdmi_rx_probe(struct platform_device *pdev) > > > > err_subdev: > > v4l2_async_unregister_subdev(sd); > > +err_cec: > > + cec_delete_adapter(dw_dev->cec_adap); > > err_phy_exit: > > dw_hdmi_phy_exit(dw_dev); > > err_hdl: > > diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.h b/drivers/media/platform/dwc/dw-hdmi-rx.h > > index f0ea1d4..775b7a9 100644 > > --- a/drivers/media/platform/dwc/dw-hdmi-rx.h > > +++ b/drivers/media/platform/dwc/dw-hdmi-rx.h > > @@ -325,6 +325,25 @@ > > > > #define DW_HDMI_HDCP22_STATUS 0x08fc > > > > +/* id_audio_and_cec_interrupt Registers */ > > +#define DW_HDMI_AUD_CEC_IEN_CLR 0x0f90 > > +#define DW_HDMI_AUD_CEC_IEN_SET 0x0f94 > > + > > +#define DW_HDMI_AUD_CEC_ISTS 0x0f98 > > +#define DW_HDMI_WAKEUPCTRL_ISTS BIT(22) > > +#define DW_HDMI_ERROR_FOLL_ISTS BIT(21) > > +#define DW_HDMI_ERROR_INIT_ISTS BIT(20) > > +#define DW_HDMI_ARBLST_ISTS BIT(19) > > +#define DW_HDMI_NACK_ISTS BIT(18) > > +#define DW_HDMI_EOM_ISTS BIT(17) > > +#define DW_HDMI_DONE_ISTS BIT(16) > > +#define DW_HDMI_SCK_STABLE_ISTS BIT(1) > > +#define DW_HDMI_CTSN_CNT_ISTS BIT(0) > > + > > +#define DW_HDMI_AUD_CEC_IEN 0x0f9c > > +#define DW_HDMI_AUD_CEC_ICLR 0x0fa0 > > +#define DW_HDMI_AUD_CEC_ISET 0x0fa4 > > + > > /* id_mode_detection_interrupt Registers */ > > #define DW_HDMI_MD_IEN_CLR 0x0fc0 > > #define DW_HDMI_MD_IEN_SET 0x0fc4 > > @@ -426,6 +445,44 @@ > > #define DW_HDMI_HDMI_ENABLE_MASK BIT(2) > > #define DW_HDMI_HDMI_ENABLE_OFFSET 2 > > > > +/* id_cec Registers */ > > +#define DW_HDMI_CEC_CTRL 0x1f00 > > +#define DW_HDMI_STANDBY_MASK BIT(4) > > +#define DW_HDMI_STANDBY_OFFSET 4 > > +#define DW_HDMI_BC_NACK_MASK BIT(3) > > +#define DW_HDMI_BC_NACK_OFFSET 3 > > +#define DW_HDMI_FRAME_TYP_MASK GENMASK(2, 1) > > +#define DW_HDMI_FRAME_TYP_OFFSET 1 > > +#define DW_HDMI_SEND_MASK BIT(0) > > +#define DW_HDMI_SEND_OFFSET 0 > > + > > +#define DW_HDMI_CEC_MASK 0x1f08 > > +#define DW_HDMI_WAKEUP_MASK BIT(6) > > +#define DW_HDMI_WAKEUP_OFFSET 6 > > +#define DW_HDMI_ERROR_FLOW_MASK BIT(5) > > +#define DW_HDMI_ERROR_FLOW_OFFSET 5 > > +#define DW_HDMI_ERROR_INITITATOR_MASK BIT(4) > > +#define DW_HDMI_ERROR_INITITATOR_OFFSET 4 > > +#define DW_HDMI_ARB_LOST_MASK BIT(3) > > +#define DW_HDMI_ARB_LOST_OFFSET 3 > > +#define DW_HDMI_NACK_MASK BIT(2) > > +#define DW_HDMI_NACK_OFFSET 2 > > +#define DW_HDMI_EOM_MASK BIT(1) > > +#define DW_HDMI_EOM_OFFSET 1 > > +#define DW_HDMI_DONE_MASK BIT(0) > > +#define DW_HDMI_DONE_OFFSET 0 > > + > > +#define DW_HDMI_CEC_ADDR_L 0x1f14 > > +#define DW_HDMI_CEC_ADDR_H 0x1f18 > > +#define DW_HDMI_CEC_TX_CNT 0x1f1c > > +#define DW_HDMI_CEC_RX_CNT 0x1f20 > > +#define DW_HDMI_CEC_TX_DATA(i) (0x1f40 + ((i) * 4)) > > +#define DW_HDMI_CEC_TX_DATA_MAX 16 > > +#define DW_HDMI_CEC_RX_DATA(i) (0x1f80 + ((i) * 4)) > > +#define DW_HDMI_CEC_RX_DATA_MAX 16 > > +#define DW_HDMI_CEC_LOCK 0x1fc0 > > +#define DW_HDMI_CEC_WAKEUPCTRL 0x1fc4 > > + > > /* id_cbus Registers */ > > #define DW_HDMI_CBUSIOCTRL 0x3020 > > #define DW_HDMI_DATAPATH_CBUSZ_MASK BIT(24) > >