brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · 7d8c5cd Raw
304 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * OpenCores tiny SPI host driver4 *5 * https://opencores.org/project,tiny_spi6 *7 * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw>8 *9 * Based on spi_s3c24xx.c, which is:10 * Copyright (c) 2006 Ben Dooks11 * Copyright (c) 2006 Simtec Electronics12 *	Ben Dooks <ben@simtec.co.uk>13 */14 15#include <linux/interrupt.h>16#include <linux/errno.h>17#include <linux/module.h>18#include <linux/platform_device.h>19#include <linux/spi/spi.h>20#include <linux/spi/spi_bitbang.h>21#include <linux/spi/spi_oc_tiny.h>22#include <linux/io.h>23#include <linux/of.h>24 25#define DRV_NAME "spi_oc_tiny"26 27#define TINY_SPI_RXDATA 028#define TINY_SPI_TXDATA 429#define TINY_SPI_STATUS 830#define TINY_SPI_CONTROL 1231#define TINY_SPI_BAUD 1632 33#define TINY_SPI_STATUS_TXE 0x134#define TINY_SPI_STATUS_TXR 0x235 36struct tiny_spi {37	/* bitbang has to be first */38	struct spi_bitbang bitbang;39	struct completion done;40 41	void __iomem *base;42	int irq;43	unsigned int freq;44	unsigned int baudwidth;45	unsigned int baud;46	unsigned int speed_hz;47	unsigned int mode;48	unsigned int len;49	unsigned int txc, rxc;50	const u8 *txp;51	u8 *rxp;52};53 54static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev)55{56	return spi_controller_get_devdata(sdev->controller);57}58 59static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz)60{61	struct tiny_spi *hw = tiny_spi_to_hw(spi);62 63	return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1;64}65 66static int tiny_spi_setup_transfer(struct spi_device *spi,67				   struct spi_transfer *t)68{69	struct tiny_spi *hw = tiny_spi_to_hw(spi);70	unsigned int baud = hw->baud;71 72	if (t) {73		if (t->speed_hz && t->speed_hz != hw->speed_hz)74			baud = tiny_spi_baud(spi, t->speed_hz);75	}76	writel(baud, hw->base + TINY_SPI_BAUD);77	writel(hw->mode, hw->base + TINY_SPI_CONTROL);78	return 0;79}80 81static int tiny_spi_setup(struct spi_device *spi)82{83	struct tiny_spi *hw = tiny_spi_to_hw(spi);84 85	if (spi->max_speed_hz != hw->speed_hz) {86		hw->speed_hz = spi->max_speed_hz;87		hw->baud = tiny_spi_baud(spi, hw->speed_hz);88	}89	hw->mode = spi->mode & SPI_MODE_X_MASK;90	return 0;91}92 93static inline void tiny_spi_wait_txr(struct tiny_spi *hw)94{95	while (!(readb(hw->base + TINY_SPI_STATUS) &96		 TINY_SPI_STATUS_TXR))97		cpu_relax();98}99 100static inline void tiny_spi_wait_txe(struct tiny_spi *hw)101{102	while (!(readb(hw->base + TINY_SPI_STATUS) &103		 TINY_SPI_STATUS_TXE))104		cpu_relax();105}106 107static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)108{109	struct tiny_spi *hw = tiny_spi_to_hw(spi);110	const u8 *txp = t->tx_buf;111	u8 *rxp = t->rx_buf;112	unsigned int i;113 114	if (hw->irq >= 0) {115		/* use interrupt driven data transfer */116		hw->len = t->len;117		hw->txp = t->tx_buf;118		hw->rxp = t->rx_buf;119		hw->txc = 0;120		hw->rxc = 0;121 122		/* send the first byte */123		if (t->len > 1) {124			writeb(hw->txp ? *hw->txp++ : 0,125			       hw->base + TINY_SPI_TXDATA);126			hw->txc++;127			writeb(hw->txp ? *hw->txp++ : 0,128			       hw->base + TINY_SPI_TXDATA);129			hw->txc++;130			writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS);131		} else {132			writeb(hw->txp ? *hw->txp++ : 0,133			       hw->base + TINY_SPI_TXDATA);134			hw->txc++;135			writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS);136		}137 138		wait_for_completion(&hw->done);139	} else {140		/* we need to tighten the transfer loop */141		writeb(txp ? *txp++ : 0, hw->base + TINY_SPI_TXDATA);142		for (i = 1; i < t->len; i++) {143			writeb(txp ? *txp++ : 0, hw->base + TINY_SPI_TXDATA);144 145			if (rxp || (i != t->len - 1))146				tiny_spi_wait_txr(hw);147			if (rxp)148				*rxp++ = readb(hw->base + TINY_SPI_TXDATA);149		}150		tiny_spi_wait_txe(hw);151		if (rxp)152			*rxp++ = readb(hw->base + TINY_SPI_RXDATA);153	}154 155	return t->len;156}157 158static irqreturn_t tiny_spi_irq(int irq, void *dev)159{160	struct tiny_spi *hw = dev;161 162	writeb(0, hw->base + TINY_SPI_STATUS);163	if (hw->rxc + 1 == hw->len) {164		if (hw->rxp)165			*hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA);166		hw->rxc++;167		complete(&hw->done);168	} else {169		if (hw->rxp)170			*hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA);171		hw->rxc++;172		if (hw->txc < hw->len) {173			writeb(hw->txp ? *hw->txp++ : 0,174			       hw->base + TINY_SPI_TXDATA);175			hw->txc++;176			writeb(TINY_SPI_STATUS_TXR,177			       hw->base + TINY_SPI_STATUS);178		} else {179			writeb(TINY_SPI_STATUS_TXE,180			       hw->base + TINY_SPI_STATUS);181		}182	}183	return IRQ_HANDLED;184}185 186#ifdef CONFIG_OF187static int tiny_spi_of_probe(struct platform_device *pdev)188{189	struct tiny_spi *hw = platform_get_drvdata(pdev);190	struct device_node *np = pdev->dev.of_node;191	u32 val;192 193	if (!np)194		return 0;195	hw->bitbang.ctlr->dev.of_node = pdev->dev.of_node;196	if (!of_property_read_u32(np, "clock-frequency", &val))197		hw->freq = val;198	if (!of_property_read_u32(np, "baud-width", &val))199		hw->baudwidth = val;200	return 0;201}202#else /* !CONFIG_OF */203static int tiny_spi_of_probe(struct platform_device *pdev)204{205	return 0;206}207#endif /* CONFIG_OF */208 209static int tiny_spi_probe(struct platform_device *pdev)210{211	struct tiny_spi_platform_data *platp = dev_get_platdata(&pdev->dev);212	struct tiny_spi *hw;213	struct spi_controller *host;214	int err = -ENODEV;215 216	host = spi_alloc_host(&pdev->dev, sizeof(struct tiny_spi));217	if (!host)218		return err;219 220	/* setup the host state. */221	host->bus_num = pdev->id;222	host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;223	host->setup = tiny_spi_setup;224	host->use_gpio_descriptors = true;225 226	hw = spi_controller_get_devdata(host);227	platform_set_drvdata(pdev, hw);228 229	/* setup the state for the bitbang driver */230	hw->bitbang.ctlr = host;231	hw->bitbang.setup_transfer = tiny_spi_setup_transfer;232	hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs;233 234	/* find and map our resources */235	hw->base = devm_platform_ioremap_resource(pdev, 0);236	if (IS_ERR(hw->base)) {237		err = PTR_ERR(hw->base);238		goto exit;239	}240	/* irq is optional */241	hw->irq = platform_get_irq(pdev, 0);242	if (hw->irq >= 0) {243		init_completion(&hw->done);244		err = devm_request_irq(&pdev->dev, hw->irq, tiny_spi_irq, 0,245				       pdev->name, hw);246		if (err)247			goto exit;248	}249	/* find platform data */250	if (platp) {251		hw->freq = platp->freq;252		hw->baudwidth = platp->baudwidth;253	} else {254		err = tiny_spi_of_probe(pdev);255		if (err)256			goto exit;257	}258 259	/* register our spi controller */260	err = spi_bitbang_start(&hw->bitbang);261	if (err)262		goto exit;263	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);264 265	return 0;266 267exit:268	spi_controller_put(host);269	return err;270}271 272static void tiny_spi_remove(struct platform_device *pdev)273{274	struct tiny_spi *hw = platform_get_drvdata(pdev);275	struct spi_controller *host = hw->bitbang.ctlr;276 277	spi_bitbang_stop(&hw->bitbang);278	spi_controller_put(host);279}280 281#ifdef CONFIG_OF282static const struct of_device_id tiny_spi_match[] = {283	{ .compatible = "opencores,tiny-spi-rtlsvn2", },284	{},285};286MODULE_DEVICE_TABLE(of, tiny_spi_match);287#endif /* CONFIG_OF */288 289static struct platform_driver tiny_spi_driver = {290	.probe = tiny_spi_probe,291	.remove_new = tiny_spi_remove,292	.driver = {293		.name = DRV_NAME,294		.pm = NULL,295		.of_match_table = of_match_ptr(tiny_spi_match),296	},297};298module_platform_driver(tiny_spi_driver);299 300MODULE_DESCRIPTION("OpenCores tiny SPI driver");301MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");302MODULE_LICENSE("GPL");303MODULE_ALIAS("platform:" DRV_NAME);304