162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * OpenCores tiny SPI master driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * https://opencores.org/project,tiny_spi
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Based on spi_s3c24xx.c, which is:
1062306a36Sopenharmony_ci * Copyright (c) 2006 Ben Dooks
1162306a36Sopenharmony_ci * Copyright (c) 2006 Simtec Electronics
1262306a36Sopenharmony_ci *	Ben Dooks <ben@simtec.co.uk>
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/interrupt.h>
1662306a36Sopenharmony_ci#include <linux/errno.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/platform_device.h>
1962306a36Sopenharmony_ci#include <linux/spi/spi.h>
2062306a36Sopenharmony_ci#include <linux/spi/spi_bitbang.h>
2162306a36Sopenharmony_ci#include <linux/spi/spi_oc_tiny.h>
2262306a36Sopenharmony_ci#include <linux/io.h>
2362306a36Sopenharmony_ci#include <linux/of.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define DRV_NAME "spi_oc_tiny"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define TINY_SPI_RXDATA 0
2862306a36Sopenharmony_ci#define TINY_SPI_TXDATA 4
2962306a36Sopenharmony_ci#define TINY_SPI_STATUS 8
3062306a36Sopenharmony_ci#define TINY_SPI_CONTROL 12
3162306a36Sopenharmony_ci#define TINY_SPI_BAUD 16
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define TINY_SPI_STATUS_TXE 0x1
3462306a36Sopenharmony_ci#define TINY_SPI_STATUS_TXR 0x2
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistruct tiny_spi {
3762306a36Sopenharmony_ci	/* bitbang has to be first */
3862306a36Sopenharmony_ci	struct spi_bitbang bitbang;
3962306a36Sopenharmony_ci	struct completion done;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	void __iomem *base;
4262306a36Sopenharmony_ci	int irq;
4362306a36Sopenharmony_ci	unsigned int freq;
4462306a36Sopenharmony_ci	unsigned int baudwidth;
4562306a36Sopenharmony_ci	unsigned int baud;
4662306a36Sopenharmony_ci	unsigned int speed_hz;
4762306a36Sopenharmony_ci	unsigned int mode;
4862306a36Sopenharmony_ci	unsigned int len;
4962306a36Sopenharmony_ci	unsigned int txc, rxc;
5062306a36Sopenharmony_ci	const u8 *txp;
5162306a36Sopenharmony_ci	u8 *rxp;
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	return spi_master_get_devdata(sdev->master);
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct tiny_spi *hw = tiny_spi_to_hw(spi);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic int tiny_spi_setup_transfer(struct spi_device *spi,
6762306a36Sopenharmony_ci				   struct spi_transfer *t)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct tiny_spi *hw = tiny_spi_to_hw(spi);
7062306a36Sopenharmony_ci	unsigned int baud = hw->baud;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (t) {
7362306a36Sopenharmony_ci		if (t->speed_hz && t->speed_hz != hw->speed_hz)
7462306a36Sopenharmony_ci			baud = tiny_spi_baud(spi, t->speed_hz);
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci	writel(baud, hw->base + TINY_SPI_BAUD);
7762306a36Sopenharmony_ci	writel(hw->mode, hw->base + TINY_SPI_CONTROL);
7862306a36Sopenharmony_ci	return 0;
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic int tiny_spi_setup(struct spi_device *spi)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct tiny_spi *hw = tiny_spi_to_hw(spi);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if (spi->max_speed_hz != hw->speed_hz) {
8662306a36Sopenharmony_ci		hw->speed_hz = spi->max_speed_hz;
8762306a36Sopenharmony_ci		hw->baud = tiny_spi_baud(spi, hw->speed_hz);
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci	hw->mode = spi->mode & SPI_MODE_X_MASK;
9062306a36Sopenharmony_ci	return 0;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic inline void tiny_spi_wait_txr(struct tiny_spi *hw)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	while (!(readb(hw->base + TINY_SPI_STATUS) &
9662306a36Sopenharmony_ci		 TINY_SPI_STATUS_TXR))
9762306a36Sopenharmony_ci		cpu_relax();
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic inline void tiny_spi_wait_txe(struct tiny_spi *hw)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	while (!(readb(hw->base + TINY_SPI_STATUS) &
10362306a36Sopenharmony_ci		 TINY_SPI_STATUS_TXE))
10462306a36Sopenharmony_ci		cpu_relax();
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct tiny_spi *hw = tiny_spi_to_hw(spi);
11062306a36Sopenharmony_ci	const u8 *txp = t->tx_buf;
11162306a36Sopenharmony_ci	u8 *rxp = t->rx_buf;
11262306a36Sopenharmony_ci	unsigned int i;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (hw->irq >= 0) {
11562306a36Sopenharmony_ci		/* use interrupt driven data transfer */
11662306a36Sopenharmony_ci		hw->len = t->len;
11762306a36Sopenharmony_ci		hw->txp = t->tx_buf;
11862306a36Sopenharmony_ci		hw->rxp = t->rx_buf;
11962306a36Sopenharmony_ci		hw->txc = 0;
12062306a36Sopenharmony_ci		hw->rxc = 0;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		/* send the first byte */
12362306a36Sopenharmony_ci		if (t->len > 1) {
12462306a36Sopenharmony_ci			writeb(hw->txp ? *hw->txp++ : 0,
12562306a36Sopenharmony_ci			       hw->base + TINY_SPI_TXDATA);
12662306a36Sopenharmony_ci			hw->txc++;
12762306a36Sopenharmony_ci			writeb(hw->txp ? *hw->txp++ : 0,
12862306a36Sopenharmony_ci			       hw->base + TINY_SPI_TXDATA);
12962306a36Sopenharmony_ci			hw->txc++;
13062306a36Sopenharmony_ci			writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS);
13162306a36Sopenharmony_ci		} else {
13262306a36Sopenharmony_ci			writeb(hw->txp ? *hw->txp++ : 0,
13362306a36Sopenharmony_ci			       hw->base + TINY_SPI_TXDATA);
13462306a36Sopenharmony_ci			hw->txc++;
13562306a36Sopenharmony_ci			writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS);
13662306a36Sopenharmony_ci		}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		wait_for_completion(&hw->done);
13962306a36Sopenharmony_ci	} else {
14062306a36Sopenharmony_ci		/* we need to tighten the transfer loop */
14162306a36Sopenharmony_ci		writeb(txp ? *txp++ : 0, hw->base + TINY_SPI_TXDATA);
14262306a36Sopenharmony_ci		for (i = 1; i < t->len; i++) {
14362306a36Sopenharmony_ci			writeb(txp ? *txp++ : 0, hw->base + TINY_SPI_TXDATA);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci			if (rxp || (i != t->len - 1))
14662306a36Sopenharmony_ci				tiny_spi_wait_txr(hw);
14762306a36Sopenharmony_ci			if (rxp)
14862306a36Sopenharmony_ci				*rxp++ = readb(hw->base + TINY_SPI_TXDATA);
14962306a36Sopenharmony_ci		}
15062306a36Sopenharmony_ci		tiny_spi_wait_txe(hw);
15162306a36Sopenharmony_ci		if (rxp)
15262306a36Sopenharmony_ci			*rxp++ = readb(hw->base + TINY_SPI_RXDATA);
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	return t->len;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic irqreturn_t tiny_spi_irq(int irq, void *dev)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct tiny_spi *hw = dev;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	writeb(0, hw->base + TINY_SPI_STATUS);
16362306a36Sopenharmony_ci	if (hw->rxc + 1 == hw->len) {
16462306a36Sopenharmony_ci		if (hw->rxp)
16562306a36Sopenharmony_ci			*hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA);
16662306a36Sopenharmony_ci		hw->rxc++;
16762306a36Sopenharmony_ci		complete(&hw->done);
16862306a36Sopenharmony_ci	} else {
16962306a36Sopenharmony_ci		if (hw->rxp)
17062306a36Sopenharmony_ci			*hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA);
17162306a36Sopenharmony_ci		hw->rxc++;
17262306a36Sopenharmony_ci		if (hw->txc < hw->len) {
17362306a36Sopenharmony_ci			writeb(hw->txp ? *hw->txp++ : 0,
17462306a36Sopenharmony_ci			       hw->base + TINY_SPI_TXDATA);
17562306a36Sopenharmony_ci			hw->txc++;
17662306a36Sopenharmony_ci			writeb(TINY_SPI_STATUS_TXR,
17762306a36Sopenharmony_ci			       hw->base + TINY_SPI_STATUS);
17862306a36Sopenharmony_ci		} else {
17962306a36Sopenharmony_ci			writeb(TINY_SPI_STATUS_TXE,
18062306a36Sopenharmony_ci			       hw->base + TINY_SPI_STATUS);
18162306a36Sopenharmony_ci		}
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci	return IRQ_HANDLED;
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci#ifdef CONFIG_OF
18762306a36Sopenharmony_ci#include <linux/of_gpio.h>
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic int tiny_spi_of_probe(struct platform_device *pdev)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct tiny_spi *hw = platform_get_drvdata(pdev);
19262306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
19362306a36Sopenharmony_ci	u32 val;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (!np)
19662306a36Sopenharmony_ci		return 0;
19762306a36Sopenharmony_ci	hw->bitbang.master->dev.of_node = pdev->dev.of_node;
19862306a36Sopenharmony_ci	if (!of_property_read_u32(np, "clock-frequency", &val))
19962306a36Sopenharmony_ci		hw->freq = val;
20062306a36Sopenharmony_ci	if (!of_property_read_u32(np, "baud-width", &val))
20162306a36Sopenharmony_ci		hw->baudwidth = val;
20262306a36Sopenharmony_ci	return 0;
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci#else /* !CONFIG_OF */
20562306a36Sopenharmony_cistatic int tiny_spi_of_probe(struct platform_device *pdev)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	return 0;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci#endif /* CONFIG_OF */
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic int tiny_spi_probe(struct platform_device *pdev)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct tiny_spi_platform_data *platp = dev_get_platdata(&pdev->dev);
21462306a36Sopenharmony_ci	struct tiny_spi *hw;
21562306a36Sopenharmony_ci	struct spi_master *master;
21662306a36Sopenharmony_ci	int err = -ENODEV;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi));
21962306a36Sopenharmony_ci	if (!master)
22062306a36Sopenharmony_ci		return err;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	/* setup the master state. */
22362306a36Sopenharmony_ci	master->bus_num = pdev->id;
22462306a36Sopenharmony_ci	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
22562306a36Sopenharmony_ci	master->setup = tiny_spi_setup;
22662306a36Sopenharmony_ci	master->use_gpio_descriptors = true;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	hw = spi_master_get_devdata(master);
22962306a36Sopenharmony_ci	platform_set_drvdata(pdev, hw);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* setup the state for the bitbang driver */
23262306a36Sopenharmony_ci	hw->bitbang.master = master;
23362306a36Sopenharmony_ci	hw->bitbang.setup_transfer = tiny_spi_setup_transfer;
23462306a36Sopenharmony_ci	hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* find and map our resources */
23762306a36Sopenharmony_ci	hw->base = devm_platform_ioremap_resource(pdev, 0);
23862306a36Sopenharmony_ci	if (IS_ERR(hw->base)) {
23962306a36Sopenharmony_ci		err = PTR_ERR(hw->base);
24062306a36Sopenharmony_ci		goto exit;
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci	/* irq is optional */
24362306a36Sopenharmony_ci	hw->irq = platform_get_irq(pdev, 0);
24462306a36Sopenharmony_ci	if (hw->irq >= 0) {
24562306a36Sopenharmony_ci		init_completion(&hw->done);
24662306a36Sopenharmony_ci		err = devm_request_irq(&pdev->dev, hw->irq, tiny_spi_irq, 0,
24762306a36Sopenharmony_ci				       pdev->name, hw);
24862306a36Sopenharmony_ci		if (err)
24962306a36Sopenharmony_ci			goto exit;
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci	/* find platform data */
25262306a36Sopenharmony_ci	if (platp) {
25362306a36Sopenharmony_ci		hw->freq = platp->freq;
25462306a36Sopenharmony_ci		hw->baudwidth = platp->baudwidth;
25562306a36Sopenharmony_ci	} else {
25662306a36Sopenharmony_ci		err = tiny_spi_of_probe(pdev);
25762306a36Sopenharmony_ci		if (err)
25862306a36Sopenharmony_ci			goto exit;
25962306a36Sopenharmony_ci	}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	/* register our spi controller */
26262306a36Sopenharmony_ci	err = spi_bitbang_start(&hw->bitbang);
26362306a36Sopenharmony_ci	if (err)
26462306a36Sopenharmony_ci		goto exit;
26562306a36Sopenharmony_ci	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	return 0;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ciexit:
27062306a36Sopenharmony_ci	spi_master_put(master);
27162306a36Sopenharmony_ci	return err;
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic void tiny_spi_remove(struct platform_device *pdev)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct tiny_spi *hw = platform_get_drvdata(pdev);
27762306a36Sopenharmony_ci	struct spi_master *master = hw->bitbang.master;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	spi_bitbang_stop(&hw->bitbang);
28062306a36Sopenharmony_ci	spi_master_put(master);
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci#ifdef CONFIG_OF
28462306a36Sopenharmony_cistatic const struct of_device_id tiny_spi_match[] = {
28562306a36Sopenharmony_ci	{ .compatible = "opencores,tiny-spi-rtlsvn2", },
28662306a36Sopenharmony_ci	{},
28762306a36Sopenharmony_ci};
28862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tiny_spi_match);
28962306a36Sopenharmony_ci#endif /* CONFIG_OF */
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_cistatic struct platform_driver tiny_spi_driver = {
29262306a36Sopenharmony_ci	.probe = tiny_spi_probe,
29362306a36Sopenharmony_ci	.remove_new = tiny_spi_remove,
29462306a36Sopenharmony_ci	.driver = {
29562306a36Sopenharmony_ci		.name = DRV_NAME,
29662306a36Sopenharmony_ci		.pm = NULL,
29762306a36Sopenharmony_ci		.of_match_table = of_match_ptr(tiny_spi_match),
29862306a36Sopenharmony_ci	},
29962306a36Sopenharmony_ci};
30062306a36Sopenharmony_cimodule_platform_driver(tiny_spi_driver);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ciMODULE_DESCRIPTION("OpenCores tiny SPI driver");
30362306a36Sopenharmony_ciMODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
30462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
30562306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
306