Commit e8483fcd authored by Malloy Liu's avatar Malloy Liu
Browse files

spi: add phytium spi support

phytium inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I8B5RL


CVE: NA

----------------------------------

add support for SPI controller on Phytium SoCs and Phytium PCIe chipsets.

Signed-off-by: default avatarMalloy Liu <liuqi1610@phytium.com.cn>
parent d67a9a26
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -454,6 +454,31 @@ config SPI_ORION
	  This enables using the SPI master controller on the Orion
	  and MVEBU chips.

config SPI_PHYTIUM
	tristate
	depends on ARCH_PHYTIUM || COMPILE_TEST

config SPI_PHYTIUM_PLAT
	tristate "Phytium SPI controller platform support"
	select SPI_PHYTIUM
	help
	  This selects a platform driver for Phytium SPI controller.

	  If you say yes to this option, support will be included for
	  Phytium SoC families of SPI controller.

config SPI_PHYTIUM_PCI
	tristate "Phytium SPI controller PCI support"
	depends on PCI
	select SPI_PHYTIUM
	help
	  This selects a PCI driver for Phytium SPI controller.

	  If you say yes to this option, support will be included for
	  Phytium PCIe chipsets of SPI controller.

	  If unsure, say N.

config SPI_PIC32
	tristate "Microchip PIC32 series SPI"
	depends on MACH_PIC32 || COMPILE_TEST
+3 −0
Original line number Diff line number Diff line
@@ -35,6 +35,9 @@ obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o
obj-$(CONFIG_SPI_DW_MMIO)		+= spi-dw-mmio.o
obj-$(CONFIG_SPI_DW_PCI)		+= spi-dw-midpci.o
spi-dw-midpci-objs			:= spi-dw-pci.o spi-dw-mid.o
obj-$(CONFIG_SPI_PHYTIUM)		+= spi-phytium.o
obj-$(CONFIG_SPI_PHYTIUM_PLAT)		+= spi-phytium-plat.o
obj-$(CONFIG_SPI_PHYTIUM_PCI)		+= spi-phytium-pci.o
obj-$(CONFIG_SPI_EFM32)			+= spi-efm32.o
obj-$(CONFIG_SPI_EP93XX)		+= spi-ep93xx.o
obj-$(CONFIG_SPI_FALCON)		+= spi-falcon.o
+127 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Phytium SPI core controller PCI driver.
 *
 * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.
 *
 * Derived from drivers/spi/spi-dw-pci.c
 *   Copyright (c) 2009, 2014 Intel Corporation.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/scatterlist.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/property.h>

#include "spi-phytium.h"

#define DRIVER_NAME "phytium_spi_pci"

static int phytium_spi_pci_probe(struct pci_dev *pdev,
			    const struct pci_device_id *id)
{
	struct phytium_spi *fts;
	int pci_bar = 0;
	int ret;

	fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi),
			GFP_KERNEL);
	if (!fts)
		return -ENOMEM;

	ret = pcim_enable_device(pdev);
	if (ret)
		return ret;

	ret = pcim_iomap_regions(pdev, 1 << pci_bar, pci_name(pdev));
	if (ret) {
		dev_err(&pdev->dev, "pci iomap failed?\n");
		return ret;
	}

	fts->regs = pcim_iomap_table(pdev)[pci_bar];
	if (IS_ERR(fts->regs)) {
		dev_err(&pdev->dev, "SPI region map failed\n");
		return PTR_ERR(fts->regs);
	}

	fts->irq = pdev->irq;
	if (fts->irq < 0) {
		dev_err(&pdev->dev, "no irq resource?\n");
		return fts->irq; /* -ENXIO */
	}

	fts->bus_num = -1;

	fts->max_freq = 48000000;

	fts->num_cs = 4;

	fts->global_cs = 1;

	ret = phytium_spi_add_host(&pdev->dev, fts);
	if (ret)
		return ret;

	pci_set_drvdata(pdev, fts);
	return 0;
}

static void phytium_spi_pci_remove(struct pci_dev *pdev)
{
	struct phytium_spi *fts = pci_get_drvdata(pdev);

	phytium_spi_remove_host(fts);
}


#ifdef CONFIG_PM_SLEEP
static int spi_suspend(struct device *dev)
{
	struct spi_master *master = dev_get_drvdata(dev);
	struct phytium_spi *fts = spi_master_get_devdata(master);

	return phytium_spi_suspend_host(fts);
}

static int spi_resume(struct device *dev)
{
	struct spi_master *master = dev_get_drvdata(dev);
	struct phytium_spi *fts = spi_master_get_devdata(master);

	return phytium_spi_resume_host(fts);
}
#endif

static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume);

static const struct pci_device_id phytium_device_pci_tbl[] = {
	{ PCI_VDEVICE(PHYTIUM, 0xdc2c) },
	{},
};

static struct pci_driver phytium_spi_pci_driver = {
	.name		= DRIVER_NAME,
	.id_table	= phytium_device_pci_tbl,
	.probe		= phytium_spi_pci_probe,
	.remove		= phytium_spi_pci_remove,
	.driver		= {
		.pm = &phytium_spi_pm_ops,
	}
};

module_pci_driver(phytium_spi_pci_driver);

MODULE_AUTHOR("Yiqun Zhang <zhangyiqun@phytium.com.cn>");
MODULE_DESCRIPTION("PCI Driver for Phytium SPI controller core");
MODULE_LICENSE("GPL v2");
+212 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Phytium SPI core controller platform driver.
 *
 * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.
 *
 * Derived from drivers/spi/spi-dw-mmio.c
 *   Copyright (c) 2010, Octasic semiconductor.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/scatterlist.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/property.h>
#include <linux/acpi.h>

#include "spi-phytium.h"

#define DRIVER_NAME "phytium_spi"

struct phytium_spi_clk {
	struct phytium_spi  fts;
	struct clk     *clk;
};

static int phytium_spi_probe(struct platform_device *pdev)
{
	struct phytium_spi_clk *ftsc;
	struct phytium_spi *fts;
	struct resource *mem;
	int ret;
	int num_cs;
	int cs_gpio;
	int global_cs;
	int i;

	ftsc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi_clk),
			GFP_KERNEL);
	if (!ftsc)
		return -ENOMEM;

	fts = &ftsc->fts;

	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!mem) {
		dev_err(&pdev->dev, "no mem resource?\n");
		return -EINVAL;
	}

	fts->regs = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(fts->regs)) {
		dev_err(&pdev->dev, "SPI region map failed\n");
		return PTR_ERR(fts->regs);
	}

	fts->irq = platform_get_irq(pdev, 0);
	if (fts->irq < 0) {
		dev_err(&pdev->dev, "no irq resource?\n");
		return fts->irq; /* -ENXIO */
	}

	if (pdev->dev.of_node) {
		ftsc->clk = devm_clk_get(&pdev->dev, NULL);

		if (IS_ERR(ftsc->clk))
			return PTR_ERR(ftsc->clk);
		ret = clk_prepare_enable(ftsc->clk);
		if (ret)
			return ret;

		fts->max_freq = clk_get_rate(ftsc->clk);
	} else if (has_acpi_companion(&pdev->dev)) {
		fts->max_freq = 48000000;
	}

	fts->bus_num = pdev->id;
	device_property_read_u32(&pdev->dev,
			"reg-io-width", &fts->reg_io_width);

	num_cs = 4;

	device_property_read_u32(&pdev->dev, "num-cs", &num_cs);

	fts->num_cs = num_cs;

	if (pdev->dev.of_node) {
		int i;

		for (i = 0; i < fts->num_cs; i++) {
			cs_gpio = of_get_named_gpio(pdev->dev.of_node,
					"cs-gpios", i);

			if (cs_gpio == -EPROBE_DEFER) {
				ret = cs_gpio;
				goto out;
			}

			if (gpio_is_valid(cs_gpio)) {
				ret = devm_gpio_request(&pdev->dev, cs_gpio,
						dev_name(&pdev->dev));
				if (ret)
					goto out;
			}
		}
	} else if (has_acpi_companion(&pdev->dev)) {
		int n;
		int *cs;
		struct gpio_desc *gpiod;

		n =  gpiod_count(&pdev->dev, "cs");

		cs = devm_kcalloc(&pdev->dev, n, sizeof(int), GFP_KERNEL);
		fts->cs = cs;

		for (i = 0; i < n; i++) {
			gpiod = devm_gpiod_get_index_optional(&pdev->dev,
							"cs", i, GPIOD_OUT_LOW);

			if (IS_ERR(gpiod)) {
				ret = PTR_ERR(gpiod);
				goto out;
			}

			cs_gpio = desc_to_gpio(gpiod);
			cs[i] = cs_gpio;
		}
	}

	device_property_read_u32(&pdev->dev, "global-cs", &global_cs);
	fts->global_cs = global_cs;

	ret = phytium_spi_add_host(&pdev->dev, fts);
	if (ret)
		goto out;

	platform_set_drvdata(pdev, ftsc);
	return 0;

out:
	clk_disable_unprepare(ftsc->clk);
	return ret;
}

static int phytium_spi_remove(struct platform_device *pdev)
{
	struct phytium_spi_clk *ftsc = platform_get_drvdata(pdev);

	phytium_spi_remove_host(&ftsc->fts);
	clk_disable_unprepare(ftsc->clk);

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int spi_suspend(struct device *dev)
{
	struct spi_master *master = dev_get_drvdata(dev);
	struct phytium_spi *fts = spi_master_get_devdata(master);

	return phytium_spi_suspend_host(fts);
}

static int spi_resume(struct device *dev)
{
	struct spi_master *master = dev_get_drvdata(dev);
	struct phytium_spi *fts = spi_master_get_devdata(master);

	return phytium_spi_resume_host(fts);
}
#endif

static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume);

static const struct of_device_id phytium_spi_of_match[] = {
	{ .compatible = "phytium,spi", .data = (void *)0 },
	{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, phytium_spi_of_match);

static const struct acpi_device_id phytium_spi_acpi_match[] = {
	{"PHYT000E", 0},
	{}
};
MODULE_DEVICE_TABLE(acpi, phytium_spi_acpi_match);

static struct platform_driver phytium_spi_driver = {
	.probe		= phytium_spi_probe,
	.remove		= phytium_spi_remove,
	.driver		= {
		.name	= DRIVER_NAME,
		.of_match_table = of_match_ptr(phytium_spi_of_match),
		.acpi_match_table = ACPI_PTR(phytium_spi_acpi_match),
		.pm = &phytium_spi_pm_ops,
	},
};
module_platform_driver(phytium_spi_driver);

MODULE_AUTHOR("Yiqun Zhang <zhangyiqun@phytium.com.cn>");
MODULE_DESCRIPTION("Platform Driver for Phytium SPI controller core");
MODULE_LICENSE("GPL v2");
+539 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Phytium SPI core controller driver.
 *
 * Copyright (c) 2019-2023, Phytium Technology Co., Ltd..
 *
 * Derived from drivers/spi/spi-dw.c
 *   Copyright (c) 2009, Intel Corporation.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/scatterlist.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include "spi-phytium.h"

static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset)
{
	return __raw_readl(fts->regs + offset);
}

static inline u16 phytium_readw(struct phytium_spi *fts, u32 offset)
{
	return __raw_readw(fts->regs + offset);
}

static inline void phytium_writel(struct phytium_spi *fts, u32 offset, u32 val)
{
	__raw_writel(val, fts->regs + offset);
}

static inline void phytium_writew(struct phytium_spi *fts, u32 offset, u16 val)
{
	__raw_writew(val, fts->regs + offset);
}

static inline u32 phytium_read_io_reg(struct phytium_spi *fts, u32 offset)
{
	switch (fts->reg_io_width) {
	case 2:
		return phytium_readw(fts, offset);
	case 4:
	default:
		return phytium_readl(fts, offset);
	}
}

static inline void phytium_write_io_reg(struct phytium_spi *fts,
		u32 offset, u32 val)
{
	switch (fts->reg_io_width) {
	case 2:
		phytium_writew(fts, offset, val);
		break;
	case 4:
	default:
		phytium_writel(fts, offset, val);
		break;
	}
}

static inline void spi_enable_chip(struct phytium_spi *fts, int enable)
{
	phytium_writel(fts, SSIENR, (enable ? 1 : 0));
}

static inline void spi_set_clk(struct phytium_spi *fts, u16 div)
{
	phytium_writel(fts, BAUDR, div);
}

static inline void spi_mask_intr(struct phytium_spi *fts, u32 mask)
{
	u32 new_mask;

	new_mask = phytium_readl(fts, IMR) & ~mask;
	phytium_writel(fts, IMR, new_mask);
}

static inline void spi_umask_intr(struct phytium_spi *fts, u32 mask)
{
	u32 new_mask;

	new_mask = phytium_readl(fts, IMR) | mask;
	phytium_writel(fts, IMR, new_mask);
}

