Linux Audio

Check our new training course

Loading...
  1/*
  2 * Altera SPI driver
  3 *
  4 * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
  5 *
  6 * Based on spi_s3c24xx.c, which is:
  7 * Copyright (c) 2006 Ben Dooks
  8 * Copyright (c) 2006 Simtec Electronics
  9 *	Ben Dooks <ben@simtec.co.uk>
 10 *
 11 * This program is free software; you can redistribute it and/or modify
 12 * it under the terms of the GNU General Public License version 2 as
 13 * published by the Free Software Foundation.
 14 */
 15
 16#include <linux/interrupt.h>
 17#include <linux/errno.h>
 18#include <linux/module.h>
 19#include <linux/platform_device.h>
 20#include <linux/spi/spi.h>
 21#include <linux/io.h>
 22#include <linux/of.h>
 23
 24#define DRV_NAME "spi_altera"
 25
 26#define ALTERA_SPI_RXDATA	0
 27#define ALTERA_SPI_TXDATA	4
 28#define ALTERA_SPI_STATUS	8
 29#define ALTERA_SPI_CONTROL	12
 30#define ALTERA_SPI_SLAVE_SEL	20
 31
 32#define ALTERA_SPI_STATUS_ROE_MSK	0x8
 33#define ALTERA_SPI_STATUS_TOE_MSK	0x10
 34#define ALTERA_SPI_STATUS_TMT_MSK	0x20
 35#define ALTERA_SPI_STATUS_TRDY_MSK	0x40
 36#define ALTERA_SPI_STATUS_RRDY_MSK	0x80
 37#define ALTERA_SPI_STATUS_E_MSK		0x100
 38
 39#define ALTERA_SPI_CONTROL_IROE_MSK	0x8
 40#define ALTERA_SPI_CONTROL_ITOE_MSK	0x10
 41#define ALTERA_SPI_CONTROL_ITRDY_MSK	0x40
 42#define ALTERA_SPI_CONTROL_IRRDY_MSK	0x80
 43#define ALTERA_SPI_CONTROL_IE_MSK	0x100
 44#define ALTERA_SPI_CONTROL_SSO_MSK	0x400
 45
 46struct altera_spi {
 47	void __iomem *base;
 48	int irq;
 49	int len;
 50	int count;
 51	int bytes_per_word;
 52	unsigned long imr;
 53
 54	/* data buffers */
 55	const unsigned char *tx;
 56	unsigned char *rx;
 57};
 58
 59static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
 60{
 61	return spi_master_get_devdata(sdev->master);
 62}
 63
 64static void altera_spi_set_cs(struct spi_device *spi, bool is_high)
 65{
 66	struct altera_spi *hw = altera_spi_to_hw(spi);
 67
 68	if (is_high) {
 69		hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
 70		writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
 71		writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
 72	} else {
 73		writel(BIT(spi->chip_select), hw->base + ALTERA_SPI_SLAVE_SEL);
 74		hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
 75		writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
 76	}
 77}
 78
 79static void altera_spi_tx_word(struct altera_spi *hw)
 80{
 81	unsigned int txd = 0;
 82
 83	if (hw->tx) {
 84		switch (hw->bytes_per_word) {
 85		case 1:
 86			txd = hw->tx[hw->count];
 87			break;
 88		case 2:
 89			txd = (hw->tx[hw->count * 2]
 90				| (hw->tx[hw->count * 2 + 1] << 8));
 91			break;
 92		}
 93	}
 94
 95	writel(txd, hw->base + ALTERA_SPI_TXDATA);
 96}
 97
 98static void altera_spi_rx_word(struct altera_spi *hw)
 99{
100	unsigned int rxd;
101
102	rxd = readl(hw->base + ALTERA_SPI_RXDATA);
103	if (hw->rx) {
104		switch (hw->bytes_per_word) {
105		case 1:
106			hw->rx[hw->count] = rxd;
107			break;
108		case 2:
109			hw->rx[hw->count * 2] = rxd;
110			hw->rx[hw->count * 2 + 1] = rxd >> 8;
111			break;
112		}
113	}
114
115	hw->count++;
116}
117
118static int altera_spi_txrx(struct spi_master *master,
119	struct spi_device *spi, struct spi_transfer *t)
120{
121	struct altera_spi *hw = spi_master_get_devdata(master);
122
123	hw->tx = t->tx_buf;
124	hw->rx = t->rx_buf;
125	hw->count = 0;
126	hw->bytes_per_word = DIV_ROUND_UP(t->bits_per_word, 8);
127	hw->len = t->len / hw->bytes_per_word;
128
129	if (hw->irq >= 0) {
130		/* enable receive interrupt */
131		hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
132		writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
133
134		/* send the first byte */
135		altera_spi_tx_word(hw);
136	} else {
137		while (hw->count < hw->len) {
138			altera_spi_tx_word(hw);
139
140			while (!(readl(hw->base + ALTERA_SPI_STATUS) &
141				 ALTERA_SPI_STATUS_RRDY_MSK))
142				cpu_relax();
143
144			altera_spi_rx_word(hw);
145		}
146		spi_finalize_current_transfer(master);
147	}
148
149	return t->len;
150}
151
152static irqreturn_t altera_spi_irq(int irq, void *dev)
153{
154	struct spi_master *master = dev;
155	struct altera_spi *hw = spi_master_get_devdata(master);
156
157	altera_spi_rx_word(hw);
158
159	if (hw->count < hw->len) {
160		altera_spi_tx_word(hw);
161	} else {
162		/* disable receive interrupt */
163		hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
164		writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
165
166		spi_finalize_current_transfer(master);
167	}
168
169	return IRQ_HANDLED;
170}
171
172static int altera_spi_probe(struct platform_device *pdev)
173{
174	struct altera_spi *hw;
175	struct spi_master *master;
176	struct resource *res;
177	int err = -ENODEV;
178
179	master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
180	if (!master)
181		return err;
182
183	/* setup the master state. */
184	master->bus_num = pdev->id;
185	master->num_chipselect = 16;
186	master->mode_bits = SPI_CS_HIGH;
187	master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 16);
188	master->dev.of_node = pdev->dev.of_node;
189	master->transfer_one = altera_spi_txrx;
190	master->set_cs = altera_spi_set_cs;
191
192	hw = spi_master_get_devdata(master);
193
194	/* find and map our resources */
195	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
196	hw->base = devm_ioremap_resource(&pdev->dev, res);
197	if (IS_ERR(hw->base)) {
198		err = PTR_ERR(hw->base);
199		goto exit;
200	}
201	/* program defaults into the registers */
202	hw->imr = 0;		/* disable spi interrupts */
203	writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
204	writel(0, hw->base + ALTERA_SPI_STATUS);	/* clear status reg */
205	if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
206		readl(hw->base + ALTERA_SPI_RXDATA);	/* flush rxdata */
207	/* irq is optional */
208	hw->irq = platform_get_irq(pdev, 0);
209	if (hw->irq >= 0) {
210		err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
211				       pdev->name, master);
212		if (err)
213			goto exit;
214	}
215
216	err = devm_spi_register_master(&pdev->dev, master);
217	if (err)
218		goto exit;
219	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
220
221	return 0;
222exit:
223	spi_master_put(master);
224	return err;
225}
226
227#ifdef CONFIG_OF
228static const struct of_device_id altera_spi_match[] = {
229	{ .compatible = "ALTR,spi-1.0", },
230	{ .compatible = "altr,spi-1.0", },
231	{},
232};
233MODULE_DEVICE_TABLE(of, altera_spi_match);
234#endif /* CONFIG_OF */
235
236static struct platform_driver altera_spi_driver = {
237	.probe = altera_spi_probe,
238	.driver = {
239		.name = DRV_NAME,
240		.pm = NULL,
241		.of_match_table = of_match_ptr(altera_spi_match),
242	},
243};
244module_platform_driver(altera_spi_driver);
245
246MODULE_DESCRIPTION("Altera SPI driver");
247MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
248MODULE_LICENSE("GPL");
249MODULE_ALIAS("platform:" DRV_NAME);
1