root/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
// 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.
 */

#include <linux/device.h>
#include <linux/module.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_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->info->reg_cec_offset;
        unsigned int val;

        if (regmap_read(adv7511->regmap_cec,
                        ADV7511_REG_CEC_TX_ENABLE + offset, &val))
                return;

        if ((val & 0x01) == 0)
                return;

        if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_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) {
                u8 status;
                u8 err_cnt = 0;
                u8 nack_cnt = 0;
                u8 low_drive_cnt = 0;
                unsigned int cnt;

                /*
                 * We set this status bit since this hardware performs
                 * retransmissions.
                 */
                status = CEC_TX_STATUS_MAX_RETRIES;
                if (regmap_read(adv7511->regmap_cec,
                            ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) {
                        err_cnt = 1;
                        status |= CEC_TX_STATUS_ERROR;
                } else {
                        nack_cnt = cnt & 0xf;
                        if (nack_cnt)
                                status |= CEC_TX_STATUS_NACK;
                        low_drive_cnt = cnt >> 4;
                        if (low_drive_cnt)
                                status |= CEC_TX_STATUS_LOW_DRIVE;
                }
                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) {
                drm_connector_hdmi_cec_transmit_attempt_done(adv7511->cec_connector,
                                                             CEC_TX_STATUS_OK);
                return;
        }
}

static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf)
{
        unsigned int offset = adv7511->info->reg_cec_offset;
        struct cec_msg msg = {};
        unsigned int len;
        unsigned int val;
        u8 i;

        if (regmap_read(adv7511->regmap_cec,
                        ADV7511_REG_CEC_RX_FRAME_LEN[rx_buf] + offset, &len))
                return;

        msg.len = len & 0x1f;

        if (msg.len > 16)
                msg.len = 16;

        if (!msg.len)
                return;

        for (i = 0; i < msg.len; i++) {
                regmap_read(adv7511->regmap_cec,
                            i + ADV7511_REG_CEC_RX_FRAME_HDR[rx_buf] + offset,
                            &val);
                msg.msg[i] = val;
        }

        /* 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);
}

int adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
{
        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;

        if (!adv7511->cec_enabled_adap && enable) {
                /* power up cec section */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_CLK_DIV + offset,
                                   0x03, 0x01);
                /* non-legacy mode and clear all rx buffers */
                regmap_write(adv7511->regmap_cec,
                             ADV7511_REG_CEC_RX_BUFFERS + offset, 0x0f);
                regmap_write(adv7511->regmap_cec,
                             ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
                /* initially disable tx */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
                /* enabled irqs: */
                /* tx: ready */
                /* tx: arbitration lost */
                /* tx: retry timeout */
                /* rx: ready 1-3 */
                regmap_update_bits(adv7511->regmap,
                                   ADV7511_REG_INT_ENABLE(1), 0x3f,
                                   ADV7511_INT1_CEC_MASK);
        } else if (adv7511->cec_enabled_adap && !enable) {
                regmap_update_bits(adv7511->regmap,
                                   ADV7511_REG_INT_ENABLE(1), 0x3f, 0);
                /* disable address mask 1-3 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
                                   0x70, 0x00);
                /* power down cec section */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_CLK_DIV + offset,
                                   0x03, 0x00);
                adv7511->cec_valid_addrs = 0;
        }
        adv7511->cec_enabled_adap = enable;
        return 0;
}

int adv7511_cec_log_addr(struct drm_bridge *bridge, u8 addr)
{
        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)
                return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;

        if (addr == CEC_LOG_ADDR_INVALID) {
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
                                   0x70, 0);
                adv7511->cec_valid_addrs = 0;
                return 0;
        }

        for (i = 0; i < ADV7511_MAX_ADDRS; i++) {
                bool is_valid = adv7511->cec_valid_addrs & (1 << i);

                if (free_idx == ADV7511_MAX_ADDRS && !is_valid)
                        free_idx = i;
                if (is_valid && adv7511->cec_addr[i] == addr)
                        return 0;
        }
        if (i == ADV7511_MAX_ADDRS) {
                i = free_idx;
                if (i == ADV7511_MAX_ADDRS)
                        return -ENXIO;
        }
        adv7511->cec_addr[i] = addr;
        adv7511->cec_valid_addrs |= 1 << i;

        switch (i) {
        case 0:
                /* enable address mask 0 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
                                   0x10, 0x10);
                /* set address for mask 0 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
                                   0x0f, addr);
                break;
        case 1:
                /* enable address mask 1 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
                                   0x20, 0x20);
                /* set address for mask 1 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
                                   0xf0, addr << 4);
                break;
        case 2:
                /* enable address mask 2 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
                                   0x40, 0x40);
                /* set address for mask 1 */
                regmap_update_bits(adv7511->regmap_cec,
                                   ADV7511_REG_CEC_LOG_ADDR_2 + offset,
                                   0x0f, addr);
                break;
        }
        return 0;
}

int adv7511_cec_transmit(struct drm_bridge *bridge, u8 attempts,
                         u32 signal_free_time, struct cec_msg *msg)
{
        struct adv7511 *adv7511 = bridge_to_adv7511(bridge);
        unsigned int offset = adv7511->info->reg_cec_offset;
        u8 len = msg->len;
        unsigned int i;

        /*
         * The number of retries is the number of attempts - 1, but retry
         * at least once. It's not clear if a value of 0 is allowed, so
         * let's do at least one retry.
         */
        regmap_update_bits(adv7511->regmap_cec,
                           ADV7511_REG_CEC_TX_RETRY + offset,
                           0x70, max(1, attempts - 1) << 4);

        /* blocking, clear cec tx irq status */
        regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38);

        /* write data */
        for (i = 0; i < len; i++)
                regmap_write(adv7511->regmap_cec,
                             i + ADV7511_REG_CEC_TX_FRAME_HDR + offset,
                             msg->msg[i]);

        /* set length (data + header) */
        regmap_write(adv7511->regmap_cec,
                     ADV7511_REG_CEC_TX_FRAME_LEN + offset, len);
        /* start transmit, enable tx */
        regmap_write(adv7511->regmap_cec,
                     ADV7511_REG_CEC_TX_ENABLE + offset, 0x01);
        return 0;
}

static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
{
        adv7511->cec_clk = devm_clk_get(dev, "cec");
        if (IS_ERR(adv7511->cec_clk)) {
                int ret = PTR_ERR(adv7511->cec_clk);

                adv7511->cec_clk = NULL;
                return ret;
        }
        clk_prepare_enable(adv7511->cec_clk);
        adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk);
        return 0;
}

int adv7511_cec_init(struct drm_bridge *bridge,
                     struct drm_connector *connector)
{
        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_connector = connector;

        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);

        /* non-legacy mode - use all three RX buffers */
        regmap_write(adv7511->regmap_cec,
                     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);

        return 0;

err_cec_parse_dt:
        regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL,
                     ADV7511_CEC_CTRL_POWER_DOWN);
        return ret == -EPROBE_DEFER ? ret : 0;
}