summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge/adv7511/adv7511_cec.c')
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_cec.c210
1 files changed, 122 insertions, 88 deletions
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
index a20a45c0b353..8ecbc25dc647 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
@@ -1,41 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* adv7511_cec.c - Analog Devices ADV7511/33 cec driver
*
* Copyright 2017 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/device.h>
#include <linux/module.h>
-#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <media/cec.h>
+#include <drm/display/drm_hdmi_cec_helper.h>
+
#include "adv7511.h"
+static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = {
+ ADV7511_REG_CEC_RX1_FRAME_HDR,
+ ADV7511_REG_CEC_RX2_FRAME_HDR,
+ ADV7511_REG_CEC_RX3_FRAME_HDR,
+};
+
+static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = {
+ ADV7511_REG_CEC_RX1_FRAME_LEN,
+ ADV7511_REG_CEC_RX2_FRAME_LEN,
+ ADV7511_REG_CEC_RX3_FRAME_LEN,
+};
+
#define ADV7511_INT1_CEC_MASK \
(ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \
- ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1)
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1 | \
+ ADV7511_INT1_CEC_RX_READY2 | ADV7511_INT1_CEC_RX_READY3)
static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->info->reg_cec_offset;
unsigned int val;
if (regmap_read(adv7511->regmap_cec,
@@ -46,8 +46,8 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
return;
if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) {
- cec_transmit_attempt_done(adv7511->cec_adap,
- CEC_TX_STATUS_ARB_LOST);
+ drm_connector_hdmi_cec_transmit_attempt_done(adv7511->cec_connector,
+ CEC_TX_STATUS_ARB_LOST);
return;
}
if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) {
@@ -74,36 +74,28 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
if (low_drive_cnt)
status |= CEC_TX_STATUS_LOW_DRIVE;
}
- cec_transmit_done(adv7511->cec_adap, status,
- 0, nack_cnt, low_drive_cnt, err_cnt);
+ drm_connector_hdmi_cec_transmit_done(adv7511->cec_connector, status,
+ 0, nack_cnt, low_drive_cnt,
+ err_cnt);
return;
}
if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) {
- cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK);
+ drm_connector_hdmi_cec_transmit_attempt_done(adv7511->cec_connector,
+ CEC_TX_STATUS_OK);
return;
}
}
-void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
- const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
- ADV7511_INT1_CEC_TX_ARBIT_LOST |
- ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ unsigned int offset = adv7511->info->reg_cec_offset;
struct cec_msg msg = {};
unsigned int len;
unsigned int val;
u8 i;
- if (irq1 & irq_tx_mask)
- adv_cec_tx_raw_status(adv7511, irq1);
-
- if (!(irq1 & ADV7511_INT1_CEC_RX_READY1))
- return;
-
if (regmap_read(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len))
+ ADV7511_REG_CEC_RX_FRAME_LEN[rx_buf] + offset, &len))
return;
msg.len = len & 0x1f;
@@ -116,23 +108,85 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
for (i = 0; i < msg.len; i++) {
regmap_read(adv7511->regmap_cec,
- i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val);
+ i + ADV7511_REG_CEC_RX_FRAME_HDR[rx_buf] + offset,
+ &val);
msg.msg[i] = val;
}
- /* toggle to re-enable rx 1 */
- regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 1);
- regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
- cec_received_msg(adv7511->cec_adap, &msg);
+ /* Toggle RX Ready Clear bit to re-enable this RX buffer */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf),
+ BIT(rx_buf));
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), 0);
+
+ drm_connector_hdmi_cec_received_msg(adv7511->cec_connector, &msg);
}
-static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
+int adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
{
- struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->info->reg_cec_offset;
+ const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
+ ADV7511_INT1_CEC_TX_ARBIT_LOST |
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ const u32 irq_rx_mask = ADV7511_INT1_CEC_RX_READY1 |
+ ADV7511_INT1_CEC_RX_READY2 |
+ ADV7511_INT1_CEC_RX_READY3;
+ unsigned int rx_status;
+ int rx_order[3] = { -1, -1, -1 };
+ int i;
+ int irq_status = IRQ_NONE;
+
+ if (irq1 & irq_tx_mask) {
+ adv_cec_tx_raw_status(adv7511, irq1);
+ irq_status = IRQ_HANDLED;
+ }
+
+ if (!(irq1 & irq_rx_mask))
+ return irq_status;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_STATUS + offset, &rx_status))
+ return irq_status;
+
+ /*
+ * ADV7511_REG_CEC_RX_STATUS[5:0] contains the reception order of RX
+ * buffers 0, 1, and 2 in bits [1:0], [3:2], and [5:4] respectively.
+ * The values are to be interpreted as follows:
+ *
+ * 0 = buffer unused
+ * 1 = buffer contains oldest received frame (if applicable)
+ * 2 = buffer contains second oldest received frame (if applicable)
+ * 3 = buffer contains third oldest received frame (if applicable)
+ *
+ * Fill rx_order with the sequence of RX buffer indices to
+ * read from in order, where -1 indicates that there are no
+ * more buffers to process.
+ */
+ for (i = 0; i < 3; i++) {
+ unsigned int timestamp = (rx_status >> (2 * i)) & 0x3;
+
+ if (timestamp)
+ rx_order[timestamp - 1] = i;
+ }
+
+ /* Read CEC RX buffers in the appropriate order as prescribed above */
+ for (i = 0; i < 3; i++) {
+ int rx_buf = rx_order[i];
+
+ if (rx_buf < 0)
+ break;
+
+ adv7511_cec_rx(adv7511, rx_buf);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int adv7511_cec_enable(struct drm_bridge *bridge, bool enable)
+{
+ struct adv7511 *adv7511 = bridge_to_adv7511(bridge);
+ unsigned int offset = adv7511->info->reg_cec_offset;
if (adv7511->i2c_cec == NULL)
return -EIO;
@@ -142,11 +196,11 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
0x03, 0x01);
- /* legacy mode and clear all rx buffers */
+ /* non-legacy mode and clear all rx buffers */
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x0f);
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
/* initially disable tx */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
@@ -154,7 +208,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
/* tx: ready */
/* tx: arbitration lost */
/* tx: retry timeout */
- /* rx: ready 1 */
+ /* rx: ready 1-3 */
regmap_update_bits(adv7511->regmap,
ADV7511_REG_INT_ENABLE(1), 0x3f,
ADV7511_INT1_CEC_MASK);
@@ -175,11 +229,10 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
return 0;
}
-static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+int adv7511_cec_log_addr(struct drm_bridge *bridge, u8 addr)
{
- struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ struct adv7511 *adv7511 = bridge_to_adv7511(bridge);
+ unsigned int offset = adv7511->info->reg_cec_offset;
unsigned int i, free_idx = ADV7511_MAX_ADDRS;
if (!adv7511->cec_enabled_adap)
@@ -244,12 +297,11 @@ static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
return 0;
}
-static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
- u32 signal_free_time, struct cec_msg *msg)
+int adv7511_cec_transmit(struct drm_bridge *bridge, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
{
- struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ struct adv7511 *adv7511 = bridge_to_adv7511(bridge);
+ unsigned int offset = adv7511->info->reg_cec_offset;
u8 len = msg->len;
unsigned int i;
@@ -280,12 +332,6 @@ static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
return 0;
}
-static const struct cec_adap_ops adv7511_cec_adap_ops = {
- .adap_enable = adv7511_cec_adap_enable,
- .adap_log_addr = adv7511_cec_adap_log_addr,
- .adap_transmit = adv7511_cec_adap_transmit,
-};
-
static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
{
adv7511->cec_clk = devm_clk_get(dev, "cec");
@@ -300,50 +346,38 @@ static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
return 0;
}
-int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
+int adv7511_cec_init(struct drm_bridge *bridge,
+ struct drm_connector *connector)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ struct adv7511 *adv7511 = bridge_to_adv7511(bridge);
+ struct device *dev = &adv7511->i2c_main->dev;
+ unsigned int offset = adv7511->info->reg_cec_offset;
int ret = adv7511_cec_parse_dt(dev, adv7511);
if (ret)
goto err_cec_parse_dt;
- adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops,
- adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS);
- if (IS_ERR(adv7511->cec_adap)) {
- ret = PTR_ERR(adv7511->cec_adap);
- goto err_cec_alloc;
- }
+ adv7511->cec_connector = connector;
- regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, 0);
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, 0);
/* cec soft reset */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_SOFT_RESET + offset, 0x01);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_SOFT_RESET + offset, 0x00);
- /* legacy mode */
+ /* non-legacy mode - use all three RX buffers */
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
((adv7511->cec_clk_freq / 750000) - 1) << 2);
- ret = cec_register_adapter(adv7511->cec_adap, dev);
- if (ret)
- goto err_cec_register;
return 0;
-err_cec_register:
- cec_delete_adapter(adv7511->cec_adap);
- adv7511->cec_adap = NULL;
-err_cec_alloc:
- dev_info(dev, "Initializing CEC failed with error %d, disabling CEC\n",
- ret);
err_cec_parse_dt:
- regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset,
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL,
ADV7511_CEC_CTRL_POWER_DOWN);
return ret == -EPROBE_DEFER ? ret : 0;
}