diff options
-rw-r--r-- | Documentation/devicetree/bindings/spi/mxs-spi.txt | 22 | ||||
-rw-r--r-- | drivers/clk/mxs/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/mxs/clk-ssp.c | 62 | ||||
-rw-r--r-- | drivers/mmc/host/mxs-mmc.c | 323 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 7 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-mxs.c | 674 | ||||
-rw-r--r-- | include/linux/spi/mxs-spi.h | 150 |
8 files changed, 1023 insertions, 218 deletions
diff --git a/Documentation/devicetree/bindings/spi/mxs-spi.txt b/Documentation/devicetree/bindings/spi/mxs-spi.txt new file mode 100644 index 000000000000..e2e13957c2a4 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/mxs-spi.txt @@ -0,0 +1,22 @@ +* Freescale MX233/MX28 SSP/SPI + +Required properties: +- compatible: Should be "fsl,<soc>-spi", where soc is "imx23" or "imx28" +- reg: Offset and length of the register set for the device +- interrupts: Should contain SSP interrupts (error irq first, dma irq second) +- fsl,ssp-dma-channel: APBX DMA channel for the SSP + +Optional properties: +- clock-frequency : Input clock frequency to the SPI block in Hz. + Default is 160000000 Hz. + +Example: + +ssp0: ssp@80010000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,imx28-spi"; + reg = <0x80010000 0x2000>; + interrupts = <96 82>; + fsl,ssp-dma-channel = <0>; +}; diff --git a/drivers/clk/mxs/Makefile b/drivers/clk/mxs/Makefile index 7bedeec08524..a6a22237e860 100644 --- a/drivers/clk/mxs/Makefile +++ b/drivers/clk/mxs/Makefile @@ -2,7 +2,7 @@ # Makefile for mxs specific clk # -obj-y += clk.o clk-pll.o clk-ref.o clk-div.o clk-frac.o +obj-y += clk.o clk-pll.o clk-ref.o clk-div.o clk-frac.o clk-ssp.o obj-$(CONFIG_SOC_IMX23) += clk-imx23.o obj-$(CONFIG_SOC_IMX28) += clk-imx28.o diff --git a/drivers/clk/mxs/clk-ssp.c b/drivers/clk/mxs/clk-ssp.c new file mode 100644 index 000000000000..af7bdbf9ebd7 --- /dev/null +++ b/drivers/clk/mxs/clk-ssp.c @@ -0,0 +1,62 @@ +/* + * Copyright 2012 DENX Software Engineering, GmbH + * + * Pulled from code: + * Portions copyright (C) 2003 Russell King, PXA MMCI Driver + * Portions copyright (C) 2004-2005 Pierre Ossman, W83L51xD SD/MMC driver + * + * Copyright 2008 Embedded Alley Solutions, Inc. + * Copyright 2009-2011 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/spi/mxs-spi.h> + +void mxs_ssp_set_clk_rate(struct mxs_ssp *ssp, unsigned int rate) +{ + unsigned int ssp_clk, ssp_sck; + u32 clock_divide, clock_rate; + u32 val; + + ssp_clk = clk_get_rate(ssp->clk); + + for (clock_divide = 2; clock_divide <= 254; clock_divide += 2) { + clock_rate = DIV_ROUND_UP(ssp_clk, rate * clock_divide); + clock_rate = (clock_rate > 0) ? clock_rate - 1 : 0; + if (clock_rate <= 255) + break; + } + + if (clock_divide > 254) { + dev_err(ssp->dev, + "%s: cannot set clock to %d\n", __func__, rate); + return; + } + + ssp_sck = ssp_clk / clock_divide / (1 + clock_rate); + + val = readl(ssp->base + HW_SSP_TIMING(ssp)); + val &= ~(BM_SSP_TIMING_CLOCK_DIVIDE | BM_SSP_TIMING_CLOCK_RATE); + val |= BF_SSP(clock_divide, TIMING_CLOCK_DIVIDE); + val |= BF_SSP(clock_rate, TIMING_CLOCK_RATE); + writel(val, ssp->base + HW_SSP_TIMING(ssp)); + + ssp->clk_rate = ssp_sck; + + dev_dbg(ssp->dev, + "%s: clock_divide %d, clock_rate %d, ssp_clk %d, rate_actual %d, rate_requested %d\n", + __func__, clock_divide, clock_rate, ssp_clk, ssp_sck, rate); +} +EXPORT_SYMBOL_GPL(mxs_ssp_set_clk_rate); diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c index ad3fcea1269e..bb4c2bf04d09 100644 --- a/drivers/mmc/host/mxs-mmc.c +++ b/drivers/mmc/host/mxs-mmc.c @@ -41,91 +41,13 @@ #include <linux/gpio.h> #include <linux/regulator/consumer.h> #include <linux/module.h> -#include <linux/fsl/mxs-dma.h> #include <linux/pinctrl/consumer.h> #include <linux/stmp_device.h> #include <linux/mmc/mxs-mmc.h> +#include <linux/spi/mxs-spi.h> #define DRIVER_NAME "mxs-mmc" -/* card detect polling timeout */ -#define MXS_MMC_DETECT_TIMEOUT (HZ/2) - -#define ssp_is_old(host) ((host)->devid == IMX23_MMC) - -/* SSP registers */ -#define HW_SSP_CTRL0 0x000 -#define BM_SSP_CTRL0_RUN (1 << 29) -#define BM_SSP_CTRL0_SDIO_IRQ_CHECK (1 << 28) -#define BM_SSP_CTRL0_IGNORE_CRC (1 << 26) -#define BM_SSP_CTRL0_READ (1 << 25) -#define BM_SSP_CTRL0_DATA_XFER (1 << 24) -#define BP_SSP_CTRL0_BUS_WIDTH (22) -#define BM_SSP_CTRL0_BUS_WIDTH (0x3 << 22) -#define BM_SSP_CTRL0_WAIT_FOR_IRQ (1 << 21) -#define BM_SSP_CTRL0_LONG_RESP (1 << 19) -#define BM_SSP_CTRL0_GET_RESP (1 << 17) -#define BM_SSP_CTRL0_ENABLE (1 << 16) -#define BP_SSP_CTRL0_XFER_COUNT (0) -#define BM_SSP_CTRL0_XFER_COUNT (0xffff) -#define HW_SSP_CMD0 0x010 -#define BM_SSP_CMD0_DBL_DATA_RATE_EN (1 << 25) -#define BM_SSP_CMD0_SLOW_CLKING_EN (1 << 22) -#define BM_SSP_CMD0_CONT_CLKING_EN (1 << 21) -#define BM_SSP_CMD0_APPEND_8CYC (1 << 20) -#define BP_SSP_CMD0_BLOCK_SIZE (16) -#define BM_SSP_CMD0_BLOCK_SIZE (0xf << 16) -#define BP_SSP_CMD0_BLOCK_COUNT (8) -#define BM_SSP_CMD0_BLOCK_COUNT (0xff << 8) -#define BP_SSP_CMD0_CMD (0) -#define BM_SSP_CMD0_CMD (0xff) -#define HW_SSP_CMD1 0x020 -#define HW_SSP_XFER_SIZE 0x030 -#define HW_SSP_BLOCK_SIZE 0x040 -#define BP_SSP_BLOCK_SIZE_BLOCK_COUNT (4) -#define BM_SSP_BLOCK_SIZE_BLOCK_COUNT (0xffffff << 4) -#define BP_SSP_BLOCK_SIZE_BLOCK_SIZE (0) -#define BM_SSP_BLOCK_SIZE_BLOCK_SIZE (0xf) -#define HW_SSP_TIMING(h) (ssp_is_old(h) ? 0x050 : 0x070) -#define BP_SSP_TIMING_TIMEOUT (16) -#define BM_SSP_TIMING_TIMEOUT (0xffff << 16) -#define BP_SSP_TIMING_CLOCK_DIVIDE (8) -#define BM_SSP_TIMING_CLOCK_DIVIDE (0xff << 8) -#define BP_SSP_TIMING_CLOCK_RATE (0) -#define BM_SSP_TIMING_CLOCK_RATE (0xff) -#define HW_SSP_CTRL1(h) (ssp_is_old(h) ? 0x060 : 0x080) -#define BM_SSP_CTRL1_SDIO_IRQ (1 << 31) -#define BM_SSP_CTRL1_SDIO_IRQ_EN (1 << 30) -#define BM_SSP_CTRL1_RESP_ERR_IRQ (1 << 29) -#define BM_SSP_CTRL1_RESP_ERR_IRQ_EN (1 << 28) -#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ (1 << 27) -#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN (1 << 26) -#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ (1 << 25) -#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN (1 << 24) -#define BM_SSP_CTRL1_DATA_CRC_IRQ (1 << 23) -#define BM_SSP_CTRL1_DATA_CRC_IRQ_EN (1 << 22) -#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ (1 << 21) -#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ_EN (1 << 20) -#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ (1 << 17) -#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN (1 << 16) -#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ (1 << 15) -#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ_EN (1 << 14) -#define BM_SSP_CTRL1_DMA_ENABLE (1 << 13) -#define BM_SSP_CTRL1_POLARITY (1 << 9) -#define BP_SSP_CTRL1_WORD_LENGTH (4) -#define BM_SSP_CTRL1_WORD_LENGTH (0xf << 4) -#define BP_SSP_CTRL1_SSP_MODE (0) -#define BM_SSP_CTRL1_SSP_MODE (0xf) -#define HW_SSP_SDRESP0(h) (ssp_is_old(h) ? 0x080 : 0x0a0) -#define HW_SSP_SDRESP1(h) (ssp_is_old(h) ? 0x090 : 0x0b0) -#define HW_SSP_SDRESP2(h) (ssp_is_old(h) ? 0x0a0 : 0x0c0) -#define HW_SSP_SDRESP3(h) (ssp_is_old(h) ? 0x0b0 : 0x0d0) -#define HW_SSP_STATUS(h) (ssp_is_old(h) ? 0x0c0 : 0x100) -#define BM_SSP_STATUS_CARD_DETECT (1 << 28) -#define BM_SSP_STATUS_SDIO_IRQ (1 << 17) - -#define BF_SSP(value, field) (((value) << BP_SSP_##field) & BM_SSP_##field) - #define MXS_MMC_IRQ_BITS (BM_SSP_CTRL1_SDIO_IRQ | \ BM_SSP_CTRL1_RESP_ERR_IRQ | \ BM_SSP_CTRL1_RESP_TIMEOUT_IRQ | \ @@ -135,31 +57,17 @@ BM_SSP_CTRL1_RECV_TIMEOUT_IRQ | \ BM_SSP_CTRL1_FIFO_OVERRUN_IRQ) -#define SSP_PIO_NUM 3 - -enum mxs_mmc_id { - IMX23_MMC, - IMX28_MMC, -}; +/* card detect polling timeout */ +#define MXS_MMC_DETECT_TIMEOUT (HZ/2) struct mxs_mmc_host { + struct mxs_ssp ssp; + struct mmc_host *mmc; struct mmc_request *mrq; struct mmc_command *cmd; struct mmc_data *data; - void __iomem *base; - int dma_channel; - struct clk *clk; - unsigned int clk_rate; - - struct dma_chan *dmach; - struct mxs_dma_data dma_data; - unsigned int dma_dir; - enum dma_transfer_direction slave_dirn; - u32 ssp_pio_words[SSP_PIO_NUM]; - - enum mxs_mmc_id devid; unsigned char bus_width; spinlock_t lock; int sdio_irq_en; @@ -186,16 +94,18 @@ static int mxs_mmc_get_ro(struct mmc_host *mmc) static int mxs_mmc_get_cd(struct mmc_host *mmc) { struct mxs_mmc_host *host = mmc_priv(mmc); + struct mxs_ssp *ssp = &host->ssp; - return !(readl(host->base + HW_SSP_STATUS(host)) & + return !(readl(ssp->base + HW_SSP_STATUS(ssp)) & BM_SSP_STATUS_CARD_DETECT); } static void mxs_mmc_reset(struct mxs_mmc_host *host) { + struct mxs_ssp *ssp = &host->ssp; u32 ctrl0, ctrl1; - stmp_reset_block(host->base); + stmp_reset_block(ssp->base); ctrl0 = BM_SSP_CTRL0_IGNORE_CRC; ctrl1 = BF_SSP(0x3, CTRL1_SSP_MODE) | @@ -211,15 +121,15 @@ static void mxs_mmc_reset(struct mxs_mmc_host *host) writel(BF_SSP(0xffff, TIMING_TIMEOUT) | BF_SSP(2, TIMING_CLOCK_DIVIDE) | BF_SSP(0, TIMING_CLOCK_RATE), - host->base + HW_SSP_TIMING(host)); + ssp->base + HW_SSP_TIMING(ssp)); if (host->sdio_irq_en) { ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK; ctrl1 |= BM_SSP_CTRL1_SDIO_IRQ_EN; } - writel(ctrl0, host->base + HW_SSP_CTRL0); - writel(ctrl1, host->base + HW_SSP_CTRL1(host)); + writel(ctrl0, ssp->base + HW_SSP_CTRL0); + writel(ctrl1, ssp->base + HW_SSP_CTRL1(ssp)); } static void mxs_mmc_start_cmd(struct mxs_mmc_host *host, @@ -230,21 +140,22 @@ static void mxs_mmc_request_done(struct mxs_mmc_host *host) struct mmc_command *cmd = host->cmd; struct mmc_data *data = host->data; struct mmc_request *mrq = host->mrq; + struct mxs_ssp *ssp = &host->ssp; if (mmc_resp_type(cmd) & MMC_RSP_PRESENT) { if (mmc_resp_type(cmd) & MMC_RSP_136) { - cmd->resp[3] = readl(host->base + HW_SSP_SDRESP0(host)); - cmd->resp[2] = readl(host->base + HW_SSP_SDRESP1(host)); - cmd->resp[1] = readl(host->base + HW_SSP_SDRESP2(host)); - cmd->resp[0] = readl(host->base + HW_SSP_SDRESP3(host)); + cmd->resp[3] = readl(ssp->base + HW_SSP_SDRESP0(ssp)); + cmd->resp[2] = readl(ssp->base + HW_SSP_SDRESP1(ssp)); + cmd->resp[1] = readl(ssp->base + HW_SSP_SDRESP2(ssp)); + cmd->resp[0] = readl(ssp->base + HW_SSP_SDRESP3(ssp)); } else { - cmd->resp[0] = readl(host->base + HW_SSP_SDRESP0(host)); + cmd->resp[0] = readl(ssp->base + HW_SSP_SDRESP0(ssp)); } } if (data) { dma_unmap_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, host->dma_dir); + data->sg_len, ssp->dma_dir); /* * If there was an error on any block, we mark all * data blocks as being in error. @@ -277,13 +188,14 @@ static irqreturn_t mxs_mmc_irq_handler(int irq, void *dev_id) struct mxs_mmc_host *host = dev_id; struct mmc_command *cmd = host->cmd; struct mmc_data *data = host->data; + struct mxs_ssp *ssp = &host->ssp; u32 stat; spin_lock(&host->lock); - stat = readl(host->base + HW_SSP_CTRL1(host)); + stat = readl(ssp->base + HW_SSP_CTRL1(ssp)); writel(stat & MXS_MMC_IRQ_BITS, - host->base + HW_SSP_CTRL1(host) + STMP_OFFSET_REG_CLR); + ssp->base + HW_SSP_CTRL1(ssp) + STMP_OFFSET_REG_CLR); spin_unlock(&host->lock); @@ -312,6 +224,7 @@ static irqreturn_t mxs_mmc_irq_handler(int irq, void *dev_id) static struct dma_async_tx_descriptor *mxs_mmc_prep_dma( struct mxs_mmc_host *host, unsigned long flags) { + struct mxs_ssp *ssp = &host->ssp; struct dma_async_tx_descriptor *desc; struct mmc_data *data = host->data; struct scatterlist * sgl; @@ -320,24 +233,24 @@ static struct dma_async_tx_descriptor *mxs_mmc_prep_dma( if (data) { /* data */ dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, host->dma_dir); + data->sg_len, ssp->dma_dir); sgl = data->sg; sg_len = data->sg_len; } else { /* pio */ - sgl = (struct scatterlist *) host->ssp_pio_words; + sgl = (struct scatterlist *) ssp->ssp_pio_words; sg_len = SSP_PIO_NUM; } - desc = dmaengine_prep_slave_sg(host->dmach, - sgl, sg_len, host->slave_dirn, flags); + desc = dmaengine_prep_slave_sg(ssp->dmach, + sgl, sg_len, ssp->slave_dirn, flags); if (desc) { desc->callback = mxs_mmc_dma_irq_callback; desc->callback_param = host; } else { if (data) dma_unmap_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, host->dma_dir); + data->sg_len, ssp->dma_dir); } return desc; @@ -345,6 +258,7 @@ static struct dma_async_tx_descriptor *mxs_mmc_prep_dma( static void mxs_mmc_bc(struct mxs_mmc_host *host) { + struct mxs_ssp *ssp = &host->ssp; struct mmc_command *cmd = host->cmd; struct dma_async_tx_descriptor *desc; u32 ctrl0, cmd0, cmd1; @@ -358,17 +272,17 @@ static void mxs_mmc_bc(struct mxs_mmc_host *host) cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN; } - host->ssp_pio_words[0] = ctrl0; - host->ssp_pio_words[1] = cmd0; - host->ssp_pio_words[2] = cmd1; - host->dma_dir = DMA_NONE; - host->slave_dirn = DMA_TRANS_NONE; + ssp->ssp_pio_words[0] = ctrl0; + ssp->ssp_pio_words[1] = cmd0; + ssp->ssp_pio_words[2] = cmd1; + ssp->dma_dir = DMA_NONE; + ssp->slave_dirn = DMA_TRANS_NONE; desc = mxs_mmc_prep_dma(host, DMA_CTRL_ACK); if (!desc) goto out; dmaengine_submit(desc); - dma_async_issue_pending(host->dmach); + dma_async_issue_pending(ssp->dmach); return; out: @@ -378,6 +292,7 @@ out: static void mxs_mmc_ac(struct mxs_mmc_host *host) { + struct mxs_ssp *ssp = &host->ssp; struct mmc_command *cmd = host->cmd; struct dma_async_tx_descriptor *desc; u32 ignore_crc, get_resp, long_resp; @@ -399,17 +314,17 @@ static void mxs_mmc_ac(struct mxs_mmc_host *host) cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN; } - host->ssp_pio_words[0] = ctrl0; - host->ssp_pio_words[1] = cmd0; - host->ssp_pio_words[2] = cmd1; - host->dma_dir = DMA_NONE; - host->slave_dirn = DMA_TRANS_NONE; + ssp->ssp_pio_words[0] = ctrl0; + ssp->ssp_pio_words[1] = cmd0; + ssp->ssp_pio_words[2] = cmd1; + ssp->dma_dir = DMA_NONE; + ssp->slave_dirn = DMA_TRANS_NONE; desc = mxs_mmc_prep_dma(host, DMA_CTRL_ACK); if (!desc) goto out; dmaengine_submit(desc); - dma_async_issue_pending(host->dmach); + dma_async_issue_pending(ssp->dmach); return; out: @@ -447,6 +362,8 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) unsigned int data_size = 0, log2_blksz; unsigned int blocks = data->blocks; + struct mxs_ssp *ssp = &host->ssp; + u32 ignore_crc, get_resp, long_resp, read; u32 ctrl0, cmd0, cmd1, val; @@ -489,15 +406,15 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) blocks = 1; /* xfer count, block size and count need to be set differently */ - if (ssp_is_old(host)) { + if (ssp_is_old(ssp)) { ctrl0 |= BF_SSP(data_size, CTRL0_XFER_COUNT); cmd0 |= BF_SSP(log2_blksz, CMD0_BLOCK_SIZE) | BF_SSP(blocks - 1, CMD0_BLOCK_COUNT); } else { - writel(data_size, host->base + HW_SSP_XFER_SIZE); + writel(data_size, ssp->base + HW_SSP_XFER_SIZE); writel(BF_SSP(log2_blksz, BLOCK_SIZE_BLOCK_SIZE) | BF_SSP(blocks - 1, BLOCK_SIZE_BLOCK_COUNT), - host->base + HW_SSP_BLOCK_SIZE); + ssp->base + HW_SSP_BLOCK_SIZE); } if ((cmd->opcode == MMC_STOP_TRANSMISSION) || @@ -512,18 +429,18 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) } /* set the timeout count */ - timeout = mxs_ns_to_ssp_ticks(host->clk_rate, data->timeout_ns); - val = readl(host->base + HW_SSP_TIMING(host)); + timeout = mxs_ns_to_ssp_ticks(ssp->clk_rate, data->timeout_ns); + val = readl(ssp->base + HW_SSP_TIMING(ssp)); val &= ~(BM_SSP_TIMING_TIMEOUT); val |= BF_SSP(timeout, TIMING_TIMEOUT); - writel(val, host->base + HW_SSP_TIMING(host)); + writel(val, ssp->base + HW_SSP_TIMING(ssp)); /* pio */ - host->ssp_pio_words[0] = ctrl0; - host->ssp_pio_words[1] = cmd0; - host->ssp_pio_words[2] = cmd1; - host->dma_dir = DMA_NONE; - host->slave_dirn = DMA_TRANS_NONE; + ssp->ssp_pio_words[0] = ctrl0; + ssp->ssp_pio_words[1] = cmd0; + ssp->ssp_pio_words[2] = cmd1; + ssp->dma_dir = DMA_NONE; + ssp->slave_dirn = DMA_TRANS_NONE; desc = mxs_mmc_prep_dma(host, 0); if (!desc) goto out; @@ -531,14 +448,14 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) /* append data sg */ WARN_ON(host->data != NULL); host->data = data; - host->dma_dir = dma_data_dir; - host->slave_dirn = slave_dirn; + ssp->dma_dir = dma_data_dir; + ssp->slave_dirn = slave_dirn; desc = mxs_mmc_prep_dma(host, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc) goto out; dmaengine_submit(desc); - dma_async_issue_pending(host->dmach); + dma_async_issue_pending(ssp->dmach); return; out: dev_warn(mmc_dev(host->mmc), @@ -579,42 +496,6 @@ static void mxs_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) mxs_mmc_start_cmd(host, mrq->cmd); } -static void mxs_mmc_set_clk_rate(struct mxs_mmc_host *host, unsigned int rate) -{ - unsigned int ssp_clk, ssp_sck; - u32 clock_divide, clock_rate; - u32 val; - - ssp_clk = clk_get_rate(host->clk); - - for (clock_divide = 2; clock_divide <= 254; clock_divide += 2) { - clock_rate = DIV_ROUND_UP(ssp_clk, rate * clock_divide); - clock_rate = (clock_rate > 0) ? clock_rate - 1 : 0; - if (clock_rate <= 255) - break; - } - - if (clock_divide > 254) { - dev_err(mmc_dev(host->mmc), - "%s: cannot set clock to %d\n", __func__, rate); - return; - } - - ssp_sck = ssp_clk / clock_divide / (1 + clock_rate); - - val = readl(host->base + HW_SSP_TIMING(host)); - val &= ~(BM_SSP_TIMING_CLOCK_DIVIDE | BM_SSP_TIMING_CLOCK_RATE); - val |= BF_SSP(clock_divide, TIMING_CLOCK_DIVIDE); - val |= BF_SSP(clock_rate, TIMING_CLOCK_RATE); - writel(val, host->base + HW_SSP_TIMING(host)); - - host->clk_rate = ssp_sck; - - dev_dbg(mmc_dev(host->mmc), - "%s: clock_divide %d, clock_rate %d, ssp_clk %d, rate_actual %d, rate_requested %d\n", - __func__, clock_divide, clock_rate, ssp_clk, ssp_sck, rate); -} - static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mxs_mmc_host *host = mmc_priv(mmc); @@ -627,12 +508,13 @@ static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) host->bus_width = 0; if (ios->clock) - mxs_mmc_set_clk_rate(host, ios->clock); + mxs_ssp_set_clk_rate(&host->ssp, ios->clock); } static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) { struct mxs_mmc_host *host = mmc_priv(mmc); + struct mxs_ssp *ssp = &host->ssp; unsigned long flags; spin_lock_irqsave(&host->lock, flags); @@ -641,19 +523,19 @@ static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) if (enable) { writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK, - host->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); writel(BM_SSP_CTRL1_SDIO_IRQ_EN, host->base + HW_SSP_CTRL1(host) + STMP_OFFSET_REG_SET); } else { writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK, - host->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); writel(BM_SSP_CTRL1_SDIO_IRQ_EN, - host->base + HW_SSP_CTRL1(host) + STMP_OFFSET_REG_CLR); + ssp->base + HW_SSP_CTRL1(ssp) + STMP_OFFSET_REG_CLR); } spin_unlock_irqrestore(&host->lock, flags); - if (enable && readl(host->base + HW_SSP_STATUS(host)) & + if (enable && readl(ssp->base + HW_SSP_STATUS(ssp)) & BM_SSP_STATUS_SDIO_IRQ) mmc_signal_sdio_irq(host->mmc); @@ -670,34 +552,35 @@ static const struct mmc_host_ops mxs_mmc_ops = { static bool mxs_mmc_dma_filter(struct dma_chan *chan, void *param) { struct mxs_mmc_host *host = param; + struct mxs_ssp *ssp = &host->ssp; if (!mxs_dma_is_apbh(chan)) return false; - if (chan->chan_id != host->dma_channel) + if (chan->chan_id != ssp->dma_channel) return false; - chan->private = &host->dma_data; + chan->private = &ssp->dma_data; return true; } -static struct platform_device_id mxs_mmc_ids[] = { +static struct platform_device_id mxs_ssp_ids[] = { { .name = "imx23-mmc", - .driver_data = IMX23_MMC, + .driver_data = IMX23_SSP, }, { .name = "imx28-mmc", - .driver_data = IMX28_MMC, + .driver_data = IMX28_SSP, }, { /* sentinel */ } }; -MODULE_DEVICE_TABLE(platform, mxs_mmc_ids); +MODULE_DEVICE_TABLE(platform, mxs_ssp_ids); static const struct of_device_id mxs_mmc_dt_ids[] = { - { .compatible = "fsl,imx23-mmc", .data = (void *) IMX23_MMC, }, - { .compatible = "fsl,imx28-mmc", .data = (void *) IMX28_MMC, }, + { .compatible = "fsl,imx23-mmc", .data = (void *) IMX23_SSP, }, + { .compatible = "fsl,imx28-mmc", .data = (void *) IMX28_SSP, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mxs_mmc_dt_ids); @@ -716,6 +599,7 @@ static int mxs_mmc_probe(struct platform_device *pdev) dma_cap_mask_t mask; struct regulator *reg_vmmc; enum of_gpio_flags flags; + struct mxs_ssp *ssp; iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); @@ -729,28 +613,30 @@ static int mxs_mmc_probe(struct platform_device *pdev) return -ENOMEM; host = mmc_priv(mmc); - host->base = devm_request_and_ioremap(&pdev->dev, iores); - if (!host->base) { + ssp = &host->ssp; + ssp->dev = &pdev->dev; + ssp->base = devm_request_and_ioremap(&pdev->dev, iores); + if (!ssp->base) { ret = -EADDRNOTAVAIL; goto out_mmc_free; } if (np) { - host->devid = (enum mxs_mmc_id) of_id->data; + ssp->devid = (enum mxs_ssp_id) of_id->data; /* * TODO: This is a temporary solution and should be changed * to use generic DMA binding later when the helpers get in. */ ret = of_property_read_u32(np, "fsl,ssp-dma-channel", - &host->dma_channel); + &ssp->dma_channel); if (ret) { dev_err(mmc_dev(host->mmc), "failed to get dma channel\n"); goto out_mmc_free; } } else { - host->devid = pdev->id_entry->driver_data; - host->dma_channel = dmares->start; + ssp->devid = pdev->id_entry->driver_data; + ssp->dma_channel = dmares->start; } host->mmc = mmc; @@ -772,20 +658,20 @@ static int mxs_mmc_probe(struct platform_device *pdev) goto out_mmc_free; } - host->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(host->clk)) { - ret = PTR_ERR(host->clk); + ssp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(ssp->clk)) { + ret = PTR_ERR(ssp->clk); goto out_mmc_free; } - clk_prepare_enable(host->clk); + clk_prepare_enable(ssp->clk); mxs_mmc_reset(host); dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); - host->dma_data.chan_irq = irq_dma; - host->dmach = dma_request_channel(mask, mxs_mmc_dma_filter, host); - if (!host->dmach) { + ssp->dma_data.chan_irq = irq_dma; + ssp->dmach = dma_request_channel(mask, mxs_mmc_dma_filter, host); + if (!ssp->dmach) { dev_err(mmc_dev(host->mmc), "%s: failed to request dma\n", __func__); goto out_clk_put; @@ -822,9 +708,9 @@ static int mxs_mmc_probe(struct platform_device *pdev) mmc->max_segs = 52; mmc->max_blk_size = 1 << 0xf; - mmc->max_blk_count = (ssp_is_old(host)) ? 0xff : 0xffffff; - mmc->max_req_size = (ssp_is_old(host)) ? 0xffff : 0xffffffff; - mmc->max_seg_size = dma_get_max_seg_size(host->dmach->device->dev); + mmc->max_blk_count = (ssp_is_old(ssp)) ? 0xff : 0xffffff; + mmc->max_req_size = (ssp_is_old(ssp)) ? 0xffff : 0xffffffff; + mmc->max_seg_size = dma_get_max_seg_size(ssp->dmach->device->dev); platform_set_drvdata(pdev, mmc); @@ -844,11 +730,11 @@ static int mxs_mmc_probe(struct platform_device *pdev) return 0; out_free_dma: - if (host->dmach) - dma_release_channel(host->dmach); + if (ssp->dmach) + dma_release_channel(ssp->dmach); out_clk_put: - clk_disable_unprepare(host->clk); - clk_put(host->clk); + clk_disable_unprepare(ssp->clk); + clk_put(ssp->clk); out_mmc_free: mmc_free_host(mmc); return ret; @@ -858,16 +744,17 @@ static int mxs_mmc_remove(struct platform_device *pdev) { struct mmc_host *mmc = platform_get_drvdata(pdev); struct mxs_mmc_host *host = mmc_priv(mmc); + struct mxs_ssp *ssp = &host->ssp; mmc_remove_host(mmc); platform_set_drvdata(pdev, NULL); - if (host->dmach) - dma_release_channel(host->dmach); + if (ssp->dmach) + dma_release_channel(ssp->dmach); - clk_disable_unprepare(host->clk); - clk_put(host->clk); + clk_disable_unprepare(ssp->clk); + clk_put(ssp->clk); mmc_free_host(mmc); @@ -879,11 +766,12 @@ static int mxs_mmc_suspend(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); struct mxs_mmc_host *host = mmc_priv(mmc); + struct mxs_ssp *ssp = &host->ssp; int ret = 0; ret = mmc_suspend_host(mmc); - clk_disable_unprepare(host->clk); + clk_disable_unprepare(ssp->clk); return ret; } @@ -892,9 +780,10 @@ static int mxs_mmc_resume(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); struct mxs_mmc_host *host = mmc_priv(mmc); + struct mxs_ssp *ssp = &host->ssp; int ret = 0; - clk_prepare_enable(host->clk); + clk_prepare_enable(ssp->clk); ret = mmc_resume_host(mmc); @@ -910,7 +799,7 @@ static const struct dev_pm_ops mxs_mmc_pm_ops = { static struct platform_driver mxs_mmc_driver = { .probe = mxs_mmc_probe, .remove = mxs_mmc_remove, - .id_table = mxs_mmc_ids, + .id_table = mxs_ssp_ids, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 5f84b5563c2d..6992b30b4e42 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -364,6 +364,13 @@ config SPI_STMP3XXX help SPI driver for Freescale STMP37xx/378x SoC SSP interface +config SPI_MXS + tristate "Freescale MXS SPI controller" + depends on ARCH_MXS + select STMP_DEVICE + help + SPI driver for Freescale MXS devices. + config SPI_TEGRA tristate "Nvidia Tegra SPI controller" depends on ARCH_TEGRA && (TEGRA_SYSTEM_DMA || TEGRA20_APB_DMA) diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3920dcf4c740..3b72d87b3309 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o +obj-$(CONFIG_SPI_MXS) += spi-mxs.o obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o diff --git a/drivers/spi/spi-mxs.c b/drivers/spi/spi-mxs.c new file mode 100644 index 000000000000..edf1360ab09e --- /dev/null +++ b/drivers/spi/spi-mxs.c @@ -0,0 +1,674 @@ +/* + * Freescale MXS SPI master driver + * + * Copyright 2012 DENX Software Engineering, GmbH. + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + * + * Rework and transition to new API by: + * Marek Vasut <marex@denx.de> + * + * Based on previous attempt by: + * Fabio Estevam <fabio.estevam@freescale.com> + * + * Based on code from U-Boot bootloader by: + * Marek Vasut <marex@denx.de> + * + * Based on spi-stmp.c, which is: + * Author: Dmitry Pervushin <dimka@embeddedalley.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/highmem.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/completion.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/stmp_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/mxs-spi.h> + +#define DRIVER_NAME "mxs-spi" + +/* Use 10S timeout for very long transfers, it should suffice. */ +#define SSP_TIMEOUT 10000 + +#define SG_MAXLEN 0xff00 + +struct mxs_spi { + struct mxs_ssp ssp; + struct completion c; +}; + +static int mxs_spi_setup_transfer(struct spi_device *dev, + struct spi_transfer *t) +{ + struct mxs_spi *spi = spi_master_get_devdata(dev->master); + struct mxs_ssp *ssp = &spi->ssp; + uint8_t bits_per_word; + uint32_t hz = 0; + + bits_per_word = dev->bits_per_word; + if (t && t->bits_per_word) + bits_per_word = t->bits_per_word; + + if (bits_per_word != 8) { + dev_err(&dev->dev, "%s, unsupported bits_per_word=%d\n", + __func__, bits_per_word); + return -EINVAL; + } + + hz = dev->max_speed_hz; + if (t && t->speed_hz) + hz = min(hz, t->speed_hz); + if (hz == 0) { + dev_err(&dev->dev, "Cannot continue with zero clock\n"); + return -EINVAL; + } + + mxs_ssp_set_clk_rate(ssp, hz); + + writel(BF_SSP_CTRL1_SSP_MODE(BV_SSP_CTRL1_SSP_MODE__SPI) | + BF_SSP_CTRL1_WORD_LENGTH + (BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS) | + ((dev->mode & SPI_CPOL) ? BM_SSP_CTRL1_POLARITY : 0) | + ((dev->mode & SPI_CPHA) ? BM_SSP_CTRL1_PHASE : 0), + ssp->base + HW_SSP_CTRL1(ssp)); + + writel(0x0, ssp->base + HW_SSP_CMD0); + writel(0x0, ssp->base + HW_SSP_CMD1); + + return 0; +} + +static int mxs_spi_setup(struct spi_device *dev) +{ + int err = 0; + + if (!dev->bits_per_word) + dev->bits_per_word = 8; + + if (dev->mode & ~(SPI_CPOL | SPI_CPHA)) + return -EINVAL; + + err = mxs_spi_setup_transfer(dev, NULL); + if (err) { + dev_err(&dev->dev, + "Failed to setup transfer, error = %d\n", err); + } + + return err; +} + +static uint32_t mxs_spi_cs_to_reg(unsigned cs) +{ + uint32_t select = 0; + + /* + * i.MX28 Datasheet: 17.10.1: HW_SSP_CTRL0 + * + * The bits BM_SSP_CTRL0_WAIT_FOR_CMD and BM_SSP_CTRL0_WAIT_FOR_IRQ + * in HW_SSP_CTRL0 register do have multiple usage, please refer to + * the datasheet for further details. In SPI mode, they are used to + * toggle the chip-select lines (nCS pins). + */ + if (cs & 1) + select |= BM_SSP_CTRL0_WAIT_FOR_CMD; + if (cs & 2) + select |= BM_SSP_CTRL0_WAIT_FOR_IRQ; + + return select; +} + +static void mxs_spi_set_cs(struct mxs_spi *spi, unsigned cs) +{ + const uint32_t mask = + BM_SSP_CTRL0_WAIT_FOR_CMD | BM_SSP_CTRL0_WAIT_FOR_IRQ; + uint32_t select; + struct mxs_ssp *ssp = &spi->ssp; + + writel(mask, ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); + select = mxs_spi_cs_to_reg(cs); + writel(select, ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); +} + +static inline void mxs_spi_enable(struct mxs_spi *spi) +{ + struct mxs_ssp *ssp = &spi->ssp; + + writel(BM_SSP_CTRL0_LOCK_CS, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); + writel(BM_SSP_CTRL0_IGNORE_CRC, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); +} + +static inline void mxs_spi_disable(struct mxs_spi *spi) +{ + struct mxs_ssp *ssp = &spi->ssp; + + writel(BM_SSP_CTRL0_LOCK_CS, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); + writel(BM_SSP_CTRL0_IGNORE_CRC, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); +} + +static int mxs_ssp_wait(struct mxs_spi *spi, int offset, int mask, bool set) +{ + const unsigned long timeout = jiffies + msecs_to_jiffies(SSP_TIMEOUT); + struct mxs_ssp *ssp = &spi->ssp; + uint32_t reg; + + do { + reg = readl_relaxed(ssp->base + offset); + + if (!set) + reg = ~reg; + + reg &= mask; + + if (reg == mask) + return 0; + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +static void mxs_ssp_dma_irq_callback(void *param) +{ + struct mxs_spi *spi = param; + complete(&spi->c); +} + +static irqreturn_t mxs_ssp_irq_handler(int irq, void *dev_id) +{ + struct mxs_ssp *ssp = dev_id; + dev_err(ssp->dev, "%s[%i] CTRL1=%08x STATUS=%08x\n", + __func__, __LINE__, + readl(ssp->base + HW_SSP_CTRL1(ssp)), + readl(ssp->base + HW_SSP_STATUS(ssp))); + return IRQ_HANDLED; +} + +static int mxs_spi_txrx_dma(struct mxs_spi *spi, int cs, + unsigned char *buf, int len, + int *first, int *last, int write) +{ + struct mxs_ssp *ssp = &spi->ssp; + struct dma_async_tx_descriptor *desc = NULL; + const bool vmalloced_buf = is_vmalloc_addr(buf); + const int desc_len = vmalloced_buf ? PAGE_SIZE : SG_MAXLEN; + const int sgs = DIV_ROUND_UP(len, desc_len); + int sg_count; + int min, ret; + uint32_t ctrl0; + struct page *vm_page; + void *sg_buf; + struct { + uint32_t pio[4]; + struct scatterlist sg; + } *dma_xfer; + + if (!len) + return -EINVAL; + + dma_xfer = kzalloc(sizeof(*dma_xfer) * sgs, GFP_KERNEL); + if (!dma_xfer) + return -ENOMEM; + + INIT_COMPLETION(spi->c); + + ctrl0 = readl(ssp->base + HW_SSP_CTRL0); + ctrl0 |= BM_SSP_CTRL0_DATA_XFER | mxs_spi_cs_to_reg(cs); + + if (*first) + ctrl0 |= BM_SSP_CTRL0_LOCK_CS; + if (!write) + ctrl0 |= BM_SSP_CTRL0_READ; + + /* Queue the DMA data transfer. */ + for (sg_count = 0; sg_count < sgs; sg_count++) { + min = min(len, desc_len); + + /* Prepare the transfer descriptor. */ + if ((sg_count + 1 == sgs) && *last) + ctrl0 |= BM_SSP_CTRL0_IGNORE_CRC; + + if (ssp->devid == IMX23_SSP) + ctrl0 |= min; + + dma_xfer[sg_count].pio[0] = ctrl0; + dma_xfer[sg_count].pio[3] = min; + + if (vmalloced_buf) { + vm_page = vmalloc_to_page(buf); + if (!vm_page) { + ret = -ENOMEM; + goto err_vmalloc; + } + sg_buf = page_address(vm_page) + + ((size_t)buf & ~PAGE_MASK); + } else { + sg_buf = buf; + } + + sg_init_one(&dma_xfer[sg_count].sg, sg_buf, min); + ret = dma_map_sg(ssp->dev, &dma_xfer[sg_count].sg, 1, + write ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + len -= min; + buf += min; + + /* Queue the PIO register write transfer. */ + desc = dmaengine_prep_slave_sg(ssp->dmach, + (struct scatterlist *)dma_xfer[sg_count].pio, + (ssp->devid == IMX23_SSP) ? 1 : 4, + DMA_TRANS_NONE, + sg_count ? DMA_PREP_INTERRUPT : 0); + if (!desc) { + dev_err(ssp->dev, + "Failed to get PIO reg. write descriptor.\n"); + ret = -EINVAL; + goto err_mapped; + } + + desc = dmaengine_prep_slave_sg(ssp->dmach, + &dma_xfer[sg_count].sg, 1, + write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!desc) { + dev_err(ssp->dev, + "Failed to get DMA data write descriptor.\n"); + ret = -EINVAL; + goto err_mapped; + } + } + + /* + * The last descriptor must have this callback, + * to finish the DMA transaction. + */ + desc->callback = mxs_ssp_dma_irq_callback; + desc->callback_param = spi; + + /* Start the transfer. */ + dmaengine_submit(desc); + dma_async_issue_pending(ssp->dmach); + + ret = wait_for_completion_timeout(&spi->c, + msecs_to_jiffies(SSP_TIMEOUT)); + if (!ret) { + dev_err(ssp->dev, "DMA transfer timeout\n"); + ret = -ETIMEDOUT; + goto err_vmalloc; + } + + ret = 0; + +err_vmalloc: + while (--sg_count >= 0) { +err_mapped: + dma_unmap_sg(ssp->dev, &dma_xfer[sg_count].sg, 1, + write ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + } + + kfree(dma_xfer); + + return ret; +} + +static int mxs_spi_txrx_pio(struct mxs_spi *spi, int cs, + unsigned char *buf, int len, + int *first, int *last, int write) +{ + struct mxs_ssp *ssp = &spi->ssp; + + if (*first) + mxs_spi_enable(spi); + + mxs_spi_set_cs(spi, cs); + + while (len--) { + if (*last && len == 0) + mxs_spi_disable(spi); + + if (ssp->devid == IMX23_SSP) { + writel(BM_SSP_CTRL0_XFER_COUNT, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); + writel(1, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); + } else { + writel(1, ssp->base + HW_SSP_XFER_SIZE); + } + + if (write) + writel(BM_SSP_CTRL0_READ, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); + else + writel(BM_SSP_CTRL0_READ, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); + + writel(BM_SSP_CTRL0_RUN, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); + + if (mxs_ssp_wait(spi, HW_SSP_CTRL0, BM_SSP_CTRL0_RUN, 1)) + return -ETIMEDOUT; + + if (write) + writel(*buf, ssp->base + HW_SSP_DATA(ssp)); + + writel(BM_SSP_CTRL0_DATA_XFER, + ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); + + if (!write) { + if (mxs_ssp_wait(spi, HW_SSP_STATUS(ssp), + BM_SSP_STATUS_FIFO_EMPTY, 0)) + return -ETIMEDOUT; + + *buf = (readl(ssp->base + HW_SSP_DATA(ssp)) & 0xff); + } + + if (mxs_ssp_wait(spi, HW_SSP_CTRL0, BM_SSP_CTRL0_RUN, 0)) + return -ETIMEDOUT; + + buf++; + } + + if (len <= 0) + return 0; + + return -ETIMEDOUT; +} + +static int mxs_spi_transfer_one(struct spi_master *master, + struct spi_message *m) +{ + struct mxs_spi *spi = spi_master_get_devdata(master); + struct mxs_ssp *ssp = &spi->ssp; + int first, last; + struct spi_transfer *t, *tmp_t; + int status = 0; + int cs; + + first = last = 0; + + cs = m->spi->chip_select; + + list_for_each_entry_safe(t, tmp_t, &m->transfers, transfer_list) { + + status = mxs_spi_setup_transfer(m->spi, t); + if (status) + break; + + if (&t->transfer_list == m->transfers.next) + first = 1; + if (&t->transfer_list == m->transfers.prev) + last = 1; + if ((t->rx_buf && t->tx_buf) || (t->rx_dma && t->tx_dma)) { + dev_err(ssp->dev, + "Cannot send and receive simultaneously\n"); + status = -EINVAL; + break; + } + + /* + * Small blocks can be transfered via PIO. + * Measured by empiric means: + * + * dd if=/dev/mtdblock0 of=/dev/null bs=1024k count=1 + * + * DMA only: 2.164808 seconds, 473.0KB/s + * Combined: 1.676276 seconds, 610.9KB/s + */ + if (t->len < 32) { + writel(BM_SSP_CTRL1_DMA_ENABLE, + ssp->base + HW_SSP_CTRL1(ssp) + + STMP_OFFSET_REG_CLR); + + if (t->tx_buf) + status = mxs_spi_txrx_pio(spi, cs, + (void *)t->tx_buf, + t->len, &first, &last, 1); + if (t->rx_buf) + status = mxs_spi_txrx_pio(spi, cs, + t->rx_buf, t->len, + &first, &last, 0); + } else { + writel(BM_SSP_CTRL1_DMA_ENABLE, + ssp->base + HW_SSP_CTRL1(ssp) + + STMP_OFFSET_REG_SET); + + if (t->tx_buf) + status = mxs_spi_txrx_dma(spi, cs, + (void *)t->tx_buf, t->len, + &first, &last, 1); + if (t->rx_buf) + status = mxs_spi_txrx_dma(spi, cs, + t->rx_buf, t->len, + &first, &last, 0); + } + + if (status) { + stmp_reset_block(ssp->base); + break; + } + + m->actual_length += t->len; + first = last = 0; + } + + m->status = 0; + spi_finalize_current_message(master); + + return status; +} + +static bool mxs_ssp_dma_filter(struct dma_chan *chan, void *param) +{ + struct mxs_ssp *ssp = param; + + if (!mxs_dma_is_apbh(chan)) + return false; + + if (chan->chan_id != ssp->dma_channel) + return false; + + chan->private = &ssp->dma_data; + + return true; +} + +static const struct of_device_id mxs_spi_dt_ids[] = { + { .compatible = "fsl,imx23-spi", .data = (void *) IMX23_SSP, }, + { .compatible = "fsl,imx28-spi", .data = (void *) IMX28_SSP, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_spi_dt_ids); + +static int __devinit mxs_spi_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(mxs_spi_dt_ids, &pdev->dev); + struct device_node *np = pdev->dev.of_node; + struct spi_master *master; + struct mxs_spi *spi; + struct mxs_ssp *ssp; + struct resource *iores, *dmares; + struct pinctrl *pinctrl; + struct clk *clk; + void __iomem *base; + int devid, dma_channel, clk_freq; + int ret = 0, irq_err, irq_dma; + dma_cap_mask_t mask; + + /* + * Default clock speed for the SPI core. 160MHz seems to + * work reasonably well with most SPI flashes, so use this + * as a default. Override with "clock-frequency" DT prop. + */ + const int clk_freq_default = 160000000; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq_err = platform_get_irq(pdev, 0); + irq_dma = platform_get_irq(pdev, 1); + if (!iores || irq_err < 0 || irq_dma < 0) + return -EINVAL; + + base = devm_request_and_ioremap(&pdev->dev, iores); + if (!base) + return -EADDRNOTAVAIL; + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + return PTR_ERR(pinctrl); + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + if (np) { + devid = (enum mxs_ssp_id) of_id->data; + /* + * TODO: This is a temporary solution and should be changed + * to use generic DMA binding later when the helpers get in. + */ + ret = of_property_read_u32(np, "fsl,ssp-dma-channel", + &dma_channel); + if (ret) { + dev_err(&pdev->dev, + "Failed to get DMA channel\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "clock-frequency", + &clk_freq); + if (ret) + clk_freq = clk_freq_default; + } else { + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmares) + return -EINVAL; + devid = pdev->id_entry->driver_data; + dma_channel = dmares->start; + clk_freq = clk_freq_default; + } + + master = spi_alloc_master(&pdev->dev, sizeof(*spi)); + if (!master) + return -ENOMEM; + + master->transfer_one_message = mxs_spi_transfer_one; + master->setup = mxs_spi_setup; + master->mode_bits = SPI_CPOL | SPI_CPHA; + master->num_chipselect = 3; + master->dev.of_node = np; + master->flags = SPI_MASTER_HALF_DUPLEX; + + spi = spi_master_get_devdata(master); + ssp = &spi->ssp; + ssp->dev = &pdev->dev; + ssp->clk = clk; + ssp->base = base; + ssp->devid = devid; + ssp->dma_channel = dma_channel; + + init_completion(&spi->c); + + ret = devm_request_irq(&pdev->dev, irq_err, mxs_ssp_irq_handler, 0, + DRIVER_NAME, ssp); + if (ret) + goto out_master_free; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + ssp->dma_data.chan_irq = irq_dma; + ssp->dmach = dma_request_channel(mask, mxs_ssp_dma_filter, ssp); + if (!ssp->dmach) { + dev_err(ssp->dev, "Failed to request DMA\n"); + goto out_master_free; + } + + clk_prepare_enable(ssp->clk); + clk_set_rate(ssp->clk, clk_freq); + ssp->clk_rate = clk_get_rate(ssp->clk) / 1000; + + stmp_reset_block(ssp->base); + + platform_set_drvdata(pdev, master); + + ret = spi_register_master(master); + if (ret) { + dev_err(&pdev->dev, "Cannot register SPI master, %d\n", ret); + goto out_free_dma; + } + + return 0; + +out_free_dma: + dma_release_channel(ssp->dmach); + clk_disable_unprepare(ssp->clk); +out_master_free: + spi_master_put(master); + return ret; +} + +static int __devexit mxs_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master; + struct mxs_spi *spi; + struct mxs_ssp *ssp; + + master = spi_master_get(platform_get_drvdata(pdev)); + spi = spi_master_get_devdata(master); + ssp = &spi->ssp; + + spi_unregister_master(master); + + dma_release_channel(ssp->dmach); + + clk_disable_unprepare(ssp->clk); + + spi_master_put(master); + + return 0; +} + +static struct platform_driver mxs_spi_driver = { + .probe = mxs_spi_probe, + .remove = __devexit_p(mxs_spi_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = mxs_spi_dt_ids, + }, +}; + +module_platform_driver(mxs_spi_driver); + +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("MXS SPI master driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mxs-spi"); diff --git a/include/linux/spi/mxs-spi.h b/include/linux/spi/mxs-spi.h new file mode 100644 index 000000000000..61ae1306db23 --- /dev/null +++ b/include/linux/spi/mxs-spi.h @@ -0,0 +1,150 @@ +/* + * include/linux/spi/mxs-spi.h + * + * Freescale i.MX233/i.MX28 SPI controller register definition + * + * Copyright 2008 Embedded Alley Solutions, Inc. + * Copyright 2009-2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __LINUX_SPI_MXS_SPI_H__ +#define __LINUX_SPI_MXS_SPI_H__ + +#include <linux/fsl/mxs-dma.h> + +#define ssp_is_old(host) ((host)->devid == IMX23_SSP) + +/* SSP registers */ +#define HW_SSP_CTRL0 0x000 +#define BM_SSP_CTRL0_RUN (1 << 29) +#define BM_SSP_CTRL0_SDIO_IRQ_CHECK (1 << 28) +#define BM_SSP_CTRL0_LOCK_CS (1 << 27) +#define BM_SSP_CTRL0_IGNORE_CRC (1 << 26) +#define BM_SSP_CTRL0_READ (1 << 25) +#define BM_SSP_CTRL0_DATA_XFER (1 << 24) +#define BP_SSP_CTRL0_BUS_WIDTH 22 +#define BM_SSP_CTRL0_BUS_WIDTH (0x3 << 22) +#define BM_SSP_CTRL0_WAIT_FOR_IRQ (1 << 21) +#define BM_SSP_CTRL0_WAIT_FOR_CMD (1 << 20) +#define BM_SSP_CTRL0_LONG_RESP (1 << 19) +#define BM_SSP_CTRL0_GET_RESP (1 << 17) +#define BM_SSP_CTRL0_ENABLE (1 << 16) +#define BP_SSP_CTRL0_XFER_COUNT 0 +#define BM_SSP_CTRL0_XFER_COUNT 0xffff +#define HW_SSP_CMD0 0x010 +#define BM_SSP_CMD0_DBL_DATA_RATE_EN (1 << 25) +#define BM_SSP_CMD0_SLOW_CLKING_EN (1 << 22) +#define BM_SSP_CMD0_CONT_CLKING_EN (1 << 21) +#define BM_SSP_CMD0_APPEND_8CYC (1 << 20) +#define BP_SSP_CMD0_BLOCK_SIZE 16 +#define BM_SSP_CMD0_BLOCK_SIZE (0xf << 16) +#define BP_SSP_CMD0_BLOCK_COUNT 8 +#define BM_SSP_CMD0_BLOCK_COUNT (0xff << 8) +#define BP_SSP_CMD0_CMD 0 +#define BM_SSP_CMD0_CMD 0xff +#define HW_SSP_CMD1 0x020 +#define HW_SSP_XFER_SIZE 0x030 +#define HW_SSP_BLOCK_SIZE 0x040 +#define BP_SSP_BLOCK_SIZE_BLOCK_COUNT 4 +#define BM_SSP_BLOCK_SIZE_BLOCK_COUNT (0xffffff << 4) +#define BP_SSP_BLOCK_SIZE_BLOCK_SIZE 0 +#define BM_SSP_BLOCK_SIZE_BLOCK_SIZE 0xf +#define HW_SSP_TIMING(h) (ssp_is_old(h) ? 0x050 : 0x070) +#define BP_SSP_TIMING_TIMEOUT 16 +#define BM_SSP_TIMING_TIMEOUT (0xffff << 16) +#define BP_SSP_TIMING_CLOCK_DIVIDE 8 +#define BM_SSP_TIMING_CLOCK_DIVIDE (0xff << 8) +#define BF_SSP_TIMING_CLOCK_DIVIDE(v) \ + (((v) << 8) & BM_SSP_TIMING_CLOCK_DIVIDE) +#define BP_SSP_TIMING_CLOCK_RATE 0 +#define BM_SSP_TIMING_CLOCK_RATE 0xff +#define BF_SSP_TIMING_CLOCK_RATE(v) \ + (((v) << 0) & BM_SSP_TIMING_CLOCK_RATE) +#define HW_SSP_CTRL1(h) (ssp_is_old(h) ? 0x060 : 0x080) +#define BM_SSP_CTRL1_SDIO_IRQ (1 << 31) +#define BM_SSP_CTRL1_SDIO_IRQ_EN (1 << 30) +#define BM_SSP_CTRL1_RESP_ERR_IRQ (1 << 29) +#define BM_SSP_CTRL1_RESP_ERR_IRQ_EN (1 << 28) +#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ (1 << 27) +#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN (1 << 26) +#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ (1 << 25) +#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN (1 << 24) +#define BM_SSP_CTRL1_DATA_CRC_IRQ (1 << 23) +#define BM_SSP_CTRL1_DATA_CRC_IRQ_EN (1 << 22) +#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ (1 << 21) +#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ_EN (1 << 20) +#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ (1 << 17) +#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN (1 << 16) +#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ (1 << 15) +#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ_EN (1 << 14) +#define BM_SSP_CTRL1_DMA_ENABLE (1 << 13) +#define BM_SSP_CTRL1_PHASE (1 << 10) +#define BM_SSP_CTRL1_POLARITY (1 << 9) +#define BP_SSP_CTRL1_WORD_LENGTH 4 +#define BM_SSP_CTRL1_WORD_LENGTH (0xf << 4) +#define BF_SSP_CTRL1_WORD_LENGTH(v) \ + (((v) << 4) & BM_SSP_CTRL1_WORD_LENGTH) +#define BV_SSP_CTRL1_WORD_LENGTH__FOUR_BITS 0x3 +#define BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS 0x7 +#define BV_SSP_CTRL1_WORD_LENGTH__SIXTEEN_BITS 0xF +#define BP_SSP_CTRL1_SSP_MODE 0 +#define BM_SSP_CTRL1_SSP_MODE 0xf +#define BF_SSP_CTRL1_SSP_MODE(v) \ + (((v) << 0) & BM_SSP_CTRL1_SSP_MODE) +#define BV_SSP_CTRL1_SSP_MODE__SPI 0x0 +#define BV_SSP_CTRL1_SSP_MODE__SSI 0x1 +#define BV_SSP_CTRL1_SSP_MODE__SD_MMC 0x3 +#define BV_SSP_CTRL1_SSP_MODE__MS 0x4 + +#define HW_SSP_DATA(h) (ssp_is_old(h) ? 0x070 : 0x090) + +#define HW_SSP_SDRESP0(h) (ssp_is_old(h) ? 0x080 : 0x0a0) +#define HW_SSP_SDRESP1(h) (ssp_is_old(h) ? 0x090 : 0x0b0) +#define HW_SSP_SDRESP2(h) (ssp_is_old(h) ? 0x0a0 : 0x0c0) +#define HW_SSP_SDRESP3(h) (ssp_is_old(h) ? 0x0b0 : 0x0d0) +#define HW_SSP_STATUS(h) (ssp_is_old(h) ? 0x0c0 : 0x100) +#define BM_SSP_STATUS_CARD_DETECT (1 << 28) +#define BM_SSP_STATUS_SDIO_IRQ (1 << 17) +#define BM_SSP_STATUS_FIFO_EMPTY (1 << 5) + +#define BF_SSP(value, field) (((value) << BP_SSP_##field) & BM_SSP_##field) + +#define SSP_PIO_NUM 3 + +enum mxs_ssp_id { + IMX23_SSP, + IMX28_SSP, +}; + +struct mxs_ssp { + struct device *dev; + void __iomem *base; + struct clk *clk; + unsigned int clk_rate; + enum mxs_ssp_id devid; + + int dma_channel; + struct dma_chan *dmach; + struct mxs_dma_data dma_data; + unsigned int dma_dir; + enum dma_transfer_direction slave_dirn; + u32 ssp_pio_words[SSP_PIO_NUM]; +}; + +void mxs_ssp_set_clk_rate(struct mxs_ssp *ssp, unsigned int rate); + +#endif /* __LINUX_SPI_MXS_SPI_H__ */ |