mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
ca8b19d61e
The Designware SPI driver uses devm_spi_register_controller() on bind.
As a consequence, on unbind, __device_release_driver() first invokes
dw_spi_remove_host() before unregistering the SPI controller via
devres_release_all().
This order is incorrect: dw_spi_remove_host() shuts down the chip,
rendering the SPI bus inaccessible even though the SPI controller is
still registered. When the SPI controller is subsequently unregistered,
it unbinds all its slave devices. Because their drivers cannot access
the SPI bus, e.g. to quiesce interrupts, the slave devices may be left
in an improper state.
As a rule, devm_spi_register_controller() must not be used if the
->remove() hook performs teardown steps which shall be performed after
unregistering the controller and specifically after unbinding of slaves.
Fix by reverting to the non-devm variant of spi_register_controller().
An alternative approach would be to use device-managed functions for all
steps in dw_spi_remove_host(), e.g. by calling devm_add_action_or_reset()
on probe. However that approach would add more LoC to the driver and
it wouldn't lend itself as well to backporting to stable.
Fixes: 04f421e7b0
("spi: dw: use managed resources")
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: stable@vger.kernel.org # v3.14+
Cc: Baruch Siach <baruch@tkos.co.il>
Link: https://lore.kernel.org/r/3fff8cb8ae44a9893840d0688be15bb88c090a14.1590408496.git.lukas@wunner.de
Signed-off-by: Mark Brown <broonie@kernel.org>
587 lines
15 KiB
C
587 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Designware SPI core controller driver (refer pxa2xx_spi.c)
|
|
*
|
|
* Copyright (c) 2009, Intel Corporation.
|
|
*/
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#include "spi-dw.h"
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#include <linux/debugfs.h>
|
|
#endif
|
|
|
|
/* Slave spi_dev related */
|
|
struct chip_data {
|
|
u8 tmode; /* TR/TO/RO/EEPROM */
|
|
u8 type; /* SPI/SSP/MicroWire */
|
|
|
|
u8 poll_mode; /* 1 means use poll mode */
|
|
|
|
u16 clk_div; /* baud rate divider */
|
|
u32 speed_hz; /* baud rate */
|
|
void (*cs_control)(u32 command);
|
|
};
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#define SPI_REGS_BUFSIZE 1024
|
|
static ssize_t dw_spi_show_regs(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct dw_spi *dws = file->private_data;
|
|
char *buf;
|
|
u32 len = 0;
|
|
ssize_t ret;
|
|
|
|
buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL);
|
|
if (!buf)
|
|
return 0;
|
|
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"%s registers:\n", dev_name(&dws->master->dev));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"=================================\n");
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"CTRL0: \t\t0x%08x\n", dw_readl(dws, DW_SPI_CTRL0));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"CTRL1: \t\t0x%08x\n", dw_readl(dws, DW_SPI_CTRL1));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"SSIENR: \t0x%08x\n", dw_readl(dws, DW_SPI_SSIENR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"SER: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SER));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"BAUDR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_BAUDR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"TXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_TXFLTR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"RXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_RXFLTR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"TXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_TXFLR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"RXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_RXFLR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"SR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"IMR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_IMR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"ISR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_ISR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"DMACR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_DMACR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"DMATDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMATDLR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"DMARDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMARDLR));
|
|
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
|
|
"=================================\n");
|
|
|
|
ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations dw_spi_regs_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
.read = dw_spi_show_regs,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static int dw_spi_debugfs_init(struct dw_spi *dws)
|
|
{
|
|
char name[32];
|
|
|
|
snprintf(name, 32, "dw_spi%d", dws->master->bus_num);
|
|
dws->debugfs = debugfs_create_dir(name, NULL);
|
|
if (!dws->debugfs)
|
|
return -ENOMEM;
|
|
|
|
debugfs_create_file("registers", S_IFREG | S_IRUGO,
|
|
dws->debugfs, (void *)dws, &dw_spi_regs_ops);
|
|
return 0;
|
|
}
|
|
|
|
static void dw_spi_debugfs_remove(struct dw_spi *dws)
|
|
{
|
|
debugfs_remove_recursive(dws->debugfs);
|
|
}
|
|
|
|
#else
|
|
static inline int dw_spi_debugfs_init(struct dw_spi *dws)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void dw_spi_debugfs_remove(struct dw_spi *dws)
|
|
{
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
void dw_spi_set_cs(struct spi_device *spi, bool enable)
|
|
{
|
|
struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
|
|
struct chip_data *chip = spi_get_ctldata(spi);
|
|
|
|
/* Chip select logic is inverted from spi_set_cs() */
|
|
if (chip && chip->cs_control)
|
|
chip->cs_control(!enable);
|
|
|
|
if (!enable)
|
|
dw_writel(dws, DW_SPI_SER, BIT(spi->chip_select));
|
|
else if (dws->cs_override)
|
|
dw_writel(dws, DW_SPI_SER, 0);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_spi_set_cs);
|
|
|
|
/* Return the max entries we can fill into tx fifo */
|
|
static inline u32 tx_max(struct dw_spi *dws)
|
|
{
|
|
u32 tx_left, tx_room, rxtx_gap;
|
|
|
|
tx_left = (dws->tx_end - dws->tx) / dws->n_bytes;
|
|
tx_room = dws->fifo_len - dw_readl(dws, DW_SPI_TXFLR);
|
|
|
|
/*
|
|
* Another concern is about the tx/rx mismatch, we
|
|
* though to use (dws->fifo_len - rxflr - txflr) as
|
|
* one maximum value for tx, but it doesn't cover the
|
|
* data which is out of tx/rx fifo and inside the
|
|
* shift registers. So a control from sw point of
|
|
* view is taken.
|
|
*/
|
|
rxtx_gap = ((dws->rx_end - dws->rx) - (dws->tx_end - dws->tx))
|
|
/ dws->n_bytes;
|
|
|
|
return min3(tx_left, tx_room, (u32) (dws->fifo_len - rxtx_gap));
|
|
}
|
|
|
|
/* Return the max entries we should read out of rx fifo */
|
|
static inline u32 rx_max(struct dw_spi *dws)
|
|
{
|
|
u32 rx_left = (dws->rx_end - dws->rx) / dws->n_bytes;
|
|
|
|
return min_t(u32, rx_left, dw_readl(dws, DW_SPI_RXFLR));
|
|
}
|
|
|
|
static void dw_writer(struct dw_spi *dws)
|
|
{
|
|
u32 max;
|
|
u16 txw = 0;
|
|
|
|
spin_lock(&dws->buf_lock);
|
|
max = tx_max(dws);
|
|
while (max--) {
|
|
/* Set the tx word if the transfer's original "tx" is not null */
|
|
if (dws->tx_end - dws->len) {
|
|
if (dws->n_bytes == 1)
|
|
txw = *(u8 *)(dws->tx);
|
|
else
|
|
txw = *(u16 *)(dws->tx);
|
|
}
|
|
dw_write_io_reg(dws, DW_SPI_DR, txw);
|
|
dws->tx += dws->n_bytes;
|
|
}
|
|
spin_unlock(&dws->buf_lock);
|
|
}
|
|
|
|
static void dw_reader(struct dw_spi *dws)
|
|
{
|
|
u32 max;
|
|
u16 rxw;
|
|
|
|
spin_lock(&dws->buf_lock);
|
|
max = rx_max(dws);
|
|
while (max--) {
|
|
rxw = dw_read_io_reg(dws, DW_SPI_DR);
|
|
/* Care rx only if the transfer's original "rx" is not null */
|
|
if (dws->rx_end - dws->len) {
|
|
if (dws->n_bytes == 1)
|
|
*(u8 *)(dws->rx) = rxw;
|
|
else
|
|
*(u16 *)(dws->rx) = rxw;
|
|
}
|
|
dws->rx += dws->n_bytes;
|
|
}
|
|
spin_unlock(&dws->buf_lock);
|
|
}
|
|
|
|
static void int_error_stop(struct dw_spi *dws, const char *msg)
|
|
{
|
|
spi_reset_chip(dws);
|
|
|
|
dev_err(&dws->master->dev, "%s\n", msg);
|
|
dws->master->cur_msg->status = -EIO;
|
|
spi_finalize_current_transfer(dws->master);
|
|
}
|
|
|
|
static irqreturn_t interrupt_transfer(struct dw_spi *dws)
|
|
{
|
|
u16 irq_status = dw_readl(dws, DW_SPI_ISR);
|
|
|
|
/* Error handling */
|
|
if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) {
|
|
dw_readl(dws, DW_SPI_ICR);
|
|
int_error_stop(dws, "interrupt_transfer: fifo overrun/underrun");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
dw_reader(dws);
|
|
if (dws->rx_end == dws->rx) {
|
|
spi_mask_intr(dws, SPI_INT_TXEI);
|
|
spi_finalize_current_transfer(dws->master);
|
|
return IRQ_HANDLED;
|
|
}
|
|
if (irq_status & SPI_INT_TXEI) {
|
|
spi_mask_intr(dws, SPI_INT_TXEI);
|
|
dw_writer(dws);
|
|
/* Enable TX irq always, it will be disabled when RX finished */
|
|
spi_umask_intr(dws, SPI_INT_TXEI);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t dw_spi_irq(int irq, void *dev_id)
|
|
{
|
|
struct spi_controller *master = dev_id;
|
|
struct dw_spi *dws = spi_controller_get_devdata(master);
|
|
u16 irq_status = dw_readl(dws, DW_SPI_ISR) & 0x3f;
|
|
|
|
if (!irq_status)
|
|
return IRQ_NONE;
|
|
|
|
if (!master->cur_msg) {
|
|
spi_mask_intr(dws, SPI_INT_TXEI);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return dws->transfer_handler(dws);
|
|
}
|
|
|
|
/* Must be called inside pump_transfers() */
|
|
static int poll_transfer(struct dw_spi *dws)
|
|
{
|
|
do {
|
|
dw_writer(dws);
|
|
dw_reader(dws);
|
|
cpu_relax();
|
|
} while (dws->rx_end > dws->rx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_spi_transfer_one(struct spi_controller *master,
|
|
struct spi_device *spi, struct spi_transfer *transfer)
|
|
{
|
|
struct dw_spi *dws = spi_controller_get_devdata(master);
|
|
struct chip_data *chip = spi_get_ctldata(spi);
|
|
unsigned long flags;
|
|
u8 imask = 0;
|
|
u16 txlevel = 0;
|
|
u32 cr0;
|
|
int ret;
|
|
|
|
dws->dma_mapped = 0;
|
|
spin_lock_irqsave(&dws->buf_lock, flags);
|
|
dws->tx = (void *)transfer->tx_buf;
|
|
dws->tx_end = dws->tx + transfer->len;
|
|
dws->rx = transfer->rx_buf;
|
|
dws->rx_end = dws->rx + transfer->len;
|
|
dws->len = transfer->len;
|
|
spin_unlock_irqrestore(&dws->buf_lock, flags);
|
|
|
|
/* Ensure dw->rx and dw->rx_end are visible */
|
|
smp_mb();
|
|
|
|
spi_enable_chip(dws, 0);
|
|
|
|
/* Handle per transfer options for bpw and speed */
|
|
if (transfer->speed_hz != dws->current_freq) {
|
|
if (transfer->speed_hz != chip->speed_hz) {
|
|
/* clk_div doesn't support odd number */
|
|
chip->clk_div = (DIV_ROUND_UP(dws->max_freq, transfer->speed_hz) + 1) & 0xfffe;
|
|
chip->speed_hz = transfer->speed_hz;
|
|
}
|
|
dws->current_freq = transfer->speed_hz;
|
|
spi_set_clk(dws, chip->clk_div);
|
|
}
|
|
|
|
dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE);
|
|
dws->dma_width = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE);
|
|
|
|
/* Default SPI mode is SCPOL = 0, SCPH = 0 */
|
|
cr0 = (transfer->bits_per_word - 1)
|
|
| (chip->type << SPI_FRF_OFFSET)
|
|
| ((((spi->mode & SPI_CPOL) ? 1 : 0) << SPI_SCOL_OFFSET) |
|
|
(((spi->mode & SPI_CPHA) ? 1 : 0) << SPI_SCPH_OFFSET) |
|
|
(((spi->mode & SPI_LOOP) ? 1 : 0) << SPI_SRL_OFFSET))
|
|
| (chip->tmode << SPI_TMOD_OFFSET);
|
|
|
|
/*
|
|
* Adjust transfer mode if necessary. Requires platform dependent
|
|
* chipselect mechanism.
|
|
*/
|
|
if (chip->cs_control) {
|
|
if (dws->rx && dws->tx)
|
|
chip->tmode = SPI_TMOD_TR;
|
|
else if (dws->rx)
|
|
chip->tmode = SPI_TMOD_RO;
|
|
else
|
|
chip->tmode = SPI_TMOD_TO;
|
|
|
|
cr0 &= ~SPI_TMOD_MASK;
|
|
cr0 |= (chip->tmode << SPI_TMOD_OFFSET);
|
|
}
|
|
|
|
dw_writel(dws, DW_SPI_CTRL0, cr0);
|
|
|
|
/* Check if current transfer is a DMA transaction */
|
|
if (master->can_dma && master->can_dma(master, spi, transfer))
|
|
dws->dma_mapped = master->cur_msg_mapped;
|
|
|
|
/* For poll mode just disable all interrupts */
|
|
spi_mask_intr(dws, 0xff);
|
|
|
|
/*
|
|
* Interrupt mode
|
|
* we only need set the TXEI IRQ, as TX/RX always happen syncronizely
|
|
*/
|
|
if (dws->dma_mapped) {
|
|
ret = dws->dma_ops->dma_setup(dws, transfer);
|
|
if (ret < 0) {
|
|
spi_enable_chip(dws, 1);
|
|
return ret;
|
|
}
|
|
} else if (!chip->poll_mode) {
|
|
txlevel = min_t(u16, dws->fifo_len / 2, dws->len / dws->n_bytes);
|
|
dw_writel(dws, DW_SPI_TXFLTR, txlevel);
|
|
|
|
/* Set the interrupt mask */
|
|
imask |= SPI_INT_TXEI | SPI_INT_TXOI |
|
|
SPI_INT_RXUI | SPI_INT_RXOI;
|
|
spi_umask_intr(dws, imask);
|
|
|
|
dws->transfer_handler = interrupt_transfer;
|
|
}
|
|
|
|
spi_enable_chip(dws, 1);
|
|
|
|
if (dws->dma_mapped) {
|
|
ret = dws->dma_ops->dma_transfer(dws, transfer);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (chip->poll_mode)
|
|
return poll_transfer(dws);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void dw_spi_handle_err(struct spi_controller *master,
|
|
struct spi_message *msg)
|
|
{
|
|
struct dw_spi *dws = spi_controller_get_devdata(master);
|
|
|
|
if (dws->dma_mapped)
|
|
dws->dma_ops->dma_stop(dws);
|
|
|
|
spi_reset_chip(dws);
|
|
}
|
|
|
|
/* This may be called twice for each spi dev */
|
|
static int dw_spi_setup(struct spi_device *spi)
|
|
{
|
|
struct dw_spi_chip *chip_info = NULL;
|
|
struct chip_data *chip;
|
|
|
|
/* Only alloc on first setup */
|
|
chip = spi_get_ctldata(spi);
|
|
if (!chip) {
|
|
chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
spi_set_ctldata(spi, chip);
|
|
}
|
|
|
|
/*
|
|
* Protocol drivers may change the chip settings, so...
|
|
* if chip_info exists, use it
|
|
*/
|
|
chip_info = spi->controller_data;
|
|
|
|
/* chip_info doesn't always exist */
|
|
if (chip_info) {
|
|
if (chip_info->cs_control)
|
|
chip->cs_control = chip_info->cs_control;
|
|
|
|
chip->poll_mode = chip_info->poll_mode;
|
|
chip->type = chip_info->type;
|
|
}
|
|
|
|
chip->tmode = SPI_TMOD_TR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_spi_cleanup(struct spi_device *spi)
|
|
{
|
|
struct chip_data *chip = spi_get_ctldata(spi);
|
|
|
|
kfree(chip);
|
|
spi_set_ctldata(spi, NULL);
|
|
}
|
|
|
|
/* Restart the controller, disable all interrupts, clean rx fifo */
|
|
static void spi_hw_init(struct device *dev, struct dw_spi *dws)
|
|
{
|
|
spi_reset_chip(dws);
|
|
|
|
/*
|
|
* Try to detect the FIFO depth if not set by interface driver,
|
|
* the depth could be from 2 to 256 from HW spec
|
|
*/
|
|
if (!dws->fifo_len) {
|
|
u32 fifo;
|
|
|
|
for (fifo = 1; fifo < 256; fifo++) {
|
|
dw_writel(dws, DW_SPI_TXFLTR, fifo);
|
|
if (fifo != dw_readl(dws, DW_SPI_TXFLTR))
|
|
break;
|
|
}
|
|
dw_writel(dws, DW_SPI_TXFLTR, 0);
|
|
|
|
dws->fifo_len = (fifo == 1) ? 0 : fifo;
|
|
dev_dbg(dev, "Detected FIFO size: %u bytes\n", dws->fifo_len);
|
|
}
|
|
|
|
/* enable HW fixup for explicit CS deselect for Amazon's alpine chip */
|
|
if (dws->cs_override)
|
|
dw_writel(dws, DW_SPI_CS_OVERRIDE, 0xF);
|
|
}
|
|
|
|
int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
|
|
{
|
|
struct spi_controller *master;
|
|
int ret;
|
|
|
|
if (!dws)
|
|
return -EINVAL;
|
|
|
|
master = spi_alloc_master(dev, 0);
|
|
if (!master)
|
|
return -ENOMEM;
|
|
|
|
dws->master = master;
|
|
dws->type = SSI_MOTO_SPI;
|
|
dws->dma_inited = 0;
|
|
dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR);
|
|
spin_lock_init(&dws->buf_lock);
|
|
|
|
spi_controller_set_devdata(master, dws);
|
|
|
|
ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev),
|
|
master);
|
|
if (ret < 0) {
|
|
dev_err(dev, "can not get IRQ\n");
|
|
goto err_free_master;
|
|
}
|
|
|
|
master->use_gpio_descriptors = true;
|
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
|
|
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16);
|
|
master->bus_num = dws->bus_num;
|
|
master->num_chipselect = dws->num_cs;
|
|
master->setup = dw_spi_setup;
|
|
master->cleanup = dw_spi_cleanup;
|
|
master->set_cs = dw_spi_set_cs;
|
|
master->transfer_one = dw_spi_transfer_one;
|
|
master->handle_err = dw_spi_handle_err;
|
|
master->max_speed_hz = dws->max_freq;
|
|
master->dev.of_node = dev->of_node;
|
|
master->dev.fwnode = dev->fwnode;
|
|
master->flags = SPI_MASTER_GPIO_SS;
|
|
master->auto_runtime_pm = true;
|
|
|
|
if (dws->set_cs)
|
|
master->set_cs = dws->set_cs;
|
|
|
|
/* Basic HW init */
|
|
spi_hw_init(dev, dws);
|
|
|
|
if (dws->dma_ops && dws->dma_ops->dma_init) {
|
|
ret = dws->dma_ops->dma_init(dws);
|
|
if (ret) {
|
|
dev_warn(dev, "DMA init failed\n");
|
|
dws->dma_inited = 0;
|
|
} else {
|
|
master->can_dma = dws->dma_ops->can_dma;
|
|
}
|
|
}
|
|
|
|
ret = spi_register_controller(master);
|
|
if (ret) {
|
|
dev_err(&master->dev, "problem registering spi master\n");
|
|
goto err_dma_exit;
|
|
}
|
|
|
|
dw_spi_debugfs_init(dws);
|
|
return 0;
|
|
|
|
err_dma_exit:
|
|
if (dws->dma_ops && dws->dma_ops->dma_exit)
|
|
dws->dma_ops->dma_exit(dws);
|
|
spi_enable_chip(dws, 0);
|
|
free_irq(dws->irq, master);
|
|
err_free_master:
|
|
spi_controller_put(master);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_spi_add_host);
|
|
|
|
void dw_spi_remove_host(struct dw_spi *dws)
|
|
{
|
|
dw_spi_debugfs_remove(dws);
|
|
|
|
spi_unregister_controller(dws->master);
|
|
|
|
if (dws->dma_ops && dws->dma_ops->dma_exit)
|
|
dws->dma_ops->dma_exit(dws);
|
|
|
|
spi_shutdown_chip(dws);
|
|
|
|
free_irq(dws->irq, dws->master);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_spi_remove_host);
|
|
|
|
int dw_spi_suspend_host(struct dw_spi *dws)
|
|
{
|
|
int ret;
|
|
|
|
ret = spi_controller_suspend(dws->master);
|
|
if (ret)
|
|
return ret;
|
|
|
|
spi_shutdown_chip(dws);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_spi_suspend_host);
|
|
|
|
int dw_spi_resume_host(struct dw_spi *dws)
|
|
{
|
|
spi_hw_init(&dws->master->dev, dws);
|
|
return spi_controller_resume(dws->master);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_spi_resume_host);
|
|
|
|
MODULE_AUTHOR("Feng Tang <feng.tang@intel.com>");
|
|
MODULE_DESCRIPTION("Driver for DesignWare SPI controller core");
|
|
MODULE_LICENSE("GPL v2");
|