📄 at91_spi.c
字号:
/* * High speed frame transmit/receive using the AT91RM9200's SPIs by pinning * down a range of virtual pages from user-space * to be able to DMA directly into/out of them, one page (4096 bytes) at a time. * * It is necessary because the pages the virtual pointers reference, might * not exist in memory (could be mapped to the zero-page, filemapped etc) * and DMA cannot trigger the MMU to force them in (and would have time * contraints making it impossible to wait for it anyway). * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */#include <linux/types.h>#include <linux/serial.h>#include <linux/module.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/kernel.h>#include <linux/pci.h>#include <linux/string.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/iobuf.h>#include <asm/io.h>#include <asm/uaccess.h> /* User space memory access functions */#include <asm/pgalloc.h>#include "at91_spi.h"/* driver for the AT91RM9200 SPI, NOTE: this driver does NOT support Variable Peripheral Select, see doc1768.pdf p. 362 */static struct spiinit { AT91PS_SPI pSpi; /* pointer to this SPI */ int id; /* id for this SPI */ AT91PS_PIO pio1; /* pio init if any for this SPI */ int pio_init1A; /* Peripheral A */ int pio_init1B; /* Peripheral B */ AT91PS_PIO pio2; /* pio init if any for this SPI */ int pio_init2A; /* Peripheral A */ int pio_init2B; /* Peripheral B */} spi_init[] = { { AT91C_BASE_SPI, AT91C_ID_SPI, AT91C_BASE_PIOA, AT91C_PA0_MISO | AT91C_PA1_MOSI | AT91C_PA2_SPCK | AT91C_PA3_NPCS0 | AT91C_PA4_NPCS1 | AT91C_PA5_NPCS2 | AT91C_PA6_NPCS3, 0, 0, 0, 0 },};struct spi_local at92_spi_priv = {0}; /* so combined usart/spi driver can get to us */static void at91_spi_int(int irq, void *private, struct pt_regs *regs){ unsigned int status; struct spi_packet *p_tail_int; AT91PS_SPI pSpi; struct spi_local *priv = private; pSpi = priv->pSpi; status = pSpi->SPI_SR & pSpi->SPI_IMR; /* start of receive portion */ if (status & AT91C_SPI_SPENDRX) { /* end of rx xfer? */ if (!pSpi->SPI_RCR) { /* done receiving both buffs? */ p_tail_int = priv->tx.p_tail_int; if (p_tail_int != priv->tx.p_head) { /* queue not empty? */ pSpi->SPI_RPR = p_tail_int->us_pr; /* take one entry from queue */ pSpi->SPI_RCR = p_tail_int->us_cr; /* set rcv cnt & prt same as xmit */ pSpi->SPI_TPR = p_tail_int->us_pr; pSpi->SPI_TCR = p_tail_int->us_cr; if (++p_tail_int >= &priv->tx.packet[AT91_SPI_MAX_PAGES]) p_tail_int = &priv->tx.packet[0]; if (p_tail_int != priv->tx.p_head) { /* queue not empty? */ pSpi->SPI_RNPR = p_tail_int->us_pr; /* take one entry from queue for NEXT */ pSpi->SPI_RNCR = p_tail_int->us_cr; pSpi->SPI_TNPR = p_tail_int->us_pr; pSpi->SPI_TNCR = p_tail_int->us_cr; if (++p_tail_int >= &priv->tx.packet[AT91_SPI_MAX_PAGES]) p_tail_int = &priv->tx.packet[0]; } priv->tx.p_tail_int = p_tail_int; } else { /* queue empty & done receiving */ pSpi->SPI_PTCR = AT91C_PDC_TXTDIS | AT91C_PDC_RXTDIS; /* disable xmit & recv xfer */ pSpi->SPI_IDR = AT91C_SPI_SPENDRX; /* stop interrupts */ wake_up_interruptible(&priv->tx.waitqueue); /* go wakeup the read */ } } else { /* still receiving */ p_tail_int = priv->tx.p_tail_int; if (p_tail_int != priv->tx.p_head) { /* queue not empty? */ pSpi->SPI_RNPR = p_tail_int->us_pr; pSpi->SPI_RNCR = p_tail_int->us_cr; pSpi->SPI_TNPR = p_tail_int->us_pr; pSpi->SPI_TNCR = p_tail_int->us_cr; if (++p_tail_int >= &priv->tx.packet[AT91_SPI_MAX_PAGES]) p_tail_int = &priv->tx.packet[0]; priv->tx.p_tail_int = p_tail_int; } else { /* queue empty & still receiving */ pSpi->SPI_RNCR = 0; /* NOTE: Must write zero or we get int's */ } } }}/* * Open */static int at91_spi_open(struct inode *inode, struct file *p_file){ struct spi_local *priv; int res; MOD_INC_USE_COUNT; p_file->private_data = priv = &at92_spi_priv; init_waitqueue_head(&priv->tx.waitqueue); spin_lock_init(&priv->tx.lock1); if (request_irq(priv->irq, at91_spi_int, 0, "HS AT91_SPI", priv)) { printk("at91_spi: unable to get IRQ %d\n", priv->irq); return -EAGAIN; } if ((res = alloc_kiovec(1, &priv->tx.kiobuf)) != 0) { free_irq(priv->irq, priv); printk("at91_spi: alloc_kiovec failed\n"); return res; } return 0;}/* * Close */static int at91_spi_close(struct inode *inode, struct file *p_file){ struct spi_local *priv = (struct spi_local *) p_file->private_data; AT91PS_SPI pSpi; pSpi = priv->pSpi; /* Disable all the SPI interrupts */ pSpi->SPI_IDR = AT91C_SPI_RDRF | AT91C_SPI_TDRE | AT91C_SPI_MODF | AT91C_SPI_OVRES; wake_up_interruptible(&priv->tx.waitqueue); /* go wakeup the write */ free_kiovec(1, &priv->tx.kiobuf); free_irq(priv->irq, priv); MOD_DEC_USE_COUNT; return 0;}/* * combine functionality for read & write */static ssize_t at91_spi_rd_wr(struct spi_frame *frame, int rd_wr, char *buf, size_t len){ struct kiobuf *kiobuf; int res, cntr, len_save, length, offset; struct spi_packet *p_head; /* Make a kiobuf that maps the entire length the reader has given us */ kiobuf = frame->kiobuf; if ((res = map_user_kiobuf(rd_wr, kiobuf, (unsigned long) buf, len))) { printk("at91_spi: map_user_kiobuf failed, return %d\n", res); return res; } /* At this point, the virtual area buf[0] -> buf[len-1] will * have corresponding pages mapped in physical memory and locked * until we unmap the kiobuf. They cannot be swapped out or moved * around. */ len_save = len; /* get copy for loop */ length = PAGE_SIZE - kiobuf->offset; /* length from offset to end of page */ length = len_save < length ? len_save : length; offset = kiobuf->offset; p_head = frame->p_head; for (cntr = 0; cntr < kiobuf->nr_pages; cntr++) { /* queue up one page or part page entry */ p_head->us_pr = __pa(page_address(kiobuf->maplist[cntr]) + offset); p_head->us_cr = length; len_save -= length; /* subtract this pages length */ length = len_save < PAGE_SIZE ? len_save : PAGE_SIZE; offset = 0; /* start all subsequent offsets at the beginning of the page */ if (++p_head >= &frame->packet[AT91_SPI_MAX_PAGES]) p_head = &frame->packet[0]; } frame->p_head = p_head; return 0;}static ssize_t at91_spi_write(struct file *p_file, const char *buf, size_t len, loff_t * ppos){ AT91PS_SPI pSpi; struct spi_local *priv; unsigned long flags; int res; long timeout; unsigned int *p_int; /* source */ unsigned char *p_char; /* user's start is dest */ priv = (struct spi_local *) p_file->private_data; pSpi = priv->pSpi; flush_cache_range(current->mm, (unsigned long) buf, (unsigned long) buf + len); /* flush cache BEFORE send is done */ if (!(priv->flags & AT91_SPI_IOCTL_MODE)) /* we're not in the ioctl mode of usart? */ { if ((res = at91_spi_rd_wr(&priv->tx, WRITE, (char *) buf, len)) != 0) return res; pSpi->SPI_PTCR = AT91C_PDC_TXTEN | AT91C_PDC_RXTEN; /* enable xmit & recv xfer */ } else { if (len <= AT91_SPI_OTHER_MAX) /* check for max */ { p_char = buf; /* get user's start */ p_int = &priv->spi_ao_packet.other[0]; /* dest */ while (p_int < &priv->spi_ao_packet.other[len]) *p_int++ = *p_char++ | priv->other_pcs; /* or in PCS */ priv->other_send_cnt = len; /* give length, MUST be done last */ } else { printk(KERN_WARNING "SPI: write: length too long\n"); return -EINVAL; /* error, length too long */ } } spin_lock_irqsave(&priv->tx.lock1, flags); /* stop int's else we wakeup b4 we sleep */ if (!(priv->flags & AT91_SPI_IOCTL_MODE)) /* we're not in the ioctl mode of usart? */ pSpi->SPI_IER = AT91C_SPI_SPENDRX; /* End of Receiver Transfer Interrupt */#define SPI_TIMEOUT (HZ / 10) /* how long we wait for a wakeup */ timeout = interruptible_sleep_on_timeout(&priv->tx.waitqueue, SPI_TIMEOUT); spin_unlock_irqrestore(&priv->tx.lock1, flags); if (timeout == 0) { // timeout without keypad input printk(KERN_WARNING "SPI: time out on device\n"); len = -EAGAIN; } if (priv->flags & AT91_SPI_IOCTL_MODE) /* we're in the ioctl mode of usart? */ { p_int = &priv->spi_ao_packet.other[0]; /* get user's start */ p_char = buf; /* dest */ while (p_int < &priv->spi_ao_packet.other[len]) *p_char++ = *p_int++; /* put back into user buffer */ } flush_cache_range(current->mm, (unsigned long) buf, (unsigned long) buf + len); /* flush cache AFTER receive is done */ if (!(priv->flags & AT91_SPI_IOCTL_MODE)) /* we're not in the ioctl mode of usart? */ unmap_kiobuf(priv->tx.kiobuf); /* The unlock_kiobuf is implicit here */ return len;}static AT91_REG *spi_csr_lut[16]= { AT91C_SPI_CSR0, AT91C_SPI_CSR1, AT91C_SPI_CSR0, AT91C_SPI_CSR2, AT91C_SPI_CSR0, AT91C_SPI_CSR1, AT91C_SPI_CSR0, AT91C_SPI_CSR3, AT91C_SPI_CSR0, AT91C_SPI_CSR1, AT91C_SPI_CSR0, AT91C_SPI_CSR2, AT91C_SPI_CSR0, AT91C_SPI_CSR1, AT91C_SPI_CSR0, AT91C_SPI_CSR0, };static int at91_spi_ioctl(struct inode *inode, struct file *p_file, unsigned int cmd, unsigned long arg){ AT91PS_SPI pSpi; struct spi_local *priv; struct serial_struct sio; priv = (struct spi_local *) p_file->private_data; pSpi = priv->pSpi; switch (cmd) { case TIOCSSERIAL: if (copy_from_user(&sio, (struct serial_struct *) arg, sizeof(struct serial_struct))) return -EFAULT; if (!(priv->flags & AT91_SPI_IOCTL_MODE)) /* we're not in the ioctl mode of usart? */ { *spi_csr_lut[(sio.flags >> 16) & 0x0f] = sio.custom_divisor; /* set new baud rate */ pSpi->SPI_MR = sio.flags | AT91C_SPI_MODFDIS; /* set the new mode */ } else priv->other_pcs = sio.flags & AT91C_SPI_PCS; /* set new PCS */ return 0; default: return -EINVAL; }}static struct file_operations at91_spi_fops = { owner:THIS_MODULE, open:at91_spi_open, release:at91_spi_close, write:at91_spi_write, ioctl:at91_spi_ioctl,};static int __init at91_spi_init(void){ struct spi_local *priv; struct spiinit *p_spi_init; AT91PS_SPI pSpi; int res; /* register char device */ res = register_chrdev(AT91_SPI_MAJOR, "at91_spi", &at91_spi_fops); if (res < 0) { printk(KERN_ERR "at91_spi: Couldn't get a major number.\n"); return res; } priv = &at92_spi_priv; p_spi_init = &spi_init[0]; *AT91C_PMC_PCER = 1 << p_spi_init->id; /* enable clock */ priv->pSpi = p_spi_init->pSpi; /* save this SPI pointer */ priv->irq = p_spi_init->id; /* save this ID */ priv->tx.p_head = priv->tx.p_tail_int = &priv->tx.packet[0]; if (p_spi_init->pio1) { /* pio init if any for this SPI */ p_spi_init->pio1->PIO_ASR = p_spi_init->pio_init1A; p_spi_init->pio1->PIO_BSR = p_spi_init->pio_init1B; p_spi_init->pio1->PIO_PDR = p_spi_init->pio_init1A | p_spi_init->pio_init1B; if (p_spi_init->pio2) { /* pio init if any for this SPI */ p_spi_init->pio1->PIO_ASR = p_spi_init->pio_init2A; p_spi_init->pio1->PIO_BSR = p_spi_init->pio_init2B; p_spi_init->pio1->PIO_PDR = p_spi_init->pio_init2A | p_spi_init-> pio_init2B; } } pSpi = priv->pSpi; /* Disable all the SPI interrupts */ pSpi->SPI_IDR = AT91C_SPI_RDRF | AT91C_SPI_TDRE | AT91C_SPI_MODF | AT91C_SPI_OVRES; pSpi->SPI_CR = AT91C_SPI_SWRST; /* hit the reset */ pSpi->SPI_PTCR = AT91C_PDC_TXTDIS | AT91C_PDC_RXTDIS; /* disble xmit & rcvr xfer */ pSpi->SPI_RCR = 0; /* clear out receive counts */ pSpi->SPI_RNCR = 0; pSpi->SPI_TCR = 0; /* clear out xmit counts */ pSpi->SPI_TNCR = 0; /* Chip Select 0 : NPCS0 %1110 */ /* from asaya@atmel.fr: I have talked to the people who designed the IP and it seems there is a small bug in it. When you have the MODF enabled, the SPI doesn't send anything when using CS0 and that's why when you switch to CS0 in variable mode the SPI is stuck. */ pSpi->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS | AT91C_SPI_PCS0; /* default mode, NOTE: see Errata #14 */ pSpi->SPI_CSR[0] = AT91C_SPI_CPOL | AT91C_SPI_BITS_8 | (AT91C_SPI_DELAY_BEFORE_SCK << 16) | ((AT91_MASTER_CLOCK / (2 * AT91C_SPI_DEFAULT_BUAD)) << 8); /* default */ pSpi->SPI_CR = AT91C_SPI_SPIEN; /* enable all */ printk("AT91 SPI driver loaded\n"); return res;}static void at91_spi_exit(void){ struct spi_local *priv; AT91PS_SPI pSpi; priv = &at92_spi_priv; pSpi = priv->pSpi; pSpi->SPI_CR = AT91C_SPI_SPIDIS; pSpi->SPI_PTCR = AT91C_PDC_TXTDIS | AT91C_PDC_RXTDIS; /* disble xmit & rcvr xfer */ *AT91C_PMC_PCDR = 1 << spi_init[0].id; /* disable clock */ unregister_chrdev(AT91_SPI_MAJOR, "at91_spi");}module_init(at91_spi_init);module_exit(at91_spi_exit);MODULE_AUTHOR("Rick Bronson");MODULE_DESCRIPTION("AT91 SPI kiobuf Driver");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -