2025cf9e19
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 263 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190529141901.208660670@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
569 lines
14 KiB
C
569 lines
14 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);
|
|
|
|
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 = tx_max(dws);
|
|
u16 txw = 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static void dw_reader(struct dw_spi *dws)
|
|
{
|
|
u32 max = rx_max(dws);
|
|
u16 rxw;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
u8 imask = 0;
|
|
u16 txlevel = 0;
|
|
u32 cr0;
|
|
int ret;
|
|
|
|
dws->dma_mapped = 0;
|
|
|
|
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;
|
|
|
|
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))
|
|
| (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;
|
|
|
|
BUG_ON(dws == NULL);
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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 = devm_spi_register_controller(dev, 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);
|
|
|
|
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");
|