static inline void spi_global_cs(struct phytium_spi *fts)
{
	u32 global_cs_en, mask, setmask;

	mask = GENMASK(fts->num_cs-1, 0) << fts->num_cs;
	setmask = ~GENMASK(fts->num_cs-1, 0);
	global_cs_en = (phytium_readl(fts, GCSR) | mask) & setmask;

	phytium_writel(fts, GCSR, global_cs_en);
}

static inline void spi_reset_chip(struct phytium_spi *fts)
{
	spi_enable_chip(fts, 0);
	if (fts->global_cs)
		spi_global_cs(fts);
	spi_mask_intr(fts, 0xff);
	spi_enable_chip(fts, 1);
}

static inline void spi_shutdown_chip(struct phytium_spi *fts)
{
	spi_enable_chip(fts, 0);
	spi_set_clk(fts, 0);
}

struct phytium_spi_chip {
	u8 poll_mode;
	u8 type;
	void (*cs_control)(u32 command);
};

struct chip_data {
	u8 cs;
	u8 tmode;
	u8 type;

	u8 poll_mode;

	u16 clk_div;
	u32 speed_hz;
	void (*cs_control)(u32 command);
};

static void phytium_spi_set_cs(struct spi_device *spi, bool enable)
{
	struct phytium_spi *fts = spi_master_get_devdata(spi->master);
	struct chip_data *chip = spi_get_ctldata(spi);
	u32 origin;

	if (chip && chip->cs_control)
		chip->cs_control(!enable);

	if (!enable) {
		phytium_writel(fts, SER, BIT(spi->chip_select));
		if (fts->global_cs) {
			origin = phytium_readl(fts, GCSR);
			phytium_writel(fts, GCSR,
					origin | (1 << spi->chip_select));
		}
	} else {
		if (fts->global_cs) {
			origin = phytium_readl(fts, GCSR);
			phytium_writel(fts, GCSR,
					origin & ~(1 << spi->chip_select));
		}
	}
}

static inline u32 tx_max(struct phytium_spi *fts)
{
	u32 tx_left, tx_room, rxtx_gap;

	tx_left = (fts->tx_end - fts->tx) / fts->n_bytes;
	tx_room = fts->fifo_len - phytium_readl(fts, TXFLR);

	rxtx_gap =  ((fts->rx_end - fts->rx) - (fts->tx_end - fts->tx))
			/ fts->n_bytes;

	return min3(tx_left, tx_room, (u32) (fts->fifo_len - rxtx_gap));
}

static inline u32 rx_max(struct phytium_spi *fts)
{
	u32 rx_left = (fts->rx_end - fts->rx) / fts->n_bytes;

	return min_t(u32, rx_left, phytium_readl(fts, RXFLR));
}

static void phytium_writer(struct phytium_spi *fts)
{
	u32 max = tx_max(fts);
	u16 txw = 0;

	while (max--) {
		if (fts->tx_end - fts->len) {
			if (fts->n_bytes == 1)
				txw = *(u8 *)(fts->tx);
			else
				txw = *(u16 *)(fts->tx);
		}
		phytium_write_io_reg(fts, DR, txw);
		fts->tx += fts->n_bytes;
	}
}

static void phytium_reader(struct phytium_spi *fts)
{
	u32 max = rx_max(fts);
	u16 rxw;

	while (max--) {
		rxw = phytium_read_io_reg(fts, DR);
		if (fts->rx_end - fts->len) {
			if (fts->n_bytes == 1)
				*(u8 *)(fts->rx) = rxw;
			else
				*(u16 *)(fts->rx) = rxw;
		}
		fts->rx += fts->n_bytes;
	}
}

static void int_error_stop(struct phytium_spi *fts, const char *msg)
{
	spi_reset_chip(fts);

	dev_err(&fts->master->dev, "%s\n", msg);
	fts->master->cur_msg->status = -EIO;
	spi_finalize_current_transfer(fts->master);
}

