123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748 |
- From e6b225ff8990635dc2d6d8dbd72e78dec1f36c62 Mon Sep 17 00:00:00 2001
- From: Weijie Gao <[email protected]>
- Date: Wed, 31 Aug 2022 19:04:45 +0800
- Subject: [PATCH 16/32] spi: add support for MediaTek spi-mem controller
- This patch adds support for spi-mem controller found on newer MediaTek SoCs
- This controller supports Single/Dual/Quad SPI mode.
- Reviewed-by: Simon Glass <[email protected]>
- Signed-off-by: SkyLake.Huang <[email protected]>
- ---
- drivers/spi/Kconfig | 8 +
- drivers/spi/Makefile | 1 +
- drivers/spi/mtk_spim.c | 701 +++++++++++++++++++++++++++++++++++++++++
- 3 files changed, 710 insertions(+)
- create mode 100644 drivers/spi/mtk_spim.c
- --- a/drivers/spi/Kconfig
- +++ b/drivers/spi/Kconfig
- @@ -276,6 +276,14 @@ config MTK_SNFI_SPI
- used to access SPI memory devices like SPI-NOR or SPI-NAND on
- platforms embedding this IP core, like MT7622/M7629.
-
- +config MTK_SPIM
- + bool "Mediatek SPI-MEM master controller driver"
- + depends on SPI_MEM
- + help
- + Enable MediaTek SPI-MEM master controller driver. This driver mainly
- + supports SPI flashes. You can use single, dual or quad mode
- + transmission on this controller.
- +
- config MVEBU_A3700_SPI
- bool "Marvell Armada 3700 SPI driver"
- select CLK_ARMADA_3720
- --- a/drivers/spi/Makefile
- +++ b/drivers/spi/Makefile
- @@ -43,6 +43,7 @@ obj-$(CONFIG_MPC8XX_SPI) += mpc8xx_spi.o
- obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o
- obj-$(CONFIG_MTK_SNFI_SPI) += mtk_snfi_spi.o
- obj-$(CONFIG_MTK_SNOR) += mtk_snor.o
- +obj-$(CONFIG_MTK_SPIM) += mtk_spim.o
- obj-$(CONFIG_MT7620_SPI) += mt7620_spi.o
- obj-$(CONFIG_MT7621_SPI) += mt7621_spi.o
- obj-$(CONFIG_MSCC_BB_SPI) += mscc_bb_spi.o
- --- /dev/null
- +++ b/drivers/spi/mtk_spim.c
- @@ -0,0 +1,701 @@
- +// SPDX-License-Identifier: GPL-2.0
- +/*
- + * Copyright (C) 2022 MediaTek Inc. All Rights Reserved.
- + *
- + * Author: SkyLake.Huang <[email protected]>
- + */
- +
- +#include <clk.h>
- +#include <cpu_func.h>
- +#include <div64.h>
- +#include <dm.h>
- +#include <spi.h>
- +#include <spi-mem.h>
- +#include <stdbool.h>
- +#include <watchdog.h>
- +#include <dm/device.h>
- +#include <dm/device_compat.h>
- +#include <dm/devres.h>
- +#include <dm/pinctrl.h>
- +#include <linux/bitops.h>
- +#include <linux/completion.h>
- +#include <linux/dma-mapping.h>
- +#include <linux/io.h>
- +#include <linux/iopoll.h>
- +
- +#define SPI_CFG0_REG 0x0000
- +#define SPI_CFG1_REG 0x0004
- +#define SPI_TX_SRC_REG 0x0008
- +#define SPI_RX_DST_REG 0x000c
- +#define SPI_TX_DATA_REG 0x0010
- +#define SPI_RX_DATA_REG 0x0014
- +#define SPI_CMD_REG 0x0018
- +#define SPI_IRQ_REG 0x001c
- +#define SPI_STATUS_REG 0x0020
- +#define SPI_PAD_SEL_REG 0x0024
- +#define SPI_CFG2_REG 0x0028
- +#define SPI_TX_SRC_REG_64 0x002c
- +#define SPI_RX_DST_REG_64 0x0030
- +#define SPI_CFG3_IPM_REG 0x0040
- +
- +#define SPI_CFG0_SCK_HIGH_OFFSET 0
- +#define SPI_CFG0_SCK_LOW_OFFSET 8
- +#define SPI_CFG0_CS_HOLD_OFFSET 16
- +#define SPI_CFG0_CS_SETUP_OFFSET 24
- +#define SPI_ADJUST_CFG0_CS_HOLD_OFFSET 0
- +#define SPI_ADJUST_CFG0_CS_SETUP_OFFSET 16
- +
- +#define SPI_CFG1_CS_IDLE_OFFSET 0
- +#define SPI_CFG1_PACKET_LOOP_OFFSET 8
- +#define SPI_CFG1_PACKET_LENGTH_OFFSET 16
- +#define SPI_CFG1_GET_TICKDLY_OFFSET 29
- +
- +#define SPI_CFG1_GET_TICKDLY_MASK GENMASK(31, 29)
- +#define SPI_CFG1_CS_IDLE_MASK 0xff
- +#define SPI_CFG1_PACKET_LOOP_MASK 0xff00
- +#define SPI_CFG1_PACKET_LENGTH_MASK 0x3ff0000
- +#define SPI_CFG1_IPM_PACKET_LENGTH_MASK GENMASK(31, 16)
- +#define SPI_CFG2_SCK_HIGH_OFFSET 0
- +#define SPI_CFG2_SCK_LOW_OFFSET 16
- +#define SPI_CFG2_SCK_HIGH_MASK GENMASK(15, 0)
- +#define SPI_CFG2_SCK_LOW_MASK GENMASK(31, 16)
- +
- +#define SPI_CMD_ACT BIT(0)
- +#define SPI_CMD_RESUME BIT(1)
- +#define SPI_CMD_RST BIT(2)
- +#define SPI_CMD_PAUSE_EN BIT(4)
- +#define SPI_CMD_DEASSERT BIT(5)
- +#define SPI_CMD_SAMPLE_SEL BIT(6)
- +#define SPI_CMD_CS_POL BIT(7)
- +#define SPI_CMD_CPHA BIT(8)
- +#define SPI_CMD_CPOL BIT(9)
- +#define SPI_CMD_RX_DMA BIT(10)
- +#define SPI_CMD_TX_DMA BIT(11)
- +#define SPI_CMD_TXMSBF BIT(12)
- +#define SPI_CMD_RXMSBF BIT(13)
- +#define SPI_CMD_RX_ENDIAN BIT(14)
- +#define SPI_CMD_TX_ENDIAN BIT(15)
- +#define SPI_CMD_FINISH_IE BIT(16)
- +#define SPI_CMD_PAUSE_IE BIT(17)
- +#define SPI_CMD_IPM_NONIDLE_MODE BIT(19)
- +#define SPI_CMD_IPM_SPIM_LOOP BIT(21)
- +#define SPI_CMD_IPM_GET_TICKDLY_OFFSET 22
- +
- +#define SPI_CMD_IPM_GET_TICKDLY_MASK GENMASK(24, 22)
- +
- +#define PIN_MODE_CFG(x) ((x) / 2)
- +
- +#define SPI_CFG3_IPM_PIN_MODE_OFFSET 0
- +#define SPI_CFG3_IPM_HALF_DUPLEX_DIR BIT(2)
- +#define SPI_CFG3_IPM_HALF_DUPLEX_EN BIT(3)
- +#define SPI_CFG3_IPM_XMODE_EN BIT(4)
- +#define SPI_CFG3_IPM_NODATA_FLAG BIT(5)
- +#define SPI_CFG3_IPM_CMD_BYTELEN_OFFSET 8
- +#define SPI_CFG3_IPM_ADDR_BYTELEN_OFFSET 12
- +#define SPI_CFG3_IPM_DUMMY_BYTELEN_OFFSET 16
- +
- +#define SPI_CFG3_IPM_CMD_PIN_MODE_MASK GENMASK(1, 0)
- +#define SPI_CFG3_IPM_CMD_BYTELEN_MASK GENMASK(11, 8)
- +#define SPI_CFG3_IPM_ADDR_BYTELEN_MASK GENMASK(15, 12)
- +#define SPI_CFG3_IPM_DUMMY_BYTELEN_MASK GENMASK(19, 16)
- +
- +#define MT8173_SPI_MAX_PAD_SEL 3
- +
- +#define MTK_SPI_PAUSE_INT_STATUS 0x2
- +
- +#define MTK_SPI_IDLE 0
- +#define MTK_SPI_PAUSED 1
- +
- +#define MTK_SPI_MAX_FIFO_SIZE 32U
- +#define MTK_SPI_PACKET_SIZE 1024
- +#define MTK_SPI_IPM_PACKET_SIZE SZ_64K
- +#define MTK_SPI_IPM_PACKET_LOOP SZ_256
- +
- +#define MTK_SPI_32BITS_MASK 0xffffffff
- +
- +#define DMA_ADDR_EXT_BITS 36
- +#define DMA_ADDR_DEF_BITS 32
- +
- +#define CLK_TO_US(freq, clkcnt) DIV_ROUND_UP((clkcnt), (freq) / 1000000)
- +
- +/* struct mtk_spim_capability
- + * @enhance_timing: Some IC design adjust cfg register to enhance time accuracy
- + * @dma_ext: Some IC support DMA addr extension
- + * @ipm_design: The IPM IP design improves some features, and supports dual/quad mode
- + * @support_quad: Whether quad mode is supported
- + */
- +struct mtk_spim_capability {
- + bool enhance_timing;
- + bool dma_ext;
- + bool ipm_design;
- + bool support_quad;
- +};
- +
- +/* struct mtk_spim_priv
- + * @base: Base address of the spi controller
- + * @state: Controller state
- + * @sel_clk: Pad clock
- + * @spi_clk: Core clock
- + * @xfer_len: Current length of data for transfer
- + * @hw_cap: Controller capabilities
- + * @tick_dly: Used to postpone SPI sampling time
- + * @sample_sel: Sample edge of MISO
- + * @dev: udevice of this spi controller
- + * @tx_dma: Tx DMA address
- + * @rx_dma: Rx DMA address
- + */
- +struct mtk_spim_priv {
- + void __iomem *base;
- + u32 state;
- + struct clk sel_clk, spi_clk;
- + u32 xfer_len;
- + struct mtk_spim_capability hw_cap;
- + u32 tick_dly;
- + u32 sample_sel;
- +
- + struct device *dev;
- + dma_addr_t tx_dma;
- + dma_addr_t rx_dma;
- +};
- +
- +static void mtk_spim_reset(struct mtk_spim_priv *priv)
- +{
- + /* set the software reset bit in SPI_CMD_REG. */
- + setbits_le32(priv->base + SPI_CMD_REG, SPI_CMD_RST);
- + clrbits_le32(priv->base + SPI_CMD_REG, SPI_CMD_RST);
- +}
- +
- +static int mtk_spim_hw_init(struct spi_slave *slave)
- +{
- + struct udevice *bus = dev_get_parent(slave->dev);
- + struct mtk_spim_priv *priv = dev_get_priv(bus);
- + u16 cpha, cpol;
- + u32 reg_val;
- +
- + cpha = slave->mode & SPI_CPHA ? 1 : 0;
- + cpol = slave->mode & SPI_CPOL ? 1 : 0;
- +
- + if (priv->hw_cap.enhance_timing) {
- + if (priv->hw_cap.ipm_design) {
- + /* CFG3 reg only used for spi-mem,
- + * here write to default value
- + */
- + writel(0x0, priv->base + SPI_CFG3_IPM_REG);
- + clrsetbits_le32(priv->base + SPI_CMD_REG,
- + SPI_CMD_IPM_GET_TICKDLY_MASK,
- + priv->tick_dly <<
- + SPI_CMD_IPM_GET_TICKDLY_OFFSET);
- + } else {
- + clrsetbits_le32(priv->base + SPI_CFG1_REG,
- + SPI_CFG1_GET_TICKDLY_MASK,
- + priv->tick_dly <<
- + SPI_CFG1_GET_TICKDLY_OFFSET);
- + }
- + }
- +
- + reg_val = readl(priv->base + SPI_CMD_REG);
- + if (priv->hw_cap.ipm_design) {
- + /* SPI transfer without idle time until packet length done */
- + reg_val |= SPI_CMD_IPM_NONIDLE_MODE;
- + if (slave->mode & SPI_LOOP)
- + reg_val |= SPI_CMD_IPM_SPIM_LOOP;
- + else
- + reg_val &= ~SPI_CMD_IPM_SPIM_LOOP;
- + }
- +
- + if (cpha)
- + reg_val |= SPI_CMD_CPHA;
- + else
- + reg_val &= ~SPI_CMD_CPHA;
- + if (cpol)
- + reg_val |= SPI_CMD_CPOL;
- + else
- + reg_val &= ~SPI_CMD_CPOL;
- +
- + /* set the mlsbx and mlsbtx */
- + if (slave->mode & SPI_LSB_FIRST) {
- + reg_val &= ~SPI_CMD_TXMSBF;
- + reg_val &= ~SPI_CMD_RXMSBF;
- + } else {
- + reg_val |= SPI_CMD_TXMSBF;
- + reg_val |= SPI_CMD_RXMSBF;
- + }
- +
- + /* do not reverse tx/rx endian */
- + reg_val &= ~SPI_CMD_TX_ENDIAN;
- + reg_val &= ~SPI_CMD_RX_ENDIAN;
- +
- + if (priv->hw_cap.enhance_timing) {
- + /* set CS polarity */
- + if (slave->mode & SPI_CS_HIGH)
- + reg_val |= SPI_CMD_CS_POL;
- + else
- + reg_val &= ~SPI_CMD_CS_POL;
- +
- + if (priv->sample_sel)
- + reg_val |= SPI_CMD_SAMPLE_SEL;
- + else
- + reg_val &= ~SPI_CMD_SAMPLE_SEL;
- + }
- +
- + /* disable dma mode */
- + reg_val &= ~(SPI_CMD_TX_DMA | SPI_CMD_RX_DMA);
- +
- + /* disable deassert mode */
- + reg_val &= ~SPI_CMD_DEASSERT;
- +
- + writel(reg_val, priv->base + SPI_CMD_REG);
- +
- + return 0;
- +}
- +
- +static void mtk_spim_prepare_transfer(struct mtk_spim_priv *priv,
- + u32 speed_hz)
- +{
- + u32 spi_clk_hz, div, sck_time, cs_time, reg_val;
- +
- + spi_clk_hz = clk_get_rate(&priv->spi_clk);
- + if (speed_hz <= spi_clk_hz / 4)
- + div = DIV_ROUND_UP(spi_clk_hz, speed_hz);
- + else
- + div = 4;
- +
- + sck_time = (div + 1) / 2;
- + cs_time = sck_time * 2;
- +
- + if (priv->hw_cap.enhance_timing) {
- + reg_val = ((sck_time - 1) & 0xffff)
- + << SPI_CFG2_SCK_HIGH_OFFSET;
- + reg_val |= ((sck_time - 1) & 0xffff)
- + << SPI_CFG2_SCK_LOW_OFFSET;
- + writel(reg_val, priv->base + SPI_CFG2_REG);
- +
- + reg_val = ((cs_time - 1) & 0xffff)
- + << SPI_ADJUST_CFG0_CS_HOLD_OFFSET;
- + reg_val |= ((cs_time - 1) & 0xffff)
- + << SPI_ADJUST_CFG0_CS_SETUP_OFFSET;
- + writel(reg_val, priv->base + SPI_CFG0_REG);
- + } else {
- + reg_val = ((sck_time - 1) & 0xff)
- + << SPI_CFG0_SCK_HIGH_OFFSET;
- + reg_val |= ((sck_time - 1) & 0xff) << SPI_CFG0_SCK_LOW_OFFSET;
- + reg_val |= ((cs_time - 1) & 0xff) << SPI_CFG0_CS_HOLD_OFFSET;
- + reg_val |= ((cs_time - 1) & 0xff) << SPI_CFG0_CS_SETUP_OFFSET;
- + writel(reg_val, priv->base + SPI_CFG0_REG);
- + }
- +
- + reg_val = readl(priv->base + SPI_CFG1_REG);
- + reg_val &= ~SPI_CFG1_CS_IDLE_MASK;
- + reg_val |= ((cs_time - 1) & 0xff) << SPI_CFG1_CS_IDLE_OFFSET;
- + writel(reg_val, priv->base + SPI_CFG1_REG);
- +}
- +
- +/**
- + * mtk_spim_setup_packet() - setup packet format.
- + * @priv: controller priv
- + *
- + * This controller sents/receives data in packets. The packet size is
- + * configurable.
- + *
- + * This function calculates the maximum packet size available for current
- + * data, and calculates the number of packets required to sent/receive data
- + * as much as possible.
- + */
- +static void mtk_spim_setup_packet(struct mtk_spim_priv *priv)
- +{
- + u32 packet_size, packet_loop, reg_val;
- +
- + /* Calculate maximum packet size */
- + if (priv->hw_cap.ipm_design)
- + packet_size = min_t(u32,
- + priv->xfer_len,
- + MTK_SPI_IPM_PACKET_SIZE);
- + else
- + packet_size = min_t(u32,
- + priv->xfer_len,
- + MTK_SPI_PACKET_SIZE);
- +
- + /* Calculates number of packets to sent/receive */
- + packet_loop = priv->xfer_len / packet_size;
- +
- + reg_val = readl(priv->base + SPI_CFG1_REG);
- + if (priv->hw_cap.ipm_design)
- + reg_val &= ~SPI_CFG1_IPM_PACKET_LENGTH_MASK;
- + else
- + reg_val &= ~SPI_CFG1_PACKET_LENGTH_MASK;
- +
- + reg_val |= (packet_size - 1) << SPI_CFG1_PACKET_LENGTH_OFFSET;
- +
- + reg_val &= ~SPI_CFG1_PACKET_LOOP_MASK;
- +
- + reg_val |= (packet_loop - 1) << SPI_CFG1_PACKET_LOOP_OFFSET;
- +
- + writel(reg_val, priv->base + SPI_CFG1_REG);
- +}
- +
- +static void mtk_spim_enable_transfer(struct mtk_spim_priv *priv)
- +{
- + u32 cmd;
- +
- + cmd = readl(priv->base + SPI_CMD_REG);
- + if (priv->state == MTK_SPI_IDLE)
- + cmd |= SPI_CMD_ACT;
- + else
- + cmd |= SPI_CMD_RESUME;
- + writel(cmd, priv->base + SPI_CMD_REG);
- +}
- +
- +static bool mtk_spim_supports_op(struct spi_slave *slave,
- + const struct spi_mem_op *op)
- +{
- + struct udevice *bus = dev_get_parent(slave->dev);
- + struct mtk_spim_priv *priv = dev_get_priv(bus);
- +
- + if (op->cmd.buswidth == 0 || op->cmd.buswidth > 4 ||
- + op->addr.buswidth > 4 || op->dummy.buswidth > 4 ||
- + op->data.buswidth > 4)
- + return false;
- +
- + if (!priv->hw_cap.support_quad && (op->cmd.buswidth > 2 ||
- + op->addr.buswidth > 2 || op->dummy.buswidth > 2 ||
- + op->data.buswidth > 2))
- + return false;
- +
- + if (op->addr.nbytes && op->dummy.nbytes &&
- + op->addr.buswidth != op->dummy.buswidth)
- + return false;
- +
- + if (op->addr.nbytes + op->dummy.nbytes > 16)
- + return false;
- +
- + if (op->data.nbytes > MTK_SPI_IPM_PACKET_SIZE) {
- + if (op->data.nbytes / MTK_SPI_IPM_PACKET_SIZE >
- + MTK_SPI_IPM_PACKET_LOOP ||
- + op->data.nbytes % MTK_SPI_IPM_PACKET_SIZE != 0)
- + return false;
- + }
- +
- + return true;
- +}
- +
- +static void mtk_spim_setup_dma_xfer(struct mtk_spim_priv *priv,
- + const struct spi_mem_op *op)
- +{
- + writel((u32)(priv->tx_dma & MTK_SPI_32BITS_MASK),
- + priv->base + SPI_TX_SRC_REG);
- +
- + if (priv->hw_cap.dma_ext)
- + writel((u32)(priv->tx_dma >> 32),
- + priv->base + SPI_TX_SRC_REG_64);
- +
- + if (op->data.dir == SPI_MEM_DATA_IN) {
- + writel((u32)(priv->rx_dma & MTK_SPI_32BITS_MASK),
- + priv->base + SPI_RX_DST_REG);
- +
- + if (priv->hw_cap.dma_ext)
- + writel((u32)(priv->rx_dma >> 32),
- + priv->base + SPI_RX_DST_REG_64);
- + }
- +}
- +
- +static int mtk_spim_transfer_wait(struct spi_slave *slave,
- + const struct spi_mem_op *op)
- +{
- + struct udevice *bus = dev_get_parent(slave->dev);
- + struct mtk_spim_priv *priv = dev_get_priv(bus);
- + u32 sck_l, sck_h, spi_bus_clk, clk_count, reg;
- + ulong us = 1;
- + int ret = 0;
- +
- + if (op->data.dir == SPI_MEM_NO_DATA)
- + clk_count = 32;
- + else
- + clk_count = op->data.nbytes;
- +
- + spi_bus_clk = clk_get_rate(&priv->spi_clk);
- + sck_l = readl(priv->base + SPI_CFG2_REG) >> SPI_CFG2_SCK_LOW_OFFSET;
- + sck_h = readl(priv->base + SPI_CFG2_REG) & SPI_CFG2_SCK_HIGH_MASK;
- + do_div(spi_bus_clk, sck_l + sck_h + 2);
- +
- + us = CLK_TO_US(spi_bus_clk, clk_count * 8);
- + us += 1000 * 1000; /* 1s tolerance */
- +
- + if (us > UINT_MAX)
- + us = UINT_MAX;
- +
- + ret = readl_poll_timeout(priv->base + SPI_STATUS_REG, reg,
- + reg & 0x1, us);
- + if (ret < 0) {
- + dev_err(priv->dev, "transfer timeout, val: 0x%lx\n", us);
- + return -ETIMEDOUT;
- + }
- +
- + return 0;
- +}
- +
- +static int mtk_spim_exec_op(struct spi_slave *slave,
- + const struct spi_mem_op *op)
- +{
- + struct udevice *bus = dev_get_parent(slave->dev);
- + struct mtk_spim_priv *priv = dev_get_priv(bus);
- + u32 reg_val, nio = 1, tx_size;
- + char *tx_tmp_buf;
- + char *rx_tmp_buf;
- + int i, ret = 0;
- +
- + mtk_spim_reset(priv);
- + mtk_spim_hw_init(slave);
- + mtk_spim_prepare_transfer(priv, slave->max_hz);
- +
- + reg_val = readl(priv->base + SPI_CFG3_IPM_REG);
- + /* opcode byte len */
- + reg_val &= ~SPI_CFG3_IPM_CMD_BYTELEN_MASK;
- + reg_val |= 1 << SPI_CFG3_IPM_CMD_BYTELEN_OFFSET;
- +
- + /* addr & dummy byte len */
- + if (op->addr.nbytes || op->dummy.nbytes)
- + reg_val |= (op->addr.nbytes + op->dummy.nbytes) <<
- + SPI_CFG3_IPM_ADDR_BYTELEN_OFFSET;
- +
- + /* data byte len */
- + if (!op->data.nbytes) {
- + reg_val |= SPI_CFG3_IPM_NODATA_FLAG;
- + writel(0, priv->base + SPI_CFG1_REG);
- + } else {
- + reg_val &= ~SPI_CFG3_IPM_NODATA_FLAG;
- + priv->xfer_len = op->data.nbytes;
- + mtk_spim_setup_packet(priv);
- + }
- +
- + if (op->addr.nbytes || op->dummy.nbytes) {
- + if (op->addr.buswidth == 1 || op->dummy.buswidth == 1)
- + reg_val |= SPI_CFG3_IPM_XMODE_EN;
- + else
- + reg_val &= ~SPI_CFG3_IPM_XMODE_EN;
- + }
- +
- + if (op->addr.buswidth == 2 ||
- + op->dummy.buswidth == 2 ||
- + op->data.buswidth == 2)
- + nio = 2;
- + else if (op->addr.buswidth == 4 ||
- + op->dummy.buswidth == 4 ||
- + op->data.buswidth == 4)
- + nio = 4;
- +
- + reg_val &= ~SPI_CFG3_IPM_CMD_PIN_MODE_MASK;
- + reg_val |= PIN_MODE_CFG(nio) << SPI_CFG3_IPM_PIN_MODE_OFFSET;
- +
- + reg_val |= SPI_CFG3_IPM_HALF_DUPLEX_EN;
- + if (op->data.dir == SPI_MEM_DATA_IN)
- + reg_val |= SPI_CFG3_IPM_HALF_DUPLEX_DIR;
- + else
- + reg_val &= ~SPI_CFG3_IPM_HALF_DUPLEX_DIR;
- + writel(reg_val, priv->base + SPI_CFG3_IPM_REG);
- +
- + tx_size = 1 + op->addr.nbytes + op->dummy.nbytes;
- + if (op->data.dir == SPI_MEM_DATA_OUT)
- + tx_size += op->data.nbytes;
- +
- + tx_size = max(tx_size, (u32)32);
- +
- + /* Fill up tx data */
- + tx_tmp_buf = kzalloc(tx_size, GFP_KERNEL);
- + if (!tx_tmp_buf) {
- + ret = -ENOMEM;
- + goto exit;
- + }
- +
- + tx_tmp_buf[0] = op->cmd.opcode;
- +
- + if (op->addr.nbytes) {
- + for (i = 0; i < op->addr.nbytes; i++)
- + tx_tmp_buf[i + 1] = op->addr.val >>
- + (8 * (op->addr.nbytes - i - 1));
- + }
- +
- + if (op->dummy.nbytes)
- + memset(tx_tmp_buf + op->addr.nbytes + 1, 0xff,
- + op->dummy.nbytes);
- +
- + if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT)
- + memcpy(tx_tmp_buf + op->dummy.nbytes + op->addr.nbytes + 1,
- + op->data.buf.out, op->data.nbytes);
- + /* Finish filling up tx data */
- +
- + priv->tx_dma = dma_map_single(tx_tmp_buf, tx_size, DMA_TO_DEVICE);
- + if (dma_mapping_error(priv->dev, priv->tx_dma)) {
- + ret = -ENOMEM;
- + goto tx_free;
- + }
- +
- + if (op->data.dir == SPI_MEM_DATA_IN) {
- + if (!IS_ALIGNED((size_t)op->data.buf.in, 4)) {
- + rx_tmp_buf = kzalloc(op->data.nbytes, GFP_KERNEL);
- + if (!rx_tmp_buf) {
- + ret = -ENOMEM;
- + goto tx_unmap;
- + }
- + } else {
- + rx_tmp_buf = op->data.buf.in;
- + }
- +
- + priv->rx_dma = dma_map_single(rx_tmp_buf, op->data.nbytes,
- + DMA_FROM_DEVICE);
- + if (dma_mapping_error(priv->dev, priv->rx_dma)) {
- + ret = -ENOMEM;
- + goto rx_free;
- + }
- + }
- +
- + reg_val = readl(priv->base + SPI_CMD_REG);
- + reg_val |= SPI_CMD_TX_DMA;
- + if (op->data.dir == SPI_MEM_DATA_IN)
- + reg_val |= SPI_CMD_RX_DMA;
- +
- + writel(reg_val, priv->base + SPI_CMD_REG);
- +
- + mtk_spim_setup_dma_xfer(priv, op);
- +
- + mtk_spim_enable_transfer(priv);
- +
- + /* Wait for the interrupt. */
- + ret = mtk_spim_transfer_wait(slave, op);
- + if (ret)
- + goto rx_unmap;
- +
- + if (op->data.dir == SPI_MEM_DATA_IN &&
- + !IS_ALIGNED((size_t)op->data.buf.in, 4))
- + memcpy(op->data.buf.in, rx_tmp_buf, op->data.nbytes);
- +
- +rx_unmap:
- + /* spi disable dma */
- + reg_val = readl(priv->base + SPI_CMD_REG);
- + reg_val &= ~SPI_CMD_TX_DMA;
- + if (op->data.dir == SPI_MEM_DATA_IN)
- + reg_val &= ~SPI_CMD_RX_DMA;
- + writel(reg_val, priv->base + SPI_CMD_REG);
- +
- + writel(0, priv->base + SPI_TX_SRC_REG);
- + writel(0, priv->base + SPI_RX_DST_REG);
- +
- + if (op->data.dir == SPI_MEM_DATA_IN)
- + dma_unmap_single(priv->rx_dma,
- + op->data.nbytes, DMA_FROM_DEVICE);
- +rx_free:
- + if (op->data.dir == SPI_MEM_DATA_IN &&
- + !IS_ALIGNED((size_t)op->data.buf.in, 4))
- + kfree(rx_tmp_buf);
- +tx_unmap:
- + dma_unmap_single(priv->tx_dma,
- + tx_size, DMA_TO_DEVICE);
- +tx_free:
- + kfree(tx_tmp_buf);
- +exit:
- + return ret;
- +}
- +
- +static int mtk_spim_adjust_op_size(struct spi_slave *slave,
- + struct spi_mem_op *op)
- +{
- + int opcode_len;
- +
- + if (!op->data.nbytes)
- + return 0;
- +
- + if (op->data.dir != SPI_MEM_NO_DATA) {
- + opcode_len = 1 + op->addr.nbytes + op->dummy.nbytes;
- + if (opcode_len + op->data.nbytes > MTK_SPI_IPM_PACKET_SIZE) {
- + op->data.nbytes = MTK_SPI_IPM_PACKET_SIZE - opcode_len;
- + /* force data buffer dma-aligned. */
- + op->data.nbytes -= op->data.nbytes % 4;
- + }
- + }
- +
- + return 0;
- +}
- +
- +static int mtk_spim_get_attr(struct mtk_spim_priv *priv, struct udevice *dev)
- +{
- + int ret;
- +
- + priv->hw_cap.enhance_timing = dev_read_bool(dev, "enhance_timing");
- + priv->hw_cap.dma_ext = dev_read_bool(dev, "dma_ext");
- + priv->hw_cap.ipm_design = dev_read_bool(dev, "ipm_design");
- + priv->hw_cap.support_quad = dev_read_bool(dev, "support_quad");
- +
- + ret = dev_read_u32(dev, "tick_dly", &priv->tick_dly);
- + if (ret < 0)
- + dev_err(priv->dev, "tick dly not set.\n");
- +
- + ret = dev_read_u32(dev, "sample_sel", &priv->sample_sel);
- + if (ret < 0)
- + dev_err(priv->dev, "sample sel not set.\n");
- +
- + return ret;
- +}
- +
- +static int mtk_spim_probe(struct udevice *dev)
- +{
- + struct mtk_spim_priv *priv = dev_get_priv(dev);
- + int ret;
- +
- + priv->base = (void __iomem *)devfdt_get_addr(dev);
- + if (!priv->base)
- + return -EINVAL;
- +
- + mtk_spim_get_attr(priv, dev);
- +
- + ret = clk_get_by_name(dev, "sel-clk", &priv->sel_clk);
- + if (ret < 0) {
- + dev_err(dev, "failed to get sel-clk\n");
- + return ret;
- + }
- +
- + ret = clk_get_by_name(dev, "spi-clk", &priv->spi_clk);
- + if (ret < 0) {
- + dev_err(dev, "failed to get spi-clk\n");
- + return ret;
- + }
- +
- + clk_enable(&priv->sel_clk);
- + clk_enable(&priv->spi_clk);
- +
- + return 0;
- +}
- +
- +static int mtk_spim_set_speed(struct udevice *dev, uint speed)
- +{
- + return 0;
- +}
- +
- +static int mtk_spim_set_mode(struct udevice *dev, uint mode)
- +{
- + return 0;
- +}
- +
- +static const struct spi_controller_mem_ops mtk_spim_mem_ops = {
- + .adjust_op_size = mtk_spim_adjust_op_size,
- + .supports_op = mtk_spim_supports_op,
- + .exec_op = mtk_spim_exec_op
- +};
- +
- +static const struct dm_spi_ops mtk_spim_ops = {
- + .mem_ops = &mtk_spim_mem_ops,
- + .set_speed = mtk_spim_set_speed,
- + .set_mode = mtk_spim_set_mode,
- +};
- +
- +static const struct udevice_id mtk_spim_ids[] = {
- + { .compatible = "mediatek,ipm-spi" },
- + {}
- +};
- +
- +U_BOOT_DRIVER(mtk_spim) = {
- + .name = "mtk_spim",
- + .id = UCLASS_SPI,
- + .of_match = mtk_spim_ids,
- + .ops = &mtk_spim_ops,
- + .priv_auto = sizeof(struct mtk_spim_priv),
- + .probe = mtk_spim_probe,
- +};
|