Skip to content

Commit

Permalink
spi: loongson: add bus driver for the loongson spi controller
Browse files Browse the repository at this point in the history
This bus driver supports the Loongson spi hardware controller in the
Loongson platforms and supports to use DTS and PCI framework to
register spi device resources.

Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn>
  • Loading branch information
YinboZhu authored and intel-lab-lkp committed Mar 24, 2023
1 parent 0cb7328 commit 3742622
Show file tree
Hide file tree
Showing 7 changed files with 536 additions and 0 deletions.
4 changes: 4 additions & 0 deletions MAINTAINERS
Expand Up @@ -12128,6 +12128,10 @@ M: Yinbo Zhu <zhuyinbo@loongson.cn>
L: linux-spi@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/spi/loongson,ls-spi.yaml
F: drivers/spi/spi-loongson-core.c
F: drivers/spi/spi-loongson-pci.c
F: drivers/spi/spi-loongson-plat.c
F: drivers/spi/spi-loongson.h

LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
M: Sathya Prakash <sathya.prakash@broadcom.com>
Expand Down
31 changes: 31 additions & 0 deletions drivers/spi/Kconfig
Expand Up @@ -509,6 +509,37 @@ config SPI_LM70_LLP
which interfaces to an LM70 temperature sensor using
a parallel port.

config SPI_LOONGSON_CORE
tristate "Loongson SPI Controller Core Driver Support"
depends on LOONGARCH || COMPILE_TEST
help
This core driver supports the Loongson spi hardware controller in
the Loongson platforms.
Say Y or M here if you want to use the SPI controller on
Loongson platform.

config SPI_LOONGSON_PCI
tristate "Loongson SPI Controller PCI Driver Support"
select SPI_LOONGSON_CORE
depends on PCI && (LOONGARCH || COMPILE_TEST)
help
This bus driver supports the Loongson spi hardware controller in
the Loongson platforms and supports to use PCI framework to
register spi device resources.
Say Y or M here if you want to use the SPI controller on
Loongson platform.

config SPI_LOONGSON_PLATFORM
tristate "Loongson SPI Controller Platform Driver Support"
select SPI_LOONGSON_CORE
depends on OF && (LOONGARCH || COMPILE_TEST)
help
This bus driver supports the Loongson spi hardware controller in
the Loongson platforms and supports to use DTS framework to
register spi device resources.
Say Y or M here if you want to use the SPI controller on
Loongson platform.

config SPI_LP8841_RTC
tristate "ICP DAS LP-8841 SPI Controller for RTC"
depends on MACH_PXA27X_DT || COMPILE_TEST
Expand Down
3 changes: 3 additions & 0 deletions drivers/spi/Makefile
Expand Up @@ -70,6 +70,9 @@ obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
obj-$(CONFIG_SPI_LANTIQ_SSC) += spi-lantiq-ssc.o
obj-$(CONFIG_SPI_JCORE) += spi-jcore.o
obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o
obj-$(CONFIG_SPI_LOONGSON_CORE) += spi-loongson-core.o
obj-$(CONFIG_SPI_LOONGSON_PCI) += spi-loongson-pci.o
obj-$(CONFIG_SPI_LOONGSON_PLATFORM) += spi-loongson-plat.o
obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o
obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o
obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o
Expand Down
302 changes: 302 additions & 0 deletions drivers/spi/spi-loongson-core.c
@@ -0,0 +1,302 @@
// SPDX-License-Identifier: GPL-2.0+
// Loongson SPI Support
// Copyright (C) 2023 Loongson Technology Corporation Limited

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/spi/spi.h>
#include <linux/clk.h>

#include "spi-loongson.h"

static inline void loongson_spi_write_reg(struct loongson_spi *spi, unsigned char reg,
unsigned char data)
{
writeb(data, spi->base + reg);
}

static inline char loongson_spi_read_reg(struct loongson_spi *spi, unsigned char reg)
{
return readb(spi->base + reg);
}

static void loongson_spi_set_cs(struct spi_device *spi, bool val)
{
int cs;
struct loongson_spi *loongson_spi = spi_master_get_devdata(spi->master);

if (loongson_spi->mode & SPI_NO_CS)
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, 0);
else {
cs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG)
& ~(0x11 << spi->chip_select);
loongson_spi_write_reg(loongson_spi,
LOONGSON_SPI_SFCS_REG,
(val ? (0x11 << spi->chip_select) :
(0x1 << spi->chip_select)) | cs);
}
}

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 char val;
const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11};

if (t)
hz = t->speed_hz;

