/***************************************************************************** * * MPC8xx CPM SPI interface. * in progress, can't get microcode patch to work * Copyright (c) 2000 Embedded Planet . * * 8/00 - microcode patch works so SPI can work with Ethernet * just the basics are here for other drivers * to use spi - tpanel, etc * Copyright (c) 2000 Montavista Software Inc. * * This file is subject to the terms and conditions of the GNU General * Public License. See the file "COPYING" in the main directory of * this archive for more details *****************************************************************************/ #include #include #include #include #include #include #include #include #include "commproc.h" #define CPM_SPI_CDEV_MAJOR 96 #define MAX_RECEIVE_SIZE 4 #define MAX_TRANSMIT_SIZE 1024 void cpm_spi_init(void); static int cpm_spi_open(struct inode *, struct file *); static int cpm_spi_close(struct inode *, struct file *); extern ssize_t cpm_spi_read(struct file *, char *, size_t, loff_t *); extern ssize_t cpm_spi_write(struct file *, char *, size_t, loff_t *); static int cpm_spi_ioctl(struct inode *, struct file *, uint, ulong); static void cpm_spi_interrupt(void *); /* Wait queue used while waiting for interrupt */ static struct wait_queue *spi_interrupt_wait; volatile spi_t *spp; volatile cpm8xx_t *cp; static struct file_operations cpm_spi_fops = { NULL, /* lseek */ cpm_spi_read, /* read */ cpm_spi_write, /* write */ NULL, /* readdir */ NULL, /* select */ cpm_spi_ioctl, /* ioctl */ NULL, /* mmap */ cpm_spi_open, /* open */ cpm_spi_close, /* close */ NULL /* fsync */ }; void cpm_spi_init() { uint mem_addr, dp_addr, reloc; volatile cbd_t *bdp; volatile immap_t *immap; cp = cpmp; /* Get pointer to Communication Processor */ immap = (immap_t *)IMAP_ADDR; /* and to internal registers */ spp = (spi_t *)&cp->cp_dparam[PROFF_SPI]; /* Check for and use a microcode relocation patch. */ if ((reloc = spp->spi_rpbase)) { printk("Microcode relocation patch used\n"); spp = (spi_t *)&cp->cp_dpmem[spp->spi_rpbase]; } /* Initialize Port B SPI pins. */ cp->cp_pbpar |= 0x0000000F; cp->cp_pbdir |= 0x0000000A; cp->cp_pbodr &= ~0x0000000F; /* Allocate space for one transmit and one receive buffer * descriptor in the DP ram. */ dp_addr = m8xx_cpm_dpalloc(sizeof(cbd_t) * 2); /* Set up the SPI parameters in the parameter ram. */ spp->spi_rbase = dp_addr + sizeof(cbd_t); spp->spi_tbase = dp_addr; /* Normal Operation (Big Endian) */ spp->spi_rfcr = 0x18;// spp->spi_tfcr = 0x18;//SMC_EB; /* Initialize required parameters if using microcode patch */ if(reloc) { spp->spi_rbptr = spp->spi_rbase; spp->spi_tbptr = spp->spi_tbase; spp->spi_rstate = 0; spp->spi_tstate = 0; } else { /* Initialize Tx/Rx parameters. We can only do this if we are not using * the microcode patch */ cp->cp_cpcr = mk_cr_cmd(CPM_CR_CH_SPI, CPM_CR_INIT_TRX) | CPM_CR_FLG; while (cp->cp_cpcr & CPM_CR_FLG); } immap->im_siu_conf.sc_sdcr = 0x00000001; /* Set maximum receive size. */ spp->spi_mrblr = MAX_RECEIVE_SIZE; /* Set the buffer address. */ mem_addr = m8xx_cpm_hostalloc(MAX_RECEIVE_SIZE); bdp = (cbd_t *)&cp->cp_dpmem[spp->spi_rbase]; bdp->cbd_bufaddr = __pa(mem_addr); bdp->cbd_sc = BD_SC_WRAP; mem_addr = m8xx_cpm_hostalloc(MAX_TRANSMIT_SIZE); bdp = (cbd_t *)&cp->cp_dpmem[spp->spi_tbase]; bdp->cbd_bufaddr = __pa(mem_addr); bdp->cbd_sc = BD_SC_WRAP|BD_SC_LAST; /* Clear the event register */ cp->cp_spie = 0xff; /* Set interupts for all events */ cp->cp_spim = 0x37; /* Install interrupt handler. */ cpm_install_handler(CPMVEC_SPI, cpm_spi_interrupt, (void *)spp); /* Allow the serial peripheral interface to generate a system interrupt */ immap->im_cpic.cpic_cicr |= CICR_IEN; /* * Initialize SPMODE * * Settings: * Reverse Data -- Normal Operation (TX/RX MSB first) * Master/Slave Mode -- Slave Mode * Character Length -- 8 Bits */ // MSB First cp->cp_spmode = SPMODE_REV; // Character length (8-bits [0x0111]) cp->cp_spmode |= (0x7 << 4); // Enable the SPI port cp->cp_spmode |= SPMODE_EN; printk("SPMODE = 0x%x\n", cp->cp_spmode); // Setupt memory map for status and control registers //printk("BR6 = %x\n", immap->im_memctl.memc_br6); //printk("OR6 = %x\n", immap->im_memctl.memc_or6); // Register the SPI driver if (register_chrdev(CPM_SPI_CDEV_MAJOR,"spi",&cpm_spi_fops)) { printk("Unable to get major %d for SPI devs\n", CPM_SPI_CDEV_MAJOR); } printk("SPI Drivers Loaded!\n"); } static int cpm_spi_open(struct inode *ip, struct file *fp) { printk("Opening spi device\n"); /* Check the minor number to make sure it is 0 */ if( MINOR(ip->i_rdev) > 0) { printk("Wrong minor number... try again\n"); return -ENODEV; } /* Set pointer to file operations struct */ fp->f_op = &cpm_spi_fops; return 0; } static int cpm_spi_close(struct inode *ip, struct file *fp) { return 0; } ssize_t cpm_spi_read(struct file *file, char *buf, size_t count, loff_t *ppos) { volatile cbd_t *tbdf, *rbdf; unsigned long flags; tbdf = (cbd_t *)&cp->cp_dpmem[spp->spi_tbase]; rbdf = (cbd_t *)&cp->cp_dpmem[spp->spi_rbase]; memset(__va(tbdf->cbd_bufaddr),0,count); tbdf->cbd_datlen = count; tbdf->cbd_sc |= BD_SC_READY; rbdf->cbd_sc |= BD_SC_EMPTY; save_flags(flags); cli(); cp->cp_spcom = 0x80; /* Start Transmit */ /* Wait for SPI transfer. */ while(rbdf->cbd_sc & BD_SC_EMPTY); restore_flags(flags); if (signal_pending(current)) return -ERESTARTSYS; if (rbdf->cbd_sc & BD_SC_EMPTY) printk("SPI read complete but rbuf empty\n"); memcpy(buf,__va(rbdf->cbd_bufaddr), 2); return count; } ssize_t cpm_spi_write(struct file *file, char *buf, size_t count, loff_t *ppos) { volatile cbd_t *tbdf, *rbdf; unsigned long flags; printk("Starting the SPI write!!!\n"); if(count > MAX_TRANSMIT_SIZE) { return -ERESTARTSYS; } tbdf = (cbd_t *)&cp->cp_dpmem[spp->spi_tbase]; rbdf = (cbd_t *)&cp->cp_dpmem[spp->spi_rbase]; memcpy(__va(tbdf->cbd_bufaddr), buf, count); tbdf->cbd_datlen = MAX_TRANSMIT_SIZE; tbdf->cbd_sc = BD_SC_READY|BD_SC_WRAP|BD_SC_LAST|BD_SC_INTRPT; printk("The event register before transmission 0x%x\n", cp->cp_spie); /* Start the transfer */ cp->cp_spcom = 0x80; // Turn on clock printk("Waiting for SPI transfer\n"); /* Wait for SPI transmit interrupt or time out (20 seconds).*/ if(interruptible_sleep_on_timeout(&spi_interrupt_wait, 20*HZ)<=0) { printk("SPI write: Time out!\n"); } // Turn off clock printk("The event register after transmission 0x%x\n", cp->cp_spie); printk("Finshed SPI transfer\n"); if (signal_pending(current)) { return -ERESTARTSYS; } /* check for errors in tx buffer */ if(tbdf->cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL)) { switch(tbdf->cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL)) { case BD_SC_READY: printk("SPI write complete but tx_buf ready!\n"); break; case BD_SC_NAK: printk("SPI write: no acknowledge!\n"); break; case BD_SC_UN: printk("SPI write: underrun!\n"); break; case BD_SC_CL: printk("SPI write: collision!\n"); break; } } printk("Finished the SPI write\n"); return count; } static int cpm_spi_ioctl(struct inode *ip, struct file *fp, u_int cmd, ulong arg) { int retval = 0; switch(cmd) { /* Dummy IOCTL Command. */ case 1: break; default: retval = -ENOIOCTLCMD; } return retval; } static void cpm_spi_interrupt(void *dev_id) { printk("Interrupt Received %x\n", cp->cp_spie); /* Clear Interrupt */ cp->cp_spie = ~0; /* Wake up process in wait queue */ wake_up_interruptible(&spi_interrupt_wait); }