forked from Minki/linux
a922f1a9ae
A relatively large set of fixes here, the biggest part of it is for fallout from the GPIO descriptor rework that affected several of the devices with usable native chip select support. There's also some new PCI IDs for Intel Jasper Lake devices. The conversion to platform_get_irq() in the fsl driver is an incremental fix for build errors introduced on SPARC by the earlier fix for error handling in probe in that driver. -----BEGIN PGP SIGNATURE----- iQFHBAABCgAxFiEEreZoqmdXGLWf4p/qJNaLcl1Uh9AFAl34xt8THGJyb29uaWVA a2VybmVsLm9yZwAKCRAk1otyXVSH0D64B/91GGhGgNybvExhls79YKQx9STnv8Vl sIVgbnVxnTHrAa+s/5ML0T8LzH9W5KcBUuL+KCd0oMdaE9QZSCKGdraEqDJz7Ie4 iHbBSAtwL0tegbyM+J4oKC3wen3Lg1u1iwn4Plo2fVKEah7zG6gdneQcf454EnEF om2Sj2K7tqoqN1jIl0j/FGQtlDXvhzp3m+CyGakjzsBvINmSGixrO5Lit/dx3qSf xkhLDWZqUk3UatusPXhpJJVxWPuVvT5NQkvUYDte1rHxrVdolYQctwBRPN/uZ2hq FcLzUbWVFx7p3cDqvuQ6XTptpue4njnaPOT/LW6tud4BexlaV1q83XYs =gPXn -----END PGP SIGNATURE----- Merge tag 'spi-fix-v5.5-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi Pull spi fixes from Mark Brown: "A relatively large set of fixes here, the biggest part of it is for fallout from the GPIO descriptor rework that affected several of the devices with usable native chip select support. There's also some new PCI IDs for Intel Jasper Lake devices. The conversion to platform_get_irq() in the fsl driver is an incremental fix for build errors introduced on SPARC by the earlier fix for error handling in probe in that driver" * tag 'spi-fix-v5.5-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi: spi: fsl: use platform_get_irq() instead of of_irq_to_resource() spi: nxp-fspi: Ensure width is respected in spi-mem operations spi: spi-ti-qspi: Fix a bug when accessing non default CS spi: fsl: don't map irq during probe spi: spi-cavium-thunderx: Add missing pci_release_regions() spi: sprd: Fix the incorrect SPI register gpiolib: of: Make of_gpio_spi_cs_get_count static spi: fsl: Handle the single hardwired chipselect case gpio: Handle counting of Freescale chipselects spi: fsl: Fix GPIO descriptor support spi: dw: Correct handling of native chipselect spi: cadence: Correct handling of native chipselect spi: pxa2xx: Add support for Intel Jasper Lake
572 lines
14 KiB
C
572 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);
|
|
|
|
/* 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 = 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) |
|
|
(((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;
|
|
|
|
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;
|
|
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 = 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");
|