static irqreturn_t interrupt_transfer(struct phytium_spi *fts)
{
	u16 irq_status = phytium_readl(fts, ISR);

	if (irq_status & (INT_TXOI | INT_RXOI | INT_RXUI)) {
		phytium_readl(fts, ICR);
		int_error_stop(fts, "irq transfer: fifo overrun/underrun");
		return IRQ_HANDLED;
	}

	phytium_reader(fts);
	if (fts->rx_end == fts->rx) {
		spi_mask_intr(fts, INT_TXEI);
		spi_finalize_current_transfer(fts->master);
		return IRQ_HANDLED;
	}
	if (irq_status & INT_TXEI) {
		spi_mask_intr(fts, INT_TXEI);
		phytium_writer(fts);
		spi_umask_intr(fts, INT_TXEI);
	}

	return IRQ_HANDLED;
}

static irqreturn_t phytium_spi_irq(int irq, void *dev_id)
{
	struct spi_master *master = dev_id;
	struct phytium_spi *fts = spi_master_get_devdata(master);
	u16 irq_status = phytium_readl(fts, ISR) & 0x3f;

	if (!irq_status)
		return IRQ_NONE;

	if (!master->cur_msg) {
		spi_mask_intr(fts, INT_TXEI);
		return IRQ_HANDLED;
	}

	if (fts->transfer_handler)
		return fts->transfer_handler(fts);
	else
		return IRQ_HANDLED;
}

static int poll_transfer(struct phytium_spi *fts)
{
	do {
		phytium_writer(fts);
		phytium_reader(fts);
		cpu_relax();
	} while (fts->rx_end > fts->rx);

	return 0;
}

static int phytium_spi_transfer_one(struct spi_master *master,
		struct spi_device *spi, struct spi_transfer *transfer)
{
	struct phytium_spi *fts = spi_master_get_devdata(master);
	struct chip_data *chip = spi_get_ctldata(spi);
	u8 imask = 0;
	u16 txlevel = 0;
	u16 clk_div;
	u32 cr0;

	fts->tx = (void *)transfer->tx_buf;
	fts->tx_end = fts->tx + transfer->len;
	fts->rx = transfer->rx_buf;
	fts->rx_end = fts->rx + transfer->len;
	fts->len = transfer->len;

	spi_enable_chip(fts, 0);

	if (transfer->speed_hz != chip->speed_hz) {
		clk_div = (fts->max_freq / transfer->speed_hz + 1) & 0xfffe;

		chip->speed_hz = transfer->speed_hz;
		chip->clk_div = clk_div;

		spi_set_clk(fts, chip->clk_div);
	}

	if (transfer->bits_per_word == 8)
		fts->n_bytes = 1;
	else if (transfer->bits_per_word == 16)
		fts->n_bytes = 2;
	else
		return -EINVAL;

	cr0 = (transfer->bits_per_word - 1)
		| (chip->type << FRF_OFFSET)
		| (spi->mode << MODE_OFFSET)
		| (chip->tmode << TMOD_OFFSET);

	if (chip->cs_control) {
		if (fts->rx && fts->tx)
			chip->tmode = TMOD_TR;
		else if (fts->rx)
			chip->tmode = TMOD_RO;
		else
			chip->tmode = TMOD_TO;

		cr0 &= ~TMOD_MASK;
		cr0 |= (chip->tmode << TMOD_OFFSET);
	}

	phytium_writel(fts, CTRL0, cr0);

	spi_mask_intr(fts, 0xff);

	if (!chip->poll_mode) {
		txlevel = min_t(u16, fts->fifo_len / 2,
				fts->len / fts->n_bytes);
		phytium_writel(fts, TXFLTR, txlevel);

		imask |= INT_TXEI | INT_TXOI |
			 INT_RXUI | INT_RXOI;
		spi_umask_intr(fts, imask);

		fts->transfer_handler = interrupt_transfer;
	}

	spi_enable_chip(fts, 1);

	if (chip->poll_mode)
		return poll_transfer(fts);

	return 1;
}

static void phytium_spi_handle_err(struct spi_master *master,
		struct spi_message *msg)
{
	struct phytium_spi *fts = spi_master_get_devdata(master);

	spi_reset_chip(fts);
}

