1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b42dfed8SFlorian Fainelli /*
3b42dfed8SFlorian Fainelli * Broadcom BCM63xx SPI controller support
4b42dfed8SFlorian Fainelli *
5cde4384eSFlorian Fainelli * Copyright (C) 2009-2012 Florian Fainelli <florian@openwrt.org>
6b42dfed8SFlorian Fainelli * Copyright (C) 2010 Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com>
7b42dfed8SFlorian Fainelli */
8b42dfed8SFlorian Fainelli
9b42dfed8SFlorian Fainelli #include <linux/kernel.h>
10b42dfed8SFlorian Fainelli #include <linux/clk.h>
11b42dfed8SFlorian Fainelli #include <linux/io.h>
12b42dfed8SFlorian Fainelli #include <linux/module.h>
13b42dfed8SFlorian Fainelli #include <linux/platform_device.h>
14b42dfed8SFlorian Fainelli #include <linux/delay.h>
15b42dfed8SFlorian Fainelli #include <linux/interrupt.h>
16b42dfed8SFlorian Fainelli #include <linux/spi/spi.h>
17b42dfed8SFlorian Fainelli #include <linux/completion.h>
18b42dfed8SFlorian Fainelli #include <linux/err.h>
19cde4384eSFlorian Fainelli #include <linux/pm_runtime.h>
20c29f0889SJonas Gorski #include <linux/of.h>
2138807adeSÁlvaro Fernández Rojas #include <linux/reset.h>
22b42dfed8SFlorian Fainelli
2344d8fb30SJonas Gorski /* BCM 6338/6348 SPI core */
2444d8fb30SJonas Gorski #define SPI_6348_RSET_SIZE 64
2544d8fb30SJonas Gorski #define SPI_6348_CMD 0x00 /* 16-bits register */
2644d8fb30SJonas Gorski #define SPI_6348_INT_STATUS 0x02
2744d8fb30SJonas Gorski #define SPI_6348_INT_MASK_ST 0x03
2844d8fb30SJonas Gorski #define SPI_6348_INT_MASK 0x04
2944d8fb30SJonas Gorski #define SPI_6348_ST 0x05
3044d8fb30SJonas Gorski #define SPI_6348_CLK_CFG 0x06
3144d8fb30SJonas Gorski #define SPI_6348_FILL_BYTE 0x07
3244d8fb30SJonas Gorski #define SPI_6348_MSG_TAIL 0x09
3344d8fb30SJonas Gorski #define SPI_6348_RX_TAIL 0x0b
3444d8fb30SJonas Gorski #define SPI_6348_MSG_CTL 0x40 /* 8-bits register */
3544d8fb30SJonas Gorski #define SPI_6348_MSG_CTL_WIDTH 8
3644d8fb30SJonas Gorski #define SPI_6348_MSG_DATA 0x41
3744d8fb30SJonas Gorski #define SPI_6348_MSG_DATA_SIZE 0x3f
3844d8fb30SJonas Gorski #define SPI_6348_RX_DATA 0x80
3944d8fb30SJonas Gorski #define SPI_6348_RX_DATA_SIZE 0x3f
4044d8fb30SJonas Gorski
4144d8fb30SJonas Gorski /* BCM 3368/6358/6262/6368 SPI core */
4244d8fb30SJonas Gorski #define SPI_6358_RSET_SIZE 1804
4344d8fb30SJonas Gorski #define SPI_6358_MSG_CTL 0x00 /* 16-bits register */
4444d8fb30SJonas Gorski #define SPI_6358_MSG_CTL_WIDTH 16
4544d8fb30SJonas Gorski #define SPI_6358_MSG_DATA 0x02
4644d8fb30SJonas Gorski #define SPI_6358_MSG_DATA_SIZE 0x21e
4744d8fb30SJonas Gorski #define SPI_6358_RX_DATA 0x400
4844d8fb30SJonas Gorski #define SPI_6358_RX_DATA_SIZE 0x220
4944d8fb30SJonas Gorski #define SPI_6358_CMD 0x700 /* 16-bits register */
5044d8fb30SJonas Gorski #define SPI_6358_INT_STATUS 0x702
5144d8fb30SJonas Gorski #define SPI_6358_INT_MASK_ST 0x703
5244d8fb30SJonas Gorski #define SPI_6358_INT_MASK 0x704
5344d8fb30SJonas Gorski #define SPI_6358_ST 0x705
5444d8fb30SJonas Gorski #define SPI_6358_CLK_CFG 0x706
5544d8fb30SJonas Gorski #define SPI_6358_FILL_BYTE 0x707
5644d8fb30SJonas Gorski #define SPI_6358_MSG_TAIL 0x709
5744d8fb30SJonas Gorski #define SPI_6358_RX_TAIL 0x70B
5844d8fb30SJonas Gorski
5944d8fb30SJonas Gorski /* Shared SPI definitions */
6044d8fb30SJonas Gorski
6144d8fb30SJonas Gorski /* Message configuration */
6244d8fb30SJonas Gorski #define SPI_FD_RW 0x00
6344d8fb30SJonas Gorski #define SPI_HD_W 0x01
6444d8fb30SJonas Gorski #define SPI_HD_R 0x02
6544d8fb30SJonas Gorski #define SPI_BYTE_CNT_SHIFT 0
6644d8fb30SJonas Gorski #define SPI_6348_MSG_TYPE_SHIFT 6
6744d8fb30SJonas Gorski #define SPI_6358_MSG_TYPE_SHIFT 14
6844d8fb30SJonas Gorski
6944d8fb30SJonas Gorski /* Command */
7044d8fb30SJonas Gorski #define SPI_CMD_NOOP 0x00
7144d8fb30SJonas Gorski #define SPI_CMD_SOFT_RESET 0x01
7244d8fb30SJonas Gorski #define SPI_CMD_HARD_RESET 0x02
7344d8fb30SJonas Gorski #define SPI_CMD_START_IMMEDIATE 0x03
7444d8fb30SJonas Gorski #define SPI_CMD_COMMAND_SHIFT 0
7544d8fb30SJonas Gorski #define SPI_CMD_COMMAND_MASK 0x000f
7644d8fb30SJonas Gorski #define SPI_CMD_DEVICE_ID_SHIFT 4
7744d8fb30SJonas Gorski #define SPI_CMD_PREPEND_BYTE_CNT_SHIFT 8
7844d8fb30SJonas Gorski #define SPI_CMD_ONE_BYTE_SHIFT 11
7944d8fb30SJonas Gorski #define SPI_CMD_ONE_WIRE_SHIFT 12
8044d8fb30SJonas Gorski #define SPI_DEV_ID_0 0
8144d8fb30SJonas Gorski #define SPI_DEV_ID_1 1
8244d8fb30SJonas Gorski #define SPI_DEV_ID_2 2
8344d8fb30SJonas Gorski #define SPI_DEV_ID_3 3
8444d8fb30SJonas Gorski
8544d8fb30SJonas Gorski /* Interrupt mask */
8644d8fb30SJonas Gorski #define SPI_INTR_CMD_DONE 0x01
8744d8fb30SJonas Gorski #define SPI_INTR_RX_OVERFLOW 0x02
8844d8fb30SJonas Gorski #define SPI_INTR_TX_UNDERFLOW 0x04
8944d8fb30SJonas Gorski #define SPI_INTR_TX_OVERFLOW 0x08
9044d8fb30SJonas Gorski #define SPI_INTR_RX_UNDERFLOW 0x10
9144d8fb30SJonas Gorski #define SPI_INTR_CLEAR_ALL 0x1f
9244d8fb30SJonas Gorski
9344d8fb30SJonas Gorski /* Status */
9444d8fb30SJonas Gorski #define SPI_RX_EMPTY 0x02
9544d8fb30SJonas Gorski #define SPI_CMD_BUSY 0x04
9644d8fb30SJonas Gorski #define SPI_SERIAL_BUSY 0x08
9744d8fb30SJonas Gorski
9844d8fb30SJonas Gorski /* Clock configuration */
9944d8fb30SJonas Gorski #define SPI_CLK_20MHZ 0x00
10044d8fb30SJonas Gorski #define SPI_CLK_0_391MHZ 0x01
10144d8fb30SJonas Gorski #define SPI_CLK_0_781MHZ 0x02 /* default */
10244d8fb30SJonas Gorski #define SPI_CLK_1_563MHZ 0x03
10344d8fb30SJonas Gorski #define SPI_CLK_3_125MHZ 0x04
10444d8fb30SJonas Gorski #define SPI_CLK_6_250MHZ 0x05
10544d8fb30SJonas Gorski #define SPI_CLK_12_50MHZ 0x06
10644d8fb30SJonas Gorski #define SPI_CLK_MASK 0x07
10744d8fb30SJonas Gorski #define SPI_SSOFFTIME_MASK 0x38
10844d8fb30SJonas Gorski #define SPI_SSOFFTIME_SHIFT 3
10944d8fb30SJonas Gorski #define SPI_BYTE_SWAP 0x80
11044d8fb30SJonas Gorski
11144d8fb30SJonas Gorski enum bcm63xx_regs_spi {
11244d8fb30SJonas Gorski SPI_CMD,
11344d8fb30SJonas Gorski SPI_INT_STATUS,
11444d8fb30SJonas Gorski SPI_INT_MASK_ST,
11544d8fb30SJonas Gorski SPI_INT_MASK,
11644d8fb30SJonas Gorski SPI_ST,
11744d8fb30SJonas Gorski SPI_CLK_CFG,
11844d8fb30SJonas Gorski SPI_FILL_BYTE,
11944d8fb30SJonas Gorski SPI_MSG_TAIL,
12044d8fb30SJonas Gorski SPI_RX_TAIL,
12144d8fb30SJonas Gorski SPI_MSG_CTL,
12244d8fb30SJonas Gorski SPI_MSG_DATA,
12344d8fb30SJonas Gorski SPI_RX_DATA,
12444d8fb30SJonas Gorski SPI_MSG_TYPE_SHIFT,
12544d8fb30SJonas Gorski SPI_MSG_CTL_WIDTH,
12644d8fb30SJonas Gorski SPI_MSG_DATA_SIZE,
12744d8fb30SJonas Gorski };
128b42dfed8SFlorian Fainelli
1295158814cSJonas Gorski #define BCM63XX_SPI_MAX_PREPEND 7
130b17de076SJonas Gorski
13165059997SJonas Gorski #define BCM63XX_SPI_MAX_CS 8
132a45fcea5SJonas Gorski #define BCM63XX_SPI_BUS_NUM 0
13365059997SJonas Gorski
134b42dfed8SFlorian Fainelli struct bcm63xx_spi {
135b42dfed8SFlorian Fainelli struct completion done;
136b42dfed8SFlorian Fainelli
137b42dfed8SFlorian Fainelli void __iomem *regs;
138b42dfed8SFlorian Fainelli int irq;
139b42dfed8SFlorian Fainelli
140b42dfed8SFlorian Fainelli /* Platform data */
14144d8fb30SJonas Gorski const unsigned long *reg_offsets;
142b85d65ddSAravind Thokala unsigned int fifo_size;
1435a670445SFlorian Fainelli unsigned int msg_type_shift;
1445a670445SFlorian Fainelli unsigned int msg_ctl_width;
145b42dfed8SFlorian Fainelli
146b42dfed8SFlorian Fainelli /* data iomem */
147b42dfed8SFlorian Fainelli u8 __iomem *tx_io;
148b42dfed8SFlorian Fainelli const u8 __iomem *rx_io;
149b42dfed8SFlorian Fainelli
150b42dfed8SFlorian Fainelli struct clk *clk;
151b42dfed8SFlorian Fainelli struct platform_device *pdev;
152b42dfed8SFlorian Fainelli };
153b42dfed8SFlorian Fainelli
bcm_spi_readb(struct bcm63xx_spi * bs,unsigned int offset)154b42dfed8SFlorian Fainelli static inline u8 bcm_spi_readb(struct bcm63xx_spi *bs,
155b42dfed8SFlorian Fainelli unsigned int offset)
156b42dfed8SFlorian Fainelli {
15744d8fb30SJonas Gorski return readb(bs->regs + bs->reg_offsets[offset]);
158b42dfed8SFlorian Fainelli }
159b42dfed8SFlorian Fainelli
bcm_spi_writeb(struct bcm63xx_spi * bs,u8 value,unsigned int offset)160b42dfed8SFlorian Fainelli static inline void bcm_spi_writeb(struct bcm63xx_spi *bs,
161b42dfed8SFlorian Fainelli u8 value, unsigned int offset)
162b42dfed8SFlorian Fainelli {
16344d8fb30SJonas Gorski writeb(value, bs->regs + bs->reg_offsets[offset]);
164b42dfed8SFlorian Fainelli }
165b42dfed8SFlorian Fainelli
bcm_spi_writew(struct bcm63xx_spi * bs,u16 value,unsigned int offset)166b42dfed8SFlorian Fainelli static inline void bcm_spi_writew(struct bcm63xx_spi *bs,
167b42dfed8SFlorian Fainelli u16 value, unsigned int offset)
168b42dfed8SFlorian Fainelli {
169682b5280SJonas Gorski #ifdef CONFIG_CPU_BIG_ENDIAN
17044d8fb30SJonas Gorski iowrite16be(value, bs->regs + bs->reg_offsets[offset]);
171158fcc4eSJonas Gorski #else
17244d8fb30SJonas Gorski writew(value, bs->regs + bs->reg_offsets[offset]);
173158fcc4eSJonas Gorski #endif
174b42dfed8SFlorian Fainelli }
175b42dfed8SFlorian Fainelli
176b85d65ddSAravind Thokala static const unsigned int bcm63xx_spi_freq_table[SPI_CLK_MASK][2] = {
177b42dfed8SFlorian Fainelli { 20000000, SPI_CLK_20MHZ },
178b42dfed8SFlorian Fainelli { 12500000, SPI_CLK_12_50MHZ },
179b42dfed8SFlorian Fainelli { 6250000, SPI_CLK_6_250MHZ },
180b42dfed8SFlorian Fainelli { 3125000, SPI_CLK_3_125MHZ },
181b42dfed8SFlorian Fainelli { 1563000, SPI_CLK_1_563MHZ },
182b42dfed8SFlorian Fainelli { 781000, SPI_CLK_0_781MHZ },
183b42dfed8SFlorian Fainelli { 391000, SPI_CLK_0_391MHZ }
184b42dfed8SFlorian Fainelli };
185b42dfed8SFlorian Fainelli
bcm63xx_spi_setup_transfer(struct spi_device * spi,struct spi_transfer * t)186cde4384eSFlorian Fainelli static void bcm63xx_spi_setup_transfer(struct spi_device *spi,
187cde4384eSFlorian Fainelli struct spi_transfer *t)
188cde4384eSFlorian Fainelli {
1891a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(spi->controller);
190cde4384eSFlorian Fainelli u8 clk_cfg, reg;
191cde4384eSFlorian Fainelli int i;
192cde4384eSFlorian Fainelli
1934dde99bdSGeert Uytterhoeven /* Default to lowest clock configuration */
1944dde99bdSGeert Uytterhoeven clk_cfg = SPI_CLK_0_391MHZ;
1954dde99bdSGeert Uytterhoeven
196b42dfed8SFlorian Fainelli /* Find the closest clock configuration */
197b42dfed8SFlorian Fainelli for (i = 0; i < SPI_CLK_MASK; i++) {
19868792e2aSJonas Gorski if (t->speed_hz >= bcm63xx_spi_freq_table[i][0]) {
199b42dfed8SFlorian Fainelli clk_cfg = bcm63xx_spi_freq_table[i][1];
200b42dfed8SFlorian Fainelli break;
201b42dfed8SFlorian Fainelli }
202b42dfed8SFlorian Fainelli }
203b42dfed8SFlorian Fainelli
204b42dfed8SFlorian Fainelli /* clear existing clock configuration bits of the register */
205b42dfed8SFlorian Fainelli reg = bcm_spi_readb(bs, SPI_CLK_CFG);
206b42dfed8SFlorian Fainelli reg &= ~SPI_CLK_MASK;
207b42dfed8SFlorian Fainelli reg |= clk_cfg;
208b42dfed8SFlorian Fainelli
209b42dfed8SFlorian Fainelli bcm_spi_writeb(bs, reg, SPI_CLK_CFG);
210b42dfed8SFlorian Fainelli dev_dbg(&spi->dev, "Setting clock register to %02x (hz %d)\n",
21168792e2aSJonas Gorski clk_cfg, t->speed_hz);
212b42dfed8SFlorian Fainelli }
213b42dfed8SFlorian Fainelli
214b42dfed8SFlorian Fainelli /* the spi->mode bits understood by this driver: */
215b42dfed8SFlorian Fainelli #define MODEBITS (SPI_CPOL | SPI_CPHA)
216b42dfed8SFlorian Fainelli
bcm63xx_txrx_bufs(struct spi_device * spi,struct spi_transfer * first,unsigned int num_transfers)217b17de076SJonas Gorski static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *first,
218b17de076SJonas Gorski unsigned int num_transfers)
219b42dfed8SFlorian Fainelli {
2201a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(spi->controller);
221b42dfed8SFlorian Fainelli u16 msg_ctl;
222b42dfed8SFlorian Fainelli u16 cmd;
223b17de076SJonas Gorski unsigned int i, timeout = 0, prepend_len = 0, len = 0;
224b17de076SJonas Gorski struct spi_transfer *t = first;
225b17de076SJonas Gorski bool do_rx = false;
226b17de076SJonas Gorski bool do_tx = false;
227b42dfed8SFlorian Fainelli
228cde4384eSFlorian Fainelli /* Disable the CMD_DONE interrupt */
229cde4384eSFlorian Fainelli bcm_spi_writeb(bs, 0, SPI_INT_MASK);
230cde4384eSFlorian Fainelli
231b42dfed8SFlorian Fainelli dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
232b42dfed8SFlorian Fainelli t->tx_buf, t->rx_buf, t->len);
233b42dfed8SFlorian Fainelli
234b17de076SJonas Gorski if (num_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND)
235b17de076SJonas Gorski prepend_len = t->len;
236b17de076SJonas Gorski
237b17de076SJonas Gorski /* prepare the buffer */
238b17de076SJonas Gorski for (i = 0; i < num_transfers; i++) {
239b17de076SJonas Gorski if (t->tx_buf) {
240b17de076SJonas Gorski do_tx = true;
241b17de076SJonas Gorski memcpy_toio(bs->tx_io + len, t->tx_buf, t->len);
242b17de076SJonas Gorski
243b17de076SJonas Gorski /* don't prepend more than one tx */
244b17de076SJonas Gorski if (t != first)
245b17de076SJonas Gorski prepend_len = 0;
246b17de076SJonas Gorski }
247b17de076SJonas Gorski
248b17de076SJonas Gorski if (t->rx_buf) {
249b17de076SJonas Gorski do_rx = true;
250b17de076SJonas Gorski /* prepend is half-duplex write only */
251b17de076SJonas Gorski if (t == first)
252b17de076SJonas Gorski prepend_len = 0;
253b17de076SJonas Gorski }
254b17de076SJonas Gorski
255b17de076SJonas Gorski len += t->len;
256b17de076SJonas Gorski
257b17de076SJonas Gorski t = list_entry(t->transfer_list.next, struct spi_transfer,
258b17de076SJonas Gorski transfer_list);
259b17de076SJonas Gorski }
260b17de076SJonas Gorski
261aa0fe826SAxel Lin reinit_completion(&bs->done);
262b42dfed8SFlorian Fainelli
263b42dfed8SFlorian Fainelli /* Fill in the Message control register */
264b17de076SJonas Gorski msg_ctl = (len << SPI_BYTE_CNT_SHIFT);
265b42dfed8SFlorian Fainelli
266b17de076SJonas Gorski if (do_rx && do_tx && prepend_len == 0)
2675a670445SFlorian Fainelli msg_ctl |= (SPI_FD_RW << bs->msg_type_shift);
268b17de076SJonas Gorski else if (do_rx)
2695a670445SFlorian Fainelli msg_ctl |= (SPI_HD_R << bs->msg_type_shift);
270b17de076SJonas Gorski else if (do_tx)
2715a670445SFlorian Fainelli msg_ctl |= (SPI_HD_W << bs->msg_type_shift);
272b42dfed8SFlorian Fainelli
2735a670445SFlorian Fainelli switch (bs->msg_ctl_width) {
2745a670445SFlorian Fainelli case 8:
2755a670445SFlorian Fainelli bcm_spi_writeb(bs, msg_ctl, SPI_MSG_CTL);
2765a670445SFlorian Fainelli break;
2775a670445SFlorian Fainelli case 16:
278b42dfed8SFlorian Fainelli bcm_spi_writew(bs, msg_ctl, SPI_MSG_CTL);
2795a670445SFlorian Fainelli break;
2805a670445SFlorian Fainelli }
281b42dfed8SFlorian Fainelli
282b42dfed8SFlorian Fainelli /* Issue the transfer */
283b42dfed8SFlorian Fainelli cmd = SPI_CMD_START_IMMEDIATE;
284b17de076SJonas Gorski cmd |= (prepend_len << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
2859e264f3fSAmit Kumar Mahapatra via Alsa-devel cmd |= (spi_get_chipselect(spi, 0) << SPI_CMD_DEVICE_ID_SHIFT);
286b42dfed8SFlorian Fainelli bcm_spi_writew(bs, cmd, SPI_CMD);
287b42dfed8SFlorian Fainelli
288cde4384eSFlorian Fainelli /* Enable the CMD_DONE interrupt */
289cde4384eSFlorian Fainelli bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK);
290b42dfed8SFlorian Fainelli
291c0fde3baSJonas Gorski timeout = wait_for_completion_timeout(&bs->done, HZ);
292c0fde3baSJonas Gorski if (!timeout)
293c0fde3baSJonas Gorski return -ETIMEDOUT;
294c0fde3baSJonas Gorski
29520e9e78fSJonas Gorski if (!do_rx)
296b17de076SJonas Gorski return 0;
297b17de076SJonas Gorski
298b17de076SJonas Gorski len = 0;
299b17de076SJonas Gorski t = first;
300c0fde3baSJonas Gorski /* Read out all the data */
301b17de076SJonas Gorski for (i = 0; i < num_transfers; i++) {
302b17de076SJonas Gorski if (t->rx_buf)
303b17de076SJonas Gorski memcpy_fromio(t->rx_buf, bs->rx_io + len, t->len);
304b17de076SJonas Gorski
305b17de076SJonas Gorski if (t != first || prepend_len == 0)
306b17de076SJonas Gorski len += t->len;
307b17de076SJonas Gorski
308b17de076SJonas Gorski t = list_entry(t->transfer_list.next, struct spi_transfer,
309b17de076SJonas Gorski transfer_list);
310b17de076SJonas Gorski }
311c0fde3baSJonas Gorski
312c0fde3baSJonas Gorski return 0;
313b42dfed8SFlorian Fainelli }
314b42dfed8SFlorian Fainelli
bcm63xx_spi_transfer_one(struct spi_controller * host,struct spi_message * m)3151a9e7619SYang Yingliang static int bcm63xx_spi_transfer_one(struct spi_controller *host,
316cde4384eSFlorian Fainelli struct spi_message *m)
317cde4384eSFlorian Fainelli {
3181a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(host);
319b17de076SJonas Gorski struct spi_transfer *t, *first = NULL;
320cde4384eSFlorian Fainelli struct spi_device *spi = m->spi;
321cde4384eSFlorian Fainelli int status = 0;
322b17de076SJonas Gorski unsigned int n_transfers = 0, total_len = 0;
323b17de076SJonas Gorski bool can_use_prepend = false;
324cde4384eSFlorian Fainelli
325b17de076SJonas Gorski /*
326b17de076SJonas Gorski * This SPI controller does not support keeping CS active after a
327b17de076SJonas Gorski * transfer.
328b17de076SJonas Gorski * Work around this by merging as many transfers we can into one big
329b17de076SJonas Gorski * full-duplex transfers.
330b17de076SJonas Gorski */
331cde4384eSFlorian Fainelli list_for_each_entry(t, &m->transfers, transfer_list) {
332b17de076SJonas Gorski if (!first)
333b17de076SJonas Gorski first = t;
334b17de076SJonas Gorski
335b17de076SJonas Gorski n_transfers++;
336b17de076SJonas Gorski total_len += t->len;
337b17de076SJonas Gorski
338b17de076SJonas Gorski if (n_transfers == 2 && !first->rx_buf && !t->tx_buf &&
339b17de076SJonas Gorski first->len <= BCM63XX_SPI_MAX_PREPEND)
340b17de076SJonas Gorski can_use_prepend = true;
341b17de076SJonas Gorski else if (can_use_prepend && t->tx_buf)
342b17de076SJonas Gorski can_use_prepend = false;
343b17de076SJonas Gorski
344c0fde3baSJonas Gorski /* we can only transfer one fifo worth of data */
345b17de076SJonas Gorski if ((can_use_prepend &&
346b17de076SJonas Gorski total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) ||
347b17de076SJonas Gorski (!can_use_prepend && total_len > bs->fifo_size)) {
348c0fde3baSJonas Gorski dev_err(&spi->dev, "unable to do transfers larger than FIFO size (%i > %i)\n",
349b17de076SJonas Gorski total_len, bs->fifo_size);
350b17de076SJonas Gorski status = -EINVAL;
351b17de076SJonas Gorski goto exit;
352b17de076SJonas Gorski }
353b17de076SJonas Gorski
354b17de076SJonas Gorski /* all combined transfers have to have the same speed */
355b17de076SJonas Gorski if (t->speed_hz != first->speed_hz) {
356b17de076SJonas Gorski dev_err(&spi->dev, "unable to change speed between transfers\n");
357c0fde3baSJonas Gorski status = -EINVAL;
358cde4384eSFlorian Fainelli goto exit;
359cde4384eSFlorian Fainelli }
360cde4384eSFlorian Fainelli
361c0fde3baSJonas Gorski /* CS will be deasserted directly after transfer */
362e7f2d4c6SAlexandru Ardelean if (t->delay.value) {
363c0fde3baSJonas Gorski dev_err(&spi->dev, "unable to keep CS asserted after transfer\n");
364c0fde3baSJonas Gorski status = -EINVAL;
365c0fde3baSJonas Gorski goto exit;
366cde4384eSFlorian Fainelli }
367cde4384eSFlorian Fainelli
368b17de076SJonas Gorski if (t->cs_change ||
369b17de076SJonas Gorski list_is_last(&t->transfer_list, &m->transfers)) {
370c0fde3baSJonas Gorski /* configure adapter for a new transfer */
371b17de076SJonas Gorski bcm63xx_spi_setup_transfer(spi, first);
372c0fde3baSJonas Gorski
373c0fde3baSJonas Gorski /* send the data */
374b17de076SJonas Gorski status = bcm63xx_txrx_bufs(spi, first, n_transfers);
375c0fde3baSJonas Gorski if (status)
376c0fde3baSJonas Gorski goto exit;
377c0fde3baSJonas Gorski
378b17de076SJonas Gorski m->actual_length += total_len;
379b17de076SJonas Gorski
380b17de076SJonas Gorski first = NULL;
381b17de076SJonas Gorski n_transfers = 0;
382b17de076SJonas Gorski total_len = 0;
383b17de076SJonas Gorski can_use_prepend = false;
384b17de076SJonas Gorski }
385cde4384eSFlorian Fainelli }
386cde4384eSFlorian Fainelli exit:
387cde4384eSFlorian Fainelli m->status = status;
3881a9e7619SYang Yingliang spi_finalize_current_message(host);
389cde4384eSFlorian Fainelli
390cde4384eSFlorian Fainelli return 0;
391b42dfed8SFlorian Fainelli }
392b42dfed8SFlorian Fainelli
3931a9e7619SYang Yingliang /* This driver supports single host mode only. Hence
394b42dfed8SFlorian Fainelli * CMD_DONE is the only interrupt we care about
395b42dfed8SFlorian Fainelli */
bcm63xx_spi_interrupt(int irq,void * dev_id)396b42dfed8SFlorian Fainelli static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id)
397b42dfed8SFlorian Fainelli {
3981a9e7619SYang Yingliang struct spi_controller *host = (struct spi_controller *)dev_id;
3991a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(host);
400b42dfed8SFlorian Fainelli u8 intr;
401b42dfed8SFlorian Fainelli
402b42dfed8SFlorian Fainelli /* Read interupts and clear them immediately */
403b42dfed8SFlorian Fainelli intr = bcm_spi_readb(bs, SPI_INT_STATUS);
404b42dfed8SFlorian Fainelli bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS);
405b42dfed8SFlorian Fainelli bcm_spi_writeb(bs, 0, SPI_INT_MASK);
406b42dfed8SFlorian Fainelli
407cde4384eSFlorian Fainelli /* A transfer completed */
408cde4384eSFlorian Fainelli if (intr & SPI_INTR_CMD_DONE)
409b42dfed8SFlorian Fainelli complete(&bs->done);
410b42dfed8SFlorian Fainelli
411b42dfed8SFlorian Fainelli return IRQ_HANDLED;
412b42dfed8SFlorian Fainelli }
413b42dfed8SFlorian Fainelli
bcm63xx_spi_max_length(struct spi_device * spi)414ccd0657cSJonas Gorski static size_t bcm63xx_spi_max_length(struct spi_device *spi)
4150135c03dSJonas Gorski {
4161a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(spi->controller);
4170135c03dSJonas Gorski
4180135c03dSJonas Gorski return bs->fifo_size;
4190135c03dSJonas Gorski }
4200135c03dSJonas Gorski
42144d8fb30SJonas Gorski static const unsigned long bcm6348_spi_reg_offsets[] = {
42244d8fb30SJonas Gorski [SPI_CMD] = SPI_6348_CMD,
42344d8fb30SJonas Gorski [SPI_INT_STATUS] = SPI_6348_INT_STATUS,
42444d8fb30SJonas Gorski [SPI_INT_MASK_ST] = SPI_6348_INT_MASK_ST,
42544d8fb30SJonas Gorski [SPI_INT_MASK] = SPI_6348_INT_MASK,
42644d8fb30SJonas Gorski [SPI_ST] = SPI_6348_ST,
42744d8fb30SJonas Gorski [SPI_CLK_CFG] = SPI_6348_CLK_CFG,
42844d8fb30SJonas Gorski [SPI_FILL_BYTE] = SPI_6348_FILL_BYTE,
42944d8fb30SJonas Gorski [SPI_MSG_TAIL] = SPI_6348_MSG_TAIL,
43044d8fb30SJonas Gorski [SPI_RX_TAIL] = SPI_6348_RX_TAIL,
43144d8fb30SJonas Gorski [SPI_MSG_CTL] = SPI_6348_MSG_CTL,
43244d8fb30SJonas Gorski [SPI_MSG_DATA] = SPI_6348_MSG_DATA,
43344d8fb30SJonas Gorski [SPI_RX_DATA] = SPI_6348_RX_DATA,
43444d8fb30SJonas Gorski [SPI_MSG_TYPE_SHIFT] = SPI_6348_MSG_TYPE_SHIFT,
43544d8fb30SJonas Gorski [SPI_MSG_CTL_WIDTH] = SPI_6348_MSG_CTL_WIDTH,
43644d8fb30SJonas Gorski [SPI_MSG_DATA_SIZE] = SPI_6348_MSG_DATA_SIZE,
43744d8fb30SJonas Gorski };
43844d8fb30SJonas Gorski
43944d8fb30SJonas Gorski static const unsigned long bcm6358_spi_reg_offsets[] = {
44044d8fb30SJonas Gorski [SPI_CMD] = SPI_6358_CMD,
44144d8fb30SJonas Gorski [SPI_INT_STATUS] = SPI_6358_INT_STATUS,
44244d8fb30SJonas Gorski [SPI_INT_MASK_ST] = SPI_6358_INT_MASK_ST,
44344d8fb30SJonas Gorski [SPI_INT_MASK] = SPI_6358_INT_MASK,
44444d8fb30SJonas Gorski [SPI_ST] = SPI_6358_ST,
44544d8fb30SJonas Gorski [SPI_CLK_CFG] = SPI_6358_CLK_CFG,
44644d8fb30SJonas Gorski [SPI_FILL_BYTE] = SPI_6358_FILL_BYTE,
44744d8fb30SJonas Gorski [SPI_MSG_TAIL] = SPI_6358_MSG_TAIL,
44844d8fb30SJonas Gorski [SPI_RX_TAIL] = SPI_6358_RX_TAIL,
44944d8fb30SJonas Gorski [SPI_MSG_CTL] = SPI_6358_MSG_CTL,
45044d8fb30SJonas Gorski [SPI_MSG_DATA] = SPI_6358_MSG_DATA,
45144d8fb30SJonas Gorski [SPI_RX_DATA] = SPI_6358_RX_DATA,
45244d8fb30SJonas Gorski [SPI_MSG_TYPE_SHIFT] = SPI_6358_MSG_TYPE_SHIFT,
45344d8fb30SJonas Gorski [SPI_MSG_CTL_WIDTH] = SPI_6358_MSG_CTL_WIDTH,
45444d8fb30SJonas Gorski [SPI_MSG_DATA_SIZE] = SPI_6358_MSG_DATA_SIZE,
45544d8fb30SJonas Gorski };
45644d8fb30SJonas Gorski
45744d8fb30SJonas Gorski static const struct platform_device_id bcm63xx_spi_dev_match[] = {
45844d8fb30SJonas Gorski {
45944d8fb30SJonas Gorski .name = "bcm6348-spi",
46044d8fb30SJonas Gorski .driver_data = (unsigned long)bcm6348_spi_reg_offsets,
46144d8fb30SJonas Gorski },
46244d8fb30SJonas Gorski {
46344d8fb30SJonas Gorski .name = "bcm6358-spi",
46444d8fb30SJonas Gorski .driver_data = (unsigned long)bcm6358_spi_reg_offsets,
46544d8fb30SJonas Gorski },
46644d8fb30SJonas Gorski {
46744d8fb30SJonas Gorski },
46844d8fb30SJonas Gorski };
469f2d0b351SJinjie Ruan MODULE_DEVICE_TABLE(platform, bcm63xx_spi_dev_match);
470b42dfed8SFlorian Fainelli
471c29f0889SJonas Gorski static const struct of_device_id bcm63xx_spi_of_match[] = {
472c29f0889SJonas Gorski { .compatible = "brcm,bcm6348-spi", .data = &bcm6348_spi_reg_offsets },
473c29f0889SJonas Gorski { .compatible = "brcm,bcm6358-spi", .data = &bcm6358_spi_reg_offsets },
474c29f0889SJonas Gorski { },
475c29f0889SJonas Gorski };
476e7989641SLiao Chen MODULE_DEVICE_TABLE(of, bcm63xx_spi_of_match);
477c29f0889SJonas Gorski
bcm63xx_spi_probe(struct platform_device * pdev)478fd4a319bSGrant Likely static int bcm63xx_spi_probe(struct platform_device *pdev)
479b42dfed8SFlorian Fainelli {
480b42dfed8SFlorian Fainelli struct resource *r;
48144d8fb30SJonas Gorski const unsigned long *bcm63xx_spireg;
482b42dfed8SFlorian Fainelli struct device *dev = &pdev->dev;
483c29f0889SJonas Gorski int irq, bus_num;
4841a9e7619SYang Yingliang struct spi_controller *host;
485b42dfed8SFlorian Fainelli struct clk *clk;
486b42dfed8SFlorian Fainelli struct bcm63xx_spi *bs;
487b42dfed8SFlorian Fainelli int ret;
488c29f0889SJonas Gorski u32 num_cs = BCM63XX_SPI_MAX_CS;
48938807adeSÁlvaro Fernández Rojas struct reset_control *reset;
490b42dfed8SFlorian Fainelli
491c29f0889SJonas Gorski if (dev->of_node) {
492c29f0889SJonas Gorski const struct of_device_id *match;
493c29f0889SJonas Gorski
494c29f0889SJonas Gorski match = of_match_node(bcm63xx_spi_of_match, dev->of_node);
495c29f0889SJonas Gorski if (!match)
49644d8fb30SJonas Gorski return -EINVAL;
497c29f0889SJonas Gorski bcm63xx_spireg = match->data;
49844d8fb30SJonas Gorski
499c29f0889SJonas Gorski of_property_read_u32(dev->of_node, "num-cs", &num_cs);
500c29f0889SJonas Gorski if (num_cs > BCM63XX_SPI_MAX_CS) {
501c29f0889SJonas Gorski dev_warn(dev, "unsupported number of cs (%i), reducing to 8\n",
502c29f0889SJonas Gorski num_cs);
503c29f0889SJonas Gorski num_cs = BCM63XX_SPI_MAX_CS;
504c29f0889SJonas Gorski }
505c29f0889SJonas Gorski
506c29f0889SJonas Gorski bus_num = -1;
507c29f0889SJonas Gorski } else if (pdev->id_entry->driver_data) {
508c29f0889SJonas Gorski const struct platform_device_id *match = pdev->id_entry;
509c29f0889SJonas Gorski
510c29f0889SJonas Gorski bcm63xx_spireg = (const unsigned long *)match->driver_data;
511c29f0889SJonas Gorski bus_num = BCM63XX_SPI_BUS_NUM;
512c29f0889SJonas Gorski } else {
513c29f0889SJonas Gorski return -EINVAL;
514c29f0889SJonas Gorski }
51544d8fb30SJonas Gorski
516b42dfed8SFlorian Fainelli irq = platform_get_irq(pdev, 0);
5176b8ac10eSStephen Boyd if (irq < 0)
518ba8afe94SGustavo A. R. Silva return irq;
519b42dfed8SFlorian Fainelli
520acf4fc6fSJingoo Han clk = devm_clk_get(dev, "spi");
521b42dfed8SFlorian Fainelli if (IS_ERR(clk)) {
522b42dfed8SFlorian Fainelli dev_err(dev, "no clock for device\n");
523acf4fc6fSJingoo Han return PTR_ERR(clk);
524b42dfed8SFlorian Fainelli }
525b42dfed8SFlorian Fainelli
52638807adeSÁlvaro Fernández Rojas reset = devm_reset_control_get_optional_exclusive(dev, NULL);
52738807adeSÁlvaro Fernández Rojas if (IS_ERR(reset))
52838807adeSÁlvaro Fernández Rojas return PTR_ERR(reset);
52938807adeSÁlvaro Fernández Rojas
5301a9e7619SYang Yingliang host = spi_alloc_host(dev, sizeof(*bs));
5311a9e7619SYang Yingliang if (!host) {
532b42dfed8SFlorian Fainelli dev_err(dev, "out of memory\n");
533acf4fc6fSJingoo Han return -ENOMEM;
534b42dfed8SFlorian Fainelli }
535b42dfed8SFlorian Fainelli
5361a9e7619SYang Yingliang bs = spi_controller_get_devdata(host);
537aa0fe826SAxel Lin init_completion(&bs->done);
538b42dfed8SFlorian Fainelli
5391a9e7619SYang Yingliang platform_set_drvdata(pdev, host);
540b42dfed8SFlorian Fainelli bs->pdev = pdev;
541b42dfed8SFlorian Fainelli
542a008ae9fSYang Yingliang bs->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
543b66c7730SJonas Gorski if (IS_ERR(bs->regs)) {
544b66c7730SJonas Gorski ret = PTR_ERR(bs->regs);
545b42dfed8SFlorian Fainelli goto out_err;
546b42dfed8SFlorian Fainelli }
547b42dfed8SFlorian Fainelli
548b42dfed8SFlorian Fainelli bs->irq = irq;
549b42dfed8SFlorian Fainelli bs->clk = clk;
55044d8fb30SJonas Gorski bs->reg_offsets = bcm63xx_spireg;
55144d8fb30SJonas Gorski bs->fifo_size = bs->reg_offsets[SPI_MSG_DATA_SIZE];
552b42dfed8SFlorian Fainelli
553b42dfed8SFlorian Fainelli ret = devm_request_irq(&pdev->dev, irq, bcm63xx_spi_interrupt, 0,
5541a9e7619SYang Yingliang pdev->name, host);
555b42dfed8SFlorian Fainelli if (ret) {
556b42dfed8SFlorian Fainelli dev_err(dev, "unable to request irq\n");
557b42dfed8SFlorian Fainelli goto out_err;
558b42dfed8SFlorian Fainelli }
559b42dfed8SFlorian Fainelli
5601a9e7619SYang Yingliang host->dev.of_node = dev->of_node;
5611a9e7619SYang Yingliang host->bus_num = bus_num;
5621a9e7619SYang Yingliang host->num_chipselect = num_cs;
5631a9e7619SYang Yingliang host->transfer_one_message = bcm63xx_spi_transfer_one;
5641a9e7619SYang Yingliang host->mode_bits = MODEBITS;
5651a9e7619SYang Yingliang host->bits_per_word_mask = SPI_BPW_MASK(8);
5661a9e7619SYang Yingliang host->max_transfer_size = bcm63xx_spi_max_length;
5671a9e7619SYang Yingliang host->max_message_size = bcm63xx_spi_max_length;
5681a9e7619SYang Yingliang host->auto_runtime_pm = true;
56944d8fb30SJonas Gorski bs->msg_type_shift = bs->reg_offsets[SPI_MSG_TYPE_SHIFT];
57044d8fb30SJonas Gorski bs->msg_ctl_width = bs->reg_offsets[SPI_MSG_CTL_WIDTH];
57144d8fb30SJonas Gorski bs->tx_io = (u8 *)(bs->regs + bs->reg_offsets[SPI_MSG_DATA]);
57244d8fb30SJonas Gorski bs->rx_io = (const u8 *)(bs->regs + bs->reg_offsets[SPI_RX_DATA]);
5735a670445SFlorian Fainelli
574b42dfed8SFlorian Fainelli /* Initialize hardware */
575ea01e8a4SJonas Gorski ret = clk_prepare_enable(bs->clk);
576ea01e8a4SJonas Gorski if (ret)
577ea01e8a4SJonas Gorski goto out_err;
578ea01e8a4SJonas Gorski
57938807adeSÁlvaro Fernández Rojas ret = reset_control_reset(reset);
58038807adeSÁlvaro Fernández Rojas if (ret) {
58138807adeSÁlvaro Fernández Rojas dev_err(dev, "unable to reset device: %d\n", ret);
58238807adeSÁlvaro Fernández Rojas goto out_clk_disable;
58338807adeSÁlvaro Fernández Rojas }
58438807adeSÁlvaro Fernández Rojas
585b42dfed8SFlorian Fainelli bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS);
586b42dfed8SFlorian Fainelli
587*7febcf11SJinjie Ruan ret = devm_pm_runtime_enable(&pdev->dev);
588*7febcf11SJinjie Ruan if (ret)
589*7febcf11SJinjie Ruan goto out_clk_disable;
5902d13f2ffSÁlvaro Fernández Rojas
591b42dfed8SFlorian Fainelli /* register and we are done */
5921a9e7619SYang Yingliang ret = devm_spi_register_controller(dev, host);
593b42dfed8SFlorian Fainelli if (ret) {
594b42dfed8SFlorian Fainelli dev_err(dev, "spi register failed\n");
595*7febcf11SJinjie Ruan goto out_clk_disable;
596b42dfed8SFlorian Fainelli }
597b42dfed8SFlorian Fainelli
5980ba2cf70SArnd Bergmann dev_info(dev, "at %pr (irq %d, FIFOs size %d)\n",
5990ba2cf70SArnd Bergmann r, irq, bs->fifo_size);
600b42dfed8SFlorian Fainelli
601b42dfed8SFlorian Fainelli return 0;
602b42dfed8SFlorian Fainelli
603b42dfed8SFlorian Fainelli out_clk_disable:
6044fbb82a7SJonas Gorski clk_disable_unprepare(clk);
605b42dfed8SFlorian Fainelli out_err:
6061a9e7619SYang Yingliang spi_controller_put(host);
607b42dfed8SFlorian Fainelli return ret;
608b42dfed8SFlorian Fainelli }
609b42dfed8SFlorian Fainelli
bcm63xx_spi_remove(struct platform_device * pdev)6108c26432eSUwe Kleine-König static void bcm63xx_spi_remove(struct platform_device *pdev)
611b42dfed8SFlorian Fainelli {
6121a9e7619SYang Yingliang struct spi_controller *host = platform_get_drvdata(pdev);
6131a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(host);
614b42dfed8SFlorian Fainelli
615b42dfed8SFlorian Fainelli /* reset spi block */
616b42dfed8SFlorian Fainelli bcm_spi_writeb(bs, 0, SPI_INT_MASK);
617b42dfed8SFlorian Fainelli
618b42dfed8SFlorian Fainelli /* HW shutdown */
6194fbb82a7SJonas Gorski clk_disable_unprepare(bs->clk);
620b42dfed8SFlorian Fainelli }
621b42dfed8SFlorian Fainelli
bcm63xx_spi_suspend(struct device * dev)622b42dfed8SFlorian Fainelli static int bcm63xx_spi_suspend(struct device *dev)
623b42dfed8SFlorian Fainelli {
6241a9e7619SYang Yingliang struct spi_controller *host = dev_get_drvdata(dev);
6251a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(host);
626b42dfed8SFlorian Fainelli
6271a9e7619SYang Yingliang spi_controller_suspend(host);
62896519957SFlorian Fainelli
6294fbb82a7SJonas Gorski clk_disable_unprepare(bs->clk);
630b42dfed8SFlorian Fainelli
631b42dfed8SFlorian Fainelli return 0;
632b42dfed8SFlorian Fainelli }
633b42dfed8SFlorian Fainelli
bcm63xx_spi_resume(struct device * dev)634b42dfed8SFlorian Fainelli static int bcm63xx_spi_resume(struct device *dev)
635b42dfed8SFlorian Fainelli {
6361a9e7619SYang Yingliang struct spi_controller *host = dev_get_drvdata(dev);
6371a9e7619SYang Yingliang struct bcm63xx_spi *bs = spi_controller_get_devdata(host);
638ea01e8a4SJonas Gorski int ret;
639b42dfed8SFlorian Fainelli
640ea01e8a4SJonas Gorski ret = clk_prepare_enable(bs->clk);
641ea01e8a4SJonas Gorski if (ret)
642ea01e8a4SJonas Gorski return ret;
643b42dfed8SFlorian Fainelli
6441a9e7619SYang Yingliang spi_controller_resume(host);
64596519957SFlorian Fainelli
646b42dfed8SFlorian Fainelli return 0;
647b42dfed8SFlorian Fainelli }
648b42dfed8SFlorian Fainelli
649cc5f6fa4SDhruva Gole static DEFINE_SIMPLE_DEV_PM_OPS(bcm63xx_spi_pm_ops, bcm63xx_spi_suspend, bcm63xx_spi_resume);
650b42dfed8SFlorian Fainelli
651b42dfed8SFlorian Fainelli static struct platform_driver bcm63xx_spi_driver = {
652b42dfed8SFlorian Fainelli .driver = {
653b42dfed8SFlorian Fainelli .name = "bcm63xx-spi",
6541bae2028SJonas Gorski .pm = &bcm63xx_spi_pm_ops,
655c29f0889SJonas Gorski .of_match_table = bcm63xx_spi_of_match,
656b42dfed8SFlorian Fainelli },
65744d8fb30SJonas Gorski .id_table = bcm63xx_spi_dev_match,
658b42dfed8SFlorian Fainelli .probe = bcm63xx_spi_probe,
6598c26432eSUwe Kleine-König .remove_new = bcm63xx_spi_remove,
660b42dfed8SFlorian Fainelli };
661b42dfed8SFlorian Fainelli
662b42dfed8SFlorian Fainelli module_platform_driver(bcm63xx_spi_driver);
663b42dfed8SFlorian Fainelli
664b42dfed8SFlorian Fainelli MODULE_ALIAS("platform:bcm63xx_spi");
665b42dfed8SFlorian Fainelli MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
666b42dfed8SFlorian Fainelli MODULE_AUTHOR("Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com>");
667b42dfed8SFlorian Fainelli MODULE_DESCRIPTION("Broadcom BCM63xx SPI Controller driver");
668b42dfed8SFlorian Fainelli MODULE_LICENSE("GPL");
669