* A dirty 8xx SPI driver.
@ 2001-12-18 15:58 Navin Boppuri
2001-12-18 19:39 ` Re:8xx SPI drivers Kevin Fry
0 siblings, 1 reply; 2+ messages in thread
From: Navin Boppuri @ 2001-12-18 15:58 UTC (permalink / raw)
To: Kevin Fry; +Cc: linuxppc-embedded
[-- Attachment #1: Type: text/plain, Size: 1800 bytes --]
Hello Kevin,
I am sending you a simple spi driver for the 8xx machine that I
developed a long time back. Please dont send hate mails to me cause the
this was a driver that I wrote to just get something done fast. There
are no proper defines for anything. There is a small HOWTO for this
driver that I beleive will help you get going. Please note that this
driver was a kernel level driver used with the 2.2.14 kernel. But with a
little tweaking, you should be able to make it make it work for the new
kernel. Also, make it a loadable kernel module if you are interested.
Anyone and everyone is free to clean up the crappy code in the driver
and put in nice defines to make the code readable. You may also put in
IOCTL's for all the configuration options for the SPI controller.
I have been busy with other things and have not had the time to clean up
this driver. Please note that this driver was my first Linux device
driver, so please be forgiving.
At the end, if you beleive that it is easier to write a driver from
scratch, get rid of it. But I beleive that this peice of code will
surely be helpful for someone trying to get things going with the SPI.
Have fun. :):)
Navin.
P.S ( This driver was written to interface the SPI to an UART. Ignore
all the UART specific defines).
-----Original Message-----
From: Kevin Fry [mailto:kevin@carts.com]
Sent: Monday, December 17, 2001 6:49 PM
To: linuxppc-embedded@lists.linuxppc.org
Subject: SPI driver?
Is there an MPC8260 SPI driver out there for Linux? It wouldn't be
terribly hard to write one on my own, but why re-invent the wheel?
A link to an 8xx driver would be almost as good. The only one I've found
so far has been SPI over a PC's parallel port. heh
Thanks
Kevin Fry
[-- Attachment #2: spi.c --]
[-- Type: application/octet-stream, Size: 12300 bytes --]
/*
MPC8xx CPM SPI interface.
Copyright (c) 2001 Navin Boppuri/Prashant Patel
(nboppuri@trinetcommunication.com/pmpatel@trinetcommunication.com)
This interface requires the microcode patches.
Helper functions for memdump put in by Ralph Nichols (ralphn@trinetcommunication.com)
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/mpc8xx.h>
#include <asm/irq.h>
#include <asm/8xx_immap.h>
#include <asm/imd.h> /* board specific include file, replace with your board include */
#include "commproc.h"
/* Helper functions to peek into tx and rx buffers */
char quickhex(int i)
{
if(i >= 10 ) return i - 10 + 'A';
return i + '0';
}
int quickisprint(int c)
{
if( c >= 'A' && c <= 'Z' ) return 1;
if( c >= 'a' && c <= 'z' ) return 1;
if( c >= '0' && c <= '9' ) return 1;
return 0;
}
void memdump(void *pv, int num)
{
int i;
unsigned char* pc = (unsigned char*)pv;
for(i = 0; i < num; i++ )
printk("%c%c ", quickhex(pc[i] >> 4), quickhex(pc[i] & 0x0f));
printk("\t");
for(i = 0; i < num; i++ )
printk("%c", quickisprint(pc[i]) ? pc[i] : '.');
printk("\n");
}
/* This is the SPI device number, but I use the minors to indicate
* the SPI device address.
*/
#define CPM_SPI_CDEV_MAJOR 89
void cpm_spi_init(void);
static int cpm_spi_open(struct inode *, struct file *);
static int cpm_spi_close(struct inode *, struct file *);
static ssize_t cpm_spi_write(struct file *, char *, size_t, loff_t *);
static ssize_t cpm_spi_read(struct file *, char *, size_t, loff_t *);
static void cpm_spi_interrupt(void *);
static struct file_operations cpm_spi_fops = {
NULL, /* lseek */
cpm_spi_read, /* read */
cpm_spi_write, /* write */
NULL, /* readdir */
NULL, /* poll */
NULL, /* ioctl */
NULL, /* mmap */
cpm_spi_open, /* open */
NULL, /* flush */
cpm_spi_close, /* close */
};
/* Only one user at a time.
*/
static int cpm_spi_flags;
#define CPM_SPI_OPEN 0x01
static dev_t spi_device;
static struct wait_queue *spi_wait;
static ushort r_tbase, r_rbase;
static cbd_t *tbdf, *rbdf;
/* RX and TX buffers */
static uint txbuffer;
static uint rxbuffer;
void
cpm_spi_init()
{
uint dp_addr, reloc;
volatile spi_t *spi;
volatile immap_t *immap;
volatile cpic8xx_t *cpi;
volatile sysconf8xx_t *sys;
volatile cpm8xx_t *cp;
int i;
immap = (immap_t *)IMAP_ADDR; /* and to internal registers */
cpi = (cpic8xx_t*)&(((immap_t*)IMAP_ADDR)->im_cpic); //cpm interrupt controller
sys = (sysconf8xx_t*)&(((immap_t*)IMAP_ADDR)->im_siu_conf); //cpm interrupt controller
cp = cpmp; /* Get pointer to Communication Processor */
spi = (spi_t *)&cp->cp_dparam[PROFF_SPI];
if ((reloc = spi->spi_rpbase))
{
spi = (spi_t *)&cp->cp_dpmem[spi->spi_rpbase];
printk(" MICROCODE RELOCATION PATCH \n");
}
/* Initialize the parameter ram.
* We need to make sure many things are initialized to zero,
* especially in the case of a microcode patch.
*/
spi->spi_rstate = 0;
spi->spi_rdp = 0;
spi->spi_rbptr = 0;
spi->spi_rbc = 0;
spi->spi_rxtmp = 0;
spi->spi_tstate = 0;
spi->spi_tdp = 0;
spi->spi_tbptr = 0;
spi->spi_tbc = 0;
spi->spi_txtmp = 0;
/* 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 IIC parameters in the parameter ram.
*/
spi->spi_rbase = r_rbase = dp_addr;
spi->spi_tbase = r_tbase = dp_addr + sizeof(cbd_t);
/***********IMPORTANT******************/
/* Setting transmit and receive buffer descriptor
pointers intially to rbase and rbase. Only the
microcode patches documentation talks about initializing
this pointer. This is missing from the sample I2C driver.
If you dont initialize these pointers, the kernel hangs. */
spi->spi_rbptr = spi->spi_rbase;
spi->spi_tbptr = spi->spi_tbase;
/* Set to big endian.*/
spi->spi_tfcr = SMC_EB;
spi->spi_rfcr = SMC_EB;
/* Set maximum receive size.*/
spi->spi_mrblr = 64;
/* Setting CPCR */
cp->cp_cpcr |= 0x51;
immap->im_siu_conf.sc_sdcr = 0x0001;/* sets SDMA configuration register */
cpi->cpic_cicr |= 0x1f80; /* enabled cpm interrupts */
cp->cp_spie = 0xff; //clear all spi events
cp->cp_spim = 0x37; //mask all SPI events, you need this for spi interrupt stuff, if used
/* My PortB setting */
/* These setting may be different for you.
Refer example 16-443 MPC823 Manual*/
cp->cp_pbodr &= ~(0x000e);
cp->cp_pbdir |= 0x000f;
cp->cp_pbpar |= 0x000e;
cp->cp_pbodr &= ~(0x1230);
cp->cp_pbdir |= 0x1230;
cp->cp_pbpar &= ~(0x1230);
cp->cp_pbdat |= 0x1231;
/* tx and rx buffer descriptors */
tbdf = (cbd_t *)&cp->cp_dpmem[r_tbase];
rbdf = (cbd_t *)&cp->cp_dpmem[r_rbase];
/* initialize tx and tx bd's */
tbdf->cbd_sc &= ~BD_SC_READY;
rbdf->cbd_sc &= ~BD_SC_EMPTY;
/* Allocate memory as required. Presently allocates only 2 bytes per buffer*/
rxbuffer = m8xx_cpm_hostalloc(2); /* Memory alloc for receive buffer */
txbuffer = m8xx_cpm_hostalloc(2); /* Memory alloc for receive buffer */
/* Set the bd's rx and tx buffer address pointers */
tbdf->cbd_bufaddr = __pa(txbuffer);
rbdf->cbd_bufaddr = __pa(rxbuffer);
/* install interrupt handler if using interrupts */
cpm_install_handler(CPMVEC_SPI, cpm_spi_interrupt, (void *)spi);
/* register spi device */
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("Succesfully registered spi 89 \n");
return;
}
/* Open does not have to do much, just make sure only one user.
*/
static int
cpm_spi_open(struct inode *ip, struct file *fp)
{
/* We allow only one user of the device.
*/
if (cpm_spi_flags & CPM_SPI_OPEN) /* device is busy */
return(EBUSY);
fp->f_op = &cpm_spi_fops;
spi_device = MINOR(ip->i_rdev);
cpm_spi_flags |= CPM_SPI_OPEN;
return(0);
}
static int
cpm_spi_close(struct inode *ip, struct file *fp)
{
volatile cpm8xx_t *cp;
cp = cpmp;
/* Shut down SPI.
*/
cp->cp_spmode = 0;
cp->cp_spie = 0xff;
cp->cp_spim = 0;
cpm_spi_flags &= ~CPM_SPI_OPEN;
return(0);
}
/* write is exactly like read.
spi does read and write simultaneously */
static ssize_t
cpm_spi_write(struct file *file, char *buf, size_t count, loff_t *ppos)
{
uint dp_addr, reloc;
ushort *receive_data;
volatile immap_t *immap;
volatile cpic8xx_t *cpi;
volatile cpm8xx_t *cp;
unsigned long flags;
immap = (immap_t *)IMAP_ADDR; /* and to internal registers */
cpi = (cpic8xx_t*)&(((immap_t*)IMAP_ADDR)->im_cpic); //cpm interrupt controller
cp = cpmp; /* Get pointer to Communication Processor */
/* Initialize buffer memory to empty */
memset((void*)txbuffer,0,2);
memset((void*)rxbuffer,0,2);
memcpy((void*)txbuffer,(void*)buf,count);/* copy transmit data from user space to tx buffer */
//memdump((void*)txbuffer,2);/* dump of txbuffer before transmit */
/*Setting tx bd status and data length*/
tbdf->cbd_sc = BD_SC_READY | BD_SC_LAST | BD_SC_WRAP;
tbdf->cbd_datlen = 2;
//memdump((void*)rxbuffer,2);/* dump of rxbuffer before transmit */
/*Setting rx bd status and data length*/
rbdf->cbd_sc = BD_SC_EMPTY | BD_SC_WRAP;
rbdf->cbd_datlen = 0; /* we set rx datalength to 0 since we are not reading here */
/* CS for device (in my case, a uart) */
cp->cp_pbdat &= ~(0x0010);
cp->cp_spmode = 0x0fff; /* spi mode setting */
cp->cp_spie = 0xff; //clear all spi events
cp->cp_spim = 0x37; //mask all SPI events
/* start spi transfer */
cp->cp_spcom |= 0x80;
/* Need this delay, otherwise hangs!!
Delay not calculated exactly. */
udelay(1000);
/* wait till transfer is done. interrupts can be used instead.
set tbdf->cbd_sc and rbdf->cb_sc to include BD_SC_INTRPT for interrupts.
*/
while((tbdf->cbd_sc & 0x8000) && (rbdf->cbd_sc & 0x8000));
cp->cp_pbdat |= 0x0010; /* disable uart chip select */
/* Ignore received data */
//memdump((void*)rxbuffer,2);/* dump of rxbuffer after transmit */
return count;
}
/* read is exactly like write.
spi does read and write simultaneously.
It's not exactly simultaneous. I've noticed that transmit is done
first and then receive.*/
static ssize_t
cpm_spi_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
uint dp_addr, reloc;
ushort *receive_data;
volatile immap_t *immap;
volatile cpic8xx_t *cpi;
volatile cpm8xx_t *cp;
unsigned long flags;
immap = (immap_t *)IMAP_ADDR; /* and to internal registers */
cpi = (cpic8xx_t*)&(((immap_t*)IMAP_ADDR)->im_cpic); //cpm interrupt controller
cp = cpmp; /* Get pointer to Communication Processor */
/* Initialize buffer memory to empty */
memset((void*)txbuffer,0,2);
memset((void*)rxbuffer,0,2);
//memdump((void*)txbuffer,2);/* dump of txbuffer before transmit */
/*Setting tx bd status and data length*/
tbdf->cbd_sc = BD_SC_READY | BD_SC_LAST | BD_SC_WRAP;
tbdf->cbd_datlen = 0; /* we set tx datalenght to 0 here since we do not write here */
/*Setting rx bd status and data length*/
//memdump((void*)rxbuffer,2);/* dump of rxbuffer before transmit */
rbdf->cbd_sc = BD_SC_EMPTY | BD_SC_WRAP;
rbdf->cbd_datlen = 2;
/* CS for device (in my case, a uart) */
cp->cp_pbdat &= ~(0x0010);
cp->cp_spmode = 0x0fff; /* spi mode setting */
cp->cp_spie = 0xff; //clear all spi events
cp->cp_spim = 0x37; //mask all SPI events
/* start spi transfer */
cp->cp_spcom |= 0x80;
/* Need this delay, otherwise hangs!!
Delay not calculated excatly. */
udelay(1000);
/* wait till transfer is done. interrupts can be used instead.
set tbdf->cbd_sc and rbdf->cb_sc to include BD_SC_INTRPT for interrupts.
*/
while((tbdf->cbd_sc & 0x8000) && (rbdf->cbd_sc & 0x8000));
cp->cp_pbdat |= 0x0010; /* disable uart chip select */
//memdump((void*)rxbuffer,2);/* dump of rxbuffer after transmit */
/* copy received 2 bytes to user space */
/***********
please note that the data in rxbuffer is in big endian format.
you will have to flip it.
*************/
copy_to_user((void*)buf,(void*)rxbuffer,count);
return count;
}
/* Interrupt handler in case you use spi interrupts. I have tried using the interrupts
and they work just fine. You can use his handler to detect */
static void
cpm_spi_interrupt(void *dev_id)
{
volatile spi_t *spi;
volatile cpm8xx_t *cp;
volatile sysconf8xx_t *sys;
volatile cpic8xx_t *cpi;
cp = cpmp; /* Get pointer to Communication Processor */
cpi = (cpic8xx_t*)&(((immap_t*)IMAP_ADDR)->im_cpic); //cpm interrupt controller
sys = (sysconf8xx_t*)&(((immap_t*)IMAP_ADDR)->im_siu_conf); //cpm interrupt controller
spi = (spi_t *)dev_id;
//printk("Spi event reg. 0x%x\n",cp->cp_spie);
/* reset spi event register and mode */
cp->cp_spie = 0xff;
cp->cp_spmode = 0x00;
cpi->cpic_cisr = 1 << 5; /* clear spi interrupt mask */
wake_up_interruptible(&spi_wait);
}
[-- Attachment #3: spinotes.txt --]
[-- Type: text/plain, Size: 3034 bytes --]
Notes for MPC8xx CPM SPI interface driver in Linux
--------------------------------------------------
This driver is an interface for the SPI controller in MPC8xx. The driver is written to work with the
microcode patches to correct the parameter ram problems. The driver supports basic init,open,close,read
and write functions. The read and write are almost similar since the SPI reads and writes data
simultaneously. But I've noticed that write takes place before read. The driver code may be modified and
used to interface SPI to external devices. In my case, I am using the SPI to communicate with 3 uarts.
The driver code was inspired by a previous I2C driver by Dan Malek. A custom SPI interface was also
contributed by Tobias Otto-Adamczak.
Please refer to Motorola documentation of microcode relocation patches to understand the relocation
process. I would like to point you to once specific task required to make the microcode relocation work. The
rbptr (pointer to rx buffer descriptor) and tbptr (pointer tx buffer descriptor) need to be initialized to
rbase and rbase values. The MPC823 manual suggests not to touch the rbptr and tbptr values. But the
documentation on microcode patches requires us to initialize these values. This is missing from the I2C
driver by Dan. Without this initialization, the kernel justs hangs immediately after the transfer is started,
which is done by setting the spcom to 0x80.
There is a memdump function provided to view the tx and rx buffers before and after the data transfers which
may be used for debugging purposes.
Interrupts may be used to check for various SPI events like the completion of receive or transmit, or
checking for multi-master error. The interrupt is generated by setting the SPI Mask register. For the SPI to
generate interrupts to flag the end of transmit of receive, you need to set the interrupt bit in the status
register of the tx and rx buffer descriptors. I have used them and it works just fine. But I dont use them in
this driver. The tx ready and rx empty bits are reset once transmit and receive is completed, respectively.
The interrupt can be used to check for other events like multi-master errors.
INSTALLING THE DRIVER
---------------------
Please note that the driver is not a loadable module. To install the driver,
i) Copy the spi.c file to linux/arch/ppc/8xx_io directory for the source tree
ii) In the Makefile of the same directory, add spi.o to O_OBJS
iii) Declare the spi initialization function in linux/drivers/char/mem.c file
void cpm_spi_init(void)
iv) Call the init function in __initfunc() in linux/driver/char/mem.c file
cpm_spi_init()
v) Recompile the linux kernel and boot. The driver will register itself with a major number of 89 during
the boot process.
vi) Create a device with minor number=0 and major number=89 in the /dev directory of your file system.
Navin Boppuri
(nboppuri@trinetcommunication.com)
Prashant Patel
(pmpatel@trinetcommunication.com)
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2001-12-18 19:39 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2001-12-18 15:58 A dirty 8xx SPI driver Navin Boppuri
2001-12-18 19:39 ` Re:8xx SPI drivers Kevin Fry
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).