spi: add spi-mem driver for MediaTek MT7629 SoC
This patch adds spi-mem driver for MediaTek MT7629 SoC to access SPI-NOR and SPI-NAND flashes. Signed-off-by: Weijie Gao <weijie.gao@mediatek.com> [jagan: squash MAINTAINERS file] Signed-off-by: Jagan Teki <jagan@amarulasolutions.com> Reviewed-by: Jagan Teki <jagan@amarulasolutions.com>
This commit is contained in:
		
							parent
							
								
									9454fee460
								
							
						
					
					
						commit
						603fcd16b1
					
				|  | @ -206,6 +206,7 @@ F:	drivers/pinctrl/mediatek/ | |||
| F:	drivers/power/domain/mtk-power-domain.c | ||||
| F:	drivers/ram/mediatek/ | ||||
| F:	drivers/spi/mtk_qspi.c | ||||
| F:	drivers/spi/mtk_snfi_spi.c | ||||
| F:	drivers/timer/mtk_timer.c | ||||
| F:	drivers/watchdog/mtk_wdt.c | ||||
| F:	drivers/net/mtk_eth.c | ||||
|  |  | |||
|  | @ -166,6 +166,15 @@ config MTK_QSPI | |||
| 	  used to access the SPI NOR flash on platforms embedding this | ||||
| 	  Mediatek QSPI IP core. | ||||
| 
 | ||||
| config MTK_SNFI_SPI | ||||
| 	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 | ||||
|  |  | |||
|  | @ -38,6 +38,7 @@ obj-$(CONFIG_MESON_SPIFC) += meson_spifc.o | |||
| obj-$(CONFIG_MPC8XX_SPI) += mpc8xx_spi.o | ||||
| obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o | ||||
| obj-$(CONFIG_MTK_QSPI) += mtk_qspi.o | ||||
| obj-$(CONFIG_MTK_SNFI_SPI) += mtk_snfi_spi.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 | ||||
|  |  | |||
|  | @ -0,0 +1,318 @@ | |||
| // SPDX-License-Identifier: GPL-2.0+
 | ||||
| /*
 | ||||
|  * Copyright (C) 2019 MediaTek Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Author: Weijie Gao <weijie.gao@mediatek.com> | ||||
|  */ | ||||
| 
 | ||||
| #include <common.h> | ||||
| #include <clk.h> | ||||
| #include <dm.h> | ||||
| #include <errno.h> | ||||
| #include <spi.h> | ||||
| #include <spi-mem.h> | ||||
| #include <stdbool.h> | ||||
| #include <watchdog.h> | ||||
| #include <dm/pinctrl.h> | ||||
| #include <linux/bitops.h> | ||||
| #include <linux/io.h> | ||||
| #include <linux/iopoll.h> | ||||
| 
 | ||||
| #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_snfi_priv { | ||||
| 	void __iomem *base; | ||||
| 
 | ||||
| 	struct clk nfi_clk; | ||||
| 	struct clk pad_clk; | ||||
| }; | ||||
| 
 | ||||
| static int mtk_snfi_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_snfi_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_snfi_priv *priv, | ||||
| 				struct udevice *bus, u32 outlen, u32 inlen) | ||||
| { | ||||
| 	int ret; | ||||
| 	u32 val; | ||||
| 
 | ||||
| #ifdef CONFIG_PINCTRL | ||||
| 	pinctrl_select_state(bus, "snfi"); | ||||
| #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(bus, "default"); | ||||
| #endif | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int mtk_snfi_mac_reset(struct mtk_snfi_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_snfi_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_snfi_priv *priv, u8 *cache, | ||||
| 				     void *data, size_t pos, size_t len) | ||||
| { | ||||
| 	void __iomem *gpram = priv->base + SNFI_GPRAM_OFFSET; | ||||
| 	u32 *buff = (u32 *)cache; | ||||
| 	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 rx data */ | ||||
| 	memcpy(data, cache + off, len); | ||||
| } | ||||
| 
 | ||||
| static int mtk_snfi_exec_op(struct spi_slave *slave, | ||||
| 			    const struct spi_mem_op *op) | ||||
| { | ||||
| 	struct udevice *bus = dev_get_parent(slave->dev); | ||||
| 	struct mtk_snfi_priv *priv = dev_get_priv(bus); | ||||
| 	u8 gpram_cache[SNFI_GPRAM_SIZE]; | ||||
| 	u32 i, len = 0, inlen = 0; | ||||
| 	int addr_sh; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	WATCHDOG_RESET(); | ||||
| 
 | ||||
| 	ret = mtk_snfi_mac_reset(priv); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	/* Put opcode */ | ||||
| 	gpram_cache[len++] = op->cmd.opcode; | ||||
| 
 | ||||
| 	/* Put address */ | ||||
| 	addr_sh = (op->addr.nbytes - 1) * 8; | ||||
| 	while (addr_sh >= 0) { | ||||
| 		gpram_cache[len++] = (op->addr.val >> addr_sh) & 0xff; | ||||
| 		addr_sh -= 8; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Put dummy bytes */ | ||||
| 	for (i = 0; i < op->dummy.nbytes; i++) | ||||
| 		gpram_cache[len++] = 0; | ||||
| 
 | ||||
| 	/* Put output data */ | ||||
| 	if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT) { | ||||
| 		memcpy(gpram_cache + 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_cache, 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, bus, len, inlen); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	/* Copy input data from GPRAM */ | ||||
| 	if (inlen) | ||||
| 		mtk_snfi_copy_from_gpram(priv, gpram_cache, op->data.buf.in, | ||||
| 					 len, inlen); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int mtk_snfi_spi_probe(struct udevice *bus) | ||||
| { | ||||
| 	struct mtk_snfi_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); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int mtk_snfi_set_speed(struct udevice *bus, uint speed) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * The SNFI does not have a bus clock divider. | ||||
| 	 * The bus clock is set in dts (pad_clk, UNIVPLL2_D8 = 50MHz). | ||||
| 	 */ | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int mtk_snfi_set_mode(struct udevice *bus, uint mode) | ||||
| { | ||||
| 	/* The SNFI supports only mode 0 */ | ||||
| 
 | ||||
| 	if (mode) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct spi_controller_mem_ops mtk_snfi_mem_ops = { | ||||
| 	.adjust_op_size = mtk_snfi_adjust_op_size, | ||||
| 	.supports_op = mtk_snfi_supports_op, | ||||
| 	.exec_op = mtk_snfi_exec_op, | ||||
| }; | ||||
| 
 | ||||
| static const struct dm_spi_ops mtk_snfi_spi_ops = { | ||||
| 	.mem_ops	= &mtk_snfi_mem_ops, | ||||
| 	.set_speed	= mtk_snfi_set_speed, | ||||
| 	.set_mode	= mtk_snfi_set_mode, | ||||
| }; | ||||
| 
 | ||||
| static const struct udevice_id mtk_snfi_spi_ids[] = { | ||||
| 	{ .compatible = "mediatek,mtk-snfi-spi" }, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
| U_BOOT_DRIVER(mtk_snfi_spi) = { | ||||
| 	.name			= "mtk_snfi_spi", | ||||
| 	.id			= UCLASS_SPI, | ||||
| 	.of_match		= mtk_snfi_spi_ids, | ||||
| 	.ops			= &mtk_snfi_spi_ops, | ||||
| 	.priv_auto_alloc_size	= sizeof(struct mtk_snfi_priv), | ||||
| 	.probe			= mtk_snfi_spi_probe, | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue