From: wurui <wu...@actions-semi.com> commit b09920d4a432c988ca637152ff14f3351eff8da1 from https://github.com/xapp-le/kernel.git
Change-Id: Id537b373b7a89a7093a18490e5c19b18a8a96ae3 --- drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi_owl.c | 1562 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1569 insertions(+) mode change 100644 => 100755 drivers/spi/Kconfig mode change 100644 => 100755 drivers/spi/Makefile create mode 100755 drivers/spi/spi_owl.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig old mode 100644 new mode 100755 index c0fe58d..bba9b56 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -650,6 +650,12 @@ config SPI_TLE62X0 sysfs interface, with each line presented as a kind of GPIO exposing both switch control and diagnostic feedback. +config SPI_OWL + tristate "Actions SOC SPI controller" + depends on ARCH_OWL + help + SPI driver for Actions SOC + # # Add new SPI protocol masters in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile old mode 100644 new mode 100755 index 62edaca..697163d --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -87,3 +87,4 @@ obj-$(CONFIG_SPI_TXX9) += spi-txx9.o obj-$(CONFIG_SPI_XCOMM) += spi-xcomm.o obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o obj-$(CONFIG_SPI_XTENSA_XTFPGA) += spi-xtensa-xtfpga.o +obj-$(CONFIG_SPI_OWL) += spi_owl.o diff --git a/drivers/spi/spi_owl.c b/drivers/spi/spi_owl.c new file mode 100755 index 0000000..e88a72b --- /dev/null +++ b/drivers/spi/spi_owl.c @@ -0,0 +1,1562 @@ +/* + * asoc_spi.c -- Actions soc SPI controller driver + * + * + * 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/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/spi/spi.h> +#include <mach/hardware.h> +#include <mach/hdmac-owl.h> +#include <mach/spi-owl.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> +#include <mach/module-owl.h> + +#define DRIVER_NAME "asoc_spi" + +/**********************************************************/ + +#define SPI_CTL 0x0 +#define SPI_CLKDIV 0x4 +#define SPI_STAT 0x8 +#define SPI_RXDAT 0xc +#define SPI_TXDAT 0x10 +#define SPI_TCNT 0x14 +#define SPI_SEED 0x18 +#define SPI_TXCR 0x1c +#define SPI_RXCR 0x20 + +#define MAX_SPI_POLL_LOOPS 5000 +#define MAX_SPI_DMA_LEN 8192 +#define BYTES_4_DMA_XFER 64 + +struct asoc_spi { +/* struct spi_master *master;*/ +/* unsigned int irq; */ +/* struct owl_gpio *owl_spi_cs_table; */ + struct device *dev; + + unsigned int irq; + spinlock_t lock; + struct clk *clk; + + u32 base; /*phy addr*/ + + struct workqueue_struct *workqueue; + struct work_struct work; + + /* Lock access to transfer list. */ + struct list_head msg_queue; + + u8 enable_dma; + +#ifdef CONFIG_DMA_ENGINE + struct dma_chan *dma_rx_channel; + struct dma_chan *dma_tx_channel; + struct sg_table sgt_rx; + struct sg_table sgt_tx; + bool dma_running; +#endif + +}; + +static inline void dump_spi_registers(struct asoc_spi *asoc_spi) +{ + dev_dbg(asoc_spi->dev, "asoc_spi: SPI0_CTL(0x%x) = 0x%x\n", asoc_spi->base + SPI_CTL, + act_readl(asoc_spi->base + SPI_CTL)); + dev_dbg(asoc_spi->dev, "asoc_spi: SPI0_STAT(0x%x) = 0x%x\n", asoc_spi->base + SPI_STAT, + act_readl(asoc_spi->base + SPI_STAT)); + dev_dbg(asoc_spi->dev, "asoc_spi: SPI0_CLKDIV(0x%x) = 0x%x\n", asoc_spi->base + SPI_CLKDIV, + act_readl(asoc_spi->base + SPI_CLKDIV)); +} + +static inline u32 spi_reg(struct asoc_spi *asoc_spi, u32 reg) +{ + return asoc_spi->base + reg; +} + + +static inline void spi_clear_stat(struct asoc_spi *asoc_spi) +{ + + act_writel(SPIx_STAT_TFER /* clear the rx FIFO */ + | SPIx_STAT_RFER /* clear the tx FIFO */ + | SPIx_STAT_BEB /* clear the Bus error bit */ + | SPIx_STAT_TCOM /* clear the transfer complete bit */ + | SPIx_STAT_TIP /* clear the tx IRQ pending bit */ + | SPIx_STAT_PIP, /* clear the rx IRQ pending bit */ + spi_reg(asoc_spi, SPI_STAT) + ); +} + +static int asoc_spi_mode_set(struct spi_device *spi) +{ + u32 ctl_reg; + struct asoc_spi *asoc_spi; + u32 tmp; + u8 mode = spi->mode; + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + + tmp = act_readl(ctl_reg); + + tmp |= SPIx_CTL_EN; + + if (mode & SPI_CPHA) + tmp |= (0x1 << 6); + else + tmp &= (~(0x1 << 6)); + + if (mode & SPI_CPOL) + tmp |= (0x1 << 7); + else + tmp &= (~(0x1 << 7)); + + if (mode & SPI_LSB_FIRST) + tmp |= SPIx_CTL_LMFS; + else + tmp &= (~SPIx_CTL_LMFS); + + if (mode & SPI_LOOP) + tmp |= SPIx_CTL_LBT; + else + tmp &= (~SPIx_CTL_LBT); + + act_writel(tmp, ctl_reg); + + return 0; +} + +static int asoc_spi_set_bus_param(struct spi_device *spi, + int bits_per_word, u8 convert_endian) +{ + u32 ctl_reg; + struct asoc_spi *asoc_spi; + u32 tmp; + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + + tmp = act_readl(ctl_reg); + tmp &= (~SPIx_CTL_DAWS(3)); + + if (bits_per_word == 8) { + tmp |= SPIx_CTL_DAWS(0); + } else if (bits_per_word == 16) { + tmp |= SPIx_CTL_DAWS(1); + } else if (bits_per_word == 32) { + tmp |= SPIx_CTL_DAWS(2); + } else { + pr_debug("Bad bits per word value %d (8, 16 or 32 are " + "allowed).\n", bits_per_word); + return -EINVAL; + } + + if (convert_endian) + tmp |= SPIx_CTL_CEB; + else + tmp &= (~SPIx_CTL_CEB); + + spi->bits_per_word = bits_per_word; + act_writel(tmp, ctl_reg); + + return 0; +} + +static int asoc_spi_baudrate_set(struct spi_device *spi, unsigned int speed) +{ + u32 spi_source_clk_hz; + u32 clk_div; + u32 clkdiv_reg; + struct asoc_spi *asoc_spi; + + asoc_spi = spi_master_get_devdata(spi->master); + + clkdiv_reg = spi_reg(asoc_spi, SPI_CLKDIV); + + spi_source_clk_hz = clk_get_rate(asoc_spi->clk); + + /* setup SPI clock register */ + /* SPICLK = HCLK/(CLKDIV*2) */ + clk_div = (spi_source_clk_hz + (2 * speed) - 1) / (speed) / 2; + if (clk_div == 0) + clk_div = 1; + + dev_dbg(&spi->dev, "asoc_spi: required speed = %d\n", speed); + dev_dbg(&spi->dev, "asoc_spi:spi clock = %d KHz(hclk = %d,clk_div = %d)\n", + spi_source_clk_hz / (clk_div * 2) / 1000, spi_source_clk_hz, clk_div); + + act_writel(SPIx_CLKDIV_CLKDIV(clk_div), clkdiv_reg); + + return 0; +} + + +static void asoc_spi_activate_cs(struct spi_device *spi) +{ + struct asoc_spi *asoc_spi; + u32 ctl_reg; + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + + act_clearl(SPIx_CTL_SSCO, ctl_reg); + +} + +static void asoc_spi_deactivate_cs(struct spi_device *spi) +{ + struct asoc_spi *asoc_spi; + u32 ctl_reg; + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + + act_setl(SPIx_CTL_SSCO, ctl_reg); +} + +int asoc_spi_wait_till_ready(struct asoc_spi *asoc_spi) +{ + int i; + u32 stat_reg = spi_reg(asoc_spi, SPI_STAT); + + for (i = 0; i < MAX_SPI_POLL_LOOPS; i++) { + if (act_readl(stat_reg) & SPIx_STAT_TCOM) { + act_writel(act_readl(stat_reg) | SPIx_STAT_TCOM, stat_reg); + dev_dbg(asoc_spi->dev, "wait num = %d\n", i); + return 1; + } + } + + dump_spi_registers(asoc_spi); + + return -1; +} + + +static void spi_callback(void *completion) +{ + complete(completion); +} + + +static inline int +asoc_spi_write_read_8bit(struct spi_device *spi, + struct spi_transfer *xfer) +{ + u32 tx_reg, rx_reg, ctl_reg; + struct asoc_spi *asoc_spi; + u32 tmp; + const u8 *tx_buf = xfer->tx_buf; + u8 *rx_buf = xfer->rx_buf; + unsigned int count = xfer->len; + + dev_dbg(&spi->dev, "%s(len:0x%x)\n", __func__, xfer->len); + if (rx_buf != NULL) { + dev_dbg(&spi->dev, " rx_buf:0x%x)\n", ((const u8 *)(xfer->rx_buf))[0]); + } + if (tx_buf != NULL) { + dev_dbg(&spi->dev, " tx_buf:0x%x)\n", ((const u8 *)(xfer->tx_buf))[0]); + } + + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + tx_reg = spi_reg(asoc_spi, SPI_TXDAT); + rx_reg = spi_reg(asoc_spi, SPI_RXDAT); + + tmp = act_readl(ctl_reg); + tmp &= (~(SPIx_CTL_RWC(3) | + SPIx_CTL_RDIC(3) | + SPIx_CTL_TDIC(3) | + SPIx_CTL_SDT(7) | + SPIx_CTL_DTS | + SPIx_CTL_TIEN | + SPIx_CTL_RIEN | + SPIx_CTL_TDEN | + SPIx_CTL_RDEN)); + tmp |= SPIx_CTL_RWC(0); + act_writel(tmp, ctl_reg); + + do { + if (tx_buf) + act_writel(*tx_buf++, tx_reg); + else + act_writel(0, tx_reg); + + if (asoc_spi_wait_till_ready(asoc_spi) < 0) { + dev_err(&spi->dev, "TXS timed out\n"); + return count; + } + + if (rx_buf) + *rx_buf++ = act_readl(rx_reg); + + count--; + + } while (count); + + if (rx_buf != NULL) { + dev_dbg(&spi->dev, " rx_buf:0x%x)\n", ((const u8 *)(xfer->rx_buf))[0]); + } + if (tx_buf != NULL) { + dev_dbg(&spi->dev, " tx_buf:0x%x)\n", ((const u8 *)(xfer->tx_buf))[0]); + } + + return count; +} + +static inline int +asoc_spi_write_read_16bit(struct spi_device *spi, + struct spi_transfer *xfer) +{ + u32 tx_reg, rx_reg, ctl_reg; + struct asoc_spi *asoc_spi; + u32 tmp; + const u16 *tx_buf = xfer->tx_buf; + u16 *rx_buf = xfer->rx_buf; + unsigned int count = xfer->len; + + if (tx_buf) { + dev_dbg(&spi->dev, " tx_buf 0x%x 0x%x\n", tx_buf[0], tx_buf[1]); + } + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + tx_reg = spi_reg(asoc_spi, SPI_TXDAT); + rx_reg = spi_reg(asoc_spi, SPI_RXDAT); + + tmp = act_readl(ctl_reg); + tmp &= (~(SPIx_CTL_RWC(3) | + SPIx_CTL_RDIC(3) | + SPIx_CTL_TDIC(3) | + SPIx_CTL_SDT(7) | + SPIx_CTL_DTS | + SPIx_CTL_TIEN | + SPIx_CTL_RIEN | + SPIx_CTL_TDEN | + SPIx_CTL_RDEN)); + tmp |= SPIx_CTL_RWC(0); + act_writel(tmp, ctl_reg); + + do { + if (tx_buf) + act_writel(*tx_buf++, tx_reg); + else + act_writel(0, tx_reg); + + if (asoc_spi_wait_till_ready(asoc_spi) < 0) { + dev_err(&spi->dev, "TXS timed out\n"); + return count; + } + + if (rx_buf) { + *rx_buf++ = act_readl(rx_reg); + dev_dbg(&spi->dev, "rx_buf 0x%x\n", rx_buf[-1]); + } + + count -= 2; + } while (count); + + return count; +} + + +static inline int +asoc_spi_write_read_32bit(struct spi_device *spi, + struct spi_transfer *xfer) +{ + u32 tx_reg, rx_reg, ctl_reg; + struct asoc_spi *asoc_spi; + u32 tmp; + const u32 *tx_buf = xfer->tx_buf; + u32 *rx_buf = xfer->rx_buf; + unsigned int count = xfer->len; + + asoc_spi = spi_master_get_devdata(spi->master); + ctl_reg = spi_reg(asoc_spi, SPI_CTL); + tx_reg = spi_reg(asoc_spi, SPI_TXDAT); + rx_reg = spi_reg(asoc_spi, SPI_RXDAT); + + tmp = act_readl(ctl_reg); + tmp &= (~(SPIx_CTL_RWC(3) | + SPIx_CTL_RDIC(3) | + SPIx_CTL_TDIC(3) | + SPIx_CTL_SDT(7) | + SPIx_CTL_DTS | + SPIx_CTL_TIEN | + SPIx_CTL_RIEN | + SPIx_CTL_TDEN | + SPIx_CTL_RDEN)); + tmp |= SPIx_CTL_RWC(0); + act_writel(tmp, ctl_reg); + + do { + if (tx_buf) + act_writel(*tx_buf++, tx_reg); + else + act_writel(0, tx_reg); + + if (asoc_spi_wait_till_ready(asoc_spi) < 0) { + dev_err(&spi->dev, "TXS timed out\n"); + return count; + } + + if (rx_buf) + *rx_buf++ = act_readl(rx_reg); + + count -= 4; + + } while (count); + + return count; +} + +static int asoc_spi_get_channel_no(unsigned int base) +{ + switch (base) { + case SPI0_BASE: + return 0; + case SPI1_BASE: + return 1; + case SPI2_BASE: + return 2; + case SPI3_BASE: + return 3; + } + return -1; +} + +static unsigned int asoc_spi_get_write_dma_trig(unsigned int base) +{ + static unsigned int trigs[] = {SPI0_T, SPI1_T, SPI2_T, SPI3_T}; + + int spi_no = asoc_spi_get_channel_no(base); + if(spi_no < 0) { + pr_err("error: 0x%x.spi do not support\n", base); + return -1; + } + + return trigs[spi_no]; +} + +static inline unsigned int asoc_spi_get_read_dma_trig(unsigned int base) +{ + static unsigned int trigs[] = {SPI0_R, SPI1_R, SPI2_R, SPI3_R}; + int spi_no = asoc_spi_get_channel_no(base); + if(spi_no < 0) { + pr_err("error: 0x%x.spi do not support\n", base); + return -1; + } + + return trigs[spi_no]; +} + +static struct page *asoc_spi_virt_to_page(const void *addr) +{ + if (is_vmalloc_addr(addr)) + return vmalloc_to_page(addr); + else + return virt_to_page(addr); +} + +static void asoc_spi_setup_dma_scatter(struct asoc_spi *asoc_spi, + void *buffer, + unsigned int length, + struct sg_table *sgtab) +{ + struct scatterlist *sg; + int bytesleft = length; + void *bufp = buffer; + int mapbytes; + int i; + + if (buffer) { + for_each_sg(sgtab->sgl, sg, sgtab->nents, i) { + if(bytesleft == 0) { + sg_mark_end(sg); + sgtab->nents = i; + break; + } + /* + * If there are less bytes left than what fits + * in the current page (plus page alignment offset) + * we just feed in this, else we stuff in as much + * as we can. + */ + if (bytesleft < (PAGE_SIZE - offset_in_page(bufp))) + mapbytes = bytesleft; + else + mapbytes = PAGE_SIZE - offset_in_page(bufp); + sg_set_page(sg, asoc_spi_virt_to_page(bufp), + mapbytes, offset_in_page(bufp)); + bufp += mapbytes; + bytesleft -= mapbytes; + dev_dbg(asoc_spi->dev, + "set RX/TX target page @ %p, %d bytes, %d left\n", + bufp, mapbytes, bytesleft); + } + } + + BUG_ON(bytesleft); +} + +static int asoc_spi_write_by_dma(struct asoc_spi *asoc_spi, + struct spi_transfer *xfer) +{ + struct dma_slave_config tx_conf = { + .dst_addr = spi_reg(asoc_spi, SPI_TXDAT), + .direction = DMA_MEM_TO_DEV, + }; + struct owl_dma_slave tx_atslave = { + .mode = PRIORITY_ZERO | SRC_INCR | DST_CONSTANT + | SRC_DCU | DST_DEV + | asoc_spi_get_write_dma_trig(asoc_spi->base), + .dma_dev = asoc_spi->dma_tx_channel->device->dev, + .trans_type = SLAVE, + }; + u32 ctl_reg = spi_reg(asoc_spi, SPI_CTL); + u32 stat_reg = spi_reg(asoc_spi, SPI_STAT); + u32 txcr_reg = spi_reg(asoc_spi, SPI_TXCR); + + struct dma_chan *txchan = asoc_spi->dma_tx_channel; + unsigned int pages; + int len, left; + void *tx_buf; + int tx_sglen; + struct dma_async_tx_descriptor *txdesc; + u32 val; + int retval; + + struct completion tx_cmp; + dma_cookie_t cookie; + enum dma_status status; + + /* Create sglists for the transfers */ + left = xfer->len; + tx_buf = (void*)xfer->tx_buf; + len = left > MAX_SPI_DMA_LEN ? MAX_SPI_DMA_LEN : left; + + pages = DIV_ROUND_UP(len, PAGE_SIZE); + retval = sg_alloc_table(&asoc_spi->sgt_tx, pages, GFP_ATOMIC); + if (retval) + goto err_slave; + + while(left > 0) { + len = left > MAX_SPI_DMA_LEN ? MAX_SPI_DMA_LEN : left; + left -= len; + + val = act_readl(ctl_reg); + val &= (~(SPIx_CTL_RWC(3) | + SPIx_CTL_RDIC(3) | + SPIx_CTL_TDIC(3) | + SPIx_CTL_SDT(7) | + SPIx_CTL_DTS | + SPIx_CTL_TIEN | + SPIx_CTL_RIEN | + SPIx_CTL_TDEN | + SPIx_CTL_RDEN)); + val |= (SPIx_CTL_RWC(1) | + SPIx_CTL_RDIC(2) | + SPIx_CTL_TDIC(2) | + SPIx_CTL_TDEN); + act_writel(val, ctl_reg); + + act_writel(len/4, txcr_reg); + + txchan->private = (void *)&tx_atslave; + retval = dmaengine_slave_config(txchan, &tx_conf); + if (retval) { + dev_err(asoc_spi->dev, "call the write slave config error\n"); + goto err_slave; + } + + /* Fill in the scatterlists for the TX buffers */ + asoc_spi_setup_dma_scatter(asoc_spi, tx_buf, len, &asoc_spi->sgt_tx); + tx_sglen = dma_map_sg(txchan->device->dev, asoc_spi->sgt_tx.sgl, + asoc_spi->sgt_tx.nents, DMA_TO_DEVICE); + if (!tx_sglen) + goto err_sgmap; + + tx_buf += len; + + /* Send scatterlists */ + txdesc = dmaengine_prep_slave_sg(txchan, + asoc_spi->sgt_tx.sgl, + tx_sglen, + DMA_MEM_TO_DEV, + 0); + if (!txdesc) + goto err_desc; + + init_completion(&tx_cmp); + + txdesc->callback = spi_callback; + txdesc->callback_param = &tx_cmp; + + cookie = dmaengine_submit(txdesc); + if (dma_submit_error(cookie)) { + dev_err(asoc_spi->dev, "submit write error!\n"); + goto err_desc; + } + + dma_async_issue_pending(txchan); + + dev_dbg(asoc_spi->dev, "write start dma\n"); + if (!wait_for_completion_timeout(&tx_cmp, 5 * HZ)) { + dev_err(asoc_spi->dev, "wait_for_completion timeout while send by dma\n"); + owl_dma_dump_all(txchan); + goto err_desc; + } + + status = dma_async_is_tx_complete(txchan, cookie, NULL, NULL); + if (status != DMA_COMPLETE) { + dev_err(asoc_spi->dev, "transfer not succeed\n"); + goto err_desc; + } + + if (asoc_spi_wait_till_ready(asoc_spi) < 0) { + dev_err(asoc_spi->dev, "TXS timed out\n"); + goto err_desc; + } + + if (act_readl(stat_reg) & + (SPIx_STAT_RFER | SPIx_STAT_TFER | SPIx_STAT_BEB)) { + dev_err(asoc_spi->dev, "spi state error while send by dma\n"); + dump_spi_registers(asoc_spi); + goto err_desc; + } + + dma_unmap_sg(txchan->device->dev, asoc_spi->sgt_tx.sgl, + asoc_spi->sgt_tx.nents, DMA_TO_DEVICE); + } + sg_free_table(&asoc_spi->sgt_tx); + return 0; + +err_desc: + dmaengine_terminate_all(txchan); +err_sgmap: + sg_free_table(&asoc_spi->sgt_tx); +err_slave: + return -EINVAL; +} + +static int asoc_spi_read_by_dma(struct asoc_spi *asoc_spi, + struct spi_transfer *xfer) +{ + struct dma_slave_config rx_conf = { + .src_addr = spi_reg(asoc_spi, SPI_RXDAT), + .direction = DMA_DEV_TO_MEM, + }; + struct owl_dma_slave rx_atslave = { + .mode = PRIORITY_ZERO | DST_INCR | SRC_CONSTANT + | DST_DCU | SRC_DEV + | asoc_spi_get_read_dma_trig(asoc_spi->base), + .dma_dev = asoc_spi->dma_rx_channel->device->dev, + .trans_type = SLAVE, + }; + u32 ctl_reg = spi_reg(asoc_spi, SPI_CTL); + u32 stat_reg = spi_reg(asoc_spi, SPI_STAT); + u32 rxcr_reg = spi_reg(asoc_spi, SPI_RXCR); + u32 tcnt_reg = spi_reg(asoc_spi, SPI_TCNT); + struct dma_chan *rxchan = asoc_spi->dma_rx_channel; + unsigned int pages; + int len, left; + void *rx_buf; + int rx_sglen; + struct dma_async_tx_descriptor *rxdesc; + + u32 val; + int retval; + + struct completion rx_cmp; + dma_cookie_t cookie; + enum dma_status status; + + /* Create sglists for the transfers */ + left = xfer->len; + rx_buf = xfer->rx_buf; + len = left > MAX_SPI_DMA_LEN ? MAX_SPI_DMA_LEN : left; + + pages = DIV_ROUND_UP(len, PAGE_SIZE); + retval = sg_alloc_table(&asoc_spi->sgt_rx, pages, GFP_ATOMIC); + if (retval) + goto err_slave; + + while(left > 0) { + len = left > MAX_SPI_DMA_LEN ? MAX_SPI_DMA_LEN : left; + left -= len; + + val = act_readl(ctl_reg); + val &= (~(SPIx_CTL_RWC(3) | + SPIx_CTL_RDIC(3) | + SPIx_CTL_TDIC(3) | + SPIx_CTL_SDT(7) | + SPIx_CTL_DTS | + SPIx_CTL_TIEN | + SPIx_CTL_RIEN | + SPIx_CTL_TDEN | + SPIx_CTL_RDEN)); + val |= (SPIx_CTL_RWC(2) | + SPIx_CTL_RDIC(2) | + SPIx_CTL_TDIC(2) | + SPIx_CTL_SDT(1) | + SPIx_CTL_DTS | + SPIx_CTL_RDEN); + act_writel(val, ctl_reg); + + act_writel(len/4, tcnt_reg); + act_writel(len/4, rxcr_reg); + + rxchan->private = (void *)&rx_atslave; + retval = dmaengine_slave_config(rxchan, &rx_conf); + if (retval) { + dev_err(asoc_spi->dev, "call the read slave config error\n"); + goto err_slave; + } + + /* Fill in the scatterlists for the RX buffers */ + asoc_spi_setup_dma_scatter(asoc_spi, rx_buf, len, &asoc_spi->sgt_rx); + rx_sglen = dma_map_sg(rxchan->device->dev, asoc_spi->sgt_rx.sgl, + asoc_spi->sgt_rx.nents, DMA_FROM_DEVICE); + if (!rx_sglen) + goto err_sgmap; + + rx_buf += len; + + /* Send scatterlists */ + rxdesc = dmaengine_prep_slave_sg(rxchan, + asoc_spi->sgt_rx.sgl, + rx_sglen, + DMA_DEV_TO_MEM, + 0); + if (!rxdesc) + goto err_desc; + + init_completion(&rx_cmp); + + rxdesc->callback = spi_callback; + rxdesc->callback_param = &rx_cmp; + + cookie = dmaengine_submit(rxdesc); + if (dma_submit_error(cookie)) { + dev_err(asoc_spi->dev, "submit read error!\n"); + goto err_desc; + } + + dma_async_issue_pending(rxchan); + + dev_dbg(asoc_spi->dev, "read start dma\n"); + if (!wait_for_completion_timeout(&rx_cmp, 5 * HZ)) { + dev_err(asoc_spi->dev, "read wait_for_completion timeout while receive by dma\n"); + owl_dma_dump_all(rxchan); + goto err_desc; + } + + status = dma_async_is_tx_complete(rxchan, cookie, NULL, NULL); + if (status != DMA_COMPLETE) { + dev_err(asoc_spi->dev, "transfer not succeed\n"); + goto err_desc; + } + + if (asoc_spi_wait_till_ready(asoc_spi) < 0) { + dev_err(asoc_spi->dev, "RXS timed out\n"); + goto err_desc; + } + + if (act_readl(stat_reg) & + (SPIx_STAT_RFER | SPIx_STAT_TFER | SPIx_STAT_BEB)) { + dev_err(asoc_spi->dev, "spi state error while send by dma\n"); + dump_spi_registers(asoc_spi); + goto err_desc; + } + + dma_unmap_sg(rxchan->device->dev, asoc_spi->sgt_rx.sgl, + asoc_spi->sgt_rx.nents, DMA_FROM_DEVICE); + } + sg_free_table(&asoc_spi->sgt_rx); + return 0; + +err_desc: + dmaengine_terminate_all(rxchan); +err_sgmap: + sg_free_table(&asoc_spi->sgt_rx); +err_slave: + return -EINVAL; +} + + +static int asoc_spi_write_read_by_dma(struct asoc_spi *asoc_spi, + struct spi_transfer *xfer) +{ + struct dma_slave_config tx_conf = { + .dst_addr = spi_reg(asoc_spi, SPI_TXDAT), + .direction = DMA_MEM_TO_DEV, + }; + struct owl_dma_slave tx_atslave = { + .mode = PRIORITY_ZERO | SRC_INCR | DST_CONSTANT + | SRC_DCU | DST_DEV + | asoc_spi_get_write_dma_trig(asoc_spi->base), + .dma_dev = asoc_spi->dma_tx_channel->device->dev, + .trans_type = SLAVE, + }; + struct dma_slave_config rx_conf = { + .src_addr = spi_reg(asoc_spi, SPI_RXDAT), + .direction = DMA_DEV_TO_MEM, + }; + struct owl_dma_slave rx_atslave = { + .mode = PRIORITY_ZERO | DST_INCR | SRC_CONSTANT + | DST_DCU | SRC_DEV + | asoc_spi_get_read_dma_trig(asoc_spi->base), + .dma_dev = asoc_spi->dma_rx_channel->device->dev, + .trans_type = SLAVE, + }; + u32 ctl_reg = spi_reg(asoc_spi, SPI_CTL); + u32 stat_reg = spi_reg(asoc_spi, SPI_STAT); + u32 txcr_reg = spi_reg(asoc_spi, SPI_TXCR); + u32 rxcr_reg = spi_reg(asoc_spi, SPI_RXCR); + struct dma_chan *txchan = asoc_spi->dma_tx_channel; + struct dma_chan *rxchan = asoc_spi->dma_rx_channel; + unsigned int pages; + int len, left; + void *tx_buf, *rx_buf; + int rx_sglen, tx_sglen; + struct dma_async_tx_descriptor *rxdesc; + struct dma_async_tx_descriptor *txdesc; + + u32 val; + int retval; + + struct completion rx_cmp, tx_cmp; + dma_cookie_t cookie; + enum dma_status status; + + /* Create sglists for the transfers */ + left = xfer->len; + tx_buf = (void*)xfer->tx_buf; + rx_buf = xfer->rx_buf; + len = left > MAX_SPI_DMA_LEN ? MAX_SPI_DMA_LEN : left; + + pages = DIV_ROUND_UP(len, PAGE_SIZE); + retval = sg_alloc_table(&asoc_spi->sgt_tx, pages, GFP_ATOMIC); + if (retval) + goto err_slave; + retval = sg_alloc_table(&asoc_spi->sgt_rx, pages, GFP_ATOMIC); + if (retval) + goto err_slave; + + while(left > 0) { + len = left > MAX_SPI_DMA_LEN ? MAX_SPI_DMA_LEN : left; + left -= len; + + val = act_readl(ctl_reg); + val &= (~(SPIx_CTL_RWC(3) | + SPIx_CTL_RDIC(3) | + SPIx_CTL_TDIC(3) | + SPIx_CTL_SDT(7) | + SPIx_CTL_DTS | + SPIx_CTL_TIEN | + SPIx_CTL_RIEN | + SPIx_CTL_TDEN | + SPIx_CTL_RDEN)); + val |= (SPIx_CTL_RWC(0) | + SPIx_CTL_RDIC(2) | + SPIx_CTL_TDIC(2) | + SPIx_CTL_SDT(1) | + SPIx_CTL_DTS | + SPIx_CTL_RDEN | + SPIx_CTL_TDEN); + act_writel(val, ctl_reg); + + act_writel(len/4, txcr_reg); + act_writel(len/4, rxcr_reg); + + txchan->private = (void *)&tx_atslave; + retval = dmaengine_slave_config(txchan, &tx_conf); + if (retval) { + dev_err(asoc_spi->dev, "call the write slave config error\n"); + goto err_slave; + } + rxchan->private = (void *)&rx_atslave; + retval = dmaengine_slave_config(rxchan, &rx_conf); + if (retval) { + dev_err(asoc_spi->dev, "call the read slave config error\n"); + goto err_slave; + } + + /* Fill in the scatterlists for the TX buffers */ + asoc_spi_setup_dma_scatter(asoc_spi, tx_buf, len, &asoc_spi->sgt_tx); + tx_sglen = dma_map_sg(txchan->device->dev, asoc_spi->sgt_tx.sgl, + asoc_spi->sgt_tx.nents, DMA_TO_DEVICE); + if (!tx_sglen) + goto err_sgmap; + asoc_spi_setup_dma_scatter(asoc_spi, rx_buf, len, &asoc_spi->sgt_rx); + rx_sglen = dma_map_sg(rxchan->device->dev, asoc_spi->sgt_rx.sgl, + asoc_spi->sgt_rx.nents, DMA_FROM_DEVICE); + if (!rx_sglen) + goto err_sgmap; + + tx_buf += len; + rx_buf += len; + + /* Send scatterlists */ + txdesc = dmaengine_prep_slave_sg(txchan, + asoc_spi->sgt_tx.sgl, + tx_sglen, + DMA_MEM_TO_DEV, + 0); + if (!txdesc) + goto err_desc; + rxdesc = dmaengine_prep_slave_sg(rxchan, + asoc_spi->sgt_rx.sgl, + rx_sglen, + DMA_DEV_TO_MEM, + 0); + if (!rxdesc) + goto err_desc; + + init_completion(&tx_cmp); + txdesc->callback = spi_callback; + txdesc->callback_param = &tx_cmp; + cookie = dmaengine_submit(txdesc); + if (dma_submit_error(cookie)) { + dev_err(asoc_spi->dev, "submit write error!\n"); + goto err_desc; + } + + init_completion(&rx_cmp); + rxdesc->callback = spi_callback; + rxdesc->callback_param = &rx_cmp; + cookie = dmaengine_submit(rxdesc); + if (dma_submit_error(cookie)) { + dev_err(asoc_spi->dev, "submit read error!\n"); + goto err_desc; + } + + dma_async_issue_pending(txchan); + dma_async_issue_pending(rxchan); + + dev_dbg(asoc_spi->dev, "write&read start dma\n"); + if (!wait_for_completion_timeout(&tx_cmp, 5 * HZ)) { + dev_err(asoc_spi->dev, "write wait_for_completion timeout while send by dma\n"); + owl_dma_dump_all(txchan); + goto err_desc; + } + if (!wait_for_completion_timeout(&rx_cmp, 1 * HZ)) { + dev_err(asoc_spi->dev, "read wait_for_completion timeout while receive by dma\n"); + owl_dma_dump_all(rxchan); + goto err_desc; + } + + status = dma_async_is_tx_complete(txchan, cookie, NULL, NULL); + if (status != DMA_COMPLETE) { + dev_err(asoc_spi->dev, "transfer not succeed\n"); + goto err_desc; + } + status = dma_async_is_tx_complete(rxchan, cookie, NULL, NULL); + if (status != DMA_COMPLETE) { + dev_err(asoc_spi->dev, "transfer not succeed\n"); + goto err_desc; + } + + if (asoc_spi_wait_till_ready(asoc_spi) < 0) { + dev_err(asoc_spi->dev, "TXS&RXS timed out\n"); + goto err_desc; + } + + if (act_readl(stat_reg) & + (SPIx_STAT_RFER | SPIx_STAT_TFER | SPIx_STAT_BEB)) { + dev_err(asoc_spi->dev, "spi state error while send by dma\n"); + dump_spi_registers(asoc_spi); + goto err_desc; + } + + dma_unmap_sg(txchan->device->dev, asoc_spi->sgt_tx.sgl, + asoc_spi->sgt_tx.nents, DMA_TO_DEVICE); + dma_unmap_sg(rxchan->device->dev, asoc_spi->sgt_rx.sgl, + asoc_spi->sgt_rx.nents, DMA_FROM_DEVICE); + } + sg_free_table(&asoc_spi->sgt_tx); + sg_free_table(&asoc_spi->sgt_rx); + return 0; + +err_desc: + dmaengine_terminate_all(rxchan); + dmaengine_terminate_all(txchan); +err_sgmap: + sg_free_table(&asoc_spi->sgt_rx); + sg_free_table(&asoc_spi->sgt_tx); +err_slave: + return -EINVAL; +} + +static unsigned int +asoc_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer) +{ + struct asoc_spi *asoc_spi; + unsigned int len; + int word_len; + + asoc_spi = spi_master_get_devdata(spi->master); + word_len = spi->bits_per_word; + len = xfer->len; + + spi_clear_stat(asoc_spi); + + if ((len < BYTES_4_DMA_XFER) || + (asoc_spi->enable_dma == 0) || + (xfer->bits_per_word != 32)) { + unsigned int count = 0; + + dev_dbg(asoc_spi->dev, "cpu wr\n"); + + if (word_len == 8) + count = asoc_spi_write_read_8bit(spi, xfer); + else if (word_len == 16) + count = asoc_spi_write_read_16bit(spi, xfer); + else if (word_len == 32) + count = asoc_spi_write_read_32bit(spi, xfer); + + return len - count; + + } else { + int retval = 0; + + if (xfer->tx_buf && (!xfer->rx_buf)) { + dev_dbg(asoc_spi->dev, "dma w%d\n", xfer->len); + retval = asoc_spi_write_by_dma(asoc_spi, xfer); + } else if ((!xfer->tx_buf) && xfer->rx_buf) { + dev_dbg(asoc_spi->dev, "dma r%d\n", xfer->len); + retval = asoc_spi_read_by_dma(asoc_spi, xfer); + } else if((xfer->tx_buf) && (xfer->rx_buf)) { + dev_dbg(asoc_spi->dev, "dma w&r%d\n", xfer->len); + retval = asoc_spi_write_read_by_dma(asoc_spi, xfer); + } else { + dev_err(&spi->dev, "cannot find valid xfer buffer\n"); + return 0; + } + + if (retval) + return 0; + else + return len; + } + +} + +/* + * called only when no transfer is active on the bus + */ +static int asoc_spi_device_setup(struct spi_device *spi) +{ + struct asoc_spi *asoc_spi; + int retval; + int bits_per_word, speed_hz; + + asoc_spi = spi_master_get_devdata(spi->master); + + speed_hz = spi->max_speed_hz; + bits_per_word = spi->bits_per_word; + + if (bits_per_word <= 8) + bits_per_word = 8; + else if (bits_per_word <= 16) + bits_per_word = 16; + else if (bits_per_word <= 32) + bits_per_word = 32; + else + return -EINVAL; + + retval = asoc_spi_baudrate_set(spi, speed_hz); + if (retval) + return retval; + + return asoc_spi_set_bus_param(spi, bits_per_word, 0); +} + + +static int asoc_spi_transfer_setup(struct spi_device *spi, + struct spi_transfer *t) +{ + struct asoc_spi *asoc_spi; + int retval; + int bits_per_word, speed_hz; + + if (!t) + return -EINVAL; + + asoc_spi = spi_master_get_devdata(spi->master); + + speed_hz = (t->speed_hz) + ? t->speed_hz : spi->max_speed_hz; + bits_per_word = (t->bits_per_word) + ? t->bits_per_word : spi->bits_per_word; + + if (bits_per_word <= 8 || t->len <= 8) + bits_per_word = 8; + else if (bits_per_word <= 16) + bits_per_word = 16; + else if (bits_per_word <= 32) + bits_per_word = 32; + else + return -EINVAL; + + + retval = asoc_spi_baudrate_set(spi, speed_hz); + if (retval) + return retval; + + return asoc_spi_set_bus_param(spi, bits_per_word, 0); +} + + +static int asoc_spi_transfer_check(struct spi_device *spi, + struct spi_transfer *t) +{ + int bits_per_word = 0; + + if ((!t) || (t->len == 0)) + return -EINVAL; + + bits_per_word = (t->bits_per_word) + ? t->bits_per_word : spi->bits_per_word; + if (bits_per_word <= 8 || t->len <= 8) + bits_per_word = 8; + else if (bits_per_word <= 16) + bits_per_word = 16; + else if (bits_per_word <= 32) + bits_per_word = 32; + else + return -EINVAL; + + /*transfer length should be alignd according to bits_per_word*/ + if (t->len & ((bits_per_word >> 3) - 1)) { + dev_err(&spi->dev, "bad transfer length!!\n"); + return -EINVAL; + } + + return 0; +} + + +static void asoc_spi_work(struct work_struct *work) +{ + struct asoc_spi *asoc_spi = + container_of(work, struct asoc_spi, work); + + dev_dbg(asoc_spi->dev, "ASOC SPI: enter spi work\n"); + + spin_lock_irq(&asoc_spi->lock); + while (!list_empty(&asoc_spi->msg_queue)) { + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + int par_override = 0; + int status = 0; + int cs_active = 0; + + dev_dbg(asoc_spi->dev, "asoc_spi: start one message\n"); + + m = container_of(asoc_spi->msg_queue.next, struct spi_message, + queue); + + list_del_init(&m->queue); + spin_unlock_irq(&asoc_spi->lock); + + spi = m->spi; + + /* Load defaults */ + status = asoc_spi_mode_set(spi); + status = asoc_spi_device_setup(spi); + + if (status < 0) + goto msg_done; + + list_for_each_entry(t, &m->transfers, transfer_list) { + dev_dbg(asoc_spi->dev, "asoc_spi: start one transfer\n"); + + status = asoc_spi_transfer_check(spi, t); + if (status < 0) + break; + + if (par_override || t->bits_per_word || t->speed_hz) { + par_override = 1; + status = asoc_spi_transfer_setup(spi, t); + if (status < 0) + break; + if (!t->speed_hz && !t->bits_per_word) + par_override = 0; + } + + if (!cs_active) { + asoc_spi_activate_cs(spi); + cs_active = 1; + } + + m->actual_length += + asoc_spi_write_read(spi, t); + + if (t->delay_usecs) + udelay(t->delay_usecs); + + if (t->cs_change) { + asoc_spi_deactivate_cs(spi); + cs_active = 0; + } + dev_dbg(asoc_spi->dev, "asoc_spi: end one transfer\n"); + } + +msg_done: + if (cs_active) + asoc_spi_deactivate_cs(spi); + + m->status = status; + m->complete(m->context); + + dev_dbg(asoc_spi->dev, "asoc_spi: end one message\n"); + spin_lock_irq(&asoc_spi->lock); + } + + spin_unlock_irq(&asoc_spi->lock); + + dev_dbg(asoc_spi->dev, "ASOC SPI: quit spi work\n"); +} + +static int asoc_spi_setup(struct spi_device *spi) +{ + struct asoc_spi *asoc_spi; + unsigned int spi_source_clk_hz; + + dev_dbg(&spi->dev, "ASOC SPI: enter spi setup\n"); + + asoc_spi = spi_master_get_devdata(spi->master); + + if (spi->bits_per_word > 32) + return -EINVAL; + + if (spi->bits_per_word == 0) + spi->bits_per_word = 8; + + spi_source_clk_hz = clk_get_rate(asoc_spi->clk); + + dev_dbg(&spi->dev, "ahb freq is %d\n", spi_source_clk_hz); + + if ((spi->max_speed_hz == 0) + || (spi->max_speed_hz > spi_source_clk_hz)) + spi->max_speed_hz = spi_source_clk_hz; + + dev_dbg(&spi->dev, "ASOC SPI: ok spi setup\n"); + + /* + * baudrate & width will be set asoc_spi_setup_transfer + */ + return 0; +} + + +static int asoc_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct asoc_spi *asoc_spi; + unsigned long flags; + + /* reject invalid messages and transfers */ + if (list_empty(&m->transfers) || !m->complete) { + m->status = -EINVAL; + return -EINVAL; + } + + asoc_spi = spi_master_get_devdata(spi->master); + + m->actual_length = 0; + m->status = -EINPROGRESS; + + spin_lock_irqsave(&asoc_spi->lock, flags); + list_add_tail(&m->queue, &asoc_spi->msg_queue); + queue_work(asoc_spi->workqueue, &asoc_spi->work); + spin_unlock_irqrestore(&asoc_spi->lock, flags); + + return 0; + +} + +static void asoc_spi_cleanup(struct spi_device *spi) +{ +} + +static struct owl_spi_pdata owl_spi_pdata = { + .max_chipselect = 4, + .enable_dma = 1, +}; + +static const struct of_device_id acts_spi_dt_ids[] = { + { + .compatible = "actions,owl-spi", + .data = &owl_spi_pdata, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, acts_spi_dt_ids); + + + +static int asoc_spi_dma_probe(struct asoc_spi *asoc_spi) +{ + dma_cap_mask_t mask; + + if (!asoc_spi->enable_dma) { + dev_dbg(asoc_spi->dev, "spi dma is disabled\n"); + return 0; + } + + /* Try to acquire a generic DMA engine slave channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + /* + * We need both RX and TX channels to do DMA, else do none + * of them. + */ + asoc_spi->dma_rx_channel = dma_request_channel(mask, NULL, NULL); + if (!asoc_spi->dma_rx_channel) { + dev_err(asoc_spi->dev, "no RX DMA channel!\n"); + goto err_no_rxchan; + } + + asoc_spi->dma_tx_channel = dma_request_channel(mask, NULL, NULL); + if (!asoc_spi->dma_tx_channel) { + dev_err(asoc_spi->dev, "no TX DMA channel!\n"); + goto err_no_txchan; + } + + dev_dbg(asoc_spi->dev, "setup for DMA on RX %s, TX %s\n", + dma_chan_name(asoc_spi->dma_rx_channel), + dma_chan_name(asoc_spi->dma_tx_channel)); + + return 0; + +err_no_txchan: + dma_release_channel(asoc_spi->dma_rx_channel); + asoc_spi->dma_rx_channel = NULL; +err_no_rxchan: + dev_err(asoc_spi->dev, "Failed to work in dma mode, work without dma!\n"); + return -ENODEV; +} + +static void asoc_spi_unmap_free_dma_scatter(struct asoc_spi *asoc_spi) +{ + /* Unmap and free the SG tables */ + dma_unmap_sg(asoc_spi->dma_tx_channel->device->dev, asoc_spi->sgt_tx.sgl, + asoc_spi->sgt_tx.nents, DMA_TO_DEVICE); + dma_unmap_sg(asoc_spi->dma_rx_channel->device->dev, asoc_spi->sgt_rx.sgl, + asoc_spi->sgt_rx.nents, DMA_FROM_DEVICE); + sg_free_table(&asoc_spi->sgt_rx); + sg_free_table(&asoc_spi->sgt_tx); +} + +static void asoc_spi_terminate_dma(struct asoc_spi *asoc_spi) +{ + struct dma_chan *rxchan = asoc_spi->dma_rx_channel; + struct dma_chan *txchan = asoc_spi->dma_tx_channel; + + dmaengine_terminate_all(rxchan); + dmaengine_terminate_all(txchan); + asoc_spi_unmap_free_dma_scatter(asoc_spi); + asoc_spi->dma_running = false; +} + +static void asoc_spi_dma_remove(struct asoc_spi *asoc_spi) +{ + if (asoc_spi->dma_running) + asoc_spi_terminate_dma(asoc_spi); + if (asoc_spi->dma_tx_channel) + dma_release_channel(asoc_spi->dma_tx_channel); + if (asoc_spi->dma_rx_channel) + dma_release_channel(asoc_spi->dma_rx_channel); +} + +static int asoc_spi_clk_enable(struct asoc_spi *asoc_spi) +{ + static int mod_ids[] = {MOD_ID_SPI0, MOD_ID_SPI1, MOD_ID_SPI2, MOD_ID_SPI3}; + static char *spi_clk_name[] = {"CMUMOD_SPI0", "CMUMOD_SPI1", "CMUMOD_SPI2", "CMUMOD_SPI3"}; + struct clk *spi_clk; + int mod_id; + int spi_no = asoc_spi_get_channel_no(asoc_spi->base); + if(spi_no < 0) + return -1; + + asoc_spi->clk = clk_get(asoc_spi->dev, "H_CLK"); + if (IS_ERR(asoc_spi->clk)) { + return PTR_ERR(asoc_spi->clk); + } + + mod_id = mod_ids[spi_no]; + + spi_clk = clk_get(NULL, spi_clk_name[spi_no]); + clk_prepare(spi_clk); + clk_enable(spi_clk); + module_clk_enable(mod_id); + module_reset(mod_id); + + return 0; +} + +static int asoc_spi_clk_disable(struct asoc_spi *asoc_spi) +{ + static int mod_ids[] = {MOD_ID_SPI0, MOD_ID_SPI1, MOD_ID_SPI2, MOD_ID_SPI3}; + + int mod_id; + int spi_no = asoc_spi_get_channel_no(asoc_spi->base); + if(spi_no < 0) + return -1; + + if (!IS_ERR(asoc_spi->clk)) + clk_put(asoc_spi->clk); + + mod_id = mod_ids[spi_no]; + + module_clk_disable(mod_id); + return 0; +} + +static int __init asoc_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct asoc_spi *asoc_spi; + struct owl_spi_pdata *spi_pdata; + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + int ret = 0; + + dev_dbg(&pdev->dev, "ASOC SPI: enter spi probe\n"); + dev_dbg(&pdev->dev, "pdev->name: %s\n", pdev->name ? pdev->name : "<null>"); + + if (np == NULL) + return -ENODEV; + match = of_match_node(acts_spi_dt_ids, np); + if (match == NULL) + return -ENODEV; + spi_pdata = (struct owl_spi_pdata *)match->data; + + master = spi_alloc_master(&pdev->dev, sizeof *asoc_spi); + if (master == NULL) { + dev_dbg(&pdev->dev, "master allocation failed\n"); + return -ENOMEM; + } + + master->bus_num = of_alias_get_id(np, "spi"); + + /* the spi->mode bits understood by this driver: */ + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + + master->setup = asoc_spi_setup; + master->transfer = asoc_spi_transfer; + master->cleanup = asoc_spi_cleanup; + + master->num_chipselect = spi_pdata->max_chipselect; + master->dev.of_node = np; + + dev_set_drvdata(&pdev->dev, master); + + asoc_spi = spi_master_get_devdata(master); + asoc_spi->dev = &pdev->dev; + asoc_spi->irq = platform_get_irq(pdev, 0); + asoc_spi->base = + (platform_get_resource(pdev, IORESOURCE_MEM, 0)->start); + + asoc_spi_clk_enable(asoc_spi); + asoc_spi->enable_dma = spi_pdata->enable_dma; + if(asoc_spi_dma_probe(asoc_spi) < 0) + goto out0; + + spin_lock_init(&asoc_spi->lock); + INIT_WORK(&asoc_spi->work, asoc_spi_work); + INIT_LIST_HEAD(&asoc_spi->msg_queue); + asoc_spi->workqueue = create_singlethread_workqueue( + dev_name(master->dev.parent)); + + if (asoc_spi->workqueue == NULL) { + ret = -EBUSY; + goto out0; + } + ret = spi_register_master(master); + if (ret < 0) + goto out1; + + dev_dbg(&pdev->dev, "ASOC SPI: spi probe ok\n"); + + return ret; + +out1: + destroy_workqueue(asoc_spi->workqueue); +out0: + asoc_spi_dma_remove(asoc_spi); + spi_master_put(master); + + dev_dbg(&pdev->dev, "ASOC SPI: spi probe failed\n"); + return ret; +} + + +static int __exit asoc_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master; + struct asoc_spi *asoc_spi; + + dev_dbg(&pdev->dev, "spi remove\n"); + + master = dev_get_drvdata(&pdev->dev); + asoc_spi = spi_master_get_devdata(master); + + cancel_work_sync(&asoc_spi->work); + asoc_spi_dma_remove(asoc_spi); + asoc_spi_clk_disable(asoc_spi); + + spi_unregister_master(master); + + dev_dbg(&pdev->dev, "spi remove ok\n"); + + return 0; +} + +MODULE_ALIAS("platform: asoc_spi"); + + +static struct platform_driver asoc_spi_driver = { + .driver = { + .name = "asoc_spi0", + .owner = THIS_MODULE, + .of_match_table = acts_spi_dt_ids, + }, + .remove = asoc_spi_remove, + +}; + +static int __init asoc_spi_init(void) +{ + int err; + err = platform_driver_probe(&asoc_spi_driver, asoc_spi_probe); + return err; +} +subsys_initcall(asoc_spi_init); + +static void __exit asoc_spi_exit(void) +{ + platform_driver_unregister(&asoc_spi_driver); +} +module_exit(asoc_spi_exit); + +MODULE_DESCRIPTION("Asoc SPI driver"); +MODULE_LICENSE("GPL"); -- 2.7.4 -- _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto