int spi_clock_slowdown(void) { unsigned long clk = spi_clock; /* Slow SPI clock down by 1.5 */ clk = (clk * 2) / 3; if (clk < 25) clk = 25; #ifdef SPI_STATS spi_stats.slowdowns++; #endif LOG(INFO, "FTDI: SPI clock slowdown, set SPI clock to %lu", clk); return spi_set_clock(clk); }
//This is run in interrupt context and apart from initialization and destruction, this is the only code //touching the host (=spihost[x]) variable. The rest of the data arrives in queues. That is why there are //no muxes in this code. static void IRAM_ATTR spi_intr(void *arg) { int i; BaseType_t r; BaseType_t do_yield=pdFALSE; spi_trans_priv *trans_buf=NULL; spi_transaction_t *trans=NULL; spi_host_t *host=(spi_host_t*)arg; //Ignore all but the trans_done int. if (!host->hw->slave.trans_done) return; /*------------ deal with the in-flight transaction -----------------*/ if (host->cur_cs != NO_CS) { spi_transaction_t *cur_trans = host->cur_trans_buf.trans; //Okay, transaction is done. if (host->cur_trans_buf.buffer_to_rcv && host->dma_chan == 0 ) { //Need to copy from SPI regs to result buffer. for (int x=0; x < cur_trans->rxlength; x+=32) { //Do a memcpy to get around possible alignment issues in rx_buffer uint32_t word=host->hw->data_buf[x/32]; int len=cur_trans->rxlength-x; if (len>32) len=32; memcpy(&host->cur_trans_buf.buffer_to_rcv[x/32], &word, (len+7)/8); } } //Call post-transaction callback, if any if (host->device[host->cur_cs]->cfg.post_cb) host->device[host->cur_cs]->cfg.post_cb(cur_trans); //Return transaction descriptor. xQueueSendFromISR(host->device[host->cur_cs]->ret_queue, &host->cur_trans_buf, &do_yield); host->cur_cs = NO_CS; } //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset. if (host->dma_chan) spicommon_dmaworkaround_idle(host->dma_chan); /*------------ new transaction starts here ------------------*/ //ToDo: This is a stupidly simple low-cs-first priority scheme. Make this configurable somehow. - JD for (i=0; i<NO_CS; i++) { if (host->device[i]) { r=xQueueReceiveFromISR(host->device[i]->trans_queue, &host->cur_trans_buf, &do_yield); trans_buf = &host->cur_trans_buf; //Stop looking if we have a transaction to send. if (r) break; } } if (i==NO_CS) { //No packet waiting. Disable interrupt. esp_intr_disable(host->intr); #ifdef CONFIG_PM_ENABLE //Release APB frequency lock esp_pm_lock_release(host->pm_lock); #endif } else { host->hw->slave.trans_done=0; //clear int bit //We have a transaction. Send it. spi_device_t *dev=host->device[i]; trans = trans_buf->trans; host->cur_cs=i; //We should be done with the transmission. assert(host->hw->cmd.usr == 0); //Reconfigure according to device settings, but only if we change CSses. if (i!=host->prev_cs) { int apbclk=APB_CLK_FREQ; int effclk=dev->clk_cfg.eff_clk; spi_set_clock(host->hw, dev->clk_cfg.reg); //Configure bit order host->hw->ctrl.rd_bit_order=(dev->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST)?1:0; host->hw->ctrl.wr_bit_order=(dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)?1:0; //Configure polarity //SPI iface needs to be configured for a delay in some cases. int nodelay=0; int extra_dummy=0; if (host->no_gpio_matrix) { if (effclk >= apbclk/2) { nodelay=1; } } else { if (effclk >= apbclk/2) { nodelay=1; extra_dummy=1; //Note: This only works on half-duplex connections. spi_bus_add_device checks for this. } else if (effclk >= apbclk/4) { nodelay=1; } } if (dev->cfg.mode==0) { host->hw->pin.ck_idle_edge=0; host->hw->user.ck_out_edge=0; host->hw->ctrl2.miso_delay_mode=nodelay?0:2; } else if (dev->cfg.mode==1) { host->hw->pin.ck_idle_edge=0; host->hw->user.ck_out_edge=1; host->hw->ctrl2.miso_delay_mode=nodelay?0:1; } else if (dev->cfg.mode==2) { host->hw->pin.ck_idle_edge=1; host->hw->user.ck_out_edge=1; host->hw->ctrl2.miso_delay_mode=nodelay?0:1; } else if (dev->cfg.mode==3) { host->hw->pin.ck_idle_edge=1; host->hw->user.ck_out_edge=0; host->hw->ctrl2.miso_delay_mode=nodelay?0:2; } //configure dummy bits host->hw->user.usr_dummy=(dev->cfg.dummy_bits+extra_dummy)?1:0; host->hw->user1.usr_dummy_cyclelen=dev->cfg.dummy_bits+extra_dummy-1; //Configure misc stuff host->hw->user.doutdin=(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX)?0:1; host->hw->user.sio=(dev->cfg.flags & SPI_DEVICE_3WIRE)?1:0; host->hw->ctrl2.setup_time=dev->cfg.cs_ena_pretrans-1; host->hw->user.cs_setup=dev->cfg.cs_ena_pretrans?1:0; host->hw->ctrl2.hold_time=dev->cfg.cs_ena_posttrans-1; host->hw->user.cs_hold=(dev->cfg.cs_ena_posttrans)?1:0; //Configure CS pin host->hw->pin.cs0_dis=(i==0)?0:1; host->hw->pin.cs1_dis=(i==1)?0:1; host->hw->pin.cs2_dis=(i==2)?0:1; } host->prev_cs = i; //Reset SPI peripheral host->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; host->hw->dma_out_link.start=0; host->hw->dma_in_link.start=0; host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); host->hw->dma_conf.out_data_burst_en=1; //Set up QIO/DIO if needed host->hw->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO); host->hw->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO); if (trans->flags & SPI_TRANS_MODE_DIO) { if (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR) { host->hw->ctrl.fread_dio=1; host->hw->user.fwrite_dio=1; } else { host->hw->ctrl.fread_dual=1; host->hw->user.fwrite_dual=1; } host->hw->ctrl.fastrd_mode=1; } else if (trans->flags & SPI_TRANS_MODE_QIO) { if (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR) { host->hw->ctrl.fread_qio=1; host->hw->user.fwrite_qio=1; } else { host->hw->ctrl.fread_quad=1; host->hw->user.fwrite_quad=1; } host->hw->ctrl.fastrd_mode=1; } //Fill DMA descriptors if (trans_buf->buffer_to_rcv) { host->hw->user.usr_miso_highpart=0; if (host->dma_chan == 0) { //No need to setup anything; we'll copy the result out of the work registers directly later. } else { spicommon_dmaworkaround_transfer_active(host->dma_chan); //mark channel as active spicommon_setup_dma_desc_links(host->dmadesc_rx, ((trans->rxlength+7)/8), (uint8_t*)trans_buf->buffer_to_rcv, true); host->hw->dma_in_link.addr=(int)(&host->dmadesc_rx[0]) & 0xFFFFF; host->hw->dma_in_link.start=1; } } else { //DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon if (host->dma_chan != 0 ) { host->hw->dma_in_link.addr=0; host->hw->dma_in_link.start=1; } } if (trans_buf->buffer_to_send) { if (host->dma_chan == 0) { //Need to copy data to registers manually for (int x=0; x < trans->length; x+=32) { //Use memcpy to get around alignment issues for txdata uint32_t word; memcpy(&word, &trans_buf->buffer_to_send[x/32], 4); host->hw->data_buf[(x/32)+8]=word; } host->hw->user.usr_mosi_highpart=1; } else { spicommon_dmaworkaround_transfer_active(host->dma_chan); //mark channel as active spicommon_setup_dma_desc_links(host->dmadesc_tx, (trans->length+7)/8, (uint8_t*)trans_buf->buffer_to_send, false); host->hw->user.usr_mosi_highpart=0; host->hw->dma_out_link.addr=(int)(&host->dmadesc_tx[0]) & 0xFFFFF; host->hw->dma_out_link.start=1; host->hw->user.usr_mosi_highpart=0; } } host->hw->mosi_dlen.usr_mosi_dbitlen=trans->length-1; if ( dev->cfg.flags & SPI_DEVICE_HALFDUPLEX ) { host->hw->miso_dlen.usr_miso_dbitlen=trans->rxlength-1; } else { //rxlength is not used in full-duplex mode host->hw->miso_dlen.usr_miso_dbitlen=trans->length-1; } //Configure bit sizes, load addr and command int cmdlen; if ( trans->flags & SPI_TRANS_VARIABLE_CMD ) { cmdlen = ((spi_transaction_ext_t*)trans)->command_bits; } else { cmdlen = dev->cfg.command_bits; } int addrlen; if ( trans->flags & SPI_TRANS_VARIABLE_ADDR ) { addrlen = ((spi_transaction_ext_t*)trans)->address_bits; } else { addrlen = dev->cfg.address_bits; } host->hw->user1.usr_addr_bitlen=addrlen-1; host->hw->user2.usr_command_bitlen=cmdlen-1; host->hw->user.usr_addr=addrlen?1:0; host->hw->user.usr_command=cmdlen?1:0; // output command will be sent from bit 7 to 0 of command_value, and then bit 15 to 8 of the same register field. uint16_t command = trans->cmd << (16-cmdlen); //shift to MSB host->hw->user2.usr_command_value = (command>>8)|(command<<8); //swap the first and second byte // shift the address to MSB of addr (and maybe slv_wr_status) register. // output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register. if (addrlen>32) { host->hw->addr = trans->addr >> (addrlen- 32); host->hw->slv_wr_status = trans->addr << (64 - addrlen); } else {