From patchwork Wed Jul 22 13:17:10 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyrille Pitchen X-Patchwork-Id: 6843561 Return-Path: X-Original-To: patchwork-linux-spi@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 44A96C05AD for ; Wed, 22 Jul 2015 13:19:32 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 3FDD7206D5 for ; Wed, 22 Jul 2015 13:19:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 0248120544 for ; Wed, 22 Jul 2015 13:19:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756764AbbGVNTF (ORCPT ); Wed, 22 Jul 2015 09:19:05 -0400 Received: from eusmtp01.atmel.com ([212.144.249.243]:18644 "EHLO eusmtp01.atmel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756348AbbGVNTA (ORCPT ); Wed, 22 Jul 2015 09:19:00 -0400 Received: from tenerife.corp.atmel.com (10.161.101.13) by eusmtp01.atmel.com (10.161.101.31) with Microsoft SMTP Server id 14.3.235.1; Wed, 22 Jul 2015 15:18:55 +0200 From: Cyrille Pitchen To: , , , , , , , , , , CC: , , , , , , , , , Cyrille Pitchen Subject: [PATCH v2 5/5] mtd: atmel-quadspi: add driver for Atmel QSPI controller Date: Wed, 22 Jul 2015 15:17:10 +0200 Message-ID: <3a43a1195054dbbdef5f5aabeae5f6377074d929.1437569902.git.cyrille.pitchen@atmel.com> X-Mailer: git-send-email 1.8.2.2 In-Reply-To: References: MIME-Version: 1.0 Sender: linux-spi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-spi@vger.kernel.org X-Spam-Status: No, score=-8.1 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This driver add support to the new Atmel QSPI controller embedded into sama5d2x SoCs. It expects a NOR memory to be connected to the QSPI controller. Signed-off-by: Cyrille Pitchen --- drivers/mtd/spi-nor/Kconfig | 7 + drivers/mtd/spi-nor/Makefile | 1 + drivers/mtd/spi-nor/atmel-quadspi.c | 900 ++++++++++++++++++++++++++++++++++++ 3 files changed, 908 insertions(+) create mode 100644 drivers/mtd/spi-nor/atmel-quadspi.c diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 64a4f0edabc7..bcdda302f5ab 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -28,4 +28,11 @@ config SPI_FSL_QUADSPI This enables support for the Quad SPI controller in master mode. We only connect the NOR to this controller now. +config SPI_ATMEL_QUADSPI + tristate "Atmel Quad SPI Controller" + depends on (ARCH_AT91 || COMPILE_TEST) + help + This enables support for the Quad SPI controller in master mode. + We only connect the NOR to this controller now. + endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 6a7ce1462247..243ea8a479ef 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o +obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c new file mode 100644 index 000000000000..058552a5882d --- /dev/null +++ b/drivers/mtd/spi-nor/atmel-quadspi.c @@ -0,0 +1,900 @@ +/* + * Driver for Atmel QSPI Controller + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Cyrille Pitchen + * + * 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. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * This driver is based on drivers/mtd/spi-nor/fsl-quadspi.c from Freescale. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* QSPI register offsets */ +#define QSPI_CR 0x0000 /* Control Register */ +#define QSPI_MR 0x0004 /* Mode Register */ +#define QSPI_RD 0x0008 /* Receive Data Register */ +#define QSPI_TD 0x000c /* Transmit Data Register */ +#define QSPI_SR 0x0010 /* Status Register */ +#define QSPI_IER 0x0014 /* Interrupt Enable Register */ +#define QSPI_IDR 0x0018 /* Interrupt Disable Register */ +#define QSPI_IMR 0x001c /* Interrupt Mask Register */ +#define QSPI_SCR 0x0020 /* Serial Clock Register */ + +#define QSPI_IAR 0x0030 /* Instruction Address Register */ +#define QSPI_ICR 0x0034 /* Instruction Code Register */ +#define QSPI_IFR 0x0038 /* Instruction Frame Register */ + +#define QSPI_SMR 0x0040 /* Scrambling Mode Register */ +#define QSPI_SKR 0x0044 /* Scrambling Key Register */ + +#define QSPI_WPMR 0x00E4 /* Write Protection Mode Register */ +#define QSPI_WPSR 0x00E8 /* Write Protection Status Register */ + +#define QSPI_VERSION 0x00FC /* Version Register */ + +/* Bitfields in QSPI_CR (Control Register) */ +#define QSPI_CR_QSPIEN_OFFSET 0 +#define QSPI_CR_QSPIEN_SIZE 1 +#define QSPI_CR_QSPIDIS_OFFSET 1 +#define QSPI_CR_QSPIDIS_SIZE 1 +#define QSPI_CR_SWRST_OFFSET 7 +#define QSPI_CR_SWRST_SIZE 1 +#define QSPI_CR_LASTXFER_OFFSET 24 +#define QSPI_CR_LASTXFER_SIZE 1 + +/* Bitfields in QSPI_MR (Mode Register) */ +#define QSPI_MR_SSM_OFFSET 0 +#define QSPI_MR_SSM_SIZE 1 +#define QSPI_MR_LLB_OFFSET 1 +#define QSPI_MR_LLB_SIZE 1 +#define QSPI_MR_WDRBT_OFFSET 2 +#define QSPI_MR_WDRBT_SIZE 1 +#define QPSI_MR_SMRM_OFFSET 3 +#define QSPI_MR_SMRM_SIZE 1 +#define QSPI_MR_CSMODE_OFFSET 4 +#define QSPI_MR_CSMODE_SIZE 2 +#define QSPI_MR_NBBITS_OFFSET 8 +#define QSPI_MR_NBBITS_SIZE 4 +#define QSPI_MR_NBBITS_8_BIT 0 +#define QSPI_MR_NBBITS_9_BIT 1 +#define QSPI_MR_NBBITS_10_BIT 2 +#define QSPI_MR_NBBITS_11_BIT 3 +#define QSPI_MR_NBBITS_12_BIT 4 +#define QSPI_MR_NBBITS_13_BIT 5 +#define QSPI_MR_NBBITS_14_BIT 6 +#define QSPI_MR_NBBITS_15_BIT 7 +#define QSPI_MR_NBBITS_16_BIT 8 +#define QSPI_MR_DLYBCT_OFFSET 16 +#define QSPI_MR_DLYBCT_SIZE 8 +#define QSPI_MR_DLYCS_OFFSET 24 +#define QSPI_MR_DLYCS_SIZE 8 + +/* Bitfields in QSPI_SR/QSPI_IER/QSPI_IDR/QSPI_IMR */ +#define QSPI_SR_RDRF_OFFSET 0 +#define QSPI_SR_RDRF_SIZE 1 +#define QSPI_SR_TDRE_OFFSET 1 +#define QSPI_SR_TDRE_SIZE 1 +#define QSPI_SR_TXEMPTY_OFFSET 2 +#define QSPI_SR_TXEMPTY_SIZE 1 +#define QSPI_SR_OVRES_OFFSET 3 +#define QSPI_SR_OVRES_SIZE 1 +#define QSPI_SR_CSR_OFFSET 8 +#define QSPI_SR_CSR_SIZE 1 +#define QPSI_SR_CSS_OFFSET 9 +#define QPSI_SR_CSS_SIZE 1 +#define QSPI_SR_INSTRE_OFFSET 10 +#define QSPI_SR_INSTRE_SIZE 1 +#define QSPI_SR_QSPIENS_OFFSET 24 +#define QSPI_SR_QSPIENS_SIZE 1 + +/* Bitfields in QSPI_SCR (Serial Clock Register) */ +#define QSPI_SCR_CPOL_OFFSET 0 +#define QSPI_SCR_CPOL_SIZE 1 +#define QSPI_SCR_CPHA_OFFSET 1 +#define QSPI_SCR_CPHA_SIZE 1 +#define QSPI_SCR_SCBR_OFFSET 8 +#define QSPI_SCR_SCBR_SIZE 8 +#define QSPI_SCR_DLYBS_OFFSET 16 +#define QSPI_SCR_DLYBS_SIZE 8 + +/* Bitfields in QSPI_ICR (Instruction Code Register) */ +#define QSPI_ICR_INST_OFFSET 0 +#define QSPI_ICR_INST_SIZE 8 +#define QSPI_ICR_OPT_OFFSET 16 +#define QSPI_ICR_OPT_SIZE 8 + +/* Bitfields in QSPI_IFR (Instruction Frame Register) */ +#define QSPI_IFR_WIDTH_OFFSET 0 +#define QSPI_IFR_WIDTH_SIZE 3 +#define QSPI_IFR_WIDTH_SINGLE_BIT_SPI 0 +#define QSPI_IFR_WIDTH_DUAL_OUTPUT 1 +#define QSPI_IFR_WIDTH_QUAD_OUTPUT 2 +#define QSPI_IFR_WIDTH_DUAL_IO 3 +#define QSPI_IFR_WIDTH_QUAD_IO 4 +#define QSPI_IFR_WIDTH_DUAL_CMD 5 +#define QSPI_IFR_WIDTH_QUAD_CMD 6 +#define QSPI_IFR_INSTEN_OFFSET 4 +#define QSPI_IFR_INSTEN_SIZE 1 +#define QSPI_IFR_ADDREN_OFFSET 5 +#define QSPI_IFR_ADDREN_SIZE 1 +#define QSPI_IFR_OPTEN_OFFSET 6 +#define QSPI_IFR_OPTEN_SIZE 1 +#define QSPI_IFR_DATAEN_OFFSET 7 +#define QSPI_IFR_DATAEN_SIZE 1 +#define QSPI_IFR_OPTL_OFFSET 8 +#define QSPI_IFR_OPTL_SIZE 2 +#define QSPI_IFR_OPTL_OPTION_1BIT 0 +#define QSPI_IFR_OPTL_OPTION_2BIT 1 +#define QSPI_IFR_OPTL_OPTION_4BIT 2 +#define QSPI_IFR_OPTL_OPTION_8BIT 3 +#define QSPI_IFR_ADDRL_OFFSET 10 +#define QSPI_IFR_ADDRL_SIZE 1 +#define QSPI_IFR_TFRTYP_OFFSET 12 +#define QSPI_IFR_TFRTYP_SIZE 2 +#define QSPI_IFR_TFRTYP_TRSFR_READ 0 +#define QSPI_IFR_TFRTYP_TRSFR_READ_MEMORY 1 +#define QSPI_IFR_TFRTYP_TRSFR_WRITE 2 +#define QSPI_IFR_TFRTYP_TRSFR_WRITE_MEMORY 3 +#define QSPI_IFR_CRM_OFFSET 14 +#define QSPI_IFR_CRM_SIZE 1 +#define QSPI_IFR_NBDUM_OFFSET 16 +#define QSPI_IFR_NBDUM_SIZE 5 + +/* Bitfields in QSPI_SMR (Scrambling Mode Register) */ +#define QSPI_SMR_SCREN_OFFSET 0 +#define QSPI_SMR_SCREN_SIZE 1 +#define QSPI_SMR_RVDIS_OFFSET 1 +#define QSPI_SMR_RVDIS_SIZE 1 + +/* Bitfields in QSPI_WPMR (Write Protection Mode Register) */ +#define QSPI_WPMR_WPEN_OFFSET 0 +#define QSPI_WPMR_WPEN_SIZE 1 +#define QSPI_WPMR_WPKEY_OFFSET 8 +#define QSPI_WPMR_WPKEY_SIZE 24 + +/* Bitfields in QSPI_WPSR (Write Protection Status Register) */ +#define QSPI_WPSR_WPVS_OFFSET 0 +#define QSPI_WPSR_WPVS_SIZE 1 +#define QSPI_WPSR_WPVSRC_OFFSET 8 +#define QSPI_WPSR_WPVSRC_SIZE 8 + +/* Bit manipulation macros */ +#define QSPI_BIT(name) \ + (1 << QSPI_##name##_OFFSET) +#define QSPI_BF(name, value) \ + (((value) & ((1 << QSPI_##name##_SIZE) - 1)) << QSPI_##name##_OFFSET) +#define QSPI_BFEXT(name, value) \ + (((value) >> QSPI_##name##_OFFSET) & ((1 << QSPI_##name##_SIZE) - 1)) +#define QSPI_BFINS(name, value, old) \ + (((old) & ~(((1 << QSPI_##name##_SIZE) - 1) << QSPI_##name##_OFFSET)) \ + | QSPI_BF(name, value)) + +/* Register access macros */ +#define qspi_readl(port, reg) \ + readl_relaxed((port)->regs + QSPI_##reg) +#define qspi_writel(port, reg, value) \ + writel_relaxed((value), (port)->regs + QSPI_##reg) + +#define qspi_readw(port, reg) \ + readw_relaxed((port)->regs + QSPI_##reg) +#define qspi_writew(port, reg, value) \ + writew_relaxed((value), (port)->regs + QSPI_##reg) + +#define qspi_readb(port, reg) \ + readb_relaxed((port)->regs + QSPI_##reg) +#define qspi_writeb(port, reg, value) \ + writeb_relaxed((value), (port)->regs + QSPI_##reg) + + +struct atmel_qspi { + void __iomem *regs; + void __iomem *mem; + dma_addr_t phys_addr; + struct dma_chan *chan; + struct clk *clk; + struct platform_device *pdev; + u32 ifr_width; + u32 pending; + + struct mtd_info mtd; + struct spi_nor nor; + u32 clk_rate; + struct completion completion; + +#ifdef DEBUG + u8 last_instruction; +#endif +}; + +struct atmel_qspi_command { + u32 ifr_tfrtyp; + union { + struct { + u32 instruction:1; + u32 address:3; + u32 mode:1; + u32 dummy:1; + u32 data:1; + u32 dma:1; + u32 reserved:24; + } bits; + u32 word; + } enable; + u8 instruction; + u8 mode; + u8 num_mode_cycles; + u8 num_dummy_cycles; + u32 address; + + size_t buf_len; + const void *tx_buf; + void *rx_buf; +}; + +#define QSPI_DMA_THRESHOLD 32 + +static void atmel_qspi_dma_callback(void *arg) +{ + struct completion *c = arg; + + complete(c); +} + +static int atmel_qspi_run_dma_transfer(struct atmel_qspi *aq, + const struct atmel_qspi_command *cmd) +{ + u32 offset = (cmd->enable.bits.address) ? cmd->address : 0; + struct dma_chan *chan = aq->chan; + struct device *dev = &aq->pdev->dev; + enum dma_data_direction direction; + dma_addr_t phys_addr, dst, src; + struct dma_async_tx_descriptor *desc; + struct completion completion; + dma_cookie_t cookie; + int err = 0; + + if (cmd->tx_buf) { + direction = DMA_TO_DEVICE; + phys_addr = dma_map_single(dev, (void *)cmd->tx_buf, + cmd->buf_len, direction); + src = phys_addr; + dst = aq->phys_addr + offset; + } else { + direction = DMA_FROM_DEVICE; + phys_addr = dma_map_single(dev, (void *)cmd->rx_buf, + cmd->buf_len, direction); + src = aq->phys_addr + offset; + dst = phys_addr; + } + err = dma_mapping_error(dev, phys_addr); + if (err) + goto exit; + + desc = chan->device->device_prep_dma_memcpy(chan, dst, src, + cmd->buf_len, + DMA_PREP_INTERRUPT); + if (!desc) { + err = -ENOMEM; + goto unmap_single; + } + + init_completion(&completion); + desc->callback = atmel_qspi_dma_callback; + desc->callback_param = &completion; + cookie = dmaengine_submit(desc); + err = dma_submit_error(cookie); + if (err) + goto unmap_single; + dma_async_issue_pending(chan); + + if (!wait_for_completion_timeout(&completion, msecs_to_jiffies(1000))) + err = -ETIMEDOUT; + + if (dma_async_is_tx_complete(chan, cookie, NULL, NULL) != DMA_COMPLETE) + err = -ETIMEDOUT; + + if (err) + dmaengine_terminate_all(chan); +unmap_single: + dma_unmap_single(dev, phys_addr, cmd->buf_len, direction); +exit: + return err; +} + +static int atmel_qspi_run_transfer(struct atmel_qspi *aq, + const struct atmel_qspi_command *cmd) +{ + void __iomem *ahb_mem; + + /* First try a DMA transfer */ + if (aq->chan && cmd->enable.bits.dma && + cmd->buf_len >= QSPI_DMA_THRESHOLD) + return atmel_qspi_run_dma_transfer(aq, cmd); + + /* Then fallback to a PIO transfer */ + 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); + + return 0; +} + +#ifdef DEBUG +static void atmel_qspi_debug_command(struct atmel_qspi *aq, + const struct atmel_qspi_command *cmd) +{ + u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE]; + size_t len = 0; + int i; + + if (cmd->enable.bits.instruction) { + if (aq->last_instruction == cmd->instruction) + return; + aq->last_instruction = cmd->instruction; + } + + if (cmd->enable.bits.instruction) + cmd_buf[len++] = cmd->instruction; + + 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 (aq->ifr_width) { + 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; + } + + print_hex_dump(KERN_DEBUG, "qspi cmd: ", DUMP_PREFIX_NONE, + 32, 1, cmd_buf, len, false); + +#ifdef VERBOSE_DEBUG + 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 +} +#else +#define atmel_qspi_debug_command(aq, cmd) +#endif + +static int atmel_qspi_run_command(struct atmel_qspi *aq, + const struct atmel_qspi_command *cmd) +{ + u32 iar, icr, ifr, sr; + int err = 0; + + iar = 0; + icr = 0; + ifr = (QSPI_BF(IFR_WIDTH, aq->ifr_width) | + QSPI_BF(IFR_TFRTYP, cmd->ifr_tfrtyp)); + + + /* Compute instruction parameters */ + if (cmd->enable.bits.instruction) { + icr |= QSPI_BF(ICR_INST, cmd->instruction); + ifr |= QSPI_BIT(IFR_INSTEN); + } + + /* Compute address parameters */ + switch (cmd->enable.bits.address) { + case 4: + ifr |= QSPI_BIT(IFR_ADDRL); + /*break;*/ /* fallback to the 24bit address case */ + case 3: + iar = (cmd->enable.bits.data) ? 0 : cmd->address; + ifr |= QSPI_BIT(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_BF(ICR_OPT, cmd->mode); + ifr |= QSPI_BIT(IFR_OPTEN); + + switch (QSPI_BFEXT(IFR_WIDTH, ifr)) { + case QSPI_IFR_WIDTH_SINGLE_BIT_SPI: + case QSPI_IFR_WIDTH_DUAL_OUTPUT: + case QSPI_IFR_WIDTH_QUAD_OUTPUT: + mode_cycle_bits = 1; + 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_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_1BIT); + break; + + case 2: + ifr |= QSPI_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_2BIT); + break; + + case 4: + ifr |= QSPI_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_4BIT); + break; + + case 8: + ifr |= QSPI_BF(IFR_OPTL, QSPI_IFR_OPTL_OPTION_8BIT); + break; + + default: + return -EINVAL; + } + } + + /* Set number of dummy cycles */ + if (cmd->enable.bits.dummy) + ifr |= QSPI_BF(IFR_NBDUM, cmd->num_dummy_cycles); + else + ifr |= QSPI_BF(IFR_NBDUM, 0); + + /* Set data enable */ + if (cmd->enable.bits.data) { + ifr |= QSPI_BIT(IFR_DATAEN); + + /* Special case for Continuous Read Mode */ + if (!cmd->tx_buf && !cmd->rx_buf) + ifr |= QSPI_BIT(IFR_CRM); + } + + /* Set QSPI Instruction Frame registers */ + atmel_qspi_debug_command(aq, cmd); + qspi_writel(aq, IAR, iar); + qspi_writel(aq, ICR, icr); + qspi_writel(aq, IFR, ifr); + + /* Skip to the final steps if there is no data */ + if (!cmd->enable.bits.data) + goto no_data; + + /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */ + (void)qspi_readl(aq, IFR); + + /* 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, CR, QSPI_BIT(CR_LASTXFER)); + + if (err) + return err; +#ifdef VERBOSE_DEBUG + 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, SR); + if (sr & QSPI_BIT(SR_INSTRE)) + return err; + + /* Wait for INSTRuction End interrupt */ + init_completion(&aq->completion); + aq->pending = 0; + qspi_writel(aq, IER, QSPI_BIT(SR_INSTRE)); + if (!wait_for_completion_timeout(&aq->completion, + msecs_to_jiffies(1000))) + err = -ETIMEDOUT; + qspi_writel(aq, IDR, QSPI_BIT(SR_INSTRE)); + + return err; +} + +static int atmel_qspi_read_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.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_READ; + 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); +} + +static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode, + u8 *buf, int len, + int write_enable) +{ + struct atmel_qspi *aq = nor->priv; + struct atmel_qspi_command cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_WRITE; + 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); +} + +static void atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len, + size_t *retlen, const u_char *write_buf) +{ + struct atmel_qspi *aq = nor->priv; + struct atmel_qspi_command cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_WRITE_MEMORY; + cmd.enable.bits.instruction = 1; + cmd.enable.bits.address = nor->addr_width; + cmd.enable.bits.data = 1; + cmd.enable.bits.dma = 1; + cmd.instruction = nor->program_opcode; + cmd.address = (u32)to; + cmd.tx_buf = write_buf; + cmd.buf_len = len; + if (!atmel_qspi_run_command(aq, &cmd)) + *retlen += len; +} + +static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs) +{ + struct atmel_qspi *aq = nor->priv; + struct atmel_qspi_command cmd; + + dev_dbg(nor->dev, "%dKiB at 0x%08x\n", + aq->mtd.erasesize / 1024, (u32)offs); + + memset(&cmd, 0, sizeof(cmd)); + cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_WRITE; + 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); +} + +static int atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len, + size_t *retlen, u_char *read_buf) +{ + struct atmel_qspi *aq = nor->priv; + struct atmel_qspi_command cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + cmd.ifr_tfrtyp = QSPI_IFR_TFRTYP_TRSFR_READ_MEMORY; + cmd.enable.bits.instruction = 1; + cmd.enable.bits.address = nor->addr_width; + cmd.enable.bits.dummy = (nor->read_dummy > 0); + cmd.enable.bits.data = 1; + cmd.enable.bits.dma = 1; + cmd.instruction = nor->read_opcode; + cmd.address = (u32)from; + cmd.num_dummy_cycles = nor->read_dummy; + cmd.rx_buf = read_buf; + cmd.buf_len = len; + err = atmel_qspi_run_command(aq, &cmd); + if (err) + return err; + + *retlen += len; + return 0; +} + +static int atmel_qspi_set_protocol(struct spi_nor *nor, enum spi_protocol proto) +{ + struct atmel_qspi *aq = nor->priv; + + switch (proto) { + case SPI_PROTO_1_1_1: + aq->ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI; + break; + case SPI_PROTO_1_1_2: + aq->ifr_width = QSPI_IFR_WIDTH_DUAL_OUTPUT; + break; + case SPI_PROTO_1_1_4: + aq->ifr_width = QSPI_IFR_WIDTH_QUAD_OUTPUT; + break; + case SPI_PROTO_1_2_2: + aq->ifr_width = QSPI_IFR_WIDTH_DUAL_IO; + break; + case SPI_PROTO_1_4_4: + aq->ifr_width = QSPI_IFR_WIDTH_QUAD_IO; + break; + case SPI_PROTO_2_2_2: + aq->ifr_width = QSPI_IFR_WIDTH_DUAL_CMD; + break; + case SPI_PROTO_4_4_4: + aq->ifr_width = QSPI_IFR_WIDTH_QUAD_CMD; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int atmel_qspi_init(struct atmel_qspi *aq) +{ + unsigned long src_rate; + u32 mr, scr, scbr; + + /* Reset the QSPI controller */ + qspi_writel(aq, CR, QSPI_BIT(CR_SWRST)); + + /* Set the QSPI controller in Serial Memory Mode */ + mr = (QSPI_BIT(MR_SSM) | + QSPI_BF(MR_NBBITS, QSPI_MR_NBBITS_8_BIT)); + qspi_writel(aq, MR, mr); + + 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); + if (scbr > 0) + scbr--; + scr = QSPI_BF(SCR_SCBR, scbr); + qspi_writel(aq, SCR, scr); + + /* Enable the QSPI controller */ + qspi_writel(aq, CR, QSPI_BIT(CR_QSPIEN)); + + return 0; +} + +static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id) +{ + struct atmel_qspi *aq = (struct atmel_qspi *)dev_id; + u32 status, mask, pending; + + status = qspi_readl(aq, SR); + mask = qspi_readl(aq, IMR); + pending = status & mask; + + if (!pending) + return IRQ_NONE; + + aq->pending |= pending; + if (pending & QSPI_BIT(SR_INSTRE)) + complete(&aq->completion); + + return IRQ_HANDLED; +} + +static int atmel_qspi_probe(struct platform_device *pdev) +{ + struct device_node *child, *np = pdev->dev.of_node; + struct mtd_part_parser_data ppdata; + struct atmel_qspi *aq; + struct resource *res; + dma_cap_mask_t mask; + struct spi_nor *nor; + struct mtd_info *mtd; + char modalias[40]; + int irq, err = 0; + + if (of_get_child_count(np) != 1) + return -ENODEV; + child = of_get_next_child(np, NULL); + + aq = devm_kzalloc(&pdev->dev, sizeof(*aq), GFP_KERNEL); + if (!aq) + return -ENOMEM; + platform_set_drvdata(pdev, aq); + aq->pdev = pdev; + /* Start in Extended SPI (1-1-1) */ + aq->ifr_width = QSPI_IFR_WIDTH_SINGLE_BIT_SPI; + + /* Map the registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + aq->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(aq->regs)) { + dev_err(&pdev->dev, "missing registers\n"); + err = PTR_ERR(aq->regs); + goto exit; + } + + /* Map the AHB memory */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + aq->mem = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(aq->mem)) { + dev_err(&pdev->dev, "missing AHB memory\n"); + err = PTR_ERR(aq->regs); + goto exit; + } + aq->phys_addr = (dma_addr_t)res->start; + + /* Get the peripheral clock */ + aq->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(aq->clk)) { + dev_err(&pdev->dev, "missing peripheral clock\n"); + err = PTR_ERR(aq->clk); + goto exit; + } + + /* Enable the peripheral clock */ + err = clk_prepare_enable(aq->clk); + if (err) { + dev_err(&pdev->dev, "failed to enable the peripheral clock\n"); + goto exit; + } + + /* Request the IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "missing IRQ\n"); + err = irq; + goto disable_clk; + } + err = devm_request_irq(&pdev->dev, irq, atmel_qspi_interrupt, + 0, dev_name(&pdev->dev), aq); + if (err) + goto disable_clk; + + /* Try to get a DMA channel for memcpy() operation */ + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + aq->chan = dma_request_channel(mask, NULL, NULL); + if (!aq->chan) + dev_warn(&pdev->dev, "no available DMA channel\n"); + + /* Setup the spi-nor */ + nor = &aq->nor; + mtd = &aq->mtd; + + nor->mtd = mtd; + nor->dev = &pdev->dev; + 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; + nor->set_protocol = atmel_qspi_set_protocol; + + if (of_modalias_node(child, modalias, sizeof(modalias)) < 0) { + err = -ENODEV; + goto release_channel; + } + + err = of_property_read_u32(child, "spi-max-frequency", &aq->clk_rate); + if (err < 0) + goto release_channel; + + err = atmel_qspi_init(aq); + if (err) + goto release_channel; + + nor->dev->of_node = child; + err = spi_nor_scan(nor, modalias, SPI_NOR_QUAD); + nor->dev->of_node = np; + if (err) + goto release_channel; + + ppdata.of_node = child; + err = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); + if (err) + goto release_channel; + + return 0; + +release_channel: + if (aq->chan) + dma_release_channel(aq->chan); +disable_clk: + clk_disable_unprepare(aq->clk); +exit: + return err; +} + +static int atmel_qspi_remove(struct platform_device *pdev) +{ + struct atmel_qspi *aq = platform_get_drvdata(pdev); + + mtd_device_unregister(&aq->mtd); + qspi_writel(aq, CR, QSPI_BIT(CR_QSPIDIS)); + if (aq->chan) + dma_release_channel(aq->chan); + clk_disable_unprepare(aq->clk); + return 0; +} + + +static const struct of_device_id atmel_qspi_dt_ids[] = { + { .compatible = "atmel,sama5d2-qspi" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_qspi_dt_ids); + +static struct platform_driver atmel_qspi_driver = { + .driver = { + .name = "atmel_qspi", + .of_match_table = atmel_qspi_dt_ids, + }, + .probe = atmel_qspi_probe, + .remove = atmel_qspi_remove, +}; +module_platform_driver(atmel_qspi_driver); + +MODULE_AUTHOR("Cyrille Pitchen "); +MODULE_DESCRIPTION("Atmel QSPI Controller driver"); +MODULE_LICENSE("GPL v2");