From patchwork Thu Oct 19 22:14:49 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: matthew.gerlach@linux.intel.com X-Patchwork-Id: 828377 X-Patchwork-Delegate: cyrille.pitchen@atmel.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.infradead.org (client-ip=65.50.211.133; helo=bombadil.infradead.org; envelope-from=linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="mjnvwq6+"; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3yJ3Ct3lDXz9t6n for ; Fri, 20 Oct 2017 09:15:54 +1100 (AEDT) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=lotOTJj+qjHNuosyr7n9q6daHHq2WvAGLTWeIulH//w=; b=mjnvwq6+zS8f1NP33B/3S+MfpW gIO5Ds3SHUdjQ1rE3E3FBmKoL3K/hW4mgTyPoQoIW9JeEpmEZHBGIm22W/BdULb+Lf4LobMntCial a7GpV/OKV1YD/slg/AsTl3s2snCVjZSN2vCyZd1/M3g/1Ep8u/N8di0vGeHwveIrsDVxO3qoYkbYo 3BrddMqp+rHKLS/MblR6z9guZ5x0Nu1M4pNPTdxXJ3y9Eu8CvQeOb6r3P4tyGcd0eXhp7aigmlP/3 r+6jkeDmCk+klDZLY/qeKl/ZS+Ioqwfrc86kpYNCnZBCQbu9dXLr/TQQUboUfuQCgjQny4btl5YzS SRDrUAFw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1e5J67-0002mW-TH; Thu, 19 Oct 2017 22:15:27 +0000 Received: from mga09.intel.com ([134.134.136.24]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1e5J63-000234-LA for linux-mtd@lists.infradead.org; Thu, 19 Oct 2017 22:15:25 +0000 Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by orsmga102.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 19 Oct 2017 15:15:06 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos; i="5.43,403,1503385200"; d="scan'208"; a="1207801384" Received: from mgerlach-mobl.amr.corp.intel.com (HELO mgerlach-VirtualBox.amr.corp.intel.com) ([10.254.234.211]) by fmsmga001.fm.intel.com with ESMTP; 19 Oct 2017 15:15:02 -0700 From: matthew.gerlach@linux.intel.com To: vndao@altera.com, dwmw2@infradead.org, computersforpeace@gmail.com, boris.brezillon@free-electrons.com, marek.vasut@gmail.com, richard@nod.at, cyrille.pitchen@wedev4u.fr, robh+dt@kernel.org, mark.rutland@arm.com, linux-mtd@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, gregkh@linuxfoundation.org, davem@davemloft.net, mchehab@kernel.org, linux-fpga@vger.kernel.org, tien.hock.loh@intel.com, hean.loong.ong@intel.com Subject: [PATCH v3 1/2] dt-bindings: mtd: Altera ASMI Parallel II IP Core Date: Thu, 19 Oct 2017 15:14:49 -0700 Message-Id: <1508451290-26426-2-git-send-email-matthew.gerlach@linux.intel.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1508451290-26426-1-git-send-email-matthew.gerlach@linux.intel.com> References: <1508451290-26426-1-git-send-email-matthew.gerlach@linux.intel.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20171019_151523_743338_24DE1784 X-CRM114-Status: GOOD ( 11.21 ) X-Spam-Score: -6.9 (------) X-Spam-Report: SpamAssassin version 3.4.1 on bombadil.infradead.org summary: Content analysis details: (-6.9 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] -0.0 RCVD_IN_MSPIKE_H3 RBL: Good reputation (+3) [134.134.136.24 listed in wl.mailspike.net] -5.0 RCVD_IN_DNSWL_HI RBL: Sender listed at http://www.dnswl.org/, high trust [134.134.136.24 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_WL Mailspike good senders X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Matthew Gerlach MIME-Version: 1.0 Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org From: Matthew Gerlach Device Tree bindings for Altera ASMI Parallel II IP Core connected to a flash chip. Signed-off-by: Matthew Gerlach Acked-by: Rob Herring --- v2: Made substitutions suggested by Rob Herring. Emphasize driver expects controller is connected to spi-nor flash. v3: add Acked-by: Rob Herring --- Documentation/devicetree/bindings/mtd/altera-asmip2.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Documentation/devicetree/bindings/mtd/altera-asmip2.txt diff --git a/Documentation/devicetree/bindings/mtd/altera-asmip2.txt b/Documentation/devicetree/bindings/mtd/altera-asmip2.txt new file mode 100644 index 0000000..2e58817 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/altera-asmip2.txt @@ -0,0 +1,15 @@ +* Altera ASMI Parallel II IP Core + +Devicetree bindings for an Altera ASMI Parallel II IP core connected to a +spi-nor flash chip. + +Required properties: +- compatible : Should be "altr,asmi-parallel2-spi-nor". +- reg : A tuple consisting of a physical address and length. + +Example: + +qspi: spi@a0001000 { + compatible = "altr,asmi-parallel2-spi-nor"; + reg = <0xa0001000 0x10>; +}; From patchwork Thu Oct 19 22:14:50 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: matthew.gerlach@linux.intel.com X-Patchwork-Id: 828378 X-Patchwork-Delegate: cyrille.pitchen@atmel.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.infradead.org (client-ip=65.50.211.133; helo=bombadil.infradead.org; envelope-from=linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="Db4+mLT3"; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3yJ3Cx3tJGz9t6n for ; Fri, 20 Oct 2017 09:15:57 +1100 (AEDT) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=DUkAp6JWa81xutjULeK4Q6eg0nItQN6npYJg3xVwIUo=; b=Db4+mLT3AKttWk 2z6EFfKdht6jXNPnxL0N7YB1sWQPkJPpifOQseDqfd7HtpYp3bM2OfJUkrrqqxp80tX9PbpQJwAoY 2mzg32Z0KzotD4F+sHgtkQ90fgm7K8+93VMIzbQ2Sz3duAhXCHOH9ZSUa35cL1xZDxHDkEVNXXtUq mMG30hqQi5OshDJ8C2RW4l9bikUv/3wWzA6haOMEF6zYY36GhdMXKsIstCgKxf6hIAeQ7Y16JORpK aHRL9QB+4pDCJzfB1FEFFDaYn0Q5rh0mffQHvJ64PiTI/EGjMVAhc+kNz8z4//F3O7uZvROLH0lDc 3+xxokMZh6s7AVgrYJ5Q==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1e5J6O-00034q-1Z; Thu, 19 Oct 2017 22:15:44 +0000 Received: from mga05.intel.com ([192.55.52.43]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1e5J6C-0002ch-Lm for linux-mtd@lists.infradead.org; Thu, 19 Oct 2017 22:15:41 +0000 Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga105.fm.intel.com with ESMTP; 19 Oct 2017 15:15:12 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos; i="5.43,403,1503385200"; d="scan'208"; a="1207801440" Received: from mgerlach-mobl.amr.corp.intel.com (HELO mgerlach-VirtualBox.amr.corp.intel.com) ([10.254.234.211]) by fmsmga001.fm.intel.com with ESMTP; 19 Oct 2017 15:15:06 -0700 From: matthew.gerlach@linux.intel.com To: vndao@altera.com, dwmw2@infradead.org, computersforpeace@gmail.com, boris.brezillon@free-electrons.com, marek.vasut@gmail.com, richard@nod.at, cyrille.pitchen@wedev4u.fr, robh+dt@kernel.org, mark.rutland@arm.com, linux-mtd@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, gregkh@linuxfoundation.org, davem@davemloft.net, mchehab@kernel.org, linux-fpga@vger.kernel.org, tien.hock.loh@intel.com, hean.loong.ong@intel.com Subject: [PATCH v3 2/2] mtd: spi-nor: Altera ASMI Parallel II IP Core Date: Thu, 19 Oct 2017 15:14:50 -0700 Message-Id: <1508451290-26426-3-git-send-email-matthew.gerlach@linux.intel.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1508451290-26426-1-git-send-email-matthew.gerlach@linux.intel.com> References: <1508451290-26426-1-git-send-email-matthew.gerlach@linux.intel.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20171019_151533_161839_F906A2B8 X-CRM114-Status: GOOD ( 23.31 ) X-Spam-Score: -4.2 (----) X-Spam-Report: SpamAssassin version 3.4.1 on bombadil.infradead.org summary: Content analysis details: (-4.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [192.55.52.43 listed in list.dnswl.org] -0.0 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Matthew Gerlach Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org From: Matthew Gerlach This patch adds support for a spi-nor, platform driver for the Altera ASMI Parallel II IP Core. The intended use case is to be able to update the flash used to load a FPGA at power up with mtd-utils. Signed-off-by: Matthew Gerlach --- v2: minor checkpatch fixing by Wu Hao Use read_dummy value as suggested by Cyrille Pitchen. Don't assume 4 byte addressing (Cryille Pichecn and Marek Vasut). Fixed #define indenting as suggested by Marek Vasut. Added units to timer values as suggested by Marek Vasut. Use io(read|write)8_rep() as suggested by Marek Vasut. Renamed function prefixed with __ as suggested by Marek Vasut. v3: make timeout a function of clock frequency (Marek Vasut) --- MAINTAINERS | 7 + drivers/mtd/spi-nor/Kconfig | 6 + drivers/mtd/spi-nor/Makefile | 1 + drivers/mtd/spi-nor/altera-asmip2.c | 494 ++++++++++++++++++++++++++++++++++++ include/linux/mtd/altera-asmip2.h | 25 ++ 5 files changed, 533 insertions(+) create mode 100644 drivers/mtd/spi-nor/altera-asmip2.c create mode 100644 include/linux/mtd/altera-asmip2.h diff --git a/MAINTAINERS b/MAINTAINERS index 0630482..620d151 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -656,6 +656,13 @@ ALPS PS/2 TOUCHPAD DRIVER R: Pali Rohár F: drivers/input/mouse/alps.* +ALTERA ASMI Parallel II Driver +M: Matthew Gerlach +L: linux-mtd@lists.infradead.org +S: Maintained +F: drivers/mtd/spi-nor/altera-asmip2.c +F: inclulde/linux/mtd/altera-asmip2.h + ALTERA I2C CONTROLLER DRIVER M: Thor Thayer S: Maintained diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index f26aaa6..b441e45 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -129,4 +129,10 @@ config SPI_STM32_QUADSPI This enables support for the STM32 Quad SPI controller. We only connect the NOR to this controller. +config SPI_ALTERA_ASMIP2 + tristate "Altera ASMI Parallel II IP" + depends on OF && HAS_IOMEM + help + Enable support for Altera ASMI Parallel II. + endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 7d84c51..1c79324 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o +obj-$(CONFIG_SPI_ALTERA_ASMIP2) += altera-asmip2.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 diff --git a/drivers/mtd/spi-nor/altera-asmip2.c b/drivers/mtd/spi-nor/altera-asmip2.c new file mode 100644 index 0000000..6bd7cd2 --- /dev/null +++ b/drivers/mtd/spi-nor/altera-asmip2.c @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2017 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define QSPI_ACTION_REG 0 +#define QSPI_ACTION_RST BIT(0) +#define QSPI_ACTION_EN BIT(1) +#define QSPI_ACTION_SC BIT(2) +#define QSPI_ACTION_CHIP_SEL_SFT 4 +#define QSPI_ACTION_DUMMY_SFT 8 +#define QSPI_ACTION_READ_BACK_SFT 16 + +#define QSPI_FIFO_CNT_REG 4 +#define QSPI_FIFO_DEPTH 0x200 +#define QSPI_FIFO_CNT_MSK 0x3ff +#define QSPI_FIFO_CNT_RX_SFT 0 +#define QSPI_FIFO_CNT_TX_SFT 12 + +#define QSPI_DATA_REG 0x8 + +#define QSPI_POLL_INTERVAL_US 5 + +struct altera_asmip2 { + u64 clk_hz; + void __iomem *csr_base; + u32 num_flashes; + struct device *dev; + struct altera_asmip2_flash *flash[ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP]; + struct mutex bus_mutex; +}; + +struct altera_asmip2_flash { + struct spi_nor nor; + struct altera_asmip2 *q; +}; + +static u64 altera_asmip2_calc_to(struct altera_asmip2 *q, int num_bytes) +{ + u64 ms; + + ms = 2 * 1000 * num_bytes; + do_div(ms, q->clk_hz); + ms += ms + 200; + ms *= 1000; + return ms; +} + +static int altera_asmip2_write_reg(struct spi_nor *nor, u8 opcode, u8 *val, + int len) +{ + struct altera_asmip2_flash *flash = nor->priv; + struct altera_asmip2 *q = flash->q; + u32 reg; + int ret; + + if ((len + 1) > QSPI_FIFO_DEPTH) { + dev_err(q->dev, "%s bad len %d > %d\n", + __func__, len + 1, QSPI_FIFO_DEPTH); + return -EINVAL; + } + + writeb(opcode, q->csr_base + QSPI_DATA_REG); + + iowrite8_rep(q->csr_base + QSPI_DATA_REG, val, len); + + reg = QSPI_ACTION_EN | QSPI_ACTION_SC; + + writel(reg, q->csr_base + QSPI_ACTION_REG); + + ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg, + (((reg >> QSPI_FIFO_CNT_TX_SFT) & + QSPI_FIFO_CNT_MSK) == 0), + QSPI_POLL_INTERVAL_US, + altera_asmip2_calc_to(q, 1 + len)); + if (ret) + dev_err(q->dev, "%s timed out\n", __func__); + + reg = QSPI_ACTION_EN; + + writel(reg, q->csr_base + QSPI_ACTION_REG); + + return ret; +} + +static int altera_asmip2_read_reg(struct spi_nor *nor, u8 opcode, u8 *val, + int len) +{ + struct altera_asmip2_flash *flash = nor->priv; + struct altera_asmip2 *q = flash->q; + u32 reg; + int ret; + + if (len > QSPI_FIFO_DEPTH) { + dev_err(q->dev, "%s bad len %d > %d\n", + __func__, len, QSPI_FIFO_DEPTH); + return -EINVAL; + } + + writeb(opcode, q->csr_base + QSPI_DATA_REG); + + reg = QSPI_ACTION_EN | QSPI_ACTION_SC | + (len << QSPI_ACTION_READ_BACK_SFT); + + writel(reg, q->csr_base + QSPI_ACTION_REG); + + ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg, + ((reg & QSPI_FIFO_CNT_MSK) == len), + QSPI_POLL_INTERVAL_US, + altera_asmip2_calc_to(q, 1 + len)); + + if (!ret) + ioread8_rep(q->csr_base + QSPI_DATA_REG, val, len); + else + dev_err(q->dev, "%s timeout\n", __func__); + + writel(QSPI_ACTION_EN, q->csr_base + QSPI_ACTION_REG); + + return ret; +} + +static inline void altera_asmip2_push_offset(struct altera_asmip2 *q, + struct spi_nor *nor, + loff_t offset) +{ + int i; + u32 val; + + for (i = (nor->addr_width - 1) * 8; i >= 0; i -= 8) { + val = (offset & (0xff << i)) >> i; + writeb(val, q->csr_base + QSPI_DATA_REG); + } +} + +static ssize_t altera_asmip2_read(struct spi_nor *nor, loff_t from, size_t len, + u_char *buf) +{ + struct altera_asmip2_flash *flash = nor->priv; + struct altera_asmip2 *q = flash->q; + size_t bytes_to_read; + u32 reg; + int ret; + u64 time; + + bytes_to_read = min_t(size_t, len, QSPI_FIFO_DEPTH); + + writeb(nor->read_opcode, q->csr_base + QSPI_DATA_REG); + + altera_asmip2_push_offset(q, nor, from); + + reg = QSPI_ACTION_EN | QSPI_ACTION_SC | + (10 << QSPI_ACTION_DUMMY_SFT) | + (bytes_to_read << QSPI_ACTION_READ_BACK_SFT); + + time= altera_asmip2_calc_to(q, 1 + nor->addr_width + bytes_to_read); + + writel(reg, q->csr_base + QSPI_ACTION_REG); + + ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg, + ((reg & QSPI_FIFO_CNT_MSK) == + bytes_to_read), QSPI_POLL_INTERVAL_US, + time); + if (ret) { + dev_err(q->dev, "%s timed out\n", __func__); + bytes_to_read = 0; + } else + ioread8_rep(q->csr_base + QSPI_DATA_REG, buf, bytes_to_read); + + writel(QSPI_ACTION_EN, q->csr_base + QSPI_ACTION_REG); + + return bytes_to_read; +} + +static ssize_t altera_asmip2_write(struct spi_nor *nor, loff_t to, + size_t len, const u_char *buf) +{ + struct altera_asmip2_flash *flash = nor->priv; + struct altera_asmip2 *q = flash->q; + size_t bytes_to_write; + u32 reg; + int ret; + u64 time; + + bytes_to_write = min_t(size_t, len, + (QSPI_FIFO_DEPTH - (nor->addr_width + 1))); + + writeb(nor->program_opcode, q->csr_base + QSPI_DATA_REG); + + altera_asmip2_push_offset(q, nor, to); + + iowrite8_rep(q->csr_base + QSPI_DATA_REG, buf, bytes_to_write); + + reg = QSPI_ACTION_EN | QSPI_ACTION_SC; + + time = altera_asmip2_calc_to(q, 1 + nor->addr_width + bytes_to_write); + + writel(reg, q->csr_base + QSPI_ACTION_REG); + + ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg, + (((reg >> QSPI_FIFO_CNT_TX_SFT) & + QSPI_FIFO_CNT_MSK) == 0), + QSPI_POLL_INTERVAL_US, time); + + if (ret) { + dev_err(q->dev, + "%s timed out waiting for fifo to clear\n", + __func__); + bytes_to_write = 0; + } + + writel(QSPI_ACTION_EN, q->csr_base + QSPI_ACTION_REG); + + return bytes_to_write; +} + +static int altera_asmip2_prep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + struct altera_asmip2_flash *flash = nor->priv; + struct altera_asmip2 *q = flash->q; + + mutex_lock(&q->bus_mutex); + + return 0; +} + +static void altera_asmip2_unprep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + struct altera_asmip2_flash *flash = nor->priv; + struct altera_asmip2 *q = flash->q; + + mutex_unlock(&q->bus_mutex); +} + +static int altera_asmip2_setup_banks(struct device *dev, + u32 bank, struct device_node *np) +{ + const struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_PP, + }; + struct altera_asmip2 *q = dev_get_drvdata(dev); + struct altera_asmip2_flash *flash; + struct spi_nor *nor; + int ret = 0; + + if (bank > q->num_flashes - 1) + return -EINVAL; + + flash = devm_kzalloc(q->dev, sizeof(*flash), GFP_KERNEL); + if (!flash) + return -ENOMEM; + + q->flash[bank] = flash; + flash->q = q; + + nor = &flash->nor; + nor->dev = dev; + nor->priv = flash; + nor->mtd.priv = nor; + spi_nor_set_flash_node(nor, np); + + /* spi nor framework*/ + nor->read_reg = altera_asmip2_read_reg; + nor->write_reg = altera_asmip2_write_reg; + nor->read = altera_asmip2_read; + nor->write = altera_asmip2_write; + nor->prepare = altera_asmip2_prep; + nor->unprepare = altera_asmip2_unprep; + + ret = spi_nor_scan(nor, NULL, &hwcaps); + if (ret) { + dev_err(nor->dev, "flash not found\n"); + return ret; + } + + ret = mtd_device_register(&nor->mtd, NULL, 0); + + return ret; +} + +static int altera_asmip2_create(struct device *dev, void __iomem *csr_base, + u64 clk_hz) +{ + struct altera_asmip2 *q; + u32 reg; + + q = devm_kzalloc(dev, sizeof(*q), GFP_KERNEL); + if (!q) + return -ENOMEM; + + q->dev = dev; + q->clk_hz = clk_hz; + q->csr_base = csr_base; + + mutex_init(&q->bus_mutex); + + dev_set_drvdata(dev, q); + + reg = readl(q->csr_base + QSPI_ACTION_REG); + if (!(reg & QSPI_ACTION_RST)) { + writel((reg | QSPI_ACTION_RST), q->csr_base + QSPI_ACTION_REG); + dev_info(dev, "%s asserting reset\n", __func__); + udelay(10); + } + + writel((reg & ~QSPI_ACTION_RST), q->csr_base + QSPI_ACTION_REG); + udelay(10); + + return 0; +} + +static int altera_asmip2_add_bank(struct device *dev, + u32 bank, struct device_node *np) +{ + struct altera_asmip2 *q = dev_get_drvdata(dev); + + if (q->num_flashes >= ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP) + return -ENOMEM; + + q->num_flashes++; + + return altera_asmip2_setup_banks(dev, bank, np); +} + +static int altera_asmip2_remove_banks(struct device *dev) +{ + struct altera_asmip2 *q = dev_get_drvdata(dev); + struct altera_asmip2_flash *flash; + int i; + int ret = 0; + + if (!q) + return -EINVAL; + + /* clean up for all nor flash */ + for (i = 0; i < q->num_flashes; i++) { + flash = q->flash[i]; + if (!flash) + continue; + + /* clean up mtd stuff */ + ret = mtd_device_unregister(&flash->nor.mtd); + if (ret) { + dev_err(dev, "error removing mtd\n"); + return ret; + } + } + + return 0; +} + +static int altera_asmip2_probe_with_pdata(struct platform_device *pdev, + struct altera_asmip2_plat_data *qdata) +{ + struct device *dev = &pdev->dev; + int ret, i; + + ret = altera_asmip2_create(dev, qdata->csr_base, qdata->clk_hz); + + if (ret) { + dev_err(dev, "failed to create qspi device %d\n", ret); + return ret; + } + + for (i = 0; i < qdata->num_chip_sel; i++) { + ret = altera_asmip2_add_bank(dev, i, NULL); + if (ret) { + dev_err(dev, "failed to add qspi bank %d\n", ret); + break; + } + } + + return ret; +} + +static int altera_asmip2_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct altera_asmip2_plat_data *qdata; + struct resource *res; + void __iomem *csr_base; + u32 bank; + int ret; + struct device_node *pp; + struct clk *clk; + + qdata = dev_get_platdata(dev); + + if (qdata) + return altera_asmip2_probe_with_pdata(pdev, qdata); + + if (!np) { + dev_err(dev, "no device tree found %p\n", pdev); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + csr_base = devm_ioremap_resource(dev, res); + if (IS_ERR(csr_base)) { + dev_err(dev, "%s: ERROR: failed to map csr base\n", __func__); + return PTR_ERR(csr_base); + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + ret = altera_asmip2_create(dev, csr_base, clk_get_rate(clk)); + + if (ret) { + dev_err(dev, "failed to create qspi device\n"); + return ret; + } + + for_each_available_child_of_node(np, pp) { + of_property_read_u32(pp, "reg", &bank); + if (bank >= ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP) { + dev_err(dev, "bad reg value %u >= %u\n", bank, + ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP); + goto error; + } + + if (altera_asmip2_add_bank(dev, bank, pp)) { + dev_err(dev, "failed to add bank %u\n", bank); + goto error; + } + } + + return 0; +error: + altera_asmip2_remove_banks(dev); + return -EIO; +} + +static int altera_asmip2_remove(struct platform_device *pdev) +{ + struct altera_asmip2 *q = dev_get_drvdata(&pdev->dev); + struct clk *clk; + clk = devm_clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)){ + clk_put(clk); + clk_disable_unprepare(clk); + } + + mutex_destroy(&q->bus_mutex); + + return altera_asmip2_remove_banks(&pdev->dev); +} + +static const struct of_device_id altera_asmip2_id_table[] = { + { .compatible = "altr,asmi-parallel2-spi-nor",}, + {} +}; +MODULE_DEVICE_TABLE(of, altera_asmip2_id_table); + +static struct platform_driver altera_asmip2_driver = { + .driver = { + .name = ALTERA_ASMIP2_DRV_NAME, + .of_match_table = altera_asmip2_id_table, + }, + .probe = altera_asmip2_probe, + .remove = altera_asmip2_remove, +}; +module_platform_driver(altera_asmip2_driver); + +MODULE_AUTHOR("Matthew Gerlach "); +MODULE_DESCRIPTION("Altera ASMI Parallel II"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" ALTERA_ASMIP2_DRV_NAME); diff --git a/include/linux/mtd/altera-asmip2.h b/include/linux/mtd/altera-asmip2.h new file mode 100644 index 0000000..3e2392a --- /dev/null +++ b/include/linux/mtd/altera-asmip2.h @@ -0,0 +1,25 @@ +/* + * + * Copyright 2017 Intel Corporation, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __ALTERA_QUADSPI_H +#define __ALTERA_QUADSPI_H + +#include + +#define ALTERA_ASMIP2_DRV_NAME "altr-asmip2" +#define ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP 3 +#define ALTERA_ASMIP2_RESOURCE_SIZE 0x10 + +struct altera_asmip2_plat_data { + u64 clk_hz; + void __iomem *csr_base; + u32 num_chip_sel; +}; + +#endif