arne48 / tinkerboard_io

Yet another library to use the 40pin header of the Asus Tinker Board.
GNU General Public License v3.0
12 stars 1 forks source link

Tinker Board as SPI slave #1

Open MackoLysy opened 6 years ago

MackoLysy commented 6 years ago

Is a possible to set Tinker Board as slave via your library?

arne48 commented 6 years ago

Thanks for that request, indeed does the library not include slave support yet. The controller of the RK3288 anyway does support operation in slave mode. Please feel free to add this feature a make a PR. You can find some documentation directly in the wiki of Rockchip. And the Firefly people also host a lot of reference documents.

MackoLysy commented 6 years ago

I can do that but can You give some adivce? From datasheet http://www.t-firefly.com/download/firefly-rk3288/docs/TRM/rk3288-chapter-42-serial-peripheral-interface-(spi).pdf I read that i need to change SPI_CTRLR0 bit 20 from 0x00 to 0x01 but how to configurate rest?? Like on Slave Mode i dont need to configurate cloack

MackoLysy commented 6 years ago

Well. After hours of reading datasheet I make conclustion. I have to make something like this

void tinkerboard_spi_slave_init(enum SPIController controller, struct spi_mode_config_t mode_config) {

  int32_t pin = _spi_configs[controller].clk;
  _set_config(_rk3288_gpio_block_base + ALIGN(_gpio_header_pins[pin].grf_bank_offset),
              _gpio_header_pins[pin].grf_pin_offset, 1, _gpio_header_pins[pin].grf_config_size);
  _gpio_header_pins[pin].mode = SPI;
  printf("Pin %d", &pin);
  pin = _spi_configs[controller].txd;
  _set_config(_rk3288_gpio_block_base + ALIGN(_gpio_header_pins[pin].grf_bank_offset),
              _gpio_header_pins[pin].grf_pin_offset, 1, _gpio_header_pins[pin].grf_config_size);
  _gpio_header_pins[pin].mode = SPI;
  printf("Pin %d", &pin);
  pin = _spi_configs[controller].rxd;
  _set_config(_rk3288_gpio_block_base + ALIGN(_gpio_header_pins[pin].grf_bank_offset),
              _gpio_header_pins[pin].grf_pin_offset, 1, _gpio_header_pins[pin].grf_config_size);
  _gpio_header_pins[pin].mode = SPI;
  printf("Pin %d", &pin);
  _spi_internals[controller].cs_pin = mode_config.slave_select;
  tinkerboard_set_gpio_mode(_spi_internals[controller].cs_pin, INPUT);
  tinkerboard_set_gpio_pud(_spi_internals[controller].cs_pin, PULLUP);
  uint32_t config = 0;
  config |= 1 << 20;
  config |= mode_config.data_frame_size << CR0_DFS_OFFSET;
  config |= mode_config.clk_mode << CR0_SCPH_OFFSET;
  config |= mode_config.byte_order << CR0_FBM_OFFSET;
  config |= 1 << CR0_BHT_OFFSET;
  config |= mode_config.transfer_mode << CR0_XFM_OFFSET;

   _spi_set_ctrlr0(controller, config);
  _spi_set_fifo_size(controller , 31);

  _spi_enable_controller(controller, 1);
  _spi_internals[controller].fifo_len = 31;
  _spi_configs[controller].initialized = 1;
}

This is my init function. I use build in transfer function. I dont know if my clock function is set for slave. Can You plase help me a bit?

arne48 commented 6 years ago

Great that you already had a look into it. Unfortunately I am pretty busy at the moment so I can't help you by experimenting myself with the RK3288 SPI controller. But I can give you my idea how it should work from my expectation.

  1. The chip select can't be done using a GPIO. Depending on what your program is doing while not receiving any data it is likely that it takes to long once the CS line is LOW to enable the SPI. Therefore, for slave mode I would just use either of the hardware CS pins.
  2. The most naive approach would be unmask the SPI interrupts and polling the SPI_ISR register to check for data. As well as writing new data to the tx buffer and clearing the interrupt. This approach has the downside that a lot of resources will be bound to this task.
  3. A better way would be use the DMA for handling the transfers so that the CPU is not needed to fill the buffers.
  4. The best way would be to use an interrupt to tell your program that data arrived and that new data needs to be written to the buffer for future transfers. Like this no polling is needed anymore and the CPU would have at least to do with the transfers as possible.

The naive approach should be totally possible by just unmasking the interrupts and checking for them. About the DMA, I saw the register for activating it but did no further research on how to use and configure them. For the interrupt based approach, I know that a general interrupt controller exists and just assume that it can be possible to be used that way. For the DMA with and without the interrupt part, it would be maybe helpful to read the spi driver code found in the Linux kernel.

About your question about the clock speed. I assume that as long as the internal clockspeed for the controller is high enough it will adjust to the clk signal of the master. I set the divider in a way that 66.7MHz are possible so even if there is an additional division by 2 it is still way fast enough to read the data from a Raspberry Pi on max speed.

arne48 commented 6 years ago

