]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
spi: add spi controller support for MediaTek MT7620 SoC
authorWeijie Gao <weijie.gao@mediatek.com>
Thu, 12 Nov 2020 08:36:42 +0000 (16:36 +0800)
committerDaniel Schwierzeck <daniel.schwierzeck@gmail.com>
Sun, 24 Jan 2021 20:39:26 +0000 (21:39 +0100)
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.

Reviewed-by: Stefan Roese <sr@denx.de>
Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/mt7620_spi.c [new file with mode: 0644]

index cd19b2d4b3d9210cd7eef7c93170ab8b5a74f962..1efb5b18f503e3b5268ed56b6a3682a0794711de 100644 (file)
@@ -210,6 +210,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
index dc9ea34c0a785067eb7a1bf98a933d5cd713be7c..d06949d2f59abe3713a48c96ac3b607c43ad26a4 100644 (file)
@@ -37,6 +37,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 (file)
index 0000000..6554e37
--- /dev/null
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ *
+ * Generic SPI driver for MediaTek MT7620 SoC
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.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)
+
+#define POLLING_SCALE          10
+#define POLLING_FRAC_USEC      100
+
+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 = POLLING_SCALE * tmo + POLLING_FRAC_USEC;
+
+       /* 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, bool 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)
+                       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)
+                       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) {
+               dev_err(dev, "mt7620_spi: Only half-duplex is supported\n");
+               return -EIO;
+       }
+
+       cs = spi_chip_select(dev);
+       if (cs < 0 || cs >= MT7620_SPI_NUM_CS) {
+               dev_err(dev, "mt7620_spi: Invalid chip select %d\n", cs);
+               return -EINVAL;
+       }
+
+       if (flags & SPI_XFER_BEGIN)
+               mt7620_spi_set_cs(ms, cs, true);
+
+       if (din)
+               ret = mt7620_spi_read(ms, cs, din, total_size);
+       else if (dout)
+               ret = mt7620_spi_write(ms, cs, dout, total_size);
+
+       if (ret)
+               dev_err(dev, "mt7620_spi: %s transaction timeout\n",
+                       din ? "read" : "write");
+
+       if (flags & SPI_XFER_END)
+               mt7620_spi_set_cs(ms, cs, false);
+
+       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) {
+               dev_err(dev, "mt7620_spi: Please provide a clock!\n");
+               return ret;
+       }
+
+       clk_enable(&clk);
+
+       ms->sys_freq = clk_get_rate(&clk);
+       if (!ms->sys_freq) {
+               dev_err(dev, "mt7620_spi: 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 = sizeof(struct mt7620_spi),
+       .probe = mt7620_spi_probe,
+};