mirror of
https://github.com/edk2-porting/linux-next.git
synced 2025-01-04 03:33:58 +08:00
9d36215250
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" Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru> Cc: Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru> Cc: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru> Cc: Andy Shevchenko <andy.shevchenko@gmail.com> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Cc: Lars Povlsen <lars.povlsen@microchip.com> Cc: wuxu.wu <wuxu.wu@huawei.com> Cc: Feng Tang <feng.tang@intel.com> 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 (30): spi: dw: Discard IRQ threshold macro spi: dw: Use ternary op to init set_cs callback spi: dw: Initialize n_bytes before the memory barrier Revert: spi: spi-dw: Add lock protect dw_spi rx/tx to prevent concurrent calls spi: dw: Clear IRQ status on DW SPI controller reset spi: dw: Disable all IRQs when controller is unused spi: dw: Use relaxed IO-methods to access FIFOs spi: dw: Discard DW SSI chip type storages spi: dw: Convert CS-override to DW SPI capabilities spi: dw: Add KeemBay Master capability 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 | 642 ++++++++++++++---- drivers/spi/spi-dw-dma.c | 16 +- drivers/spi/spi-dw-mmio.c | 36 +- drivers/spi/spi-dw.h | 85 ++- 8 files changed, 960 insertions(+), 221 deletions(-) create mode 100644 drivers/spi/spi-dw-bt1.c -- 2.27.0
278 lines
7.1 KiB
C
278 lines
7.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef DW_SPI_HEADER_H
|
|
#define DW_SPI_HEADER_H
|
|
|
|
#include <linux/bits.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/irqreturn.h>
|
|
#include <linux/io.h>
|
|
#include <linux/scatterlist.h>
|
|
|
|
/* Register offsets */
|
|
#define DW_SPI_CTRLR0 0x00
|
|
#define DW_SPI_CTRLR1 0x04
|
|
#define DW_SPI_SSIENR 0x08
|
|
#define DW_SPI_MWCR 0x0c
|
|
#define DW_SPI_SER 0x10
|
|
#define DW_SPI_BAUDR 0x14
|
|
#define DW_SPI_TXFTLR 0x18
|
|
#define DW_SPI_RXFTLR 0x1c
|
|
#define DW_SPI_TXFLR 0x20
|
|
#define DW_SPI_RXFLR 0x24
|
|
#define DW_SPI_SR 0x28
|
|
#define DW_SPI_IMR 0x2c
|
|
#define DW_SPI_ISR 0x30
|
|
#define DW_SPI_RISR 0x34
|
|
#define DW_SPI_TXOICR 0x38
|
|
#define DW_SPI_RXOICR 0x3c
|
|
#define DW_SPI_RXUICR 0x40
|
|
#define DW_SPI_MSTICR 0x44
|
|
#define DW_SPI_ICR 0x48
|
|
#define DW_SPI_DMACR 0x4c
|
|
#define DW_SPI_DMATDLR 0x50
|
|
#define DW_SPI_DMARDLR 0x54
|
|
#define DW_SPI_IDR 0x58
|
|
#define DW_SPI_VERSION 0x5c
|
|
#define DW_SPI_DR 0x60
|
|
#define DW_SPI_RX_SAMPLE_DLY 0xf0
|
|
#define DW_SPI_CS_OVERRIDE 0xf4
|
|
|
|
/* Bit fields in CTRLR0 */
|
|
#define SPI_DFS_OFFSET 0
|
|
|
|
#define SPI_FRF_OFFSET 4
|
|
#define SPI_FRF_SPI 0x0
|
|
#define SPI_FRF_SSP 0x1
|
|
#define SPI_FRF_MICROWIRE 0x2
|
|
#define SPI_FRF_RESV 0x3
|
|
|
|
#define SPI_MODE_OFFSET 6
|
|
#define SPI_SCPH_OFFSET 6
|
|
#define SPI_SCOL_OFFSET 7
|
|
|
|
#define SPI_TMOD_OFFSET 8
|
|
#define SPI_TMOD_MASK (0x3 << SPI_TMOD_OFFSET)
|
|
#define SPI_TMOD_TR 0x0 /* xmit & recv */
|
|
#define SPI_TMOD_TO 0x1 /* xmit only */
|
|
#define SPI_TMOD_RO 0x2 /* recv only */
|
|
#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */
|
|
|
|
#define SPI_SLVOE_OFFSET 10
|
|
#define SPI_SRL_OFFSET 11
|
|
#define SPI_CFS_OFFSET 12
|
|
|
|
/* Bit fields in CTRLR0 based on DWC_ssi_databook.pdf v1.01a */
|
|
#define DWC_SSI_CTRLR0_SRL_OFFSET 13
|
|
#define DWC_SSI_CTRLR0_TMOD_OFFSET 10
|
|
#define DWC_SSI_CTRLR0_TMOD_MASK GENMASK(11, 10)
|
|
#define DWC_SSI_CTRLR0_SCPOL_OFFSET 9
|
|
#define DWC_SSI_CTRLR0_SCPH_OFFSET 8
|
|
#define DWC_SSI_CTRLR0_FRF_OFFSET 6
|
|
#define DWC_SSI_CTRLR0_DFS_OFFSET 0
|
|
|
|
/*
|
|
* For Keem Bay, CTRLR0[31] is used to select controller mode.
|
|
* 0: SSI is slave
|
|
* 1: SSI is master
|
|
*/
|
|
#define DWC_SSI_CTRLR0_KEEMBAY_MST BIT(31)
|
|
|
|
/* Bit fields in SR, 7 bits */
|
|
#define SR_MASK 0x7f /* cover 7 bits */
|
|
#define SR_BUSY (1 << 0)
|
|
#define SR_TF_NOT_FULL (1 << 1)
|
|
#define SR_TF_EMPT (1 << 2)
|
|
#define SR_RF_NOT_EMPT (1 << 3)
|
|
#define SR_RF_FULL (1 << 4)
|
|
#define SR_TX_ERR (1 << 5)
|
|
#define SR_DCOL (1 << 6)
|
|
|
|
/* Bit fields in ISR, IMR, RISR, 7 bits */
|
|
#define SPI_INT_TXEI (1 << 0)
|
|
#define SPI_INT_TXOI (1 << 1)
|
|
#define SPI_INT_RXUI (1 << 2)
|
|
#define SPI_INT_RXOI (1 << 3)
|
|
#define SPI_INT_RXFI (1 << 4)
|
|
#define SPI_INT_MSTI (1 << 5)
|
|
|
|
/* Bit fields in DMACR */
|
|
#define SPI_DMA_RDMAE (1 << 0)
|
|
#define SPI_DMA_TDMAE (1 << 1)
|
|
|
|
enum dw_ssi_type {
|
|
SSI_MOTO_SPI = 0,
|
|
SSI_TI_SSP,
|
|
SSI_NS_MICROWIRE,
|
|
};
|
|
|
|
/* DW SPI capabilities */
|
|
#define DW_SPI_CAP_CS_OVERRIDE BIT(0)
|
|
#define DW_SPI_CAP_KEEMBAY_MST BIT(1)
|
|
|
|
struct dw_spi;
|
|
struct dw_spi_dma_ops {
|
|
int (*dma_init)(struct device *dev, struct dw_spi *dws);
|
|
void (*dma_exit)(struct dw_spi *dws);
|
|
int (*dma_setup)(struct dw_spi *dws, struct spi_transfer *xfer);
|
|
bool (*can_dma)(struct spi_controller *master, struct spi_device *spi,
|
|
struct spi_transfer *xfer);
|
|
int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer);
|
|
void (*dma_stop)(struct dw_spi *dws);
|
|
};
|
|
|
|
struct dw_spi {
|
|
struct spi_controller *master;
|
|
|
|
void __iomem *regs;
|
|
unsigned long paddr;
|
|
int irq;
|
|
u32 fifo_len; /* depth of the FIFO buffer */
|
|
u32 max_freq; /* max bus freq supported */
|
|
|
|
u32 caps; /* DW SPI capabilities */
|
|
|
|
u32 reg_io_width; /* DR I/O width in bytes */
|
|
u16 bus_num;
|
|
u16 num_cs; /* supported slave numbers */
|
|
void (*set_cs)(struct spi_device *spi, bool enable);
|
|
u32 (*update_cr0)(struct spi_controller *master, struct spi_device *spi,
|
|
struct spi_transfer *transfer);
|
|
|
|
/* Current message transfer state info */
|
|
size_t len;
|
|
void *tx;
|
|
void *tx_end;
|
|
void *rx;
|
|
void *rx_end;
|
|
int dma_mapped;
|
|
u8 n_bytes; /* current is a 1/2 bytes op */
|
|
irqreturn_t (*transfer_handler)(struct dw_spi *dws);
|
|
u32 current_freq; /* frequency in hz */
|
|
u32 cur_rx_sample_dly;
|
|
u32 def_rx_sample_dly_ns;
|
|
|
|
/* DMA info */
|
|
struct dma_chan *txchan;
|
|
u32 txburst;
|
|
struct dma_chan *rxchan;
|
|
u32 rxburst;
|
|
u32 dma_sg_burst;
|
|
unsigned long dma_chan_busy;
|
|
dma_addr_t dma_addr; /* phy address of the Data register */
|
|
const struct dw_spi_dma_ops *dma_ops;
|
|
struct completion dma_completion;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
struct dentry *debugfs;
|
|
struct debugfs_regset32 regset;
|
|
#endif
|
|
};
|
|
|
|
static inline u32 dw_readl(struct dw_spi *dws, u32 offset)
|
|
{
|
|
return __raw_readl(dws->regs + offset);
|
|
}
|
|
|
|
static inline void dw_writel(struct dw_spi *dws, u32 offset, u32 val)
|
|
{
|
|
__raw_writel(val, dws->regs + offset);
|
|
}
|
|
|
|
static inline u32 dw_read_io_reg(struct dw_spi *dws, u32 offset)
|
|
{
|
|
switch (dws->reg_io_width) {
|
|
case 2:
|
|
return readw_relaxed(dws->regs + offset);
|
|
case 4:
|
|
default:
|
|
return readl_relaxed(dws->regs + offset);
|
|
}
|
|
}
|
|
|
|
static inline void dw_write_io_reg(struct dw_spi *dws, u32 offset, u32 val)
|
|
{
|
|
switch (dws->reg_io_width) {
|
|
case 2:
|
|
writew_relaxed(val, dws->regs + offset);
|
|
break;
|
|
case 4:
|
|
default:
|
|
writel_relaxed(val, dws->regs + offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline void spi_enable_chip(struct dw_spi *dws, int enable)
|
|
{
|
|
dw_writel(dws, DW_SPI_SSIENR, (enable ? 1 : 0));
|
|
}
|
|
|
|
static inline void spi_set_clk(struct dw_spi *dws, u16 div)
|
|
{
|
|
dw_writel(dws, DW_SPI_BAUDR, div);
|
|
}
|
|
|
|
/* Disable IRQ bits */
|
|
static inline void spi_mask_intr(struct dw_spi *dws, u32 mask)
|
|
{
|
|
u32 new_mask;
|
|
|
|
new_mask = dw_readl(dws, DW_SPI_IMR) & ~mask;
|
|
dw_writel(dws, DW_SPI_IMR, new_mask);
|
|
}
|
|
|
|
/* Enable IRQ bits */
|
|
static inline void spi_umask_intr(struct dw_spi *dws, u32 mask)
|
|
{
|
|
u32 new_mask;
|
|
|
|
new_mask = dw_readl(dws, DW_SPI_IMR) | mask;
|
|
dw_writel(dws, DW_SPI_IMR, new_mask);
|
|
}
|
|
|
|
/*
|
|
* This disables the SPI controller, interrupts, clears the interrupts status,
|
|
* and re-enable the controller back. Transmit and receive FIFO buffers are
|
|
* cleared when the device is disabled.
|
|
*/
|
|
static inline void spi_reset_chip(struct dw_spi *dws)
|
|
{
|
|
spi_enable_chip(dws, 0);
|
|
spi_mask_intr(dws, 0xff);
|
|
dw_readl(dws, DW_SPI_ICR);
|
|
spi_enable_chip(dws, 1);
|
|
}
|
|
|
|
static inline void spi_shutdown_chip(struct dw_spi *dws)
|
|
{
|
|
spi_enable_chip(dws, 0);
|
|
spi_set_clk(dws, 0);
|
|
}
|
|
|
|
extern void dw_spi_set_cs(struct spi_device *spi, bool enable);
|
|
extern int dw_spi_add_host(struct device *dev, struct dw_spi *dws);
|
|
extern void dw_spi_remove_host(struct dw_spi *dws);
|
|
extern int dw_spi_suspend_host(struct dw_spi *dws);
|
|
extern int dw_spi_resume_host(struct dw_spi *dws);
|
|
extern u32 dw_spi_update_cr0(struct spi_controller *master,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *transfer);
|
|
extern u32 dw_spi_update_cr0_v1_01a(struct spi_controller *master,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *transfer);
|
|
|
|
#ifdef CONFIG_SPI_DW_DMA
|
|
|
|
extern void dw_spi_dma_setup_mfld(struct dw_spi *dws);
|
|
extern void dw_spi_dma_setup_generic(struct dw_spi *dws);
|
|
|
|
#else
|
|
|
|
static inline void dw_spi_dma_setup_mfld(struct dw_spi *dws) {}
|
|
static inline void dw_spi_dma_setup_generic(struct dw_spi *dws) {}
|
|
|
|
#endif /* !CONFIG_SPI_DW_DMA */
|
|
|
|
#endif /* DW_SPI_HEADER_H */
|