I hope my explanation helped you a bit finding your way into this topic. It might be difficult but should be totally possible. And it would be really cool if this feature can be added. Further maybe you should consider my patched build of armbian. It has the rt preempt patch set and SPI support of the kernel is disabled for those on the header. So these drivers wouldn't interfere with your code. https://github.com/arne48/armbian_build

MackoLysy commented 6 years ago

Thanks for tips. I would like to know what static inline void _set_config(uint32_t *register_addr, uint32_t offset, uint32_t config, uint32_t config_length) do. I try to figure out from this line of code.

uint32_t pin = _spi_configs[controller].clk;
  _set_config(_rk3288_gpio_block_base + ALIGN(_gpio_header_pins[pin].grf_bank_offset),
              _gpio_header_pins[pin].grf_pin_offset, 1, _gpio_header_pins[pin].grf_config_size);
  _gpio_header_pins[pin].mode = SPI;

From your tips it looks like i have to:

As a CPU resources it wont be a problem and it have to be SPI. Any request from you will be helpful.

MackoLysy commented 6 years ago

And how I can recive Data from FIFO?

MackoLysy commented 6 years ago

Now i have problem. My code dont work's on line before load config you have ideas how to repair that?

arne48 commented 6 years ago

The _set_config function sets the pin function by configuring the IOMUX. Because almost every pin can have several functions you need to set which component is finally using the pin. And here is the first thing what could be wrong. You need to set one (and I would start with CS0) of the chip select pins using this function. The register for that is GPIO8A[7] which you can find in the documentation of the GRF.

But the value to set it to the SPI function is 1 just as it is for the .clk, .rxd and .txd. So this code should do it:

uint32_t pin = _spi_configs[controller].cs0;
  _set_config(_rk3288_gpio_block_base + ALIGN(_gpio_header_pins[pin].grf_bank_offset),
              _gpio_header_pins[pin].grf_pin_offset, 1, _gpio_header_pins[pin].grf_config_size);

Reading and writing data from and to the FIFOs is done via the RXDR and TXDR registers.

About the interrupt handling. This is maybe the most tricky part. First I would try if after unmasking the interrupts in SPI_ISR gets set after writing data to the slave. An even more basic test should be if the SPI_RXFLR is counting up. With these tests you can check if the SPI controller is in general working in slave mode and reacting to its CS and the CLK of the master.

After that I would try to figure out how to use the DMA and then how Interrupt handling can be realized. Because as you said you basically don't care about how much the CPU has to work on this, I would approach it like this.

MackoLysy commented 6 years ago

i finally set all to spi slave but now i get errors when i try to send and recive files. My buffers are stuck and use this function.

void tinkerboard_spi_transfer(enum SPIController controller, uint8_t* tx_buff, uint8_t* rx_buff, uint32_t length, struct spi_mode_config_t mode_config) {

  _spi_enable_controller(controller, 1);

  unsigned long remain = 0;
  _spi_internals[controller].tx = tx_buff;
  _spi_internals[controller].tx_end = tx_buff + length;
  _spi_internals[controller].rx = rx_buff;
  _spi_internals[controller].rx_end = rx_buff + length;

  // Check if slave select is used
  if(_spi_internals[controller].cs_pin != NO_SS) {
    tinkerboard_set_gpio_state(_spi_internals[controller].cs_pin, LOW);
  }

  do {
    if(_spi_internals[controller].tx) {
      remain = _spi_internals[controller].tx_end - _spi_internals[controller].tx;
      _spi_send(controller, mode_config);
    }

    if(_spi_internals[controller].rx) {
      remain = _spi_internals[controller].rx_end - _spi_internals[controller].rx;
      _spi_receive(controller, mode_config);
    }
  } while (remain);

  // If spi controller is still busy keep waiting and eventually timing out
  if(_spi_internals[controller].tx) {
    _spi_wait_for_idle(controller);
  }

  // Check if slave select is used
  if(_spi_internals[controller].cs_pin != NO_SS) {
    tinkerboard_set_gpio_state(_spi_internals[controller].cs_pin, HIGH);
  }

  _spi_enable_controller(controller, 0);
}

Can you help me with this matter?

MackoLysy commented 6 years ago

So i get to the point where i need to set interrupt on chip CS(or on whatever chip) and read data baiscly. But the biggest problem is how to set PIN interrupt.

MackoLysy commented 6 years ago

and from my logic it should work like this

The main problem is how to set interrrupt and how to get data as slave. This buffer

arne48 commented 6 years ago

Normally the CS when in slave mode should be handled by the hardware. The driver then works with the status registers to know when new data is available. The interrupt I was talking about should be raised when the buffers are empty. Like this you will have the most amount of free CPU cycles by utilizing the available hardware.

arne48 commented 6 years ago

@MackoLysy how is it going, could you make some progress on the SPI slave topic?

HctNew commented 4 years ago

Hello, after I configured SPI slave mode, the BUSY bit of the SR register remains BUSY, even if I fail to SPI.

HctNew commented 4 years ago

@MackoLysy could you make some progress on the SPI slave topic?