2016-12-08 22:58:44 +08:00
|
|
|
/*
|
|
|
|
* Marvell Armada-3700 SPI controller driver
|
|
|
|
*
|
|
|
|
* Copyright (C) 2016 Marvell Ltd.
|
|
|
|
*
|
|
|
|
* Author: Wilson Ding <dingwei@marvell.com>
|
|
|
|
* Author: Romain Perier <romain.perier@free-electrons.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/completion.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_irq.h>
|
|
|
|
#include <linux/of_device.h>
|
|
|
|
#include <linux/pinctrl/consumer.h>
|
|
|
|
#include <linux/spi/spi.h>
|
|
|
|
|
|
|
|
#define DRIVER_NAME "armada_3700_spi"
|
|
|
|
|
|
|
|
#define A3700_SPI_TIMEOUT 10
|
|
|
|
|
|
|
|
/* SPI Register Offest */
|
|
|
|
#define A3700_SPI_IF_CTRL_REG 0x00
|
|
|
|
#define A3700_SPI_IF_CFG_REG 0x04
|
|
|
|
#define A3700_SPI_DATA_OUT_REG 0x08
|
|
|
|
#define A3700_SPI_DATA_IN_REG 0x0C
|
|
|
|
#define A3700_SPI_IF_INST_REG 0x10
|
|
|
|
#define A3700_SPI_IF_ADDR_REG 0x14
|
|
|
|
#define A3700_SPI_IF_RMODE_REG 0x18
|
|
|
|
#define A3700_SPI_IF_HDR_CNT_REG 0x1C
|
|
|
|
#define A3700_SPI_IF_DIN_CNT_REG 0x20
|
|
|
|
#define A3700_SPI_IF_TIME_REG 0x24
|
|
|
|
#define A3700_SPI_INT_STAT_REG 0x28
|
|
|
|
#define A3700_SPI_INT_MASK_REG 0x2C
|
|
|
|
|
|
|
|
/* A3700_SPI_IF_CTRL_REG */
|
|
|
|
#define A3700_SPI_EN BIT(16)
|
|
|
|
#define A3700_SPI_ADDR_NOT_CONFIG BIT(12)
|
|
|
|
#define A3700_SPI_WFIFO_OVERFLOW BIT(11)
|
|
|
|
#define A3700_SPI_WFIFO_UNDERFLOW BIT(10)
|
|
|
|
#define A3700_SPI_RFIFO_OVERFLOW BIT(9)
|
|
|
|
#define A3700_SPI_RFIFO_UNDERFLOW BIT(8)
|
|
|
|
#define A3700_SPI_WFIFO_FULL BIT(7)
|
|
|
|
#define A3700_SPI_WFIFO_EMPTY BIT(6)
|
|
|
|
#define A3700_SPI_RFIFO_FULL BIT(5)
|
|
|
|
#define A3700_SPI_RFIFO_EMPTY BIT(4)
|
|
|
|
#define A3700_SPI_WFIFO_RDY BIT(3)
|
|
|
|
#define A3700_SPI_RFIFO_RDY BIT(2)
|
|
|
|
#define A3700_SPI_XFER_RDY BIT(1)
|
|
|
|
#define A3700_SPI_XFER_DONE BIT(0)
|
|
|
|
|
|
|
|
/* A3700_SPI_IF_CFG_REG */
|
|
|
|
#define A3700_SPI_WFIFO_THRS BIT(28)
|
|
|
|
#define A3700_SPI_RFIFO_THRS BIT(24)
|
|
|
|
#define A3700_SPI_AUTO_CS BIT(20)
|
|
|
|
#define A3700_SPI_DMA_RD_EN BIT(18)
|
|
|
|
#define A3700_SPI_FIFO_MODE BIT(17)
|
|
|
|
#define A3700_SPI_SRST BIT(16)
|
|
|
|
#define A3700_SPI_XFER_START BIT(15)
|
|
|
|
#define A3700_SPI_XFER_STOP BIT(14)
|
|
|
|
#define A3700_SPI_INST_PIN BIT(13)
|
|
|
|
#define A3700_SPI_ADDR_PIN BIT(12)
|
|
|
|
#define A3700_SPI_DATA_PIN1 BIT(11)
|
|
|
|
#define A3700_SPI_DATA_PIN0 BIT(10)
|
|
|
|
#define A3700_SPI_FIFO_FLUSH BIT(9)
|
|
|
|
#define A3700_SPI_RW_EN BIT(8)
|
|
|
|
#define A3700_SPI_CLK_POL BIT(7)
|
|
|
|
#define A3700_SPI_CLK_PHA BIT(6)
|
|
|
|
#define A3700_SPI_BYTE_LEN BIT(5)
|
|
|
|
#define A3700_SPI_CLK_PRESCALE BIT(0)
|
|
|
|
#define A3700_SPI_CLK_PRESCALE_MASK (0x1f)
|
|
|
|
|
|
|
|
#define A3700_SPI_WFIFO_THRS_BIT 28
|
|
|
|
#define A3700_SPI_RFIFO_THRS_BIT 24
|
|
|
|
#define A3700_SPI_FIFO_THRS_MASK 0x7
|
|
|
|
|
|
|
|
#define A3700_SPI_DATA_PIN_MASK 0x3
|
|
|
|
|
|
|
|
/* A3700_SPI_IF_HDR_CNT_REG */
|
|
|
|
#define A3700_SPI_DUMMY_CNT_BIT 12
|
|
|
|
#define A3700_SPI_DUMMY_CNT_MASK 0x7
|
|
|
|
#define A3700_SPI_RMODE_CNT_BIT 8
|
|
|
|
#define A3700_SPI_RMODE_CNT_MASK 0x3
|
|
|
|
#define A3700_SPI_ADDR_CNT_BIT 4
|
|
|
|
#define A3700_SPI_ADDR_CNT_MASK 0x7
|
|
|
|
#define A3700_SPI_INSTR_CNT_BIT 0
|
|
|
|
#define A3700_SPI_INSTR_CNT_MASK 0x3
|
|
|
|
|
|
|
|
/* A3700_SPI_IF_TIME_REG */
|
|
|
|
#define A3700_SPI_CLK_CAPT_EDGE BIT(7)
|
|
|
|
|
|
|
|
/* Flags and macros for struct a3700_spi */
|
|
|
|
#define A3700_INSTR_CNT 1
|
|
|
|
#define A3700_ADDR_CNT 3
|
|
|
|
#define A3700_DUMMY_CNT 1
|
|
|
|
|
|
|
|
struct a3700_spi {
|
|
|
|
struct spi_master *master;
|
|
|
|
void __iomem *base;
|
|
|
|
struct clk *clk;
|
|
|
|
unsigned int irq;
|
|
|
|
unsigned int flags;
|
|
|
|
bool xmit_data;
|
|
|
|
const u8 *tx_buf;
|
|
|
|
u8 *rx_buf;
|
|
|
|
size_t buf_len;
|
|
|
|
u8 byte_len;
|
|
|
|
u32 wait_mask;
|
|
|
|
struct completion done;
|
|
|
|
u32 addr_cnt;
|
|
|
|
u32 instr_cnt;
|
|
|
|
size_t hdr_cnt;
|
|
|
|
};
|
|
|
|
|
|
|
|
static u32 spireg_read(struct a3700_spi *a3700_spi, u32 offset)
|
|
|
|
{
|
|
|
|
return readl(a3700_spi->base + offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void spireg_write(struct a3700_spi *a3700_spi, u32 offset, u32 data)
|
|
|
|
{
|
|
|
|
writel(data, a3700_spi->base + offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_auto_cs_unset(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val &= ~A3700_SPI_AUTO_CS;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_activate_cs(struct a3700_spi *a3700_spi, unsigned int cs)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CTRL_REG);
|
|
|
|
val |= (A3700_SPI_EN << cs);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CTRL_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_deactivate_cs(struct a3700_spi *a3700_spi,
|
|
|
|
unsigned int cs)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CTRL_REG);
|
|
|
|
val &= ~(A3700_SPI_EN << cs);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CTRL_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_pin_mode_set(struct a3700_spi *a3700_spi,
|
|
|
|
unsigned int pin_mode)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val &= ~(A3700_SPI_INST_PIN | A3700_SPI_ADDR_PIN);
|
|
|
|
val &= ~(A3700_SPI_DATA_PIN0 | A3700_SPI_DATA_PIN1);
|
|
|
|
|
|
|
|
switch (pin_mode) {
|
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
val |= A3700_SPI_DATA_PIN0;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
val |= A3700_SPI_DATA_PIN1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(&a3700_spi->master->dev, "wrong pin mode %u", pin_mode);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_fifo_mode_set(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val |= A3700_SPI_FIFO_MODE;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_mode_set(struct a3700_spi *a3700_spi,
|
|
|
|
unsigned int mode_bits)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
|
|
|
|
if (mode_bits & SPI_CPOL)
|
|
|
|
val |= A3700_SPI_CLK_POL;
|
|
|
|
else
|
|
|
|
val &= ~A3700_SPI_CLK_POL;
|
|
|
|
|
|
|
|
if (mode_bits & SPI_CPHA)
|
|
|
|
val |= A3700_SPI_CLK_PHA;
|
|
|
|
else
|
|
|
|
val &= ~A3700_SPI_CLK_PHA;
|
|
|
|
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_clock_set(struct a3700_spi *a3700_spi,
|
|
|
|
unsigned int speed_hz, u16 mode)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
u32 prescale;
|
|
|
|
|
|
|
|
prescale = DIV_ROUND_UP(clk_get_rate(a3700_spi->clk), speed_hz);
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val = val & ~A3700_SPI_CLK_PRESCALE_MASK;
|
|
|
|
|
|
|
|
val = val | (prescale & A3700_SPI_CLK_PRESCALE_MASK);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
if (prescale <= 2) {
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_TIME_REG);
|
|
|
|
val |= A3700_SPI_CLK_CAPT_EDGE;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_TIME_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val &= ~(A3700_SPI_CLK_POL | A3700_SPI_CLK_PHA);
|
|
|
|
|
|
|
|
if (mode & SPI_CPOL)
|
|
|
|
val |= A3700_SPI_CLK_POL;
|
|
|
|
|
|
|
|
if (mode & SPI_CPHA)
|
|
|
|
val |= A3700_SPI_CLK_PHA;
|
|
|
|
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_bytelen_set(struct a3700_spi *a3700_spi, unsigned int len)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
if (len == 4)
|
|
|
|
val |= A3700_SPI_BYTE_LEN;
|
|
|
|
else
|
|
|
|
val &= ~A3700_SPI_BYTE_LEN;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
a3700_spi->byte_len = len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_fifo_flush(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
int timeout = A3700_SPI_TIMEOUT;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val |= A3700_SPI_FIFO_FLUSH;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
while (--timeout) {
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
if (!(val & A3700_SPI_FIFO_FLUSH))
|
|
|
|
return 0;
|
|
|
|
udelay(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_init(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
struct spi_master *master = a3700_spi->master;
|
|
|
|
u32 val;
|
|
|
|
int i, ret = 0;
|
|
|
|
|
|
|
|
/* Reset SPI unit */
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val |= A3700_SPI_SRST;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
udelay(A3700_SPI_TIMEOUT);
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val &= ~A3700_SPI_SRST;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
/* Disable AUTO_CS and deactivate all chip-selects */
|
|
|
|
a3700_spi_auto_cs_unset(a3700_spi);
|
|
|
|
for (i = 0; i < master->num_chipselect; i++)
|
|
|
|
a3700_spi_deactivate_cs(a3700_spi, i);
|
|
|
|
|
|
|
|
/* Enable FIFO mode */
|
|
|
|
a3700_spi_fifo_mode_set(a3700_spi);
|
|
|
|
|
|
|
|
/* Set SPI mode */
|
|
|
|
a3700_spi_mode_set(a3700_spi, master->mode_bits);
|
|
|
|
|
|
|
|
/* Reset counters */
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_HDR_CNT_REG, 0);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_DIN_CNT_REG, 0);
|
|
|
|
|
|
|
|
/* Mask the interrupts and clear cause bits */
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_INT_MASK_REG, 0);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_INT_STAT_REG, ~0U);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t a3700_spi_interrupt(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct spi_master *master = dev_id;
|
|
|
|
struct a3700_spi *a3700_spi;
|
|
|
|
u32 cause;
|
|
|
|
|
|
|
|
a3700_spi = spi_master_get_devdata(master);
|
|
|
|
|
|
|
|
/* Get interrupt causes */
|
|
|
|
cause = spireg_read(a3700_spi, A3700_SPI_INT_STAT_REG);
|
|
|
|
|
|
|
|
if (!cause || !(a3700_spi->wait_mask & cause))
|
|
|
|
return IRQ_NONE;
|
|
|
|
|
|
|
|
/* mask and acknowledge the SPI interrupts */
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_INT_MASK_REG, 0);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_INT_STAT_REG, cause);
|
|
|
|
|
|
|
|
/* Wake up the transfer */
|
2016-12-16 17:33:59 +08:00
|
|
|
complete(&a3700_spi->done);
|
2016-12-08 22:58:44 +08:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool a3700_spi_wait_completion(struct spi_device *spi)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi;
|
|
|
|
unsigned int timeout;
|
|
|
|
unsigned int ctrl_reg;
|
|
|
|
unsigned long timeout_jiffies;
|
|
|
|
|
|
|
|
a3700_spi = spi_master_get_devdata(spi->master);
|
|
|
|
|
|
|
|
/* SPI interrupt is edge-triggered, which means an interrupt will
|
|
|
|
* be generated only when detecting a specific status bit changed
|
|
|
|
* from '0' to '1'. So when we start waiting for a interrupt, we
|
|
|
|
* need to check status bit in control reg first, if it is already 1,
|
|
|
|
* then we do not need to wait for interrupt
|
|
|
|
*/
|
|
|
|
ctrl_reg = spireg_read(a3700_spi, A3700_SPI_IF_CTRL_REG);
|
|
|
|
if (a3700_spi->wait_mask & ctrl_reg)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
reinit_completion(&a3700_spi->done);
|
|
|
|
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_INT_MASK_REG,
|
|
|
|
a3700_spi->wait_mask);
|
|
|
|
|
|
|
|
timeout_jiffies = msecs_to_jiffies(A3700_SPI_TIMEOUT);
|
|
|
|
timeout = wait_for_completion_timeout(&a3700_spi->done,
|
|
|
|
timeout_jiffies);
|
|
|
|
|
|
|
|
a3700_spi->wait_mask = 0;
|
|
|
|
|
|
|
|
if (timeout)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* there might be the case that right after we checked the
|
|
|
|
* status bits in this routine and before start to wait for
|
|
|
|
* interrupt by wait_for_completion_timeout, the interrupt
|
|
|
|
* happens, to avoid missing it we need to double check
|
|
|
|
* status bits in control reg, if it is already 1, then
|
|
|
|
* consider that we have the interrupt successfully and
|
|
|
|
* return true.
|
|
|
|
*/
|
|
|
|
ctrl_reg = spireg_read(a3700_spi, A3700_SPI_IF_CTRL_REG);
|
|
|
|
if (a3700_spi->wait_mask & ctrl_reg)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_INT_MASK_REG, 0);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool a3700_spi_transfer_wait(struct spi_device *spi,
|
|
|
|
unsigned int bit_mask)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi;
|
|
|
|
|
|
|
|
a3700_spi = spi_master_get_devdata(spi->master);
|
|
|
|
a3700_spi->wait_mask = bit_mask;
|
|
|
|
|
|
|
|
return a3700_spi_wait_completion(spi);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_fifo_thres_set(struct a3700_spi *a3700_spi,
|
|
|
|
unsigned int bytes)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val &= ~(A3700_SPI_FIFO_THRS_MASK << A3700_SPI_RFIFO_THRS_BIT);
|
|
|
|
val |= (bytes - 1) << A3700_SPI_RFIFO_THRS_BIT;
|
|
|
|
val &= ~(A3700_SPI_FIFO_THRS_MASK << A3700_SPI_WFIFO_THRS_BIT);
|
|
|
|
val |= (7 - bytes) << A3700_SPI_WFIFO_THRS_BIT;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_transfer_setup(struct spi_device *spi,
|
|
|
|
struct spi_transfer *xfer)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi;
|
|
|
|
unsigned int byte_len;
|
|
|
|
|
|
|
|
a3700_spi = spi_master_get_devdata(spi->master);
|
|
|
|
|
|
|
|
a3700_spi_clock_set(a3700_spi, xfer->speed_hz, spi->mode);
|
|
|
|
|
|
|
|
byte_len = xfer->bits_per_word >> 3;
|
|
|
|
|
|
|
|
a3700_spi_fifo_thres_set(a3700_spi, byte_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_set_cs(struct spi_device *spi, bool enable)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi = spi_master_get_devdata(spi->master);
|
|
|
|
|
|
|
|
if (!enable)
|
|
|
|
a3700_spi_activate_cs(a3700_spi, spi->chip_select);
|
|
|
|
else
|
|
|
|
a3700_spi_deactivate_cs(a3700_spi, spi->chip_select);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_header_set(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 instr_cnt = 0, addr_cnt = 0, dummy_cnt = 0;
|
|
|
|
u32 val = 0;
|
|
|
|
|
|
|
|
/* Clear the header registers */
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_INST_REG, 0);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_ADDR_REG, 0);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_RMODE_REG, 0);
|
|
|
|
|
|
|
|
/* Set header counters */
|
|
|
|
if (a3700_spi->tx_buf) {
|
|
|
|
if (a3700_spi->buf_len <= a3700_spi->instr_cnt) {
|
|
|
|
instr_cnt = a3700_spi->buf_len;
|
|
|
|
} else if (a3700_spi->buf_len <= (a3700_spi->instr_cnt +
|
|
|
|
a3700_spi->addr_cnt)) {
|
|
|
|
instr_cnt = a3700_spi->instr_cnt;
|
|
|
|
addr_cnt = a3700_spi->buf_len - instr_cnt;
|
|
|
|
} else if (a3700_spi->buf_len <= a3700_spi->hdr_cnt) {
|
|
|
|
instr_cnt = a3700_spi->instr_cnt;
|
|
|
|
addr_cnt = a3700_spi->addr_cnt;
|
|
|
|
/* Need to handle the normal write case with 1 byte
|
|
|
|
* data
|
|
|
|
*/
|
|
|
|
if (!a3700_spi->tx_buf[instr_cnt + addr_cnt])
|
|
|
|
dummy_cnt = a3700_spi->buf_len - instr_cnt -
|
|
|
|
addr_cnt;
|
|
|
|
}
|
|
|
|
val |= ((instr_cnt & A3700_SPI_INSTR_CNT_MASK)
|
|
|
|
<< A3700_SPI_INSTR_CNT_BIT);
|
|
|
|
val |= ((addr_cnt & A3700_SPI_ADDR_CNT_MASK)
|
|
|
|
<< A3700_SPI_ADDR_CNT_BIT);
|
|
|
|
val |= ((dummy_cnt & A3700_SPI_DUMMY_CNT_MASK)
|
|
|
|
<< A3700_SPI_DUMMY_CNT_BIT);
|
|
|
|
}
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_HDR_CNT_REG, val);
|
|
|
|
|
|
|
|
/* Update the buffer length to be transferred */
|
|
|
|
a3700_spi->buf_len -= (instr_cnt + addr_cnt + dummy_cnt);
|
|
|
|
|
|
|
|
/* Set Instruction */
|
|
|
|
val = 0;
|
|
|
|
while (instr_cnt--) {
|
|
|
|
val = (val << 8) | a3700_spi->tx_buf[0];
|
|
|
|
a3700_spi->tx_buf++;
|
|
|
|
}
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_INST_REG, val);
|
|
|
|
|
|
|
|
/* Set Address */
|
|
|
|
val = 0;
|
|
|
|
while (addr_cnt--) {
|
|
|
|
val = (val << 8) | a3700_spi->tx_buf[0];
|
|
|
|
a3700_spi->tx_buf++;
|
|
|
|
}
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_ADDR_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_is_wfifo_full(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CTRL_REG);
|
|
|
|
return (val & A3700_SPI_WFIFO_FULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_fifo_write(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (!a3700_is_wfifo_full(a3700_spi) && a3700_spi->buf_len) {
|
|
|
|
val = 0;
|
|
|
|
if (a3700_spi->buf_len >= 4) {
|
|
|
|
val = cpu_to_le32(*(u32 *)a3700_spi->tx_buf);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_DATA_OUT_REG, val);
|
|
|
|
|
|
|
|
a3700_spi->buf_len -= 4;
|
|
|
|
a3700_spi->tx_buf += 4;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* If the remained buffer length is less than 4-bytes,
|
|
|
|
* we should pad the write buffer with all ones. So that
|
|
|
|
* it avoids overwrite the unexpected bytes following
|
|
|
|
* the last one.
|
|
|
|
*/
|
|
|
|
val = GENMASK(31, 0);
|
|
|
|
while (a3700_spi->buf_len) {
|
|
|
|
val &= ~(0xff << (8 * i));
|
|
|
|
val |= *a3700_spi->tx_buf++ << (8 * i);
|
|
|
|
i++;
|
|
|
|
a3700_spi->buf_len--;
|
|
|
|
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_DATA_OUT_REG,
|
|
|
|
val);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_is_rfifo_empty(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 val = spireg_read(a3700_spi, A3700_SPI_IF_CTRL_REG);
|
|
|
|
|
|
|
|
return (val & A3700_SPI_RFIFO_EMPTY);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_fifo_read(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
while (!a3700_is_rfifo_empty(a3700_spi) && a3700_spi->buf_len) {
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_DATA_IN_REG);
|
|
|
|
if (a3700_spi->buf_len >= 4) {
|
|
|
|
u32 data = le32_to_cpu(val);
|
|
|
|
memcpy(a3700_spi->rx_buf, &data, 4);
|
|
|
|
|
|
|
|
a3700_spi->buf_len -= 4;
|
|
|
|
a3700_spi->rx_buf += 4;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* When remain bytes is not larger than 4, we should
|
|
|
|
* avoid memory overwriting and just write the left rx
|
|
|
|
* buffer bytes.
|
|
|
|
*/
|
|
|
|
while (a3700_spi->buf_len) {
|
|
|
|
*a3700_spi->rx_buf = val & 0xff;
|
|
|
|
val >>= 8;
|
|
|
|
|
|
|
|
a3700_spi->buf_len--;
|
|
|
|
a3700_spi->rx_buf++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void a3700_spi_transfer_abort_fifo(struct a3700_spi *a3700_spi)
|
|
|
|
{
|
|
|
|
int timeout = A3700_SPI_TIMEOUT;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val |= A3700_SPI_XFER_STOP;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
while (--timeout) {
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
if (!(val & A3700_SPI_XFER_START))
|
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
a3700_spi_fifo_flush(a3700_spi);
|
|
|
|
|
|
|
|
val &= ~A3700_SPI_XFER_STOP;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_prepare_message(struct spi_master *master,
|
|
|
|
struct spi_message *message)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi = spi_master_get_devdata(master);
|
|
|
|
struct spi_device *spi = message->spi;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = clk_enable(a3700_spi->clk);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&spi->dev, "failed to enable clk with error %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Flush the FIFOs */
|
|
|
|
ret = a3700_spi_fifo_flush(a3700_spi);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
a3700_spi_bytelen_set(a3700_spi, 4);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_transfer_one(struct spi_master *master,
|
|
|
|
struct spi_device *spi,
|
|
|
|
struct spi_transfer *xfer)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi = spi_master_get_devdata(master);
|
|
|
|
int ret = 0, timeout = A3700_SPI_TIMEOUT;
|
|
|
|
unsigned int nbits = 0;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
a3700_spi_transfer_setup(spi, xfer);
|
|
|
|
|
|
|
|
a3700_spi->tx_buf = xfer->tx_buf;
|
|
|
|
a3700_spi->rx_buf = xfer->rx_buf;
|
|
|
|
a3700_spi->buf_len = xfer->len;
|
|
|
|
|
|
|
|
/* SPI transfer headers */
|
|
|
|
a3700_spi_header_set(a3700_spi);
|
|
|
|
|
|
|
|
if (xfer->tx_buf)
|
|
|
|
nbits = xfer->tx_nbits;
|
|
|
|
else if (xfer->rx_buf)
|
|
|
|
nbits = xfer->rx_nbits;
|
|
|
|
|
|
|
|
a3700_spi_pin_mode_set(a3700_spi, nbits);
|
|
|
|
|
|
|
|
if (xfer->rx_buf) {
|
|
|
|
/* Set read data length */
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_DIN_CNT_REG,
|
|
|
|
a3700_spi->buf_len);
|
|
|
|
/* Start READ transfer */
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val &= ~A3700_SPI_RW_EN;
|
|
|
|
val |= A3700_SPI_XFER_START;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
} else if (xfer->tx_buf) {
|
|
|
|
/* Start Write transfer */
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val |= (A3700_SPI_XFER_START | A3700_SPI_RW_EN);
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there are data to be written to the SPI device, xmit_data
|
|
|
|
* flag is set true; otherwise the instruction in SPI_INSTR does
|
|
|
|
* not require data to be written to the SPI device, then
|
|
|
|
* xmit_data flag is set false.
|
|
|
|
*/
|
|
|
|
a3700_spi->xmit_data = (a3700_spi->buf_len != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (a3700_spi->buf_len) {
|
|
|
|
if (a3700_spi->tx_buf) {
|
|
|
|
/* Wait wfifo ready */
|
|
|
|
if (!a3700_spi_transfer_wait(spi,
|
|
|
|
A3700_SPI_WFIFO_RDY)) {
|
|
|
|
dev_err(&spi->dev,
|
|
|
|
"wait wfifo ready timed out\n");
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
/* Fill up the wfifo */
|
|
|
|
ret = a3700_spi_fifo_write(a3700_spi);
|
|
|
|
if (ret)
|
|
|
|
goto error;
|
|
|
|
} else if (a3700_spi->rx_buf) {
|
|
|
|
/* Wait rfifo ready */
|
|
|
|
if (!a3700_spi_transfer_wait(spi,
|
|
|
|
A3700_SPI_RFIFO_RDY)) {
|
|
|
|
dev_err(&spi->dev,
|
|
|
|
"wait rfifo ready timed out\n");
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
/* Drain out the rfifo */
|
|
|
|
ret = a3700_spi_fifo_read(a3700_spi);
|
|
|
|
if (ret)
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stop a write transfer in fifo mode:
|
|
|
|
* - wait all the bytes in wfifo to be shifted out
|
|
|
|
* - set XFER_STOP bit
|
|
|
|
* - wait XFER_START bit clear
|
|
|
|
* - clear XFER_STOP bit
|
|
|
|
* Stop a read transfer in fifo mode:
|
|
|
|
* - the hardware is to reset the XFER_START bit
|
|
|
|
* after the number of bytes indicated in DIN_CNT
|
|
|
|
* register
|
|
|
|
* - just wait XFER_START bit clear
|
|
|
|
*/
|
|
|
|
if (a3700_spi->tx_buf) {
|
|
|
|
if (a3700_spi->xmit_data) {
|
|
|
|
/*
|
|
|
|
* If there are data written to the SPI device, wait
|
|
|
|
* until SPI_WFIFO_EMPTY is 1 to wait for all data to
|
|
|
|
* transfer out of write FIFO.
|
|
|
|
*/
|
|
|
|
if (!a3700_spi_transfer_wait(spi,
|
|
|
|
A3700_SPI_WFIFO_EMPTY)) {
|
|
|
|
dev_err(&spi->dev, "wait wfifo empty timed out\n");
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* If the instruction in SPI_INSTR does not require data
|
|
|
|
* to be written to the SPI device, wait until SPI_RDY
|
|
|
|
* is 1 for the SPI interface to be in idle.
|
|
|
|
*/
|
|
|
|
if (!a3700_spi_transfer_wait(spi, A3700_SPI_XFER_RDY)) {
|
|
|
|
dev_err(&spi->dev, "wait xfer ready timed out\n");
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
val |= A3700_SPI_XFER_STOP;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (--timeout) {
|
|
|
|
val = spireg_read(a3700_spi, A3700_SPI_IF_CFG_REG);
|
|
|
|
if (!(val & A3700_SPI_XFER_START))
|
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timeout == 0) {
|
|
|
|
dev_err(&spi->dev, "wait transfer start clear timed out\n");
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
val &= ~A3700_SPI_XFER_STOP;
|
|
|
|
spireg_write(a3700_spi, A3700_SPI_IF_CFG_REG, val);
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
error:
|
|
|
|
a3700_spi_transfer_abort_fifo(a3700_spi);
|
|
|
|
out:
|
|
|
|
spi_finalize_current_transfer(master);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_unprepare_message(struct spi_master *master,
|
|
|
|
struct spi_message *message)
|
|
|
|
{
|
|
|
|
struct a3700_spi *a3700_spi = spi_master_get_devdata(master);
|
|
|
|
|
|
|
|
clk_disable(a3700_spi->clk);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct of_device_id a3700_spi_dt_ids[] = {
|
|
|
|
{ .compatible = "marvell,armada-3700-spi", .data = NULL },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(of, a3700_spi_dt_ids);
|
|
|
|
|
|
|
|
static int a3700_spi_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
struct device_node *of_node = dev->of_node;
|
|
|
|
struct resource *res;
|
|
|
|
struct spi_master *master;
|
|
|
|
struct a3700_spi *spi;
|
|
|
|
u32 num_cs = 0;
|
2016-12-13 18:28:12 +08:00
|
|
|
int irq, ret = 0;
|
2016-12-08 22:58:44 +08:00
|
|
|
|
|
|
|
master = spi_alloc_master(dev, sizeof(*spi));
|
|
|
|
if (!master) {
|
|
|
|
dev_err(dev, "master allocation failed\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (of_property_read_u32(of_node, "num-cs", &num_cs)) {
|
|
|
|
dev_err(dev, "could not find num-cs\n");
|
|
|
|
ret = -ENXIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
master->bus_num = pdev->id;
|
|
|
|
master->dev.of_node = of_node;
|
|
|
|
master->mode_bits = SPI_MODE_3;
|
|
|
|
master->num_chipselect = num_cs;
|
|
|
|
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(32);
|
|
|
|
master->prepare_message = a3700_spi_prepare_message;
|
|
|
|
master->transfer_one = a3700_spi_transfer_one;
|
|
|
|
master->unprepare_message = a3700_spi_unprepare_message;
|
|
|
|
master->set_cs = a3700_spi_set_cs;
|
|
|
|
master->flags = SPI_MASTER_HALF_DUPLEX;
|
|
|
|
master->mode_bits |= (SPI_RX_DUAL | SPI_RX_DUAL |
|
|
|
|
SPI_RX_QUAD | SPI_TX_QUAD);
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, master);
|
|
|
|
|
|
|
|
spi = spi_master_get_devdata(master);
|
|
|
|
memset(spi, 0, sizeof(struct a3700_spi));
|
|
|
|
|
|
|
|
spi->master = master;
|
|
|
|
spi->instr_cnt = A3700_INSTR_CNT;
|
|
|
|
spi->addr_cnt = A3700_ADDR_CNT;
|
|
|
|
spi->hdr_cnt = A3700_INSTR_CNT + A3700_ADDR_CNT +
|
|
|
|
A3700_DUMMY_CNT;
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
spi->base = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR(spi->base)) {
|
|
|
|
ret = PTR_ERR(spi->base);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2016-12-13 18:28:12 +08:00
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
if (irq < 0) {
|
|
|
|
dev_err(dev, "could not get irq: %d\n", irq);
|
2016-12-08 22:58:44 +08:00
|
|
|
ret = -ENXIO;
|
|
|
|
goto error;
|
|
|
|
}
|
2016-12-13 18:28:12 +08:00
|
|
|
spi->irq = irq;
|
2016-12-08 22:58:44 +08:00
|
|
|
|
|
|
|
init_completion(&spi->done);
|
|
|
|
|
|
|
|
spi->clk = devm_clk_get(dev, NULL);
|
|
|
|
if (IS_ERR(spi->clk)) {
|
|
|
|
dev_err(dev, "could not find clk: %ld\n", PTR_ERR(spi->clk));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = clk_prepare(spi->clk);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "could not prepare clk: %d\n", ret);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = a3700_spi_init(spi);
|
|
|
|
if (ret)
|
|
|
|
goto error_clk;
|
|
|
|
|
|
|
|
ret = devm_request_irq(dev, spi->irq, a3700_spi_interrupt, 0,
|
|
|
|
dev_name(dev), master);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "could not request IRQ: %d\n", ret);
|
|
|
|
goto error_clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = devm_spi_register_master(dev, master);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Failed to register master\n");
|
|
|
|
goto error_clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_clk:
|
|
|
|
clk_disable_unprepare(spi->clk);
|
|
|
|
error:
|
|
|
|
spi_master_put(master);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int a3700_spi_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct spi_master *master = platform_get_drvdata(pdev);
|
|
|
|
struct a3700_spi *spi = spi_master_get_devdata(master);
|
|
|
|
|
|
|
|
clk_unprepare(spi->clk);
|
|
|
|
spi_master_put(master);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct platform_driver a3700_spi_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = DRIVER_NAME,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.of_match_table = of_match_ptr(a3700_spi_dt_ids),
|
|
|
|
},
|
|
|
|
.probe = a3700_spi_probe,
|
|
|
|
.remove = a3700_spi_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
module_platform_driver(a3700_spi_driver);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Armada-3700 SPI driver");
|
|
|
|
MODULE_AUTHOR("Wilson Ding <dingwei@marvell.com>");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_ALIAS("platform:" DRIVER_NAME);
|