mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
16 KiB
676 lines
16 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
// |
|
// 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 <[email protected]> |
|
// |
|
// Based on previous attempt by: |
|
// Fabio Estevam <[email protected]> |
|
// |
|
// Based on code from U-Boot bootloader by: |
|
// Marek Vasut <[email protected]> |
|
// |
|
// Based on spi-stmp.c, which is: |
|
// Author: Dmitry Pervushin <[email protected]> |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/ioport.h> |
|
#include <linux/of.h> |
|
#include <linux/of_device.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/pinctrl/consumer.h> |
|
#include <linux/regulator/consumer.h> |
|
#include <linux/pm_runtime.h> |
|
#include <linux/module.h> |
|
#include <linux/stmp_device.h> |
|
#include <linux/spi/spi.h> |
|
#include <linux/spi/mxs-spi.h> |
|
#include <trace/events/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 |
|
|
|
/* |
|
* Flags for txrx functions. More efficient that using an argument register for |
|
* each one. |
|
*/ |
|
#define TXRX_WRITE (1<<0) /* This is a write */ |
|
#define TXRX_DEASSERT_CS (1<<1) /* De-assert CS at end of txrx */ |
|
|
|
struct mxs_spi { |
|
struct mxs_ssp ssp; |
|
struct completion c; |
|
unsigned int sck; /* Rate requested (vs actual) */ |
|
}; |
|
|
|
static int mxs_spi_setup_transfer(struct spi_device *dev, |
|
const struct spi_transfer *t) |
|
{ |
|
struct mxs_spi *spi = spi_master_get_devdata(dev->master); |
|
struct mxs_ssp *ssp = &spi->ssp; |
|
const unsigned int hz = min(dev->max_speed_hz, t->speed_hz); |
|
|
|
if (hz == 0) { |
|
dev_err(&dev->dev, "SPI clock rate of zero not allowed\n"); |
|
return -EINVAL; |
|
} |
|
|
|
if (hz != spi->sck) { |
|
mxs_ssp_set_clk_rate(ssp, hz); |
|
/* |
|
* Save requested rate, hz, rather than the actual rate, |
|
* ssp->clk_rate. Otherwise we would set the rate every transfer |
|
* when the actual rate is not quite the same as requested rate. |
|
*/ |
|
spi->sck = hz; |
|
/* |
|
* Perhaps we should return an error if the actual clock is |
|
* nowhere close to what was requested? |
|
*/ |
|
} |
|
|
|
writel(BM_SSP_CTRL0_LOCK_CS, |
|
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); |
|
|
|
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 u32 mxs_spi_cs_to_reg(unsigned cs) |
|
{ |
|
u32 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 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; |
|
u32 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, |
|
unsigned char *buf, int len, |
|
unsigned int flags) |
|
{ |
|
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; |
|
u32 ctrl0; |
|
struct page *vm_page; |
|
struct { |
|
u32 pio[4]; |
|
struct scatterlist sg; |
|
} *dma_xfer; |
|
|
|
if (!len) |
|
return -EINVAL; |
|
|
|
dma_xfer = kcalloc(sgs, sizeof(*dma_xfer), GFP_KERNEL); |
|
if (!dma_xfer) |
|
return -ENOMEM; |
|
|
|
reinit_completion(&spi->c); |
|
|
|
/* Chip select was already programmed into CTRL0 */ |
|
ctrl0 = readl(ssp->base + HW_SSP_CTRL0); |
|
ctrl0 &= ~(BM_SSP_CTRL0_XFER_COUNT | BM_SSP_CTRL0_IGNORE_CRC | |
|
BM_SSP_CTRL0_READ); |
|
ctrl0 |= BM_SSP_CTRL0_DATA_XFER; |
|
|
|
if (!(flags & TXRX_WRITE)) |
|
ctrl0 |= BM_SSP_CTRL0_READ; |
|
|
|
/* Queue the DMA data transfer. */ |
|
for (sg_count = 0; sg_count < sgs; sg_count++) { |
|
/* Prepare the transfer descriptor. */ |
|
min = min(len, desc_len); |
|
|
|
/* |
|
* De-assert CS on last segment if flag is set (i.e., no more |
|
* transfers will follow) |
|
*/ |
|
if ((sg_count + 1 == sgs) && (flags & TXRX_DEASSERT_CS)) |
|
ctrl0 |= BM_SSP_CTRL0_IGNORE_CRC; |
|
|
|
if (ssp->devid == IMX23_SSP) { |
|
ctrl0 &= ~BM_SSP_CTRL0_XFER_COUNT; |
|
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_init_table(&dma_xfer[sg_count].sg, 1); |
|
sg_set_page(&dma_xfer[sg_count].sg, vm_page, |
|
min, offset_in_page(buf)); |
|
} else { |
|
sg_init_one(&dma_xfer[sg_count].sg, buf, min); |
|
} |
|
|
|
ret = dma_map_sg(ssp->dev, &dma_xfer[sg_count].sg, 1, |
|
(flags & TXRX_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, |
|
(flags & TXRX_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); |
|
|
|
if (!wait_for_completion_timeout(&spi->c, |
|
msecs_to_jiffies(SSP_TIMEOUT))) { |
|
dev_err(ssp->dev, "DMA transfer timeout\n"); |
|
ret = -ETIMEDOUT; |
|
dmaengine_terminate_all(ssp->dmach); |
|
goto err_vmalloc; |
|
} |
|
|
|
ret = 0; |
|
|
|
err_vmalloc: |
|
while (--sg_count >= 0) { |
|
err_mapped: |
|
dma_unmap_sg(ssp->dev, &dma_xfer[sg_count].sg, 1, |
|
(flags & TXRX_WRITE) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); |
|
} |
|
|
|
kfree(dma_xfer); |
|
|
|
return ret; |
|
} |
|
|
|
static int mxs_spi_txrx_pio(struct mxs_spi *spi, |
|
unsigned char *buf, int len, |
|
unsigned int flags) |
|
{ |
|
struct mxs_ssp *ssp = &spi->ssp; |
|
|
|
writel(BM_SSP_CTRL0_IGNORE_CRC, |
|
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); |
|
|
|
while (len--) { |
|
if (len == 0 && (flags & TXRX_DEASSERT_CS)) |
|
writel(BM_SSP_CTRL0_IGNORE_CRC, |
|
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); |
|
|
|
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 (flags & TXRX_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 (flags & TXRX_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 (!(flags & TXRX_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; |
|
struct spi_transfer *t; |
|
unsigned int flag; |
|
int status = 0; |
|
|
|
/* Program CS register bits here, it will be used for all transfers. */ |
|
writel(BM_SSP_CTRL0_WAIT_FOR_CMD | BM_SSP_CTRL0_WAIT_FOR_IRQ, |
|
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); |
|
writel(mxs_spi_cs_to_reg(m->spi->chip_select), |
|
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); |
|
|
|
list_for_each_entry(t, &m->transfers, transfer_list) { |
|
|
|
trace_spi_transfer_start(m, t); |
|
|
|
status = mxs_spi_setup_transfer(m->spi, t); |
|
if (status) |
|
break; |
|
|
|
/* De-assert on last transfer, inverted by cs_change flag */ |
|
flag = (&t->transfer_list == m->transfers.prev) ^ t->cs_change ? |
|
TXRX_DEASSERT_CS : 0; |
|
|
|
/* |
|
* 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, |
|
(void *)t->tx_buf, |
|
t->len, flag | TXRX_WRITE); |
|
if (t->rx_buf) |
|
status = mxs_spi_txrx_pio(spi, |
|
t->rx_buf, t->len, |
|
flag); |
|
} 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, |
|
(void *)t->tx_buf, t->len, |
|
flag | TXRX_WRITE); |
|
if (t->rx_buf) |
|
status = mxs_spi_txrx_dma(spi, |
|
t->rx_buf, t->len, |
|
flag); |
|
} |
|
|
|
trace_spi_transfer_stop(m, t); |
|
|
|
if (status) { |
|
stmp_reset_block(ssp->base); |
|
break; |
|
} |
|
|
|
m->actual_length += t->len; |
|
} |
|
|
|
m->status = status; |
|
spi_finalize_current_message(master); |
|
|
|
return status; |
|
} |
|
|
|
static int mxs_spi_runtime_suspend(struct device *dev) |
|
{ |
|
struct spi_master *master = dev_get_drvdata(dev); |
|
struct mxs_spi *spi = spi_master_get_devdata(master); |
|
struct mxs_ssp *ssp = &spi->ssp; |
|
int ret; |
|
|
|
clk_disable_unprepare(ssp->clk); |
|
|
|
ret = pinctrl_pm_select_idle_state(dev); |
|
if (ret) { |
|
int ret2 = clk_prepare_enable(ssp->clk); |
|
|
|
if (ret2) |
|
dev_warn(dev, "Failed to reenable clock after failing pinctrl request (pinctrl: %d, clk: %d)\n", |
|
ret, ret2); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int mxs_spi_runtime_resume(struct device *dev) |
|
{ |
|
struct spi_master *master = dev_get_drvdata(dev); |
|
struct mxs_spi *spi = spi_master_get_devdata(master); |
|
struct mxs_ssp *ssp = &spi->ssp; |
|
int ret; |
|
|
|
ret = pinctrl_pm_select_default_state(dev); |
|
if (ret) |
|
return ret; |
|
|
|
ret = clk_prepare_enable(ssp->clk); |
|
if (ret) |
|
pinctrl_pm_select_idle_state(dev); |
|
|
|
return ret; |
|
} |
|
|
|
static int __maybe_unused mxs_spi_suspend(struct device *dev) |
|
{ |
|
struct spi_master *master = dev_get_drvdata(dev); |
|
int ret; |
|
|
|
ret = spi_master_suspend(master); |
|
if (ret) |
|
return ret; |
|
|
|
if (!pm_runtime_suspended(dev)) |
|
return mxs_spi_runtime_suspend(dev); |
|
else |
|
return 0; |
|
} |
|
|
|
static int __maybe_unused mxs_spi_resume(struct device *dev) |
|
{ |
|
struct spi_master *master = dev_get_drvdata(dev); |
|
int ret; |
|
|
|
if (!pm_runtime_suspended(dev)) |
|
ret = mxs_spi_runtime_resume(dev); |
|
else |
|
ret = 0; |
|
if (ret) |
|
return ret; |
|
|
|
ret = spi_master_resume(master); |
|
if (ret < 0 && !pm_runtime_suspended(dev)) |
|
mxs_spi_runtime_suspend(dev); |
|
|
|
return ret; |
|
} |
|
|
|
static const struct dev_pm_ops mxs_spi_pm = { |
|
SET_RUNTIME_PM_OPS(mxs_spi_runtime_suspend, |
|
mxs_spi_runtime_resume, NULL) |
|
SET_SYSTEM_SLEEP_PM_OPS(mxs_spi_suspend, mxs_spi_resume) |
|
}; |
|
|
|
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 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 clk *clk; |
|
void __iomem *base; |
|
int devid, clk_freq; |
|
int ret = 0, irq_err; |
|
|
|
/* |
|
* 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; |
|
|
|
irq_err = platform_get_irq(pdev, 0); |
|
if (irq_err < 0) |
|
return irq_err; |
|
|
|
base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(base)) |
|
return PTR_ERR(base); |
|
|
|
clk = devm_clk_get(&pdev->dev, NULL); |
|
if (IS_ERR(clk)) |
|
return PTR_ERR(clk); |
|
|
|
devid = (enum mxs_ssp_id) of_id->data; |
|
ret = of_property_read_u32(np, "clock-frequency", |
|
&clk_freq); |
|
if (ret) |
|
clk_freq = clk_freq_default; |
|
|
|
master = spi_alloc_master(&pdev->dev, sizeof(*spi)); |
|
if (!master) |
|
return -ENOMEM; |
|
|
|
platform_set_drvdata(pdev, master); |
|
|
|
master->transfer_one_message = mxs_spi_transfer_one; |
|
master->bits_per_word_mask = SPI_BPW_MASK(8); |
|
master->mode_bits = SPI_CPOL | SPI_CPHA; |
|
master->num_chipselect = 3; |
|
master->dev.of_node = np; |
|
master->flags = SPI_MASTER_HALF_DUPLEX; |
|
master->auto_runtime_pm = true; |
|
|
|
spi = spi_master_get_devdata(master); |
|
ssp = &spi->ssp; |
|
ssp->dev = &pdev->dev; |
|
ssp->clk = clk; |
|
ssp->base = base; |
|
ssp->devid = devid; |
|
|
|
init_completion(&spi->c); |
|
|
|
ret = devm_request_irq(&pdev->dev, irq_err, mxs_ssp_irq_handler, 0, |
|
dev_name(&pdev->dev), ssp); |
|
if (ret) |
|
goto out_master_free; |
|
|
|
ssp->dmach = dma_request_chan(&pdev->dev, "rx-tx"); |
|
if (IS_ERR(ssp->dmach)) { |
|
dev_err(ssp->dev, "Failed to request DMA\n"); |
|
ret = PTR_ERR(ssp->dmach); |
|
goto out_master_free; |
|
} |
|
|
|
pm_runtime_enable(ssp->dev); |
|
if (!pm_runtime_enabled(ssp->dev)) { |
|
ret = mxs_spi_runtime_resume(ssp->dev); |
|
if (ret < 0) { |
|
dev_err(ssp->dev, "runtime resume failed\n"); |
|
goto out_dma_release; |
|
} |
|
} |
|
|
|
ret = pm_runtime_get_sync(ssp->dev); |
|
if (ret < 0) { |
|
pm_runtime_put_noidle(ssp->dev); |
|
dev_err(ssp->dev, "runtime_get_sync failed\n"); |
|
goto out_pm_runtime_disable; |
|
} |
|
|
|
clk_set_rate(ssp->clk, clk_freq); |
|
|
|
ret = stmp_reset_block(ssp->base); |
|
if (ret) |
|
goto out_pm_runtime_put; |
|
|
|
ret = devm_spi_register_master(&pdev->dev, master); |
|
if (ret) { |
|
dev_err(&pdev->dev, "Cannot register SPI master, %d\n", ret); |
|
goto out_pm_runtime_put; |
|
} |
|
|
|
pm_runtime_put(ssp->dev); |
|
|
|
return 0; |
|
|
|
out_pm_runtime_put: |
|
pm_runtime_put(ssp->dev); |
|
out_pm_runtime_disable: |
|
pm_runtime_disable(ssp->dev); |
|
out_dma_release: |
|
dma_release_channel(ssp->dmach); |
|
out_master_free: |
|
spi_master_put(master); |
|
return ret; |
|
} |
|
|
|
static int mxs_spi_remove(struct platform_device *pdev) |
|
{ |
|
struct spi_master *master; |
|
struct mxs_spi *spi; |
|
struct mxs_ssp *ssp; |
|
|
|
master = platform_get_drvdata(pdev); |
|
spi = spi_master_get_devdata(master); |
|
ssp = &spi->ssp; |
|
|
|
pm_runtime_disable(&pdev->dev); |
|
if (!pm_runtime_status_suspended(&pdev->dev)) |
|
mxs_spi_runtime_suspend(&pdev->dev); |
|
|
|
dma_release_channel(ssp->dmach); |
|
|
|
return 0; |
|
} |
|
|
|
static struct platform_driver mxs_spi_driver = { |
|
.probe = mxs_spi_probe, |
|
.remove = mxs_spi_remove, |
|
.driver = { |
|
.name = DRIVER_NAME, |
|
.of_match_table = mxs_spi_dt_ids, |
|
.pm = &mxs_spi_pm, |
|
}, |
|
}; |
|
|
|
module_platform_driver(mxs_spi_driver); |
|
|
|
MODULE_AUTHOR("Marek Vasut <[email protected]>"); |
|
MODULE_DESCRIPTION("MXS SPI master driver"); |
|
MODULE_LICENSE("GPL"); |
|
MODULE_ALIAS("platform:mxs-spi");
|
|
|