// 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 #include #include #include #include #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) { cec_transmit_attempt_done(adv7511->cec_adap, 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; } cec_transmit_done(adv7511->cec_adap, 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); 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); cec_received_msg(adv7511->cec_adap, &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; } static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) { struct adv7511 *adv7511 = cec_get_drvdata(adap); 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; } static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) { struct adv7511 *adv7511 = cec_get_drvdata(adap); 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; } static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, u32 signal_free_time, struct cec_msg *msg) { struct adv7511 *adv7511 = cec_get_drvdata(adap); 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 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"); 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 device *dev, struct adv7511 *adv7511) { 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; } 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); 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, ADV7511_CEC_CTRL_POWER_DOWN); return ret == -EPROBE_DEFER ? ret : 0; }