Originally I intended to merge a dedicated Baikal-T1 System Boot SPI
Controller driver into the kernel and leave the DW APB SSI driver
untouched. But after a long discussion (see the link at the bottom of the
letter) Mark and Andy persuaded me to integrate what we developed there
into the DW APB SSI core driver to be useful for another controllers,
which may have got the same peculiarities/problems as ours:
- No IRQ.
- No DMA.
- No GPIO CS, so a native CS is utilized.
- small Tx/Rx FIFO depth.
- Automatic CS assertion/de-assertion.
- Slow system bus.
All of them have been fixed in the framework of this patchset in some
extent at least for the SPI memory operations. As I expected it wasn't
that easy and the integration took that many patches as you can see from
the subject. Though some of them are mere cleanups or weakly related with
the subject fixes, but we just couldn't leave the code as is at some
places since we were working with the DW APB SSI driver anyway. Here is
what we did to fix the original DW APB SSI driver, to make it less messy.
First two patches are just cleanups to simplify the DW APB SSI device
initialization a bit. We suggest to discard the IRQ threshold macro as
unused and use a ternary operator to initialize the set_cs callback
instead of assigning-and-updating it.
Then we've discovered that the n_bytes field of the driver private data is
used by the DW APB SSI IRQ handler, which requires it to be initialized
before the SMP memory barrier and to be visible from another CPUs. Speaking
about the SMP memory barrier. Having one right after the shared resources
initialization is enough and there is no point in using the spin-lock to
protect the Tx/Rx buffer pointers. The protection functionality is
redundant there by the driver design. (Though I have a doubt whether the
SMP memory barrier is also required there because the normal IO-methods
like readl/writel implies a full memory barrier. So any memory operations
performed before them are supposed to be seen by devices and another CPUs.
See the patch log for details of my concern.)
Thirdly we've found out that there is some confusion in the IRQs
masking/unmasking/clearing in the SPI-transfer procedure. Multiple interrupts
are unmasked on the SPI-transfer initialization, but just TXEI is only
masked back on completion. Similarly IRQ status isn't cleared on the
controller reset, which actually makes the reset being not full and errors
prone in the controller probe procedure.
Another very important optimization is using the IO-relaxed accessors in
the dw_read_io_reg()/dw_write_io_reg() methods. Since the Tx/Rx FIFO data
registers are the most frequently accessible controller resource, using
relaxed accessors there will significantly improve the data read/write
performance. At least on Baikal-T1 SoC such modification opens up a way to
have the DW APB SSI controller working with higher SPI bus speeds, than
without it.
Fifthly we've made an effort to cleanup the code using the SPI-device
private data - chip_data. We suggest to remove the chip type from there
since it isn't used and isn't implemented right anyway. Then instead of
having a bus speed, clock divider, transfer mode preserved there, and
recalculating the CR0 fields of the SPI-device-specific phase, polarity
and frame format each time the SPI transfer is requested, we can save it
in the chip_data instance. By doing so we'll make that structure finally
used as it was supposed to by design (see the spi-fsl-dspi.c, spi-pl022.c,
spi-pxa2xx.c drivers for examples).
Sixthly instead of having the SPI-transfer specific CR0-update callback,
we suggest to implement the DW APB SSI controller capabilities approach.
By doing so we can now inject the vendor-specific peculiarities in
different parts of the DW APB SSI core driver (which is required to
implement both SPI-transfers and the SPI memory operations). This will
also make the code less confusing like defining a callback in the core
driver, setting it up in the glue layer, then calling it from the core
driver again. Seeing the small capabilities implementation embedded
in-situ is more readable than tracking the callbacks assignments. This
will concern the CS-override, Keembay master setup, DW SSI-specific CR0
registers layout capabilities.
Seventhly since there are going to be two types of the transfers
implemented in the DW APB SSI core driver, we need a common method to set
the controller configuration like, Tx/Rx-mode, bus speed, data frame size
and number of data frames to read in case of the memory operations. So we
just detached the corresponding code from the SPI-transfer-one method and
made it to be a part of the new dw_spi_update_config() function, which is
former update_cr0(). Note that the new method will be also useful for the
glue drivers, which due to the hardware design need to create their own
memory operations (for instance, for the dirmap-operations provided in the
Baikal-T System Boot SPI controller driver).
Eighthly it is the data IO procedure and IRQ-based SPI-transfer
implementation refactoring. The former one will look much simpler if the
buffers initial pointers and the buffers length data utilized instead of
the Tx/Rx buffers start and end pointers. The later one currently lacks of
valid execution at the final stage of the SPI-transfer. So if there is no
data left to send, but there is still data which needs to be received, the
Tx FIFO Empty IRQ will constantly happen until all of the requested
inbound data is received. So we suggest to fix that by taking the Rx FIFO
Empty IRQ into account.
Ninthly it's potentially errors prone to enable the DW APB SSI interrupts
before enabling the chip. It specifically concerns a case if for some
reason the DW APB SSI IRQs handler is executed before the controller is
enabled. That will cause a part of the outbound data loss. So we suggest
to reverse the order.
Tenthly in order to be able to pre-initialize the Tx FIFO with data and
only the start the SPI memory operations we need to have any CS
de-activated. We'll fulfil that requirement by explicitly clearing the CS
on the SPI transfer completion and at the explicit controller reset.
Then seeing all the currently available and potentially being created
types of the SPI transfers need to perform the DW APB SSI controller
status register check and the errors handler procedure, we've created a
common method for all of them.
Eleventhly if before we've mostly had a series of fixups, cleanups and
refactorings, here we've finally come to the new functionality
implementation. It concerns the poll-based transfer (as Baikal-T1 System
Boot SPI controller lacks a dedicated IRQ lane connected) and the SPI
memory operations implementation. If the former feature is pretty much
straightforward (see the patch log for details), the later one is a bit
tricky. It's based on the EEPROM-read (write-then-read) and the Tx-only
modes of the DW APB SSI controller, which as performing the automatic data
read and write let's us to implement the faster IO procedure than using
the Tx-Rx-mode-based approach. Having the memory-operations implemented
that way is the best thing we can currently do to provide the errors-less
SPI transfers to SPI devices with native CS attached.
Note the approach utilized here to develop the SPI memory operations can
be also used to create the "automatic CS toggle problem"-free(ish) SPI
transfers (combine SPI-message transfers into two buffers, disable
interrupts, push-pull the combined data). But we don't provide a solution
in the framework of this patchset. It is a matter of a dedicated one,
which we currently don't intend to spend our time on.
Finally at the closure of the this patchset you'll find patches, which
provide the Baikal-T1-specific DW APB SSI controllers support. The SoC has
got three SPI controllers. Two of them are pretty much normal DW APB SSI
interfaces: with IRQ, DMA, FIFOs of 64 words depth, 4x CSs. But the third
one as being a part of the Baikal-T1 System Boot Controller has got a very
limited resources: no IRQ, no DMA, only a single native chip-select and
Tx/Rx FIFOs with just 8 words depth available. In order to provide a
transparent initial boot code execution the System Boot SPI Controller is
also utilized by an vendor-specific IP-block, which exposes an SPI flash
memory direct mapping interface. Please see the corresponding patch for
details.
Link: https://lore.kernel.org/linux-spi/20200508093621.31619-1-Sergey.Semin@baikalelectronics.ru/
[1] "LINUX KERNEL MEMORY BARRIERS", Documentation/memory-barriers.txt,
Section "KERNEL I/O BARRIER EFFECTS"
Link: https://lore.kernel.org/linux-spi/20200920112914.26501-1-Sergey.Semin@baikalelectronics.ru
Changelog v2:
- Replace the ternary operator with the if-else statement in the set_cs
callback setting up.
- Get back the in-code comments to the dw_spi_update_cr0() method and it'
further derivatives.
- Discard the patches from the series as being merged in:
[PATCH 00/10] spi: spi-dw: Remove extraneous locking
[PATCH 00/09] spi: dw: Add KeemBay Master capability
[PATCH 00/08] spi: dw: Convert CS-override to DW SPI capabilities
[PATCH 00/07] spi: dw: Discard DW SSI chip type storages
[PATCH 00/06] spi: dw: Use relaxed IO-methods to access FIFOs
[PATCH 00/05] spi: dw: Disable all IRQs when controller is unused
[PATCH 00/04] spi: dw: Clear IRQ status on DW SPI controller reset
[PATCH 00/03] spi: dw: Initialize n_bytes before the memory barrier
[PATCH 00/01] spi: dw: Discard IRQ threshold macro
Link: https://lore.kernel.org/linux-spi/20200930185545.29959-1-Sergey.Semin@baikalelectronics.ru
Changelog v3:
- Remove dw_spi_update_cr0() callback assignment from the DW APB SSI PCI
glue-driver.
Link: https://lore.kernel.org/linux-spi/20201001222829.15977-1-Sergey.Semin@baikalelectronics.ru
Changelog v4:
- Rename dw_spi_get_cr0() to dw_spi_prepare_cr0().
Signed-off-by: Serge Semin <redacted>
Cc: Alexey Malahov <redacted>
Cc: Ramil Zaripov <redacted>
Cc: Pavel Parkhomenko <redacted>
Cc: Andy Shevchenko <redacted>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Lars Povlsen <redacted>
Cc: wuxu.wu <redacted>
Cc: Feng Tang <redacted>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: linux-spi@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Serge Semin (21):
spi: dw: Use an explicit set_cs assignment
spi: dw: Add DWC SSI capability
spi: dw: Detach SPI device specific CR0 config method
spi: dw: Update SPI bus speed in a config function
spi: dw: Simplify the SPI bus speed config procedure
spi: dw: Update Rx sample delay in the config function
spi: dw: Add DW SPI controller config structure
spi: dw: Refactor data IO procedure
spi: dw: Refactor IRQ-based SPI transfer procedure
spi: dw: Perform IRQ setup in a dedicated function
spi: dw: Unmask IRQs after enabling the chip
spi: dw: Discard chip enabling on DMA setup error
spi: dw: De-assert chip-select on reset
spi: dw: Explicitly de-assert CS on SPI transfer completion
spi: dw: Move num-of retries parameter to the header file
spi: dw: Add generic DW SSI status-check method
spi: dw: Add memory operations support
spi: dw: Introduce max mem-ops SPI bus frequency setting
spi: dw: Add poll-based SPI transfers support
dt-bindings: spi: dw: Add Baikal-T1 SPI Controllers
spi: dw: Add Baikal-T1 SPI Controller glue driver
.../bindings/spi/snps,dw-apb-ssi.yaml | 33 +-
drivers/spi/Kconfig | 29 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-dw-bt1.c | 339 +++++++++
drivers/spi/spi-dw-core.c | 641 ++++++++++++++----
drivers/spi/spi-dw-dma.c | 16 +-
drivers/spi/spi-dw-mmio.c | 20 +-
drivers/spi/spi-dw-pci.c | 6 -
drivers/spi/spi-dw.h | 48 +-
9 files changed, 957 insertions(+), 176 deletions(-)
create mode 100644 drivers/spi/spi-dw-bt1.c
--
2.27.0
Simplify the dw_spi_add_host() method a bit by replacing the currently
implemented default set_cs callback setting up and later having it
overwritten by a custom function with direct if-else-based callback
assignment.
Signed-off-by: Serge Semin <redacted>
---
Changelog v2:
- Replace the ternary operator with the if-else statement.
---
drivers/spi/spi-dw-core.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
Indeed there is no point in detecting the SPI peripheral device parameters
and initializing the CR0 register fields each time an SPI transfer is
executed. Instead let's define a dedicated CR0 chip-data member, which
will be initialized in accordance with the SPI device settings at the
moment of setting it up.
By doing so we'll finally make the SPI device chip_data serving as it's
supposed to - to preserve the SPI device specific DW SPI configuration.
See spi-fsl-dspi.c, spi-pl022.c, spi-pxa2xx.c drivers for example of the
way the chip data is utilized.
Signed-off-by: Serge Semin <redacted>
---
Changelog v4:
- Rename dw_spi_get_cr0() to dw_spi_prepare_cr0().
---
drivers/spi/spi-dw-core.c | 43 +++++++++++++++++++++++++++------------
1 file changed, 30 insertions(+), 13 deletions(-)
@@ -269,13 +262,29 @@ static void dw_spi_update_cr0(struct dw_spi *dws, struct spi_device *spi,/* CTRLR0[13] Shift Register Loop */cr0|=((spi->mode&SPI_LOOP)?1:0)<<DWC_SSI_CTRLR0_SRL_OFFSET;-/* CTRLR0[11:10] Transfer Mode */-cr0|=chip->tmode<<DWC_SSI_CTRLR0_TMOD_OFFSET;-if(dws->caps&DW_SPI_CAP_KEEMBAY_MST)cr0|=DWC_SSI_CTRLR0_KEEMBAY_MST;}+returncr0;+}++staticvoiddw_spi_update_cr0(structdw_spi*dws,structspi_device*spi,+structspi_transfer*transfer)+{+structchip_data*chip=spi_get_ctldata(spi);+u32cr0=chip->cr0;++/* CTRLR0[ 4/3: 0] Data Frame Size */+cr0|=(transfer->bits_per_word-1);++if(!(dws->caps&DW_SPI_CAP_DWC_SSI))+/* CTRLR0[ 9:8] Transfer Mode */+cr0|=chip->tmode<<SPI_TMOD_OFFSET;+else+/* CTRLR0[11:10] Transfer Mode */+cr0|=chip->tmode<<DWC_SSI_CTRLR0_TMOD_OFFSET;+dw_writel(dws,DW_SPI_CTRLR0,cr0);}
@@ -373,6 +382,7 @@ static void dw_spi_handle_err(struct spi_controller *master,/* This may be called twice for each spi dev */staticintdw_spi_setup(structspi_device*spi){+structdw_spi*dws=spi_controller_get_devdata(spi->controller);structchip_data*chip;/* Only alloc on first setup */
@@ -396,6 +406,13 @@ static int dw_spi_setup(struct spi_device *spi)dws->max_freq);}+/*+*UpdateCR0dataeachtimethesetupcallbackisinvokedsince+*thedeviceparameterscouldhavebeenchanged,forinstance,by+*theMMCSPIdriverorsomethingelse.+*/+chip->cr0=dw_spi_prepare_cr0(dws,spi);+chip->tmode=SPI_TMOD_TR;return0;
The code currently responsible for the SPI communication speed setting up
is a bit messy. Most likely for some historical reason the bus frequency
is saved in the peripheral chip private data. It's pointless now since the
custom communication speed is a SPI-transfer-specific thing and only if
there is no SPI transfer data specified (like during the SPI memory
operations) it can be taken from the SPI device structure. But even in the
later case there is no point in having the clock divider and the SPI bus
frequency saved in the chip data, because the controller can be used for
both SPI-transfer-based and SPI-transfer-less communications. From
software point of view keeping the current clock divider in an SPI-device
specific storage may give a small performance gain (to avoid sometimes a
round-up division), but in comparison to the total SPI transfer time it
just doesn't worth saving a few CPU cycles in comparison to the total SPI
transfer time while having the harder to read code. The only optimization,
which could worth preserving in the code is to avoid unnecessary DW SPI
controller registers update if it's possible. So to speak let's simplify
the SPI communication speed update procedure by removing the clock-related
fields from the peripheral chip data and update the DW SPI clock divider
only if it's really changed. The later change is reached by keeping the
effective SPI bus speed in the internal DW SPI private data.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 23 ++++++++++-------------
1 file changed, 10 insertions(+), 13 deletions(-)
@@ -287,15 +286,13 @@ static void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,dw_writel(dws,DW_SPI_CTRLR0,cr0);-/* Handle per transfer options for bpw and speed */-if(transfer->speed_hz!=dws->current_freq){-if(transfer->speed_hz!=chip->speed_hz){-/* clk_div doesn't support odd number */-chip->clk_div=(DIV_ROUND_UP(dws->max_freq,transfer->speed_hz)+1)&0xfffe;-chip->speed_hz=transfer->speed_hz;-}-dws->current_freq=transfer->speed_hz;-spi_set_clk(dws,chip->clk_div);+/* Note DW APB SSI clock divider doesn't support odd numbers */+clk_div=(DIV_ROUND_UP(dws->max_freq,transfer->speed_hz)+1)&0xfffe;+speed_hz=dws->max_freq/clk_div;++if(dws->current_freq!=speed_hz){+spi_set_clk(dws,clk_div);+dws->current_freq=speed_hz;}}
@@ -323,7 +320,7 @@ static int dw_spi_transfer_one(struct spi_controller *master,dw_spi_update_config(dws,spi,transfer);-transfer->effective_speed_hz=dws->max_freq/chip->clk_div;+transfer->effective_speed_hz=dws->current_freq;/* Check if current transfer is a DMA transaction */if(master->can_dma&&master->can_dma(master,spi,transfer))
Rx sample delay can be SPI device specific, and should be synchronously
initialized with the rest of the communication and peripheral device
related controller setups. So let's move the Rx-sample delay setup into
the DW APB SSI configuration update method.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
@@ -326,12 +331,6 @@ static int dw_spi_transfer_one(struct spi_controller *master,if(master->can_dma&&master->can_dma(master,spi,transfer))dws->dma_mapped=master->cur_msg_mapped;-/* Update RX sample delay if required */-if(dws->cur_rx_sample_dly!=chip->rx_sample_dly){-dw_writel(dws,DW_SPI_RX_SAMPLE_DLY,chip->rx_sample_dly);-dws->cur_rx_sample_dly=chip->rx_sample_dly;-}-/* For poll mode just disable all interrupts */spi_mask_intr(dws,0xff);
In order to make the transfer_one() callback method more readable and
for unification with the DMA-based transfer, let's detach the IRQ setup
procedure into a dedicated function. While at it rename the IRQ-based
transfer handler function to be dw_spi-prefixe and looking more like the
DMA-related one.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 41 ++++++++++++++++++++++-----------------
1 file changed, 23 insertions(+), 18 deletions(-)
@@ -324,8 +345,6 @@ static int dw_spi_transfer_one(struct spi_controller *master,.dfs=transfer->bits_per_word,.freq=transfer->speed_hz,};-u8imask=0;-u16txlevel=0;intret;dws->dma_mapped=0;
@@ -358,21 +377,7 @@ static int dw_spi_transfer_one(struct spi_controller *master,returnret;}}else{-/*-*OriginallyTxandRxdatalengthsmatch.RxFIFOThresholdlevel-*willbeadjustedatthefinalstageoftheIRQ-basedSPItransfer-*executionsonottolosetheleftoveroftheincomingdata.-*/-txlevel=min_t(u16,dws->fifo_len/2,dws->tx_len);-dw_writel(dws,DW_SPI_TXFTLR,txlevel);-dw_writel(dws,DW_SPI_RXFTLR,txlevel-1);--/* Set the interrupt mask */-imask|=SPI_INT_TXEI|SPI_INT_TXOI|-SPI_INT_RXUI|SPI_INT_RXOI|SPI_INT_RXFI;-spi_umask_intr(dws,imask);--dws->transfer_handler=interrupt_transfer;+dw_spi_irq_setup(dws);}spi_enable_chip(dws,1);
SPI memory operations implementation will require to have the CS register
cleared before executing the operation in order not to have the
transmission automatically started prior the Tx FIFO is pre-initialized.
Let's clear the register then on explicit controller reset to fulfil the
requirements in case of an error or having the CS left set by a bootloader
or another software.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw.h | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
In some circumstances the current implementation of the SPI memory
operations may occasionally fail even though they are executed in the
atomic context. This may happen if the system bus is relatively slow in
comparison to the SPI bus frequency, or there is a concurrent access to
it, which makes the MMIO-operations occasionally stalling before
push-pulling data from the DW APB SPI FIFOs. These two problems we've
discovered on the Baikal-T1 SoC. In order to fix them we have no choice
but to set an artificial limitation on the SPI bus speed.
Note currently this limitation will be only applicable for the memory
operations, since the standard SPI core interface is implemented with an
assumption that there is no problem with the automatic CS toggling.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 4 +++-
drivers/spi/spi-dw.h | 1 +
2 files changed, 4 insertions(+), 1 deletion(-)
@@ -148,6 +148,7 @@ struct dw_spi {unsignedlongpaddr;intirq;u32fifo_len;/* depth of the FIFO buffer */+u32max_mem_freq;/* max mem-ops bus freq */u32max_freq;/* max bus freq supported */u32caps;/* DW SPI capabilities */
Aside from the synchronous Tx-Rx mode, which has been utilized to create
the normal SPI transfers in the framework of the DW SSI driver, DW SPI
controller supports Tx-only and EEPROM-read modes. The former one just
enables the controller to transmit all the data from the Tx FIFO ignoring
anything retrieved from the MISO lane. The later mode is so called
write-then-read operation: DW SPI controller first pushes out all the data
from the Tx FIFO, after that it'll automatically receive as much data as
has been specified by means of the CTRLR1 register. Both of those modes
can be used to implement the memory operations supported by the SPI-memory
subsystem.
The memory operation implementation is pretty much straightforward, except
a few peculiarities we have had to take into account to make things
working. Since DW SPI controller doesn't provide a way to directly set and
clear the native CS lane level, but instead automatically de-asserts it
when a transfer going on, we have to make sure the Tx FIFO isn't empty
during entire Tx procedure. In addition we also need to read data from the
Rx FIFO as fast as possible to prevent it' overflow with automatically
fetched incoming traffic. The denoted peculiarities get to cause even more
problems if DW SSI controller is equipped with relatively small FIFO and
is connected to a relatively slow system bus (APB) (with respect to the
SPI bus speed). In order to workaround the problems for as much as it's
possible, the memory operation execution procedure collects all the Tx
data into a single buffer and disables the local IRQs to speed the
write-then-optionally-read method up.
Note the provided memory operations are utilized by default only if
a glue driver hasn't provided a custom version of ones and this is not
a DW APB SSI controller with fixed automatic CS toggle functionality.
Co-developed-by: Ramil Zaripov <redacted>
Signed-off-by: Ramil Zaripov <redacted>
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/Kconfig | 1 +
drivers/spi/spi-dw-core.c | 301 ++++++++++++++++++++++++++++++++++++++
drivers/spi/spi-dw.h | 13 ++
3 files changed, 315 insertions(+)
@@ -422,6 +425,301 @@ static void dw_spi_handle_err(struct spi_controller *master,spi_reset_chip(dws);}+staticintdw_spi_adjust_mem_op_size(structspi_mem*mem,structspi_mem_op*op)+{+if(op->data.dir==SPI_MEM_DATA_IN)+op->data.nbytes=clamp_val(op->data.nbytes,0,SPI_NDF_MASK+1);++return0;+}++staticbooldw_spi_supports_mem_op(structspi_mem*mem,+conststructspi_mem_op*op)+{+if(op->data.buswidth>1||op->addr.buswidth>1||+op->dummy.buswidth>1||op->cmd.buswidth>1)+returnfalse;++returnspi_mem_default_supports_op(mem,op);+}++staticintdw_spi_init_mem_buf(structdw_spi*dws,conststructspi_mem_op*op)+{+unsignedinti,j,len;+u8*out;++/*+*CalculatethetotallengthoftheEEPROMcommandtransferand+*eitherusethepre-allocatedbufferorcreateatemporaryone.+*/+len=op->cmd.nbytes+op->addr.nbytes+op->dummy.nbytes;+if(op->data.dir==SPI_MEM_DATA_OUT)+len+=op->data.nbytes;++if(len<=SPI_BUF_SIZE){+out=dws->buf;+}else{+out=kzalloc(len,GFP_KERNEL);+if(!out)+return-ENOMEM;+}++/*+*Collecttheoperationcode,addressanddummybytesintothesingle+*buffer.Ifit'satransferwithdatatobesent,alsocopyitintothe+*singlebufferinordertospeedthedatatransmissionup.+*/+for(i=0;i<op->cmd.nbytes;++i)+out[i]=SPI_GET_BYTE(op->cmd.opcode,op->cmd.nbytes-i-1);+for(j=0;j<op->addr.nbytes;++i,++j)+out[i]=SPI_GET_BYTE(op->addr.val,op->addr.nbytes-j-1);+for(j=0;j<op->dummy.nbytes;++i,++j)+out[i]=0x0;++if(op->data.dir==SPI_MEM_DATA_OUT)+memcpy(&out[i],op->data.buf.out,op->data.nbytes);++dws->n_bytes=1;+dws->tx=out;+dws->tx_len=len;+if(op->data.dir==SPI_MEM_DATA_IN){+dws->rx=op->data.buf.in;+dws->rx_len=op->data.nbytes;+}else{+dws->rx=NULL;+dws->rx_len=0;+}++return0;+}++staticvoiddw_spi_free_mem_buf(structdw_spi*dws)+{+if(dws->tx!=dws->buf)+kfree(dws->tx);+}++staticintdw_spi_write_then_read(structdw_spi*dws,structspi_device*spi)+{+u32room,entries,sts;+unsignedintlen;+u8*buf;++/*+*Atinitialstagewejustpre-filltheTxFIFOinwithnorush,+*sincenativeCShasn'tbeenenabledyetandtheautomaticdata+*transmissionwon'tstarttilwedothat.+*/+len=min(dws->fifo_len,dws->tx_len);+buf=dws->tx;+while(len--)+dw_write_io_reg(dws,DW_SPI_DR,*buf++);++/*+*AftersettinganybitintheSERregisterthetransmissionwill+*startautomatically.Wehavetokeepupwiththatprocedure+*otherwisetheCSde-assertionwillhappenwhereuponthememory+*operationwillbepre-terminated.+*/+len=dws->tx_len-((void*)buf-dws->tx);+dw_spi_set_cs(spi,false);+while(len){+entries=readl_relaxed(dws->regs+DW_SPI_TXFLR);+if(!entries){+dev_err(&dws->master->dev,"CS de-assertion on Tx\n");+return-EIO;+}+room=min(dws->fifo_len-entries,len);+for(;room;--room,--len)+dw_write_io_reg(dws,DW_SPI_DR,*buf++);+}++/*+*DatafetchingwillstartautomaticallyiftheEEPROM-readmodeis+*activated.Wehavetokeepupwiththeincomingdatapaceto+*preventtheRxFIFOoverflowcausingtheinbounddataloss.+*/+len=dws->rx_len;+buf=dws->rx;+while(len){+entries=readl_relaxed(dws->regs+DW_SPI_RXFLR);+if(!entries){+sts=readl_relaxed(dws->regs+DW_SPI_RISR);+if(sts&SPI_INT_RXOI){+dev_err(&dws->master->dev,"FIFO overflow on Rx\n");+return-EIO;+}+continue;+}+entries=min(entries,len);+for(;entries;--entries,--len)+*buf++=dw_read_io_reg(dws,DW_SPI_DR);+}++return0;+}++staticinlinebooldw_spi_ctlr_busy(structdw_spi*dws)+{+returndw_readl(dws,DW_SPI_SR)&SR_BUSY;+}++staticintdw_spi_wait_mem_op_done(structdw_spi*dws)+{+intretry=SPI_WAIT_RETRIES;+structspi_delaydelay;+unsignedlongns,us;+u32nents;++nents=dw_readl(dws,DW_SPI_TXFLR);+ns=NSEC_PER_SEC/dws->current_freq*nents;+ns*=dws->n_bytes*BITS_PER_BYTE;+if(ns<=NSEC_PER_USEC){+delay.unit=SPI_DELAY_UNIT_NSECS;+delay.value=ns;+}else{+us=DIV_ROUND_UP(ns,NSEC_PER_USEC);+delay.unit=SPI_DELAY_UNIT_USECS;+delay.value=clamp_val(us,0,USHRT_MAX);+}++while(dw_spi_ctlr_busy(dws)&&retry--)+spi_delay_exec(&delay,NULL);++if(retry<0){+dev_err(&dws->master->dev,"Mem op hanged up\n");+return-EIO;+}++return0;+}++staticvoiddw_spi_stop_mem_op(structdw_spi*dws,structspi_device*spi)+{+spi_enable_chip(dws,0);+dw_spi_set_cs(spi,true);+spi_enable_chip(dws,1);+}++/*+*TheSPImemoryoperationimplementationbelowisthebestchoiceforthe+*devices,whichareselectedbythenativechip-selectlane.It's+*specificallydevelopedtoworkaroundtheproblemwithautomaticchip-select+*lanetogglewhenthereisnodataintheTxFIFObuffer.Luckilythecurrent+*SPI-memcorecallsexec_op()callbackonlyiftheGPIO-basedCSis+*unavailable.+*/+staticintdw_spi_exec_mem_op(structspi_mem*mem,conststructspi_mem_op*op)+{+structdw_spi*dws=spi_controller_get_devdata(mem->spi->controller);+structdw_spi_cfgcfg;+unsignedlongflags;+intret;++/*+*Collecttheoutbounddataintoasinglebuffertospeedthe+*transmissionupatleastontheinitialstage.+*/+ret=dw_spi_init_mem_buf(dws,op);+if(ret)+returnret;++/*+*DWSPIEEPROM-readmodeisrequiredonlyfortheSPImemoryData-IN+*operation.Transmit-onlymodeissuitablefortherestofthem.+*/+cfg.dfs=8;+cfg.freq=mem->spi->max_speed_hz;+if(op->data.dir==SPI_MEM_DATA_IN){+cfg.tmode=SPI_TMOD_EPROMREAD;+cfg.ndf=op->data.nbytes;+}else{+cfg.tmode=SPI_TMOD_TO;+}++spi_enable_chip(dws,0);++dw_spi_update_config(dws,mem->spi,&cfg);++spi_mask_intr(dws,0xff);++spi_enable_chip(dws,1);++/*+*DWAPBSSIcontrollerhasverynastypeculiarities.Firstoriginally+*(withoutanyvendor-specificmodifications)itdoesn'tprovidea+*directwaytosetandclearthenativechip-selectsignal.Instead+*thecontrollerassertstheCSlaneifTxFIFOisn'temptyanda+*transmissionisgoingon,andautomaticallyde-assertsitbackto+*thehighleveliftheTxFIFOdoesn'thaveanythingtobepushed+*out.Duetothatamulti-taskingorheavyIRQsactivitymightbe+*fatal,sincethetransferprocedurepreemptionmaycausetheTxFIFO+*gettingemptyandsuddenCSde-assertion,whichinthemiddleofthe+*transferwillmostlikelycausethedataloss.Secondlythe+*EEPROM-readorRead-onlyDWSPItransfermodesimplytheincoming+*databeingautomaticallypulledinintotheRxFIFO.Soifthe+*driversoftwareislateinfetchingthedatafromtheFIFObefore+*it'soverflown,newincomingdatawillbelost.Inordertomake+*suretheexecutedmemoryoperationsareCS-atomicandtopreventthe+*RxFIFOoverflowwehavetodisablethelocalinterruptssotoblock+*anypreemptionduringthesubsequentIOoperations.+*+*Note.AtsomecircumstancesdisablingIRQsmaynothelptoprevent+*theproblemsdescribedabove.TheCSde-assertionandRxFIFO+*overflowmaystillhappenduetotherelativelyslowsystembusor+*CPUnotworkingfastenough,sothewrite-then-readalgoimplemented+*herejustwon'tkeepupwiththeSPIbusdatatransfer.Such+*situationishighlyplatformspecificandissupposedtobefixedby+*manuallyrestrictingtheSPIbusfrequencyusingthe+*dws->max_mem_freqparameter.+*/+local_irq_save(flags);+preempt_disable();++ret=dw_spi_write_then_read(dws,mem->spi);++local_irq_restore(flags);+preempt_enable();++/*+*Waitfortheoperationbeingfinishedandcheckthecontroller+*statusonlyiftherehasn'tbeenanyrun-timeerrordetected.Inthe+*formercaseit'sjustpointless.Inthelateronetopreventan+*additionalerrormessageprintingsinceanyhwerrorflagbeingset+*wouldbeduetoanerrordetectedonthedatatransfer.+*/+if(!ret){+ret=dw_spi_wait_mem_op_done(dws);+if(!ret)+ret=dw_spi_check_status(dws,true);+}++dw_spi_stop_mem_op(dws,mem->spi);++dw_spi_free_mem_buf(dws);++returnret;+}++/*+*Initializethedefaultmemoryoperationsifagluelayerhasn'tspecified+*customones.DirectmappingoperationswillbepreservedanywaysinceDWSPI+*controllerdoesn'thaveanembeddeddirmapinterface.Notethememory+*operationsimplementedinthisdriveristhebestchoiceonlyfortheDWAPB+*SSIcontrollerwithstandardnativeCSfunctionality.Ifahardwarevendor+*hasfixedtheautomaticCSassertion/de-assertionpeculiarity,thenitwill+*besafertousethenormalSPI-messages-basedtransfersimplementation.+*/+staticvoiddw_spi_init_mem_ops(structdw_spi*dws)+{+if(!dws->mem_ops.exec_op&&!(dws->caps&DW_SPI_CAP_CS_OVERRIDE)&&+!dws->set_cs){+dws->mem_ops.adjust_op_size=dw_spi_adjust_mem_op_size;+dws->mem_ops.supports_op=dw_spi_supports_mem_op;+dws->mem_ops.exec_op=dw_spi_exec_mem_op;+}+}+/* This may be called twice for each spi dev */staticintdw_spi_setup(structspi_device*spi){
@@ -153,6 +162,7 @@ struct dw_spi {unsignedinttx_len;void*rx;unsignedintrx_len;+u8buf[SPI_BUF_SIZE];intdma_mapped;u8n_bytes;/* current is a 1/2 bytes op */irqreturn_t(*transfer_handler)(structdw_spi*dws);
The parameter will be needed for another wait-done method being added in
the framework of the SPI memory operation modification in a further
commit.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-dma.c | 5 ++---
drivers/spi/spi-dw.h | 2 ++
2 files changed, 4 insertions(+), 3 deletions(-)
The DW SSI errors handling method can be generically implemented for all
types of the transfers: IRQ, DMA and poll-based ones. It will be a
function which checks the overflow/underflow error flags and resets the
controller if any of them is set. In the framework of this commit we make
use of the new method to detect the errors in the IRQ- and DMA-based SPI
transfer execution procedures.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 43 +++++++++++++++++++++++++++++++--------
drivers/spi/spi-dw-dma.c | 11 ++--------
drivers/spi/spi-dw.h | 1 +
3 files changed, 37 insertions(+), 18 deletions(-)
It's theoretically erroneous to enable IRQ before the chip is turned on.
If IRQ handler gets executed before the chip is enabled, then any data
written to the Tx FIFO will be just ignored.
I say "theoretically" because we haven't noticed any problem with that,
but let's fix it anyway just in case...
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
By design of the currently available native set_cs callback, the CS
de-assertion will be done only if it's required by the corresponding
controller capability. But in order to pre-fill the Tx FIFO buffer with
data during the SPI memory ops execution the SER register needs to be left
cleared before that. We'll also need a way to explicitly set and clear the
corresponding CS bit at a certain moment of the operation. Let's alter
the set_cs function then to also de-activate the CS, when it's required.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
These controllers are based on the DW APB SSI IP-core and embedded into
the SoC, so two of them are equipped with IRQ, DMA, 64 words FIFOs and 4
native CS, while another one as being utilized by the Baikal-T1 System
Boot Controller has got a very limited resources: no IRQ, no DMA, only a
single native chip-select and just 8 bytes Tx/Rx FIFOs available. That's
why we have to mark the IRQ to be optional for the later interface.
The SPI controller embedded into the Baikal-T1 System Boot Controller can
be also used to directly access an external SPI flash by means of a
dedicated FSM. The corresponding MMIO region availability is switchable by
the embedded multiplexor, which phandle can be specified in the dts node.
* We added a new example to test out the non-standard Baikal-T1 System
Boot SPI Controller DT binding.
Co-developed-by: Ramil Zaripov <redacted>
Signed-off-by: Ramil Zaripov <redacted>
Signed-off-by: Serge Semin <redacted>
Reviewed-by: Rob Herring <robh@kernel.org>
---
.../bindings/spi/snps,dw-apb-ssi.yaml | 33 +++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
A functionality of the poll-based transfer has been removed by
commit 1ceb09717e98 ("spi: dw: remove cs_control and poll_mode members
from chip_data") with a justification that "there is no user of one
anymore". It turns out one of our DW APB SSI core is synthesized with no
IRQ line attached and the only possible way of using it is to implement a
poll-based SPI transfer procedure. So we have to get the removed
functionality back, but with some alterations described below.
First of all the poll-based transfer is activated only if the DW SPI
controller doesn't have an IRQ line attached and the Linux IRQ number is
initialized with the IRQ_NOTCONNECTED value. Secondly the transfer
procedure is now executed with a delay performed between writer and reader
methods. The delay value is calculated based on the number of data words
expected to be received on the current iteration. Finally the errors
status is checked on each iteration.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 40 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
@@ -408,6 +444,8 @@ static int dw_spi_transfer_one(struct spi_controller *master,if(dws->dma_mapped)returndws->dma_ops->dma_transfer(dws,transfer);+elseif(dws->irq==IRQ_NOTCONNECTED)+returndw_spi_poll_transfer(dws,transfer);dw_spi_irq_setup(dws);
@@ -817,7 +855,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)ret=request_irq(dws->irq,dw_spi_irq,IRQF_SHARED,dev_name(dev),master);-if(ret<0){+if(ret<0&&ret!=-ENOTCONN){dev_err(dev,"can not get IRQ\n");gotoerr_free_master;}
The Tx and Rx data write/read procedure can be significantly simplified by
using Tx/Rx transfer lengths instead of the end pointers. By having the
Tx/Rx data leftover lengths (in the number of transfer words) we can get
rid of all subtraction and division operations utilized here and there in
the tx_max(), rx_max(), dw_writer() and dw_reader() methods. Such
modification will not only give us the more optimized IO procedures, but
will make the data IO methods much more readable than before.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 37 +++++++++++++++++--------------------
drivers/spi/spi-dw.h | 5 ++---
2 files changed, 19 insertions(+), 23 deletions(-)
@@ -108,9 +108,8 @@ EXPORT_SYMBOL_GPL(dw_spi_set_cs);/* Return the max entries we can fill into tx fifo */staticinlineu32tx_max(structdw_spi*dws){-u32tx_left,tx_room,rxtx_gap;+u32tx_room,rxtx_gap;-tx_left=(dws->tx_end-dws->tx)/dws->n_bytes;tx_room=dws->fifo_len-dw_readl(dws,DW_SPI_TXFLR);/*
@@ -121,18 +120,15 @@ static inline u32 tx_max(struct dw_spi *dws)*shiftregisters.Soacontrolfromswpointof*viewistaken.*/-rxtx_gap=((dws->rx_end-dws->rx)-(dws->tx_end-dws->tx))-/dws->n_bytes;+rxtx_gap=dws->fifo_len-(dws->rx_len-dws->tx_len);-returnmin3(tx_left,tx_room,(u32)(dws->fifo_len-rxtx_gap));+returnmin3((u32)dws->tx_len,tx_room,rxtx_gap);}/* Return the max entries we should read out of rx fifo */staticinlineu32rx_max(structdw_spi*dws){-u32rx_left=(dws->rx_end-dws->rx)/dws->n_bytes;--returnmin_t(u32,rx_left,dw_readl(dws,DW_SPI_RXFLR));+returnmin_t(u32,dws->rx_len,dw_readl(dws,DW_SPI_RXFLR));}staticvoiddw_writer(structdw_spi*dws)
@@ -141,15 +137,16 @@ static void dw_writer(struct dw_spi *dws)u16txw=0;while(max--){-/* Set the tx word if the transfer's original "tx" is not null */-if(dws->tx_end-dws->len){+if(dws->tx){if(dws->n_bytes==1)txw=*(u8*)(dws->tx);elsetxw=*(u16*)(dws->tx);++dws->tx+=dws->n_bytes;}dw_write_io_reg(dws,DW_SPI_DR,txw);-dws->tx+=dws->n_bytes;+--dws->tx_len;}}
@@ -160,14 +157,15 @@ static void dw_reader(struct dw_spi *dws)while(max--){rxw=dw_read_io_reg(dws,DW_SPI_DR);-/* Care rx only if the transfer's original "rx" is not null */-if(dws->rx_end-dws->len){+if(dws->rx){if(dws->n_bytes==1)*(u8*)(dws->rx)=rxw;else*(u16*)(dws->rx)=rxw;++dws->rx+=dws->n_bytes;}-dws->rx+=dws->n_bytes;+--dws->rx_len;}}
@@ -320,12 +318,11 @@ static int dw_spi_transfer_one(struct spi_controller *master,dws->dma_mapped=0;dws->n_bytes=DIV_ROUND_UP(transfer->bits_per_word,BITS_PER_BYTE);dws->tx=(void*)transfer->tx_buf;-dws->tx_end=dws->tx+transfer->len;+dws->tx_len=transfer->len/dws->n_bytes;dws->rx=transfer->rx_buf;-dws->rx_end=dws->rx+transfer->len;-dws->len=transfer->len;+dws->rx_len=dws->tx_len;-/* Ensure dw->rx and dw->rx_end are visible */+/* Ensure the data above is visible for all CPUs */smp_mb();spi_enable_chip(dws,0);
@@ -352,7 +349,7 @@ static int dw_spi_transfer_one(struct spi_controller *master,returnret;}}else{-txlevel=min_t(u16,dws->fifo_len/2,dws->len/dws->n_bytes);+txlevel=min_t(u16,dws->fifo_len/2,dws->tx_len);dw_writel(dws,DW_SPI_TXFTLR,txlevel);/* Set the interrupt mask */
@@ -147,11 +147,10 @@ struct dw_spi {void(*set_cs)(structspi_device*spi,boolenable);/* Current message transfer state info */-size_tlen;void*tx;-void*tx_end;+unsignedinttx_len;void*rx;-void*rx_end;+unsignedintrx_len;intdma_mapped;u8n_bytes;/* current is a 1/2 bytes op */irqreturn_t(*transfer_handler)(structdw_spi*dws);
Baikal-T1 is equipped with three DW APB SSI-based MMIO SPI controllers.
Two of them are pretty much normal: with IRQ, DMA, FIFOs of 64 words
depth, 4x CSs, but the third one as being a part of the Baikal-T1 System
Boot Controller has got a very limited resources: no IRQ, no DMA, only a
single native chip-select and Tx/Rx FIFO with just 8 words depth
available. In order to provide a transparent initial boot code execution
the Boot SPI controller is also utilized by an vendor-specific IP-block,
which exposes an SPI flash direct mapping interface. Since both direct
mapping and SPI controller normal utilization are mutual exclusive only
one of these interfaces can be used to access an external SPI slave
device. That's why a dedicated mux is embedded into the System Boot
Controller. All of that is taken into account in the Baikal-T1-specific DW
APB SSI glue driver implemented by means of the DW SPI core module.
Co-developed-by: Ramil Zaripov <redacted>
Signed-off-by: Ramil Zaripov <redacted>
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/Kconfig | 28 ++++
drivers/spi/Makefile | 1 +
drivers/spi/spi-dw-bt1.c | 339 +++++++++++++++++++++++++++++++++++++++
3 files changed, 368 insertions(+)
create mode 100644 drivers/spi/spi-dw-bt1.c
DW APB SSI controller can be used by the two SPI core interfaces:
traditional SPI transfers and SPI memory operations. The controller needs
to be accordingly configured at runtime when the corresponding operations
are executed. In order to do that for the both interfaces from a single
function we introduce a new data wrapper for the transfer mode, data
width, number of data frames (for the automatic data transfer) and the bus
frequency. It will be used by the update_config() method to tune the DW
APB SSI up.
The update_config() method is made exported to be used not only by the DW
SPI core driver, but by the glue layer drivers too. This will be required
in a coming further commit.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 29 +++++++++++++++++------------
drivers/spi/spi-dw.h | 10 ++++++++++
2 files changed, 27 insertions(+), 12 deletions(-)
It's pointless to enable the chip back if the DMA setup procedure fails,
since we'll disable it on the next transfer anyway. For the same reason We
don't do that in case of a failure detected in any other methods called
from the transfer_one() method.
While at it consider any non-zero value returned from the dma_setup
callback to be erroneous as it's supposed to be in the kernel.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
Current IRQ-based SPI transfer execution procedure doesn't work well at
the final stage of the execution. If all the Tx data is sent out (written
to the Tx FIFO) but there is some data left to receive, the Tx FIFO Empty
IRQ will constantly happen until all of the requested inbound data is
received. Though for a short period of time, but it will make the system
less responsive. In order to fix that let's refactor the SPI transfer
execution procedure by taking the Rx FIFO Full IRQ into account. We'll read
and write SPI transfer data each time the IRQ happens as before. If all
the outbound data is sent out, we'll disable the Tx FIFO Empty IRQ. If
there is still some data to receive, we'll adjust the Rx FIFO Threshold
level, so the next IRQ would be raised at the moment of all incoming data
being available in the Rx FIFO.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 33 ++++++++++++++++++++++++---------
1 file changed, 24 insertions(+), 9 deletions(-)
@@ -189,17 +189,30 @@ static irqreturn_t interrupt_transfer(struct dw_spi *dws)returnIRQ_HANDLED;}+/*+*ReaddatafromtheRxFIFOeverytimewe'vegotachanceexecuting+*thismethod.Ifthereisnothinglefttoreceive,terminatethe+*procedure.OtherwiseadjusttheRxFIFOThresholdlevelifit'sa+*finalstageofthetransfer.Bydoingsowe'llgetthenextIRQ+*rightwhentheleftoverincomingdataisreceived.+*/dw_reader(dws);if(!dws->rx_len){spi_mask_intr(dws,0xff);spi_finalize_current_transfer(dws->master);-returnIRQ_HANDLED;+}elseif(dws->rx_len<=dw_readl(dws,DW_SPI_RXFTLR)){+dw_writel(dws,DW_SPI_RXFTLR,dws->rx_len-1);}++/*+*SenddataoutifTxFIFOEmptyIRQisreceived.TheIRQwillbe+*disabledafterthedatatransmissionisfinishedsonotto+*havetheTXEIRQfloodatthefinalstageofthetransfer.+*/if(irq_status&SPI_INT_TXEI){-spi_mask_intr(dws,SPI_INT_TXEI);dw_writer(dws);-/* Enable TX irq always, it will be disabled when RX finished */-spi_umask_intr(dws,SPI_INT_TXEI);+if(!dws->tx_len)+spi_mask_intr(dws,SPI_INT_TXEI);}returnIRQ_HANDLED;
@@ -338,10 +351,6 @@ static int dw_spi_transfer_one(struct spi_controller *master,/* For poll mode just disable all interrupts */spi_mask_intr(dws,0xff);-/*-*Interruptmode-*weonlyneedsettheTXEIIRQ,asTX/RXalwayshappensyncronizely-*/if(dws->dma_mapped){ret=dws->dma_ops->dma_setup(dws,transfer);if(ret<0){
@@ -349,12 +358,18 @@ static int dw_spi_transfer_one(struct spi_controller *master,returnret;}}else{+/*+*OriginallyTxandRxdatalengthsmatch.RxFIFOThresholdlevel+*willbeadjustedatthefinalstageoftheIRQ-basedSPItransfer+*executionsonottolosetheleftoveroftheincomingdata.+*/txlevel=min_t(u16,dws->fifo_len/2,dws->tx_len);dw_writel(dws,DW_SPI_TXFTLR,txlevel);+dw_writel(dws,DW_SPI_RXFTLR,txlevel-1);/* Set the interrupt mask */imask|=SPI_INT_TXEI|SPI_INT_TXOI|-SPI_INT_RXUI|SPI_INT_RXOI;+SPI_INT_RXUI|SPI_INT_RXOI|SPI_INT_RXFI;spi_umask_intr(dws,imask);dws->transfer_handler=interrupt_transfer;
The SPI bus speed update functionality will be useful in another parts of
the driver too (like to implement the SPI memory operations and from the
DW SPI glue layers). Let's move it to the update_cr0() method then and
since the later is now updating not only the CTRLR0 register alter its
prototype to have a generic function name not related to CR0.
Leave the too long line with the chip->clk_div setting as is for now,
since it's going to be changed later anyway.
Signed-off-by: Serge Semin <redacted>
---
drivers/spi/spi-dw-core.c | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
@@ -286,6 +286,17 @@ static void dw_spi_update_cr0(struct dw_spi *dws, struct spi_device *spi,cr0|=chip->tmode<<DWC_SSI_CTRLR0_TMOD_OFFSET;dw_writel(dws,DW_SPI_CTRLR0,cr0);++/* Handle per transfer options for bpw and speed */+if(transfer->speed_hz!=dws->current_freq){+if(transfer->speed_hz!=chip->speed_hz){+/* clk_div doesn't support odd number */+chip->clk_div=(DIV_ROUND_UP(dws->max_freq,transfer->speed_hz)+1)&0xfffe;+chip->speed_hz=transfer->speed_hz;+}+dws->current_freq=transfer->speed_hz;+spi_set_clk(dws,chip->clk_div);+}}staticintdw_spi_transfer_one(structspi_controller*master,
@@ -310,21 +321,10 @@ static int dw_spi_transfer_one(struct spi_controller *master,spi_enable_chip(dws,0);-/* Handle per transfer options for bpw and speed */-if(transfer->speed_hz!=dws->current_freq){-if(transfer->speed_hz!=chip->speed_hz){-/* clk_div doesn't support odd number */-chip->clk_div=(DIV_ROUND_UP(dws->max_freq,transfer->speed_hz)+1)&0xfffe;-chip->speed_hz=transfer->speed_hz;-}-dws->current_freq=transfer->speed_hz;-spi_set_clk(dws,chip->clk_div);-}+dw_spi_update_config(dws,spi,transfer);transfer->effective_speed_hz=dws->max_freq/chip->clk_div;-dw_spi_update_cr0(dws,spi,transfer);-/* Check if current transfer is a DMA transaction */if(master->can_dma&&master->can_dma(master,spi,transfer))dws->dma_mapped=master->cur_msg_mapped;
Currently DWC SSI core is supported by means of setting up the
core-specific update_cr0() callback. It isn't suitable for multiple
reasons. First of all having exported several methods doing the same thing
but for different chips makes the code harder to maintain. Secondly the
spi-dw-core driver exports the methods, then the spi-dw-mmio driver sets
the private data callback with one of them so to be called by the core
driver again. That makes the code logic too complicated. Thirdly using
callbacks for just updating the CR0 register is problematic, since in case
if the register needed to be updated from different parts of the code,
we'd have to create another callback (for instance the SPI device-specific
parameters don't need to be calculated each time the SPI transfer is
submitted, so it's better to pre-calculate the CR0 data at the SPI-device
setup stage).
So keeping all the above in mind let's discard the update_cr0() callbacks,
define a generic and static dw_spi_update_cr0() method and create the
DW_SPI_CAP_DWC_SSI capability, which when enabled would activate the
alternative CR0 register layout.
While at it add the comments to the code path of the normal DW APB SSI
controller setup to make the dw_spi_update_cr0() method looking coherent.
Signed-off-by: Serge Semin <redacted>
---
Changelog v2:
- Get back the in-code comments to the dw_spi_update_cr0() method and it'
further derivatives.
Changelog v3:
- Remove dw_spi_update_cr0() callback assignment from the DW APB SSI PCI
glue-driver.
---
drivers/spi/spi-dw-core.c | 80 ++++++++++++++++++---------------------
drivers/spi/spi-dw-mmio.c | 20 +---------
drivers/spi/spi-dw-pci.c | 6 ---
drivers/spi/spi-dw.h | 9 +----
4 files changed, 40 insertions(+), 75 deletions(-)
@@ -228,60 +228,56 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id)returndws->transfer_handler(dws);}-/* Configure CTRLR0 for DW_apb_ssi */-u32dw_spi_update_cr0(structspi_controller*master,structspi_device*spi,-structspi_transfer*transfer)+staticvoiddw_spi_update_cr0(structdw_spi*dws,structspi_device*spi,+structspi_transfer*transfer){structchip_data*chip=spi_get_ctldata(spi);u32cr0;-/* Default SPI mode is SCPOL = 0, SCPH = 0 */-cr0=(transfer->bits_per_word-1)-|(SSI_MOTO_SPI<<SPI_FRF_OFFSET)-|((((spi->mode&SPI_CPOL)?1:0)<<SPI_SCOL_OFFSET)|-(((spi->mode&SPI_CPHA)?1:0)<<SPI_SCPH_OFFSET)|-(((spi->mode&SPI_LOOP)?1:0)<<SPI_SRL_OFFSET))-|(chip->tmode<<SPI_TMOD_OFFSET);+/* CTRLR0[ 4/3: 0] Data Frame Size */+cr0=(transfer->bits_per_word-1);-returncr0;-}-EXPORT_SYMBOL_GPL(dw_spi_update_cr0);+if(!(dws->caps&DW_SPI_CAP_DWC_SSI)){+/* CTRLR0[ 5: 4] Frame Format */+cr0|=SSI_MOTO_SPI<<SPI_FRF_OFFSET;-/* Configure CTRLR0 for DWC_ssi */-u32dw_spi_update_cr0_v1_01a(structspi_controller*master,-structspi_device*spi,-structspi_transfer*transfer)-{-structdw_spi*dws=spi_controller_get_devdata(master);-structchip_data*chip=spi_get_ctldata(spi);-u32cr0;+/*+*SPImode(SCPOL|SCPH)+*CTRLR0[6]SerialClockPhase+*CTRLR0[7]SerialClockPolarity+*/+cr0|=((spi->mode&SPI_CPOL)?1:0)<<SPI_SCOL_OFFSET;+cr0|=((spi->mode&SPI_CPHA)?1:0)<<SPI_SCPH_OFFSET;-/* CTRLR0[ 4: 0] Data Frame Size */-cr0=(transfer->bits_per_word-1);+/* CTRLR0[11] Shift Register Loop */+cr0|=((spi->mode&SPI_LOOP)?1:0)<<SPI_SRL_OFFSET;-/* CTRLR0[ 7: 6] Frame Format */-cr0|=SSI_MOTO_SPI<<DWC_SSI_CTRLR0_FRF_OFFSET;+/* CTRLR0[ 9:8] Transfer Mode */+cr0|=chip->tmode<<SPI_TMOD_OFFSET;+}else{+/* CTRLR0[ 7: 6] Frame Format */+cr0|=SSI_MOTO_SPI<<DWC_SSI_CTRLR0_FRF_OFFSET;-/*-*SPImode(SCPOL|SCPH)-*CTRLR0[8]SerialClockPhase-*CTRLR0[9]SerialClockPolarity-*/-cr0|=((spi->mode&SPI_CPOL)?1:0)<<DWC_SSI_CTRLR0_SCPOL_OFFSET;-cr0|=((spi->mode&SPI_CPHA)?1:0)<<DWC_SSI_CTRLR0_SCPH_OFFSET;+/*+*SPImode(SCPOL|SCPH)+*CTRLR0[8]SerialClockPhase+*CTRLR0[9]SerialClockPolarity+*/+cr0|=((spi->mode&SPI_CPOL)?1:0)<<DWC_SSI_CTRLR0_SCPOL_OFFSET;+cr0|=((spi->mode&SPI_CPHA)?1:0)<<DWC_SSI_CTRLR0_SCPH_OFFSET;-/* CTRLR0[11:10] Transfer Mode */-cr0|=chip->tmode<<DWC_SSI_CTRLR0_TMOD_OFFSET;+/* CTRLR0[13] Shift Register Loop */+cr0|=((spi->mode&SPI_LOOP)?1:0)<<DWC_SSI_CTRLR0_SRL_OFFSET;-/* CTRLR0[13] Shift Register Loop */-cr0|=((spi->mode&SPI_LOOP)?1:0)<<DWC_SSI_CTRLR0_SRL_OFFSET;+/* CTRLR0[11:10] Transfer Mode */+cr0|=chip->tmode<<DWC_SSI_CTRLR0_TMOD_OFFSET;-if(dws->caps&DW_SPI_CAP_KEEMBAY_MST)-cr0|=DWC_SSI_CTRLR0_KEEMBAY_MST;+if(dws->caps&DW_SPI_CAP_KEEMBAY_MST)+cr0|=DWC_SSI_CTRLR0_KEEMBAY_MST;+}-returncr0;+dw_writel(dws,DW_SPI_CTRLR0,cr0);}-EXPORT_SYMBOL_GPL(dw_spi_update_cr0_v1_01a);staticintdw_spi_transfer_one(structspi_controller*master,structspi_device*spi,structspi_transfer*transfer)
@@ -290,7 +286,6 @@ static int dw_spi_transfer_one(struct spi_controller *master,structchip_data*chip=spi_get_ctldata(spi);u8imask=0;u16txlevel=0;-u32cr0;intret;dws->dma_mapped=0;
@@ -319,8 +314,7 @@ static int dw_spi_transfer_one(struct spi_controller *master,transfer->effective_speed_hz=dws->max_freq/chip->clk_div;-cr0=dws->update_cr0(master,spi,transfer);-dw_writel(dws,DW_SPI_CTRLR0,cr0);+dw_spi_update_cr0(dws,spi,transfer);/* Check if current transfer is a DMA transaction */if(master->can_dma&&master->can_dma(master,spi,transfer))
@@ -136,8 +137,6 @@ struct dw_spi {u16bus_num;u16num_cs;/* supported slave numbers */void(*set_cs)(structspi_device*spi,boolenable);-u32(*update_cr0)(structspi_controller*master,structspi_device*spi,-structspi_transfer*transfer);/* Current message transfer state info */size_tlen;
From: Mark Brown <broonie@kernel.org> Date: 2020-10-08 22:02:09
On Thu, 8 Oct 2020 02:54:49 +0300, Serge Semin wrote:
Originally I intended to merge a dedicated Baikal-T1 System Boot SPI
Controller driver into the kernel and leave the DW APB SSI driver
untouched. But after a long discussion (see the link at the bottom of the
letter) Mark and Andy persuaded me to integrate what we developed there
into the DW APB SSI core driver to be useful for another controllers,
which may have got the same peculiarities/problems as ours:
- No IRQ.
- No DMA.
- No GPIO CS, so a native CS is utilized.
- small Tx/Rx FIFO depth.
- Automatic CS assertion/de-assertion.
- Slow system bus.
All of them have been fixed in the framework of this patchset in some
extent at least for the SPI memory operations. As I expected it wasn't
that easy and the integration took that many patches as you can see from
the subject. Though some of them are mere cleanups or weakly related with
the subject fixes, but we just couldn't leave the code as is at some
places since we were working with the DW APB SSI driver anyway. Here is
what we did to fix the original DW APB SSI driver, to make it less messy.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next
Thanks!
[01/21] spi: dw: Use an explicit set_cs assignment
commit: f68fe8de360b9c441caf22f35557b4c9fd97dd84
[02/21] spi: dw: Add DWC SSI capability
commit: d6bbd1193fe93b7ee037724553b3574dcb48e6da
[03/21] spi: dw: Detach SPI device specific CR0 config method
commit: a3577bd8cba554f962b6af082eb43dde7fe7cd09
[04/21] spi: dw: Update SPI bus speed in a config function
commit: f76f3142c5fc90f67794f6649cecec86a6eb87b0
[05/21] spi: dw: Simplify the SPI bus speed config procedure
commit: c449ad7425aa2eb58f275ce977130918827b0d20
[06/21] spi: dw: Update Rx sample delay in the config function
commit: 2613d2bfbeacea2bc796a54219ba05385ae7436a
[07/21] spi: dw: Add DW SPI controller config structure
commit: 3ff60c6b644e2002e062ed97825ead19e31c2769
[08/21] spi: dw: Refactor data IO procedure
commit: 8dedbeac8ab24d2da9271df2c8291971169846f2
[09/21] spi: dw: Refactor IRQ-based SPI transfer procedure
commit: ddcc2733c1591c137f7ce60f24ba5401c295427f
[10/21] spi: dw: Perform IRQ setup in a dedicated function
commit: 82d02944d238a6fdac729d135623b1d88ca8cbd6
[11/21] spi: dw: Unmask IRQs after enabling the chip
commit: da8f58909e7e047a01e4577807e648482672eddd
[12/21] spi: dw: Discard chip enabling on DMA setup error
commit: c6cb3815f70d39e377bec6b44d25f2d8b68b324e
[13/21] spi: dw: De-assert chip-select on reset
commit: fbddc989a5c441099978aad320ada0d5327309f4
[14/21] spi: dw: Explicitly de-assert CS on SPI transfer completion
commit: 49d7d695ca4bb2f62290c7039c4165556f0ca1e4
[15/21] spi: dw: Move num-of retries parameter to the header file
commit: cf75baeac72c7cb57a8cf781e90cfd8ea77f2d51
[16/21] spi: dw: Add generic DW SSI status-check method
commit: bf64b66036eef7d5a92fb1cb7398ef67a29fc64b
[17/21] spi: dw: Add memory operations support
commit: 6423207e57ea53826eaae1a14c14fd6d22561b06
[18/21] spi: dw: Introduce max mem-ops SPI bus frequency setting
commit: 84ecaf4a7837e8c0957a59d77fd7e8e4926968cb
[19/21] spi: dw: Add poll-based SPI transfers support
commit: 14345c33461bc2373bc4f75f40baf4650e95ee54
[20/21] spi: dw: Add Baikal-T1 SPI Controller bindings
commit: ca4e2ac20f938c372b83d1cb16ec00f7c89191df
[21/21] spi: dw: Add Baikal-T1 SPI Controller glue driver
commit: abf00907538e21c469a10809dc2991982673fcbf
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark