// SPDX-License-Identifier: GPL-2.0-only
/*
* Designware SPI core controller driver (refer pxa2xx_spi.c)
*
* Copyright (c) 2009, Intel Corporation.
*/
#include <linux/bitfield.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/preempt.h>
#include <linux/highmem.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <linux/string.h>
#include <linux/of.h>
#include "spi-dw.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#endif
/* Slave spi_device related */
struct dw_spi_chip_data {
u32 cr0;
u32 rx_sample_dly; /* RX sample delay */
};
#ifdef CONFIG_DEBUG_FS
#define DW_SPI_DBGFS_REG(_name, _off) \
{ \
.name = _name, \
.offset = _off, \
}
static const struct debugfs_reg32 dw_spi_dbgfs_regs[] = {
DW_SPI_DBGFS_REG("CTRLR0", DW_SPI_CTRLR0),
DW_SPI_DBGFS_REG("CTRLR1", DW_SPI_CTRLR1),
DW_SPI_DBGFS_REG("SSIENR", DW_SPI_SSIENR),
DW_SPI_DBGFS_REG("SER", DW_SPI_SER),
DW_SPI_DBGFS_REG("BAUDR", DW_SPI_BAUDR),
DW_SPI_DBGFS_REG("TXFTLR", DW_SPI_TXFTLR),
DW_SPI_DBGFS_REG("RXFTLR", DW_SPI_RXFTLR),
DW_SPI_DBGFS_REG("TXFLR", DW_SPI_TXFLR),
DW_SPI_DBGFS_REG("RXFLR", DW_SPI_RXFLR),
DW_SPI_DBGFS_REG("SR", DW_SPI_SR),
DW_SPI_DBGFS_REG("IMR", DW_SPI_IMR),
DW_SPI_DBGFS_REG("ISR", DW_SPI_ISR),
DW_SPI_DBGFS_REG("DMACR", DW_SPI_DMACR),
DW_SPI_DBGFS_REG("DMATDLR", DW_SPI_DMATDLR),
DW_SPI_DBGFS_REG("DMARDLR", DW_SPI_DMARDLR),
DW_SPI_DBGFS_REG("RX_SAMPLE_DLY", DW_SPI_RX_SAMPLE_DLY),
};
static void dw_spi_debugfs_init(struct dw_spi *dws)
{
char name[32];
snprintf(name, 32, "dw_spi%d", dws->host->bus_num);
dws->debugfs = debugfs_create_dir(name, NULL);
dws->regset.regs = dw_spi_dbgfs_regs;
dws->regset.nregs = ARRAY_SIZE(dw_spi_dbgfs_regs);
dws->regset.base = dws->regs;
debugfs_create_regset32("registers", 0400, dws->debugfs, &dws->regset);
}
static void dw_spi_debugfs_remove(struct dw_spi *dws)
{
debugfs_remove_recursive(dws->debugfs);
}
#else
static inline void dw_spi_debugfs_init(struct dw_spi *dws)
{
}
static inline void dw_spi_debugfs_remove(struct dw_spi *dws)
{
}
#endif /* CONFIG_DEBUG_FS */
void dw_spi_set_cs(struct spi_device *spi, bool enable)
{
struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
bool cs_high = !!(spi->mode & SPI_CS_HIGH);
/*
* DW SPI controller demands any native CS being set in order to
* proceed with data transfer. So in order to activate the SPI
* communications we must set a corresponding bit in the Slave
* Enable register no matter whether the SPI core is configured to
* support active-high or active-low CS level.
*/
if (cs_high == enable)
dw_writel(dws, DW_SPI_SER, BIT(spi_get_chipselect(spi, 0)));
else
dw_writel(dws, DW_SPI_SER, 0);
}
EXPORT_SYMBOL_NS_GPL(dw_spi_set_cs, SPI_DW_CORE);
/* Return the max entries we can fill into tx fifo */
static inline u32 dw_spi_tx_max(struct dw_spi *dws)
{
u32 tx_room, rxtx_gap;
tx_room = dws->fifo_len - dw_readl(dws, DW_SPI_TXFLR);
/*
* Another concern is about the tx/rx mismatch, we
* though to use (dws->fifo_len - rxflr - txflr) as
* one maximum value for tx, but it doesn't cover the
* data which is out of tx/rx fifo and inside the
* shift registers. So a control from sw point of
* view is taken.
*/
rxtx_gap = dws->fifo_len - (dws->rx_len - dws->tx_len);
return min3((u32)dws->tx_len, tx_room, rxtx_gap);
}
/* Return the max entries we should read out of rx fifo */
static inline u32 dw_spi_rx_max(struct dw_spi *dws)
{
return min_t(u32, dws->rx_len, dw_readl(dws, DW_SPI_RXFLR));
}
static void dw_writer(struct dw_spi *dws)
{
u32 max = dw_spi_tx_max(dws);
u32 txw = 0;
while (max--) {
if (dws->tx) {
if (dws->n_bytes == 1)
txw = *(u8 *)(dws->tx);
else if (dws->n_bytes == 2)
txw = *(u16 *)(dws->tx);
else
txw = *(u32 *)(dws->tx);
dws->tx += dws->n_bytes;
}
dw_write_io_reg(dws, DW_SPI_DR, txw);
--dws->tx_len;
}
}
static void dw_reader(struct dw_spi *dws)
{
u32 max = dw_spi_rx_max(dws);
u32 rxw;
while (max--) {
rxw = dw_read_io_reg(dws, DW_SPI_DR);
if (dws->rx) {
if (dws->n_bytes == 1)
*(u8 *)(dws->rx) = rxw;
else if (dws->n_bytes == 2)
*(u16 *)(dws->rx) = rxw;
else
*(u32 *)(dws->rx) = rxw;
dws->rx += dws->n_bytes;
}
--dws->rx_len;
}
}
int dw_spi_check_status(struct dw_spi *dws, bool raw)
{
u32 irq_status;
int ret = 0;
if (raw)
irq_status = dw_readl(dws, DW_SPI_RISR);
else
irq_status = dw_readl(dws, DW_SPI_ISR);
if (irq_status & DW_SPI_INT_RXOI) {
dev_err