|
|
|
@ -5,6 +5,8 @@ Subject: [PATCH 43/53] spi: add mt7621 support |
|
|
|
|
|
|
|
|
|
Signed-off-by: John Crispin <blogic@openwrt.org>
|
|
|
|
|
---
|
|
|
|
|
Note: This patch contains upstream mt7621-spi at 9c562d8411a54f6731cdc587c29968d9e8610c85
|
|
|
|
|
|
|
|
|
|
drivers/spi/Kconfig | 6 +
|
|
|
|
|
drivers/spi/Makefile | 1 +
|
|
|
|
|
drivers/spi/spi-mt7621.c | 480 ++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
@ -38,7 +40,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/drivers/spi/spi-mt7621.c
|
|
|
|
|
@@ -0,0 +1,494 @@
|
|
|
|
|
@@ -0,0 +1,515 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * spi-mt7621.c -- MediaTek MT7621 SPI controller driver
|
|
|
|
|
+ *
|
|
|
|
@ -96,7 +98,8 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+#define MT7621_CPOL BIT(4)
|
|
|
|
|
+#define MT7621_LSB_FIRST BIT(3)
|
|
|
|
|
+
|
|
|
|
|
+#define RT2880_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_CS_HIGH)
|
|
|
|
|
+#define RT2880_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | \
|
|
|
|
|
+ SPI_LSB_FIRST | SPI_CS_HIGH)
|
|
|
|
|
+
|
|
|
|
|
+struct mt7621_spi;
|
|
|
|
|
+
|
|
|
|
@ -106,6 +109,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ unsigned int sys_freq;
|
|
|
|
|
+ unsigned int speed;
|
|
|
|
|
+ struct clk *clk;
|
|
|
|
|
+ int pending_write;
|
|
|
|
|
+
|
|
|
|
|
+ struct mt7621_spi_ops *ops;
|
|
|
|
|
+};
|
|
|
|
@ -131,14 +135,13 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+
|
|
|
|
|
+ master |= 7 << 29;
|
|
|
|
|
+ master |= 1 << 2;
|
|
|
|
|
+#ifdef CONFIG_SOC_MT7620
|
|
|
|
|
+ if (duplex)
|
|
|
|
|
+ master |= 1 << 10;
|
|
|
|
|
+ else
|
|
|
|
|
+#endif
|
|
|
|
|
+ master &= ~(1 << 10);
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_MASTER, master);
|
|
|
|
|
+ rs->pending_write = 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void mt7621_spi_set_cs(struct spi_device *spi, int enable)
|
|
|
|
@ -147,7 +150,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ int cs = spi->chip_select;
|
|
|
|
|
+ u32 polar = 0;
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_reset(rs, cs);
|
|
|
|
|
+ mt7621_spi_reset(rs, cs);
|
|
|
|
|
+ if (enable)
|
|
|
|
|
+ polar = BIT(cs);
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_POLAR, polar);
|
|
|
|
@ -180,36 +183,34 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ reg |= MT7621_LSB_FIRST;
|
|
|
|
|
+
|
|
|
|
|
+ reg &= ~(MT7621_CPHA | MT7621_CPOL);
|
|
|
|
|
+ switch(spi->mode & (SPI_CPOL | SPI_CPHA)) {
|
|
|
|
|
+ case SPI_MODE_0:
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SPI_MODE_1:
|
|
|
|
|
+ reg |= MT7621_CPHA;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SPI_MODE_2:
|
|
|
|
|
+ reg |= MT7621_CPOL;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SPI_MODE_3:
|
|
|
|
|
+ reg |= MT7621_CPOL | MT7621_CPHA;
|
|
|
|
|
+ break;
|
|
|
|
|
+ switch (spi->mode & (SPI_CPOL | SPI_CPHA)) {
|
|
|
|
|
+ case SPI_MODE_0:
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SPI_MODE_1:
|
|
|
|
|
+ reg |= MT7621_CPHA;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SPI_MODE_2:
|
|
|
|
|
+ reg |= MT7621_CPOL;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SPI_MODE_3:
|
|
|
|
|
+ reg |= MT7621_CPOL | MT7621_CPHA;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_MASTER, reg);
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static inline int mt7621_spi_wait_till_ready(struct spi_device *spi)
|
|
|
|
|
+static inline int mt7621_spi_wait_till_ready(struct mt7621_spi *rs)
|
|
|
|
|
+{
|
|
|
|
|
+ struct mt7621_spi *rs = spidev_to_mt7621_spi(spi);
|
|
|
|
|
+ int i;
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < RALINK_SPI_WAIT_MAX_LOOP; i++) {
|
|
|
|
|
+ u32 status;
|
|
|
|
|
+
|
|
|
|
|
+ status = mt7621_spi_read(rs, MT7621_SPI_TRANS);
|
|
|
|
|
+ if ((status & SPITRANS_BUSY) == 0) {
|
|
|
|
|
+ if ((status & SPITRANS_BUSY) == 0)
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ cpu_relax();
|
|
|
|
|
+ udelay(1);
|
|
|
|
|
+ }
|
|
|
|
@ -217,92 +218,124 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ return -ETIMEDOUT;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int mt7621_spi_transfer_half_duplex(struct spi_master *master,
|
|
|
|
|
+ struct spi_message *m)
|
|
|
|
|
+static void mt7621_spi_read_half_duplex(struct mt7621_spi *rs,
|
|
|
|
|
+ int rx_len, u8 *buf)
|
|
|
|
|
+{
|
|
|
|
|
+ struct mt7621_spi *rs = spi_master_get_devdata(master);
|
|
|
|
|
+ struct spi_device *spi = m->spi;
|
|
|
|
|
+ unsigned int speed = spi->max_speed_hz;
|
|
|
|
|
+ struct spi_transfer *t = NULL;
|
|
|
|
|
+ int status = 0;
|
|
|
|
|
+ int i, len = 0;
|
|
|
|
|
+ int rx_len = 0;
|
|
|
|
|
+ u32 data[9] = { 0 };
|
|
|
|
|
+ u32 val;
|
|
|
|
|
+ /* Combine with any pending write, and perform one or
|
|
|
|
|
+ * more half-duplex transactions reading 'len' bytes.
|
|
|
|
|
+ * Data to be written is already in MT7621_SPI_DATA*
|
|
|
|
|
+ */
|
|
|
|
|
+ int tx_len = rs->pending_write;
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_wait_till_ready(spi);
|
|
|
|
|
+ rs->pending_write = 0;
|
|
|
|
|
+
|
|
|
|
|
+ list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
|
|
|
+ const u8 *buf = t->tx_buf;
|
|
|
|
|
+ while (rx_len || tx_len) {
|
|
|
|
|
+ int i;
|
|
|
|
|
+ u32 val = (min(tx_len, 4) * 8) << 24;
|
|
|
|
|
+ int rx = min(rx_len, 32);
|
|
|
|
|
+
|
|
|
|
|
+ if (t->rx_buf)
|
|
|
|
|
+ rx_len += t->len;
|
|
|
|
|
+ if (tx_len > 4)
|
|
|
|
|
+ val |= (tx_len - 4) * 8;
|
|
|
|
|
+ val |= (rx * 8) << 12;
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val);
|
|
|
|
|
+
|
|
|
|
|
+ if (!buf)
|
|
|
|
|
+ continue;
|
|
|
|
|
+ tx_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (t->speed_hz < speed)
|
|
|
|
|
+ speed = t->speed_hz;
|
|
|
|
|
+ val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
|
|
|
|
|
+ val |= SPI_CTL_START;
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * m25p80 might attempt to write more data than we can handle.
|
|
|
|
|
+ * truncate the message to what we can fit into the registers
|
|
|
|
|
+ */
|
|
|
|
|
+ if (len + t->len > 36)
|
|
|
|
|
+ t->len = 36 - len;
|
|
|
|
|
+ mt7621_spi_wait_till_ready(rs);
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < t->len; i++, len++)
|
|
|
|
|
+ data[len / 4] |= buf[i] << (8 * (len & 3));
|
|
|
|
|
+ for (i = 0; i < rx; i++) {
|
|
|
|
|
+ if ((i % 4) == 0)
|
|
|
|
|
+ val = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
|
|
|
|
|
+ *buf++ = val & 0xff;
|
|
|
|
|
+ val >>= 8;
|
|
|
|
|
+ }
|
|
|
|
|
+ rx_len -= i;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+ if (WARN_ON(rx_len > 32)) {
|
|
|
|
|
+ status = -EIO;
|
|
|
|
|
+ goto msg_done;
|
|
|
|
|
+ }
|
|
|
|
|
+static inline void mt7621_spi_flush(struct mt7621_spi *rs)
|
|
|
|
|
+{
|
|
|
|
|
+ mt7621_spi_read_half_duplex(rs, 0, NULL);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+ if (mt7621_spi_prepare(spi, speed)) {
|
|
|
|
|
+ status = -EIO;
|
|
|
|
|
+ goto msg_done;
|
|
|
|
|
+static void mt7621_spi_write_half_duplex(struct mt7621_spi *rs,
|
|
|
|
|
+ int tx_len, const u8 *buf)
|
|
|
|
|
+{
|
|
|
|
|
+ int val = 0;
|
|
|
|
|
+ int len = rs->pending_write;
|
|
|
|
|
+
|
|
|
|
|
+ if (len & 3) {
|
|
|
|
|
+ val = mt7621_spi_read(rs, MT7621_SPI_OPCODE + (len & ~3));
|
|
|
|
|
+ if (len < 4) {
|
|
|
|
|
+ val <<= (4 - len) * 8;
|
|
|
|
|
+ val = swab32(val);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ data[0] = swab32(data[0]);
|
|
|
|
|
+ if (len < 4)
|
|
|
|
|
+ data[0] >>= (4 - len) * 8;
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < len; i += 4)
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_OPCODE + i, data[i / 4]);
|
|
|
|
|
+
|
|
|
|
|
+ val = (min_t(int, len, 4) * 8) << 24;
|
|
|
|
|
+ if (len > 4)
|
|
|
|
|
+ val |= (len - 4) * 8;
|
|
|
|
|
+ val |= (rx_len * 8) << 12;
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val);
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_set_cs(spi, 1);
|
|
|
|
|
+ while (tx_len > 0) {
|
|
|
|
|
+ if (len >= 36) {
|
|
|
|
|
+ rs->pending_write = len;
|
|
|
|
|
+ mt7621_spi_flush(rs);
|
|
|
|
|
+ len = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
|
|
|
|
|
+ val |= SPI_CTL_START;
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
|
|
|
|
|
+ val |= *buf++ << (8 * (len & 3));
|
|
|
|
|
+ len++;
|
|
|
|
|
+ if ((len & 3) == 0) {
|
|
|
|
|
+ if (len == 4)
|
|
|
|
|
+ /* The byte-order of the opcode is weird! */
|
|
|
|
|
+ val = swab32(val);
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_OPCODE + len - 4, val);
|
|
|
|
|
+ val = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ tx_len -= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (len & 3) {
|
|
|
|
|
+ if (len < 4) {
|
|
|
|
|
+ val = swab32(val);
|
|
|
|
|
+ val >>= (4 - len) * 8;
|
|
|
|
|
+ }
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_OPCODE + (len & ~3), val);
|
|
|
|
|
+ }
|
|
|
|
|
+ rs->pending_write = len;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_wait_till_ready(spi);
|
|
|
|
|
+static int mt7621_spi_transfer_half_duplex(struct spi_master *master,
|
|
|
|
|
+ struct spi_message *m)
|
|
|
|
|
+{
|
|
|
|
|
+ struct mt7621_spi *rs = spi_master_get_devdata(master);
|
|
|
|
|
+ struct spi_device *spi = m->spi;
|
|
|
|
|
+ unsigned int speed = spi->max_speed_hz;
|
|
|
|
|
+ struct spi_transfer *t = NULL;
|
|
|
|
|
+ int status = 0;
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_set_cs(spi, 0);
|
|
|
|
|
+ mt7621_spi_wait_till_ready(rs);
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < rx_len; i += 4)
|
|
|
|
|
+ data[i / 4] = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
|
|
|
|
|
+ list_for_each_entry(t, &m->transfers, transfer_list)
|
|
|
|
|
+ if (t->speed_hz < speed)
|
|
|
|
|
+ speed = t->speed_hz;
|
|
|
|
|
+
|
|
|
|
|
+ m->actual_length = len + rx_len;
|
|
|
|
|
+ if (mt7621_spi_prepare(spi, speed)) {
|
|
|
|
|
+ status = -EIO;
|
|
|
|
|
+ goto msg_done;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ len = 0;
|
|
|
|
|
+ mt7621_spi_set_cs(spi, 1);
|
|
|
|
|
+ m->actual_length = 0;
|
|
|
|
|
+ list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
|
|
|
+ u8 *buf = t->rx_buf;
|
|
|
|
|
+
|
|
|
|
|
+ if (!buf)
|
|
|
|
|
+ continue;
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < t->len; i++, len++)
|
|
|
|
|
+ buf[i] = data[len / 4] >> (8 * (len & 3));
|
|
|
|
|
+ if (t->rx_buf)
|
|
|
|
|
+ mt7621_spi_read_half_duplex(rs, t->len, t->rx_buf);
|
|
|
|
|
+ else if (t->tx_buf)
|
|
|
|
|
+ mt7621_spi_write_half_duplex(rs, t->len, t->tx_buf);
|
|
|
|
|
+ m->actual_length += t->len;
|
|
|
|
|
+ }
|
|
|
|
|
+ mt7621_spi_flush(rs);
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_set_cs(spi, 0);
|
|
|
|
|
+msg_done:
|
|
|
|
|
+ m->status = status;
|
|
|
|
|
+ spi_finalize_current_message(master);
|
|
|
|
@ -310,7 +343,6 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#ifdef CONFIG_SOC_MT7620
|
|
|
|
|
+static int mt7621_spi_transfer_full_duplex(struct spi_master *master,
|
|
|
|
|
+ struct spi_message *m)
|
|
|
|
|
+{
|
|
|
|
@ -324,7 +356,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ u32 data[9] = { 0 };
|
|
|
|
|
+ u32 val = 0;
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_wait_till_ready(spi);
|
|
|
|
|
+ mt7621_spi_wait_till_ready(rs);
|
|
|
|
|
+
|
|
|
|
|
+ list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
|
|
|
+ const u8 *buf = t->tx_buf;
|
|
|
|
@ -369,7 +401,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ val |= SPI_CTL_START;
|
|
|
|
|
+ mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_wait_till_ready(spi);
|
|
|
|
|
+ mt7621_spi_wait_till_ready(rs);
|
|
|
|
|
+
|
|
|
|
|
+ mt7621_spi_set_cs(spi, 0);
|
|
|
|
|
+
|
|
|
|
@ -395,18 +427,15 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+static int mt7621_spi_transfer_one_message(struct spi_master *master,
|
|
|
|
|
+ struct spi_message *m)
|
|
|
|
|
+{
|
|
|
|
|
+ struct spi_device *spi = m->spi;
|
|
|
|
|
+#ifdef CONFIG_SOC_MT7620
|
|
|
|
|
+ int cs = spi->chip_select;
|
|
|
|
|
+
|
|
|
|
|
+ if (cs)
|
|
|
|
|
+ return mt7621_spi_transfer_full_duplex(master, m);
|
|
|
|
|
+#endif
|
|
|
|
|
+ return mt7621_spi_transfer_half_duplex(master, m);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
@ -433,11 +462,6 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+};
|
|
|
|
|
+MODULE_DEVICE_TABLE(of, mt7621_spi_match);
|
|
|
|
|
+
|
|
|
|
|
+static size_t mt7621_max_transfer_size(struct spi_device *spi)
|
|
|
|
|
+{
|
|
|
|
|
+ return 32;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int mt7621_spi_probe(struct platform_device *pdev)
|
|
|
|
|
+{
|
|
|
|
|
+ const struct of_device_id *match;
|
|
|
|
@ -483,7 +507,6 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ master->bits_per_word_mask = SPI_BPW_MASK(8);
|
|
|
|
|
+ master->dev.of_node = pdev->dev.of_node;
|
|
|
|
|
+ master->num_chipselect = 2;
|
|
|
|
|
+ master->max_transfer_size = mt7621_max_transfer_size;
|
|
|
|
|
+
|
|
|
|
|
+ dev_set_drvdata(&pdev->dev, master);
|
|
|
|
|
+
|
|
|
|
@ -493,6 +516,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+ rs->master = master;
|
|
|
|
|
+ rs->sys_freq = clk_get_rate(rs->clk);
|
|
|
|
|
+ rs->ops = ops;
|
|
|
|
|
+ rs->pending_write = 0;
|
|
|
|
|
+ dev_info(&pdev->dev, "sys_freq: %u\n", rs->sys_freq);
|
|
|
|
|
+
|
|
|
|
|
+ device_reset(&pdev->dev);
|
|
|
|
@ -521,7 +545,6 @@ Signed-off-by: John Crispin <blogic@openwrt.org> |
|
|
|
|
+static struct platform_driver mt7621_spi_driver = {
|
|
|
|
|
+ .driver = {
|
|
|
|
|
+ .name = DRIVER_NAME,
|
|
|
|
|
+ .owner = THIS_MODULE,
|
|
|
|
|
+ .of_match_table = mt7621_spi_match,
|
|
|
|
|
+ },
|
|
|
|
|
+ .probe = mt7621_spi_probe,
|
|
|
|
|