diff options
Diffstat (limited to 'drivers/spi/spi-uniphier.c')
| -rw-r--r-- | drivers/spi/spi-uniphier.c | 429 |
1 files changed, 358 insertions, 71 deletions
diff --git a/drivers/spi/spi-uniphier.c b/drivers/spi/spi-uniphier.c index 5a6137fe172d..ff2142f87277 100644 --- a/drivers/spi/spi-uniphier.c +++ b/drivers/spi/spi-uniphier.c @@ -7,22 +7,26 @@ #include <linux/bitfield.h> #include <linux/bitops.h> #include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #define SSI_TIMEOUT_MS 2000 +#define SSI_POLL_TIMEOUT_US 200 #define SSI_MAX_CLK_DIVIDER 254 #define SSI_MIN_CLK_DIVIDER 4 struct uniphier_spi_priv { void __iomem *base; + dma_addr_t base_dma_addr; struct clk *clk; - struct spi_master *master; + struct spi_controller *host; struct completion xfer_done; int error; @@ -30,6 +34,7 @@ struct uniphier_spi_priv { unsigned int rx_bytes; const u8 *tx_buf; u8 *rx_buf; + atomic_t dma_busy; bool is_save_param; u8 bits_per_word; @@ -59,11 +64,16 @@ struct uniphier_spi_priv { #define SSI_FPS_FSTRT BIT(14) #define SSI_SR 0x14 +#define SSI_SR_BUSY BIT(7) #define SSI_SR_RNE BIT(0) #define SSI_IE 0x18 +#define SSI_IE_TCIE BIT(4) #define SSI_IE_RCIE BIT(3) +#define SSI_IE_TXRE BIT(2) +#define SSI_IE_RXRE BIT(1) #define SSI_IE_RORIE BIT(0) +#define SSI_IE_ALL_MASK GENMASK(4, 0) #define SSI_IS 0x1c #define SSI_IS_RXRS BIT(9) @@ -85,15 +95,19 @@ struct uniphier_spi_priv { #define SSI_RXDR 0x24 #define SSI_FIFO_DEPTH 8U +#define SSI_FIFO_BURST_NUM 1 + +#define SSI_DMA_RX_BUSY BIT(1) +#define SSI_DMA_TX_BUSY BIT(0) static inline unsigned int bytes_per_word(unsigned int bits) { return bits <= 8 ? 1 : (bits <= 16 ? 2 : 4); } -static inline void uniphier_spi_irq_enable(struct spi_device *spi, u32 mask) +static inline void uniphier_spi_irq_enable(struct uniphier_spi_priv *priv, + u32 mask) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); u32 val; val = readl(priv->base + SSI_IE); @@ -101,9 +115,9 @@ static inline void uniphier_spi_irq_enable(struct spi_device *spi, u32 mask) writel(val, priv->base + SSI_IE); } -static inline void uniphier_spi_irq_disable(struct spi_device *spi, u32 mask) +static inline void uniphier_spi_irq_disable(struct uniphier_spi_priv *priv, + u32 mask) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); u32 val; val = readl(priv->base + SSI_IE); @@ -113,7 +127,7 @@ static inline void uniphier_spi_irq_disable(struct spi_device *spi, u32 mask) static void uniphier_spi_set_mode(struct spi_device *spi) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller); u32 val1, val2; /* @@ -128,7 +142,7 @@ static void uniphier_spi_set_mode(struct spi_device *spi) * FSTRT start frame timing * 0: rising edge of clock, 1: falling edge of clock */ - switch (spi->mode & (SPI_CPOL | SPI_CPHA)) { + switch (spi->mode & SPI_MODE_X_MASK) { case SPI_MODE_0: /* CKPHS=1, CKINIT=0, CKDLY=1, FSTRT=0 */ val1 = SSI_CKS_CKPHS | SSI_CKS_CKDLY; @@ -166,7 +180,7 @@ static void uniphier_spi_set_mode(struct spi_device *spi) static void uniphier_spi_set_transfer_size(struct spi_device *spi, int size) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller); u32 val; val = readl(priv->base + SSI_TXWDS); @@ -184,7 +198,7 @@ static void uniphier_spi_set_transfer_size(struct spi_device *spi, int size) static void uniphier_spi_set_baudrate(struct spi_device *spi, unsigned int speed) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller); u32 val, ckdiv; /* @@ -203,7 +217,7 @@ static void uniphier_spi_set_baudrate(struct spi_device *spi, static void uniphier_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller); u32 val; priv->error = 0; @@ -214,6 +228,7 @@ static void uniphier_spi_setup_transfer(struct spi_device *spi, if (!priv->is_save_param || priv->mode != spi->mode) { uniphier_spi_set_mode(spi); priv->mode = spi->mode; + priv->is_save_param = false; } if (!priv->is_save_param || priv->bits_per_word != t->bits_per_word) { @@ -226,8 +241,7 @@ static void uniphier_spi_setup_transfer(struct spi_device *spi, priv->speed_hz = t->speed_hz; } - if (!priv->is_save_param) - priv->is_save_param = true; + priv->is_save_param = true; /* reset FIFOs */ val = SSI_FC_TXFFL | SSI_FC_RXFFL; @@ -288,29 +302,38 @@ static void uniphier_spi_recv(struct uniphier_spi_priv *priv) } } -static void uniphier_spi_fill_tx_fifo(struct uniphier_spi_priv *priv) +static void uniphier_spi_set_fifo_threshold(struct uniphier_spi_priv *priv, + unsigned int threshold) { - unsigned int tx_count; u32 val; - tx_count = DIV_ROUND_UP(priv->tx_bytes, - bytes_per_word(priv->bits_per_word)); - tx_count = min(tx_count, SSI_FIFO_DEPTH); - - /* set fifo threshold */ val = readl(priv->base + SSI_FC); val &= ~(SSI_FC_TXFTH_MASK | SSI_FC_RXFTH_MASK); - val |= FIELD_PREP(SSI_FC_TXFTH_MASK, tx_count); - val |= FIELD_PREP(SSI_FC_RXFTH_MASK, tx_count); + val |= FIELD_PREP(SSI_FC_TXFTH_MASK, SSI_FIFO_DEPTH - threshold); + val |= FIELD_PREP(SSI_FC_RXFTH_MASK, threshold); writel(val, priv->base + SSI_FC); +} + +static void uniphier_spi_fill_tx_fifo(struct uniphier_spi_priv *priv) +{ + unsigned int fifo_threshold, fill_words; + unsigned int bpw = bytes_per_word(priv->bits_per_word); - while (tx_count--) + fifo_threshold = DIV_ROUND_UP(priv->rx_bytes, bpw); + fifo_threshold = min(fifo_threshold, SSI_FIFO_DEPTH); + + uniphier_spi_set_fifo_threshold(priv, fifo_threshold); + + fill_words = fifo_threshold - + DIV_ROUND_UP(priv->rx_bytes - priv->tx_bytes, bpw); + + while (fill_words--) uniphier_spi_send(priv); } static void uniphier_spi_set_cs(struct spi_device *spi, bool enable) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller); u32 val; val = readl(priv->base + SSI_FPS); @@ -323,50 +346,257 @@ static void uniphier_spi_set_cs(struct spi_device *spi, bool enable) writel(val, priv->base + SSI_FPS); } -static int uniphier_spi_transfer_one(struct spi_master *master, - struct spi_device *spi, - struct spi_transfer *t) +static bool uniphier_spi_can_dma(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *t) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(master); - int status; + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + unsigned int bpw = bytes_per_word(priv->bits_per_word); - uniphier_spi_setup_transfer(spi, t); + if ((!host->dma_tx && !host->dma_rx) + || (!host->dma_tx && t->tx_buf) + || (!host->dma_rx && t->rx_buf)) + return false; + + return DIV_ROUND_UP(t->len, bpw) > SSI_FIFO_DEPTH; +} + +static void uniphier_spi_dma_rxcb(void *data) +{ + struct spi_controller *host = data; + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + int state = atomic_fetch_andnot(SSI_DMA_RX_BUSY, &priv->dma_busy); + + uniphier_spi_irq_disable(priv, SSI_IE_RXRE); + + if (!(state & SSI_DMA_TX_BUSY)) + spi_finalize_current_transfer(host); +} + +static void uniphier_spi_dma_txcb(void *data) +{ + struct spi_controller *host = data; + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + int state = atomic_fetch_andnot(SSI_DMA_TX_BUSY, &priv->dma_busy); + + uniphier_spi_irq_disable(priv, SSI_IE_TXRE); + + if (!(state & SSI_DMA_RX_BUSY)) + spi_finalize_current_transfer(host); +} + +static int uniphier_spi_transfer_one_dma(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *t) +{ + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + struct dma_async_tx_descriptor *rxdesc = NULL, *txdesc = NULL; + int buswidth; + + atomic_set(&priv->dma_busy, 0); + + uniphier_spi_set_fifo_threshold(priv, SSI_FIFO_BURST_NUM); + + if (priv->bits_per_word <= 8) + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (priv->bits_per_word <= 16) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + + if (priv->rx_buf) { + struct dma_slave_config rxconf = { + .direction = DMA_DEV_TO_MEM, + .src_addr = priv->base_dma_addr + SSI_RXDR, + .src_addr_width = buswidth, + .src_maxburst = SSI_FIFO_BURST_NUM, + }; + + dmaengine_slave_config(host->dma_rx, &rxconf); + + rxdesc = dmaengine_prep_slave_sg( + host->dma_rx, + t->rx_sg.sgl, t->rx_sg.nents, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxdesc) + goto out_err_prep; + + rxdesc->callback = uniphier_spi_dma_rxcb; + rxdesc->callback_param = host; + + uniphier_spi_irq_enable(priv, SSI_IE_RXRE); + atomic_or(SSI_DMA_RX_BUSY, &priv->dma_busy); + + dmaengine_submit(rxdesc); + dma_async_issue_pending(host->dma_rx); + } + + if (priv->tx_buf) { + struct dma_slave_config txconf = { + .direction = DMA_MEM_TO_DEV, + .dst_addr = priv->base_dma_addr + SSI_TXDR, + .dst_addr_width = buswidth, + .dst_maxburst = SSI_FIFO_BURST_NUM, + }; + + dmaengine_slave_config(host->dma_tx, &txconf); + + txdesc = dmaengine_prep_slave_sg( + host->dma_tx, + t->tx_sg.sgl, t->tx_sg.nents, + DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!txdesc) + goto out_err_prep; + + txdesc->callback = uniphier_spi_dma_txcb; + txdesc->callback_param = host; + + uniphier_spi_irq_enable(priv, SSI_IE_TXRE); + atomic_or(SSI_DMA_TX_BUSY, &priv->dma_busy); + + dmaengine_submit(txdesc); + dma_async_issue_pending(host->dma_tx); + } + + /* signal that we need to wait for completion */ + return (priv->tx_buf || priv->rx_buf); + +out_err_prep: + if (rxdesc) + dmaengine_terminate_sync(host->dma_rx); + + return -EINVAL; +} + +static int uniphier_spi_transfer_one_irq(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *t) +{ + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + struct device *dev = host->dev.parent; + unsigned long time_left; reinit_completion(&priv->xfer_done); uniphier_spi_fill_tx_fifo(priv); - uniphier_spi_irq_enable(spi, SSI_IE_RCIE | SSI_IE_RORIE); + uniphier_spi_irq_enable(priv, SSI_IE_RCIE | SSI_IE_RORIE); - status = wait_for_completion_timeout(&priv->xfer_done, - msecs_to_jiffies(SSI_TIMEOUT_MS)); + time_left = wait_for_completion_timeout(&priv->xfer_done, + msecs_to_jiffies(SSI_TIMEOUT_MS)); - uniphier_spi_irq_disable(spi, SSI_IE_RCIE | SSI_IE_RORIE); + uniphier_spi_irq_disable(priv, SSI_IE_RCIE | SSI_IE_RORIE); - if (status < 0) - return status; + if (!time_left) { + dev_err(dev, "transfer timeout.\n"); + return -ETIMEDOUT; + } return priv->error; } -static int uniphier_spi_prepare_transfer_hardware(struct spi_master *master) +static int uniphier_spi_transfer_one_poll(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *t) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + int loop = SSI_POLL_TIMEOUT_US * 10; + + while (priv->tx_bytes) { + uniphier_spi_fill_tx_fifo(priv); + + while ((priv->rx_bytes - priv->tx_bytes) > 0) { + while (!(readl(priv->base + SSI_SR) & SSI_SR_RNE) + && loop--) + ndelay(100); + + if (loop == -1) + goto irq_transfer; + + uniphier_spi_recv(priv); + } + } + + return 0; + +irq_transfer: + return uniphier_spi_transfer_one_irq(host, spi, t); +} + +static int uniphier_spi_transfer_one(struct spi_controller *host, + struct spi_device *spi, + struct spi_transfer *t) +{ + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + unsigned long threshold; + bool use_dma; + + /* Terminate and return success for 0 byte length transfer */ + if (!t->len) + return 0; + + uniphier_spi_setup_transfer(spi, t); + + use_dma = host->can_dma ? host->can_dma(host, spi, t) : false; + if (use_dma) + return uniphier_spi_transfer_one_dma(host, spi, t); + + /* + * If the transfer operation will take longer than + * SSI_POLL_TIMEOUT_US, it should use irq. + */ + threshold = DIV_ROUND_UP(SSI_POLL_TIMEOUT_US * priv->speed_hz, + USEC_PER_SEC * BITS_PER_BYTE); + if (t->len > threshold) + return uniphier_spi_transfer_one_irq(host, spi, t); + else + return uniphier_spi_transfer_one_poll(host, spi, t); +} + +static int uniphier_spi_prepare_transfer_hardware(struct spi_controller *host) +{ + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); writel(SSI_CTL_EN, priv->base + SSI_CTL); return 0; } -static int uniphier_spi_unprepare_transfer_hardware(struct spi_master *master) +static int uniphier_spi_unprepare_transfer_hardware(struct spi_controller *host) { - struct uniphier_spi_priv *priv = spi_master_get_devdata(master); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); writel(0, priv->base + SSI_CTL); return 0; } +static void uniphier_spi_handle_err(struct spi_controller *host, + struct spi_message *msg) +{ + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); + u32 val; + + /* stop running spi transfer */ + writel(0, priv->base + SSI_CTL); + + /* reset FIFOs */ + val = SSI_FC_TXFFL | SSI_FC_RXFFL; + writel(val, priv->base + SSI_FC); + + uniphier_spi_irq_disable(priv, SSI_IE_ALL_MASK); + + if (atomic_read(&priv->dma_busy) & SSI_DMA_TX_BUSY) { + dmaengine_terminate_async(host->dma_tx); + atomic_andnot(SSI_DMA_TX_BUSY, &priv->dma_busy); + } + + if (atomic_read(&priv->dma_busy) & SSI_DMA_RX_BUSY) { + dmaengine_terminate_async(host->dma_rx); + atomic_andnot(SSI_DMA_RX_BUSY, &priv->dma_busy); + } +} + static irqreturn_t uniphier_spi_handler(int irq, void *dev_id) { struct uniphier_spi_priv *priv = dev_id; @@ -411,43 +641,44 @@ done: static int uniphier_spi_probe(struct platform_device *pdev) { struct uniphier_spi_priv *priv; - struct spi_master *master; + struct spi_controller *host; struct resource *res; + struct dma_slave_caps caps; + u32 dma_tx_burst = 0, dma_rx_burst = 0; unsigned long clk_rate; int irq; int ret; - master = spi_alloc_master(&pdev->dev, sizeof(*priv)); - if (!master) + host = spi_alloc_host(&pdev->dev, sizeof(*priv)); + if (!host) return -ENOMEM; - platform_set_drvdata(pdev, master); + platform_set_drvdata(pdev, host); - priv = spi_master_get_devdata(master); - priv->master = master; + priv = spi_controller_get_devdata(host); + priv->host = host; priv->is_save_param = false; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(&pdev->dev, res); + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(priv->base)) { ret = PTR_ERR(priv->base); - goto out_master_put; + goto out_host_put; } + priv->base_dma_addr = res->start; priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, "failed to get clock\n"); ret = PTR_ERR(priv->clk); - goto out_master_put; + goto out_host_put; } ret = clk_prepare_enable(priv->clk); if (ret) - goto out_master_put; + goto out_host_put; irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(&pdev->dev, "failed to get IRQ\n"); ret = irq; goto out_disable_clk; } @@ -463,42 +694,98 @@ static int uniphier_spi_probe(struct platform_device *pdev) clk_rate = clk_get_rate(priv->clk); - master->max_speed_hz = DIV_ROUND_UP(clk_rate, SSI_MIN_CLK_DIVIDER); - master->min_speed_hz = DIV_ROUND_UP(clk_rate, SSI_MAX_CLK_DIVIDER); - master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; - master->dev.of_node = pdev->dev.of_node; - master->bus_num = pdev->id; - master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); + host->max_speed_hz = DIV_ROUND_UP(clk_rate, SSI_MIN_CLK_DIVIDER); + host->min_speed_hz = DIV_ROUND_UP(clk_rate, SSI_MAX_CLK_DIVIDER); + host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; + host->dev.of_node = pdev->dev.of_node; + host->bus_num = pdev->id; + host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); - master->set_cs = uniphier_spi_set_cs; - master->transfer_one = uniphier_spi_transfer_one; - master->prepare_transfer_hardware + host->set_cs = uniphier_spi_set_cs; + host->transfer_one = uniphier_spi_transfer_one; + host->prepare_transfer_hardware = uniphier_spi_prepare_transfer_hardware; - master->unprepare_transfer_hardware + host->unprepare_transfer_hardware = uniphier_spi_unprepare_transfer_hardware; - master->num_chipselect = 1; + host->handle_err = uniphier_spi_handle_err; + host->can_dma = uniphier_spi_can_dma; + + host->num_chipselect = 1; + host->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX; + + host->dma_tx = dma_request_chan(&pdev->dev, "tx"); + if (IS_ERR_OR_NULL(host->dma_tx)) { + if (PTR_ERR(host->dma_tx) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto out_disable_clk; + } + host->dma_tx = NULL; + dma_tx_burst = INT_MAX; + } else { + ret = dma_get_slave_caps(host->dma_tx, &caps); + if (ret) { + dev_err(&pdev->dev, "failed to get TX DMA capacities: %d\n", + ret); + goto out_release_dma; + } + dma_tx_burst = caps.max_burst; + } - ret = devm_spi_register_master(&pdev->dev, master); + host->dma_rx = dma_request_chan(&pdev->dev, "rx"); + if (IS_ERR_OR_NULL(host->dma_rx)) { + if (PTR_ERR(host->dma_rx) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto out_release_dma; + } + host->dma_rx = NULL; + dma_rx_burst = INT_MAX; + } else { + ret = dma_get_slave_caps(host->dma_rx, &caps); + if (ret) { + dev_err(&pdev->dev, "failed to get RX DMA capacities: %d\n", + ret); + goto out_release_dma; + } + dma_rx_burst = caps.max_burst; + } + + host->max_dma_len = min(dma_tx_burst, dma_rx_burst); + + ret = devm_spi_register_controller(&pdev->dev, host); if (ret) - goto out_disable_clk; + goto out_release_dma; return 0; +out_release_dma: + if (!IS_ERR_OR_NULL(host->dma_rx)) { + dma_release_channel(host->dma_rx); + host->dma_rx = NULL; + } + if (!IS_ERR_OR_NULL(host->dma_tx)) { + dma_release_channel(host->dma_tx); + host->dma_tx = NULL; + } + out_disable_clk: clk_disable_unprepare(priv->clk); -out_master_put: - spi_master_put(master); +out_host_put: + spi_controller_put(host); return ret; } -static int uniphier_spi_remove(struct platform_device *pdev) +static void uniphier_spi_remove(struct platform_device *pdev) { - struct uniphier_spi_priv *priv = platform_get_drvdata(pdev); + struct spi_controller *host = platform_get_drvdata(pdev); + struct uniphier_spi_priv *priv = spi_controller_get_devdata(host); - clk_disable_unprepare(priv->clk); + if (host->dma_tx) + dma_release_channel(host->dma_tx); + if (host->dma_rx) + dma_release_channel(host->dma_rx); - return 0; + clk_disable_unprepare(priv->clk); } static const struct of_device_id uniphier_spi_match[] = { |
