/** * @brief See ENG-376. * * We use a mailbox notification from the SVC to solve a race condition * involving FCT transmission. When the SVC makes a connection, it sets all the * relevant DME parameters as defined in MIPI UniPro 1.6, then pokes the * bridge mailbox, telling it that it is safe to send FCTs on a given CPort. */ static int mailbox_evt(void) { uint32_t cportid; int rc; uint32_t e2efc; uint32_t val; DBG_UNIPRO("mailbox interrupt received\n"); /* * Figure out which CPort to turn on FCT. The desired CPort is always * the mailbox value - 1. */ rc = unipro_attr_local_read(TSB_MAILBOX, &cportid, 0); if (rc) { return rc; } if (cportid >= cport_count) { DBG_UNIPRO("cportid %d in mailbox exceeds count of cports %d\n", cportid, cport_count); return -EINVAL; } cportid--; rc = unipro_attr_local_read(T_CPORTFLAGS, &val, cportid); if (rc) { return rc; } if (val & CPORT_FLAGS_E2EFC) { DBG_UNIPRO("Enabling E2EFC on cport %u\n", cportid); if (cportid < 32) { e2efc = unipro_read(CPB_RX_E2EFC_EN_0); e2efc |= (1 << cportid); unipro_write(CPB_RX_E2EFC_EN_0, e2efc); } else if (cportid < 64) { e2efc = unipro_read(CPB_RX_E2EFC_EN_1); e2efc |= (1 << (cportid - 32)); unipro_write(CPB_RX_E2EFC_EN_1, e2efc); } } configure_connected_cport(cportid); /* Acknowledge the mailbox write */ rc = tsb_unipro_mbox_ack(cportid + 1); if (rc) { return rc; } return 0; }
static void unipro_evt_handler(enum unipro_event evt) { int retval; DBG_UNIPRO("UniPro: event %d.\n", evt); switch (evt) { case UNIPRO_EVT_MAILBOX: mailbox_evt(); break; case UNIPRO_EVT_LUP_DONE: if (tsb_get_rev_id() == tsb_rev_es2) es2_apply_mphy_fixup(); retval = unipro_enable_mailbox_irq(); if (retval) { lowsyslog("unipro: failed to enable mailbox irq\n"); } break; } if (evt_handler) { evt_handler(evt); } }
/** * @brief Send data down to a CPort * @return number of bytes effectively sent (>= 0), or error code (< 0) * @param[in] cportid: cport to send down * @param[in] buf: data buffer * @param[in] len: size of data to send * @param[in] som: "start of message" flag */ static int unipro_send_sync(unsigned int cportid, const void *buf, size_t len, bool som) { struct cport *cport; uint16_t count; uint8_t *tx_buf; if (len > CPORT_BUF_SIZE) { return -EINVAL; } cport = cport_handle(cportid); if (!cport) { return -EINVAL; } if (!cport->connected) { lldbg("CP%d unconnected\n", cport->cportid); return -EPIPE; } DEBUGASSERT(TRANSFER_MODE == 2); /* * If this is not the start of a new message, * message data must be written to first address of CPort Tx Buffer + 1. */ if (!som) { tx_buf = cport->tx_buf + sizeof(uint32_t); } else { tx_buf = cport->tx_buf; } count = unipro_get_tx_free_buffer_space(cport); if (!count) { /* No free space in TX FIFO, cannot send anything. */ DBG_UNIPRO("No free space in CP%d Tx Buffer\n", cportid); return 0; } else if (count > len) { count = len; } /* Copy message data in CPort Tx FIFO */ DBG_UNIPRO("Sending %u bytes to CP%d\n", count, cportid); memcpy(tx_buf, buf, count); return (int) count; }
static int unipro_dma_xfer(struct unipro_xfer_descriptor *desc, struct dma_channel *channel) { int retval; size_t xfer_len; void *cport_buf; void *xfer_buf; xfer_len = unipro_get_tx_free_buffer_space(desc->cport); if (!xfer_len) return -ENOSPC; xfer_len = MIN(desc->remaining_xfer_len, xfer_len); desc->channel = channel; desc->dma_arg.unipro_tx_arg.next_transfer_len = xfer_len; if (xfer_len == desc->remaining_xfer_len) desc->dma_arg.unipro_tx_arg.eom_addr = CPORT_EOM_BIT(desc->cport); DBG_UNIPRO("xfer: chan=%u, len=%zu\n", channel->id, xfer_len); cport_buf = desc->cport->tx_buf; xfer_buf = (void*) desc->data; /* resuming a paused xfer */ if (desc->remaining_xfer_len != desc->len) { cport_buf = (char*) cport_buf + 1; /* skip the start byte */ /* move buffer offset to the beginning of the remaning bytes to xfer */ xfer_buf = (char*) xfer_buf + (desc->len - desc->remaining_xfer_len); } retval = device_dma_transfer(unipro_dma.dev, channel->id, xfer_buf, cport_buf, xfer_len, &desc->dma_arg); if (retval) { lowsyslog("unipro: failed to start DMA transfer: %d\n", retval); return retval; } return 0; }
/** * @brief RX EOM interrupt handler * @param irq irq number * @param context register context (unused) */ static int irq_rx_eom(int irq, void *context) { struct cport *cport = irqn_to_cport(irq); void *data = cport->rx_buf; uint32_t transferred_size; (void)context; void *newbuf; clear_rx_interrupt(cport); if (!cport->driver) { lldbg("dropping message on cport %hu where no driver is registered\n", cport->cportid); return -ENODEV; } transferred_size = unipro_read(CPB_RX_TRANSFERRED_DATA_SIZE_00 + (cport->cportid * sizeof(uint32_t))); DBG_UNIPRO("cport: %u driver: %s size=%u payload=0x%x\n", cport->cportid, cport->driver->name, transferred_size, data); if (cport->driver->rx_handler) { newbuf = unipro_rxbuf_alloc(cport->cportid); if (newbuf) { unipro_switch_rxbuf(cport->cportid, newbuf); unipro_unpause_rx(cport->cportid); } else { cport->switch_buf_on_free = true; } cport->driver->rx_handler(cport->cportid, data, (size_t)transferred_size); } return 0; }
static int unipro_dma_xfer(struct unipro_xfer_descriptor *desc, struct dma_channel *channel) { int retval; size_t xfer_len; void *cport_buf; void *xfer_buf; struct device_dma_op *dma_op = NULL; if (tsb_get_rev_id() == tsb_rev_es2) { xfer_len = unipro_get_tx_free_buffer_space(desc->cport); if (!xfer_len) return -ENOSPC; xfer_len = MIN(desc->len - desc->data_offset, xfer_len); } else { DEBUGASSERT(desc->data_offset == 0); xfer_len = desc->len; } desc->channel = channel; retval = device_dma_op_alloc(unipro_dma.dev, 1, 0, &dma_op); if (retval != OK) { lowsyslog("unipro: failed allocate a DMA op, retval = %d.\n", retval); return retval; } dma_op->callback = (void *) unipro_dma_tx_callback; dma_op->callback_arg = desc; dma_op->callback_events = DEVICE_DMA_CALLBACK_EVENT_COMPLETE; if (tsb_get_rev_id() != tsb_rev_es2) { dma_op->callback_events |= DEVICE_DMA_CALLBACK_EVENT_START; } dma_op->sg_count = 1; dma_op->sg[0].len = xfer_len; DBG_UNIPRO("xfer: chan=%u, len=%zu\n", channel->id, xfer_len); cport_buf = desc->cport->tx_buf; xfer_buf = (void*) desc->data; /* resuming a paused xfer */ if (desc->data_offset != 0) { cport_buf = (char*) cport_buf + sizeof(uint64_t); /* skip the first DWORD */ /* move buffer offset to the beginning of the remaning bytes to xfer */ xfer_buf = (char*) xfer_buf + desc->data_offset; } dma_op->sg[0].src_addr = (off_t) xfer_buf; dma_op->sg[0].dst_addr = (off_t) cport_buf; desc->data_offset += xfer_len; retval = device_dma_enqueue(unipro_dma.dev, channel->chan, dma_op); if (retval) { lowsyslog("unipro: failed to start DMA transfer: %d\n", retval); return retval; } return 0; }