linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* Changes in the MPC8xx I2C driver
@ 2000-06-09 10:19 Kim Jørgensen
  2000-06-09 17:13 ` Dan Malek
  2000-08-07 16:39 ` I2C,SPI and QMC Frank Przybylski
  0 siblings, 2 replies; 6+ messages in thread
From: Kim Jørgensen @ 2000-06-09 10:19 UTC (permalink / raw)
  To: 'linuxppc-embedded@lists.linuxppc.org'

[-- Attachment #1: Type: text/plain, Size: 1209 bytes --]

Hi,
I have made some changes in the MPC8xx I2C driver, mainly adding write
support and making the driver re-entrant. This might have some ones interest
so I'll post the driver here.

I don't use the minor number for the device address, instead I added an
ioctl function to allow set up of the device address, internal address
length and bus frequency. The setup data is saved in a struct, which is
accessed by the private pointer in the file pointer.
This allows several processes to talk to several i2c devices with different
bus frequency at the (almost) same time. I had a need for this, that's why I
made is that way.

I have removed all of the microcode relocation code in the driver, as I
couldn't make the driver work with this code and I had no need for applying
the patch.

I added these defines in commproc.h:
#define BD_SC_NAK       ((ushort)0x0004)        /* No Acknowledge */
#define BD_SC_UN        ((ushort)0x0002)        /* Underrun */
#define BD_SC_CL        ((ushort)0x0001)        /* Collision */

I made some clumsy code for converting offset to internal address, maybe
some one can tell me how to make this in a simpler way?

Any suggestions and comments are welcome.

--
Kim Jorgensen



[-- Attachment #2: iic.c --]
[-- Type: application/octet-stream, Size: 14013 bytes --]


/* MPC8xx CPM I2C interface. Copyright (c) 1999 Dan Malek (dmalek@jlc.net).
 * This driver only support I2C master mode.
 */
#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/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 "commproc.h"

/* Print debug messages
 */
#define DEBUG

/* This is the I2C device number.
 */
#define CPM_I2C_CDEV_MAJOR 89

/* Defines to make I2C clock run at 100kHz (101kHz) @ 50MHz CPU clock */
#define I2BRG_100KHZ	0x1A
#define I2MOD_100KHZ 	0x0C

/* Defines to make I2C clock run at 400kHz (391kHz) @ 50MHz CPU clock */
#define I2BRG_400KHZ	0x0B
#define I2MOD_400KHZ 	0x0E

void	cpm_iic_init(void);
static int cpm_iic_open(struct inode *, struct file *);
static int cpm_iic_close(struct inode *, struct file *);
static ssize_t cpm_iic_read(struct file *, char *, size_t, loff_t *);
static ssize_t cpm_iic_write(struct file *, const char *, size_t, loff_t *);
static int cpm_iic_ioctl(struct inode *, struct file *, uint, ulong);
static void cpm_iic_interrupt(void *);

static struct file_operations cpm_iic_fops = {
	NULL,			/* lseek */
	cpm_iic_read,		/* read */
	cpm_iic_write,		/* write */
	NULL,			/* readdir */
	NULL,			/* select */
	cpm_iic_ioctl,		/* ioctl */
	NULL,			/* mmap */
	cpm_iic_open,		/* open */
	cpm_iic_close,		/* close */
	NULL			/* fsync */
};

/* File pointer specific setup
 */
typedef struct i2c_setup_struct_def
{
    u_char device_address;		/* The device we want to talk to */
    u_char internal_address_len;	/* The length of internal address */
    uint  clock_freq;			/* The i2c clock frequency */
} i2c_setup_struct;

/* Mutex used to make sure only one uses the i2c bus at time.
 */
struct semaphore bus_busy_mutex = MUTEX;

/* Wait queue used while waiting for interrupt
 */
static struct wait_queue *i2c_interrupt_wait;

volatile i2c8xx_t	*i2c;
volatile cbd_t		*tbdf, *rbdf;

void
cpm_iic_init()
{
	uint	mem_addr, dp_addr;
	volatile cpm8xx_t	*cp;
	volatile iic_t		*iip;
	volatile immap_t	*immap;

	/* print version string
	 */
	printk("CPM I2C driver version 0.01\n");

	/* Get pointer to Communication Processor
	 * and to internal registers
	 */
	cp = cpmp;
	immap = (immap_t *)IMAP_ADDR;

	iip = (iic_t *)&cp->cp_dparam[PROFF_IIC];
	i2c = (i2c8xx_t *)&(immap->im_i2c);

	/* Initialize Port B I2C pins.
	 */
	cp->cp_pbpar |= 0x00000030;
	cp->cp_pbdir |= 0x00000030;
	cp->cp_pbodr |= 0x00000030;

	/* Disable interrupts.
	 */
	i2c->i2c_i2cmr = 0;
	i2c->i2c_i2cer = 0xff;

	/* Set I2C controller in master mode
	 */
	i2c->i2c_i2com = 0x01;

	/* Allocate space for two transmit and one receive buffer
	 * descriptor in the DP ram.
	 */
	dp_addr = m8xx_cpm_dpalloc(sizeof(cbd_t) * 3);

	/* Allocate space for two transmit and one receive buffer in the host memory.
	 * The most you can read or write at once from I2C is 128 bytes.
	 */
	mem_addr = m8xx_cpm_hostalloc(128 * 3);

	/* Set up the I2C parameters in the parameter ram.
	 */
	iip->iic_rbase =  dp_addr;
	iip->iic_tbase =  dp_addr + sizeof(cbd_t);

	/* Set the buffer address.
	 */
	rbdf = (cbd_t *)&cp->cp_dpmem[iip->iic_rbase];
	tbdf = (cbd_t *)&cp->cp_dpmem[iip->iic_tbase];

	rbdf[0].cbd_bufaddr = __pa(mem_addr);
	tbdf[0].cbd_bufaddr = __pa(mem_addr+128);
	tbdf[1].cbd_bufaddr = __pa(mem_addr+256);

	/* Set big endian byte order
	 */
	iip->iic_tfcr = SMC_EB;
	iip->iic_rfcr = SMC_EB;

	/* Set maximum receive size.
	 */
	iip->iic_mrblr = 128;

	/* Initialize Tx/Rx parameters.
	 */
	cp->cp_cpcr = mk_cr_cmd(CPM_CR_CH_I2C, CPM_CR_INIT_TRX) | CPM_CR_FLG;
	while (cp->cp_cpcr & CPM_CR_FLG);

	/* Install interrupt handler.
	 */
	cpm_install_handler(CPMVEC_I2C, cpm_iic_interrupt, (void *)iip);

	/* Register the I2C driver
	 */
	if (register_chrdev(CPM_I2C_CDEV_MAJOR,"i2c",&cpm_iic_fops))
		printk("unable to get major %d for I2C devs\n",	CPM_I2C_CDEV_MAJOR);

}

static int
cpm_iic_open(struct inode *ip, struct file *fp)
{
    i2c_setup_struct *i2c_setup;

    /* Check minor number.
     */
    if( MINOR(ip->i_rdev) > 0)
	 return -ENODEV;

    /* Alloc some memory for I2C setup struct
     */
    fp->private_data = kmalloc(sizeof(i2c_setup_struct), GFP_KERNEL);
    i2c_setup = (i2c_setup_struct *)(fp->private_data);
    if(!i2c_setup)
	return -ENOMEM;

    /* Set device address and internal address length to 0
     * and i2c clock freq. to 100kHz
     */
    i2c_setup->device_address = 0;
    i2c_setup->internal_address_len = 0;
    i2c_setup->clock_freq = 100000;
    
    /* Set pointer to file operations struct
     */
    fp->f_op = &cpm_iic_fops;

    return 0;
}

static int
cpm_iic_close(struct inode *ip, struct file *fp)
{
        /* Free space used by i2c setup struct
	 */
	kfree(fp->private_data);

	return 0;
}

/* Read from I2C.
 * This is a two step process if internal addresses is used.  First, we send 
 * the "dummy" write to set the internal address for the read.  Second, we 
 * perform the read operation.
 */
static ssize_t
cpm_iic_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
{
        i2c_setup_struct	*i2c_setup;
	loff_t	offset;
	u_char	*tb, *tb2, *rb, len, tmp_off[4];

	/* Get pointer to i2c setup data
	 */
	i2c_setup = (i2c_setup_struct *)(fp->private_data);

	/* Get pointer to transmit and receive buffer
	 */
	tb =  __va(tbdf[0].cbd_bufaddr);
	tb2 = __va(tbdf[1].cbd_bufaddr);
	rb =  __va(rbdf[0].cbd_bufaddr);

	/* Get file offset (internal address)
	 */
	offset = *ppos;

	tmp_off[0] = (char)(offset >> 24);
	tmp_off[1] = (char)((offset >> 16) % 0x100);
	tmp_off[2] = (char)((offset >> 8) % 0x100);
	tmp_off[3] = (char)(offset % 0x100);

	/* Enter critical section
	 */
	down(&bus_busy_mutex);
        if (signal_pending(current))
	{
	    up(&bus_busy_mutex);
	    return -ERESTARTSYS;
	}

	if(i2c_setup->internal_address_len)		/* Use internal address	*/
	{
	    *tb = i2c_setup->device_address & 0xfe;	/* Device address, write request */
	    tbdf[0].cbd_datlen = i2c_setup->internal_address_len + 1 ;	/* Length */

	    /* copy offset into transmit buffer
	     */
	     for(len=0; len < i2c_setup->internal_address_len; len++)
	         tb[1+len] = tmp_off[(4 - i2c_setup->internal_address_len) + len];
	    
	    *tb2 = i2c_setup->device_address | 0x01;	/* Device address, read request */
	    tbdf[1].cbd_datlen = count + 1;		/* Length */

	    tbdf[0].cbd_sc = BD_SC_READY | BD_IIC_START;
	    tbdf[1].cbd_sc = BD_SC_READY | BD_SC_LAST | BD_SC_WRAP | BD_IIC_START;
	}
	else						/* No internal address */
	{
	    *tb = i2c_setup->device_address | 0x01;	/* Device address, read request */
    	    tbdf[0].cbd_datlen = count + 1;		/* Length */

	    tbdf[0].cbd_sc = BD_SC_READY | BD_SC_LAST | BD_SC_WRAP | BD_IIC_START;
	}
	
	rbdf[0].cbd_sc = BD_SC_EMPTY | BD_SC_INTRPT | BD_SC_WRAP;
	
	i2c->i2c_i2cmr = 0x15;			/* Enable receive interrupt */
	i2c->i2c_i2cer = 0xff;			/* Clear event register */

	if(i2c_setup->clock_freq == 400000)	/* Make clock run at 400kHz */
	{
	    i2c->i2c_i2brg = I2BRG_400KHZ;
	    i2c->i2c_i2mod = I2MOD_400KHZ;
	}
	else					/* Make clock run at 100kHz */
	{
	    i2c->i2c_i2brg = I2BRG_100KHZ;
	    i2c->i2c_i2mod = I2MOD_100KHZ;
	}

	i2c->i2c_i2mod |= 0x01;			/* Chip errata, set enable here */
	i2c->i2c_i2com |= 0x80;			/* Start transmit */

	/* Wait for I2C receive interrupt or time out (1 second).
	*/
	if(interruptible_sleep_on_timeout(&i2c_interrupt_wait, 1*HZ)<=0)
	{
#ifdef DEBUG
	    printk("I2C read: Time out!\n");
#endif
	}

	i2c->i2c_i2mod &= ~0x01;		/* Chip errata, clear enable */

	if(signal_pending(current))
	{
	    up(&bus_busy_mutex);
	    return -ERESTARTSYS;
	}

	/* check for errors in first tx buffer
	 */
	if(tbdf[0].cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL))
	{
#ifdef DEBUG
	    switch(tbdf[0].cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL))
	    {
		case BD_SC_READY:
		    printk("I2C read complete but tx_buf ready!\n");
		    break;
		case BD_SC_NAK:
		    printk("I2C read: no acknowledge!\n");
		    break;
		case BD_SC_UN:
	    	    printk("I2C read: underrun!\n");
		    break;
		case BD_SC_CL:
		    printk("I2C read: collision!\n");
		    break;
	    }
#endif
	    up(&bus_busy_mutex);
	    return -EFAULT;
	}

	if(i2c_setup->internal_address_len)
	{
	    /* check for errors in second tx buffer
	     */
	    if(tbdf[1].cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL))
	    {
#ifdef DEBUG
		switch(tbdf[1].cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL))
		{
		    case BD_SC_READY:
			printk("I2C read complete but tx_buf ready!\n");
			break;
		    case BD_SC_NAK:
			printk("I2C read: no acknowledge!\n");
			break;
    		    case BD_SC_UN:
	    		printk("I2C read: underrun!\n");
			break;
		    case BD_SC_CL:
			printk("I2C read: collision!\n");
			break;
		}
#endif
		up(&bus_busy_mutex);
		return -EFAULT;
	    }
	}

	/* check for errors in rx buffer
	 */
	if (rbdf[0].cbd_sc & BD_SC_EMPTY)
	{
	    up(&bus_busy_mutex);
#ifdef DEBUG
	    printk("I2C read complete but rx_buf empty!");
#endif
    	    return -EFAULT;
	}
	
	if (copy_to_user(buf, rb, count))
	{
	    up(&bus_busy_mutex);
	    return -EFAULT;
	}

	/* Leave critical section
	 */
        up(&bus_busy_mutex);
	
	*ppos += count;
	return count;
}

