/* Thing to emulate single-lane MIPI using a flipflop and a bunch of resistors. */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "esp_system.h" #include "driver/spi_common.h" #include "soc/gpio_struct.h" #include "soc/spi_struct.h" #include "soc/spi_reg.h" #include "driver/gpio.h" #include "esp_heap_alloc_caps.h" #include "mipi.h" #include "hexdump.h" //IO pins #define GPIO_D0N_LS 4 #define GPIO_D0P_LS 33 #define GPIO_D0_HS 32 #define GPIO_CLKP_LS 25 #define GPIO_CLKN_LS 27 #define GPIO_FF_NRST 14 #define GPIO_FF_CLK 12 #define GPIO_NRST 5 #define HOST VSPI_HOST #define IRQSRC ETS_SPI3_DMA_INTR_SOURCE #define DMACH 2 #define DESCCNT 8 #define SOTEOTWAIT() asm volatile("nop; nop; nop; nop") //#define SOTEOTWAIT() ets_delay_us(10); static spi_dev_t *spidev; static int cur_idle_desc=0; static lldesc_t idle_dmadesc[3]; static lldesc_t data_dmadesc[DESCCNT]; static SemaphoreHandle_t sem=NULL; //ToDo: move these to spi_common... static int spi_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); } /* * Set the SPI clock to a certain frequency. Returns the effective frequency set, which may be slightly * different from the requested frequency. */ static int spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { int pre, n, h, l, eff_clk; //In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value. if (hz>((fapb/4)*3)) { //Using Fapb directly will give us the best result here. hw->clock.clkcnt_l=0; hw->clock.clkcnt_h=0; hw->clock.clkcnt_n=0; hw->clock.clkdiv_pre=0; hw->clock.clk_equ_sysclk=1; eff_clk=fapb; } else { //For best duty cycle resolution, we want n to be as close to 32 as possible, but //we also need a pre/n combo that gets us as close as possible to the intended freq. //To do this, we bruteforce n and calculate the best pre to go along with that. //If there's a choice between pre/n combos that give the same result, use the one //with the higher n. int bestn=-1; int bestpre=-1; int besterr=0; int errval; for (n=2; n<=64; n++) { //Start at 2: we need to be able to set h/l so we have at least one high and one low pulse. //Effectively, this does pre=round((fapb/n)/hz). pre=((fapb/n)+(hz/2))/hz; if (pre<=0) pre=1; if (pre>8192) pre=8192; errval=abs(spi_freq_for_pre_n(fapb, pre, n)-hz); if (bestn==-1 || errval<=besterr) { besterr=errval; bestn=n; bestpre=pre; } } n=bestn; pre=bestpre; l=n; //This effectively does round((duty_cycle*n)/256) h=(duty_cycle*n+127)/256; if (h<=0) h=1; hw->clock.clk_equ_sysclk=0; hw->clock.clkcnt_n=n-1; hw->clock.clkdiv_pre=pre-1; hw->clock.clkcnt_h=h-1; hw->clock.clkcnt_l=l-1; eff_clk=spi_freq_for_pre_n(fapb, pre, n); } return eff_clk; } static void spidma_intr(void *arg) { BaseType_t xHigherPriorityTaskWoken=0; spidev->dma_int_clr.val=0xFFFFFFFF; //clear all ints //Data is sent // ets_printf("int\n"); xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } /* Brings up the clock and data lines to LP11, resyncs the flipflop, restarts the clock and DMA engine. */ void mipiResync() { //Get clock and data transceivers back in idle state gpio_set_level(GPIO_CLKN_LS, 1); SOTEOTWAIT(); gpio_set_level(GPIO_CLKP_LS, 1); //Stop DMA transfer spidev->dma_conf.dma_tx_stop=1; while (spidev->ext2.val!=0) ; //Clear flipflop gpio_set_level(GPIO_FF_NRST, 0); ets_delay_us(1); gpio_set_level(GPIO_FF_NRST, 1); //Clock is in LP11 now. We should go LP01, LP00 to enable HS receivers gpio_set_level(GPIO_CLKP_LS, 0); SOTEOTWAIT(); gpio_set_level(GPIO_CLKN_LS, 0); cur_idle_desc=0; idle_dmadesc[0].qe.stqe_next=&idle_dmadesc[0]; //Set SPI to transfer contents of idle dmadesc spidev->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; spidev->dma_out_link.start=0; spidev->dma_in_link.start=0; spidev->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); spidev->dma_conf.dma_tx_stop=0; spidev->dma_conf.dma_continue=1; spidev->dma_conf.out_data_burst_en=1; spidev->user.usr_mosi_highpart=0; spidev->dma_out_link.addr=(int)(&idle_dmadesc[0]) & 0xFFFFF; spidev->dma_out_link.start=1; spidev->user.usr_mosi=1; /* HACK for inverted clock */ // spidev->user.usr_addr=1; // spidev->user1.usr_addr_bitlen=0; //1 addr bit /* End hack */ spidev->cmd.usr=1; } void mipiInit() { esp_err_t ret; bool io_native=false; spi_bus_config_t buscfg={ .miso_io_num=-1, .mosi_io_num=GPIO_D0_HS, .sclk_io_num=GPIO_FF_CLK, .quadwp_io_num=-1, .quadhd_io_num=-1, .max_transfer_sz=4096*3 }; gpio_config_t io_conf={ .intr_type=GPIO_INTR_DISABLE, .mode=GPIO_MODE_OUTPUT, .pin_bit_mask=(1<dma_conf.val|=SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; spidev->dma_out_link.start=0; spidev->dma_in_link.start=0; spidev->dma_conf.val&=~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); //Reset timing spidev->ctrl2.val=0; spi_set_clock(spidev, 80000000, 40000000, 128); //Configure SPI host spidev->ctrl.rd_bit_order=1; //LSB first spidev->ctrl.wr_bit_order=1; spidev->pin.ck_idle_edge=0; spidev->user.ck_out_edge=1; //0 for <40MHz? spidev->ctrl2.miso_delay_mode=0; spidev->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO); spidev->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO); //Disable unneeded ints spidev->slave.val=0; spidev->dma_int_ena.val=0; //Set int on EOF spidev->dma_int_clr.val=0xFFFFFFFF; //clear all ints spidev->dma_int_ena.out_eof=1; // spidev->dma_int_ena.in_suc_eof=1; //Init GPIO to MIPI idle levels gpio_set_level(GPIO_D0N_LS, 1); gpio_set_level(GPIO_D0P_LS, 1); gpio_set_level(GPIO_CLKN_LS, 1); gpio_set_level(GPIO_CLKP_LS, 1); //Reset display gpio_set_level(GPIO_NRST, 0); ets_delay_us(200); gpio_set_level(GPIO_NRST, 1); //Wait till display lives vTaskDelay(100/portTICK_RATE_MS); sem=xSemaphoreCreateBinary(); // xSemaphoreGive(sem); mipiResync(0); } void mipiSendMultiple(uint8_t **data, int *lengths, int count) { esp_err_t ret; if (count==0) return; //no need to send anything assert(data[0][0]==0xB8); // hexdump(data, count); //Set up link to new transfer int next_idle_desc=(cur_idle_desc==0)?1:0; int last=-1; for (int i=0; i