@@ -968,6 +968,12 @@ config SPI_AMD
help
Enables SPI controller driver for AMD SoC.
+config SPI_LOONGSON
+ tristate "Loongson SPI Controller Support"
+ depends on CPU_LOONGSON32 || CPU_LOONGSON64
+ help
+ This is the driver for Loongson spi master controller.
+
#
# Add new SPI master controllers in alphabetical order above this line
#
@@ -131,6 +131,7 @@ obj-$(CONFIG_SPI_XTENSA_XTFPGA) += spi-xtensa-xtfpga.o
obj-$(CONFIG_SPI_ZYNQ_QSPI) += spi-zynq-qspi.o
obj-$(CONFIG_SPI_ZYNQMP_GQSPI) += spi-zynqmp-gqspi.o
obj-$(CONFIG_SPI_AMD) += spi-amd.o
+obj-$(CONFIG_SPI_LOONGSON) += spi-loongson.o
# SPI slave protocol handlers
obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o
new file mode 100644
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Loongson3A+7A SPI driver
+ *
+ * Copyright (C) 2017 Juxin Gao <gaojuxin@loongson.cn>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/spi/spi.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+/*define spi register */
+#define SPCR 0x00
+#define SPSR 0x01
+#define FIFO 0x02
+#define SPER 0x03
+#define PARA 0x04
+#define SFCS 0x05
+#define TIMI 0x06
+
+struct loongson_spi {
+ struct work_struct work;
+ spinlock_t lock;
+ struct workqueue_struct *wq;
+ struct list_head msg_queue;
+ struct spi_master *master;
+ void __iomem *base;
+ int cs_active;
+ unsigned int hz;
+ unsigned char spcr, sper;
+ unsigned int mode;
+};
+
+static inline int set_cs(struct loongson_spi *loongson_spi, struct spi_device *spi, int val);
+
+static void loongson_spi_write_reg(struct loongson_spi *spi,
+ unsigned char reg, unsigned char data)
+{
+ writeb(data, spi->base + reg);
+}
+
+static char loongson_spi_read_reg(struct loongson_spi *spi,
+ unsigned char reg)
+{
+ return readb(spi->base + reg);
+}
+
+static int loongson_spi_update_state(struct loongson_spi *loongson_spi, struct spi_device *spi,
+ struct spi_transfer *t)
+{
+ unsigned int hz;
+ unsigned int div, div_tmp;
+ unsigned int bit;
+ unsigned long clk;
+ unsigned char val;
+ const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11};
+
+ hz = t ? t->speed_hz : spi->max_speed_hz;
+
+ if (!hz)
+ hz = spi->max_speed_hz;
+
+ if ((hz && loongson_spi->hz != hz) || ((spi->mode ^ loongson_spi->mode) & (SPI_CPOL | SPI_CPHA))) {
+ clk = 100000000;
+ div = DIV_ROUND_UP(clk, hz);
+
+ if (div < 2)
+ div = 2;
+
+ if (div > 4096)
+ div = 4096;
+
+ bit = fls(div) - 1;
+ if ((1<<bit) == div)
+ bit--;
+ div_tmp = rdiv[bit];
+
+ dev_dbg(&spi->dev, "clk = %ld hz = %d div_tmp = %d bit = %d\n",
+ clk, hz, div_tmp, bit);
+
+ loongson_spi->hz = hz;
+ loongson_spi->spcr = div_tmp & 3;
+ loongson_spi->sper = (div_tmp >> 2) & 3;
+
+ val = loongson_spi_read_reg(loongson_spi, SPCR);
+ val &= ~0xc;
+ if (spi->mode & SPI_CPOL)
+ val |= 8;
+ if (spi->mode & SPI_CPHA)
+ val |= 4;
+ loongson_spi_write_reg(loongson_spi, SPCR, (val & ~3) | loongson_spi->spcr);
+ val = loongson_spi_read_reg(loongson_spi, SPER);
+ loongson_spi_write_reg(loongson_spi, SPER, (val & ~3) | loongson_spi->sper);
+ loongson_spi->mode = spi->mode;
+ }
+
+ return 0;
+}
+
+
+
+static int loongson_spi_setup(struct spi_device *spi)
+{
+ struct loongson_spi *loongson_spi;
+
+ loongson_spi = spi_master_get_devdata(spi->master);
+ if (spi->bits_per_word % 8)
+ return -EINVAL;
+
+ if (spi->chip_select >= spi->master->num_chipselect)
+ return -EINVAL;
+
+ loongson_spi_update_state(loongson_spi, spi, NULL);
+
+ set_cs(loongson_spi, spi, 1);
+
+ return 0;
+}
+
+static int loongson_spi_write_read_8bit(struct spi_device *spi,
+ const u8 **tx_buf, u8 **rx_buf, unsigned int num)
+{
+ struct loongson_spi *loongson_spi;
+ loongson_spi = spi_master_get_devdata(spi->master);
+
+ if (tx_buf && *tx_buf) {
+ loongson_spi_write_reg(loongson_spi, FIFO, *((*tx_buf)++));
+ while ((loongson_spi_read_reg(loongson_spi, SPSR) & 0x1) == 1);
+ } else {
+ loongson_spi_write_reg(loongson_spi, FIFO, 0);
+ while ((loongson_spi_read_reg(loongson_spi, SPSR) & 0x1) == 1);
+ }
+
+ if (rx_buf && *rx_buf)
+ *(*rx_buf)++ = loongson_spi_read_reg(loongson_spi, FIFO);
+ else
+ loongson_spi_read_reg(loongson_spi, FIFO);
+
+ return 1;
+}
+
+
+static unsigned int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer)
+{
+ struct loongson_spi *loongson_spi;
+ unsigned int count;
+ const u8 *tx = xfer->tx_buf;
+ u8 *rx = xfer->rx_buf;
+
+ loongson_spi = spi_master_get_devdata(spi->master);
+ count = xfer->len;
+
+ do {
+ if (loongson_spi_write_read_8bit(spi, &tx, &rx, count) < 0)
+ goto out;
+ count--;
+ } while (count);
+
+out:
+ return xfer->len - count;
+
+}
+
+static inline int set_cs(struct loongson_spi *loongson_spi, struct spi_device *spi, int val)
+{
+ int cs = loongson_spi_read_reg(loongson_spi, SFCS) & ~(0x11 << spi->chip_select);
+
+ if (spi->mode & SPI_CS_HIGH)
+ val = !val;
+ loongson_spi_write_reg(loongson_spi, SFCS,
+ (val ? (0x11 << spi->chip_select):(0x1 << spi->chip_select)) | cs);
+ return 0;
+}
+
+static void loongson_spi_work(struct work_struct *work)
+{
+ struct loongson_spi *loongson_spi =
+ container_of(work, struct loongson_spi, work);
+ int param;
+
+ spin_lock(&loongson_spi->lock);
+ param = loongson_spi_read_reg(loongson_spi, PARA);
+ loongson_spi_write_reg(loongson_spi, PARA, param&~1);
+ while (!list_empty(&loongson_spi->msg_queue)) {
+
+ struct spi_message *m;
+ struct spi_device *spi;
+ struct spi_transfer *t = NULL;
+
+ m = container_of(loongson_spi->msg_queue.next, struct spi_message, queue);
+
+ list_del_init(&m->queue);
+ spin_unlock(&loongson_spi->lock);
+
+ spi = m->spi;
+
+ set_cs(loongson_spi, spi, 0);
+
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+
+ loongson_spi_update_state(loongson_spi, spi, t);
+
+ if (t->len)
+ m->actual_length +=
+ loongson_spi_write_read(spi, t);
+ }
+
+ set_cs(loongson_spi, spi, 1);
+ m->complete(m->context);
+
+ spin_lock(&loongson_spi->lock);
+ }
+
+ loongson_spi_write_reg(loongson_spi, PARA, param);
+ spin_unlock(&loongson_spi->lock);
+}
+
+static int loongson_spi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+ struct loongson_spi *loongson_spi;
+ struct spi_transfer *t = NULL;
+
+ m->actual_length = 0;
+ m->status = 0;
+
+ if (list_empty(&m->transfers) || !m->complete)
+ return -EINVAL;
+
+ loongson_spi = spi_master_get_devdata(spi->master);
+
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+
+ if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {
+ dev_err(&spi->dev,
+ "message rejected : "
+ "invalid transfer data buffers\n");
+ goto msg_rejected;
+ }
+ }
+
+ spin_lock(&loongson_spi->lock);
+ list_add_tail(&m->queue, &loongson_spi->msg_queue);
+ queue_work(loongson_spi->wq, &loongson_spi->work);
+ spin_unlock(&loongson_spi->lock);
+
+ return 0;
+msg_rejected:
+
+ m->status = -EINVAL;
+ if (m->complete)
+ m->complete(m->context);
+ return -EINVAL;
+}
+
+static int loongson_spi_probe(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct loongson_spi *spi;
+ int ret;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct loongson_spi));
+
+ if (master == NULL) {
+ dev_dbg(&pdev->dev, "master allocation failed\n");
+ return -ENOMEM;
+ }
+
+ if (pdev->id != -1)
+ master->bus_num = pdev->id;
+
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+ master->setup = loongson_spi_setup;
+
+ master->transfer = loongson_spi_transfer;
+
+ master->num_chipselect = 4;
+
+ master->dev.of_node = of_node_get(pdev->dev.of_node);
+
+ dev_set_drvdata(&pdev->dev, master);
+
+ spi = spi_master_get_devdata(master);
+
+ spi->wq = create_singlethread_workqueue(pdev->name);
+
+ spi->master = master;
+
+ spi->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(spi->base)) {
+ dev_err(&pdev->dev, "Cannot map IO\n");
+ goto free_master;
+ }
+ loongson_spi_write_reg(spi, SPCR, 0x51);
+ loongson_spi_write_reg(spi, SPER, 0x00);
+ loongson_spi_write_reg(spi, TIMI, 0x01);
+ loongson_spi_write_reg(spi, PARA, 0x40);
+ spi->mode = 0;
+
+ INIT_WORK(&spi->work, loongson_spi_work);
+
+ spin_lock_init(&spi->lock);
+ INIT_LIST_HEAD(&spi->msg_queue);
+
+ ret = devm_spi_register_master(&pdev->dev, master);
+ if (ret)
+ goto free_master;
+
+ return ret;
+
+free_master:
+ spi_master_put(master);
+ return ret;
+
+}
+
+static const struct of_device_id loongson_spi_id_table[] = {
+ { .compatible = "loongson,loongson-spi", },
+ { },
+};
+
+static struct platform_driver loongson_spi_driver = {
+ .probe = loongson_spi_probe,
+ .driver = {
+ .name = "loongson-spi",
+ .owner = THIS_MODULE,
+ .bus = &platform_bus_type,
+ .of_match_table = of_match_ptr(loongson_spi_id_table),
+
+ },
+};
+
+static struct resource loongson_spi_resources[] = {
+ [0] = {
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct platform_device loongson_spi_device = {
+ .name = "loongson-spi",
+ .id = 0,
+ .num_resources = ARRAY_SIZE(loongson_spi_resources),
+ .resource = loongson_spi_resources,
+};
+
+
+static int loongson_spi_pci_register(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ int ret;
+ unsigned char v8;
+
+ /* Enable device in PCI config */
+ ret = pci_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "%s pci_enable_device failed\n", __func__);
+ goto err_out;
+ }
+ /* request the mem regions */
+ ret = pci_request_region(pdev, 0, "loongson-spi io");
+ if (ret) {
+ dev_err(&pdev->dev, "%s request_region failed\n", __func__);
+ goto err_out;
+ }
+ loongson_spi_resources[0].start = pci_resource_start(pdev, 0);
+ loongson_spi_resources[0].end = pci_resource_end(pdev, 0);
+ /* need api from pci irq */
+ ret = pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &v8);
+
+ if (ret == PCIBIOS_SUCCESSFUL) {
+ loongson_spi_resources[1].start = v8;
+ loongson_spi_resources[1].end = v8;
+ platform_device_register(&loongson_spi_device);
+ }
+
+err_out:
+ return ret;
+}
+
+static void loongson_spi_pci_unregister(struct pci_dev *pdev)
+{
+ pci_release_region(pdev, 0);
+}
+
+static struct pci_device_id loongson_spi_devices[] = {
+ {PCI_DEVICE(0x0014, 0x7a0b)},
+ {0, 0, 0, 0, 0, 0, 0}
+};
+
+static struct pci_driver loongson_spi_pci_driver = {
+ .name = "loongson-spi-pci",
+ .id_table = loongson_spi_devices,
+ .probe = loongson_spi_pci_register,
+ .remove = loongson_spi_pci_unregister,
+};
+
+static int __init loongson_spi_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&loongson_spi_driver);
+ if (!ret)
+ ret = pci_register_driver(&loongson_spi_pci_driver);
+ return ret;
+}
+
+static void __exit loongson_spi_exit(void)
+{
+ platform_driver_unregister(&loongson_spi_driver);
+ pci_unregister_driver(&loongson_spi_pci_driver);
+}
+
+subsys_initcall(loongson_spi_init);
+module_exit(loongson_spi_exit);
+
+MODULE_AUTHOR("Juxin Gao <gaojuxin@loongson.cn>");
+MODULE_AUTHOR("Qing Zhang <zhangqing@loongson.cn>");
+MODULE_DESCRIPTION("Loongson3A+7A SPI driver");
+MODULE_LICENSE("GPL v2");
This module is integrated into the Loongson-3A SoC and the LS7A bridge chip. The SPI controller has the following characteristics: -Full-duplex synchronous serial data transmission -Support up to 4 variable length byte transmission -Main mode support -Mode failure generates an error flag and issues an interrupt request -Double buffer receiver -Serial clock with programmable polarity and phase -SPI can be controlled in wait mode -Support boot from SPI Loongson bridge chip and SOC are connected to the nor-flash slave device, the model is en25q32b, and the device driver under the mtd subsystem is used for testing. Signed-off-by: Qing Zhang <zhangqing@loongson.cn> --- drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-loongson.c | 428 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 435 insertions(+) create mode 100644 drivers/spi/spi-loongson.c