summaryrefslogblamecommitdiff
path: root/drivers/spi/spi-uniphier.c
blob: 4a18cf89619473f825160d5bf45be728e6478a66 (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                            
                        
                            


                            





                                    
                                   




                                   
                                 
                        
                                    






                                    
                          




























                                              
                                      


                                      
                                      
                                      

                                      
                                      
                                             




















                                              



                                      





                                                            

                                                                          
 






                                         

                                                                           
 








                                                         
                                                                                     













                                                                       
                                              




































                                                                            
                                                                                     
















                                                              
                                                                                     

















                                                                            
                                                                                     









                                                              
                                            











                                                                              
                                   



























































                                                                         

                                                                           
 

                

                                                        

                                                                         
                                         













                                                                     
 
                            




                                                                    
                                                                                     











                                          
                                                             


                                                        
                                                                          

                                                               


                                            






                                                          

                                                                          




                                                                          
                                                    



                                             

                                                                          




                                                                          
                                                    

 
                                                                     


                                                                
                                                                          





















                                                                      
                                                              

                                                 
                                     





                                                                           
                                              




                                                            
                                                      









                                                                   
                                                              

                                                 
                                     





                                                                           
                                              




                                                            
                                                      






                                                        
                                                       



                       
                                                                     

                                                                
 

                                                                          
                                
 



                                            
                                                                  
 

                                                                          
 
                                                                   
 



                                                    



                           
                                                                      


                                                                 
                                                                          



















                                                                          
                                                           

 
                                                                 


                                                            
                                                                          
                                
                     






                                                                     
                                                                      
                    
                                                                   
 






                                                                      
                                                                   
            
                                                                    

 
                                                                              
 
                                                                          





                                                 
                                                                                
 
                                                                          





                                        
                                                                

                                                            
                                                                          








                                          


                                                             
                                                        



                                                                
                                                        

                                                                

 











































                                                                      
                                    


                                               



                               

                                                         

                               
                                         
 

                                                

                                    
                                                                           

                                          
                                  
         
                                         




                                                             
                                  



                                            
                                  


                                        














                                                                     





                                                                            
 


                                                       
                                                                         
                                         
                                                                           

                                                   
 

                                                                      
 


                                                             
                                            
                                             
                 
                                    

                                       
                                                              


                                                                                    
                                             



                                              


                                                             
                                            
                                             
                 
                                    

                                       
                                                              


                                                                                    
                                             



                                              
                                                            
 
                                                             
                
                                     


                 
                


                                                  
         


                                                  

         


                                         

                                 


                   
                                                             
 

                                                                          
 



                                                  
 
                                         









                                                         
                                          










                                                                     
// SPDX-License-Identifier: GPL-2.0
// spi-uniphier.c - Socionext UniPhier SPI controller driver
// Copyright 2012      Panasonic Corporation
// Copyright 2016-2018 Socionext Inc.

#include <linux/kernel.h>
#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>

#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_controller *host;
	struct completion xfer_done;

	int error;
	unsigned int tx_bytes;
	unsigned int rx_bytes;
	const u8 *tx_buf;
	u8 *rx_buf;
	atomic_t dma_busy;

	bool is_save_param;
	u8 bits_per_word;
	u16 mode;
	u32 speed_hz;
};

#define SSI_CTL			0x00
#define   SSI_CTL_EN		BIT(0)

#define SSI_CKS			0x04
#define   SSI_CKS_CKRAT_MASK	GENMASK(7, 0)
#define   SSI_CKS_CKPHS		BIT(14)
#define   SSI_CKS_CKINIT	BIT(13)
#define   SSI_CKS_CKDLY		BIT(12)

#define SSI_TXWDS		0x08
#define   SSI_TXWDS_WDLEN_MASK	GENMASK(13, 8)
#define   SSI_TXWDS_TDTF_MASK	GENMASK(7, 6)
#define   SSI_TXWDS_DTLEN_MASK	GENMASK(5, 0)

#define SSI_RXWDS		0x0c
#define   SSI_RXWDS_DTLEN_MASK	GENMASK(5, 0)