/* I2C write funktion.
*/
static ssize_t
cpm_iic_write(struct file *fp, const char *buf, size_t count, loff_t *ppos)
{
	i2c_setup_struct 	*i2c_setup;
	loff_t	offset;
	u_char	*tb, len, tmp_off[4];

	/* Get pointer to i2c setup data
	 */
	i2c_setup = (i2c_setup_struct *)(fp->private_data);
	
	/* Get file offset (internal address)
	 */
	offset = *ppos;

	tmp_off[0] = (char)(offset >> 24);
	tmp_off[1] = (char)((offset >> 16) % 0x100);
	tmp_off[2] = (char)((offset >> 8) % 0x100);
	tmp_off[3] = (char)(offset % 0x100);


	/* Enter critical section
	 */
	down(&bus_busy_mutex);
        if(signal_pending(current))
	{
	    up(&bus_busy_mutex);
	    return -ERESTARTSYS;
	}

	/* Get pointer to transmit buffer
	 */
	tb = __va(tbdf[0].cbd_bufaddr);

	*tb = i2c_setup->device_address & 0xfe;	/* Set device address, write request */


	if(i2c_setup->internal_address_len)	/* Use internal address	*/
	{
            /* copy offset into transmit buffer
	     */
	    for(len=0; len < i2c_setup->internal_address_len; len++)
		tb[1+len] = tmp_off[(4 - i2c_setup->internal_address_len) + len];
	}

	/* copy data into transmit buffer
	 */
	if(copy_from_user(tb+i2c_setup->internal_address_len+1, buf, count))
	{
	    up(&bus_busy_mutex);
	    return -EFAULT;
	}

	tbdf[0].cbd_datlen = count + i2c_setup->internal_address_len + 1;	/* Data length */
	tbdf[0].cbd_sc =  BD_SC_READY | BD_SC_INTRPT | BD_SC_LAST | BD_SC_WRAP | BD_IIC_START;

	i2c->i2c_i2cmr = 0x16;			/* Enable transmit interrupt */
	i2c->i2c_i2cer = 0xff;			/* Clear event register */


	if(i2c_setup->clock_freq == 400000)	/* Make clock run at 400kHz */
	{
	    i2c->i2c_i2brg = I2BRG_400KHZ;
	    i2c->i2c_i2mod = I2MOD_400KHZ;
	}
	else					/* Make clock run at 100kHz */
	{
	    i2c->i2c_i2brg = I2BRG_100KHZ;
	    i2c->i2c_i2mod = I2MOD_100KHZ;
	}

	i2c->i2c_i2mod |= 0x01;			/* Chip errata, set enable here */
	i2c->i2c_i2com |= 0x80;			/* Start transmit */

	/* Wait for I2C transmit interrupt or time out (1 second).
	*/
	if(interruptible_sleep_on_timeout(&i2c_interrupt_wait, 1*HZ)<=0)
	{
#ifdef DEBUG
	    printk("I2C write: Time out!\n");
#endif
	}

	i2c->i2c_i2mod &= ~0x01;		/* Chip errata, clear enable */

	if (signal_pending(current))
	{
	    up(&bus_busy_mutex);
	    return -ERESTARTSYS;
	}

	/* check for errors in tx buffer
	 */
	if(tbdf[0].cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL))
	{
#ifdef DEBUG
	    switch(tbdf[0].cbd_sc & (BD_SC_READY | BD_SC_NAK | BD_SC_UN | BD_SC_CL))
	    {
		case BD_SC_READY:
		    printk("I2C write complete but tx_buf ready!\n");
		    break;
		case BD_SC_NAK:
		    printk("I2C write: no acknowledge!\n");
		    break;
		case BD_SC_UN:
	    	    printk("I2C write: underrun!\n");
		    break;
		case BD_SC_CL:
		    printk("I2C write: collision!\n");
		    break;
	    }
#endif
	    up(&bus_busy_mutex);
	    return -EFAULT;
	}

	/* Leave critical section
	 */
	up(&bus_busy_mutex);

        *ppos += count;
	return count;
}

