mirror of https://github.com/OpenIPC/firmware.git
				
				
				
			
		
			
				
	
	
		
			1491 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Diff
		
	
	
			
		
		
	
	
			1491 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Diff
		
	
	
| diff -drupN a/drivers/spi/ingenic_spi.c b/drivers/spi/ingenic_spi.c
 | |
| --- a/drivers/spi/ingenic_spi.c	1970-01-01 03:00:00.000000000 +0300
 | |
| +++ b/drivers/spi/ingenic_spi.c	2022-06-09 05:02:33.000000000 +0300
 | |
| @@ -0,0 +1,1486 @@
 | |
| +/* linux/drivers/spi/ingenic_spi.c
 | |
| + *
 | |
| + * SSI controller for SPI protocol,use FIFO and DMA;
 | |
| + * base-to: linux/drivers/spi/spi_bitbang.c
 | |
| + *
 | |
| + * Copyright (c) 2010 Ingenic
 | |
| + * Author:Shumb <sbhuang@ingenic.cn>
 | |
| + *
 | |
| + * This program is free software; you can redistribute it and/or modify
 | |
| + * it under the terms of the GNU General Public License version 2 as
 | |
| + * published by the Free Software Foundation.
 | |
| + *
 | |
| +*/
 | |
| +
 | |
| +#include <linux/init.h>
 | |
| +#include <linux/spinlock.h>
 | |
| +#include <linux/workqueue.h>
 | |
| +#include <linux/interrupt.h>
 | |
| +#include <linux/delay.h>
 | |
| +#include <linux/dmaengine.h>
 | |
| +#include <linux/dma-mapping.h>
 | |
| +#include <linux/errno.h>
 | |
| +#include <linux/err.h>
 | |
| +#include <linux/clk.h>
 | |
| +#include <linux/gpio.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/spi/spi.h>
 | |
| +#include <linux/spi/spi_bitbang.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/of_gpio.h>
 | |
| +#include <asm/uaccess.h>
 | |
| +#include <asm/irq.h>
 | |
| +#include <asm/io.h>
 | |
| +
 | |
| +#include "ingenic_spi.h"
 | |
| +
 | |
| +/* #define SSI_DEGUG */
 | |
| +#ifdef SSI_DEGUG
 | |
| +#define  print_dbg(format,arg...)				\
 | |
| +	printk(format,## arg)
 | |
| +#else
 | |
| +#define  print_dbg(format,arg...)
 | |
| +#endif
 | |
| +
 | |
| +#define INGENIC_SPI_RX_BUF(type)							\
 | |
| +	u32 ingenic_spi_rx_buf_##type(struct ingenic_spi *ingspi)	\
 | |
| +	{														\
 | |
| +		u32 data  = spi_readl(ingspi, SSI_DR);					\
 | |
| +		type * rx = (type *)ingspi->rx;							\
 | |
| +		*rx++ = (type)(data);								\
 | |
| +		ingspi->rx = (u8 *)rx;									\
 | |
| +		return (u32)data;									\
 | |
| +	}
 | |
| +
 | |
| +#define INGENIC_SPI_TX_BUF(type)							\
 | |
| +	u32 ingenic_spi_tx_buf_##type(struct ingenic_spi *ingspi)	\
 | |
| +	{														\
 | |
| +		u32 data;											\
 | |
| +		const type * tx = (type *)ingspi->tx;					\
 | |
| +		data = *tx++;										\
 | |
| +		ingspi->tx = (u8 *)tx;									\
 | |
| +		transmit_data(ingspi, data);							\
 | |
| +		return (u32)data;									\
 | |
| +	}
 | |
| +
 | |
| +INGENIC_SPI_RX_BUF(u8)
 | |
| +INGENIC_SPI_TX_BUF(u8)
 | |
| +
 | |
| +INGENIC_SPI_RX_BUF(u16)
 | |
| +INGENIC_SPI_TX_BUF(u16)
 | |
| +
 | |
| +INGENIC_SPI_RX_BUF(u32)
 | |
| +INGENIC_SPI_TX_BUF(u32)
 | |
| +
 | |
| +/* the spi->mode bits understood by this driver: */
 | |
| +#define MODEBITS (SPI_3WIRE | SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_LOOP)
 | |
| +#define SPI_BITS_SUPPORT  (SPI_BITS_8 | SPI_BITS_16 | SPI_BITS_32)
 | |
| +
 | |
| +static void ingenic_spi_cs(struct ingenic_spi_info *spi, u8 cs, unsigned int pol)
 | |
| +{
 | |
| +#ifdef CONFIG_INGENIC_SPI_PIO_CE
 | |
| +	u32 pin_value = *(spi->chipselect + cs);
 | |
| +	gpio_direction_output(pin_value, !pol ? 0 : 1);
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static void ingenic_spi_chipsel(struct spi_device *spi, int value)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0;
 | |
| +
 | |
| +    /*printk("%s[%d]: value = %d\n",__func__,__LINE__, value);*/
 | |
| +	switch (value) {
 | |
| +	case BITBANG_CS_INACTIVE:
 | |
| +		/* chip disable selected */
 | |
| +		if (ingspi->set_cs && ingspi->pdata)
 | |
| +			ingspi->set_cs(ingspi->pdata, spi->chip_select, cspol^1);
 | |
| +		break;
 | |
| +	case BITBANG_CS_ACTIVE:
 | |
| +		if (spi->mode & SPI_CPHA)
 | |
| +			set_spi_clock_phase(ingspi, 1);
 | |
| +		else
 | |
| +			set_spi_clock_phase(ingspi, 0);
 | |
| +
 | |
| +		if (spi->mode & SPI_CPOL)
 | |
| +			set_spi_clock_polarity(ingspi, 1);
 | |
| +		else
 | |
| +			set_spi_clock_polarity(ingspi, 0);
 | |
| +
 | |
| +		if (!(spi->mode & SPI_LSB_FIRST)) {
 | |
| +			set_tx_msb(ingspi);
 | |
| +			set_rx_msb(ingspi);
 | |
| +		} else {
 | |
| +			set_tx_lsb(ingspi);
 | |
| +			set_rx_lsb(ingspi);
 | |
| +		}
 | |
| +
 | |
| +		if (spi->mode & SPI_LOOP)
 | |
| +			enable_loopback(ingspi);
 | |
| +		else
 | |
| +			disable_loopback(ingspi);
 | |
| +
 | |
| +		/* chip enable selected */
 | |
| +		if (ingspi->set_cs && ingspi->pdata)
 | |
| +			ingspi->set_cs(ingspi->pdata, spi->chip_select, cspol);
 | |
| +		break;
 | |
| +	default:
 | |
| +		break;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void ingenic_spi_clk_enable(struct ingenic_spi *ingspi) {
 | |
| +	if(ingspi->clk_flag == 0)
 | |
| +		return;
 | |
| +
 | |
| +	clk_set_rate(ingspi->clk_cgu, ingspi->max_clk);
 | |
| +	clk_prepare_enable(ingspi->clk_cgu);
 | |
| +	clk_prepare_enable(ingspi->clk_gate);
 | |
| +	ingspi->clk_flag = 0;
 | |
| +}
 | |
| +
 | |
| +static void ingenic_spi_clk_disable(struct ingenic_spi *ingspi) {
 | |
| +	if(ingspi->clk_flag)
 | |
| +		return;
 | |
| +
 | |
| +	clk_disable_unprepare(ingspi->clk_cgu);
 | |
| +	clk_disable_unprepare(ingspi->clk_gate);
 | |
| +	ingspi->clk_flag = 1;
 | |
| +}
 | |
| +
 | |
| +static unsigned long ingenic_spi_clk_get_rate(struct spi_device *spi)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	unsigned long rate;
 | |
| +	u16 cgv;
 | |
| +
 | |
| +	spin_lock(&ingspi->lock);
 | |
| +	rate = clk_get_rate(ingspi->clk_cgu);
 | |
| +	cgv = spi_readl(ingspi, SSI_GR);
 | |
| +	spin_unlock(&ingspi->lock);
 | |
| +	return (rate / (2 * (cgv + 1)));
 | |
| +}
 | |
| +static int ingenic_spi_clk_set_rate(struct spi_device *spi, unsigned long rate)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	unsigned long cur_rate;
 | |
| +	unsigned long src_rate;
 | |
| +	int cgv;
 | |
| +
 | |
| +	cur_rate = ingenic_spi_clk_get_rate(spi);
 | |
| +	if(cur_rate == rate)
 | |
| +		return 0;
 | |
| +	spin_lock(&ingspi->lock);
 | |
| +	src_rate = clk_get_rate(ingspi->clk_cgu);
 | |
| +	cgv = (src_rate / (rate * 2)) - 1;
 | |
| +	if(cgv < 0) {
 | |
| +		printk("spi clk set %ld not support, src_rate = %ld\n", rate, src_rate);
 | |
| +		return -1;
 | |
| +	}
 | |
| +    /*printk("%s[%d]: clk_cgu = %ld, cur_rate = %d, rate = %ld, cgv = %d\n",__func__,__LINE__,src_rate, cur_rate, rate, cgv);*/
 | |
| +	spi_writel(ingspi, SSI_GR, cgv);
 | |
| +	spin_unlock(&ingspi->lock);
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void dma_tx_callback(void *data)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = data;
 | |
| +
 | |
| +	dma_unmap_sg(ingspi->txchan->device->dev, ingspi->sg_tx, 1, DMA_TO_DEVICE);
 | |
| +	complete(&ingspi->done_tx_dma);
 | |
| +}
 | |
| +
 | |
| +static void dma_rx_callback(void *data)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = data;
 | |
| +
 | |
| +	dma_unmap_sg(ingspi->txchan->device->dev, ingspi->sg_tx, 1, DMA_TO_DEVICE);
 | |
| +	dma_unmap_sg(ingspi->rxchan->device->dev, ingspi->sg_rx, 1, DMA_FROM_DEVICE);
 | |
| +	complete(&ingspi->done_rx_dma);
 | |
| +}
 | |