#define SSI_FPS			0x10
#define   SSI_FPS_FSPOL		BIT(15)
#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)
#define   SSI_IS_RCID		BIT(3)
#define   SSI_IS_RORID		BIT(0)

#define SSI_IC			0x1c
#define   SSI_IC_TCIC		BIT(4)
#define   SSI_IC_RCIC		BIT(3)
#define   SSI_IC_RORIC		BIT(0)

#define SSI_FC			0x20
#define   SSI_FC_TXFFL		BIT(12)
#define   SSI_FC_TXFTH_MASK	GENMASK(11, 8)
#define   SSI_FC_RXFFL		BIT(4)
#define   SSI_FC_RXFTH_MASK	GENMASK(3, 0)

#define SSI_TXDR		0x24
#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 uniphier_spi_priv *priv,
					   u32 mask)
{
	u32 val;

	val = readl(priv->base + SSI_IE);
	val |= mask;
	writel(val, priv->base + SSI_IE);
}

static inline void uniphier_spi_irq_disable(struct uniphier_spi_priv *priv,
					    u32 mask)
{
	u32 val;

	val = readl(priv->base + SSI_IE);
	val &= ~mask;
	writel(val, priv->base + SSI_IE);
}

static void uniphier_spi_set_mode(struct spi_device *spi)
{
	struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller);
	u32 val1, val2;

	/*
	 * clock setting
	 * CKPHS    capture timing. 0:rising edge, 1:falling edge
	 * CKINIT   clock initial level. 0:low, 1:high
	 * CKDLY    clock delay. 0:no delay, 1:delay depending on FSTRT
	 *          (FSTRT=0: 1 clock, FSTRT=1: 0.5 clock)
	 *
	 * frame setting
	 * FSPOL    frame signal porarity. 0: low, 1: high
	 * FSTRT    start frame timing
	 *          0: rising edge of clock, 1: falling edge of clock
	 */
	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;
		val2 = 0;
		break;
	case SPI_MODE_1:
		/* CKPHS=0, CKINIT=0, CKDLY=0, FSTRT=1 */
		val1 = 0;
		val2 = SSI_FPS_FSTRT;
		break;
	case SPI_MODE_2:
		/* CKPHS=0, CKINIT=1, CKDLY=1, FSTRT=1 */
		val1 = SSI_CKS_CKINIT | SSI_CKS_CKDLY;
		val2 = SSI_FPS_FSTRT;
		break;
	case SPI_MODE_3:
		/* CKPHS=1, CKINIT=1, CKDLY=0, FSTRT=0 */
		val1 = SSI_CKS_CKPHS | SSI_CKS_CKINIT;
		val2 = 0;
		break;
	}

	if (!(spi->mode & SPI_CS_HIGH))
		val2 |= SSI_FPS_FSPOL;

	writel(val1, priv->base + SSI_CKS);
	writel(val2, priv->base + SSI_FPS);

	val1 = 0;
	if (spi->mode & SPI_LSB_FIRST)
		val1 |= FIELD_PREP(SSI_TXWDS_TDTF_MASK, 1);
	writel(val1, priv->base + SSI_TXWDS);
	writel(val1, priv->base + SSI_RXWDS);
}

static void uniphier_spi_set_transfer_size(struct spi_device *spi, int size)
{
	struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller);
	u32 val;

	val = readl(priv->base + SSI_TXWDS);
	val &= ~(SSI_TXWDS_WDLEN_MASK | SSI_TXWDS_DTLEN_MASK);
	val |= FIELD_PREP(SSI_TXWDS_WDLEN_MASK, size);
	val |= FIELD_PREP(SSI_TXWDS_DTLEN_MASK, size);
	writel(val, priv->base + SSI_TXWDS);

	val = readl(priv->base + SSI_RXWDS);
	val &= ~SSI_RXWDS_DTLEN_MASK;
	val |= FIELD_PREP(SSI_RXWDS_DTLEN_MASK, size);
	writel(val, priv->base + SSI_RXWDS);
}

