mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 01:24:47 +01:00
The LPSPI driver currently does not support setting SPI bus clock polarity and phase, add support for it. It is important to configure correct initial clock polarity and phase before the GPIO chipselect toggles, otherwise a chip attached to the bus might recognize the first change of clock signal as the first clock cycle and get confused. In order to set up the correct polarity and phase on the clock signal before the GPIO chipselects get configured by the SPI core, the controller has to be briefly brought up in fsl_lpspi_prepare_message(). The fsl_lpspi_prepare_message() behaves like a zero-length transfer which always uses PIO and never DMA, and which leaves the clock signal in the correct state at the end of such transfer, which happens before the GPIO chipselect toggles. Signed-off-by: Marek Vasut <marex@nabladev.com> Reviewed-by: Frank Li <Frank.Li@nxp.com> Link: https://patch.msgid.link/20260127222353.1452003-1-marex@nabladev.com Signed-off-by: Mark Brown <broonie@kernel.org>
1084 lines
26 KiB
C
1084 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
//
|
|
// Freescale i.MX7ULP LPSPI driver
|
|
//
|
|
// Copyright 2016 Freescale Semiconductor, Inc.
|
|
// Copyright 2018, 2023, 2025 NXP
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma/imx-dma.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/spi/spi_bitbang.h>
|
|
#include <linux/types.h>
|
|
#include <linux/minmax.h>
|
|
|
|
#define DRIVER_NAME "fsl_lpspi"
|
|
|
|
#define FSL_LPSPI_RPM_TIMEOUT 50 /* 50ms */
|
|
|
|
/* The maximum bytes that edma can transfer once.*/
|
|
#define FSL_LPSPI_MAX_EDMA_BYTES ((1 << 15) - 1)
|
|
|
|
/* i.MX7ULP LPSPI registers */
|
|
#define IMX7ULP_VERID 0x0
|
|
#define IMX7ULP_PARAM 0x4
|
|
#define IMX7ULP_CR 0x10
|
|
#define IMX7ULP_SR 0x14
|
|
#define IMX7ULP_IER 0x18
|
|
#define IMX7ULP_DER 0x1c
|
|
#define IMX7ULP_CFGR0 0x20
|
|
#define IMX7ULP_CFGR1 0x24
|
|
#define IMX7ULP_DMR0 0x30
|
|
#define IMX7ULP_DMR1 0x34
|
|
#define IMX7ULP_CCR 0x40
|
|
#define IMX7ULP_FCR 0x58
|
|
#define IMX7ULP_FSR 0x5c
|
|
#define IMX7ULP_TCR 0x60
|
|
#define IMX7ULP_TDR 0x64
|
|
#define IMX7ULP_RSR 0x70
|
|
#define IMX7ULP_RDR 0x74
|
|
|
|
/* General control register field define */
|
|
#define CR_RRF BIT(9)
|
|
#define CR_RTF BIT(8)
|
|
#define CR_RST BIT(1)
|
|
#define CR_MEN BIT(0)
|
|
#define SR_MBF BIT(24)
|
|
#define SR_TCF BIT(10)
|
|
#define SR_FCF BIT(9)
|
|
#define SR_RDF BIT(1)
|
|
#define SR_TDF BIT(0)
|
|
#define IER_TCIE BIT(10)
|
|
#define IER_FCIE BIT(9)
|
|
#define IER_RDIE BIT(1)
|
|
#define IER_TDIE BIT(0)
|
|
#define DER_RDDE BIT(1)
|
|
#define DER_TDDE BIT(0)
|
|
#define CFGR1_PCSCFG BIT(27)
|
|
#define CFGR1_PINCFG (BIT(24)|BIT(25))
|
|
#define CFGR1_PCSPOL_MASK GENMASK(11, 8)
|
|
#define CFGR1_NOSTALL BIT(3)
|
|
#define CFGR1_HOST BIT(0)
|
|
#define FSR_TXCOUNT (0xFF)
|
|
#define RSR_RXEMPTY BIT(1)
|
|
#define TCR_CPOL BIT(31)
|
|
#define TCR_CPHA BIT(30)
|
|
#define TCR_CONT BIT(21)
|
|
#define TCR_CONTC BIT(20)
|
|
#define TCR_RXMSK BIT(19)
|
|
#define TCR_TXMSK BIT(18)
|
|
|
|
#define SR_CLEAR_MASK GENMASK(13, 8)
|
|
|
|
struct fsl_lpspi_devtype_data {
|
|
u8 prescale_max : 3; /* 0 == no limit */
|
|
bool query_hw_for_num_cs : 1;
|
|
};
|
|
|
|
struct lpspi_config {
|
|
u8 bpw;
|
|
u8 chip_select;
|
|
u8 prescale;
|
|
u16 mode;
|
|
u32 speed_hz;
|
|
u32 effective_speed_hz;
|
|
};
|
|
|
|
struct fsl_lpspi_data {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
unsigned long base_phys;
|
|
struct clk *clk_ipg;
|
|
struct clk *clk_per;
|
|
bool is_target;
|
|
bool is_only_cs1;
|
|
bool is_first_byte;
|
|
|
|
void *rx_buf;
|
|
const void *tx_buf;
|
|
void (*tx)(struct fsl_lpspi_data *);
|
|
void (*rx)(struct fsl_lpspi_data *);
|
|
|
|
u32 remain;
|
|
u8 watermark;
|
|
u8 txfifosize;
|
|
u8 rxfifosize;
|
|
|
|
struct lpspi_config config;
|
|
struct completion xfer_done;
|
|
|
|
bool target_aborted;
|
|
|
|
/* DMA */
|
|
bool usedma;
|
|
struct completion dma_rx_completion;
|
|
struct completion dma_tx_completion;
|
|
|
|
const struct fsl_lpspi_devtype_data *devtype_data;
|
|
};
|
|
|
|
/*
|
|
* Devices with ERR051608 have a max TCR_PRESCALE value of 1, otherwise there is
|
|
* no prescale limit: https://www.nxp.com/docs/en/errata/i.MX93_1P87f.pdf
|
|
*/
|
|
static const struct fsl_lpspi_devtype_data imx93_lpspi_devtype_data = {
|
|
.prescale_max = 1,
|
|
.query_hw_for_num_cs = true,
|
|
};
|
|
|
|
static const struct fsl_lpspi_devtype_data imx7ulp_lpspi_devtype_data = {
|
|
/* All defaults */
|
|
};
|
|
|
|
static const struct fsl_lpspi_devtype_data s32g_lpspi_devtype_data = {
|
|
.query_hw_for_num_cs = true,
|
|
};
|
|
|
|
static const struct of_device_id fsl_lpspi_dt_ids[] = {
|
|
{ .compatible = "fsl,imx7ulp-spi", .data = &imx7ulp_lpspi_devtype_data,},
|
|
{ .compatible = "fsl,imx93-spi", .data = &imx93_lpspi_devtype_data,},
|
|
{ .compatible = "nxp,s32g2-lpspi", .data = &s32g_lpspi_devtype_data,},
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, fsl_lpspi_dt_ids);
|
|
|
|
#define LPSPI_BUF_RX(type) \
|
|
static void fsl_lpspi_buf_rx_##type(struct fsl_lpspi_data *fsl_lpspi) \
|
|
{ \
|
|
unsigned int val = readl(fsl_lpspi->base + IMX7ULP_RDR); \
|
|
\
|
|
if (fsl_lpspi->rx_buf) { \
|
|
*(type *)fsl_lpspi->rx_buf = val; \
|
|
fsl_lpspi->rx_buf += sizeof(type); \
|
|
} \
|
|
}
|
|
|
|
#define LPSPI_BUF_TX(type) \
|
|
static void fsl_lpspi_buf_tx_##type(struct fsl_lpspi_data *fsl_lpspi) \
|
|
{ \
|
|
type val = 0; \
|
|
\
|
|
if (fsl_lpspi->tx_buf) { \
|
|
val = *(type *)fsl_lpspi->tx_buf; \
|
|
fsl_lpspi->tx_buf += sizeof(type); \
|
|
} \
|
|
\
|
|
fsl_lpspi->remain -= sizeof(type); \
|
|
writel(val, fsl_lpspi->base + IMX7ULP_TDR); \
|
|
}
|
|
|
|
LPSPI_BUF_RX(u8)
|
|
LPSPI_BUF_TX(u8)
|
|
LPSPI_BUF_RX(u16)
|
|
LPSPI_BUF_TX(u16)
|
|
LPSPI_BUF_RX(u32)
|
|
LPSPI_BUF_TX(u32)
|
|
|
|
static void fsl_lpspi_intctrl(struct fsl_lpspi_data *fsl_lpspi,
|
|
unsigned int enable)
|
|
{
|
|
writel(enable, fsl_lpspi->base + IMX7ULP_IER);
|
|
}
|
|
|
|
static int fsl_lpspi_bytes_per_word(const int bpw)
|
|
{
|
|
return DIV_ROUND_UP(bpw, BITS_PER_BYTE);
|
|
}
|
|
|
|
static bool fsl_lpspi_can_dma(struct spi_controller *controller,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *transfer)
|
|
{
|
|
unsigned int bytes_per_word;
|
|
|
|
if (!controller->dma_rx)
|
|
return false;
|
|
|
|
bytes_per_word = fsl_lpspi_bytes_per_word(transfer->bits_per_word);
|
|
|
|
switch (bytes_per_word) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int lpspi_prepare_xfer_hardware(struct spi_controller *controller)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
int ret;
|
|
|
|
ret = pm_runtime_resume_and_get(fsl_lpspi->dev);
|
|
if (ret < 0) {
|
|
dev_err(fsl_lpspi->dev, "failed to enable clock\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lpspi_unprepare_xfer_hardware(struct spi_controller *controller)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
|
|
pm_runtime_put_autosuspend(fsl_lpspi->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fsl_lpspi_write_tx_fifo(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
u8 txfifo_cnt;
|
|
u32 temp;
|
|
|
|
txfifo_cnt = readl(fsl_lpspi->base + IMX7ULP_FSR) & 0xff;
|
|
|
|
while (txfifo_cnt < fsl_lpspi->txfifosize) {
|
|
if (!fsl_lpspi->remain)
|
|
break;
|
|
fsl_lpspi->tx(fsl_lpspi);
|
|
txfifo_cnt++;
|
|
}
|
|
|
|
if (txfifo_cnt < fsl_lpspi->txfifosize) {
|
|
if (!fsl_lpspi->is_target) {
|
|
temp = readl(fsl_lpspi->base + IMX7ULP_TCR);
|
|
temp &= ~TCR_CONTC;
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_TCR);
|
|
}
|
|
|
|
fsl_lpspi_intctrl(fsl_lpspi, IER_FCIE);
|
|
} else
|
|
fsl_lpspi_intctrl(fsl_lpspi, IER_TDIE);
|
|
}
|
|
|
|
static void fsl_lpspi_read_rx_fifo(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
while (!(readl(fsl_lpspi->base + IMX7ULP_RSR) & RSR_RXEMPTY))
|
|
fsl_lpspi->rx(fsl_lpspi);
|
|
}
|
|
|
|
static void fsl_lpspi_set_cmd(struct fsl_lpspi_data *fsl_lpspi,
|
|
struct spi_device *spi)
|
|
{
|
|
u32 temp = 0;
|
|
|
|
temp |= fsl_lpspi->config.bpw - 1;
|
|
temp |= (fsl_lpspi->config.mode & 0x3) << 30;
|
|
temp |= (fsl_lpspi->config.chip_select & 0x3) << 24;
|
|
if (!fsl_lpspi->is_target) {
|
|
temp |= fsl_lpspi->config.prescale << 27;
|
|
/*
|
|
* Set TCR_CONT will keep SS asserted after current transfer.
|
|
* For the first transfer, clear TCR_CONTC to assert SS.
|
|
* For subsequent transfer, set TCR_CONTC to keep SS asserted.
|
|
*/
|
|
if (!fsl_lpspi->usedma) {
|
|
temp |= TCR_CONT;
|
|
if (fsl_lpspi->is_first_byte)
|
|
temp &= ~TCR_CONTC;
|
|
else
|
|
temp |= TCR_CONTC;
|
|
}
|
|
}
|
|
|
|
if (spi->mode & SPI_CPOL)
|
|
temp |= TCR_CPOL;
|
|
|
|
if (spi->mode & SPI_CPHA)
|
|
temp |= TCR_CPHA;
|
|
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_TCR);
|
|
|
|
dev_dbg(fsl_lpspi->dev, "TCR=0x%x\n", temp);
|
|
}
|
|
|
|
static void fsl_lpspi_set_watermark(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
u32 temp;
|
|
|
|
if (!fsl_lpspi->usedma)
|
|
temp = fsl_lpspi->watermark >> 1 |
|
|
(fsl_lpspi->watermark >> 1) << 16;
|
|
else
|
|
temp = fsl_lpspi->watermark >> 1;
|
|
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_FCR);
|
|
|
|
dev_dbg(fsl_lpspi->dev, "FCR=0x%x\n", temp);
|
|
}
|
|
|
|
static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
struct lpspi_config config = fsl_lpspi->config;
|
|
unsigned int perclk_rate, div;
|
|
u8 prescale_max;
|
|
u8 prescale;
|
|
int scldiv;
|
|
|
|
perclk_rate = clk_get_rate(fsl_lpspi->clk_per);
|
|
prescale_max = fsl_lpspi->devtype_data->prescale_max ?: 7;
|
|
|
|
if (!config.speed_hz) {
|
|
dev_err(fsl_lpspi->dev,
|
|
"error: the transmission speed provided is 0!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (config.speed_hz > perclk_rate / 2) {
|
|
div = 2;
|
|
} else {
|
|
div = DIV_ROUND_UP(perclk_rate, config.speed_hz);
|
|
}
|
|
|
|
for (prescale = 0; prescale <= prescale_max; prescale++) {
|
|
scldiv = div / (1 << prescale) - 2;
|
|
if (scldiv >= 0 && scldiv < 256) {
|
|
fsl_lpspi->config.prescale = prescale;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (scldiv < 0 || scldiv >= 256)
|
|
return -EINVAL;
|
|
|
|
writel(scldiv | (scldiv << 8) | ((scldiv >> 1) << 16),
|
|
fsl_lpspi->base + IMX7ULP_CCR);
|
|
|
|
fsl_lpspi->config.effective_speed_hz = perclk_rate / (scldiv + 2) *
|
|
(1 << prescale);
|
|
|
|
dev_dbg(fsl_lpspi->dev, "perclk=%u, speed=%u, prescale=%u, scldiv=%d\n",
|
|
perclk_rate, config.speed_hz, prescale, scldiv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_dma_configure(struct spi_controller *controller)
|
|
{
|
|
int ret;
|
|
enum dma_slave_buswidth buswidth;
|
|
struct dma_slave_config rx = {}, tx = {};
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
|
|
switch (fsl_lpspi_bytes_per_word(fsl_lpspi->config.bpw)) {
|
|
case 4:
|
|
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
break;
|
|
case 2:
|
|
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
break;
|
|
case 1:
|
|
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
tx.direction = DMA_MEM_TO_DEV;
|
|
tx.dst_addr = fsl_lpspi->base_phys + IMX7ULP_TDR;
|
|
tx.dst_addr_width = buswidth;
|
|
tx.dst_maxburst = 1;
|
|
ret = dmaengine_slave_config(controller->dma_tx, &tx);
|
|
if (ret) {
|
|
dev_err(fsl_lpspi->dev, "TX dma configuration failed with %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
rx.direction = DMA_DEV_TO_MEM;
|
|
rx.src_addr = fsl_lpspi->base_phys + IMX7ULP_RDR;
|
|
rx.src_addr_width = buswidth;
|
|
rx.src_maxburst = 1;
|
|
ret = dmaengine_slave_config(controller->dma_rx, &rx);
|
|
if (ret) {
|
|
dev_err(fsl_lpspi->dev, "RX dma configuration failed with %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
u32 temp;
|
|
int ret;
|
|
|
|
if (!fsl_lpspi->is_target) {
|
|
ret = fsl_lpspi_set_bitrate(fsl_lpspi);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
fsl_lpspi_set_watermark(fsl_lpspi);
|
|
|
|
if (!fsl_lpspi->is_target)
|
|
temp = CFGR1_HOST;
|
|
else
|
|
temp = CFGR1_PINCFG;
|
|
if (fsl_lpspi->config.mode & SPI_CS_HIGH)
|
|
temp |= FIELD_PREP(CFGR1_PCSPOL_MASK,
|
|
BIT(fsl_lpspi->config.chip_select));
|
|
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_CFGR1);
|
|
|
|
temp = readl(fsl_lpspi->base + IMX7ULP_CR);
|
|
temp |= CR_RRF | CR_RTF | CR_MEN;
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_CR);
|
|
|
|
temp = 0;
|
|
if (fsl_lpspi->usedma)
|
|
temp = DER_TDDE | DER_RDDE;
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_DER);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_setup_transfer(struct spi_controller *controller,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *t)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(spi->controller);
|
|
|
|
if (t == NULL)
|
|
return -EINVAL;
|
|
|
|
fsl_lpspi->config.mode = spi->mode;
|
|
fsl_lpspi->config.bpw = t->bits_per_word;
|
|
fsl_lpspi->config.speed_hz = t->speed_hz;
|
|
if (fsl_lpspi->is_only_cs1)
|
|
fsl_lpspi->config.chip_select = 1;
|
|
else
|
|
fsl_lpspi->config.chip_select = spi_get_chipselect(spi, 0);
|
|
|
|
if (!fsl_lpspi->config.speed_hz)
|
|
fsl_lpspi->config.speed_hz = spi->max_speed_hz;
|
|
if (!fsl_lpspi->config.bpw)
|
|
fsl_lpspi->config.bpw = spi->bits_per_word;
|
|
|
|
/* Initialize the functions for transfer */
|
|
if (fsl_lpspi->config.bpw <= 8) {
|
|
fsl_lpspi->rx = fsl_lpspi_buf_rx_u8;
|
|
fsl_lpspi->tx = fsl_lpspi_buf_tx_u8;
|
|
} else if (fsl_lpspi->config.bpw <= 16) {
|
|
fsl_lpspi->rx = fsl_lpspi_buf_rx_u16;
|
|
fsl_lpspi->tx = fsl_lpspi_buf_tx_u16;
|
|
} else {
|
|
fsl_lpspi->rx = fsl_lpspi_buf_rx_u32;
|
|
fsl_lpspi->tx = fsl_lpspi_buf_tx_u32;
|
|
}
|
|
|
|
fsl_lpspi->watermark = min(fsl_lpspi->txfifosize, t->len);
|
|
|
|
return fsl_lpspi_config(fsl_lpspi);
|
|
}
|
|
|
|
static int fsl_lpspi_prepare_message(struct spi_controller *controller,
|
|
struct spi_message *msg)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
struct spi_device *spi = msg->spi;
|
|
struct spi_transfer *t;
|
|
int ret;
|
|
|
|
t = list_first_entry_or_null(&msg->transfers, struct spi_transfer,
|
|
transfer_list);
|
|
if (!t)
|
|
return 0;
|
|
|
|
fsl_lpspi->is_first_byte = true;
|
|
fsl_lpspi->usedma = false;
|
|
ret = fsl_lpspi_setup_transfer(controller, spi, t);
|
|
|
|
if (fsl_lpspi_can_dma(controller, spi, t))
|
|
fsl_lpspi->usedma = true;
|
|
else
|
|
fsl_lpspi->usedma = false;
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
fsl_lpspi_set_cmd(fsl_lpspi, spi);
|
|
|
|
/* No IRQs */
|
|
writel(0, fsl_lpspi->base + IMX7ULP_IER);
|
|
|
|
/* Controller disable, clear FIFOs, clear status */
|
|
writel(CR_RRF | CR_RTF, fsl_lpspi->base + IMX7ULP_CR);
|
|
writel(SR_CLEAR_MASK, fsl_lpspi->base + IMX7ULP_SR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_target_abort(struct spi_controller *controller)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
|
|
fsl_lpspi->target_aborted = true;
|
|
if (!fsl_lpspi->usedma)
|
|
complete(&fsl_lpspi->xfer_done);
|
|
else {
|
|
complete(&fsl_lpspi->dma_tx_completion);
|
|
complete(&fsl_lpspi->dma_rx_completion);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_wait_for_completion(struct spi_controller *controller)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
|
|
if (fsl_lpspi->is_target) {
|
|
if (wait_for_completion_interruptible(&fsl_lpspi->xfer_done) ||
|
|
fsl_lpspi->target_aborted) {
|
|
dev_dbg(fsl_lpspi->dev, "interrupted\n");
|
|
return -EINTR;
|
|
}
|
|
} else {
|
|
if (!wait_for_completion_timeout(&fsl_lpspi->xfer_done, HZ)) {
|
|
dev_dbg(fsl_lpspi->dev, "wait for completion timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_reset(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
u32 temp;
|
|
|
|
if (!fsl_lpspi->usedma) {
|
|
/* Disable all interrupt */
|
|
fsl_lpspi_intctrl(fsl_lpspi, 0);
|
|
}
|
|
|
|
/* Clear FIFO and disable module */
|
|
temp = CR_RRF | CR_RTF;
|
|
writel(temp, fsl_lpspi->base + IMX7ULP_CR);
|
|
|
|
/* W1C for all flags in SR */
|
|
writel(SR_CLEAR_MASK, fsl_lpspi->base + IMX7ULP_SR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fsl_lpspi_dma_rx_callback(void *cookie)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi = (struct fsl_lpspi_data *)cookie;
|
|
|
|
complete(&fsl_lpspi->dma_rx_completion);
|
|
}
|
|
|
|
static void fsl_lpspi_dma_tx_callback(void *cookie)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi = (struct fsl_lpspi_data *)cookie;
|
|
|
|
complete(&fsl_lpspi->dma_tx_completion);
|
|
}
|
|
|
|
static int fsl_lpspi_calculate_timeout(struct fsl_lpspi_data *fsl_lpspi,
|
|
int size)
|
|
{
|
|
unsigned long timeout = 0;
|
|
|
|
/* Time with actual data transfer and CS change delay related to HW */
|
|
timeout = (8 + 4) * size / fsl_lpspi->config.speed_hz;
|
|
|
|
/* Add extra second for scheduler related activities */
|
|
timeout += 1;
|
|
|
|
/* Double calculated timeout */
|
|
return secs_to_jiffies(2 * timeout);
|
|
}
|
|
|
|
static int fsl_lpspi_dma_transfer(struct spi_controller *controller,
|
|
struct fsl_lpspi_data *fsl_lpspi,
|
|
struct spi_transfer *transfer)
|
|
{
|
|
struct dma_async_tx_descriptor *desc_tx, *desc_rx;
|
|
unsigned long transfer_timeout;
|
|
unsigned long time_left;
|
|
struct sg_table *tx = &transfer->tx_sg, *rx = &transfer->rx_sg;
|
|
int ret;
|
|
|
|
ret = fsl_lpspi_dma_configure(controller);
|
|
if (ret)
|
|
return ret;
|
|
|
|
desc_rx = dmaengine_prep_slave_sg(controller->dma_rx,
|
|
rx->sgl, rx->nents, DMA_DEV_TO_MEM,
|
|
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
if (!desc_rx)
|
|
return -EINVAL;
|
|
|
|
desc_rx->callback = fsl_lpspi_dma_rx_callback;
|
|
desc_rx->callback_param = (void *)fsl_lpspi;
|
|
dmaengine_submit(desc_rx);
|
|
reinit_completion(&fsl_lpspi->dma_rx_completion);
|
|
dma_async_issue_pending(controller->dma_rx);
|
|
|
|
desc_tx = dmaengine_prep_slave_sg(controller->dma_tx,
|
|
tx->sgl, tx->nents, DMA_MEM_TO_DEV,
|
|
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
if (!desc_tx) {
|
|
dmaengine_terminate_all(controller->dma_tx);
|
|
return -EINVAL;
|
|
}
|
|
|
|
desc_tx->callback = fsl_lpspi_dma_tx_callback;
|
|
desc_tx->callback_param = (void *)fsl_lpspi;
|
|
dmaengine_submit(desc_tx);
|
|
reinit_completion(&fsl_lpspi->dma_tx_completion);
|
|
dma_async_issue_pending(controller->dma_tx);
|
|
|
|
fsl_lpspi->target_aborted = false;
|
|
|
|
if (!fsl_lpspi->is_target) {
|
|
transfer_timeout = fsl_lpspi_calculate_timeout(fsl_lpspi,
|
|
transfer->len);
|
|
|
|
/* Wait eDMA to finish the data transfer.*/
|
|
time_left = wait_for_completion_timeout(&fsl_lpspi->dma_tx_completion,
|
|
transfer_timeout);
|
|
if (!time_left) {
|
|
dev_err(fsl_lpspi->dev, "I/O Error in DMA TX\n");
|
|
dmaengine_terminate_all(controller->dma_tx);
|
|
dmaengine_terminate_all(controller->dma_rx);
|
|
fsl_lpspi_reset(fsl_lpspi);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
time_left = wait_for_completion_timeout(&fsl_lpspi->dma_rx_completion,
|
|
transfer_timeout);
|
|
if (!time_left) {
|
|
dev_err(fsl_lpspi->dev, "I/O Error in DMA RX\n");
|
|
dmaengine_terminate_all(controller->dma_tx);
|
|
dmaengine_terminate_all(controller->dma_rx);
|
|
fsl_lpspi_reset(fsl_lpspi);
|
|
return -ETIMEDOUT;
|
|
}
|
|
} else {
|
|
if (wait_for_completion_interruptible(&fsl_lpspi->dma_tx_completion) ||
|
|
fsl_lpspi->target_aborted) {
|
|
dev_dbg(fsl_lpspi->dev,
|
|
"I/O Error in DMA TX interrupted\n");
|
|
dmaengine_terminate_all(controller->dma_tx);
|
|
dmaengine_terminate_all(controller->dma_rx);
|
|
fsl_lpspi_reset(fsl_lpspi);
|
|
return -EINTR;
|
|
}
|
|
|
|
if (wait_for_completion_interruptible(&fsl_lpspi->dma_rx_completion) ||
|
|
fsl_lpspi->target_aborted) {
|
|
dev_dbg(fsl_lpspi->dev,
|
|
"I/O Error in DMA RX interrupted\n");
|
|
dmaengine_terminate_all(controller->dma_tx);
|
|
dmaengine_terminate_all(controller->dma_rx);
|
|
fsl_lpspi_reset(fsl_lpspi);
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
fsl_lpspi_reset(fsl_lpspi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fsl_lpspi_dma_exit(struct spi_controller *controller)
|
|
{
|
|
if (controller->dma_rx) {
|
|
dma_release_channel(controller->dma_rx);
|
|
controller->dma_rx = NULL;
|
|
}
|
|
|
|
if (controller->dma_tx) {
|
|
dma_release_channel(controller->dma_tx);
|
|
controller->dma_tx = NULL;
|
|
}
|
|
}
|
|
|
|
static int fsl_lpspi_dma_init(struct device *dev,
|
|
struct fsl_lpspi_data *fsl_lpspi,
|
|
struct spi_controller *controller)
|
|
{
|
|
int ret;
|
|
|
|
/* Prepare for TX DMA: */
|
|
controller->dma_tx = dma_request_chan(dev, "tx");
|
|
if (IS_ERR(controller->dma_tx)) {
|
|
ret = PTR_ERR(controller->dma_tx);
|
|
dev_dbg(dev, "can't get the TX DMA channel, error %d!\n", ret);
|
|
controller->dma_tx = NULL;
|
|
goto err;
|
|
}
|
|
|
|
/* Prepare for RX DMA: */
|
|
controller->dma_rx = dma_request_chan(dev, "rx");
|
|
if (IS_ERR(controller->dma_rx)) {
|
|
ret = PTR_ERR(controller->dma_rx);
|
|
dev_dbg(dev, "can't get the RX DMA channel, error %d\n", ret);
|
|
controller->dma_rx = NULL;
|
|
goto err;
|
|
}
|
|
|
|
init_completion(&fsl_lpspi->dma_rx_completion);
|
|
init_completion(&fsl_lpspi->dma_tx_completion);
|
|
controller->can_dma = fsl_lpspi_can_dma;
|
|
controller->max_dma_len = FSL_LPSPI_MAX_EDMA_BYTES;
|
|
|
|
return 0;
|
|
err:
|
|
fsl_lpspi_dma_exit(controller);
|
|
return ret;
|
|
}
|
|
|
|
static int fsl_lpspi_pio_transfer(struct spi_controller *controller,
|
|
struct spi_transfer *t)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
int ret;
|
|
|
|
fsl_lpspi->tx_buf = t->tx_buf;
|
|
fsl_lpspi->rx_buf = t->rx_buf;
|
|
fsl_lpspi->remain = t->len;
|
|
|
|
reinit_completion(&fsl_lpspi->xfer_done);
|
|
fsl_lpspi->target_aborted = false;
|
|
|
|
fsl_lpspi_write_tx_fifo(fsl_lpspi);
|
|
|
|
ret = fsl_lpspi_wait_for_completion(controller);
|
|
|
|
fsl_lpspi_reset(fsl_lpspi);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fsl_lpspi_transfer_one(struct spi_controller *controller,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *t)
|
|
{
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
int ret;
|
|
|
|
if (fsl_lpspi_can_dma(controller, spi, t))
|
|
fsl_lpspi->usedma = true;
|
|
else
|
|
fsl_lpspi->usedma = false;
|
|
|
|
ret = fsl_lpspi_setup_transfer(controller, spi, t);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
t->effective_speed_hz = fsl_lpspi->config.effective_speed_hz;
|
|
|
|
fsl_lpspi_set_cmd(fsl_lpspi, spi);
|
|
fsl_lpspi->is_first_byte = false;
|
|
|
|
if (fsl_lpspi->usedma)
|
|
ret = fsl_lpspi_dma_transfer(controller, fsl_lpspi, t);
|
|
else
|
|
ret = fsl_lpspi_pio_transfer(controller, t);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id)
|
|
{
|
|
u32 temp_SR, temp_IER;
|
|
struct fsl_lpspi_data *fsl_lpspi = dev_id;
|
|
|
|
temp_IER = readl(fsl_lpspi->base + IMX7ULP_IER);
|
|
fsl_lpspi_intctrl(fsl_lpspi, 0);
|
|
temp_SR = readl(fsl_lpspi->base + IMX7ULP_SR);
|
|
|
|
fsl_lpspi_read_rx_fifo(fsl_lpspi);
|
|
|
|
if ((temp_SR & SR_TDF) && (temp_IER & IER_TDIE)) {
|
|
fsl_lpspi_write_tx_fifo(fsl_lpspi);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (temp_SR & SR_MBF ||
|
|
readl(fsl_lpspi->base + IMX7ULP_FSR) & FSR_TXCOUNT) {
|
|
writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
|
|
fsl_lpspi_intctrl(fsl_lpspi, IER_FCIE | (temp_IER & IER_TDIE));
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (temp_SR & SR_FCF && (temp_IER & IER_FCIE)) {
|
|
writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
|
|
complete(&fsl_lpspi->xfer_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int fsl_lpspi_runtime_resume(struct device *dev)
|
|
{
|
|
struct spi_controller *controller = dev_get_drvdata(dev);
|
|
struct fsl_lpspi_data *fsl_lpspi;
|
|
int ret;
|
|
|
|
fsl_lpspi = spi_controller_get_devdata(controller);
|
|
|
|
ret = clk_prepare_enable(fsl_lpspi->clk_per);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(fsl_lpspi->clk_ipg);
|
|
if (ret) {
|
|
clk_disable_unprepare(fsl_lpspi->clk_per);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_runtime_suspend(struct device *dev)
|
|
{
|
|
struct spi_controller *controller = dev_get_drvdata(dev);
|
|
struct fsl_lpspi_data *fsl_lpspi;
|
|
|
|
fsl_lpspi = spi_controller_get_devdata(controller);
|
|
|
|
clk_disable_unprepare(fsl_lpspi->clk_per);
|
|
clk_disable_unprepare(fsl_lpspi->clk_ipg);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int fsl_lpspi_init_rpm(struct fsl_lpspi_data *fsl_lpspi)
|
|
{
|
|
struct device *dev = fsl_lpspi->dev;
|
|
|
|
pm_runtime_enable(dev);
|
|
pm_runtime_set_autosuspend_delay(dev, FSL_LPSPI_RPM_TIMEOUT);
|
|
pm_runtime_use_autosuspend(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_lpspi_probe(struct platform_device *pdev)
|
|
{
|
|
const struct fsl_lpspi_devtype_data *devtype_data;
|
|
struct fsl_lpspi_data *fsl_lpspi;
|
|
struct spi_controller *controller;
|
|
struct resource *res;
|
|
int ret, irq;
|
|
u32 num_cs;
|
|
u32 temp;
|
|
bool is_target;
|
|
|
|
devtype_data = of_device_get_match_data(&pdev->dev);
|
|
if (!devtype_data)
|
|
return -ENODEV;
|
|
|
|
is_target = of_property_read_bool((&pdev->dev)->of_node, "spi-slave");
|
|
if (is_target)
|
|
controller = devm_spi_alloc_target(&pdev->dev,
|
|
sizeof(struct fsl_lpspi_data));
|
|
else
|
|
controller = devm_spi_alloc_host(&pdev->dev,
|
|
sizeof(struct fsl_lpspi_data));
|
|
|
|
if (!controller)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, controller);
|
|
|
|
fsl_lpspi = spi_controller_get_devdata(controller);
|
|
fsl_lpspi->dev = &pdev->dev;
|
|
fsl_lpspi->is_target = is_target;
|
|
fsl_lpspi->is_only_cs1 = of_property_read_bool((&pdev->dev)->of_node,
|
|
"fsl,spi-only-use-cs1-sel");
|
|
fsl_lpspi->devtype_data = devtype_data;
|
|
|
|
init_completion(&fsl_lpspi->xfer_done);
|
|
|
|
fsl_lpspi->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
|
if (IS_ERR(fsl_lpspi->base)) {
|
|
ret = PTR_ERR(fsl_lpspi->base);
|
|
return ret;
|
|
}
|
|
fsl_lpspi->base_phys = res->start;
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
ret = irq;
|
|
return ret;
|
|
}
|
|
|
|
ret = devm_request_irq(&pdev->dev, irq, fsl_lpspi_isr, IRQF_NO_AUTOEN,
|
|
dev_name(&pdev->dev), fsl_lpspi);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "can't get irq%d: %d\n", irq, ret);
|
|
return ret;
|
|
}
|
|
|
|
fsl_lpspi->clk_per = devm_clk_get(&pdev->dev, "per");
|
|
if (IS_ERR(fsl_lpspi->clk_per)) {
|
|
ret = PTR_ERR(fsl_lpspi->clk_per);
|
|
return ret;
|
|
}
|
|
|
|
fsl_lpspi->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
|
|
if (IS_ERR(fsl_lpspi->clk_ipg)) {
|
|
ret = PTR_ERR(fsl_lpspi->clk_ipg);
|
|
return ret;
|
|
}
|
|
|
|
/* enable the clock */
|
|
ret = fsl_lpspi_init_rpm(fsl_lpspi);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = pm_runtime_get_sync(fsl_lpspi->dev);
|
|
if (ret < 0) {
|
|
dev_err(fsl_lpspi->dev, "failed to enable clock\n");
|
|
goto out_pm_get;
|
|
}
|
|
|
|
temp = readl(fsl_lpspi->base + IMX7ULP_PARAM);
|
|
fsl_lpspi->txfifosize = 1 << (temp & 0x0f);
|
|
fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f);
|
|
if (of_property_read_u32((&pdev->dev)->of_node, "num-cs",
|
|
&num_cs)) {
|
|
if (devtype_data->query_hw_for_num_cs)
|
|
num_cs = ((temp >> 16) & 0xf);
|
|
else
|
|
num_cs = 1;
|
|
}
|
|
|
|
controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32);
|
|
controller->prepare_message = fsl_lpspi_prepare_message;
|
|
controller->transfer_one = fsl_lpspi_transfer_one;
|
|
controller->prepare_transfer_hardware = lpspi_prepare_xfer_hardware;
|
|
controller->unprepare_transfer_hardware = lpspi_unprepare_xfer_hardware;
|
|
controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
|
controller->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX;
|
|
controller->bus_num = pdev->id;
|
|
controller->num_chipselect = num_cs;
|
|
controller->target_abort = fsl_lpspi_target_abort;
|
|
if (!fsl_lpspi->is_target)
|
|
controller->use_gpio_descriptors = true;
|
|
|
|
ret = fsl_lpspi_dma_init(&pdev->dev, fsl_lpspi, controller);
|
|
if (ret == -EPROBE_DEFER)
|
|
goto out_pm_get;
|
|
if (ret < 0) {
|
|
dev_warn(&pdev->dev, "dma setup error %d, use pio\n", ret);
|
|
enable_irq(irq);
|
|
}
|
|
|
|
ret = devm_spi_register_controller(&pdev->dev, controller);
|
|
if (ret < 0) {
|
|
dev_err_probe(&pdev->dev, ret, "spi_register_controller error\n");
|
|
goto free_dma;
|
|
}
|
|
|
|
pm_runtime_put_autosuspend(fsl_lpspi->dev);
|
|
|
|
return 0;
|
|
|
|
free_dma:
|
|
fsl_lpspi_dma_exit(controller);
|
|
out_pm_get:
|
|
pm_runtime_dont_use_autosuspend(fsl_lpspi->dev);
|
|
pm_runtime_put_sync(fsl_lpspi->dev);
|
|
pm_runtime_disable(fsl_lpspi->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void fsl_lpspi_remove(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *controller = platform_get_drvdata(pdev);
|
|
struct fsl_lpspi_data *fsl_lpspi =
|
|
spi_controller_get_devdata(controller);
|
|
|
|
fsl_lpspi_dma_exit(controller);
|
|
|
|
pm_runtime_dont_use_autosuspend(fsl_lpspi->dev);
|
|
pm_runtime_disable(fsl_lpspi->dev);
|
|
}
|
|
|
|
static int fsl_lpspi_suspend(struct device *dev)
|
|
{
|
|
pinctrl_pm_select_sleep_state(dev);
|
|
return pm_runtime_force_suspend(dev);
|
|
}
|
|
|
|
static int fsl_lpspi_resume(struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = pm_runtime_force_resume(dev);
|
|
if (ret) {
|
|
dev_err(dev, "Error in resume: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
pinctrl_pm_select_default_state(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops fsl_lpspi_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(fsl_lpspi_runtime_suspend,
|
|
fsl_lpspi_runtime_resume, NULL)
|
|
SYSTEM_SLEEP_PM_OPS(fsl_lpspi_suspend, fsl_lpspi_resume)
|
|
};
|
|
|
|
static struct platform_driver fsl_lpspi_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.of_match_table = fsl_lpspi_dt_ids,
|
|
.pm = pm_ptr(&fsl_lpspi_pm_ops),
|
|
},
|
|
.probe = fsl_lpspi_probe,
|
|
.remove = fsl_lpspi_remove,
|
|
};
|
|
module_platform_driver(fsl_lpspi_driver);
|
|
|
|
MODULE_DESCRIPTION("LPSPI Controller driver");
|
|
MODULE_AUTHOR("Gao Pan <pandy.gao@nxp.com>");
|
|
MODULE_LICENSE("GPL");
|