static void tegra_cec_error_recovery(struct tegra_cec *cec) { u32 hw_ctrl; hw_ctrl = cec_read(cec, TEGRA_CEC_HW_CONTROL); cec_write(cec, TEGRA_CEC_HW_CONTROL, 0); cec_write(cec, TEGRA_CEC_INT_STAT, 0xffffffff); cec_write(cec, TEGRA_CEC_HW_CONTROL, hw_ctrl); }
static int tegra_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, u32 signal_free_time_ms, struct cec_msg *msg) { bool retry_xfer = signal_free_time_ms == CEC_SIGNAL_FREE_TIME_RETRY; struct tegra_cec *cec = adap->priv; unsigned int i; u32 mode = 0; u32 mask; if (cec_msg_is_broadcast(msg)) mode = TEGRA_CEC_TX_REG_BCAST; cec->tx_buf_cur = 0; cec->tx_buf_cnt = msg->len; for (i = 0; i < msg->len; i++) { cec->tx_buf[i] = mode | msg->msg[i]; if (i == 0) cec->tx_buf[i] |= TEGRA_CEC_TX_REG_START_BIT; if (i == msg->len - 1) cec->tx_buf[i] |= TEGRA_CEC_TX_REG_EOM; if (i == 0 && retry_xfer) cec->tx_buf[i] |= TEGRA_CEC_TX_REG_RETRY; } mask = cec_read(cec, TEGRA_CEC_INT_MASK); cec_write(cec, TEGRA_CEC_INT_MASK, mask | TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY); return 0; }
static void tda998x_destroy(struct tda998x_priv *priv) { /* disable all IRQs and free the IRQ handler */ cec_write(priv, REG_CEC_RXSHPDINTENA, 0); reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); if (priv->hdmi->irq) { free_irq(priv->hdmi->irq, priv); cancel_delayed_work_sync(&priv->dwork); } i2c_unregister_device(priv->cec); }
static int tegra_cec_adap_monitor_all_enable(struct cec_adapter *adap, bool enable) { struct tegra_cec *cec = adap->priv; u32 reg = cec_read(cec, TEGRA_CEC_HW_CONTROL); if (enable) reg |= TEGRA_CEC_HWCTRL_RX_SNOOP; else reg &= ~TEGRA_CEC_HWCTRL_RX_SNOOP; cec_write(cec, TEGRA_CEC_HW_CONTROL, reg); return 0; }
static int tegra_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) { struct tegra_cec *cec = adap->priv; u32 state = cec_read(cec, TEGRA_CEC_HW_CONTROL); if (logical_addr == CEC_LOG_ADDR_INVALID) state &= ~TEGRA_CEC_HWCTRL_RX_LADDR_MASK; else state |= TEGRA_CEC_HWCTRL_RX_LADDR((1 << logical_addr)); cec_write(cec, TEGRA_CEC_HW_CONTROL, state); return 0; }
static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) { struct device_node *np = client->dev.of_node; u32 video; int rev_lo, rev_hi, ret; unsigned short cec_addr; priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3); priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1); priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5); priv->current_page = 0xff; priv->hdmi = client; /* CEC I2C address bound to TDA998x I2C addr by configuration pins */ cec_addr = 0x34 + (client->addr & 0x03); priv->cec = i2c_new_dummy(client->adapter, cec_addr); if (!priv->cec) return -ENODEV; priv->dpms = DRM_MODE_DPMS_OFF; mutex_init(&priv->mutex); /* protect the page access */ init_waitqueue_head(&priv->edid_delay_waitq); setup_timer(&priv->edid_delay_timer, tda998x_edid_delay_done, (unsigned long)priv); INIT_WORK(&priv->detect_work, tda998x_detect_work); /* wake up the device: */ cec_write(priv, REG_CEC_ENAMODS, CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); tda998x_reset(priv); /* read version: */ rev_lo = reg_read(priv, REG_VERSION_LSB); rev_hi = reg_read(priv, REG_VERSION_MSB); if (rev_lo < 0 || rev_hi < 0) { ret = rev_lo < 0 ? rev_lo : rev_hi; goto fail; } priv->rev = rev_lo | rev_hi << 8; /* mask off feature bits: */ priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */ switch (priv->rev) { case TDA9989N2: dev_info(&client->dev, "found TDA9989 n2"); break; case TDA19989: dev_info(&client->dev, "found TDA19989"); break; case TDA19989N2: dev_info(&client->dev, "found TDA19989 n2"); break; case TDA19988: dev_info(&client->dev, "found TDA19988"); break; default: dev_err(&client->dev, "found unsupported device: %04x\n", priv->rev); goto fail; } /* after reset, enable DDC: */ reg_write(priv, REG_DDC_DISABLE, 0x00); /* set clock on DDC channel: */ reg_write(priv, REG_TX3, 39); /* if necessary, disable multi-master: */ if (priv->rev == TDA19989) reg_set(priv, REG_I2C_MASTER, I2C_MASTER_DIS_MM); cec_write(priv, REG_CEC_FRO_IM_CLK_CTRL, CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); /* initialize the optional IRQ */ if (client->irq) { int irqf_trigger; /* init read EDID waitqueue and HDP work */ init_waitqueue_head(&priv->wq_edid); /* clear pending interrupts */ reg_read(priv, REG_INT_FLAGS_0); reg_read(priv, REG_INT_FLAGS_1); reg_read(priv, REG_INT_FLAGS_2); irqf_trigger = irqd_get_trigger_type(irq_get_irq_data(client->irq)); ret = request_threaded_irq(client->irq, NULL, tda998x_irq_thread, irqf_trigger | IRQF_ONESHOT, "tda998x", priv); if (ret) { dev_err(&client->dev, "failed to request IRQ#%u: %d\n", client->irq, ret); goto fail; } /* enable HPD irq */ cec_write(priv, REG_CEC_RXSHPDINTENA, CEC_RXSHPDLEV_HPD); } /* enable EDID read irq: */ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); if (!np) return 0; /* non-DT */ /* get the optional video properties */ ret = of_property_read_u32(np, "video-ports", &video); if (ret == 0) { priv->vip_cntrl_0 = video >> 16; priv->vip_cntrl_1 = video >> 8; priv->vip_cntrl_2 = video; }
static int tegra_cec_adap_enable(struct cec_adapter *adap, bool enable) { struct tegra_cec *cec = adap->priv; cec->rx_buf_cnt = 0; cec->tx_buf_cnt = 0; cec->tx_buf_cur = 0; cec_write(cec, TEGRA_CEC_HW_CONTROL, 0); cec_write(cec, TEGRA_CEC_INT_MASK, 0); cec_write(cec, TEGRA_CEC_INT_STAT, 0xffffffff); cec_write(cec, TEGRA_CEC_SW_CONTROL, 0); if (!enable) return 0; cec_write(cec, TEGRA_CEC_INPUT_FILTER, (1U << 31) | 0x20); cec_write(cec, TEGRA_CEC_RX_TIMING_0, (0x7a << TEGRA_CEC_RX_TIM0_START_BIT_MAX_LO_TIME_SHIFT) | (0x6d << TEGRA_CEC_RX_TIM0_START_BIT_MIN_LO_TIME_SHIFT) | (0x93 << TEGRA_CEC_RX_TIM0_START_BIT_MAX_DURATION_SHIFT) | (0x86 << TEGRA_CEC_RX_TIM0_START_BIT_MIN_DURATION_SHIFT)); cec_write(cec, TEGRA_CEC_RX_TIMING_1, (0x35 << TEGRA_CEC_RX_TIM1_DATA_BIT_MAX_LO_TIME_SHIFT) | (0x21 << TEGRA_CEC_RX_TIM1_DATA_BIT_SAMPLE_TIME_SHIFT) | (0x56 << TEGRA_CEC_RX_TIM1_DATA_BIT_MAX_DURATION_SHIFT) | (0x40 << TEGRA_CEC_RX_TIM1_DATA_BIT_MIN_DURATION_SHIFT)); cec_write(cec, TEGRA_CEC_RX_TIMING_2, (0x50 << TEGRA_CEC_RX_TIM2_END_OF_BLOCK_TIME_SHIFT)); cec_write(cec, TEGRA_CEC_TX_TIMING_0, (0x74 << TEGRA_CEC_TX_TIM0_START_BIT_LO_TIME_SHIFT) | (0x8d << TEGRA_CEC_TX_TIM0_START_BIT_DURATION_SHIFT) | (0x08 << TEGRA_CEC_TX_TIM0_BUS_XITION_TIME_SHIFT) | (0x71 << TEGRA_CEC_TX_TIM0_BUS_ERROR_LO_TIME_SHIFT)); cec_write(cec, TEGRA_CEC_TX_TIMING_1, (0x2f << TEGRA_CEC_TX_TIM1_LO_DATA_BIT_LO_TIME_SHIFT) | (0x13 << TEGRA_CEC_TX_TIM1_HI_DATA_BIT_LO_TIME_SHIFT) | (0x4b << TEGRA_CEC_TX_TIM1_DATA_BIT_DURATION_SHIFT) | (0x21 << TEGRA_CEC_TX_TIM1_ACK_NAK_BIT_SAMPLE_TIME_SHIFT)); cec_write(cec, TEGRA_CEC_TX_TIMING_2, (0x07 << TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_ADDITIONAL_FRAME_SHIFT) | (0x05 << TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_NEW_FRAME_SHIFT) | (0x03 << TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_RETRY_FRAME_SHIFT)); cec_write(cec, TEGRA_CEC_INT_MASK, TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN | TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD | TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED | TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED | TEGRA_CEC_INT_MASK_TX_FRAME_TRANSMITTED | TEGRA_CEC_INT_MASK_RX_REGISTER_FULL | TEGRA_CEC_INT_MASK_RX_REGISTER_OVERRUN); cec_write(cec, TEGRA_CEC_HW_CONTROL, TEGRA_CEC_HWCTRL_TX_RX_MODE); return 0; }
static irqreturn_t tegra_cec_irq_handler(int irq, void *data) { struct device *dev = data; struct tegra_cec *cec = dev_get_drvdata(dev); u32 status, mask; status = cec_read(cec, TEGRA_CEC_INT_STAT); mask = cec_read(cec, TEGRA_CEC_INT_MASK); status &= mask; if (!status) return IRQ_HANDLED; if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN) { dev_err(dev, "TX underrun, interrupt timing issue!\n"); tegra_cec_error_recovery(cec); cec_write(cec, TEGRA_CEC_INT_MASK, mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY); cec->tx_done = true; cec->tx_status = CEC_TX_STATUS_ERROR; return IRQ_WAKE_THREAD; } if ((status & TEGRA_CEC_INT_STAT_TX_ARBITRATION_FAILED) || (status & TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED)) { tegra_cec_error_recovery(cec); cec_write(cec, TEGRA_CEC_INT_MASK, mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY); cec->tx_done = true; if (status & TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED) cec->tx_status = CEC_TX_STATUS_LOW_DRIVE; else cec->tx_status = CEC_TX_STATUS_ARB_LOST; return IRQ_WAKE_THREAD; } if (status & TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED) { cec_write(cec, TEGRA_CEC_INT_STAT, TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED); if (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD) { tegra_cec_error_recovery(cec); cec->tx_done = true; cec->tx_status = CEC_TX_STATUS_NACK; } else { cec->tx_done = true; cec->tx_status = CEC_TX_STATUS_OK; } return IRQ_WAKE_THREAD; } if (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD) dev_warn(dev, "TX NAKed on the fly!\n"); if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY) { if (cec->tx_buf_cur == cec->tx_buf_cnt) { cec_write(cec, TEGRA_CEC_INT_MASK, mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY); } else { cec_write(cec, TEGRA_CEC_TX_REGISTER, cec->tx_buf[cec->tx_buf_cur++]); cec_write(cec, TEGRA_CEC_INT_STAT, TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY); } } if (status & (TEGRA_CEC_INT_STAT_RX_REGISTER_OVERRUN | TEGRA_CEC_INT_STAT_RX_BUS_ANOMALY_DETECTED | TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED | TEGRA_CEC_INT_STAT_RX_BUS_ERROR_DETECTED)) { cec_write(cec, TEGRA_CEC_INT_STAT, (TEGRA_CEC_INT_STAT_RX_REGISTER_OVERRUN | TEGRA_CEC_INT_STAT_RX_BUS_ANOMALY_DETECTED | TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED | TEGRA_CEC_INT_STAT_RX_BUS_ERROR_DETECTED)); } else if (status & TEGRA_CEC_INT_STAT_RX_REGISTER_FULL) { u32 v; cec_write(cec, TEGRA_CEC_INT_STAT, TEGRA_CEC_INT_STAT_RX_REGISTER_FULL); v = cec_read(cec, TEGRA_CEC_RX_REGISTER); if (cec->rx_buf_cnt < CEC_MAX_MSG_SIZE) cec->rx_buf[cec->rx_buf_cnt++] = v & 0xff; if (v & TEGRA_CEC_RX_REGISTER_EOM) { cec->rx_done = true; return IRQ_WAKE_THREAD; } } return IRQ_HANDLED; }