static int
cpm_iic_ioctl(struct inode *ip, struct file *fp, u_int cmd, ulong arg)
{
	int	retval = 0;
	i2c_setup_struct *i2c_setup;

	i2c_setup = (i2c_setup_struct *)(fp->private_data);

	switch(cmd)
	{
	    /* Set device address.
	     */
	    case 1:
		i2c_setup->device_address = (u_char)arg & 0xfe; 	/* Set device address, ignore LSB */
		break;

	    /* Set internal address length.
	     */
	    case 2:
	    	if((u_char)arg > 4)
		    retval = -EINVAL;
		else 
		    i2c_setup->internal_address_len = (u_char)arg;
		break;

	    /* Set the bus clock frequency.
	     */
	    case 3:
		if(((uint)arg != 100000) && ((uint)arg != 400000))
		    retval = -EINVAL;
		else
		    i2c_setup->clock_freq = (uint)arg;
		break;
		
	    default: retval = -ENOIOCTLCMD;
	}

	return retval;
}

static void
cpm_iic_interrupt(void *dev_id)
{
	/* Clear interrupt.
	*/
	i2c->i2c_i2cer = 0xff;

	/* Wake up process in wait queue
	*/
	wake_up_interruptible(&i2c_interrupt_wait);
}

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2000-08-09  8:40 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2000-06-09 10:19 Changes in the MPC8xx I2C driver Kim Jørgensen
2000-06-09 17:13 ` Dan Malek
2000-08-07 16:39 ` I2C,SPI and QMC Frank Przybylski
2000-08-07 17:42   ` Matthew Locke
2000-08-08 19:27     ` Dan Malek
2000-08-09  8:40       ` Frank Przybylski

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).