From mboxrd@z Thu Jan 1 00:00:00 1970 From: Weijie Gao Date: Fri, 26 Apr 2019 17:22:35 +0800 Subject: [U-Boot] [PATCH 3/5] spi: add spi-mem driver for MediaTek MT7629 SoC Message-ID: <1556270555-7709-1-git-send-email-weijie.gao@mediatek.com> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de This patch adds spi-mem driver for MediaTek MT7629 SoC to access SPI-NOR and SPI-NAND flashes. Cc: Jagan Teki Signed-off-by: Weijie Gao --- drivers/spi/Kconfig | 9 ++ drivers/spi/Makefile | 1 + drivers/spi/mtk_spimem.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 drivers/spi/mtk_spimem.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 92d7ca6..cdb726c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -139,6 +139,15 @@ config MT7621_SPI the SPI NOR flash on platforms embedding this Ralink / MediaTek SPI core, like MT7621/7628/7688. +config MTK_SPIMEM + bool "Mediatek SPI memory controller driver" + depends on SPI_MEM + help + Enable the Mediatek SPI memory controller driver. This driver is + originally based on the MediaTek SNFI IP core. It can only be + used to access SPI memory devices like SPI-NOR or SPI-NAND on + platforms embedding this IP core, like MT7622/M7629. + config MVEBU_A3700_SPI bool "Marvell Armada 3700 SPI driver" select CLK_ARMADA_3720 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index f1e3bec..71a598e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_LPC32XX_SSP) += lpc32xx_ssp.o obj-$(CONFIG_MESON_SPIFC) += meson_spifc.o obj-$(CONFIG_MPC8XX_SPI) += mpc8xx_spi.o obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o +obj-$(CONFIG_MTK_SPIMEM) += mtk_spimem.o obj-$(CONFIG_MT7621_SPI) += mt7621_spi.o obj-$(CONFIG_MSCC_BB_SPI) += mscc_bb_spi.o obj-$(CONFIG_MVEBU_A3700_SPI) += mvebu_a3700_spi.o diff --git a/drivers/spi/mtk_spimem.c b/drivers/spi/mtk_spimem.c new file mode 100644 index 0000000..ad25160 --- /dev/null +++ b/drivers/spi/mtk_spimem.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 MediaTek Inc. All Rights Reserved. + * + * Author: Weijie Gao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SNFI_MAC_CTL 0x500 +#define MAC_XIO_SEL BIT(4) +#define SF_MAC_EN BIT(3) +#define SF_TRIG BIT(2) +#define WIP_READY BIT(1) +#define WIP BIT(0) + +#define SNFI_MAC_OUTL 0x504 +#define SNFI_MAC_INL 0x508 + +#define SNFI_MISC_CTL 0x538 +#define SW_RST BIT(28) +#define FIFO_RD_LTC_SHIFT 25 +#define FIFO_RD_LTC GENMASK(26, 25) +#define LATCH_LAT_SHIFT 8 +#define LATCH_LAT GENMASK(9, 8) +#define CS_DESELECT_CYC_SHIFT 0 +#define CS_DESELECT_CYC GENMASK(4, 0) + +#define SNF_STA_CTL1 0x550 +#define SPI_STATE GENMASK(3, 0) + +#define SNFI_GPRAM_OFFSET 0x800 +#define SNFI_GPRAM_SIZE 0x80 + +#define SNFI_POLL_INTERVAL 500000 +#define SNFI_RST_POLL_INTERVAL 1000000 + +struct mtk_spimem_priv { + void __iomem *base; + + struct udevice *dev; + + struct clk nfi_clk; + struct clk pad_clk; +}; + +static int mtk_spimem_adjust_op_size(struct spi_slave *slave, + struct spi_mem_op *op) +{ + u32 nbytes; + + /* + * When there is input data, it will be appended after the output + * data in the GPRAM. So the total size of either pure output data + * or the output+input data must not exceed the GPRAM size. + */ + + nbytes = sizeof(op->cmd.opcode) + op->addr.nbytes + + op->dummy.nbytes; + + if (nbytes + op->data.nbytes <= SNFI_GPRAM_SIZE) + return 0; + + if (nbytes >= SNFI_GPRAM_SIZE) + return -ENOTSUPP; + + op->data.nbytes = SNFI_GPRAM_SIZE - nbytes; + + return 0; +} + +static bool mtk_spimem_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + if (op->cmd.buswidth > 1 || op->addr.buswidth > 1 || + op->dummy.buswidth > 1 || op->data.buswidth > 1) + return false; + + return true; +} + +static int mtk_snfi_mac_trigger(struct mtk_spimem_priv *priv, + u32 outlen, u32 inlen) +{ + int ret; + u32 val; + +#ifdef CONFIG_PINCTRL + pinctrl_select_state(priv->dev, "default"); +#endif + + writel(SF_MAC_EN, priv->base + SNFI_MAC_CTL); + writel(outlen, priv->base + SNFI_MAC_OUTL); + writel(inlen, priv->base + SNFI_MAC_INL); + + writel(SF_MAC_EN | SF_TRIG, priv->base + SNFI_MAC_CTL); + + ret = readl_poll_timeout(priv->base + SNFI_MAC_CTL, val, + val & WIP_READY, SNFI_POLL_INTERVAL); + if (ret) { + printf("%s: timed out waiting for WIP_READY\n", __func__); + goto cleanup; + } + + ret = readl_poll_timeout(priv->base + SNFI_MAC_CTL, val, + !(val & WIP), SNFI_POLL_INTERVAL); + if (ret) + printf("%s: timed out waiting for WIP cleared\n", __func__); + + writel(0, priv->base + SNFI_MAC_CTL); + +cleanup: +#ifdef CONFIG_PINCTRL + pinctrl_select_state(priv->dev, "snor_mmap"); +#endif + + return ret; +} + +static int mtk_snfi_mac_reset(struct mtk_spimem_priv *priv) +{ + int ret; + u32 val; + + setbits_32(priv->base + SNFI_MISC_CTL, SW_RST); + + ret = readl_poll_timeout(priv->base + SNF_STA_CTL1, val, + !(val & SPI_STATE), SNFI_POLL_INTERVAL); + if (ret) + printf("%s: failed to reset snfi mac\n", __func__); + + writel((2 << FIFO_RD_LTC_SHIFT) | + (10 << CS_DESELECT_CYC_SHIFT), + priv->base + SNFI_MISC_CTL); + + return ret; +} + +static void mtk_snfi_copy_to_gpram(struct mtk_spimem_priv *priv, + const void *data, size_t len) +{ + void __iomem *gpram = priv->base + SNFI_GPRAM_OFFSET; + size_t i, n = (len + sizeof(u32) - 1) / sizeof(u32); + const u32 *buff = data; + + /* + * The output data will always be copied to the beginning of + * the GPRAM. Uses word write for better performace. + * + * Trailing bytes in the last word are not cared. + */ + + for (i = 0; i < n; i++) + writel(buff[i], gpram + i * sizeof(u32)); +} + +static void mtk_snfi_copy_from_gpram(struct mtk_spimem_priv *priv, + void *data, size_t pos, size_t len) +{ + void __iomem *gpram = priv->base + SNFI_GPRAM_OFFSET; + u8 gpdata[SNFI_GPRAM_SIZE]; + u32 *buff = (u32 *)gpdata; + size_t i, off, end; + + /* Start position in the buffer */ + off = pos & (sizeof(u32) - 1); + + /* End position for copy */ + end = (len + pos + sizeof(u32) - 1) & (~(sizeof(u32) - 1)); + + /* Start position for copy */ + pos &= ~(sizeof(u32) - 1); + + /* + * Read aligned data from GPRAM to buffer first. + * Uses word read for better performace. + */ + i = 0; + while (pos < end) { + buff[i++] = readl(gpram + pos); + pos += sizeof(u32); + } + + /* Copy real data */ + memcpy(data, gpdata + off, len); +} + +static int mtk_spimem_exec_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + struct udevice *bus = dev_get_parent(slave->dev); + struct mtk_spimem_priv *priv = dev_get_priv(bus); + u8 gpram_data[SNFI_GPRAM_SIZE]; + u32 i, len = 0, inlen = 0; + int addr_sh; + int ret; + + ret = mtk_snfi_mac_reset(priv); + if (ret) + return ret; + + /* Put opcode */ + gpram_data[len++] = op->cmd.opcode; + + /* Put address */ + addr_sh = (op->addr.nbytes - 1) * 8; + while (addr_sh >= 0) { + gpram_data[len++] = (op->addr.val >> addr_sh) & 0xff; + addr_sh -= 8; + } + + /* Put dummy bytes */ + for (i = 0; i < op->dummy.nbytes; i++) + gpram_data[len++] = 0; + + /* Put output data */ + if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT) { + memcpy(gpram_data + len, op->data.buf.out, op->data.nbytes); + len += op->data.nbytes; + } + + /* Copy final output data to GPRAM */ + mtk_snfi_copy_to_gpram(priv, gpram_data, len); + + /* Start one SPI transaction */ + if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_IN) + inlen = op->data.nbytes; + + ret = mtk_snfi_mac_trigger(priv, len, inlen); + if (ret) + return ret; + + /* Copy input data from GPRAM */ + if (inlen) + mtk_snfi_copy_from_gpram(priv, op->data.buf.in, len, inlen); + + return 0; +} + +static int mtk_spimem_probe(struct udevice *bus) +{ + struct mtk_spimem_priv *priv = dev_get_priv(bus); + int ret; + + priv->base = (void __iomem *)devfdt_get_addr(bus); + if (!priv->base) + return -EINVAL; + + ret = clk_get_by_name(bus, "nfi_clk", &priv->nfi_clk); + if (ret < 0) + return ret; + + ret = clk_get_by_name(bus, "pad_clk", &priv->pad_clk); + if (ret < 0) + return ret; + + clk_enable(&priv->nfi_clk); + clk_enable(&priv->pad_clk); + + priv->dev = bus; + + return 0; +} + +static int mtk_spimem_set_speed(struct udevice *bus, uint speed) +{ + return 0; +} + +static int mtk_spimem_set_mode(struct udevice *bus, uint mode) +{ + if (mode) + return -EINVAL; + + return 0; +} + +static int mtk_spimem_child_pre_probe(struct udevice *dev) +{ + struct dm_spi_slave_platdata *plat = dev_get_parent_platdata(dev); + + if (plat->cs > 0) { + printf("%s: invalid chip select %u\n", __func__, plat->cs); + return -ENODEV; + } + + return 0; +} + +static const struct spi_controller_mem_ops mem_ops = { + .adjust_op_size = mtk_spimem_adjust_op_size, + .supports_op = mtk_spimem_supports_op, + .exec_op = mtk_spimem_exec_op, +}; + +static const struct dm_spi_ops mtk_spimem_ops = { + .mem_ops = &mem_ops, + .set_speed = mtk_spimem_set_speed, + .set_mode = mtk_spimem_set_mode, +}; + +static const struct udevice_id mtk_spimem_ids[] = { + { .compatible = "mediatek,mtk-spimem" }, + { } +}; + +U_BOOT_DRIVER(mtk_spimem) = { + .name = "mtk_spimem", + .id = UCLASS_SPI, + .of_match = mtk_spimem_ids, + .ops = &mtk_spimem_ops, + .priv_auto_alloc_size = sizeof(struct mtk_spimem_priv), + .probe = mtk_spimem_probe, + .child_pre_probe = mtk_spimem_child_pre_probe, +}; -- 1.9.1