static void uniphier_spi_set_baudrate(struct spi_device *spi,
				      unsigned int speed)
{
	struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller);
	u32 val, ckdiv;

	/*
	 * the supported rates are even numbers from 4 to 254. (4,6,8...254)
	 * round up as we look for equal or less speed
	 */
	ckdiv = DIV_ROUND_UP(clk_get_rate(priv->clk), speed);
	ckdiv = round_up(ckdiv, 2);

	val = readl(priv->base + SSI_CKS);
	val &= ~SSI_CKS_CKRAT_MASK;
	val |= ckdiv & SSI_CKS_CKRAT_MASK;
	writel(val, priv->base + SSI_CKS);
}

static void uniphier_spi_setup_transfer(struct spi_device *spi,
				       struct spi_transfer *t)
{
	struct uniphier_spi_priv *priv = spi_controller_get_devdata(spi->controller);
	u32 val;

	priv->error = 0;
	priv->tx_buf = t->tx_buf;
	priv->rx_buf = t->rx_buf;
	priv->tx_bytes = priv->rx_bytes = t->len;

	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) {
		uniphier_spi_set_transfer_size(spi, t->bits_per_word);
		priv->bits_per_word = t->bits_per_word;
	}

	if (!priv->is_save_param || priv->speed_hz != t->speed_hz) {
		uniphier_spi_set_baudrate(spi, t->speed_hz);
		priv->speed_hz = t->speed_hz;
	}

	priv->is_save_param = true;

	/* reset FIFOs */
	val = SSI_FC_TXFFL | SSI_FC_RXFFL;
	writel(val, priv->base + SSI_FC);
}

static void uniphier_spi_send(struct uniphier_spi_priv *priv)
{
	int wsize;
	u32 val = 0;

	wsize = min(bytes_per_word(priv->bits_per_word), priv->tx_bytes);
	priv->tx_bytes -= wsize;

	if (priv->tx_buf) {
		switch (wsize) {
		case 1:
			val = *priv->tx_buf;
			break;
		case 2:
			val = get_unaligned_le16(priv->tx_buf);
			break;
		case 4:
			val = get_unaligned_le32(priv->tx_buf);
			break;
		}

		priv->tx_buf += wsize;
	}

	writel(val, priv->base + SSI_TXDR);
}

static void uniphier_spi_recv(struct uniphier_spi_priv *priv)
{
	int rsize;
	u32 val;

	rsize = min(bytes_per_word(priv->bits_per_word), priv->rx_bytes);
	priv->rx_bytes -= rsize;

	val = readl(priv->base + SSI_RXDR);

	if (priv->rx_buf) {
		switch (rsize) {
		case 1:
			*priv->rx_buf = val;
			break;
		case 2:
			put_unaligned_le16(val, priv->rx_buf);
			break;
		case 4:
			put_unaligned_le32(val, priv->rx_buf);
			break;
		}

		priv->rx_buf += rsize;
	}
}

static void uniphier_spi_set_fifo_threshold(struct uniphier_spi_priv *priv,
					    unsigned int threshold)
{
	u32 val;

	val = readl(priv->base + SSI_FC);
	val &= ~(SSI_FC_TXFTH_MASK | SSI_FC_RXFTH_MASK);
	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);

	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_controller_get_devdata(spi->controller);
	u32 val;

	val = readl(priv->base + SSI_FPS);

	if (enable)
		val |= SSI_FPS_FSPOL;
	else
		val &= ~SSI_FPS_FSPOL;

	writel(val, priv->base + SSI_FPS);
}

static bool uniphier_spi_can_dma(struct spi_controller *host,
				 struct spi_device *spi,
				 struct spi_transfer *t)
{
	struct uniphier_spi_priv *priv = spi_controller_get_devdata(host);
	unsigned int bpw = bytes_per_word(priv->bits_per_word);

	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(priv, SSI_IE_RCIE | SSI_IE_RORIE);

	time_left = wait_for_completion_timeout(&priv->xfer_done,
					msecs_to_jiffies(SSI_TIMEOUT_MS));

