mirror of https://gitee.com/openkylin/linux.git
Merge remote-tracking branches 'spi/topic/mem' and 'spi/topic/mtd' into spi-next
This commit is contained in:
commit
74ff666bd7
|
@ -39,15 +39,6 @@ config SPI_ASPEED_SMC
|
|||
and support for the SPI flash memory controller (SPI) for
|
||||
the host firmware. The implementation only supports SPI NOR.
|
||||
|
||||
config SPI_ATMEL_QUADSPI
|
||||
tristate "Atmel Quad SPI Controller"
|
||||
depends on ARCH_AT91 || (ARM && COMPILE_TEST)
|
||||
depends on OF && HAS_IOMEM
|
||||
help
|
||||
This enables support for the Quad SPI controller in master mode.
|
||||
This driver does not support generic SPI. The implementation only
|
||||
supports SPI NOR.
|
||||
|
||||
config SPI_CADENCE_QUADSPI
|
||||
tristate "Cadence Quad SPI controller"
|
||||
depends on OF && (ARM || ARM64 || COMPILE_TEST)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o
|
||||
obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
|
||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||
|
|
|
@ -91,6 +91,15 @@ config SPI_AT91_USART
|
|||
This selects a driver for the AT91 USART Controller as SPI Master,
|
||||
present on AT91 and SAMA5 SoC series.
|
||||
|
||||
config SPI_ATMEL_QUADSPI
|
||||
tristate "Atmel Quad SPI Controller"
|
||||
depends on ARCH_AT91 || (ARM && COMPILE_TEST && !ARCH_EBSA110)
|
||||
depends on OF && HAS_IOMEM
|
||||
help
|
||||
This enables support for the Quad SPI controller in master mode.
|
||||
This driver does not support generic SPI. The implementation only
|
||||
supports spi-mem interface.
|
||||
|
||||
config SPI_AU1550
|
||||
tristate "Au1550/Au1200/Au1300 SPI Controller"
|
||||
depends on MIPS_ALCHEMY
|
||||
|
|
|
@ -16,6 +16,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
|
|||
obj-$(CONFIG_SPI_ALTERA) += spi-altera.o
|
||||
obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
|
||||
obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o
|
||||
obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
|
||||
obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o
|
||||
obj-$(CONFIG_SPI_ATH79) += spi-ath79.o
|
||||
obj-$(CONFIG_SPI_AU1550) += spi-au1550.o
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
* Driver for Atmel QSPI Controller
|
||||
*
|
||||
* Copyright (C) 2015 Atmel Corporation
|
||||
* Copyright (C) 2018 Cryptera A/S
|
||||
*
|
||||
* Author: Cyrille Pitchen <cyrille.pitchen@atmel.com>
|
||||
* Author: Piotr Bugalski <bugalski.piotr@gmail.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
|
||||
|
@ -27,14 +29,10 @@
|
|||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/platform_data/atmel.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
|
||||
/* QSPI register offsets */
|
||||
#define QSPI_CR 0x0000 /* Control Register */
|
||||
|
@ -67,7 +65,7 @@
|
|||
#define QSPI_CR_LASTXFER BIT(24)
|
||||
|
||||
/* Bitfields in QSPI_MR (Mode Register) */
|
||||
#define QSPI_MR_SSM BIT(0)
|
||||
#define QSPI_MR_SMM BIT(0)
|
||||
#define QSPI_MR_LLB BIT(1)
|
||||
#define QSPI_MR_WDRBT BIT(2)
|
||||
#define QSPI_MR_SMRM BIT(3)
|
||||
|
@ -157,33 +155,24 @@ struct atmel_qspi {
|
|||
struct clk *clk;
|
||||
struct platform_device *pdev;
|
||||
u32 pending;
|
||||
|
||||
struct spi_nor nor;
|
||||
u32 clk_rate;
|
||||
struct completion cmd_completion;
|
||||
};
|
||||
|
||||
struct atmel_qspi_command {
|
||||
union {
|
||||
struct {
|
||||
u32 instruction:1;
|
||||
u32 address:3;
|
||||
u32 mode:1;
|
||||
u32 dummy:1;
|
||||
u32 data:1;
|
||||
u32 reserved:25;
|
||||
} bits;
|
||||
u32 word;
|
||||
} enable;
|
||||
u8 instruction;
|
||||
u8 mode;
|
||||
u8 num_mode_cycles;
|
||||
u8 num_dummy_cycles;
|
||||
u32 address;
|
||||
struct qspi_mode {
|
||||
u8 cmd_buswidth;
|
||||
u8 addr_buswidth;
|
||||
u8 data_buswidth;
|
||||
u32 config;
|
||||
};
|
||||
|
||||
size_t buf_len;
|
||||
const void *tx_buf;
|
||||
void *rx_buf;
|
||||
static const struct qspi_mode sama5d2_qspi_modes[] = {
|
||||
{ 1, 1, 1, QSPI_IFR_WIDTH_SINGLE_BIT_SPI },
|
||||
{ 1, 1, 2, QSPI_IFR_WIDTH_DUAL_OUTPUT },
|
||||
{ 1, 1, 4, QSPI_IFR_WIDTH_QUAD_OUTPUT },
|
||||
{ 1, 2, 2, QSPI_IFR_WIDTH_DUAL_IO },
|
||||
{ 1, 4, 4, QSPI_IFR_WIDTH_QUAD_IO },
|
||||
{ 2, 2, 2, QSPI_IFR_WIDTH_DUAL_CMD },
|
||||
{ 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD },
|
||||
};
|
||||
|
||||
/* Register access functions */
|
||||
|
@ -197,246 +186,140 @@ static inline void qspi_writel(struct atmel_qspi *aq, u32 reg, u32 value)
|
|||
writel_relaxed(value, aq->regs + reg);
|
||||
}
|
||||
|
||||
static int atmel_qspi_run_transfer(struct atmel_qspi *aq,
|
||||
const struct atmel_qspi_command *cmd)
|
||||
static inline bool is_compatible(const struct spi_mem_op *op,
|
||||
const struct qspi_mode *mode)
|
||||
{
|
||||
void __iomem *ahb_mem;
|
||||
if (op->cmd.buswidth != mode->cmd_buswidth)
|
||||
return false;
|
||||
|
||||
/* Then fallback to a PIO transfer (memcpy() DOES NOT work!) */
|
||||
ahb_mem = aq->mem;
|
||||
if (cmd->enable.bits.address)
|
||||
ahb_mem += cmd->address;
|
||||
if (cmd->tx_buf)
|
||||
_memcpy_toio(ahb_mem, cmd->tx_buf, cmd->buf_len);
|
||||
else
|
||||
_memcpy_fromio(cmd->rx_buf, ahb_mem, cmd->buf_len);
|
||||
if (op->addr.nbytes && op->addr.buswidth != mode->addr_buswidth)
|
||||
return false;
|
||||
|
||||
return 0;
|
||||
if (op->data.nbytes && op->data.buswidth != mode->data_buswidth)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static void atmel_qspi_debug_command(struct atmel_qspi *aq,
|
||||
const struct atmel_qspi_command *cmd,
|
||||
u32 ifr)
|
||||
static int find_mode(const struct spi_mem_op *op)
|
||||
{
|
||||
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
|
||||
size_t len = 0;
|
||||
int i;
|
||||
u32 i;
|
||||
|
||||
if (cmd->enable.bits.instruction)
|
||||
cmd_buf[len++] = cmd->instruction;
|
||||
for (i = 0; i < ARRAY_SIZE(sama5d2_qspi_modes); i++)
|
||||
if (is_compatible(op, &sama5d2_qspi_modes[i]))
|
||||
return i;
|
||||
|
||||
for (i = cmd->enable.bits.address-1; i >= 0; --i)
|
||||
cmd_buf[len++] = (cmd->address >> (i << 3)) & 0xff;
|
||||
|
||||
if (cmd->enable.bits.mode)
|
||||
cmd_buf[len++] = cmd->mode;
|
||||
|
||||
if (cmd->enable.bits.dummy) {
|
||||
int num = cmd->num_dummy_cycles;
|
||||
|
||||
switch (ifr & QSPI_IFR_WIDTH_MASK) {
|
||||
case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
|
||||
case QSPI_IFR_WIDTH_DUAL_OUTPUT:
|
||||
case QSPI_IFR_WIDTH_QUAD_OUTPUT:
|
||||
num >>= 3;
|
||||
break;
|
||||
case QSPI_IFR_WIDTH_DUAL_IO:
|
||||
case QSPI_IFR_WIDTH_DUAL_CMD:
|
||||
num >>= 2;
|
||||
break;
|
||||
case QSPI_IFR_WIDTH_QUAD_IO:
|
||||
case QSPI_IFR_WIDTH_QUAD_CMD:
|
||||
num >>= 1;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < num; ++i)
|
||||
cmd_buf[len++] = 0;
|
||||
}
|
||||
|
||||
/* Dump the SPI command */
|
||||
print_hex_dump(KERN_DEBUG, "qspi cmd: ", DUMP_PREFIX_NONE,
|
||||
32, 1, cmd_buf, len, false);
|
||||
|
||||
#ifdef VERBOSE_DEBUG
|
||||
/* If verbose debug is enabled, also dump the TX data */
|
||||
if (cmd->enable.bits.data && cmd->tx_buf)
|
||||
print_hex_dump(KERN_DEBUG, "qspi tx : ", DUMP_PREFIX_NONE,
|
||||
32, 1, cmd->tx_buf, cmd->buf_len, false);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
#define atmel_qspi_debug_command(aq, cmd, ifr)
|
||||
#endif
|
||||
|
||||
static int atmel_qspi_run_command(struct atmel_qspi *aq,
|
||||
const struct atmel_qspi_command *cmd,
|
||||
u32 ifr_tfrtyp, enum spi_nor_protocol proto)
|
||||
static bool atmel_qspi_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
if (find_mode(op) < 0)
|
||||
return false;
|
||||
|
||||
/* special case not supported by hardware */
|
||||
if (op->addr.nbytes == 2 && op->cmd.buswidth != op->addr.buswidth &&
|
||||
op->dummy.nbytes == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
{
|
||||
struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->master);
|
||||
int mode;
|
||||
u32 dummy_cycles = 0;
|
||||
u32 iar, icr, ifr, sr;
|
||||
int err = 0;
|
||||
|
||||
iar = 0;
|
||||
icr = 0;
|
||||
ifr = ifr_tfrtyp;
|
||||
icr = QSPI_ICR_INST(op->cmd.opcode);
|
||||
ifr = QSPI_IFR_INSTEN;
|
||||
|
||||
/* Set the SPI protocol */
|
||||
switch (proto) {
|
||||
case SNOR_PROTO_1_1_1:
|
||||
ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
|
||||
break;
|
||||
qspi_writel(aq, QSPI_MR, QSPI_MR_SMM);
|
||||
|
||||
case SNOR_PROTO_1_1_2:
|
||||
ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT;
|
||||
break;
|
||||
mode = find_mode(op);
|
||||
if (mode < 0)
|
||||
return -ENOTSUPP;
|
||||
|
||||
case SNOR_PROTO_1_1_4:
|
||||
ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT;
|
||||
break;
|
||||
ifr |= sama5d2_qspi_modes[mode].config;
|
||||
|
||||
case SNOR_PROTO_1_2_2:
|
||||
ifr |= QSPI_IFR_WIDTH_DUAL_IO;
|
||||
break;
|
||||
if (op->dummy.buswidth && op->dummy.nbytes)
|
||||
dummy_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;
|
||||
|
||||
case SNOR_PROTO_1_4_4:
|
||||
ifr |= QSPI_IFR_WIDTH_QUAD_IO;
|
||||
break;
|
||||
|
||||
case SNOR_PROTO_2_2_2:
|
||||
ifr |= QSPI_IFR_WIDTH_DUAL_CMD;
|
||||
break;
|
||||
|
||||
case SNOR_PROTO_4_4_4:
|
||||
ifr |= QSPI_IFR_WIDTH_QUAD_CMD;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Compute instruction parameters */
|
||||
if (cmd->enable.bits.instruction) {
|
||||
icr |= QSPI_ICR_INST(cmd->instruction);
|
||||
ifr |= QSPI_IFR_INSTEN;
|
||||
}
|
||||
|
||||
/* Compute address parameters */
|
||||
switch (cmd->enable.bits.address) {
|
||||
case 4:
|
||||
ifr |= QSPI_IFR_ADDRL;
|
||||
/* fall through to the 24bit (3 byte) address case. */
|
||||
case 3:
|
||||
iar = (cmd->enable.bits.data) ? 0 : cmd->address;
|
||||
ifr |= QSPI_IFR_ADDREN;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Compute option parameters */
|
||||
if (cmd->enable.bits.mode && cmd->num_mode_cycles) {
|
||||
u32 mode_cycle_bits, mode_bits;
|
||||
|
||||
icr |= QSPI_ICR_OPT(cmd->mode);
|
||||
ifr |= QSPI_IFR_OPTEN;
|
||||
|
||||
switch (ifr & QSPI_IFR_WIDTH_MASK) {
|
||||
case QSPI_IFR_WIDTH_SINGLE_BIT_SPI:
|
||||
case QSPI_IFR_WIDTH_DUAL_OUTPUT:
|
||||
case QSPI_IFR_WIDTH_QUAD_OUTPUT:
|
||||
mode_cycle_bits = 1;
|
||||
if (op->addr.buswidth) {
|
||||
switch (op->addr.nbytes) {
|
||||
case 0:
|
||||
break;
|
||||
case QSPI_IFR_WIDTH_DUAL_IO:
|
||||
case QSPI_IFR_WIDTH_DUAL_CMD:
|
||||
mode_cycle_bits = 2;
|
||||
break;
|
||||
case QSPI_IFR_WIDTH_QUAD_IO:
|
||||
case QSPI_IFR_WIDTH_QUAD_CMD:
|
||||
mode_cycle_bits = 4;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mode_bits = cmd->num_mode_cycles * mode_cycle_bits;
|
||||
switch (mode_bits) {
|
||||
case 1:
|
||||
ifr |= QSPI_IFR_OPTL_1BIT;
|
||||
ifr |= QSPI_IFR_OPTEN | QSPI_IFR_OPTL_8BIT;
|
||||
icr |= QSPI_ICR_OPT(op->addr.val & 0xff);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
ifr |= QSPI_IFR_OPTL_2BIT;
|
||||
if (dummy_cycles < 8 / op->addr.buswidth) {
|
||||
ifr &= ~QSPI_IFR_INSTEN;
|
||||
ifr |= QSPI_IFR_ADDREN;
|
||||
iar = (op->cmd.opcode << 16) |
|
||||
(op->addr.val & 0xffff);
|
||||
} else {
|
||||
ifr |= QSPI_IFR_ADDREN;
|
||||
iar = (op->addr.val << 8) & 0xffffff;
|
||||
dummy_cycles -= 8 / op->addr.buswidth;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
ifr |= QSPI_IFR_ADDREN;
|
||||
iar = op->addr.val & 0xffffff;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
ifr |= QSPI_IFR_OPTL_4BIT;
|
||||
ifr |= QSPI_IFR_ADDREN | QSPI_IFR_ADDRL;
|
||||
iar = op->addr.val & 0x7ffffff;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
ifr |= QSPI_IFR_OPTL_8BIT;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set number of dummy cycles */
|
||||
if (cmd->enable.bits.dummy)
|
||||
ifr |= QSPI_IFR_NBDUM(cmd->num_dummy_cycles);
|
||||
if (dummy_cycles)
|
||||
ifr |= QSPI_IFR_NBDUM(dummy_cycles);
|
||||
|
||||
/* Set data enable */
|
||||
if (cmd->enable.bits.data) {
|
||||
if (op->data.nbytes)
|
||||
ifr |= QSPI_IFR_DATAEN;
|
||||
|
||||
/* Special case for Continuous Read Mode */
|
||||
if (!cmd->tx_buf && !cmd->rx_buf)
|
||||
ifr |= QSPI_IFR_CRM;
|
||||
}
|
||||
if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes)
|
||||
ifr |= QSPI_IFR_TFRTYP_TRSFR_READ;
|
||||
else
|
||||
ifr |= QSPI_IFR_TFRTYP_TRSFR_WRITE;
|
||||
|
||||
/* Clear pending interrupts */
|
||||
(void)qspi_readl(aq, QSPI_SR);
|
||||
|
||||
/* Set QSPI Instruction Frame registers */
|
||||
atmel_qspi_debug_command(aq, cmd, ifr);
|
||||
qspi_writel(aq, QSPI_IAR, iar);
|
||||
qspi_writel(aq, QSPI_ICR, icr);
|
||||
qspi_writel(aq, QSPI_IFR, ifr);
|
||||
|
||||
/* Skip to the final steps if there is no data */
|
||||
if (!cmd->enable.bits.data)
|
||||
goto no_data;
|
||||
if (op->data.nbytes) {
|
||||
/* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */
|
||||
(void)qspi_readl(aq, QSPI_IFR);
|
||||
|
||||
/* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */
|
||||
(void)qspi_readl(aq, QSPI_IFR);
|
||||
/* Send/Receive data */
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
_memcpy_fromio(op->data.buf.in,
|
||||
aq->mem + iar, op->data.nbytes);
|
||||
else
|
||||
_memcpy_toio(aq->mem + iar,
|
||||
op->data.buf.out, op->data.nbytes);
|
||||
|
||||
/* Stop here for continuous read */
|
||||
if (!cmd->tx_buf && !cmd->rx_buf)
|
||||
return 0;
|
||||
/* Send/Receive data */
|
||||
err = atmel_qspi_run_transfer(aq, cmd);
|
||||
/* Release the chip-select */
|
||||
qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER);
|
||||
}
|
||||
|
||||
/* Release the chip-select */
|
||||
qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
#if defined(DEBUG) && defined(VERBOSE_DEBUG)
|
||||
/*
|
||||
* If verbose debug is enabled, also dump the RX data in addition to
|
||||
* the SPI command previously dumped by atmel_qspi_debug_command()
|
||||
*/
|
||||
if (cmd->rx_buf)
|
||||
print_hex_dump(KERN_DEBUG, "qspi rx : ", DUMP_PREFIX_NONE,
|
||||
32, 1, cmd->rx_buf, cmd->buf_len, false);
|
||||
#endif
|
||||
no_data:
|
||||
/* Poll INSTRuction End status */
|
||||
sr = qspi_readl(aq, QSPI_SR);
|
||||
if ((sr & QSPI_SR_CMD_COMPLETED) == QSPI_SR_CMD_COMPLETED)
|
||||
|
@ -454,129 +337,50 @@ static int atmel_qspi_run_command(struct atmel_qspi *aq,
|
|||
return err;
|
||||
}
|
||||
|
||||
static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode,
|
||||
u8 *buf, int len)
|
||||
const char *atmel_qspi_get_name(struct spi_mem *spimem)
|
||||
{
|
||||
struct atmel_qspi *aq = nor->priv;
|
||||
struct atmel_qspi_command cmd;
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.enable.bits.instruction = 1;
|
||||
cmd.enable.bits.data = 1;
|
||||
cmd.instruction = opcode;
|
||||
cmd.rx_buf = buf;
|
||||
cmd.buf_len = len;
|
||||
return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ,
|
||||
nor->reg_proto);
|
||||
return dev_name(spimem->spi->dev.parent);
|
||||
}
|
||||
|
||||
static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode,
|
||||
u8 *buf, int len)
|
||||
{
|
||||
struct atmel_qspi *aq = nor->priv;
|
||||
struct atmel_qspi_command cmd;
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.enable.bits.instruction = 1;
|
||||
cmd.enable.bits.data = (buf != NULL && len > 0);
|
||||
cmd.instruction = opcode;
|
||||
cmd.tx_buf = buf;
|
||||
cmd.buf_len = len;
|
||||
return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
|
||||
nor->reg_proto);
|
||||
}
|
||||
|
||||
static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
const u_char *write_buf)
|
||||
{
|
||||
struct atmel_qspi *aq = nor->priv;
|
||||
struct atmel_qspi_command cmd;
|
||||
ssize_t ret;
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.enable.bits.instruction = 1;
|
||||
cmd.enable.bits.address = nor->addr_width;
|
||||
cmd.enable.bits.data = 1;
|
||||
cmd.instruction = nor->program_opcode;
|
||||
cmd.address = (u32)to;
|
||||
cmd.tx_buf = write_buf;
|
||||
cmd.buf_len = len;
|
||||
ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM,
|
||||
nor->write_proto);
|
||||
return (ret < 0) ? ret : len;
|
||||
}
|
||||
|
||||
static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs)
|
||||
{
|
||||
struct atmel_qspi *aq = nor->priv;
|
||||
struct atmel_qspi_command cmd;
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.enable.bits.instruction = 1;
|
||||
cmd.enable.bits.address = nor->addr_width;
|
||||
cmd.instruction = nor->erase_opcode;
|
||||
cmd.address = (u32)offs;
|
||||
return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE,
|
||||
nor->reg_proto);
|
||||
}
|
||||
|
||||
static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
u_char *read_buf)
|
||||
{
|
||||
struct atmel_qspi *aq = nor->priv;
|
||||
struct atmel_qspi_command cmd;
|
||||
u8 num_mode_cycles, num_dummy_cycles;
|
||||
ssize_t ret;
|
||||
|
||||
if (nor->read_dummy >= 2) {
|
||||
num_mode_cycles = 2;
|
||||
num_dummy_cycles = nor->read_dummy - 2;
|
||||
} else {
|
||||
num_mode_cycles = nor->read_dummy;
|
||||
num_dummy_cycles = 0;
|
||||
}
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.enable.bits.instruction = 1;
|
||||
cmd.enable.bits.address = nor->addr_width;
|
||||
cmd.enable.bits.mode = (num_mode_cycles > 0);
|
||||
cmd.enable.bits.dummy = (num_dummy_cycles > 0);
|
||||
cmd.enable.bits.data = 1;
|
||||
cmd.instruction = nor->read_opcode;
|
||||
cmd.address = (u32)from;
|
||||
cmd.mode = 0xff; /* This value prevents from entering the 0-4-4 mode */
|
||||
cmd.num_mode_cycles = num_mode_cycles;
|
||||
cmd.num_dummy_cycles = num_dummy_cycles;
|
||||
cmd.rx_buf = read_buf;
|
||||
cmd.buf_len = len;
|
||||
ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ_MEM,
|
||||
nor->read_proto);
|
||||
return (ret < 0) ? ret : len;
|
||||
}
|
||||
|
||||
static int atmel_qspi_init(struct atmel_qspi *aq)
|
||||
static const struct spi_controller_mem_ops atmel_qspi_mem_ops = {
|
||||
.supports_op = atmel_qspi_supports_op,
|
||||
.exec_op = atmel_qspi_exec_op,
|
||||
.get_name = atmel_qspi_get_name
|
||||
};
|
||||
|
||||
static int atmel_qspi_setup(struct spi_device *spi)
|
||||
{
|
||||
struct spi_controller *ctrl = spi->master;
|
||||
struct atmel_qspi *aq = spi_controller_get_devdata(ctrl);
|
||||
unsigned long src_rate;
|
||||
u32 mr, scr, scbr;
|
||||
u32 scr, scbr;
|
||||
|
||||
/* Reset the QSPI controller */
|
||||
qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST);
|
||||
if (ctrl->busy)
|
||||
return -EBUSY;
|
||||
|
||||
/* Set the QSPI controller in Serial Memory Mode */
|
||||
mr = QSPI_MR_NBBITS(8) | QSPI_MR_SSM;
|
||||
qspi_writel(aq, QSPI_MR, mr);
|
||||
if (!spi->max_speed_hz)
|
||||
return -EINVAL;
|
||||
|
||||
src_rate = clk_get_rate(aq->clk);
|
||||
if (!src_rate)
|
||||
return -EINVAL;
|
||||
|
||||
/* Compute the QSPI baudrate */
|
||||
scbr = DIV_ROUND_UP(src_rate, aq->clk_rate);
|
||||
scbr = DIV_ROUND_UP(src_rate, spi->max_speed_hz);
|
||||
if (scbr > 0)
|
||||
scbr--;
|
||||
|
||||
scr = QSPI_SCR_SCBR(scbr);
|
||||
qspi_writel(aq, QSPI_SCR, scr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_qspi_init(struct atmel_qspi *aq)
|
||||
{
|
||||
/* Reset the QSPI controller */
|
||||
qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST);
|
||||
|
||||
/* Enable the QSPI controller */
|
||||
qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIEN);
|
||||
|
||||
|
@ -604,38 +408,25 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
|
|||
|
||||
static int atmel_qspi_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct spi_nor_hwcaps hwcaps = {
|
||||
.mask = SNOR_HWCAPS_READ |
|
||||
SNOR_HWCAPS_READ_FAST |
|
||||
SNOR_HWCAPS_READ_1_1_2 |
|
||||
SNOR_HWCAPS_READ_1_2_2 |
|
||||
SNOR_HWCAPS_READ_2_2_2 |
|
||||
SNOR_HWCAPS_READ_1_1_4 |
|
||||
SNOR_HWCAPS_READ_1_4_4 |
|
||||
SNOR_HWCAPS_READ_4_4_4 |
|
||||
SNOR_HWCAPS_PP |
|
||||
SNOR_HWCAPS_PP_1_1_4 |
|
||||
SNOR_HWCAPS_PP_1_4_4 |
|
||||
SNOR_HWCAPS_PP_4_4_4,
|
||||
};
|
||||
struct device_node *child, *np = pdev->dev.of_node;
|
||||
struct spi_controller *ctrl;
|
||||
struct atmel_qspi *aq;
|
||||
struct resource *res;
|
||||
struct spi_nor *nor;
|
||||
struct mtd_info *mtd;
|
||||
int irq, err = 0;
|
||||
|
||||
if (of_get_child_count(np) != 1)
|
||||
return -ENODEV;
|
||||
child = of_get_next_child(np, NULL);
|
||||
ctrl = spi_alloc_master(&pdev->dev, sizeof(*aq));
|
||||
if (!ctrl)
|
||||
return -ENOMEM;
|
||||
|
||||
aq = devm_kzalloc(&pdev->dev, sizeof(*aq), GFP_KERNEL);
|
||||
if (!aq) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
|
||||
ctrl->setup = atmel_qspi_setup;
|
||||
ctrl->bus_num = -1;
|
||||
ctrl->mem_ops = &atmel_qspi_mem_ops;
|
||||
ctrl->num_chipselect = 1;
|
||||
ctrl->dev.of_node = pdev->dev.of_node;
|
||||
platform_set_drvdata(pdev, ctrl);
|
||||
|
||||
aq = spi_controller_get_devdata(ctrl);
|
||||
|
||||
platform_set_drvdata(pdev, aq);
|
||||
init_completion(&aq->cmd_completion);
|
||||
aq->pdev = pdev;
|
||||
|
||||
|
@ -684,54 +475,30 @@ static int atmel_qspi_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
goto disable_clk;
|
||||
|
||||
/* Setup the spi-nor */
|
||||
nor = &aq->nor;
|
||||
mtd = &nor->mtd;
|
||||
|
||||
nor->dev = &pdev->dev;
|
||||
spi_nor_set_flash_node(nor, child);
|
||||
nor->priv = aq;
|
||||
mtd->priv = nor;
|
||||
|
||||
nor->read_reg = atmel_qspi_read_reg;
|
||||
nor->write_reg = atmel_qspi_write_reg;
|
||||
nor->read = atmel_qspi_read;
|
||||
nor->write = atmel_qspi_write;
|
||||
nor->erase = atmel_qspi_erase;
|
||||
|
||||
err = of_property_read_u32(child, "spi-max-frequency", &aq->clk_rate);
|
||||
if (err < 0)
|
||||
goto disable_clk;
|
||||
|
||||
err = atmel_qspi_init(aq);
|
||||
if (err)
|
||||
goto disable_clk;
|
||||
|
||||
err = spi_nor_scan(nor, NULL, &hwcaps);
|
||||
err = spi_register_controller(ctrl);
|
||||
if (err)
|
||||
goto disable_clk;
|
||||
|
||||
err = mtd_device_register(mtd, NULL, 0);
|
||||
if (err)
|
||||
goto disable_clk;
|
||||
|
||||
of_node_put(child);
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clk:
|
||||
clk_disable_unprepare(aq->clk);
|
||||
exit:
|
||||
of_node_put(child);
|
||||
spi_controller_put(ctrl);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int atmel_qspi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct atmel_qspi *aq = platform_get_drvdata(pdev);
|
||||
struct spi_controller *ctrl = platform_get_drvdata(pdev);
|
||||
struct atmel_qspi *aq = spi_controller_get_devdata(ctrl);
|
||||
|
||||
mtd_device_unregister(&aq->nor.mtd);
|
||||
spi_unregister_controller(ctrl);
|
||||
qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIDIS);
|
||||
clk_disable_unprepare(aq->clk);
|
||||
return 0;
|
||||
|
@ -777,5 +544,6 @@ static struct platform_driver atmel_qspi_driver = {
|
|||
module_platform_driver(atmel_qspi_driver);
|
||||
|
||||
MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>");
|
||||
MODULE_AUTHOR("Piotr Bugalski <bugalski.piotr@gmail.com");
|
||||
MODULE_DESCRIPTION("Atmel QSPI Controller driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -149,7 +149,7 @@ static bool spi_mem_default_supports_op(struct spi_mem *mem,
|
|||
spi_check_buswidth_req(mem, op->dummy.buswidth, true))
|
||||
return false;
|
||||
|
||||
if (op->data.nbytes &&
|
||||
if (op->data.dir != SPI_MEM_NO_DATA &&
|
||||
spi_check_buswidth_req(mem, op->data.buswidth,
|
||||
op->data.dir == SPI_MEM_DATA_OUT))
|
||||
return false;
|
||||
|
@ -220,6 +220,44 @@ bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_supports_op);
|
||||
|
||||
static int spi_mem_access_start(struct spi_mem *mem)
|
||||
{
|
||||
struct spi_controller *ctlr = mem->spi->controller;
|
||||
|
||||
/*
|
||||
* Flush the message queue before executing our SPI memory
|
||||
* operation to prevent preemption of regular SPI transfers.
|
||||
*/
|
||||
spi_flush_queue(ctlr);
|
||||
|
||||
if (ctlr->auto_runtime_pm) {
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_get_sync(ctlr->dev.parent);
|
||||
if (ret < 0) {
|
||||
dev_err(&ctlr->dev, "Failed to power device: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&ctlr->bus_lock_mutex);
|
||||
mutex_lock(&ctlr->io_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void spi_mem_access_end(struct spi_mem *mem)
|
||||
{
|
||||
struct spi_controller *ctlr = mem->spi->controller;
|
||||
|
||||
mutex_unlock(&ctlr->io_mutex);
|
||||
mutex_unlock(&ctlr->bus_lock_mutex);
|
||||
|
||||
if (ctlr->auto_runtime_pm)
|
||||
pm_runtime_put(ctlr->dev.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_mem_exec_op() - Execute a memory operation
|
||||
* @mem: the SPI memory
|
||||
|
@ -249,30 +287,13 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|||
return -ENOTSUPP;
|
||||
|
||||
if (ctlr->mem_ops) {
|
||||
/*
|
||||
* Flush the message queue before executing our SPI memory
|
||||
* operation to prevent preemption of regular SPI transfers.
|
||||
*/
|
||||
spi_flush_queue(ctlr);
|
||||
ret = spi_mem_access_start(mem);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ctlr->auto_runtime_pm) {
|
||||
ret = pm_runtime_get_sync(ctlr->dev.parent);
|
||||
if (ret < 0) {
|
||||
dev_err(&ctlr->dev,
|
||||
"Failed to power device: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&ctlr->bus_lock_mutex);
|
||||
mutex_lock(&ctlr->io_mutex);
|
||||
ret = ctlr->mem_ops->exec_op(mem, op);
|
||||
mutex_unlock(&ctlr->io_mutex);
|
||||
mutex_unlock(&ctlr->bus_lock_mutex);
|
||||
|
||||
if (ctlr->auto_runtime_pm)
|
||||
pm_runtime_put(ctlr->dev.parent);
|
||||
spi_mem_access_end(mem);
|
||||
|
||||
/*
|
||||
* Some controllers only optimize specific paths (typically the
|
||||
|
@ -418,6 +439,210 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
|
||||
|
||||
static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, void *buf)
|
||||
{
|
||||
struct spi_mem_op op = desc->info.op_tmpl;
|
||||
int ret;
|
||||
|
||||
op.addr.val = desc->info.offset + offs;
|
||||
op.data.buf.in = buf;
|
||||
op.data.nbytes = len;
|
||||
ret = spi_mem_adjust_op_size(desc->mem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_mem_exec_op(desc->mem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return op.data.nbytes;
|
||||
}
|
||||
|
||||
static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, const void *buf)
|
||||
{
|
||||
struct spi_mem_op op = desc->info.op_tmpl;
|
||||
int ret;
|
||||
|
||||
op.addr.val = desc->info.offset + offs;
|
||||
op.data.buf.out = buf;
|
||||
op.data.nbytes = len;
|
||||
ret = spi_mem_adjust_op_size(desc->mem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_mem_exec_op(desc->mem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return op.data.nbytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_mem_dirmap_create() - Create a direct mapping descriptor
|
||||
* @mem: SPI mem device this direct mapping should be created for
|
||||
* @info: direct mapping information
|
||||
*
|
||||
* This function is creating a direct mapping descriptor which can then be used
|
||||
* to access the memory using spi_mem_dirmap_read() or spi_mem_dirmap_write().
|
||||
* If the SPI controller driver does not support direct mapping, this function
|
||||
* fallback to an implementation using spi_mem_exec_op(), so that the caller
|
||||
* doesn't have to bother implementing a fallback on his own.
|
||||
*
|
||||
* Return: a valid pointer in case of success, and ERR_PTR() otherwise.
|
||||
*/
|
||||
struct spi_mem_dirmap_desc *
|
||||
spi_mem_dirmap_create(struct spi_mem *mem,
|
||||
const struct spi_mem_dirmap_info *info)
|
||||
{
|
||||
struct spi_controller *ctlr = mem->spi->controller;
|
||||
struct spi_mem_dirmap_desc *desc;
|
||||
int ret = -ENOTSUPP;
|
||||
|
||||
/* Make sure the number of address cycles is between 1 and 8 bytes. */
|
||||
if (!info->op_tmpl.addr.nbytes || info->op_tmpl.addr.nbytes > 8)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/* data.dir should either be SPI_MEM_DATA_IN or SPI_MEM_DATA_OUT. */
|
||||
if (info->op_tmpl.data.dir == SPI_MEM_NO_DATA)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
|
||||
if (!desc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
desc->mem = mem;
|
||||
desc->info = *info;
|
||||
if (ctlr->mem_ops && ctlr->mem_ops->dirmap_create)
|
||||
ret = ctlr->mem_ops->dirmap_create(desc);
|
||||
|
||||
if (ret) {
|
||||
desc->nodirmap = true;
|
||||
if (!spi_mem_supports_op(desc->mem, &desc->info.op_tmpl))
|
||||
ret = -ENOTSUPP;
|
||||
else
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
kfree(desc);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_dirmap_create);
|
||||
|
||||
/**
|
||||
* spi_mem_dirmap_destroy() - Destroy a direct mapping descriptor
|
||||
* @desc: the direct mapping descriptor to destroy
|
||||
* @info: direct mapping information
|
||||
*
|
||||
* This function destroys a direct mapping descriptor previously created by
|
||||
* spi_mem_dirmap_create().
|
||||
*/
|
||||
void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc)
|
||||
{
|
||||
struct spi_controller *ctlr = desc->mem->spi->controller;
|
||||
|
||||
if (!desc->nodirmap && ctlr->mem_ops && ctlr->mem_ops->dirmap_destroy)
|
||||
ctlr->mem_ops->dirmap_destroy(desc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_dirmap_destroy);
|
||||
|
||||
/**
|
||||
* spi_mem_dirmap_dirmap_read() - Read data through a direct mapping
|
||||
* @desc: direct mapping descriptor
|
||||
* @offs: offset to start reading from. Note that this is not an absolute
|
||||
* offset, but the offset within the direct mapping which already has
|
||||
* its own offset
|
||||
* @len: length in bytes
|
||||
* @buf: destination buffer. This buffer must be DMA-able
|
||||
*
|
||||
* This function reads data from a memory device using a direct mapping
|
||||
* previously instantiated with spi_mem_dirmap_create().
|
||||
*
|
||||
* Return: the amount of data read from the memory device or a negative error
|
||||
* code. Note that the returned size might be smaller than @len, and the caller
|
||||
* is responsible for calling spi_mem_dirmap_read() again when that happens.
|
||||
*/
|
||||
ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, void *buf)
|
||||
{
|
||||
struct spi_controller *ctlr = desc->mem->spi->controller;
|
||||
ssize_t ret;
|
||||
|
||||
if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN)
|
||||
return -EINVAL;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
if (desc->nodirmap) {
|
||||
ret = spi_mem_no_dirmap_read(desc, offs, len, buf);
|
||||
} else if (ctlr->mem_ops && ctlr->mem_ops->dirmap_read) {
|
||||
ret = spi_mem_access_start(desc->mem);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ctlr->mem_ops->dirmap_read(desc, offs, len, buf);
|
||||
|
||||
spi_mem_access_end(desc->mem);
|
||||
} else {
|
||||
ret = -ENOTSUPP;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_dirmap_read);
|
||||
|
||||
/**
|
||||
* spi_mem_dirmap_dirmap_write() - Write data through a direct mapping
|
||||
* @desc: direct mapping descriptor
|
||||
* @offs: offset to start writing from. Note that this is not an absolute
|
||||
* offset, but the offset within the direct mapping which already has
|
||||
* its own offset
|
||||
* @len: length in bytes
|
||||
* @buf: source buffer. This buffer must be DMA-able
|
||||
*
|
||||
* This function writes data to a memory device using a direct mapping
|
||||
* previously instantiated with spi_mem_dirmap_create().
|
||||
*
|
||||
* Return: the amount of data written to the memory device or a negative error
|
||||
* code. Note that the returned size might be smaller than @len, and the caller
|
||||
* is responsible for calling spi_mem_dirmap_write() again when that happens.
|
||||
*/
|
||||
ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, const void *buf)
|
||||
{
|
||||
struct spi_controller *ctlr = desc->mem->spi->controller;
|
||||
ssize_t ret;
|
||||
|
||||
if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_OUT)
|
||||
return -EINVAL;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
if (desc->nodirmap) {
|
||||
ret = spi_mem_no_dirmap_write(desc, offs, len, buf);
|
||||
} else if (ctlr->mem_ops && ctlr->mem_ops->dirmap_write) {
|
||||
ret = spi_mem_access_start(desc->mem);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ctlr->mem_ops->dirmap_write(desc, offs, len, buf);
|
||||
|
||||
spi_mem_access_end(desc->mem);
|
||||
} else {
|
||||
ret = -ENOTSUPP;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_dirmap_write);
|
||||
|
||||
static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv)
|
||||
{
|
||||
return container_of(drv, struct spi_mem_driver, spidrv.driver);
|
||||
|
|
|
@ -57,10 +57,12 @@
|
|||
/**
|
||||
* enum spi_mem_data_dir - describes the direction of a SPI memory data
|
||||
* transfer from the controller perspective
|
||||
* @SPI_MEM_NO_DATA: no data transferred
|
||||
* @SPI_MEM_DATA_IN: data coming from the SPI memory
|
||||
* @SPI_MEM_DATA_OUT: data sent the SPI memory
|
||||
* @SPI_MEM_DATA_OUT: data sent to the SPI memory
|
||||
*/
|
||||
enum spi_mem_data_dir {
|
||||
SPI_MEM_NO_DATA,
|
||||
SPI_MEM_DATA_IN,
|
||||
SPI_MEM_DATA_OUT,
|
||||
};
|
||||
|
@ -122,6 +124,49 @@ struct spi_mem_op {
|
|||
.data = __data, \
|
||||
}
|
||||
|
||||
/**
|
||||
* struct spi_mem_dirmap_info - Direct mapping information
|
||||
* @op_tmpl: operation template that should be used by the direct mapping when
|
||||
* the memory device is accessed
|
||||
* @offset: absolute offset this direct mapping is pointing to
|
||||
* @length: length in byte of this direct mapping
|
||||
*
|
||||
* These information are used by the controller specific implementation to know
|
||||
* the portion of memory that is directly mapped and the spi_mem_op that should
|
||||
* be used to access the device.
|
||||
* A direct mapping is only valid for one direction (read or write) and this
|
||||
* direction is directly encoded in the ->op_tmpl.data.dir field.
|
||||
*/
|
||||
struct spi_mem_dirmap_info {
|
||||
struct spi_mem_op op_tmpl;
|
||||
u64 offset;
|
||||
u64 length;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_mem_dirmap_desc - Direct mapping descriptor
|
||||
* @mem: the SPI memory device this direct mapping is attached to
|
||||
* @info: information passed at direct mapping creation time
|
||||
* @nodirmap: set to 1 if the SPI controller does not implement
|
||||
* ->mem_ops->dirmap_create() or when this function returned an
|
||||
* error. If @nodirmap is true, all spi_mem_dirmap_{read,write}()
|
||||
* calls will use spi_mem_exec_op() to access the memory. This is a
|
||||
* degraded mode that allows spi_mem drivers to use the same code
|
||||
* no matter whether the controller supports direct mapping or not
|
||||
* @priv: field pointing to controller specific data
|
||||
*
|
||||
* Common part of a direct mapping descriptor. This object is created by
|
||||
* spi_mem_dirmap_create() and controller implementation of ->create_dirmap()
|
||||
* can create/attach direct mapping resources to the descriptor in the ->priv
|
||||
* field.
|
||||
*/
|
||||
struct spi_mem_dirmap_desc {
|
||||
struct spi_mem *mem;
|
||||
struct spi_mem_dirmap_info info;
|
||||
unsigned int nodirmap;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_mem - describes a SPI memory device
|
||||
* @spi: the underlying SPI device
|
||||
|
@ -177,10 +222,32 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem)
|
|||
* Note that if the implementation of this function allocates memory
|
||||
* dynamically, then it should do so with devm_xxx(), as we don't
|
||||
* have a ->free_name() function.
|
||||
* @dirmap_create: create a direct mapping descriptor that can later be used to
|
||||
* access the memory device. This method is optional
|
||||
* @dirmap_destroy: destroy a memory descriptor previous created by
|
||||
* ->dirmap_create()
|
||||
* @dirmap_read: read data from the memory device using the direct mapping
|
||||
* created by ->dirmap_create(). The function can return less
|
||||
* data than requested (for example when the request is crossing
|
||||
* the currently mapped area), and the caller of
|
||||
* spi_mem_dirmap_read() is responsible for calling it again in
|
||||
* this case.
|
||||
* @dirmap_write: write data to the memory device using the direct mapping
|
||||
* created by ->dirmap_create(). The function can return less
|
||||
* data than requested (for example when the request is crossing
|
||||
* the currently mapped area), and the caller of
|
||||
* spi_mem_dirmap_write() is responsible for calling it again in
|
||||
* this case.
|
||||
*
|
||||
* This interface should be implemented by SPI controllers providing an
|
||||
* high-level interface to execute SPI memory operation, which is usually the
|
||||
* case for QSPI controllers.
|
||||
*
|
||||
* Note on ->dirmap_{read,write}(): drivers should avoid accessing the direct
|
||||
* mapping from the CPU because doing that can stall the CPU waiting for the
|
||||
* SPI mem transaction to finish, and this will make real-time maintainers
|
||||
* unhappy and might make your system less reactive. Instead, drivers should
|
||||
* use DMA to access this direct mapping.
|
||||
*/
|
||||
struct spi_controller_mem_ops {
|
||||
int (*adjust_op_size)(struct spi_mem *mem, struct spi_mem_op *op);
|
||||
|
@ -189,6 +256,12 @@ struct spi_controller_mem_ops {
|
|||
int (*exec_op)(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op);
|
||||
const char *(*get_name)(struct spi_mem *mem);
|
||||
int (*dirmap_create)(struct spi_mem_dirmap_desc *desc);
|
||||
void (*dirmap_destroy)(struct spi_mem_dirmap_desc *desc);
|
||||
ssize_t (*dirmap_read)(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, void *buf);
|
||||
ssize_t (*dirmap_write)(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, const void *buf);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -249,6 +322,15 @@ int spi_mem_exec_op(struct spi_mem *mem,
|
|||
|
||||
const char *spi_mem_get_name(struct spi_mem *mem);
|
||||
|
||||
struct spi_mem_dirmap_desc *
|
||||
spi_mem_dirmap_create(struct spi_mem *mem,
|
||||
const struct spi_mem_dirmap_info *info);
|
||||
void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc);
|
||||
ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, void *buf);
|
||||
ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, const void *buf);
|
||||
|
||||
int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv,
|
||||
struct module *owner);
|
||||
|
||||
|
|
Loading…
Reference in New Issue