[PATCH 14/18] spi: add spi controller support for MediaTek MT7620 SoC

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[PATCH 14/18] spi: add spi controller support for MediaTek MT7620 SoC

Weijie Gao-2
This patch adds spi controller support for MediaTek MT7620 SoC.

The SPI controller supports two chip selects. These two chip selects are
implemented as two separate register groups, but they share the same bus
(DI/DO/CLK), only CS pins are dedicated for each register group.
Appearently these two register groups cannot operates simulataneously so
they are implemented as one controller.

Signed-off-by: Weijie Gao <[hidden email]>
---
 drivers/spi/Kconfig      |   7 +
 drivers/spi/Makefile     |   1 +
 drivers/spi/mt7620_spi.c | 277 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 285 insertions(+)
 create mode 100644 drivers/spi/mt7620_spi.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 5df97c80fa..66af68db7b 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -202,6 +202,13 @@ config MSCC_BB_SPI
   Enable MSCC bitbang SPI driver. This driver can be used on
   MSCC SOCs.
 
+config MT7620_SPI
+ bool "MediaTek MT7620 SPI driver"
+ depends on SOC_MT7620
+ help
+  Enable the MT7620 SPI driver. This driver can be used to access
+  generic SPI devices on MediaTek MT7620 SoC.
+
 config MT7621_SPI
  bool "MediaTek MT7621 SPI driver"
  depends on SOC_MT7628
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index b5c9ff1af8..bb078e9ff2 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -36,6 +36,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_SNFI_SPI) += mtk_snfi_spi.o
+obj-$(CONFIG_MT7620_SPI) += mt7620_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
diff --git a/drivers/spi/mt7620_spi.c b/drivers/spi/mt7620_spi.c
new file mode 100644
index 0000000000..c0b3d1d8eb
--- /dev/null
+++ b/drivers/spi/mt7620_spi.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ *
+ * Author: Weijie Gao <[hidden email]>
+ *
+ * Generic SPI driver for MediaTek MT7620 SoC
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <spi.h>
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+
+#define MT7620_SPI_NUM_CS 2
+#define MT7620_SPI_MASTER1_OFF 0x00
+#define MT7620_SPI_MASTER2_OFF 0x40
+
+/* SPI_STAT */
+#define   SPI_BUSY BIT(0)
+
+/* SPI_CFG */
+#define   MSB_FIRST BIT(8)
+#define   SPI_CLK_POL BIT(6)
+#define   RX_CLK_EDGE BIT(5)
+#define   TX_CLK_EDGE BIT(4)
+#define   SPI_CLK_S 0
+#define   SPI_CLK_M GENMASK(2, 0)
+
+/* SPI_CTL */
+#define   START_WR BIT(2)
+#define   START_RD BIT(1)
+#define   SPI_HIGH BIT(0)
+
+#define SPI_ARB 0xf0
+#define   ARB_EN BIT(31)
+
+struct mt7620_spi_master_regs {
+ u32 stat;
+ u32 reserved0[3];
+ u32 cfg;
+ u32 ctl;
+ u32 reserved1[2];
+ u32 data;
+};
+
+struct mt7620_spi {
+ void __iomem *regs;
+ struct mt7620_spi_master_regs *m[MT7620_SPI_NUM_CS];
+ unsigned int sys_freq;
+ u32 wait_us;
+ uint mode;
+ uint speed;
+};
+
+static void mt7620_spi_master_setup(struct mt7620_spi *ms, int cs)
+{
+ u32 rate, prescale, freq, tmo, cfg;
+
+ /* Calculate the clock divsior */
+ rate = DIV_ROUND_UP(ms->sys_freq, ms->speed);
+ rate = roundup_pow_of_two(rate);
+
+ prescale = ilog2(rate / 2);
+ if (prescale > 6)
+ prescale = 6;
+
+ /* Calculate the real clock, and usecs for one byte transaction */
+ freq = ms->sys_freq >> (prescale + 1);
+ tmo = DIV_ROUND_UP(8 * 1000000, freq);
+
+ /* 10 times tolerance plus 100us */
+ ms->wait_us = 10 * tmo + 100;
+
+ /* set SPI_CFG */
+ cfg = prescale << SPI_CLK_S;
+
+ switch (ms->mode & (SPI_CPOL | SPI_CPHA)) {
+ case SPI_MODE_0:
+ cfg |= TX_CLK_EDGE;
+ break;
+ case SPI_MODE_1:
+ cfg |= RX_CLK_EDGE;
+ break;
+ case SPI_MODE_2:
+ cfg |= SPI_CLK_POL | RX_CLK_EDGE;
+ break;
+ case SPI_MODE_3:
+ cfg |= SPI_CLK_POL | TX_CLK_EDGE;
+ break;
+ }
+
+ if (!(ms->mode & SPI_LSB_FIRST))
+ cfg |= MSB_FIRST;
+
+ writel(cfg, &ms->m[cs]->cfg);
+
+ writel(SPI_HIGH, &ms->m[cs]->ctl);
+}
+
+static void mt7620_spi_set_cs(struct mt7620_spi *ms, int cs, int enable)
+{
+ if (enable)
+ mt7620_spi_master_setup(ms, cs);
+
+ if (ms->mode & SPI_CS_HIGH)
+ enable = !enable;
+
+ if (enable)
+ clrbits_32(&ms->m[cs]->ctl, SPI_HIGH);
+ else
+ setbits_32(&ms->m[cs]->ctl, SPI_HIGH);
+}
+
+static int mt7620_spi_set_mode(struct udevice *bus, uint mode)
+{
+ struct mt7620_spi *ms = dev_get_priv(bus);
+
+ ms->mode = mode;
+
+ /* Mode 0 is buggy. Force to use mode 3 */
+ if ((mode & SPI_MODE_3) == SPI_MODE_0)
+ ms->mode |= SPI_MODE_3;
+
+ return 0;
+}
+
+static int mt7620_spi_set_speed(struct udevice *bus, uint speed)
+{
+ struct mt7620_spi *ms = dev_get_priv(bus);
+
+ ms->speed = speed;
+
+ return 0;
+}
+
+static inline int mt7620_spi_busy_poll(struct mt7620_spi *ms, int cs)
+{
+ u32 val;
+
+ return readl_poll_timeout(&ms->m[cs]->stat, val, !(val & SPI_BUSY),
+  ms->wait_us);
+}
+
+static int mt7620_spi_read(struct mt7620_spi *ms, int cs, u8 *buf, size_t len)
+{
+ int ret;
+
+ while (len) {
+ setbits_32(&ms->m[cs]->ctl, START_RD);
+
+ ret = mt7620_spi_busy_poll(ms, cs);
+ if (ret) {
+ pr_err("SPI read transaction timeout\n");
+ return ret;
+ }
+
+ *buf++ = (u8)readl(&ms->m[cs]->data);
+
+ len--;
+ }
+
+ return 0;
+}
+
+static int mt7620_spi_write(struct mt7620_spi *ms, int cs, const u8 *buf,
+    size_t len)
+{
+ int ret;
+
+ while (len) {
+ writel(*buf++, &ms->m[cs]->data);
+ setbits_32(&ms->m[cs]->ctl, START_WR);
+
+ ret = mt7620_spi_busy_poll(ms, cs);
+ if (ret) {
+ pr_err("SPI write transaction timeout\n");
+ return ret;
+ }
+
+ len--;
+ }
+
+ return 0;
+}
+
+static int mt7620_spi_xfer(struct udevice *dev, unsigned int bitlen,
+   const void *dout, void *din, unsigned long flags)
+{
+ struct udevice *bus = dev->parent;
+ struct mt7620_spi *ms = dev_get_priv(bus);
+ int total_size = bitlen >> 3;
+ int cs, ret = 0;
+
+ /*
+ * This driver only supports half-duplex, so complain and bail out
+ * upon full-duplex messages
+ */
+ if (dout && din) {
+ pr_err("Only half-duplex SPI transfer supported\n");
+ return -EIO;
+ }
+
+ cs = spi_chip_select(dev);
+ if (cs < 0 || cs >= MT7620_SPI_NUM_CS) {
+ pr_err("Invalid chip select %d\n", cs);
+ return -EINVAL;
+ }
+
+ if (flags & SPI_XFER_BEGIN)
+ mt7620_spi_set_cs(ms, cs, 1);
+
+ if (din)
+ ret = mt7620_spi_read(ms, cs, din, total_size);
+ else if (dout)
+ ret = mt7620_spi_write(ms, cs, dout, total_size);
+
+ if (flags & SPI_XFER_END)
+ mt7620_spi_set_cs(ms, cs, 0);
+
+ return ret;
+}
+
+static int mt7620_spi_probe(struct udevice *dev)
+{
+ struct mt7620_spi *ms = dev_get_priv(dev);
+ struct clk clk;
+ int ret;
+
+ ms->regs = dev_remap_addr(dev);
+ if (!ms->regs)
+ return -EINVAL;
+
+ ms->m[0] = ms->regs + MT7620_SPI_MASTER1_OFF;
+ ms->m[1] = ms->regs + MT7620_SPI_MASTER2_OFF;
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret < 0) {
+ printf("Please provide a clock!\n");
+ return ret;
+ }
+
+ clk_enable(&clk);
+
+ ms->sys_freq = clk_get_rate(&clk);
+ if (!ms->sys_freq) {
+ printf("Please provide a valid bus clock!\n");
+ return -EINVAL;
+ }
+
+ writel(ARB_EN, ms->regs + SPI_ARB);
+
+ return 0;
+}
+
+static const struct dm_spi_ops mt7620_spi_ops = {
+ .set_mode = mt7620_spi_set_mode,
+ .set_speed = mt7620_spi_set_speed,
+ .xfer = mt7620_spi_xfer,
+};
+
+static const struct udevice_id mt7620_spi_ids[] = {
+ { .compatible = "mediatek,mt7620-spi" },
+ { }
+};
+
+U_BOOT_DRIVER(mt7620_spi) = {
+ .name = "mt7620_spi",
+ .id = UCLASS_SPI,
+ .of_match = mt7620_spi_ids,
+ .ops = &mt7620_spi_ops,
+ .priv_auto_alloc_size = sizeof(struct mt7620_spi),
+ .probe = mt7620_spi_probe,
+};
--
2.17.1