| +
 | |
| +/*extern void jzdma_dump(struct dma_chan *chan);*/
 | |
| +static int ingenic_spi_dma_txrx(struct spi_device *spi, struct spi_transfer *t)
 | |
| +{
 | |
| +	int ret;
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	struct dma_slave_config rx_config, tx_config;
 | |
| +	struct dma_async_tx_descriptor *rxdesc;
 | |
| +	struct dma_async_tx_descriptor *txdesc;
 | |
| +	struct dma_chan *rxchan = ingspi->rxchan;
 | |
| +	struct dma_chan *txchan = ingspi->txchan;
 | |
| +	struct ingenic_intr_cnt *g_ingenic_intr;
 | |
| +	int dma_ds[] = {64, 32, 16, 4, 2, 1};
 | |
| +	int i;
 | |
| +
 | |
| +//    printk("%s[%d]: \n",__func__,__LINE__);
 | |
| +	/* Check that the channels are available */
 | |
| +	if (!txchan || !rxchan) {
 | |
| +		dev_err(&spi->dev, "no dma channel\n");
 | |
| +		return -ENODEV;
 | |
| +	}
 | |
| +
 | |
| +	if (t->len % ingspi->transfer_unit_size) {
 | |
| +		pr_err("The length of tranfer data is error\n");
 | |
| +		return -EFAULT;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->rw_mode = 0;
 | |
| +	if(t->tx_buf)
 | |
| +		ingspi->rw_mode |= W_MODE;
 | |
| +	if(t->rx_buf)
 | |
| +		ingspi->rw_mode |= R_MODE;
 | |
| +
 | |
| +	/* all transfer starts with tx, ends with rx. */
 | |
| +	if (ingspi->rw_mode & W_MODE)
 | |
| +		ingspi->tx = t->tx_buf;
 | |
| +	else
 | |
| +		ingspi->tx = ingspi->buffer;
 | |
| +
 | |
| +	if (ingspi->rw_mode & R_MODE)
 | |
| +		ingspi->rx = t->rx_buf;
 | |
| +	else
 | |
| +		ingspi->rx = ingspi->buffer;
 | |
| +
 | |
| +	memset(ingspi->buffer, 0, BUFFER_SIZE);
 | |
| +
 | |
| +	switch (ingspi->transfer_unit_size) {
 | |
| +	case SPI_8BITS:
 | |
| +		tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
 | |
| +		tx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
 | |
| +		rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
 | |
| +		rx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
 | |
| +		tx_config.dst_maxburst = 1;
 | |
| +		tx_config.src_maxburst = 1;
 | |
| +		rx_config.src_maxburst = 1;
 | |
| +		rx_config.dst_maxburst = 1;
 | |
| +		break;
 | |
| +	case SPI_16BITS:
 | |
| +		tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
 | |
| +		tx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
 | |
| +		rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
 | |
| +		rx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
 | |
| +		tx_config.dst_maxburst = 2;
 | |
| +		tx_config.src_maxburst = 2;
 | |
| +		rx_config.src_maxburst = 2;
 | |
| +		rx_config.dst_maxburst = 2;
 | |
| +		break;
 | |
| +	case SPI_32BITS:
 | |
| +		tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 | |
| +		tx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 | |
| +		rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 | |
| +		rx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 | |
| +		tx_config.dst_maxburst = 4;
 | |
| +		tx_config.src_maxburst = 4;
 | |
| +		rx_config.src_maxburst = 4;
 | |
| +		rx_config.dst_maxburst = 4;
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	tx_config.dst_addr = (dma_addr_t)(ingspi->phys + SSI_DR);
 | |
| +	rx_config.src_addr = (dma_addr_t)(ingspi->phys + SSI_DR);
 | |
| +
 | |
| +	tx_config.direction = DMA_MEM_TO_DEV;
 | |
| +	rx_config.direction = DMA_DEV_TO_MEM;
 | |
| +
 | |
| +	dmaengine_slave_config(txchan, &tx_config);
 | |
| +	dmaengine_slave_config(rxchan, &rx_config);
 | |
| +
 | |
| +	/* set tx dma trigger */
 | |
| +	for (i = 0; i < ARRAY_SIZE(dma_ds); i++) {
 | |
| +		if (t->len / dma_ds[i])
 | |
| +			break;
 | |
| +	}
 | |
| +
 | |
| +	if (i < ARRAY_SIZE(dma_ds)) {
 | |
| +		ingspi->dma_tx_unit = dma_ds[i];
 | |
| +	} else {
 | |
| +		print_dbg("DMA tx block_size force to defaut set!!!");
 | |
| +		ingspi->dma_tx_unit = INGENIC_SSI_DMA_BURST_LENGTH;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->tx_trigger = ingspi->dma_tx_unit / (ingspi->txfifo_width >> 3);
 | |
| +	//set_tx_trigger(ingspi, ingspi->tx_trigger);
 | |
| +	set_tx_trigger(ingspi, 8); //The transfer is steady if the trigger number is used
 | |
| +	print_dbg("t->len: %d, tx fifo width: %d, set tx trigger value to %d\n", t->len, ingspi->txfifo_width, ingspi->tx_trigger);
 | |
| +
 | |
| +	sg_init_one(ingspi->sg_tx, ingspi->tx, t->len);
 | |
| +	if (dma_map_sg(ingspi->txchan->device->dev,
 | |
| +		       ingspi->sg_tx, 1, DMA_TO_DEVICE) != 1) {
 | |
| +		dev_err(&spi->dev, "dma_map_sg tx error\n");
 | |
| +		printk("%s LINE %d: %s\n", __func__, __LINE__, __FILE__);
 | |
| +		goto err_tx_sgmap;
 | |
| +	}
 | |
| +
 | |
| +	txdesc = txchan->device->device_prep_slave_sg(txchan,
 | |
| +				      ingspi->sg_tx,
 | |
| +				      1,
 | |
| +				      DMA_TO_DEVICE,
 | |
| +				      DMA_PREP_INTERRUPT | DMA_CTRL_ACK,NULL);
 | |
| +	if (!txdesc) {
 | |
| +		dev_err(&spi->dev, "device_prep_slave_sg error\n");
 | |
| +		printk("%s LINE %d: %s\n", __func__, __LINE__, __FILE__);
 | |
| +		goto err_txdesc;
 | |
| +	}
 | |
| +
 | |
| +	// config controller
 | |
| +	disable_tx_intr(ingspi);
 | |
| +	disable_rx_intr(ingspi);
 | |
| +
 | |
| +	//revisit
 | |
| +	disable_tx_error_intr(ingspi);
 | |
| +	disable_rx_error_intr(ingspi);
 | |
| +
 | |
| +	start_transmit(ingspi);
 | |
| +	//finish_transmit(ingspi);
 | |
| +
 | |
| +	flush_fifo(ingspi);
 | |
| +
 | |
| +//	enable_receive(ingspi);
 | |
| +	clear_errors(ingspi);
 | |
| +
 | |
| +	g_ingenic_intr = ingspi->g_ingenic_intr;
 | |
| +	memset(g_ingenic_intr, 0, sizeof *g_ingenic_intr);
 | |
| +
 | |
| +	if (!(ingspi->rw_mode & R_MODE)) {
 | |
| +		txdesc->callback = dma_tx_callback;
 | |
| +		txdesc->callback_param = ingspi;
 | |
| +		enable_tx_error_intr(ingspi);
 | |
| +
 | |
| +		dmaengine_submit(txdesc);
 | |
| +		dma_async_issue_pending(txchan);
 | |
| +
 | |
| +		enable_receive(ingspi);
 | |
| +		ret = wait_for_completion_interruptible_timeout(&ingspi->done_tx_dma, 60 * HZ);
 | |
| +		if (ret <= 0) {
 | |
| +			printk("The tx_dma umap wait timeout\n");
 | |
| +			goto err_txdesc;
 | |
| +		}
 | |
| +		ret = wait_for_completion_interruptible_timeout(&ingspi->done, 60 * HZ);
 | |
| +		if (ret <= 0) {
 | |
| +			printk("The spi transfer wait timeout\n");
 | |
| +			goto err_txdesc;
 | |
| +		}
 | |
| +
 | |
| +		if(t->cs_change)
 | |
| +			finish_transmit(ingspi);
 | |
| +		flush_rxfifo(ingspi);
 | |
| +		clear_errors(ingspi);
 | |
| +
 | |
| +		return t->len;
 | |
| +	}
 | |
| +	/*  prepare spi dma rx */
 | |
| +	for (i = 0; i < ARRAY_SIZE(dma_ds); i++) {
 | |
| +		if (!(t->len % dma_ds[i]))
 | |
| +			break;
 | |
| +	}
 | |
| +
 | |
| +	if (i < ARRAY_SIZE(dma_ds)) {
 | |
| +		ingspi->dma_rx_unit = dma_ds[i];
 | |
| +	} else {
 | |
| +		print_dbg("DMA rx block_size force to defaut set!!!");
 | |
| +		ingspi->dma_rx_unit = INGENIC_SSI_DMA_BURST_LENGTH;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->rx_trigger = ingspi->dma_rx_unit/(ingspi->rxfifo_width >> 3);
 | |
| +	//set_rx_trigger(ingspi, ingspi->rx_trigger);
 | |
| +	set_rx_trigger(ingspi, 1); //the rx trigger is steady for tranfer
 | |
| +	print_dbg("t->len: %d, rx fifo width: %d, set rx trigger value to %d\n", t->len, ingspi->rxfifo_width, ingspi->rx_trigger);
 | |
| +
 | |
| +	sg_init_one(ingspi->sg_rx, ingspi->rx, t->len);
 | |
| +
 | |
| +	if (dma_map_sg(ingspi->rxchan->device->dev,
 | |
| +		       ingspi->sg_rx, 1, DMA_FROM_DEVICE) != 1) {
 | |
| +		dev_err(&spi->dev, "dma_map_sg rx error\n");
 | |
| +		goto err_rx_sgmap;
 | |
| +	}
 | |
| +
 | |
| +	rxdesc = rxchan->device->device_prep_slave_sg(rxchan,
 | |
| +				      ingspi->sg_rx,
 | |
| +				      1,
 | |
| +				      DMA_FROM_DEVICE,
 | |
| +				      DMA_PREP_INTERRUPT | DMA_CTRL_ACK,NULL);
 | |
| +	if (!rxdesc) {
 | |
| +		dev_err(&spi->dev, "device_prep_slave_sg error\n");
 | |
| +		goto err_rxdesc;
 | |
| +	}
 | |
| +
 | |
| +	txdesc->callback = NULL;
 | |
| +	txdesc->callback_param = NULL;
 | |
| +
 | |
| +	rxdesc->callback = dma_rx_callback;
 | |
| +	rxdesc->callback_param = ingspi;
 | |
| +	enable_rx_error_intr(ingspi);
 | |
| +	enable_tx_error_intr(ingspi);
 | |
| +
 | |
| +	dmaengine_submit(txdesc);
 | |
| +	dmaengine_submit(rxdesc);
 | |
| +
 | |
| +	dma_async_issue_pending(rxchan);
 | |
| +	dma_async_issue_pending(txchan);
 | |
| +
 | |
| +	enable_receive(ingspi);
 | |
| +//	dump_spi_reg(ingspi);
 | |
| +	ret = wait_for_completion_interruptible_timeout(&ingspi->done_rx, 60 * HZ);
 | |
| +	if (ret <= 0) {
 | |
| +		dump_spi_reg(ingspi);
 | |
| +		printk("The spi receiver wait timeout\n");
 | |
| +		goto err_rxdesc;
 | |
| +	}
 | |
| +	/*jzdma_dump(rxchan);*/
 | |
| +    /*printk("%s[%d]: wait dma\n",__func__,__LINE__);*/
 | |
| +	ret = wait_for_completion_interruptible_timeout(&ingspi->done_rx_dma, 60 * HZ);
 | |
| +	if (ret <= 0) {
 | |
| +		dump_spi_reg(ingspi);
 | |
| +		printk("The spi dam_callback wait timeout\n");
 | |
| +		goto err_rxdesc;
 | |
| +	}
 | |
| +	finish_transmit(ingspi);
 | |
| +	//flush_rxfifo(ingspi);
 | |
| +	clear_errors(ingspi);
 | |
| +
 | |
| +	return t->len;
 | |
| +
 | |
| +err_rxdesc:
 | |
| +	dma_unmap_sg(rxchan->device->dev, ingspi->sg_rx, 1, DMA_FROM_DEVICE);
 | |
| +err_rx_sgmap:
 | |
| +err_txdesc:
 | |
| +	dma_unmap_sg(txchan->device->dev, ingspi->sg_tx, 1, DMA_TO_DEVICE);
 | |
| +err_tx_sgmap:
 | |
| +	printk("<< dma_txrx error. out of memory >>\n");
 | |
| +	return -ENOMEM;
 | |
| +}
 | |
| +
 | |
| +static irqreturn_t ingenic_spi_dma_irq_callback(struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	struct ingenic_intr_cnt *g_ingenic_intr = ingspi->g_ingenic_intr;
 | |
| +	print_dbg("%s: status register: %08x\n", __func__, spi_readl(ingspi, SSI_SR));
 | |
| +
 | |
| +	if (ssi_underrun(ingspi) && tx_error_intr(ingspi)) {
 | |
| +		print_dbg("UNDR:\n");
 | |
| +
 | |
| +		g_ingenic_intr->ssi_eti++;
 | |
| +		disable_tx_error_intr(ingspi);
 | |
| +
 | |
| +		clear_errors(ingspi);
 | |
| +		complete(&ingspi->done);
 | |
| +		complete(&ingspi->done_rx);
 | |
| +
 | |
| +		goto irq_done;
 | |
| +	}
 | |
| +
 | |
| +	if (ssi_overrun(ingspi) && rx_error_intr(ingspi)) {
 | |
| +			print_dbg(" overrun:\n");
 | |
| +			g_ingenic_intr->ssi_eri++;
 | |
| +
 | |
| +			clear_errors(ingspi);
 | |
| +			complete(&ingspi->done);
 | |
| +			complete(&ingspi->done_rx);
 | |
| +	}
 | |
| +
 | |
| +irq_done:
 | |
| +	return IRQ_HANDLED;
 | |
| +}
 | |
| +
 | |
| +static inline u32 cpu_read_rxfifo(struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	u8 unit_size = ingspi->transfer_unit_size;
 | |
| +	u32 cnt, dat;
 | |
| +	int dummy_read = 0;
 | |
| +
 | |
| +	print_dbg("The count of RxFIFO is %d \n", get_rxfifo_count(ingspi));
 | |
| +	if (get_rxfifo_count(ingspi) < 1)
 | |
| +		return 0;
 | |
| +
 | |
| +	cnt = ingspi->rlen;
 | |
| +	if ((ingspi->rw_mode & RW_MODE) == W_MODE) {
 | |
| +		print_dbg("W_MODE\n");
 | |
| +		dummy_read = 1;
 | |
| +	}
 | |
| +
 | |
| +	spin_lock(&ingspi->lock);
 | |
| +
 | |
| +	while (!rxfifo_empty(ingspi)) {
 | |
| +		ingspi->rlen += unit_size;
 | |
| +		if (dummy_read)
 | |
| +			dat = spi_readl(ingspi, SSI_DR);
 | |
| +		else
 | |
| +			dat = ingspi->get_rx(ingspi);
 | |
| +	}
 | |
| +
 | |
| +	spin_unlock(&ingspi->lock);
 | |
| +
 | |
| +	return (ingspi->rlen - cnt);
 | |
| +}
 | |
| +
 | |
| +static inline u32 cpu_write_txfifo(struct ingenic_spi *ingspi, u32 entries)
 | |
| +{
 | |
| +	u8 unit_size = ingspi->transfer_unit_size;
 | |
| +	u32 i, cnt, count;
 | |
| +	u32 dat;
 | |
| +
 | |
| +	if ((!entries ) || (!(ingspi->rw_mode & RW_MODE)))
 | |
| +		return 0;
 | |
| +
 | |
| +	cnt = entries;
 | |
| +	count = cnt * unit_size;
 | |
| +
 | |
| +	spin_lock(&ingspi->lock);
 | |
| +	if (ingspi->rw_mode & W_MODE) {
 | |
| +		for (i = 0; i < cnt; i++) {
 | |
| +			ingspi->count += unit_size;
 | |
| +			dat = (u32)(ingspi->get_tx(ingspi));
 | |
| +		}
 | |
| +	} else {		 /* read, fill txfifo with 0 */
 | |
| +		for (i = 0; i < cnt; i++) {
 | |
| +			ingspi->count += unit_size;
 | |
| +			transmit_data(ingspi, 0);
 | |
| +		}
 | |
| +	}
 | |
| +	spin_unlock(&ingspi->lock);
 | |
| +
 | |
| +	print_dbg("ingspi->count:%d. %s LINE %d: %s\n", ingspi->count, __func__, __LINE__, __FILE__);
 | |
| +	return count;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_cpu_transfer(struct ingenic_spi *ingspi, long length)
 | |
| +{
 | |
| +	unsigned char int_flag = 0, last_flag = 0;
 | |
| +	u32 entries = 0, send_entries = 0;
 | |
| +	u32 unit_size, trigger;
 | |
| +	long leave_len_bytes;
 | |
| +	u32 retlen;
 | |
| +
 | |
| +	print_dbg("%s LINE %d: %s\n", __func__, __LINE__, __FILE__);
 | |
| +
 | |
| +	/* calculate the left entries */
 | |
| +	leave_len_bytes = ingspi->len - ingspi->count;
 | |
| +
 | |
| +	if (ingspi->len < ingspi->count) {
 | |
| +		dev_err(ingspi->dev,
 | |
| +			"Fill data len error!!!(len : count > %d : %d)\n",
 | |
| +			ingspi->len, ingspi->count);
 | |
| +		return -1;
 | |
| +	}
 | |
| +
 | |
| +	if (leave_len_bytes == 0) {
 | |
| +		print_dbg("leave_len_bytes = 0\n");
 | |
| +		printk("leave_len_bytes = 0\n");
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	if (ingspi->len % ingspi->transfer_unit_size) {
 | |
| +		pr_err("The length of tranfer data is error\n");
 | |
| +		return -EFAULT;
 | |
| +	}
 | |
| +
 | |
| +	unit_size = ingspi->transfer_unit_size;
 | |
| +	if (unit_size == SPI_8BITS)
 | |
| +		entries = leave_len_bytes;
 | |
| +	else if (unit_size == SPI_16BITS )
 | |
| +		entries = leave_len_bytes >> 1;
 | |
| +	else if (unit_size == SPI_32BITS )
 | |
| +		entries = leave_len_bytes >> 2;
 | |
| +	else {
 | |
| +		dev_err(ingspi->dev,"transfer_unit_size error!\n");
 | |
| +		return -1;
 | |
| +	}
 | |
| +	print_dbg("%s unit_size:%d, entries:%d\n", __func__, unit_size, entries);
 | |
| +
 | |
| +	/* calculate the entries which will be sent currently
 | |
| +	 * distinguish between the first and interrupt */
 | |
| +	if (ingspi->is_first) {
 | |
| +		/* CPU Mode should reset SSI triggers at first */
 | |
| +		ingspi->tx_trigger = SSI_TX_FIFO_THRESHOLD * 8;
 | |
| +		ingspi->rx_trigger = (SSI_RX_FIFO_THRESHOLD - SSI_SAFE_THRESHOLD) * 8;
 | |
| +
 | |
| +		set_tx_trigger(ingspi, ingspi->tx_trigger);
 | |
| +		set_rx_trigger(ingspi, ingspi->rx_trigger);
 | |
| +
 | |
| +		if(entries <= INGENIC_SSI_MAX_FIFO_ENTRIES)	{
 | |
| +			send_entries = entries;
 | |
| +		} else {
 | |
| +			/* need enable half_intr, left entries will be sent
 | |
| +			   in SSI interrupt and receive the datas */
 | |
| +			send_entries = INGENIC_SSI_MAX_FIFO_ENTRIES;
 | |
| +			int_flag = 1;
 | |
| +		}
 | |
| +		start_transmit(ingspi);
 | |
| +
 | |
| +		ingspi->is_first = 0;
 | |
| +	} else { /* happen in interrupts */
 | |
| +		trigger = INGENIC_SSI_MAX_FIFO_ENTRIES - ingspi->tx_trigger;
 | |
| +		if (entries <= trigger) {
 | |
| +			send_entries = entries;
 | |
| +			/* the last part of data shouldn't disable RXI_intr
 | |
| +			   at once !!! */
 | |
| +			last_flag = 1;
 | |
| +		} else {
 | |
| +			/* need enable half_intr, left entries will be sent
 | |
| +			   in SSI interrupt and receive the datas */
 | |
| +			send_entries = CPU_ONCE_BLOCK_ENTRIES;
 | |
| +			int_flag = 1;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (length > 0) {
 | |
| +		length = length/ingspi->transfer_unit_size;
 | |
| +		if (length < send_entries)
 | |
| +			send_entries = length;
 | |
| +	}
 | |
| +
 | |
| +	/* fill the txfifo with CPU Mode */
 | |
| +	retlen = cpu_write_txfifo(ingspi, send_entries);
 | |
| +	if (!retlen) {
 | |
| +		dev_info(ingspi->dev,"cpu_write_txfifo error!\n");
 | |
| +		return -1;
 | |
| +	}
 | |
| +	print_dbg("+:(%d)\n", retlen);
 | |
| +
 | |
| +	enable_tx_error_intr(ingspi);
 | |
| +	enable_rx_error_intr(ingspi);
 | |
| +
 | |
| +	/* every time should control the SSI half_intrs */
 | |
| +	if (int_flag) {
 | |
| +		enable_txfifo_half_empty_intr(ingspi);
 | |
| +		enable_rxfifo_half_full_intr(ingspi);
 | |
| +	} else {
 | |
| +		disable_txfifo_half_empty_intr(ingspi);
 | |
| +		disable_rxfifo_half_full_intr(ingspi);
 | |
| +	}
 | |
| +
 | |
| +	/* to avoid RxFIFO overflow when CPU Mode at last time to fill */
 | |
| +	if (last_flag) {
 | |
| +		last_flag = 0;
 | |
| +		enable_rxfifo_half_full_intr(ingspi);
 | |
| +	}
 | |
| +
 | |
| +#ifdef SSI_DEGUG
 | |
| +	dump_spi_reg(ingspi);
 | |
| +#endif
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_pio_txrx(struct spi_device *spi, struct spi_transfer *t)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	struct ingenic_intr_cnt *g_ingenic_intr = ingspi->g_ingenic_intr;
 | |
| +	u32 entries;
 | |
| +	int status;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	ingspi->tx = t->tx_buf;
 | |
| +	ingspi->rx = t->rx_buf;
 | |
| +	ingspi->len = t->len;
 | |
| +	ingspi->count = 0;
 | |
| +	ingspi->rlen = 0;
 | |
| +	ingspi->dma_flag &= ~SPI_DMA_ACK;
 | |
| +
 | |
| +	ingspi->rw_mode = 0;
 | |
| +	if(ingspi->tx)
 | |
| +		ingspi->rw_mode |= W_MODE;
 | |
| +	if(ingspi->rx)
 | |
| +		ingspi->rw_mode |= R_MODE;
 | |
| +
 | |
| +	disable_tx_intr(ingspi);
 | |
| +	disable_rx_intr(ingspi);
 | |
| +
 | |
| +	start_transmit(ingspi);
 | |
| +	flush_fifo(ingspi);
 | |
| +
 | |
| +	enable_receive(ingspi);
 | |
| +	clear_errors(ingspi);
 | |
| +
 | |
| +	memset(g_ingenic_intr, 0, sizeof(struct ingenic_intr_cnt));
 | |
| +	/* Calculate Max IRQ numbers for SSI error out */
 | |
| +	entries = ingspi->len * 8 / ingspi->bits_per_word;
 | |
| +	g_ingenic_intr->max_ssi_intr = (entries + INGENIC_SSI_MAX_FIFO_ENTRIES - 1) /
 | |
| +				  INGENIC_SSI_MAX_FIFO_ENTRIES * 2 + 2;
 | |
| +
 | |
| +#ifdef SSI_DEGUG
 | |
| +	dump_spi_reg(ingspi);
 | |
| +#endif
 | |
| +
 | |
| +	/* This start SSI transfer, write data or 0 to txFIFO.
 | |
| +	 * irq is locked to protect SSI config registers */
 | |
| +	spin_lock_irqsave(&ingspi->txrx_lock, flags);
 | |
| +	ingspi->is_first = 1;
 | |
| +	status = ingenic_spi_cpu_transfer(ingspi, 0);
 | |
| +	if (status < 0) {
 | |
| +		dev_err(ingspi->dev,"ERROR:spi_transfer error(%d)!\n", status);
 | |
| +		disable_tx_intr(ingspi);
 | |
| +		disable_rx_intr(ingspi);
 | |
| +		spin_unlock_irqrestore(&ingspi->txrx_lock, flags);
 | |
| +
 | |
| +		return status;
 | |
| +	}
 | |
| +	spin_unlock_irqrestore(&ingspi->txrx_lock, flags);
 | |
| +
 | |
| +	/* wait the interrupt finish the transfer( one spi_transfer be sent ) */
 | |
| +	wait_for_completion_interruptible(&ingspi->done);
 | |
| +
 | |
| +	if(t->cs_change)
 | |
| +		finish_transmit(ingspi);
 | |
| +	clear_errors(ingspi);
 | |
| +
 | |
| +	if (ingspi->rlen != t->len) {
 | |
| +		dev_info(ingspi->dev, "Length error:ingspi->rlen=%d  t->len=%d\n", ingspi->rlen,t->len);
 | |
| +
 | |
| +		if(ingspi->rlen > ingspi->len)
 | |
| +			ingspi->rlen = ingspi->len;
 | |
| +	}
 | |
| +
 | |
| +	return ingspi->rlen;
 | |
| +}
 | |
| +
 | |
| +static irqreturn_t ingenic_spi_pio_irq_callback(struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	struct ingenic_intr_cnt *g_ingenic_intr = ingspi->g_ingenic_intr;
 | |
| +	long left_count = ingspi->len - ingspi->count;
 | |
| +	u8 flag = 0;
 | |
| +	u32 cnt;
 | |
| +	int status;
 | |
| +
 | |
| +	g_ingenic_intr->ssi_intr_cnt++;
 | |
| +	/* to avoid die in interrupt if some error occur */
 | |
| +	if (g_ingenic_intr->ssi_intr_cnt > g_ingenic_intr->max_ssi_intr) {
 | |
| +		disable_tx_intr(ingspi);
 | |
| +		disable_rx_intr(ingspi);
 | |
| +		dev_err(ingspi->dev,"ssi interrupts too many count(%d)!\n",
 | |
| +			g_ingenic_intr->ssi_intr_cnt);
 | |
| +
 | |
| +		complete(&ingspi->done);
 | |
| +		goto irq_done;
 | |
| +	}
 | |
| +
 | |
| +	if ( ssi_underrun(ingspi) && tx_error_intr(ingspi) ) {
 | |
| +		print_dbg("UNDR:");
 | |
| +		g_ingenic_intr->ssi_eti++;
 | |
| +		disable_tx_error_intr(ingspi);
 | |
| +
 | |
| +		if(left_count == 0){
 | |
| +			cnt = cpu_read_rxfifo(ingspi);
 | |
| +			print_dbg("-:(%d)\n",cnt);
 | |
| +
 | |
| +			disable_tx_intr(ingspi);
 | |
| +			disable_rx_intr(ingspi);
 | |
| +
 | |
| +			complete(&ingspi->done);
 | |
| +		} else {
 | |
| +			clear_errors(ingspi);
 | |
| +			enable_tx_error_intr(ingspi);
 | |
| +		}
 | |
| +
 | |
| +		flag++;
 | |
| +	}
 | |
| +
 | |
| +	if ( ssi_overrun(ingspi) && rx_error_intr(ingspi) ) {
 | |
| +		print_dbg(" overrun:");
 | |
| +		g_ingenic_intr->ssi_eri++;
 | |
| +
 | |
| +		cnt = cpu_read_rxfifo(ingspi);
 | |
| +		print_dbg("-:(%d)\n",cnt);
 | |
| +
 | |
| +		flag++;
 | |
| +	}
 | |
| +
 | |
| +	if ( rxfifo_half_full(ingspi) &&
 | |
| +		rxfifo_half_full_intr(ingspi)) {
 | |
| +
 | |
| +		print_dbg("RXI:");
 | |
| +		g_ingenic_intr->ssi_rxi++;
 | |
| +
 | |
| +		cnt = cpu_read_rxfifo(ingspi);
 | |
| +		print_dbg("-:(%d)\n",cnt);
 | |
| +
 | |
| +		flag++;
 | |
| +	}
 | |
| +
 | |
| +	if ( txfifo_half_empty_intr(ingspi) &&
 | |
| +		txfifo_half_empty(ingspi)) {
 | |
| +
 | |
| +		print_dbg("TXI:");
 | |
| +		g_ingenic_intr->ssi_txi++;
 | |
| +
 | |
| +		status = ingenic_spi_cpu_transfer(ingspi, 0);
 | |
| +		if (status < 0) {
 | |
| +			dev_err(ingspi->dev,"ingenic_spi_cpu_transfer error!!!!!\n");
 | |
| +			disable_tx_intr(ingspi);
 | |
| +			disable_rx_intr(ingspi);
 | |
| +			complete(&ingspi->done);
 | |
| +
 | |
| +			goto irq_done;
 | |
| +		}
 | |
| +		flag++;
 | |
| +	}
 | |
| +
 | |
| +	if (!flag) {
 | |
| +		dev_info(ingspi->dev, "\nERROR:SSI interrupt Type error\n");
 | |
| +		complete(&ingspi->done);
 | |
| +	}
 | |
| +
 | |
| +irq_done:
 | |
| +	clear_errors(ingspi);
 | |
| +	return IRQ_HANDLED;
 | |
| +}
 | |
| +
 | |
| +/* every spi_transfer could call this routine to setup itself */
 | |
| +static int ingenic_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	u8  bpw, fifo_width;
 | |
| +	u32 hz;
 | |
| +	int ret;
 | |
| +
 | |
| +    /*printk("%s[%d]: \n",__func__,__LINE__);*/
 | |
| +	bpw = spi->bits_per_word;
 | |
| +	hz  = spi->max_speed_hz;
 | |
| +
 | |
| +	if (t) {
 | |
| +		if(t->bits_per_word)
 | |
| +			bpw = t->bits_per_word;
 | |
| +		if(t->speed_hz)
 | |
| +			hz = t->speed_hz;
 | |
| +	}
 | |
| +
 | |
| +	if (bpw < 2 || bpw > 32) {
 | |
| +		dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (ingspi->use_dma) {
 | |
| +		ingspi->txrx_bufs = &ingenic_spi_dma_txrx;
 | |
| +		ingspi->irq_callback = &ingenic_spi_dma_irq_callback;
 | |
| +	} else {
 | |
| +		ingspi->txrx_bufs = &ingenic_spi_pio_txrx;
 | |
| +		ingspi->irq_callback = &ingenic_spi_pio_irq_callback;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->bits_per_word = bpw;
 | |
| +	if (bpw <= 8) {
 | |
| +		ingspi->transfer_unit_size = SPI_8BITS;
 | |
| +		ingspi->get_rx = ingenic_spi_rx_buf_u8;
 | |
| +		ingspi->get_tx = ingenic_spi_tx_buf_u8;
 | |
| +		fifo_width = FIFO_W8;
 | |
| +	} else if (bpw <= 16) {
 | |
| +		ingspi->transfer_unit_size = SPI_16BITS;
 | |
| +		ingspi->get_rx = ingenic_spi_rx_buf_u16;
 | |
| +		ingspi->get_tx = ingenic_spi_tx_buf_u16;
 | |
| +		fifo_width = FIFO_W16;
 | |
| +	} else {
 | |
| +		ingspi->transfer_unit_size = SPI_32BITS;
 | |
| +		ingspi->get_rx = ingenic_spi_rx_buf_u32;
 | |
| +		ingspi->get_tx = ingenic_spi_tx_buf_u32;
 | |
| +		fifo_width = FIFO_W32;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->txfifo_width = fifo_width;
 | |
| +	ingspi->rxfifo_width = fifo_width;
 | |
| +	set_frame_length(ingspi, fifo_width);
 | |
| +
 | |
| +	if (spi->mode & SPI_LSB_FIRST) {
 | |
| +		set_tx_lsb(ingspi);
 | |
| +		set_rx_lsb(ingspi);
 | |
| +	} else {
 | |
| +		set_tx_msb(ingspi);
 | |
| +		set_rx_msb(ingspi);
 | |
| +	}
 | |
| +
 | |
| +	if((ret = ingenic_spi_clk_set_rate(spi, hz)))
 | |
| +		return ret;
 | |
| +
 | |
| +	dev_dbg(&spi->dev, "The real SPI CLK is %ld Hz\n", ingenic_spi_clk_get_rate(spi));
 | |
| +
 | |
| +	mutex_lock(&ingspi->bitbang.lock);
 | |
| +	if (!ingspi->bitbang.busy) {
 | |
| +		ingspi->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
 | |
| +		/* need to ndelay for 0.5 clocktick ? */
 | |
| +	}
 | |
| +	mutex_unlock(&ingspi->bitbang.lock);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_setup(struct spi_device *spi)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	unsigned long flags;
 | |
| +	unsigned int frmhl = 0;
 | |
| +
 | |
| +	spin_lock_irqsave(&ingspi->lock, flags);
 | |
| +	if (ingspi->state & SUSPND) {
 | |
| +		spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +		dev_err(&spi->dev,
 | |
| +			"setup: SPI-%d not active!\n", spi->master->bus_num);
 | |
| +		return -ESHUTDOWN;
 | |
| +	}
 | |
| +	spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +
 | |
| +	if (spi->chip_select >= spi->master->num_chipselect) {
 | |
| +		dev_err(&spi->dev, "cs%d >= max %d\n",
 | |
| +			spi->chip_select,
 | |
| +			spi->master->num_chipselect);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (spi->chip_select == 0) {
 | |
| +		select_ce(ingspi);
 | |
| +		frmhl = spi_readl(ingspi, SSI_CR1);
 | |
| +		frmhl &= ~(1<<30);
 | |
| +		frmhl |= (spi->mode & SPI_CS_HIGH ? 1 : 0) << 30;
 | |
| +		spi_writel(ingspi, SSI_CR1, frmhl);
 | |
| +	} else if (spi->chip_select == 1) {
 | |
| +		select_ce2(ingspi);
 | |
| +		frmhl = spi_readl(ingspi, SSI_CR1);
 | |
| +		frmhl &= ~(1<<31);
 | |
| +		frmhl |= (spi->mode & SPI_CS_HIGH ? 1 : 0) << 31;
 | |
| +		spi_writel(ingspi, SSI_CR1, frmhl);
 | |
| +	} else
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	if (!spi->bits_per_word)
 | |
| +		spi->bits_per_word = 8;
 | |
| +
 | |
| +	if (spi->mode & ~MODEBITS) {
 | |
| +		dev_info(&spi->dev, "Warning: unsupported mode bits %x\n",
 | |
| +			spi->mode & ~MODEBITS);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +	ingspi->spi_mode = spi->mode;
 | |
| +
 | |
| +	if (spi->mode & SPI_LSB_FIRST) {
 | |
| +		set_tx_lsb(ingspi);
 | |
| +		set_rx_lsb(ingspi);
 | |
| +	} else {
 | |
| +		set_tx_msb(ingspi);
 | |
| +		set_rx_msb(ingspi);
 | |
| +	}
 | |
| +
 | |
| +	if (spi->bits_per_word & ~SPI_BITS_SUPPORT) {
 | |
| +		dev_info(&spi->dev, "Warning: unsupported bits_per_word: %d\n",
 | |
| +			spi->bits_per_word);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (!spi->max_speed_hz) {
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +    /*printk("%s[%d]: ingspi->max_clk = %ld, spi->max_speed_hz = %ld\n",__func__,__LINE__,ingspi->max_clk, spi->max_speed_hz);*/
 | |
| +	if (ingspi->max_clk < spi->max_speed_hz) {
 | |
| +		dev_info(&spi->dev, "Warning:invalid clock(%d Hz) be set to source clk(%d Hz)!\n",
 | |
| +				 spi->max_speed_hz,(uint)ingspi->max_clk);
 | |
| +		spi->max_speed_hz = ingspi->max_clk;
 | |
| +	}
 | |
| +
 | |
| +	mutex_lock(&ingspi->bitbang.lock);
 | |
| +	if (!ingspi->bitbang.busy) {
 | |
| +		ingspi->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
 | |
| +		/* need to ndelay for 0.5 clocktick ? */
 | |
| +	}
 | |
| +	mutex_unlock(&ingspi->bitbang.lock);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * ingenic_spi_txrx - functions which will handle transfer data
 | |
| + * @spi: spi device on which data transfer to be done
 | |
| + * @t: spi transfer in which transfer info is filled
 | |
| + *
 | |
| + * This function will put data to be transferred into data register
 | |
| + * of SPI controller and then wait until the completion will be marked
 | |
| + * by the IRQ Handler.
 | |
| + */
 | |
| +static int ingenic_spi_txrx(struct spi_device * spi, struct spi_transfer *t)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = spi_master_get_devdata(spi->master);
 | |
| +	unsigned int ret;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&ingspi->lock, flags);
 | |
| +	if (ingspi->state & SUSPND) {
 | |
| +		ingspi->state &= ~SPIBUSY;
 | |
| +		spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +		printk("Now enter suspend, so cann't tranfer data\n");
 | |
| +		return -ESHUTDOWN;
 | |
| +	}
 | |
| +	ingspi->state |= SPIBUSY;
 | |
| +	spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +
 | |
| +	ret = ingspi->txrx_bufs(spi, t);
 | |
| +
 | |
| +	spin_lock_irqsave(&ingspi->lock, flags);
 | |
| +	ingspi->state &= ~SPIBUSY;
 | |
| +	spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static irqreturn_t ingenic_spi_irq(int irq, void *dev)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = dev;
 | |
| +
 | |
| +	return ingspi->irq_callback(ingspi);
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_init_setup(struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	ingspi->clk_flag = 1;
 | |
| +	ingenic_spi_clk_enable(ingspi);
 | |
| +
 | |
| +	/* disable the SSI controller */
 | |
| +	ssi_disable(ingspi);
 | |
| +
 | |
| +	/* set default half_intr trigger */
 | |
| +	ingspi->tx_trigger = SSI_TX_FIFO_THRESHOLD * 8;
 | |
| +	ingspi->rx_trigger = SSI_RX_FIFO_THRESHOLD * 8;
 | |
| +	set_tx_trigger(ingspi, ingspi->tx_trigger);
 | |
| +	set_rx_trigger(ingspi, ingspi->rx_trigger);
 | |
| +
 | |
| +	/* First,mask the interrupt, while verify the status ? */
 | |
| +	disable_tx_intr(ingspi);
 | |
| +	disable_rx_intr(ingspi);
 | |
| +
 | |
| +	disable_receive(ingspi);
 | |
| +
 | |
| +	set_spi_clock_phase(ingspi, 0);
 | |
| +	set_spi_clock_polarity(ingspi, 0);
 | |
| +	set_tx_msb(ingspi);
 | |
| +	set_rx_msb(ingspi);
 | |
| +
 | |
| +	set_spi_format(ingspi);
 | |
| +	set_frame_length(ingspi, 8);
 | |
| +	disable_loopback(ingspi);
 | |
| +	flush_fifo(ingspi);
 | |
| +
 | |
| +	underrun_auto_clear(ingspi);
 | |
| +	clear_errors(ingspi);
 | |
| +	ssi_enable(ingspi);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +#ifdef CONFIG_OF
 | |
| +static struct ingenic_spi_info *ingenic_spi_parse_dt(struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	struct ingenic_spi_info *isi;
 | |
| +	struct device *dev = ingspi->dev;
 | |
| +	unsigned int value;
 | |
| +	int i;
 | |
| +	isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL);
 | |
| +	if (!isi)
 | |
| +		return ERR_PTR(-ENOMEM);
 | |
| +
 | |
| +	if(of_property_read_u32(dev->of_node, "spi-max-frequency", &value)) {
 | |
| +		dev_warn(dev, "spi-max-frequency not specified\n");
 | |
| +		isi->max_clk = 0;
 | |
| +	} else {
 | |
| +		isi->max_clk = value;
 | |
| +	}
 | |
| +
 | |
| +    /*printk("%s[%d]: isi->max_clk = %ld\n",__func__,__LINE__, isi->max_clk);*/
 | |
| +	if(of_property_read_u32(dev->of_node, "ingenic,has_dma_support", &value)) {
 | |
| +		dev_warn(dev, "spi-max-frequency not specified\n");
 | |
| +		ingspi->use_dma = 0;
 | |
| +	} else {
 | |
| +		ingspi->use_dma = value;
 | |
| +	}
 | |
| +
 | |
| +	if (of_property_read_u32(dev->of_node, "ingenic,chnl", &value)) {
 | |
| +		dev_warn(dev, "ingenic,channel not specified\n");
 | |
| +		isi->chnl = 0;
 | |
| +	} else {
 | |
| +		isi->chnl = value;
 | |
| +	}
 | |
| +
 | |
| +    /*printk("%s[%d]: isi->chnl = %d\n",__func__,__LINE__, isi->chnl);*/
 | |
| +	if (of_property_read_u32(dev->of_node, "num-cs", &value)) {
 | |
| +		dev_warn(dev, "num_cs not specified\n");
 | |
| +		isi->num_chipselect = 0;
 | |
| +	} else {
 | |
| +		isi->num_chipselect = value;
 | |
| +	}
 | |
| +
 | |
| +    /*printk("%s[%d]: isi->num_chipselect = %d\n",__func__,__LINE__, isi->num_chipselect);*/
 | |
| +	if (of_property_read_u32(dev->of_node, "ingenic,allow_cs_same", &value)) {
 | |
| +		dev_warn(dev, "ingenic,allow_cs_same not specified\n");
 | |
| +		isi->allow_cs_same = 0;
 | |
| +	} else {
 | |
| +		isi->allow_cs_same = value;
 | |
| +	}
 | |
| +
 | |
| +    /*printk("%s[%d]: isi->allow_cs_same = %d\n",__func__,__LINE__, isi->allow_cs_same);*/
 | |
| +	if (of_property_read_u32(dev->of_node, "ingenic,bus_num", &value)) {
 | |
| +		dev_warn(dev, "ingenic,bus_num not specified\n");
 | |
| +		isi->bus_num = 0;
 | |
| +	} else {
 | |
| +		isi->bus_num = value;
 | |
| +	}
 | |
| +
 | |
| +    /*printk("%s[%d]: isi->bus_num = %d\n",__func__,__LINE__, isi->bus_num);*/
 | |
| +	for (i = 0; i < isi->num_chipselect; i++) {
 | |
| +		int cs_gpio = of_get_named_gpio(dev->of_node, "cs-gpios", i);
 | |
| +		if (cs_gpio == -EPROBE_DEFER) {
 | |
| +			break;
 | |
| +		}
 | |
| +		isi->chipselects[i] = cs_gpio;
 | |
| +		/*printk("%s[%d]: cs%d is gpio = %d\n",__func__,__LINE__, i ,cs_gpio);*/
 | |
| +		if (gpio_is_valid(cs_gpio)) {
 | |
| +			if (devm_gpio_request(dev, cs_gpio, "INGENIC_SPI_CS")) {
 | |
| +				if(!isi->allow_cs_same)
 | |
| +					dev_err(dev, "could not request %d gpio\n", cs_gpio);
 | |
| +			} else if (gpio_direction_output(cs_gpio, 1))
 | |
| +				dev_err(dev, "could not set gpio %d as output\n", cs_gpio);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return isi;
 | |
| +}
 | |
| +#else
 | |
| +static struct ingenic_spi_info *ingenic_spi_parse_dt(struct device *dev)
 | |
| +{
 | |
| +	return dev_get_platdata(dev);
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +static int ingenic_spi_clk_init(struct platform_device *pdev, struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	struct clk *clk;
 | |
| +	char clkname[16];
 | |
| +	int err = 0;
 | |
| +
 | |
| +	pdev->id = of_alias_get_id(pdev->dev.of_node, "spi");
 | |
| +	sprintf(clkname, "gate_ssi%d", pdev->id);
 | |
| +	ingspi->clk_gate = devm_clk_get(&pdev->dev, clkname);
 | |
| +	ingspi->clk_cgu = devm_clk_get(&pdev->dev, "div_ssi");
 | |
| +
 | |
| +	if (IS_ERR(ingspi->clk_cgu) || IS_ERR(ingspi->clk_gate)) {
 | |
| +		dev_err(&pdev->dev, "Cannot get spi clock\n");
 | |
| +		err = PTR_ERR(ingspi->clk_cgu);
 | |
| +		return err;
 | |
| +	}
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_configure_dma(struct ingenic_spi *ingspi)
 | |
| +{
 | |
| +	struct device *dev = ingspi->dev;
 | |
| +	dma_cap_mask_t mask;
 | |
| +	int err = 0;
 | |
| +
 | |
| +	dma_cap_zero(mask);
 | |
| +	dma_cap_set(DMA_SLAVE, mask);
 | |
| +
 | |
| +	ingspi->txchan = dma_request_slave_channel_reason(dev, "tx");
 | |
| +	if (IS_ERR(ingspi->txchan)) {
 | |
| +		err = PTR_ERR(ingspi->txchan);
 | |
| +		if (err == -EPROBE_DEFER) {
 | |
| +			dev_warn(dev, "no DMA channel available at the moment\n");
 | |
| +			return err;
 | |
| +		}
 | |
| +		dev_err(dev, "DMA TX channel not available, SPI unable to use DMA\n");
 | |
| +		err = -EBUSY;
 | |
| +		goto error;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * No reason to check EPROBE_DEFER here since we have already requested
 | |
| +	 * tx channel. If it fails here, it's for another reason.
 | |
| +	 */
 | |
| +	ingspi->rxchan = dma_request_slave_channel(dev, "rx");
 | |
| +
 | |
| +	if (!ingspi->rxchan) {
 | |
| +		dev_err(dev, "DMA RX channel not available, SPI unable to use DMA\n");
 | |
| +		err = -EBUSY;
 | |
| +		goto error;
 | |
| +	}
 | |
| +
 | |
| +	//alloc temp buffer for dma
 | |
| +	ingspi->buffer = dma_alloc_coherent(dev, BUFFER_SIZE,
 | |
| +										&ingspi->buffer_dma, GFP_KERNEL);
 | |
| +	if (!ingspi->buffer) {
 | |
| +		dev_err(dev, "SPI request temp dma buffer failed");
 | |
| +		goto error;
 | |
| +	}
 | |
| +
 | |
| +#if 0
 | |
| +	ingspi->buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
 | |
| +	if (!ingspi->buffer) {
 | |
| +		dev_err(dev, "SPI request temp dma buffer failed");
 | |
| +		goto error;
 | |
| +	}
 | |
| +	print_dbg("<< ingspi->buffer addr:%p >>\n", ingspi->buffer);
 | |
| +#endif
 | |
| +
 | |
| +	ingspi->sg_tx = devm_kmalloc(dev, sizeof(struct scatterlist), GFP_KERNEL);
 | |
| +	if (!ingspi->sg_tx) {
 | |
| +		dev_err(dev, "Failed to alloc tx scatterlist\n");
 | |
| +		goto error;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->sg_rx = devm_kmalloc(dev, sizeof(struct scatterlist), GFP_KERNEL);
 | |
| +	if(!ingspi->sg_rx) {
 | |
| +		dev_err(dev, "Failed to alloc rx scatterlist\n");
 | |
| +		goto error;
 | |
| +	}
 | |
| +
 | |
| +
 | |
| +	dev_info(dev, "Using %s (tx) and %s (rx) for DMA transfers\n",
 | |
| +			 dma_chan_name(ingspi->txchan),
 | |
| +			 dma_chan_name(ingspi->rxchan));
 | |
| +	return 0;
 | |
| +error:
 | |
| +	if (ingspi->rxchan)
 | |
| +		dma_release_channel(ingspi->rxchan);
 | |
| +	if (!IS_ERR(ingspi->txchan))
 | |
| +		dma_release_channel(ingspi->txchan);
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi;
 | |
| +	struct spi_master *master;
 | |
| +	struct device_node *np = pdev->dev.of_node;
 | |
| +	struct ingenic_spi_info *pdata = dev_get_platdata(&pdev->dev);
 | |
| +	struct resource	*res;
 | |
| +	int err = 0;
 | |
| +
 | |
| +	master = spi_alloc_master(&pdev->dev, sizeof(struct ingenic_spi));
 | |
| +	if (!master) {
 | |
| +		dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
 | |
| +		return -ENOMEM;
 | |
| +	}
 | |
| +
 | |
| +	/* the spi->mode bits understood by this drivers: */
 | |
| +	master->mode_bits = MODEBITS;
 | |
| +
 | |
| +	ingspi = spi_master_get_devdata(master);
 | |
| +	ingspi->g_ingenic_intr = devm_kzalloc(&pdev->dev,
 | |
| +										  sizeof(struct ingenic_intr_cnt),GFP_KERNEL);
 | |
| +	if(!ingspi->g_ingenic_intr) {
 | |
| +		dev_err(&pdev->dev, "No memory for ingenic_intr_cnt\n");
 | |
| +		return -ENOMEM;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->master = spi_master_get(master);
 | |
| +	ingspi->dev = &pdev->dev;
 | |
| +
 | |
| +	if (!pdata && np) {
 | |
| +		pdata = ingenic_spi_parse_dt(ingspi);
 | |
| +		if (IS_ERR(pdata))
 | |
| +			return PTR_ERR(pdata);
 | |
| +	}
 | |
| +
 | |
| +	if (!pdata) {
 | |
| +		dev_err(&pdev->dev, "platform_data missing!\n");
 | |
| +		return -ENODEV;
 | |
| +	}
 | |
| +
 | |
| +
 | |
| +	ingspi->pdata = pdata;
 | |
| +	ingspi->chnl= ingspi->pdata->chnl;
 | |
| +	master->bus_num = (s16)ingspi->pdata->bus_num;
 | |
| +	if(master->bus_num != 0 && master->bus_num != 1){
 | |
| +		dev_err(&pdev->dev, "No this channel, bus_num= %d.\n", master->bus_num);
 | |
| +		err = -ENOENT;
 | |
| +		goto err_no_pdata;
 | |
| +	}
 | |
| +
 | |
| +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | |
| +	if (!res) {
 | |
| +		dev_err(&pdev->dev, "Unable to get SPI MEM resource\n");
 | |
| +		return -ENXIO;
 | |
| +	}
 | |
| +	ingspi->phys = res->start;
 | |
| +	ingspi->iomem = devm_ioremap_resource(&pdev->dev, res);
 | |
| +	if (!ingspi->iomem) {
 | |
| +		dev_err(&pdev->dev, "Cannot map IO\n");
 | |
| +		err = -ENXIO;
 | |
| +		goto err_no_iomap;
 | |
| +	}
 | |
| +
 | |
| +	ingspi->irq = platform_get_irq(pdev, 0);
 | |
| +	if (ingspi->irq <= 0) {
 | |
| +		dev_err(&pdev->dev, "No IRQ specified\n");
 | |
| +		err = -ENOENT;
 | |
| +		goto err_no_irq;
 | |
| +	}
 | |
| +
 | |
| +	ingenic_spi_clk_init(pdev, ingspi);
 | |
| +
 | |
| +	ingspi->spi_clk = ingspi->pdata->src_clk;
 | |
| +	ingspi->max_clk = ingspi->pdata->max_clk;
 | |
| +
 | |
| +	platform_set_drvdata(pdev, ingspi);
 | |
| +	init_completion(&ingspi->done);
 | |
| +	init_completion(&ingspi->done_rx);
 | |
| +	init_completion(&ingspi->done_tx_dma);
 | |
| +	init_completion(&ingspi->done_rx_dma);
 | |
| +	spin_lock_init(&ingspi->lock);
 | |
| +	spin_lock_init(&ingspi->txrx_lock);
 | |
| +
 | |
| +	master->bus_num = ingspi->pdata->bus_num;
 | |
| +	master->num_chipselect = ingspi->pdata->num_chipselect;
 | |
| +	master->dev.of_node = pdev->dev.of_node;
 | |
| +	/* setup the state for the bitbang driver */
 | |
| +	ingspi->bitbang.master         = ingspi->master;
 | |
| +	ingspi->bitbang.setup_transfer = ingenic_spi_setupxfer;
 | |
| +	ingspi->bitbang.chipselect     = ingenic_spi_chipsel;
 | |
| +	ingspi->bitbang.txrx_bufs      = ingenic_spi_txrx;
 | |
| +	ingspi->bitbang.master->setup  = ingenic_spi_setup;
 | |
| +	ingspi->fifodepth = INGENIC_SSI_MAX_FIFO_ENTRIES;
 | |
| +	ingspi->set_cs = &ingenic_spi_cs;
 | |
| +
 | |
| +	ingenic_spi_init_setup(ingspi);
 | |
| +
 | |
| +	if (ingspi->use_dma) {
 | |
| +		ingenic_spi_configure_dma(ingspi);
 | |
| +	}
 | |
| +
 | |
| +	/* request SSI irq */
 | |
| +	err = devm_request_irq(&pdev->dev, ingspi->irq, ingenic_spi_irq, 0, pdev->name, ingspi);
 | |
| +	if (err) {
 | |
| +		dev_err(&pdev->dev, "Cannot claim IRQ\n");
 | |
| +		goto err_register;
 | |
| +	}
 | |
| +
 | |
| +	dev_dbg(ingspi->dev, "bitbang at %p\n", &ingspi->bitbang);
 | |
| +
 | |
| +	err = spi_bitbang_start(&ingspi->bitbang);
 | |
| +	if (err) {
 | |
| +		dev_err(&pdev->dev, "Failed to register SPI master ERR_NO:%d\n",err);
 | |
| +		goto err_register;
 | |
| +	}
 | |
| +
 | |
| +	printk(KERN_INFO "INGENIC SSI Controller for SPI channel %d driver register\n",ingspi->chnl);
 | |
| +	return 0;
 | |
| +
 | |
| +err_register:
 | |
| +	free_irq(ingspi->irq, ingspi);
 | |
| +err_no_irq:
 | |
| +	if(ingspi->clk_gate)
 | |
| +		clk_put(ingspi->clk_gate);
 | |
| +	if(ingspi->clk_cgu)
 | |
| +		clk_put(ingspi->clk_cgu);
 | |
| +	iounmap(ingspi->iomem);
 | |
| +err_no_iomap:
 | |
| +	release_resource(ingspi->ioarea);
 | |
| +	kfree(ingspi->ioarea);
 | |
| +#ifdef CONFIG_INGENIC_SPI_PIO_CE
 | |
| +err_cs_gpio:
 | |
| +	for (i = 0; i < num_cs_got; i++)
 | |
| +		gpio_free(ingspi->pdata->chipselect[i]);
 | |
| +#endif
 | |
| +err_no_pdata:
 | |
| +	spi_master_put(ingspi->master);
 | |
| +
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_remove(struct platform_device *dev)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = platform_get_drvdata(dev);
 | |
| +
 | |
| +	spi_master_put(ingspi->master);
 | |
| +	spi_bitbang_stop(&ingspi->bitbang);
 | |
| +
 | |
| +	platform_set_drvdata(dev, NULL);
 | |
| +
 | |
| +	free_irq(ingspi->irq, ingspi);
 | |
| +	iounmap(ingspi->iomem);
 | |
| +
 | |
| +	ingenic_spi_clk_disable(ingspi);
 | |
| +	clk_put(ingspi->clk_gate);
 | |
| +	clk_put(ingspi->clk_cgu);
 | |
| +
 | |
| +	release_resource(ingspi->ioarea);
 | |
| +	kfree(ingspi->ioarea);
 | |
| +
 | |
| +	/* release DMA channel */
 | |
| +	if (ingspi->rxchan) {
 | |
| +		dma_release_channel(ingspi->rxchan);
 | |
| +	}
 | |
| +	if (ingspi->txchan) {
 | |
| +		dma_release_channel(ingspi->txchan);
 | |
| +	}
 | |
| +
 | |
| +#ifdef CONFIG_INGENIC_SPI_PIO_CE
 | |
| +	/* release chipselect gpio */
 | |
| +	{
 | |
| +		int i;
 | |
| +		for (i = 0; i < ingspi->pdata->num_chipselect; i++)
 | |
| +			gpio_free(ingspi->pdata->chipselect[i]);
 | |
| +	}
 | |
| +#endif
 | |
| +
 | |
| +	kfree(ingspi->g_ingenic_intr);
 | |
| +	kfree(ingspi);
 | |
| +	printk(KERN_INFO "INGENIC SSI Controller for SPI channel %d driver removed\n",ingspi->chnl);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +#ifdef CONFIG_PM
 | |
| +static int ingenic_spi_suspend(struct platform_device *pdev, pm_message_t msg)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = platform_get_drvdata(pdev);
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&ingspi->lock, flags);
 | |
| +	ingspi->state |= SUSPND;
 | |
| +	spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +
 | |
| +	while (ingspi->state & SPIBUSY)
 | |
| +		printk("Now spi is busy, waitting!\n");
 | |
| +
 | |
| +	ingenic_spi_clk_disable(ingspi);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int ingenic_spi_resume(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct ingenic_spi *ingspi = platform_get_drvdata(pdev);
 | |
| +	unsigned long	flags;
 | |
| +
 | |
| +	ingenic_spi_clk_enable(ingspi);
 | |
| +
 | |
| +	spin_lock_irqsave(&ingspi->lock, flags);
 | |
| +	ingspi->state &= ~SUSPND;
 | |
| +	spin_unlock_irqrestore(&ingspi->lock, flags);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +#else
 | |
| +#define ingenic_spi_suspend NULL
 | |
| +#define ingenic_spi_resume  NULL
 | |
| +#endif
 | |
| +
 | |
| +static const struct of_device_id ingenic_spi_match[] = {
 | |
| +	{ .compatible = "ingenic,spi", },
 | |
| +	{}
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(of, ingenic_spi_match);
 | |
| +
 | |
| +static struct platform_driver ingenic_spidrv = {
 | |
| +	.probe	    = ingenic_spi_probe,
 | |
| +	.remove		= ingenic_spi_remove,
 | |
| +	.suspend	= ingenic_spi_suspend,
 | |
| +	.resume		= ingenic_spi_resume,
 | |
| +	.driver		= {
 | |
| +		.name	= "ingenic-spi",
 | |
| +		.of_match_table	= ingenic_spi_match,
 | |
| +		.owner	= THIS_MODULE,
 | |
| +	},
 | |
| +};
 | |
| +
 | |
| +module_platform_driver(ingenic_spidrv);
 | |
| +
 | |
| +MODULE_ALIAS("ingenic_spi");
 | |
| +MODULE_AUTHOR("Bo Liu <bo.liu@ingenic.com>");
 | |
| +MODULE_DESCRIPTION("INGENIC SPI controller driver");
 | |
| +MODULE_LICENSE("GPL");
 |