Unverified Commit 6c7a8640 authored by Yinbo Zhu's avatar Yinbo Zhu Committed by Mark Brown
Browse files

spi: loongson: add bus driver for the loongson spi controller



This bus driver supports the Loongson SPI hardware controller in the
Loongson platforms and supports the use DTS and PCI framework to
register SPI device resources.

Signed-off-by: default avatarYinbo Zhu <zhuyinbo@loongson.cn>
Cc: Andy Shevchenko <andy.shevchenko@gmail.com>
Cc: Mark Brown <broonie@kernel.org>
Reviewed-by: default avatarAndy Shevchenko <andy.shevchenko@gmail.com>
Link: https://lore.kernel.org/r/20230613075834.5219-3-zhuyinbo@loongson.cn


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent b350e6c6
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -12276,6 +12276,10 @@ M: Yinbo Zhu <zhuyinbo@loongson.cn>
L:	linux-spi@vger.kernel.org
S:	Maintained
F:	Documentation/devicetree/bindings/spi/loongson,ls2k-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
LOONGSON-2 SOC SERIES GUTS DRIVER
M:	Yinbo Zhu <zhuyinbo@loongson.cn>
+26 −0
Original line number Diff line number Diff line
@@ -516,6 +516,32 @@ config SPI_LM70_LLP
	  which interfaces to an LM70 temperature sensor using
	  a parallel port.

config SPI_LOONGSON_CORE
	tristate
	depends on LOONGARCH || COMPILE_TEST

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
+3 −0
Original line number Diff line number Diff line
@@ -71,6 +71,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
+279 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0+
// Loongson SPI Support
// Copyright (C) 2023 Loongson Technology Corporation Limited

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.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 en)
{
	int cs;
	unsigned char mask = (BIT(4) | BIT(0)) << spi_get_chipselect(spi, 0);
	unsigned char val = en ? mask :  (BIT(0) << spi_get_chipselect(spi, 0));
	struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);

	cs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG) & ~mask;
	loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, val | cs);
}

static void loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int hz)
{
	unsigned char val;
	unsigned int div, div_tmp;
	static const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11};

	div = clamp_val(DIV_ROUND_UP_ULL(loongson_spi->clk_rate, hz), 2, 4096);
	div_tmp = rdiv[fls(div - 1)];
	loongson_spi->spcr = (div_tmp & GENMASK(1, 0)) >> 0;
	loongson_spi->sper = (div_tmp & GENMASK(3, 2)) >> 2;
	val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
	val &= ~GENMASK(1, 0);
	loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val |
			       loongson_spi->spcr);
	val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
	val &= ~GENMASK(1, 0);
	loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, val |
			       loongson_spi->sper);
	loongson_spi->hz = hz;
}

static void loongson_spi_set_mode(struct loongson_spi *loongson_spi,
				  struct spi_device *spi)
{
	unsigned char val;

	val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
	val &= ~(LOONGSON_SPI_SPCR_CPOL | LOONGSON_SPI_SPCR_CPHA);
	if (spi->mode & SPI_CPOL)
		val |= LOONGSON_SPI_SPCR_CPOL;
	if (spi->mode & SPI_CPHA)
		val |= LOONGSON_SPI_SPCR_CPHA;

	loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val);
	loongson_spi->mode |= spi->mode;
}

static int loongson_spi_update_state(struct loongson_spi *loongson_spi,
				struct spi_device *spi, struct spi_transfer *t)
{
	if (t && loongson_spi->hz != t->speed_hz)
		loongson_spi_set_clk(loongson_spi, t->speed_hz);

	if ((spi->mode ^ loongson_spi->mode) & SPI_MODE_X_MASK)
		loongson_spi_set_mode(loongson_spi, spi);

	return 0;
}

static int loongson_spi_setup(struct spi_device *spi)
{
	struct loongson_spi *loongson_spi;

	loongson_spi = spi_controller_get_devdata(spi->controller);
	if (spi->bits_per_word % 8)
		return -EINVAL;

	if (spi_get_chipselect(spi, 0) >= spi->controller->num_chipselect)
		return -EINVAL;

	loongson_spi->hz = 0;
	loongson_spi_set_cs(spi, true);

	return 0;
}

