android_kernel_lenovo_1050f/drivers/spi/spi-dw-mid.c

278 lines
7.2 KiB
C

/*
* Special handling for DW core on Intel MID platform
*
* Copyright (c) 2009, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include "spi-dw.h"
#ifdef CONFIG_SPI_DW_MID_DMA
#include <linux/intel_mid_dma.h>
#include <linux/pci.h>
struct mid_dma {
struct intel_mid_dma_slave dmas_tx;
struct intel_mid_dma_slave dmas_rx;
};
static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param)
{
struct dw_spi *dws = param;
return dws->dmac && (&dws->dmac->dev == chan->device->dev);
}
static int mid_spi_dma_init(struct dw_spi *dws)
{
struct mid_dma *dw_dma = dws->dma_priv;
struct intel_mid_dma_slave *rxs, *txs;
dma_cap_mask_t mask;
/*
* Get pci device for DMA controller, currently it could only
* be the DMA controller of either Moorestown or Medfield
*/
dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0813, NULL);
if (!dws->dmac)
dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0827, NULL);
if (!dws->dmac)
dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x08EF, NULL);
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
/* 1. Init rx channel */
dws->rxchan = dma_request_channel(mask, mid_spi_dma_chan_filter, dws);
if (!dws->rxchan)
goto err_exit;
rxs = &dw_dma->dmas_rx;
rxs->hs_mode = LNW_DMA_HW_HS;
rxs->cfg_mode = LNW_DMA_PER_TO_MEM;
dws->rxchan->private = rxs;
/* 2. Init tx channel */
dws->txchan = dma_request_channel(mask, mid_spi_dma_chan_filter, dws);
if (!dws->txchan)
goto free_rxchan;
txs = &dw_dma->dmas_tx;
txs->hs_mode = LNW_DMA_HW_HS;
txs->cfg_mode = LNW_DMA_MEM_TO_PER;
dws->txchan->private = txs;
dws->dma_inited = 1;
return 0;
free_rxchan:
dma_release_channel(dws->rxchan);
err_exit:
return -1;
}
static void mid_spi_dma_exit(struct dw_spi *dws)
{
dma_release_channel(dws->txchan);
dma_release_channel(dws->rxchan);
}
/*
* dws->dma_chan_done is cleared before the dma transfer starts,
* callback for rx/tx channel will each increment it by 1.
* Reaching 2 means the whole spi transaction is done.
*/
static void dw_spi_dma_done(void *arg)
{
struct dw_spi *dws = arg;
if (++dws->dma_chan_done != 2)
return;
dw_spi_xfer_done(dws);
}
static int mid_spi_dma_transfer(struct dw_spi *dws, int cs_change)
{
struct dma_async_tx_descriptor *txdesc = NULL, *rxdesc = NULL;
struct dma_chan *txchan, *rxchan;
struct dma_slave_config *txconf, *rxconf;
u16 dma_ctrl = 0;
enum dma_ctrl_flags flag;
struct device *dev = &dws->master->dev;
struct intel_mid_dma_slave *rxs, *txs;
/* 1. setup DMA related registers */
if (cs_change) {
spi_enable_chip(dws, 0);
dw_writew(dws, DW_SPI_DMARDLR, 0xf);
dw_writew(dws, DW_SPI_DMATDLR, 0x10);
if (dws->tx_dma)
dma_ctrl |= 0x2;
if (dws->rx_dma)
dma_ctrl |= 0x1;
dw_writew(dws, DW_SPI_DMACR, dma_ctrl);
spi_enable_chip(dws, 1);
}
dws->dma_chan_done = 0;
txchan = dws->txchan;
rxchan = dws->rxchan;
txs = txchan->private;
rxs = rxchan->private;
txconf = &txs->dma_slave;
rxconf = &rxs->dma_slave;
flag = DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_DEST_UNMAP | DMA_CTRL_ACK;
/* 2. Prepare the TX dma transfer */
txconf->direction = DMA_MEM_TO_DEV;
txconf->dst_addr = dws->dma_addr;
txconf->src_maxburst = LNW_DMA_MSIZE_16;
txconf->dst_maxburst = LNW_DMA_MSIZE_16;
txconf->src_addr_width = dws->dma_width;
txconf->dst_addr_width = dws->dma_width;
txconf->device_fc = false;
txchan->device->device_control(txchan, DMA_SLAVE_CONFIG,
(unsigned long) txconf);
txdesc = txchan->device->device_prep_dma_memcpy
(txchan, /* DMA Channel */
dws->dma_addr, /* DAR */
dws->tx_dma, /* SAR */
dws->len, /* Data Length */
flag);
if (txdesc) {
txdesc->callback = dw_spi_dma_done;
txdesc->callback_param = dws;
} else {
dev_err(dev, "ERROR: prepare txdesc failed\n");
return -EINVAL;
}
/* 3. Prepare the RX dma transfer */
rxconf->direction = DMA_DEV_TO_MEM;
rxconf->src_addr = dws->dma_addr;
rxconf->src_maxburst = LNW_DMA_MSIZE_16;
rxconf->dst_maxburst = LNW_DMA_MSIZE_16;
rxconf->dst_addr_width = dws->dma_width;
rxconf->src_addr_width = dws->dma_width;
rxconf->device_fc = false;
rxchan->device->device_control(rxchan, DMA_SLAVE_CONFIG,
(unsigned long) rxconf);
rxdesc = rxchan->device->device_prep_dma_memcpy
(rxchan, /* DMA Channel */
dws->rx_dma, /* DAR */
dws->dma_addr, /* SAR */
dws->len, /* Data Length */
flag);
if (rxdesc) {
rxdesc->callback = dw_spi_dma_done;
rxdesc->callback_param = dws;
} else {
dev_err(dev, "ERROR: prepare rxdesc failed\n");
return -EINVAL;
}
/* rx must be started before tx due to spi instinct */
rxdesc->tx_submit(rxdesc);
txdesc->tx_submit(txdesc);
return 0;
}
static int mid_spi_dma_suspend(struct dw_spi *dws)
{
struct dma_chan *txchan, *rxchan;
txchan = dws->txchan;
rxchan = dws->rxchan;
txchan->device->device_control(txchan, DMA_TERMINATE_ALL, 0);
rxchan->device->device_control(rxchan, DMA_TERMINATE_ALL, 0);
txchan->device->device_control(txchan, DMA_PAUSE, 0);
rxchan->device->device_control(rxchan, DMA_PAUSE, 0);
return 0;
}
static int mid_spi_dma_resume(struct dw_spi *dws)
{
struct dma_chan *txchan, *rxchan;
txchan = dws->txchan;
rxchan = dws->rxchan;
txchan->device->device_control(txchan, DMA_RESUME, 0);
rxchan->device->device_control(rxchan, DMA_RESUME, 0);
return 0;
}
static struct dw_spi_dma_ops mid_dma_ops = {
.dma_init = mid_spi_dma_init,
.dma_exit = mid_spi_dma_exit,
.dma_transfer = mid_spi_dma_transfer,
.dma_suspend = mid_spi_dma_suspend,
.dma_resume = mid_spi_dma_resume,
};
#endif
/* Some specific info for SPI0 controller on Moorestown */
/* HW info for MRST CLk Control Unit, one 32b reg */
#define MRST_SPI_CLK_BASE 100000000 /* 100m */
#define MRST_CLK_SPI0_REG 0xff11d86c
#define CLK_SPI_BDIV_OFFSET 0
#define CLK_SPI_BDIV_MASK 0x00000007
#define CLK_SPI_CDIV_OFFSET 9
#define CLK_SPI_CDIV_MASK 0x00000e00
#define CLK_SPI_DISABLE_OFFSET 8
int dw_spi_mid_init(struct dw_spi *dws, int bus_num)
{
void __iomem *clk_reg;
u32 clk_cdiv;
clk_reg = ioremap_nocache(MRST_CLK_SPI0_REG + bus_num * 4, 16);
if (!clk_reg)
return -ENOMEM;
/* get SPI controller operating freq info */
clk_cdiv = (readl(clk_reg) & CLK_SPI_CDIV_MASK) >> CLK_SPI_CDIV_OFFSET;
dws->max_freq = MRST_SPI_CLK_BASE / (clk_cdiv + 1);
iounmap(clk_reg);
dws->num_cs = 16;
dws->fifo_len = 40; /* FIFO has 40 words buffer */
#ifdef CONFIG_SPI_DW_MID_DMA
dws->dma_priv = kzalloc(sizeof(struct mid_dma), GFP_KERNEL);
if (!dws->dma_priv)
return -ENOMEM;
dws->dma_ops = &mid_dma_ops;
#endif
return 0;
}