From patchwork Tue Oct 2 06:13:55 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ryder Lee X-Patchwork-Id: 977717 X-Patchwork-Delegate: trini@ti.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.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mediatek.com Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 42Pbyw5L9dz9sj9 for ; Tue, 2 Oct 2018 21:10:00 +1000 (AEST) Received: by lists.denx.de (Postfix, from userid 105) id 4EAADC22028; Tue, 2 Oct 2018 11:06:49 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: * X-Spam-Status: No, score=1.3 required=5.0 tests=RDNS_NONE, UNPARSEABLE_RELAY autolearn=no autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 9BA2EC22018; Tue, 2 Oct 2018 10:59:37 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 650B9C21FB3; Tue, 2 Oct 2018 06:14:14 +0000 (UTC) Received: from mailgw02.mediatek.com (unknown [210.61.82.184]) by lists.denx.de (Postfix) with ESMTPS id 7F0EEC21FB5 for ; Tue, 2 Oct 2018 06:14:13 +0000 (UTC) X-UUID: f97d17eb0678499aaf9893ee5f00c5dc-20181002 Received: from mtkcas08.mediatek.inc [(172.21.101.126)] by mailgw02.mediatek.com (envelope-from ) (mhqrelay.mediatek.com ESMTP with TLS) with ESMTP id 275337311; Tue, 02 Oct 2018 14:14:06 +0800 Received: from MTKCAS06.mediatek.inc (172.21.101.30) by mtkmbs08n1.mediatek.inc (172.21.101.55) with Microsoft SMTP Server (TLS) id 15.0.1395.4; Tue, 2 Oct 2018 14:14:04 +0800 Received: from mtkslt306.mediatek.inc (10.21.14.136) by MTKCAS06.mediatek.inc (172.21.101.73) with Microsoft SMTP Server id 15.0.1395.4 via Frontend Transport; Tue, 2 Oct 2018 14:14:05 +0800 From: Ryder Lee To: Tom Rini , Simon Glass , Albert Aribaud Date: Tue, 2 Oct 2018 14:13:55 +0800 Message-ID: <8fb2d6da7f5a599cc8fcb89b979efbc9e7e9c243.1538460580.git.ryder.lee@mediatek.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: References: MIME-Version: 1.0 X-MTK: N X-Mailman-Approved-At: Tue, 02 Oct 2018 10:59:24 +0000 Cc: Steven Liu , Roy Luo , Sean Wang , Weijie Gao , u-boot@lists.denx.de, Jagan Teki , Guochun Mao Subject: [U-Boot] [PATCH 18/20] spi: mtk_qspi: add qspi driver for MT7629 SoC X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" From: Guochun Mao This patch adds MT7629 qspi driver for accessing SPI NOR flash. Cc: Jagan Teki Signed-off-by: Guochun Mao --- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/mtk_qspi.c | 406 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 414 insertions(+) create mode 100644 drivers/spi/mtk_qspi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7d4d47d..0c37ac0 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -117,6 +117,13 @@ config MVEBU_A3700_SPI used to access the SPI NOR flash on platforms embedding this Marvell IP core. +config MTK_QSPI + bool "Mediatek QSPI driver" + help + Enable the Mediatek QSPI driver. This driver can be + used to access the SPI NOR flash on platforms embedding this + Mediatek QSPI IP core. + config PIC32_SPI bool "Microchip PIC32 SPI driver" depends on MACH_PIC32 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 6679987..f1c6400 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o obj-$(CONFIG_LPC32XX_SSP) += lpc32xx_ssp.o obj-$(CONFIG_MPC8XX_SPI) += mpc8xx_spi.o obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o +obj-$(CONFIG_MTK_QSPI) += mtk_qspi.o obj-$(CONFIG_MVEBU_A3700_SPI) += mvebu_a3700_spi.o obj-$(CONFIG_MXC_SPI) += mxc_spi.o obj-$(CONFIG_MXS_SPI) += mxs_spi.o diff --git a/drivers/spi/mtk_qspi.c b/drivers/spi/mtk_qspi.c new file mode 100644 index 0000000..87117fa --- /dev/null +++ b/drivers/spi/mtk_qspi.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 MediaTek, Inc. + * Author : Guochun.Mao@mediatek.com + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Register Offset */ +struct mtk_qspi_regs { + u32 cmd; + u32 cnt; + u32 rdsr; + u32 rdata; + u32 radr[3]; + u32 wdata; + u32 prgdata[6]; + u32 shreg[10]; + u32 cfg[2]; + u32 shreg10; + u32 mode_mon; + u32 status[4]; + u32 flash_time; + u32 flash_cfg; + u32 reserved_0[3]; + u32 sf_time; + u32 pp_dw_data; + u32 reserved_1; + u32 delsel_0[2]; + u32 intrstus; + u32 intren; + u32 reserved_2; + u32 cfg3; + u32 reserved_3; + u32 chksum; + u32 aaicmd; + u32 wrprot; + u32 radr3; + u32 dual; + u32 delsel_1[3]; +}; + +struct mtk_qspi_platdata { + fdt_addr_t reg_base; + fdt_addr_t mem_base; +}; + +struct mtk_qspi_priv { + struct mtk_qspi_regs *regs; + unsigned long *mem_base; + u8 op; + /* Max paras length is 3 bytes. */ + u8 tx[3]; + u32 txlen; + u8 *rx; + u32 rxlen; +}; + +#define MTK_QSPI_CMD_POLLINGREG_US 500000 +#define MTK_QSPI_WRBUF_SIZE 256 +#define MTK_QSPI_COMMAND_ENABLE 0x30 + +/* NOR flash controller commands */ +#define MTK_QSPI_RD_TRIGGER BIT(0) +#define MTK_QSPI_READSTATUS BIT(1) +#define MTK_QSPI_PRG_CMD BIT(2) +#define MTK_QSPI_WR_TRIGGER BIT(4) +#define MTK_QSPI_WRITESTATUS BIT(5) +#define MTK_QSPI_AUTOINC BIT(7) + +/* NOR flash commands */ +#define MTK_QSPI_OP_WREN 0x6 +#define MTK_QSPI_OP_READ_QUAD 0x6B +#define MTK_QSPI_OP_READ_DUAL 0x3B +#define MTK_QSPI_OP_FAST_READ 0xB +#define MTK_QSPI_OP_READ 0x3 +#define MTK_QSPI_OP_PP 0x2 + +#define MTK_QSPI_MAX_RX_TX_SHIFT 0x6 +#define MTK_QSPI_MAX_SHIFT 0x8 + +#define MTK_QSPI_WR_BUF_ENABLE 0x1 +#define MTK_QSPI_WR_BUF_DISABLE 0x0 + +#define MTK_QSPI_FAST_READ_ENABLE 0x1 +#define MTK_QSPI_FAST_READ_DISABLE 0x0 + +#define MTK_QSPI_READ_MODE_MASK 0x1f +#define MTK_QSPI_READ_QUAD_EN 0x4 +#define MTK_QSPI_READ_DUAL_EN 0x1 + +static int mtk_qspi_execute_cmd(struct mtk_qspi_priv *priv, u8 cmd) +{ + u8 tmp; + u8 val = cmd & ~MTK_QSPI_AUTOINC; + + writeb(cmd, &priv->regs->cmd); + + return readb_poll_timeout(&priv->regs->cmd, tmp, !(val & tmp), + MTK_QSPI_CMD_POLLINGREG_US); +} + +static int mtk_qspi_tx_rx(struct mtk_qspi_priv *priv) +{ + int len = 1 + priv->txlen + priv->rxlen; + int i, ret, idx; + + if (len > MTK_QSPI_MAX_SHIFT) + return -ERR_INVAL; + + writeb(len * 8, &priv->regs->cnt); + + /* start at PRGDATA5, go down to PRGDATA0 */ + idx = MTK_QSPI_MAX_RX_TX_SHIFT - 1; + + /* opcode */ + writeb(priv->op, &priv->regs->prgdata[idx]); + idx--; + + /* program TX data */ + for (i = 0; i < priv->txlen; i++, idx--) + writeb(priv->tx[i], &priv->regs->prgdata[idx]); + + /* clear out rest of TX registers */ + while (idx >= 0) { + writeb(0, &priv->regs->prgdata[idx]); + idx--; + } + + ret = mtk_qspi_execute_cmd(priv, MTK_QSPI_PRG_CMD); + if (ret) + return ret; + + /* restart at first RX byte */ + idx = priv->rxlen - 1; + + /* read out RX data */ + for (i = 0; i < priv->rxlen; i++, idx--) + priv->rx[i] = readb(&priv->regs->shreg[idx]); + + return 0; +} + +static void mtk_qspi_set_read_mode(struct mtk_qspi_priv *priv) +{ + u8 val = readb(&priv->regs->dual) & (~MTK_QSPI_READ_MODE_MASK); + + /* clean fast read bit */ + writeb(MTK_QSPI_FAST_READ_DISABLE, &priv->regs->cfg[0]); + + switch (priv->op) { + case MTK_QSPI_OP_FAST_READ: + writeb(MTK_QSPI_FAST_READ_ENABLE, &priv->regs->cfg[0]); + break; + case MTK_QSPI_OP_READ_DUAL: + writeb(priv->op, &priv->regs->prgdata[3]); + val |= MTK_QSPI_READ_DUAL_EN; + break; + case MTK_QSPI_OP_READ_QUAD: + writeb(priv->op, &priv->regs->prgdata[4]); + val |= MTK_QSPI_READ_QUAD_EN; + break; + default: + break; + } + + writeb(val, &priv->regs->dual); +} + +static int mtk_qspi_read(struct mtk_qspi_priv *priv, + u32 addr, u8 *buf, u32 len) +{ + memcpy(buf, (u8 *)priv->mem_base + addr, len); + return 0; +} + +static void mtk_qspi_set_addr(struct mtk_qspi_priv *priv, u32 addr) +{ + int i; + + for (i = 0; i < 3; i++) { + writeb(addr & 0xff, &priv->regs->radr[i]); + addr >>= 8; + } +} + +static int mtk_qspi_write_single_byte(struct mtk_qspi_priv *priv, + u32 addr, u32 length, const u8 *data) +{ + int i, ret; + + mtk_qspi_set_addr(priv, addr); + + for (i = 0; i < length; i++) { + writeb(*data++, &priv->regs->wdata); + ret = mtk_qspi_execute_cmd(priv, MTK_QSPI_WR_TRIGGER); + if (ret < 0) + return ret; + } + return 0; +} + +static int mtk_qspi_write_buffer(struct mtk_qspi_priv *priv, u32 addr, + const u8 *buf) +{ + int i, data; + + mtk_qspi_set_addr(priv, addr); + + for (i = 0; i < MTK_QSPI_WRBUF_SIZE; i += 4) { + data = buf[i + 3] << 24 | buf[i + 2] << 16 | + buf[i + 1] << 8 | buf[i]; + writel(data, &priv->regs->pp_dw_data); + } + + return mtk_qspi_execute_cmd(priv, MTK_QSPI_WR_TRIGGER); +} + +static int mtk_qspi_write(struct mtk_qspi_priv *priv, + u32 addr, const u8 *buf, u32 len) +{ + int ret; + + /* setting pre-fetch buffer for page program*/ + writel(MTK_QSPI_WR_BUF_ENABLE, &priv->regs->cfg[1]); + while (len >= MTK_QSPI_WRBUF_SIZE) { + ret = mtk_qspi_write_buffer(priv, addr, buf); + if (ret < 0) + return ret; + + len -= MTK_QSPI_WRBUF_SIZE; + addr += MTK_QSPI_WRBUF_SIZE; + buf += MTK_QSPI_WRBUF_SIZE; + } + /* disable pre-fetch buffer for page program*/ + writel(MTK_QSPI_WR_BUF_DISABLE, &priv->regs->cfg[1]); + + if (len) + return mtk_qspi_write_single_byte(priv, addr, len, buf); + + return 0; +} + +static int mtk_qspi_claim_bus(struct udevice *dev) +{ + /* Nothing to do */ + return 0; +} + +static int mtk_qspi_release_bus(struct udevice *dev) +{ + /* Nothing to do */ + return 0; +} + +static int mtk_qspi_transfer(struct mtk_qspi_priv *priv, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + u32 bytes = DIV_ROUND_UP(bitlen, 8); + u32 addr; + + if (!bytes) + return -ERR_INVAL; + + if (dout) { + if (flags & SPI_XFER_BEGIN) { + /* dout format unknown here, + * parse op code & potential paras first. + */ + priv->op = *(u8 *)dout; + if (bytes > 1) + memcpy(priv->tx, (u8 *)dout + 1, + bytes <= 4 ? bytes - 1 : 3); + priv->txlen = bytes - 1; + } + + if (flags == SPI_XFER_END) { + if (priv->op == MTK_QSPI_OP_PP) { + /* here dout format should be: + * 1B op-code + 3B address + data + */ + if (bytes <= 4) + return -ERR_INVAL; + addr = priv->tx[0] << 16 | priv->tx[1] << 8 | + priv->tx[2]; + return mtk_qspi_write(priv, addr, + (u8 *)dout + 4, + bytes - 4); + } + + /* operations without reveiving data. */ + priv->rx = NULL; + priv->rxlen = 0; + return mtk_qspi_tx_rx(priv); + } + } + + if (din) { + if (priv->op == MTK_QSPI_OP_READ || + priv->op == MTK_QSPI_OP_FAST_READ || + priv->op == MTK_QSPI_OP_READ_DUAL || + priv->op == MTK_QSPI_OP_READ_QUAD) { + /* if run to here, priv->tx[3] should be the address + * where read data from, + * and, din is the buf to receive data. + */ + mtk_qspi_set_read_mode(priv); + addr = priv->tx[0] << 16 | priv->tx[1] << 8 | + priv->tx[2]; + return mtk_qspi_read(priv, addr, (u8 *)din, bytes); + } + + /* would be reading flash register */ + priv->rx = (u8 *)din; + priv->rxlen = bytes; + return mtk_qspi_tx_rx(priv); + } + + return 0; +} + +static int mtk_qspi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct mtk_qspi_priv *priv = dev_get_priv(bus); + + return mtk_qspi_transfer(priv, bitlen, dout, din, flags); +} + +static int mtk_qspi_set_speed(struct udevice *bus, uint speed) +{ + /* Nothing to do */ + return 0; +} + +static int mtk_qspi_set_mode(struct udevice *bus, uint mode) +{ + /* Nothing to do */ + return 0; +} + +static int mtk_qspi_ofdata_to_platdata(struct udevice *bus) +{ + struct resource res_reg, res_mem; + struct mtk_qspi_platdata *plat = bus->platdata; + int ret; + + ret = dev_read_resource_byname(bus, "reg_base", &res_reg); + if (ret) { + debug("Error: can't get regs base address(ret = %d)!\n", ret); + return -ENOMEM; + } + ret = dev_read_resource_byname(bus, "mem_base", &res_mem); + if (ret) { + debug("Error: can't get mmap base address(ret = %d)!\n", ret); + return -ENOMEM; + } + plat->mem_base = res_mem.start; + plat->reg_base = res_reg.start; + + return 0; +} + +static int mtk_qspi_probe(struct udevice *bus) +{ + struct mtk_qspi_platdata *plat = dev_get_platdata(bus); + struct mtk_qspi_priv *priv = dev_get_priv(bus); + + priv->regs = (struct mtk_qspi_regs *)plat->reg_base; + priv->mem_base = (unsigned long *)plat->mem_base; + + writel(MTK_QSPI_COMMAND_ENABLE, &priv->regs->wrprot); + + return 0; +} + +static const struct dm_spi_ops mtk_qspi_ops = { + .claim_bus = mtk_qspi_claim_bus, + .release_bus = mtk_qspi_release_bus, + .xfer = mtk_qspi_xfer, + .set_speed = mtk_qspi_set_speed, + .set_mode = mtk_qspi_set_mode, +}; + +static const struct udevice_id mtk_qspi_ids[] = { + { .compatible = "mediatek,mt7629-qspi" }, + { } +}; + +U_BOOT_DRIVER(mtk_qspi) = { + .name = "mtk_qspi", + .id = UCLASS_SPI, + .of_match = mtk_qspi_ids, + .ops = &mtk_qspi_ops, + .ofdata_to_platdata = mtk_qspi_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct mtk_qspi_platdata), + .priv_auto_alloc_size = sizeof(struct mtk_qspi_priv), + .probe = mtk_qspi_probe, +};