Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tinker Board as SPI slave #1

Open
MackoLysy opened this issue Apr 22, 2018 · 16 comments
Open

Tinker Board as SPI slave #1

MackoLysy opened this issue Apr 22, 2018 · 16 comments

Comments

@MackoLysy
Copy link

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

@arne48
Copy link
Owner

arne48 commented Apr 23, 2018

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
Copy link
Author

MackoLysy commented Apr 23, 2018

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
Copy link
Author

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
Copy link
Owner

arne48 commented Apr 24, 2018

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 arne48 closed this as completed Apr 24, 2018
@arne48 arne48 reopened this Apr 24, 2018
@arne48
Copy link
Owner

arne48 commented Apr 24, 2018

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
Copy link
Author

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:

  • set TinkerBoard as slave(config |= 1 << 20;)
  • set clk as input(i dont know how to do that like tinkerboard_set_gpio_mode)
  • put interrrupt on CSN0 when is rising edge (i dont know how to do that). Your API dont provide interrupt handling.

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

@MackoLysy
Copy link
Author

And how I can recive Data from FIFO?

@MackoLysy
Copy link
Author

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

@arne48
Copy link
Owner

arne48 commented Apr 25, 2018

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
Copy link
Author

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
Copy link
Author

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
Copy link
Author

and from my logic it should work like this

  • Interrupt from CS
  • Read data from Master
  • Send data to Master

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

@arne48
Copy link
Owner

arne48 commented Apr 27, 2018

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
Copy link
Owner

arne48 commented Aug 8, 2018

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

@HctNew
Copy link

HctNew commented Mar 13, 2020

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

@HctNew
Copy link

HctNew commented Mar 16, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants