1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * Broadcom SPI Host Controller Driver - Linux Per-port
4 *
5 * Copyright (C) 1999-2019, Broadcom.
6 *
7 * Unless you and Broadcom execute a separate written software license
8 * agreement governing use of this software, this software is licensed to you
9 * under the terms of the GNU General Public License version 2 (the "GPL"),
10 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
11 * following added to such license:
12 *
13 * As a special exception, the copyright holders of this software give you
14 * permission to link this software with independent modules, and to copy and
15 * distribute the resulting executable under terms of your choice, provided that
16 * you also meet, for each linked independent module, the terms and conditions of
17 * the license of that module. An independent module is a module which is not
18 * derived from this software. The special exception does not apply to any
19 * modifications of the software.
20 *
21 * Notwithstanding the above, under no circumstances may you combine this
22 * software in any way with any other Broadcom software provided under a license
23 * other than the GPL, without Broadcom's express prior written consent.
24 *
25 *
26 * <<Broadcom-WL-IPTag/Open:>>
27 *
28 * $Id: bcmsdspi_linux.c 514727 2014-11-12 03:02:48Z $
29 */
30
31 #include <typedefs.h>
32 #include <bcmutils.h>
33
34 #include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */
35 #include <sdiovar.h> /* to get msglevel bit values */
36
37 #ifdef BCMSPI_ANDROID
38 #include <bcmsdh.h>
39 #include <bcmspibrcm.h>
40 #include <linux/spi/spi.h>
41 #else
42 #include <pcicfg.h>
43 #include <sdio.h> /* SDIO Device and Protocol Specs */
44 #include <linux/sched.h> /* request_irq(), free_irq() */
45 #include <bcmsdspi.h>
46 #include <bcmspi.h>
47 #endif /* BCMSPI_ANDROID */
48
49 #ifndef BCMSPI_ANDROID
50 extern uint sd_crc;
51 module_param(sd_crc, uint, 0);
52
53 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
54 #define KERNEL26
55 #endif // endif
56 #endif /* !BCMSPI_ANDROID */
57
58 struct sdos_info {
59 sdioh_info_t *sd;
60 spinlock_t lock;
61 #ifndef BCMSPI_ANDROID
62 wait_queue_head_t intr_wait_queue;
63 #endif /* !BCMSPI_ANDROID */
64 };
65
66 #ifndef BCMSPI_ANDROID
67 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
68 #define BLOCKABLE() (!in_atomic())
69 #else
70 #define BLOCKABLE() (!in_interrupt())
71 #endif // endif
72
73 /* Interrupt handler */
74 static irqreturn_t
sdspi_isr(int irq,void * dev_id,struct pt_regs * ptregs)75 sdspi_isr(int irq, void *dev_id
76 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
77 , struct pt_regs *ptregs
78 #endif // endif
79 )
80 {
81 sdioh_info_t *sd;
82 struct sdos_info *sdos;
83 bool ours;
84
85 sd = (sdioh_info_t *)dev_id;
86 sd->local_intrcount++;
87
88 if (!sd->card_init_done) {
89 sd_err(("%s: Hey Bogus intr...not even initted: irq %d\n", __FUNCTION__, irq));
90 return IRQ_RETVAL(FALSE);
91 } else {
92 ours = spi_check_client_intr(sd, NULL);
93
94 /* For local interrupts, wake the waiting process */
95 if (ours && sd->got_hcint) {
96 sdos = (struct sdos_info *)sd->sdos_info;
97 wake_up_interruptible(&sdos->intr_wait_queue);
98 }
99
100 return IRQ_RETVAL(ours);
101 }
102 }
103 #endif /* !BCMSPI_ANDROID */
104
105 #ifdef BCMSPI_ANDROID
106 static struct spi_device *gBCMSPI = NULL;
107
108 extern int bcmsdh_probe(struct device *dev);
109 extern int bcmsdh_remove(struct device *dev);
110
bcmsdh_spi_probe(struct spi_device * spi_dev)111 static int bcmsdh_spi_probe(struct spi_device *spi_dev)
112 {
113 int ret = 0;
114
115 gBCMSPI = spi_dev;
116
117 #ifdef SPI_PIO_32BIT_RW
118 spi_dev->bits_per_word = 32;
119 #else
120 spi_dev->bits_per_word = 8;
121 #endif /* SPI_PIO_32BIT_RW */
122 ret = spi_setup(spi_dev);
123
124 if (ret) {
125 sd_err(("bcmsdh_spi_probe: spi_setup fail with %d\n", ret));
126 }
127 sd_err(("bcmsdh_spi_probe: spi_setup with %d, bits_per_word=%d\n",
128 ret, spi_dev->bits_per_word));
129 ret = bcmsdh_probe(&spi_dev->dev);
130
131 return ret;
132 }
133
bcmsdh_spi_remove(struct spi_device * spi_dev)134 static int bcmsdh_spi_remove(struct spi_device *spi_dev)
135 {
136 int ret = 0;
137
138 ret = bcmsdh_remove(&spi_dev->dev);
139 gBCMSPI = NULL;
140
141 return ret;
142 }
143
144 static struct spi_driver bcmsdh_spi_driver = {
145 .probe = bcmsdh_spi_probe,
146 .remove = bcmsdh_spi_remove,
147 .driver = {
148 .name = "wlan_spi",
149 .bus = &spi_bus_type,
150 .owner = THIS_MODULE,
151 },
152 };
153
154 /*
155 * module init
156 */
bcmsdh_register_client_driver(void)157 int bcmsdh_register_client_driver(void)
158 {
159 int error = 0;
160 sd_trace(("bcmsdh_gspi: %s Enter\n", __FUNCTION__));
161
162 error = spi_register_driver(&bcmsdh_spi_driver);
163
164 return error;
165 }
166
167 /*
168 * module cleanup
169 */
bcmsdh_unregister_client_driver(void)170 void bcmsdh_unregister_client_driver(void)
171 {
172 sd_trace(("%s Enter\n", __FUNCTION__));
173 spi_unregister_driver(&bcmsdh_spi_driver);
174 }
175 #endif /* BCMSPI_ANDROID */
176
177 /* Register with Linux for interrupts */
178 int
spi_register_irq(sdioh_info_t * sd,uint irq)179 spi_register_irq(sdioh_info_t *sd, uint irq)
180 {
181 #ifndef BCMSPI_ANDROID
182 sd_trace(("Entering %s: irq == %d\n", __FUNCTION__, irq));
183 if (request_irq(irq, sdspi_isr, IRQF_SHARED, "bcmsdspi", sd) < 0) {
184 sd_err(("%s: request_irq() failed\n", __FUNCTION__));
185 return ERROR;
186 }
187 #endif /* !BCMSPI_ANDROID */
188 return SUCCESS;
189 }
190
191 /* Free Linux irq */
192 void
spi_free_irq(uint irq,sdioh_info_t * sd)193 spi_free_irq(uint irq, sdioh_info_t *sd)
194 {
195 #ifndef BCMSPI_ANDROID
196 free_irq(irq, sd);
197 #endif /* !BCMSPI_ANDROID */
198 }
199
200 /* Map Host controller registers */
201 #ifndef BCMSPI_ANDROID
202 uint32 *
spi_reg_map(osl_t * osh,uintptr addr,int size)203 spi_reg_map(osl_t *osh, uintptr addr, int size)
204 {
205 return (uint32 *)REG_MAP(addr, size);
206 }
207
208 void
spi_reg_unmap(osl_t * osh,uintptr addr,int size)209 spi_reg_unmap(osl_t *osh, uintptr addr, int size)
210 {
211 REG_UNMAP((void*)(uintptr)addr);
212 }
213 #endif /* !BCMSPI_ANDROID */
214
215 int
spi_osinit(sdioh_info_t * sd)216 spi_osinit(sdioh_info_t *sd)
217 {
218 struct sdos_info *sdos;
219
220 sdos = (struct sdos_info*)MALLOC(sd->osh, sizeof(struct sdos_info));
221 sd->sdos_info = (void*)sdos;
222 if (sdos == NULL)
223 return BCME_NOMEM;
224
225 sdos->sd = sd;
226 spin_lock_init(&sdos->lock);
227 #ifndef BCMSPI_ANDROID
228 init_waitqueue_head(&sdos->intr_wait_queue);
229 #endif /* !BCMSPI_ANDROID */
230 return BCME_OK;
231 }
232
233 void
spi_osfree(sdioh_info_t * sd)234 spi_osfree(sdioh_info_t *sd)
235 {
236 struct sdos_info *sdos;
237 ASSERT(sd && sd->sdos_info);
238
239 sdos = (struct sdos_info *)sd->sdos_info;
240 MFREE(sd->osh, sdos, sizeof(struct sdos_info));
241 }
242
243 /* Interrupt enable/disable */
244 SDIOH_API_RC
sdioh_interrupt_set(sdioh_info_t * sd,bool enable)245 sdioh_interrupt_set(sdioh_info_t *sd, bool enable)
246 {
247 ulong flags;
248 struct sdos_info *sdos;
249
250 sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling"));
251
252 sdos = (struct sdos_info *)sd->sdos_info;
253 ASSERT(sdos);
254
255 if (!(sd->host_init_done && sd->card_init_done)) {
256 sd_err(("%s: Card & Host are not initted - bailing\n", __FUNCTION__));
257 return SDIOH_API_RC_FAIL;
258 }
259
260 #ifndef BCMSPI_ANDROID
261 if (enable && !(sd->intr_handler && sd->intr_handler_arg)) {
262 sd_err(("%s: no handler registered, will not enable\n", __FUNCTION__));
263 return SDIOH_API_RC_FAIL;
264 }
265 #endif /* !BCMSPI_ANDROID */
266
267 /* Ensure atomicity for enable/disable calls */
268 spin_lock_irqsave(&sdos->lock, flags);
269
270 sd->client_intr_enabled = enable;
271 #ifndef BCMSPI_ANDROID
272 if (enable && !sd->lockcount)
273 spi_devintr_on(sd);
274 else
275 spi_devintr_off(sd);
276 #endif /* !BCMSPI_ANDROID */
277
278 spin_unlock_irqrestore(&sdos->lock, flags);
279
280 return SDIOH_API_RC_SUCCESS;
281 }
282
283 /* Protect against reentrancy (disable device interrupts while executing) */
284 void
spi_lock(sdioh_info_t * sd)285 spi_lock(sdioh_info_t *sd)
286 {
287 ulong flags;
288 struct sdos_info *sdos;
289
290 sdos = (struct sdos_info *)sd->sdos_info;
291 ASSERT(sdos);
292
293 sd_trace(("%s: %d\n", __FUNCTION__, sd->lockcount));
294
295 spin_lock_irqsave(&sdos->lock, flags);
296 if (sd->lockcount) {
297 sd_err(("%s: Already locked!\n", __FUNCTION__));
298 ASSERT(sd->lockcount == 0);
299 }
300 #ifdef BCMSPI_ANDROID
301 if (sd->client_intr_enabled)
302 bcmsdh_oob_intr_set(0);
303 #else
304 spi_devintr_off(sd);
305 #endif /* BCMSPI_ANDROID */
306 sd->lockcount++;
307 spin_unlock_irqrestore(&sdos->lock, flags);
308 }
309
310 /* Enable client interrupt */
311 void
spi_unlock(sdioh_info_t * sd)312 spi_unlock(sdioh_info_t *sd)
313 {
314 ulong flags;
315 struct sdos_info *sdos;
316
317 sd_trace(("%s: %d, %d\n", __FUNCTION__, sd->lockcount, sd->client_intr_enabled));
318 ASSERT(sd->lockcount > 0);
319
320 sdos = (struct sdos_info *)sd->sdos_info;
321 ASSERT(sdos);
322
323 spin_lock_irqsave(&sdos->lock, flags);
324 if (--sd->lockcount == 0 && sd->client_intr_enabled) {
325 #ifdef BCMSPI_ANDROID
326 bcmsdh_oob_intr_set(1);
327 #else
328 spi_devintr_on(sd);
329 #endif /* BCMSPI_ANDROID */
330 }
331 spin_unlock_irqrestore(&sdos->lock, flags);
332 }
333
334 #ifndef BCMSPI_ANDROID
spi_waitbits(sdioh_info_t * sd,bool yield)335 void spi_waitbits(sdioh_info_t *sd, bool yield)
336 {
337 #ifndef BCMSDYIELD
338 ASSERT(!yield);
339 #endif // endif
340 sd_trace(("%s: yield %d canblock %d\n",
341 __FUNCTION__, yield, BLOCKABLE()));
342
343 /* Clear the "interrupt happened" flag and last intrstatus */
344 sd->got_hcint = FALSE;
345
346 #ifdef BCMSDYIELD
347 if (yield && BLOCKABLE()) {
348 struct sdos_info *sdos;
349 sdos = (struct sdos_info *)sd->sdos_info;
350 /* Wait for the indication, the interrupt will be masked when the ISR fires. */
351 wait_event_interruptible(sdos->intr_wait_queue, (sd->got_hcint));
352 } else
353 #endif /* BCMSDYIELD */
354 {
355 spi_spinbits(sd);
356 }
357
358 }
359 #else /* !BCMSPI_ANDROID */
360 int bcmgspi_dump = 0; /* Set to dump complete trace of all SPI bus transactions */
361
362 static void
hexdump(char * pfx,unsigned char * msg,int msglen)363 hexdump(char *pfx, unsigned char *msg, int msglen)
364 {
365 int i, col;
366 char buf[80];
367
368 ASSERT(strlen(pfx) + 49 <= sizeof(buf));
369
370 col = 0;
371
372 for (i = 0; i < msglen; i++, col++) {
373 if (col % 16 == 0)
374 strcpy(buf, pfx);
375 sprintf(buf + strlen(buf), "%02x", msg[i]);
376 if ((col + 1) % 16 == 0)
377 printf("%s\n", buf);
378 else
379 sprintf(buf + strlen(buf), " ");
380 }
381
382 if (col % 16 != 0)
383 printf("%s\n", buf);
384 }
385
386 /* Send/Receive an SPI Packet */
387 void
spi_sendrecv(sdioh_info_t * sd,uint8 * msg_out,uint8 * msg_in,int msglen)388 spi_sendrecv(sdioh_info_t *sd, uint8 *msg_out, uint8 *msg_in, int msglen)
389 {
390 int write = 0;
391 int tx_len = 0;
392 struct spi_message msg;
393 struct spi_transfer t[2];
394
395 spi_message_init(&msg);
396 memset(t, 0, 2*sizeof(struct spi_transfer));
397
398 if (sd->wordlen == 2)
399 #if !(defined(SPI_PIO_RW_BIGENDIAN) && defined(SPI_PIO_32BIT_RW))
400 write = msg_out[2] & 0x80;
401 #else
402 write = msg_out[1] & 0x80;
403 #endif /* !(defined(SPI_PIO_RW_BIGENDIAN) && defined(SPI_PIO_32BIT_RW)) */
404 if (sd->wordlen == 4)
405 #if !(defined(SPI_PIO_RW_BIGENDIAN) && defined(SPI_PIO_32BIT_RW))
406 write = msg_out[0] & 0x80;
407 #else
408 write = msg_out[3] & 0x80;
409 #endif /* !(defined(SPI_PIO_RW_BIGENDIAN) && defined(SPI_PIO_32BIT_RW)) */
410
411 if (bcmgspi_dump) {
412 hexdump(" OUT: ", msg_out, msglen);
413 }
414
415 tx_len = write ? msglen-4 : 4;
416
417 sd_trace(("spi_sendrecv: %s, wordlen %d, cmd : 0x%02x 0x%02x 0x%02x 0x%02x\n",
418 write ? "WR" : "RD", sd->wordlen,
419 msg_out[0], msg_out[1], msg_out[2], msg_out[3]));
420
421 t[0].tx_buf = (char *)&msg_out[0];
422 t[0].rx_buf = 0;
423 t[0].len = tx_len;
424
425 spi_message_add_tail(&t[0], &msg);
426
427 t[1].rx_buf = (char *)&msg_in[tx_len];
428 t[1].tx_buf = 0;
429 t[1].len = msglen-tx_len;
430
431 spi_message_add_tail(&t[1], &msg);
432 spi_sync(gBCMSPI, &msg);
433
434 if (bcmgspi_dump) {
435 hexdump(" IN : ", msg_in, msglen);
436 }
437 }
438 #endif /* !BCMSPI_ANDROID */
439