static int phytium_spi_setup(struct spi_device *spi)
{
	struct phytium_spi_chip *chip_info = NULL;
	struct chip_data *chip;
	struct spi_master *master = spi->master;
	struct phytium_spi *fts = spi_master_get_devdata(master);
	int ret;
	u32 cr0;

	spi_enable_chip(fts, 0);

	chip = spi_get_ctldata(spi);
	if (!chip) {
		chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL);
		if (!chip)
			return -ENOMEM;
		spi_set_ctldata(spi, chip);
	}

	chip_info = spi->controller_data;

	if (chip_info) {
		if (chip_info->cs_control)
			chip->cs_control = chip_info->cs_control;

		chip->poll_mode = chip_info->poll_mode;
		chip->type = chip_info->type;
	}

	chip->tmode = 0;

	cr0 = (spi->bits_per_word - 1) | (chip->type << FRF_OFFSET) |
	      (spi->mode << MODE_OFFSET) | (chip->tmode << TMOD_OFFSET);

	phytium_writel(fts, CTRL0, cr0);

	if (gpio_is_valid(spi->cs_gpio)) {
		ret = gpio_direction_output(spi->cs_gpio,
				!(spi->mode & SPI_CS_HIGH));
		if (ret)
			return ret;
	}

	spi_enable_chip(fts, 1);

	return 0;
}

static void phytium_spi_cleanup(struct spi_device *spi)
{
	struct chip_data *chip = spi_get_ctldata(spi);

	kfree(chip);
	spi_set_ctldata(spi, NULL);
}

static void spi_hw_init(struct device *dev, struct phytium_spi *fts)
{
	spi_reset_chip(fts);

	if (!fts->fifo_len) {
		u32 fifo;

		for (fifo = 1; fifo < 256; fifo++) {
			phytium_writel(fts, TXFLTR, fifo);
			if (fifo != phytium_readl(fts, TXFLTR))
				break;
		}
		phytium_writel(fts, TXFLTR, 0);

		fts->fifo_len = (fifo == 1) ? 0 : fifo;
		dev_dbg(dev, "Detected FIFO size: %u bytes\n", fts->fifo_len);
	}
}

int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts)
{
	struct spi_master *master;
	int ret;

	WARN_ON(fts == NULL);

	master = spi_alloc_master(dev, 0);
	if (!master)
		return -ENOMEM;

	fts->master = master;
	snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num);

	ret = request_irq(fts->irq, phytium_spi_irq,
				IRQF_SHARED, fts->name, master);
	if (ret < 0) {
		dev_err(dev, "can not get IRQ\n");
		goto err_free_master;
	}

	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
	master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
	master->bus_num = fts->bus_num;
	master->num_chipselect = fts->num_cs;
	master->setup = phytium_spi_setup;
	master->cleanup = phytium_spi_cleanup;
	master->set_cs = phytium_spi_set_cs;
	master->transfer_one = phytium_spi_transfer_one;
	master->handle_err = phytium_spi_handle_err;
	master->max_speed_hz = fts->max_freq;
	master->dev.of_node = dev->of_node;
	master->dev.fwnode = dev->fwnode;
	master->flags = SPI_MASTER_GPIO_SS;
	master->cs_gpios = fts->cs;

	spi_hw_init(dev, fts);

	spi_master_set_devdata(master, fts);
	ret = devm_spi_register_master(dev, master);
	if (ret) {
		dev_err(&master->dev, "problem registering spi master\n");
		goto err_exit;
	}

	return 0;

err_exit:
	spi_enable_chip(fts, 0);
	free_irq(fts->irq, master);
err_free_master:
	spi_master_put(master);
	return ret;
}
EXPORT_SYMBOL_GPL(phytium_spi_add_host);

void phytium_spi_remove_host(struct phytium_spi *fts)
{
	spi_shutdown_chip(fts);

	free_irq(fts->irq, fts->master);
}
EXPORT_SYMBOL_GPL(phytium_spi_remove_host);

int phytium_spi_suspend_host(struct phytium_spi *fts)
{
	int ret;

	ret = spi_controller_suspend(fts->master);
	if (ret)
		return ret;

	spi_shutdown_chip(fts);
	return 0;
}
EXPORT_SYMBOL_GPL(phytium_spi_suspend_host);

int phytium_spi_resume_host(struct phytium_spi *fts)
{
	int ret;

	spi_hw_init(&fts->master->dev, fts);
	ret = spi_controller_resume(fts->master);
	if (ret)
		dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret);
	return ret;
}
EXPORT_SYMBOL_GPL(phytium_spi_resume_host);

MODULE_AUTHOR("Zhu Mingshuai <zhumingshuai@phytium.com.cn>");
MODULE_AUTHOR("Chen Baozi <chenbaozi@phytium.com.cn>");
MODULE_DESCRIPTION("Driver for Phytium SPI controller core");
MODULE_LICENSE("GPL v2");
Loading