static int loongson_spi_write_read_8bit(struct spi_device *spi, const u8 **tx_buf,
					u8 **rx_buf, unsigned int num)
{
	int ret;
	struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);

	if (tx_buf && *tx_buf)
		loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, *((*tx_buf)++));
	else
		loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, 0);

	ret = readb_poll_timeout(loongson_spi->base + LOONGSON_SPI_SPSR_REG,
				 loongson_spi->spsr, (loongson_spi->spsr &
				 LOONGSON_SPI_SPSR_RFEMPTY) != LOONGSON_SPI_SPSR_RFEMPTY,
				 1, USEC_PER_MSEC);

	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 ret;
}

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

	count = xfer->len;
	do {
		ret = loongson_spi_write_read_8bit(spi, &tx, &rx, count);
		if (ret)
			break;
	} while (--count);

	return ret;
}

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 &
			       ~LOONGSON_SPI_PARA_MEM_EN);

	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_controller_get_devdata(spi->controller);

	loongson_spi_update_state(loongson_spi, spi, xfer);
	if (xfer->len)
		return 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_controller(struct device *dev, void __iomem *regs)
{
	struct spi_controller *controller;
	struct loongson_spi *spi;
	struct clk *clk;

	controller = devm_spi_alloc_host(dev, sizeof(struct loongson_spi));
	if (controller == NULL)
		return -ENOMEM;

	controller->mode_bits = SPI_MODE_X_MASK | SPI_CS_HIGH;
	controller->setup = loongson_spi_setup;
	controller->prepare_message = loongson_spi_prepare_message;
	controller->transfer_one = loongson_spi_transfer_one;
	controller->unprepare_message = loongson_spi_unprepare_message;
	controller->set_cs = loongson_spi_set_cs;
	controller->num_chipselect = 4;
	device_set_node(&controller->dev, dev_fwnode(dev));
	dev_set_drvdata(dev, controller);

	spi = spi_controller_get_devdata(controller);
	spi->base = regs;
	spi->controller = controller;

	clk = devm_clk_get_optional(dev, NULL);
	if (IS_ERR(clk))
		return dev_err_probe(dev, PTR_ERR(clk), "unable to get clock\n");

	spi->clk_rate = clk_get_rate(clk);
	loongson_spi_reginit(spi);

	spi->mode = 0;

	return devm_spi_register_controller(dev, controller);
}
EXPORT_SYMBOL_NS_GPL(loongson_spi_init_controller, SPI_LOONGSON_CORE);

static int __maybe_unused loongson_spi_suspend(struct device *dev)
{
	struct loongson_spi *loongson_spi;
	struct spi_controller *controller;

	controller = dev_get_drvdata(dev);
	spi_controller_suspend(controller);

	loongson_spi = spi_controller_get_devdata(controller);

	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_controller *controller;

	controller = dev_get_drvdata(dev);
	loongson_spi = spi_controller_get_devdata(controller);

	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);

	spi_controller_resume(controller);

	return 0;
}

const struct dev_pm_ops loongson_spi_dev_pm_ops = {
	.suspend = loongson_spi_suspend,
	.resume = loongson_spi_resume,
};
EXPORT_SYMBOL_NS_GPL(loongson_spi_dev_pm_ops, SPI_LOONGSON_CORE);

MODULE_DESCRIPTION("Loongson SPI core driver");
MODULE_LICENSE("GPL");
+55 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0+
// PCI interface driver for Loongson SPI Support
// Copyright (C) 2023 Loongson Technology Corporation Limited

#include <linux/mod_devicetable.h>
#include <linux/pci.h>

#include "spi-loongson.h"

static int loongson_spi_pci_register(struct pci_dev *pdev,
			const struct pci_device_id *ent)
{
	int ret;
	void __iomem *reg_base;
	struct device *dev = &pdev->dev;
	int pci_bar = 0;

	ret = pcim_enable_device(pdev);
	if (ret < 0)
		return dev_err_probe(dev, ret, "cannot enable pci device\n");

	ret = pcim_iomap_regions(pdev, BIT(pci_bar), pci_name(pdev));
	if (ret)
		return dev_err_probe(dev, ret, "failed to request and remap memory\n");

	reg_base = pcim_iomap_table(pdev)[pci_bar];

	ret = loongson_spi_init_controller(dev, reg_base);
	if (ret)
		return dev_err_probe(dev, ret, "failed to initialize controller\n");

	return 0;
}

static struct pci_device_id loongson_spi_devices[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a0b) },
	{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a1b) },
	{ }
};
MODULE_DEVICE_TABLE(pci, loongson_spi_devices);

static struct pci_driver loongson_spi_pci_driver = {
	.name       = "loongson-spi-pci",
	.id_table   = loongson_spi_devices,
	.probe      = loongson_spi_pci_register,
	.driver	= {
		.bus = &pci_bus_type,
		.pm = &loongson_spi_dev_pm_ops,
	},
};
module_pci_driver(loongson_spi_pci_driver);

MODULE_DESCRIPTION("Loongson spi pci driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(SPI_LOONGSON_CORE);
Loading