if ((hz && loongson_spi->hz != hz) ||
((spi->mode ^ loongson_spi->mode) & (SPI_CPOL | SPI_CPHA))) {
div = DIV_ROUND_UP_ULL(loongson_spi->clk_rate, 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_rate = %llu hz = %d div_tmp = %d bit = %d\n",
loongson_spi->clk_rate, 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, LOONGSON_SPI_SPCR_REG);
val &= ~0xc;
if (spi->mode & SPI_CPOL)
val |= 8;
if (spi->mode & SPI_CPHA)
val |= 4;

loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, (val & ~3) |
loongson_spi->spcr);
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, (val & ~3) |
loongson_spi->sper);
loongson_spi->mode &= SPI_NO_CS;
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->hz = 0;
loongson_spi->mode &= SPI_NO_CS;
loongson_spi_set_cs(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 = spi_master_get_devdata(spi->master);
unsigned long timeout = jiffies + SPI_COMPLETION_TIMEOUT;

if (tx_buf && *tx_buf) {
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, *((*tx_buf)++));
while ((loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG) & 0x1) == 1 &&
time_after(timeout, jiffies))
cpu_relax();
} else {
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, 0);
while ((loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG) & 0x1) == 1 &&
time_after(timeout, jiffies))
cpu_relax();
}

if (rx_buf && *rx_buf)
*(*rx_buf)++ = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG);
else
loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG);

return 0;
}

static unsigned int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer)
{
unsigned int count;
const u8 *tx = xfer->tx_buf;
u8 *rx = xfer->rx_buf;

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 int loongson_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr);

loongson_spi->para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para & ~1);

return 0;
}

static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_device *spi,
struct spi_transfer *xfer)
{
struct loongson_spi *loongson_spi = spi_master_get_devdata(spi->master);

loongson_spi_update_state(loongson_spi, spi, xfer);
if (xfer->len)
xfer->len = loongson_spi_write_read(spi, xfer);

return 0;
}

static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctrl);

loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para);

return 0;
}

static void loongson_spi_reginit(struct loongson_spi *loongson_spi_dev)
{
unsigned char val;

val = loongson_spi_read_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG);
val &= ~LOONGSON_SPI_SPCR_SPE;
loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val);

loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPSR_REG,
(LOONGSON_SPI_SPSR_SPIF | LOONGSON_SPI_SPSR_WCOL));

val = loongson_spi_read_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG);
val |= LOONGSON_SPI_SPCR_SPE;
loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val);
}

int loongson_spi_init_master(struct device *dev, struct resource *res)
{
struct spi_master *master;
struct loongson_spi *spi;
struct clk *clk;
int ret;

master = spi_alloc_master(dev, sizeof(struct loongson_spi));
if (master == NULL) {
dev_dbg(dev, "master allocation failed\n");
return -ENOMEM;
}

master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->setup = loongson_spi_setup;
master->prepare_message = loongson_spi_prepare_message;
master->transfer_one = loongson_spi_transfer_one;
master->unprepare_message = loongson_spi_unprepare_message;
master->set_cs = loongson_spi_set_cs;
master->num_chipselect = 4;
master->dev.of_node = of_node_get(dev->of_node);
dev_set_drvdata(dev, master);

spi = spi_master_get_devdata(master);

spi->master = master;

spi->base = devm_ioremap(dev, res->start, resource_size(res));
if (spi->base == NULL) {
dev_err(dev, "cannot map io\n");
ret = -ENXIO;
goto free_master;
}

clk = devm_clk_get(dev, NULL);
if (!IS_ERR(clk))
spi->clk_rate = clk_get_rate(clk);

loongson_spi_reginit(spi);

spi->mode = 0;
if (of_get_property(dev->of_node, "spi-nocs", NULL))
spi->mode |= SPI_NO_CS;

ret = spi_register_master(master);
if (ret < 0)
goto free_master;

return ret;

free_master:
kfree(master);
spi_master_put(master);

return ret;
}
EXPORT_SYMBOL_GPL(loongson_spi_init_master);

static int __maybe_unused loongson_spi_suspend(struct device *dev)
{
struct loongson_spi *loongson_spi;
struct spi_master *master;

master = dev_get_drvdata(dev);
loongson_spi = spi_master_get_devdata(master);

loongson_spi->spcr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
loongson_spi->sper = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
loongson_spi->spsr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
loongson_spi->para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
loongson_spi->sfcs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG);
loongson_spi->timi = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_TIMI_REG);

return 0;
}

static int __maybe_unused loongson_spi_resume(struct device *dev)
{
struct loongson_spi *loongson_spi;
struct spi_master *master;

master = dev_get_drvdata(dev);
loongson_spi = spi_master_get_devdata(master);

loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, loongson_spi->spcr);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, loongson_spi->sper);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPSR_REG, loongson_spi->spsr);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, loongson_spi->sfcs);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_TIMI_REG, loongson_spi->timi);

return 0;
}

const struct dev_pm_ops loongson_spi_dev_pm_ops = {
.suspend = loongson_spi_suspend,
.resume = loongson_spi_resume,
};

MODULE_DESCRIPTION("Loongson spi core driver");
MODULE_LICENSE("GPL");

0 comments on commit 3742622

Please sign in to comment.