	uniphier_spi_irq_disable(priv, SSI_IE_RCIE | SSI_IE_RORIE);

	if (!time_left) {
		dev_err(dev, "transfer timeout.\n");
		return -ETIMEDOUT;
	}

	return priv->error;
}

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_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_controller *host)
{
	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;
	u32 val, stat;

	stat = readl(priv->base + SSI_IS);
	val = SSI_IC_TCIC | SSI_IC_RCIC | SSI_IC_RORIC;
	writel(val, priv->base + SSI_IC);

	/* rx fifo overrun */
	if (stat & SSI_IS_RORID) {
		priv->error = -EIO;
		goto done;
	}

	/* rx complete */
	if ((stat & SSI_IS_RCID) && (stat & SSI_IS_RXRS)) {
		while ((readl(priv->base + SSI_SR) & SSI_SR_RNE) &&
				(priv->rx_bytes - priv->tx_bytes) > 0)
			uniphier_spi_recv(priv);

		if ((readl(priv->base + SSI_SR) & SSI_SR_RNE) ||
				(priv->rx_bytes != priv->tx_bytes)) {
			priv->error = -EIO;
			goto done;
		} else if (priv->rx_bytes == 0)
			goto done;

		/* next tx transfer */
		uniphier_spi_fill_tx_fifo(priv);

		return IRQ_HANDLED;
	}

	return IRQ_NONE;

done:
	complete(&priv->xfer_done);
	return IRQ_HANDLED;
}

static int uniphier_spi_probe(struct platform_device *pdev)
{
	struct uniphier_spi_priv *priv;
	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;

	host = spi_alloc_host(&pdev->dev, sizeof(*priv));
	if (!host)
		return -ENOMEM;

	platform_set_drvdata(pdev, host);

	priv = spi_controller_get_devdata(host);
	priv->host = host;
	priv->is_save_param = false;

	priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
	if (IS_ERR(priv->base)) {
		ret = PTR_ERR(priv->base);
		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_host_put;
	}

	ret = clk_prepare_enable(priv->clk);
	if (ret)
		goto out_host_put;

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		ret = irq;
		goto out_disable_clk;
	}

	ret = devm_request_irq(&pdev->dev, irq, uniphier_spi_handler,
			       0, "uniphier-spi", priv);
	if (ret) {
		dev_err(&pdev->dev, "failed to request IRQ\n");
		goto out_disable_clk;
	}

	init_completion(&priv->xfer_done);

	clk_rate = clk_get_rate(priv->clk);

	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);

	host->set_cs = uniphier_spi_set_cs;
	host->transfer_one = uniphier_spi_transfer_one;
	host->prepare_transfer_hardware
				= uniphier_spi_prepare_transfer_hardware;
	host->unprepare_transfer_hardware
				= uniphier_spi_unprepare_transfer_hardware;
	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;
	}

	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_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_host_put:
	spi_controller_put(host);
	return ret;
}

static void uniphier_spi_remove(struct platform_device *pdev)
{
	struct spi_controller *host = platform_get_drvdata(pdev);
	struct uniphier_spi_priv *priv = spi_controller_get_devdata(host);

	if (host->dma_tx)
		dma_release_channel(host->dma_tx);
	if (host->dma_rx)
		dma_release_channel(host->dma_rx);

	clk_disable_unprepare(priv->clk);
}

static const struct of_device_id uniphier_spi_match[] = {
	{ .compatible = "socionext,uniphier-scssi" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uniphier_spi_match);

static struct platform_driver uniphier_spi_driver = {
	.probe = uniphier_spi_probe,
	.remove_new = uniphier_spi_remove,
	.driver = {
		.name = "uniphier-spi",
		.of_match_table = uniphier_spi_match,
	},
};
module_platform_driver(uniphier_spi_driver);

MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
MODULE_AUTHOR("Keiji Hayashibara <hayashibara.keiji@socionext.com>");
MODULE_DESCRIPTION("Socionext UniPhier SPI controller driver");
MODULE_LICENSE("GPL v2");