Commit b42dfed83d95a3c9e9cbd708f1993a7474abb79a
Committed by
Grant Likely
1 parent
1cc2df9d6f
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
spi: add Broadcom BCM63xx SPI controller driver
This patch adds support for the SPI controller found on the Broadcom BCM63xx SoCs. Signed-off-by: Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com> Signed-off-by: Florian Fainelli <florian@openwrt.org> Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Showing 3 changed files with 493 additions and 0 deletions Side-by-side Diff
drivers/spi/Kconfig
... | ... | @@ -94,6 +94,12 @@ |
94 | 94 | If you say yes to this option, support will be included for the |
95 | 95 | PSC SPI controller found on Au1550, Au1200 and Au1300 series. |
96 | 96 | |
97 | +config SPI_BCM63XX | |
98 | + tristate "Broadcom BCM63xx SPI controller" | |
99 | + depends on BCM63XX | |
100 | + help | |
101 | + Enable support for the SPI controller on the Broadcom BCM63xx SoCs. | |
102 | + | |
97 | 103 | config SPI_BITBANG |
98 | 104 | tristate "Utilities for Bitbanging SPI masters" |
99 | 105 | help |
drivers/spi/Makefile
... | ... | @@ -14,6 +14,7 @@ |
14 | 14 | obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o |
15 | 15 | obj-$(CONFIG_SPI_ATH79) += spi-ath79.o |
16 | 16 | obj-$(CONFIG_SPI_AU1550) += spi-au1550.o |
17 | +obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63xx.o | |
17 | 18 | obj-$(CONFIG_SPI_BFIN) += spi-bfin5xx.o |
18 | 19 | obj-$(CONFIG_SPI_BFIN_SPORT) += spi-bfin-sport.o |
19 | 20 | obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o |
drivers/spi/spi-bcm63xx.c
1 | +/* | |
2 | + * Broadcom BCM63xx SPI controller support | |
3 | + * | |
4 | + * Copyright (C) 2009-2011 Florian Fainelli <florian@openwrt.org> | |
5 | + * Copyright (C) 2010 Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com> | |
6 | + * | |
7 | + * This program is free software; you can redistribute it and/or | |
8 | + * modify it under the terms of the GNU General Public License | |
9 | + * as published by the Free Software Foundation; either version 2 | |
10 | + * of the License, or (at your option) any later version. | |
11 | + * | |
12 | + * This program is distributed in the hope that it will be useful, | |
13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | + * GNU General Public License for more details. | |
16 | + * | |
17 | + * You should have received a copy of the GNU General Public License | |
18 | + * along with this program; if not, write to the | |
19 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
20 | + */ | |
21 | + | |
22 | +#include <linux/kernel.h> | |
23 | +#include <linux/init.h> | |
24 | +#include <linux/clk.h> | |
25 | +#include <linux/io.h> | |
26 | +#include <linux/module.h> | |
27 | +#include <linux/platform_device.h> | |
28 | +#include <linux/delay.h> | |
29 | +#include <linux/interrupt.h> | |
30 | +#include <linux/spi/spi.h> | |
31 | +#include <linux/completion.h> | |
32 | +#include <linux/err.h> | |
33 | + | |
34 | +#include <bcm63xx_dev_spi.h> | |
35 | + | |
36 | +#define PFX KBUILD_MODNAME | |
37 | +#define DRV_VER "0.1.2" | |
38 | + | |
39 | +struct bcm63xx_spi { | |
40 | + spinlock_t lock; | |
41 | + int stopping; | |
42 | + struct completion done; | |
43 | + | |
44 | + void __iomem *regs; | |
45 | + int irq; | |
46 | + | |
47 | + /* Platform data */ | |
48 | + u32 speed_hz; | |
49 | + unsigned fifo_size; | |
50 | + | |
51 | + /* Data buffers */ | |
52 | + const unsigned char *tx_ptr; | |
53 | + unsigned char *rx_ptr; | |
54 | + | |
55 | + /* data iomem */ | |
56 | + u8 __iomem *tx_io; | |
57 | + const u8 __iomem *rx_io; | |
58 | + | |
59 | + int remaining_bytes; | |
60 | + | |
61 | + struct clk *clk; | |
62 | + struct platform_device *pdev; | |
63 | +}; | |
64 | + | |
65 | +static inline u8 bcm_spi_readb(struct bcm63xx_spi *bs, | |
66 | + unsigned int offset) | |
67 | +{ | |
68 | + return bcm_readb(bs->regs + bcm63xx_spireg(offset)); | |
69 | +} | |
70 | + | |
71 | +static inline u16 bcm_spi_readw(struct bcm63xx_spi *bs, | |
72 | + unsigned int offset) | |
73 | +{ | |
74 | + return bcm_readw(bs->regs + bcm63xx_spireg(offset)); | |
75 | +} | |
76 | + | |
77 | +static inline void bcm_spi_writeb(struct bcm63xx_spi *bs, | |
78 | + u8 value, unsigned int offset) | |
79 | +{ | |
80 | + bcm_writeb(value, bs->regs + bcm63xx_spireg(offset)); | |
81 | +} | |
82 | + | |
83 | +static inline void bcm_spi_writew(struct bcm63xx_spi *bs, | |
84 | + u16 value, unsigned int offset) | |
85 | +{ | |
86 | + bcm_writew(value, bs->regs + bcm63xx_spireg(offset)); | |
87 | +} | |
88 | + | |
89 | +static const unsigned bcm63xx_spi_freq_table[SPI_CLK_MASK][2] = { | |
90 | + { 20000000, SPI_CLK_20MHZ }, | |
91 | + { 12500000, SPI_CLK_12_50MHZ }, | |
92 | + { 6250000, SPI_CLK_6_250MHZ }, | |
93 | + { 3125000, SPI_CLK_3_125MHZ }, | |
94 | + { 1563000, SPI_CLK_1_563MHZ }, | |
95 | + { 781000, SPI_CLK_0_781MHZ }, | |
96 | + { 391000, SPI_CLK_0_391MHZ } | |
97 | +}; | |
98 | + | |
99 | +static int bcm63xx_spi_setup_transfer(struct spi_device *spi, | |
100 | + struct spi_transfer *t) | |
101 | +{ | |
102 | + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); | |
103 | + u8 bits_per_word; | |
104 | + u8 clk_cfg, reg; | |
105 | + u32 hz; | |
106 | + int i; | |
107 | + | |
108 | + bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word; | |
109 | + hz = (t) ? t->speed_hz : spi->max_speed_hz; | |
110 | + if (bits_per_word != 8) { | |
111 | + dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n", | |
112 | + __func__, bits_per_word); | |
113 | + return -EINVAL; | |
114 | + } | |
115 | + | |
116 | + if (spi->chip_select > spi->master->num_chipselect) { | |
117 | + dev_err(&spi->dev, "%s, unsupported slave %d\n", | |
118 | + __func__, spi->chip_select); | |
119 | + return -EINVAL; | |
120 | + } | |
121 | + | |
122 | + /* Find the closest clock configuration */ | |
123 | + for (i = 0; i < SPI_CLK_MASK; i++) { | |
124 | + if (hz <= bcm63xx_spi_freq_table[i][0]) { | |
125 | + clk_cfg = bcm63xx_spi_freq_table[i][1]; | |
126 | + break; | |
127 | + } | |
128 | + } | |
129 | + | |
130 | + /* No matching configuration found, default to lowest */ | |
131 | + if (i == SPI_CLK_MASK) | |
132 | + clk_cfg = SPI_CLK_0_391MHZ; | |
133 | + | |
134 | + /* clear existing clock configuration bits of the register */ | |
135 | + reg = bcm_spi_readb(bs, SPI_CLK_CFG); | |
136 | + reg &= ~SPI_CLK_MASK; | |
137 | + reg |= clk_cfg; | |
138 | + | |
139 | + bcm_spi_writeb(bs, reg, SPI_CLK_CFG); | |
140 | + dev_dbg(&spi->dev, "Setting clock register to %02x (hz %d)\n", | |
141 | + clk_cfg, hz); | |
142 | + | |
143 | + return 0; | |
144 | +} | |
145 | + | |
146 | +/* the spi->mode bits understood by this driver: */ | |
147 | +#define MODEBITS (SPI_CPOL | SPI_CPHA) | |
148 | + | |
149 | +static int bcm63xx_spi_setup(struct spi_device *spi) | |
150 | +{ | |
151 | + struct bcm63xx_spi *bs; | |
152 | + int ret; | |
153 | + | |
154 | + bs = spi_master_get_devdata(spi->master); | |
155 | + | |
156 | + if (bs->stopping) | |
157 | + return -ESHUTDOWN; | |
158 | + | |
159 | + if (!spi->bits_per_word) | |
160 | + spi->bits_per_word = 8; | |
161 | + | |
162 | + if (spi->mode & ~MODEBITS) { | |
163 | + dev_err(&spi->dev, "%s, unsupported mode bits %x\n", | |
164 | + __func__, spi->mode & ~MODEBITS); | |
165 | + return -EINVAL; | |
166 | + } | |
167 | + | |
168 | + ret = bcm63xx_spi_setup_transfer(spi, NULL); | |
169 | + if (ret < 0) { | |
170 | + dev_err(&spi->dev, "setup: unsupported mode bits %x\n", | |
171 | + spi->mode & ~MODEBITS); | |
172 | + return ret; | |
173 | + } | |
174 | + | |
175 | + dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n", | |
176 | + __func__, spi->mode & MODEBITS, spi->bits_per_word, 0); | |
177 | + | |
178 | + return 0; | |
179 | +} | |
180 | + | |
181 | +/* Fill the TX FIFO with as many bytes as possible */ | |
182 | +static void bcm63xx_spi_fill_tx_fifo(struct bcm63xx_spi *bs) | |
183 | +{ | |
184 | + u8 size; | |
185 | + | |
186 | + /* Fill the Tx FIFO with as many bytes as possible */ | |
187 | + size = bs->remaining_bytes < bs->fifo_size ? bs->remaining_bytes : | |
188 | + bs->fifo_size; | |
189 | + memcpy_toio(bs->tx_io, bs->tx_ptr, size); | |
190 | + bs->remaining_bytes -= size; | |
191 | +} | |
192 | + | |
193 | +static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) | |
194 | +{ | |
195 | + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); | |
196 | + u16 msg_ctl; | |
197 | + u16 cmd; | |
198 | + | |
199 | + dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", | |
200 | + t->tx_buf, t->rx_buf, t->len); | |
201 | + | |
202 | + /* Transmitter is inhibited */ | |
203 | + bs->tx_ptr = t->tx_buf; | |
204 | + bs->rx_ptr = t->rx_buf; | |
205 | + init_completion(&bs->done); | |
206 | + | |
207 | + if (t->tx_buf) { | |
208 | + bs->remaining_bytes = t->len; | |
209 | + bcm63xx_spi_fill_tx_fifo(bs); | |
210 | + } | |
211 | + | |
212 | + /* Enable the command done interrupt which | |
213 | + * we use to determine completion of a command */ | |
214 | + bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); | |
215 | + | |
216 | + /* Fill in the Message control register */ | |
217 | + msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT); | |
218 | + | |
219 | + if (t->rx_buf && t->tx_buf) | |
220 | + msg_ctl |= (SPI_FD_RW << SPI_MSG_TYPE_SHIFT); | |
221 | + else if (t->rx_buf) | |
222 | + msg_ctl |= (SPI_HD_R << SPI_MSG_TYPE_SHIFT); | |
223 | + else if (t->tx_buf) | |
224 | + msg_ctl |= (SPI_HD_W << SPI_MSG_TYPE_SHIFT); | |
225 | + | |
226 | + bcm_spi_writew(bs, msg_ctl, SPI_MSG_CTL); | |
227 | + | |
228 | + /* Issue the transfer */ | |
229 | + cmd = SPI_CMD_START_IMMEDIATE; | |
230 | + cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); | |
231 | + cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); | |
232 | + bcm_spi_writew(bs, cmd, SPI_CMD); | |
233 | + wait_for_completion(&bs->done); | |
234 | + | |
235 | + /* Disable the CMD_DONE interrupt */ | |
236 | + bcm_spi_writeb(bs, 0, SPI_INT_MASK); | |
237 | + | |
238 | + return t->len - bs->remaining_bytes; | |
239 | +} | |
240 | + | |
241 | +static int bcm63xx_transfer(struct spi_device *spi, struct spi_message *m) | |
242 | +{ | |
243 | + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); | |
244 | + struct spi_transfer *t; | |
245 | + int ret = 0; | |
246 | + | |
247 | + if (unlikely(list_empty(&m->transfers))) | |
248 | + return -EINVAL; | |
249 | + | |
250 | + if (bs->stopping) | |
251 | + return -ESHUTDOWN; | |
252 | + | |
253 | + list_for_each_entry(t, &m->transfers, transfer_list) { | |
254 | + ret += bcm63xx_txrx_bufs(spi, t); | |
255 | + } | |
256 | + | |
257 | + m->complete(m->context); | |
258 | + | |
259 | + return ret; | |
260 | +} | |
261 | + | |
262 | +/* This driver supports single master mode only. Hence | |
263 | + * CMD_DONE is the only interrupt we care about | |
264 | + */ | |
265 | +static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id) | |
266 | +{ | |
267 | + struct spi_master *master = (struct spi_master *)dev_id; | |
268 | + struct bcm63xx_spi *bs = spi_master_get_devdata(master); | |
269 | + u8 intr; | |
270 | + u16 cmd; | |
271 | + | |
272 | + /* Read interupts and clear them immediately */ | |
273 | + intr = bcm_spi_readb(bs, SPI_INT_STATUS); | |
274 | + bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); | |
275 | + bcm_spi_writeb(bs, 0, SPI_INT_MASK); | |
276 | + | |
277 | + /* A tansfer completed */ | |
278 | + if (intr & SPI_INTR_CMD_DONE) { | |
279 | + u8 rx_tail; | |
280 | + | |
281 | + rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); | |
282 | + | |
283 | + /* Read out all the data */ | |
284 | + if (rx_tail) | |
285 | + memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail); | |
286 | + | |
287 | + /* See if there is more data to send */ | |
288 | + if (bs->remaining_bytes > 0) { | |
289 | + bcm63xx_spi_fill_tx_fifo(bs); | |
290 | + | |
291 | + /* Start the transfer */ | |
292 | + bcm_spi_writew(bs, SPI_HD_W << SPI_MSG_TYPE_SHIFT, | |
293 | + SPI_MSG_CTL); | |
294 | + cmd = bcm_spi_readw(bs, SPI_CMD); | |
295 | + cmd |= SPI_CMD_START_IMMEDIATE; | |
296 | + cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); | |
297 | + bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); | |
298 | + bcm_spi_writew(bs, cmd, SPI_CMD); | |
299 | + } else { | |
300 | + complete(&bs->done); | |
301 | + } | |
302 | + } | |
303 | + | |
304 | + return IRQ_HANDLED; | |
305 | +} | |
306 | + | |
307 | + | |
308 | +static int __devinit bcm63xx_spi_probe(struct platform_device *pdev) | |
309 | +{ | |
310 | + struct resource *r; | |
311 | + struct device *dev = &pdev->dev; | |
312 | + struct bcm63xx_spi_pdata *pdata = pdev->dev.platform_data; | |
313 | + int irq; | |
314 | + struct spi_master *master; | |
315 | + struct clk *clk; | |
316 | + struct bcm63xx_spi *bs; | |
317 | + int ret; | |
318 | + | |
319 | + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
320 | + if (!r) { | |
321 | + dev_err(dev, "no iomem\n"); | |
322 | + ret = -ENXIO; | |
323 | + goto out; | |
324 | + } | |
325 | + | |
326 | + irq = platform_get_irq(pdev, 0); | |
327 | + if (irq < 0) { | |
328 | + dev_err(dev, "no irq\n"); | |
329 | + ret = -ENXIO; | |
330 | + goto out; | |
331 | + } | |
332 | + | |
333 | + clk = clk_get(dev, "spi"); | |
334 | + if (IS_ERR(clk)) { | |
335 | + dev_err(dev, "no clock for device\n"); | |
336 | + ret = PTR_ERR(clk); | |
337 | + goto out; | |
338 | + } | |
339 | + | |
340 | + master = spi_alloc_master(dev, sizeof(*bs)); | |
341 | + if (!master) { | |
342 | + dev_err(dev, "out of memory\n"); | |
343 | + ret = -ENOMEM; | |
344 | + goto out_clk; | |
345 | + } | |
346 | + | |
347 | + bs = spi_master_get_devdata(master); | |
348 | + init_completion(&bs->done); | |
349 | + | |
350 | + platform_set_drvdata(pdev, master); | |
351 | + bs->pdev = pdev; | |
352 | + | |
353 | + if (!devm_request_mem_region(&pdev->dev, r->start, | |
354 | + resource_size(r), PFX)) { | |
355 | + dev_err(dev, "iomem request failed\n"); | |
356 | + ret = -ENXIO; | |
357 | + goto out_err; | |
358 | + } | |
359 | + | |
360 | + bs->regs = devm_ioremap_nocache(&pdev->dev, r->start, | |
361 | + resource_size(r)); | |
362 | + if (!bs->regs) { | |
363 | + dev_err(dev, "unable to ioremap regs\n"); | |
364 | + ret = -ENOMEM; | |
365 | + goto out_err; | |
366 | + } | |
367 | + | |
368 | + bs->irq = irq; | |
369 | + bs->clk = clk; | |
370 | + bs->fifo_size = pdata->fifo_size; | |
371 | + | |
372 | + ret = devm_request_irq(&pdev->dev, irq, bcm63xx_spi_interrupt, 0, | |
373 | + pdev->name, master); | |
374 | + if (ret) { | |
375 | + dev_err(dev, "unable to request irq\n"); | |
376 | + goto out_err; | |
377 | + } | |
378 | + | |
379 | + master->bus_num = pdata->bus_num; | |
380 | + master->num_chipselect = pdata->num_chipselect; | |
381 | + master->setup = bcm63xx_spi_setup; | |
382 | + master->transfer = bcm63xx_transfer; | |
383 | + bs->speed_hz = pdata->speed_hz; | |
384 | + bs->stopping = 0; | |
385 | + bs->tx_io = (u8 *)(bs->regs + bcm63xx_spireg(SPI_MSG_DATA)); | |
386 | + bs->rx_io = (const u8 *)(bs->regs + bcm63xx_spireg(SPI_RX_DATA)); | |
387 | + spin_lock_init(&bs->lock); | |
388 | + | |
389 | + /* Initialize hardware */ | |
390 | + clk_enable(bs->clk); | |
391 | + bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); | |
392 | + | |
393 | + /* register and we are done */ | |
394 | + ret = spi_register_master(master); | |
395 | + if (ret) { | |
396 | + dev_err(dev, "spi register failed\n"); | |
397 | + goto out_clk_disable; | |
398 | + } | |
399 | + | |
400 | + dev_info(dev, "at 0x%08x (irq %d, FIFOs size %d) v%s\n", | |
401 | + r->start, irq, bs->fifo_size, DRV_VER); | |
402 | + | |
403 | + return 0; | |
404 | + | |
405 | +out_clk_disable: | |
406 | + clk_disable(clk); | |
407 | +out_err: | |
408 | + platform_set_drvdata(pdev, NULL); | |
409 | + spi_master_put(master); | |
410 | +out_clk: | |
411 | + clk_put(clk); | |
412 | +out: | |
413 | + return ret; | |
414 | +} | |
415 | + | |
416 | +static int __devexit bcm63xx_spi_remove(struct platform_device *pdev) | |
417 | +{ | |
418 | + struct spi_master *master = platform_get_drvdata(pdev); | |
419 | + struct bcm63xx_spi *bs = spi_master_get_devdata(master); | |
420 | + | |
421 | + /* reset spi block */ | |
422 | + bcm_spi_writeb(bs, 0, SPI_INT_MASK); | |
423 | + spin_lock(&bs->lock); | |
424 | + bs->stopping = 1; | |
425 | + | |
426 | + /* HW shutdown */ | |
427 | + clk_disable(bs->clk); | |
428 | + clk_put(bs->clk); | |
429 | + | |
430 | + spin_unlock(&bs->lock); | |
431 | + platform_set_drvdata(pdev, 0); | |
432 | + spi_unregister_master(master); | |
433 | + | |
434 | + return 0; | |
435 | +} | |
436 | + | |
437 | +#ifdef CONFIG_PM | |
438 | +static int bcm63xx_spi_suspend(struct device *dev) | |
439 | +{ | |
440 | + struct spi_master *master = | |
441 | + platform_get_drvdata(to_platform_device(dev)); | |
442 | + struct bcm63xx_spi *bs = spi_master_get_devdata(master); | |
443 | + | |
444 | + clk_disable(bs->clk); | |
445 | + | |
446 | + return 0; | |
447 | +} | |
448 | + | |
449 | +static int bcm63xx_spi_resume(struct device *dev) | |
450 | +{ | |
451 | + struct spi_master *master = | |
452 | + platform_get_drvdata(to_platform_device(dev)); | |
453 | + struct bcm63xx_spi *bs = spi_master_get_devdata(master); | |
454 | + | |
455 | + clk_enable(bs->clk); | |
456 | + | |
457 | + return 0; | |
458 | +} | |
459 | + | |
460 | +static const struct dev_pm_ops bcm63xx_spi_pm_ops = { | |
461 | + .suspend = bcm63xx_spi_suspend, | |
462 | + .resume = bcm63xx_spi_resume, | |
463 | +}; | |
464 | + | |
465 | +#define BCM63XX_SPI_PM_OPS (&bcm63xx_spi_pm_ops) | |
466 | +#else | |
467 | +#define BCM63XX_SPI_PM_OPS NULL | |
468 | +#endif | |
469 | + | |
470 | +static struct platform_driver bcm63xx_spi_driver = { | |
471 | + .driver = { | |
472 | + .name = "bcm63xx-spi", | |
473 | + .owner = THIS_MODULE, | |
474 | + .pm = BCM63XX_SPI_PM_OPS, | |
475 | + }, | |
476 | + .probe = bcm63xx_spi_probe, | |
477 | + .remove = __devexit_p(bcm63xx_spi_remove), | |
478 | +}; | |
479 | + | |
480 | +module_platform_driver(bcm63xx_spi_driver); | |
481 | + | |
482 | +MODULE_ALIAS("platform:bcm63xx_spi"); | |
483 | +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); | |
484 | +MODULE_AUTHOR("Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com>"); | |
485 | +MODULE_DESCRIPTION("Broadcom BCM63xx SPI Controller driver"); | |
486 | +MODULE_LICENSE("GPL"); |