@@ -135,6 +135,16 @@
<entry>This CEC adapter is an HDMI source, &ie; it has an HDMI output
connector.</entry>
</row>
+ <row>
+ <entry><constant>CEC_CAP_ARC</constant></entry>
+ <entry>0x00000080</entry>
+ <entry>This adapter supports the Audio Return Channel protocol.</entry>
+ </row>
+ <row>
+ <entry><constant>CEC_CAP_CDC_HPD</constant></entry>
+ <entry>0x00000100</entry>
+ <entry>This adapter supports the hotplug detect protocol over CDC.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -187,6 +187,42 @@ The follower can of course always call &CEC-TRANSMIT;.</para>
&cs-def;
<tbody valign="top">
<row>
+ <entry><constant>CEC_MSG_INITIATE_ARC</constant></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><constant>CEC_MSG_TERMINATE_ARC</constant></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><constant>CEC_MSG_REQUEST_ARC_INITIATION</constant></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><constant>CEC_MSG_REQUEST_ARC_TERMINATION</constant></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><constant>CEC_MSG_REPORT_ARC_INITIATED</constant></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><constant>CEC_MSG_REPORT_ARC_TERMINATED</constant></entry>
+ <entry>If <constant>CEC_CAP_ARC</constant> is not set, then just pass
+ it on to userspace for processing. However, if <constant>CEC_CAP_ARC</constant> is
+ set, then the core framework processes this message and userspace will
+ not see it, not even in passthrough mode.</entry>
+ </row>
+ <row>
+ <entry><constant>CEC_MSG_CDC_MESSAGE</constant></entry>
+ <entry>If <constant>CEC_CAP_CDC_HPD</constant> is not set, then just pass
+ it on to userspace for processing. Do the same if the CDC command is not
+ one of <constant>CEC_MSG_CDC_HPD_REPORT_STATE</constant> or
+ <constant>CEC_MSG_CDC_HPD_SET_STATE</constant>. Else the core framework
+ processes this message and userspace will not see it, not even in passthrough
+ mode.</entry>
+ </row>
+ <row>
<entry><constant>CEC_MSG_GET_CEC_VERSION</constant></entry>
<entry>When in passthrough mode this message has to be handled by userspace,
otherwise the core will return the CEC version that was set with &CEC-ADAP-S-LOG-ADDRS;.</entry>
@@ -217,6 +217,16 @@ struct cec_adap_ops {
/* High-level CEC message callback */
int (*received)(struct cec_adapter *adap, struct cec_msg *msg);
+
+ /* High-level CDC Hotplug Detect callbacks */
+ u8 (*source_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state);
+ void (*sink_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state, u8 cdc_hpd_error);
+
+ /* High-level Audio Return Channel callbacks */
+ int (*sink_initiate_arc)(struct cec_adapter *adap);
+ int (*sink_terminate_arc)(struct cec_adapter *adap);
+ int (*source_arc_initiated)(struct cec_adapter *adap);
+ int (*source_arc_terminated)(struct cec_adapter *adap);
};
The received() callback allows the driver to optionally handle a newly
@@ -229,6 +239,62 @@ callback. If it doesn't want to handle this message, then it should return
-ENOMSG, otherwise the CEC framework assumes it processed this message and
it will not no anything with it.
+The other callbacks deal with two CEC features: CDC Hotplug Detect and
+Audio Return Channel. Here the framework takes care of handling these
+messages and it calls the callbacks to notify the driver when it needs
+to take action.
+
+CDC Hotplug Support
+-------------------
+
+A source received a hotplug state change message:
+
+ u8 (*source_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state);
+
+A source received a CEC_MSG_CDC_HPD_SET_STATE message. The framework will
+reply with a CEC_MSG_CDC_HPD_REPORT_STATE message and this callback is used
+to fill in the HPD Error Code Operand of the REPORT_STATE message. In addition,
+the driver can act in this callback on the hotplug state change.
+
+Only implement if CEC_CAP_CDC_HPD is set.
+
+A sink received a hotplug report state message:
+
+ void (*sink_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state, u8 cdc_hpd_error);
+
+A sink received a CEC_MSG_CDC_HPD_REPORT_STATE message. This callback will
+do anything necessary to implement this hotplug change. The two arguments
+are the HPD Error State and HPD Error Code Operands from the CEC_MSG_CDC_HPD_REPORT_STATE
+message.
+
+
+Audio Return Channel Support
+----------------------------
+
+Called if a CEC_MSG_INITIATE_ARC message is received by an HDMI sink.
+This callback should start sending audio over the audio return channel. If
+successful it should return 0.
+
+ int (*sink_initiate_arc)(struct cec_adapter *adap);
+
+Called if a CEC_MSG_TERMINATE_ARC message is received by an HDMI sink.
+This callback should stop sending audio over the audio return channel. If
+successful it should return 0.
+
+ void (*sink_terminate_arc)(struct cec_adapter *adap);
+
+Called if a CEC_MSG_REPORT_ARC_INITIATED message is received by an
+HDMI source. This callback can be used to enable receiving audio from
+the audio return channel.
+
+ void (*source_arc_initiated)(struct cec_adapter *adap);
+
+Called if a CEC_MSG_REPORT_ARC_TERMINATED message is received by an
+HDMI source. This callback can be used to disable receiving audio from
+the audio return channel.
+
+ void (*source_arc_terminated)(struct cec_adapter *adap);
+
CEC framework functions
-----------------------
@@ -274,6 +340,15 @@ unconfiguring. This function will just return if the physical address is
invalid. Once the physical address becomes valid, then the framework will
attempt to claim these logical addresses.
+u8 cec_sink_cdc_hpd(struct cec_adapter *adap, u8 input_port, u8 cdc_hpd_state);
+
+If an HDMI receiver supports hotplug signalling over CDC (CEC_CAP_CDC_HPD is
+set), then the driver should call this function whenever the hotplug state
+changes for an input. This call will send an appropriate CDC message over
+the CEC line. It returns CEC_OP_HPD_ERROR_NONE on success, if the adapter
+is unconfigured it returns CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE and if
+the cec_transmit fails it returns CEC_OP_HPD_ERROR_OTHER.
+
void cec_log_status(struct cec_adapter *adap);
This logs the current CEC adapter status in the kernel log. Useful for
@@ -160,6 +160,42 @@ u16 cec_phys_addr_parent(u16 phys_addr)
}
EXPORT_SYMBOL_GPL(cec_phys_addr_parent);
+/*
+ * Two physical addresses are adjacent if they have a direct link.
+ * So 0.0.0.0 and 1.0.0.0 are adjacent, but not 0.0.0.0 and 1.1.0.0.
+ * And 2.3.0.0 and 2.3.1.0 are adjacent, but not 2.3.0.0 and 2.4.0.0.
+ *
+ * In other words, the two addresses share the same prefix, but then
+ * one has a zero and the other has a non-zero value. And the remaining
+ * components are all zero for both.
+ */
+static bool cec_pa_are_adjacent(const struct cec_adapter *adap, u16 pa1, u16 pa2)
+{
+ u16 mask = 0xf000;
+ int i;
+
+ if (pa1 == CEC_PHYS_ADDR_INVALID || pa2 == CEC_PHYS_ADDR_INVALID)
+ return false;
+ for (i = 0; i < 3; i++) {
+ if ((pa1 & mask) != (pa2 & mask))
+ break;
+ mask = (mask >> 4) | 0xf000;
+ }
+ if ((pa1 & ~mask) || (pa2 & ~mask))
+ return false;
+ if (!(pa1 & mask) ^ !(pa2 & mask))
+ return true;
+ return false;
+}
+
+static bool cec_la_are_adjacent(const struct cec_adapter *adap, u8 la1, u8 la2)
+{
+ u16 pa1 = adap->phys_addrs[la1];
+ u16 pa2 = adap->phys_addrs[la2];
+
+ return cec_pa_are_adjacent(adap, pa1, pa2);
+}
+
static int cec_log_addr2idx(const struct cec_adapter *adap, u8 log_addr)
{
int i;
@@ -1034,7 +1070,9 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg,
int la_idx = cec_log_addr2idx(adap, dest_laddr);
bool is_directed = la_idx >= 0;
bool from_unregistered = init_laddr == 0xf;
+ u16 cdc_phys_addr;
struct cec_msg tx_cec_msg = { };
+ u8 *tx_msg = tx_cec_msg.msg;
dprintk(1, "cec_receive_notify: %*ph\n", msg->len, msg->msg);
@@ -1045,12 +1083,57 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg,
}
/*
- * REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and
+ * ARC, CDC and REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and
* CEC_MSG_USER_CONTROL_RELEASED messages always have to be
* handled by the CEC core, even if the passthrough mode is on.
- * The others are just ignored if passthrough mode is on.
+ * ARC and CDC messages will never be seen even if passthrough is
+ * on, but the others are just passed on normally after we processed
+ * them.
*/
switch (msg->msg[1]) {
+ case CEC_MSG_INITIATE_ARC:
+ case CEC_MSG_TERMINATE_ARC:
+ case CEC_MSG_REQUEST_ARC_INITIATION:
+ case CEC_MSG_REQUEST_ARC_TERMINATION:
+ case CEC_MSG_REPORT_ARC_INITIATED:
+ case CEC_MSG_REPORT_ARC_TERMINATED:
+ /* ARC messages are never passed through if CAP_ARC is set */
+
+ /* Abort/ignore if ARC is not supported */
+ if (!(adap->capabilities & CEC_CAP_ARC)) {
+ /* Just abort if nobody is listening */
+ if (is_directed && !is_reply && !adap->cec_follower &&
+ !adap->follower_cnt)
+ return cec_feature_abort(adap, msg);
+ goto skip_processing;
+ }
+ /* Ignore if addressing is wrong */
+ if (is_broadcast || from_unregistered)
+ return 0;
+ break;
+
+ case CEC_MSG_CDC_MESSAGE:
+ switch (msg->msg[4]) {
+ case CEC_MSG_CDC_HPD_REPORT_STATE:
+ case CEC_MSG_CDC_HPD_SET_STATE:
+ /*
+ * CDC_HPD messages are never passed through if
+ * CAP_CDC_HPD is set
+ */
+
+ /* Ignore if CDC_HPD is not supported */
+ if (!(adap->capabilities & CEC_CAP_CDC_HPD))
+ goto skip_processing;
+ /* or the addressing is wrong */
+ if (!is_broadcast)
+ return 0;
+ break;
+ default:
+ /* Other CDC messages are ignored */
+ goto skip_processing;
+ }
+ break;
+
case CEC_MSG_GET_CEC_VERSION:
case CEC_MSG_GIVE_DEVICE_VENDOR_ID:
case CEC_MSG_ABORT:
@@ -1186,6 +1269,97 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg,
return cec_report_features(adap, la_idx);
return 0;
+ case CEC_MSG_REQUEST_ARC_INITIATION:
+ if (!adap->is_source ||
+ !cec_la_are_adjacent(adap, dest_laddr, init_laddr))
+ return cec_feature_refused(adap, msg);
+ cec_msg_initiate_arc(&tx_cec_msg, false);
+ return cec_transmit_msg(adap, &tx_cec_msg, false);
+
+ case CEC_MSG_REQUEST_ARC_TERMINATION:
+ if (!adap->is_source ||
+ !cec_la_are_adjacent(adap, dest_laddr, init_laddr))
+ return cec_feature_refused(adap, msg);
+ cec_msg_terminate_arc(&tx_cec_msg, false);
+ return cec_transmit_msg(adap, &tx_cec_msg, false);
+
+ case CEC_MSG_INITIATE_ARC:
+ if (adap->is_source ||
+ !cec_la_are_adjacent(adap, dest_laddr, init_laddr))
+ return cec_feature_refused(adap, msg);
+ if (call_op(adap, sink_initiate_arc))
+ return 0;
+ cec_msg_report_arc_initiated(&tx_cec_msg);
+ return cec_transmit_msg(adap, &tx_cec_msg, false);
+
+ case CEC_MSG_TERMINATE_ARC:
+ if (adap->is_source ||
+ !cec_la_are_adjacent(adap, dest_laddr, init_laddr))
+ return cec_feature_refused(adap, msg);
+ call_void_op(adap, sink_terminate_arc);
+ cec_msg_report_arc_terminated(&tx_cec_msg);
+ return cec_transmit_msg(adap, &tx_cec_msg, false);
+
+ case CEC_MSG_REPORT_ARC_INITIATED:
+ if (!adap->is_source ||
+ !cec_la_are_adjacent(adap, dest_laddr, init_laddr))
+ return cec_feature_refused(adap, msg);
+ call_void_op(adap, source_arc_initiated);
+ return 0;
+
+ case CEC_MSG_REPORT_ARC_TERMINATED:
+ if (!adap->is_source ||
+ !cec_la_are_adjacent(adap, dest_laddr, init_laddr))
+ return cec_feature_refused(adap, msg);
+ call_void_op(adap, source_arc_terminated);
+ return 0;
+
+ case CEC_MSG_CDC_MESSAGE: {
+ unsigned int shift;
+ unsigned int input_port;
+
+ cdc_phys_addr = (msg->msg[2] << 8) | msg->msg[3];
+ if (!cec_pa_are_adjacent(adap, cdc_phys_addr, adap->phys_addr))
+ return 0;
+
+ switch (msg->msg[4]) {
+ case CEC_MSG_CDC_HPD_REPORT_STATE:
+ /*
+ * Ignore if we're not a sink or the message comes from
+ * an upstream device.
+ */
+ if (adap->is_source || cdc_phys_addr <= adap->phys_addr)
+ return 0;
+ adap->ops->sink_cdc_hpd(adap, msg->msg[5] >> 4, msg->msg[5] & 0xf);
+ return 0;
+ case CEC_MSG_CDC_HPD_SET_STATE:
+ /* Ignore if we're not a source */
+ if (!adap->is_source)
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+
+ input_port = msg->msg[5] >> 4;
+ for (shift = 0; shift < 16; shift += 4) {
+ if (cdc_phys_addr & (0xf000 >> shift))
+ continue;
+ cdc_phys_addr |= input_port << (12 - shift);
+ break;
+ }
+ if (cdc_phys_addr != adap->phys_addr)
+ return 0;
+
+ tx_cec_msg.len = 6;
+ /* broadcast reply */
+ tx_msg[0] = (adap->log_addrs.log_addr[0] << 4) | 0xf;
+ cec_msg_cdc_hpd_report_state(&tx_cec_msg,
+ msg->msg[5] & 0xf,
+ adap->ops->source_cdc_hpd(adap, msg->msg[5] & 0xf));
+ return cec_transmit_msg(adap, &tx_cec_msg, false);
+ }
+
default:
/*
* Unprocessed messages are aborted if userspace isn't doing
@@ -1681,6 +1855,26 @@ void cec_log_status(struct cec_adapter *adap, struct cec_fh *fh)
}
EXPORT_SYMBOL_GPL(cec_log_status);
+/*
+ * Called by drivers to update the CDC HPD state of an input port.
+ */
+u8 cec_sink_cdc_hpd(struct cec_adapter *adap, u8 input_port, u8 cdc_hpd_state)
+{
+ struct cec_msg msg = { };
+ int err;
+
+ if (!adap->is_configured)
+ return CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE;
+
+ msg.msg[0] = (adap->log_addrs.log_addr[0] << 4) | 0xf;
+ cec_msg_cdc_hpd_set_state(&msg, input_port, cdc_hpd_state);
+ err = cec_transmit_msg(adap, &msg, false);
+ if (err)
+ return CEC_OP_HPD_ERROR_OTHER;
+ return CEC_OP_HPD_ERROR_NONE;
+}
+EXPORT_SYMBOL_GPL(cec_sink_cdc_hpd);
+
/* CEC file operations */
@@ -517,10 +517,10 @@ static void cobalt_stream_struct_init(struct cobalt *cobalt)
static int cobalt_create_cec_adap(struct cobalt_stream *s)
{
u32 caps = CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS |
- CEC_CAP_PASSTHROUGH | CEC_CAP_RC;
+ CEC_CAP_PASSTHROUGH | CEC_CAP_RC | CEC_CAP_ARC;
if (s->is_output)
- caps |= CEC_CAP_IS_SOURCE;
+ caps |= CEC_CAP_IS_SOURCE | CEC_CAP_CDC_HPD;
s->cec_adap = cec_create_adapter(&cobalt_cec_adap_ops,
s, s->vdev.name, caps, 1,
&s->cobalt->pci_dev->dev);
@@ -1195,6 +1195,38 @@ static int cobalt_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
attempts, signal_free_time, msg);
}
+static u8 cobalt_cec_cdc_hpd(struct cec_adapter *adap, u8 cdc_hpd_state)
+{
+ switch (cdc_hpd_state) {
+ case CEC_OP_HPD_STATE_EDID_DISABLE:
+ case CEC_OP_HPD_STATE_EDID_ENABLE:
+ case CEC_OP_HPD_STATE_EDID_DISABLE_ENABLE:
+ return CEC_OP_HPD_ERROR_NONE;
+ case CEC_OP_HPD_STATE_CP_EDID_DISABLE:
+ case CEC_OP_HPD_STATE_CP_EDID_ENABLE:
+ case CEC_OP_HPD_STATE_CP_EDID_DISABLE_ENABLE:
+ default:
+ return CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE;
+ }
+}
+
+static int cobalt_sink_initiate_arc(struct cec_adapter *adap)
+{
+ return 0;
+}
+
+static void cobalt_sink_terminate_arc(struct cec_adapter *adap)
+{
+}
+
+static void cobalt_source_arc_initiated(struct cec_adapter *adap)
+{
+}
+
+static void cobalt_source_arc_terminated(struct cec_adapter *adap)
+{
+}
+
static int cobalt_received(struct cec_adapter *adap, struct cec_msg *msg)
{
struct cec_msg reply;
@@ -1223,6 +1255,11 @@ const struct cec_adap_ops cobalt_cec_adap_ops = {
.adap_enable = cobalt_cec_adap_enable,
.adap_log_addr = cobalt_cec_adap_log_addr,
.adap_transmit = cobalt_cec_adap_transmit,
+ .source_cdc_hpd = cobalt_cec_cdc_hpd,
+ .sink_initiate_arc = cobalt_sink_initiate_arc,
+ .sink_terminate_arc = cobalt_sink_terminate_arc,
+ .source_arc_initiated = cobalt_source_arc_initiated,
+ .source_arc_terminated = cobalt_source_arc_terminated,
.received = cobalt_received,
};
@@ -108,6 +108,16 @@ struct cec_adap_ops {
/* High-level CEC message callback */
int (*received)(struct cec_adapter *adap, struct cec_msg *msg);
+
+ /* High-level CDC Hotplug Detect callbacks */
+ u8 (*source_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state);
+ void (*sink_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state, u8 cdc_hpd_error);
+
+ /* High-level Audio Return Channel callbacks */
+ int (*sink_initiate_arc)(struct cec_adapter *adap);
+ void (*sink_terminate_arc)(struct cec_adapter *adap);
+ void (*source_arc_initiated)(struct cec_adapter *adap);
+ void (*source_arc_terminated)(struct cec_adapter *adap);
};
/*
@@ -194,6 +204,7 @@ void cec_s_available_log_addrs(struct cec_adapter *adap, u8 available_las);
int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg,
bool block);
+u8 cec_sink_cdc_hpd(struct cec_adapter *adap, u8 input_port, u8 cdc_hpd_state);
void cec_log_status(struct cec_adapter *adap, struct cec_fh *fh);
/* Called by the adapter */
@@ -226,6 +226,7 @@ static inline bool cec_msg_status_is_ok(const struct cec_msg *msg)
#define CEC_CAP_TRANSMIT (1 << 2)
/*
* Passthrough all messages instead of processing them.
+ * Note: ARC and CDC messages are always processed.
*/
#define CEC_CAP_PASSTHROUGH (1 << 3)
/* Supports remote control */
@@ -234,6 +235,10 @@ static inline bool cec_msg_status_is_ok(const struct cec_msg *msg)
#define CEC_CAP_MONITOR_ALL (1 << 5)
/* Is a source */
#define CEC_CAP_IS_SOURCE (1 << 6)
+/* Supports ARC */
+#define CEC_CAP_ARC (1 << 7)
+/* Supports CDC HPD */
+#define CEC_CAP_CDC_HPD (1 << 8)
/**
* struct cec_caps - CEC capabilities structure.