⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 saa1064_rpxcllf.c.txt

📁 这是《嵌入式linux-硬件、软件与接口》一书对应的所有linux方面实例的源代码
💻 TXT
字号:
/*
 * SAA1064_rpxcllf v1.0 11/10/01
 * www.embeddedlinuxinterfacing.com
 *
 * The original location of this code is
 * http://www.embeddedlinuxinterfacing.com/chapters/10/
 *
 * Copyright (C) 2001 by Craig Hollabaugh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/*
 * SAA1064_rpxcllf.c is based on procfs_example.c by Erik Mouw.
 * For more information, please see The Linux Kernel Procfs Guide, Erik Mouw
 * http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html
 *
*/

/* SAA1064_rpxcllf
 * This device driver demonstrates I2C communication with a SAA1064 LED
 * display driver. The RPX-CLLF's MPC860 port B is used for I2C data (SDA)
 * and clock (SCL) signals. This routine doesn't use the MPC860's I2C
 * controller but implements a bit-banging algorithm.
 *
 * The driver creates a /proc directory entry called
 * /proc/trailblazer/temperaturedisplay0. Scripts can write values to
 * temperaturedisplay0 which are then displayed on the LED displays.
 *
 * This driver only communicates with a single SAA1064 at I2C bus address 0.
 */


/*
powerpc-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/powerpc-linux/include  -c SAA1064_rpxcllf.c -o /tftpboot/powerpc-rootfs/tmp/SAA1064_rpxcllf.o
*/


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/delay.h>

#include <asm/8xx_immap.h>

#define MODULE_VERSION "1.0"
#define MODULE_NAME "SAA1064_rpxcllf"

#define SDA   0x00000001
#define SCL   0x00000002
#define DELAY 5

#define SAA1064ADDRESS 0x70

volatile immap_t *immap;

/* references
 * see section 34.3 Port B MPC860 PowerQUICC User's Manual
 *
 * For more SAA1064 or I2C protocol, visit the Philips Semiconductor website
 * at http://www.semiconductors.philips.com
 */

static struct proc_dir_entry  *tb_dir,
                              *temperaturedisplay0_file;

/* here are the I2C signaling macros */
#define SCLLow()  immap->im_cpm.cp_pbdat &= ~SCL
#define SCLHigh() immap->im_cpm.cp_pbdat |=  SCL
#define SDALow()  immap->im_cpm.cp_pbdat &= ~SDA
#define SDAHigh() immap->im_cpm.cp_pbdat |=  SDA
#define readSDA() (SDA == (immap->im_cpm.cp_pbdat & SDA))
#define readSCL() (SCL == (immap->im_cpm.cp_pbdat & SCL))


/*
 * function startCommunication
 * This function sets SDA and SCL in the idle state then
 * initiates the 'start' condition
 */
void startCommunication(void)
{
  SDAHigh();      /* put SDA in idle state    */
  SCLHigh();      /* put SCL in idle state    */
  udelay(DELAY);  /* let lines settle         */
  SDALow();       /* initiate start condition */
  udelay(DELAY);  /* let lines settle         */
  SCLLow();       /* initiate start condition */
}

/*
 * function stopCommunication
 * This function sets SDA and SCL in an known state then
 * initiates the 'stop' condition
 */
void stopCommunication(void)
{
  SCLLow();       /* put SCL in known state  */
  SDALow();       /* put SDA in known state  */
  udelay(DELAY);  /* let lines settle        */
  SCLHigh();      /* initiate stop condition */
  SDAHigh();      /* initiate stop condition */
}

/*
 * function receiveByte
 * This function toggles the clock line while reading
 * the transmitted data bits. I2C communications sends
 * MSB first.
 */
unsigned char receiveByte(void)
{
  unsigned char i, b;

  SDAHigh();                 /* this tri-states SDA                      */
  b = 0;
  for (i = 0; i < 8; i++)
  {
    udelay(DELAY);            /* let lines settle                        */
    SCLHigh();                /* send the clock                          */
    udelay(DELAY);            /* let lines settle                        */
    b = (b << 1) | readSDA(); /* shift the bits then OR the incoming bit */
    SCLLow();                 /* send the clock                          */
  }

  /* this sets up for the next incoming byte */
  udelay(DELAY);
  SCLHigh();
  udelay(DELAY*2);
  SCLLow();
  return b;
}

/*
 * function sendByte
 * This function toggles the clock line while transmitting
 * data bits. I2C communications sends MSB first. This function
 * allows monitors the acknowledge bit (bit 9) asserted by the
 * receiver.
 */
unsigned char sendByte(unsigned char b)
{
  unsigned char i;

  for (i = 0; i < 8; i++)
  {
    if (0x80 == (b & 0x80)) /* is the MSB 0 or 1? */
      SDAHigh();
    else
      SDALow();

    udelay(DELAY);          /* let lines settle   */
    SCLHigh();              /* send the clock     */
    udelay(DELAY);          /* let lines settle   */
    SCLLow();               /* send the clock     */

    b = (b << 1);           /* shift to the left  */
  }

  /* this sets up for the next outgoing byte      */
  udelay(DELAY);
  SDAHigh();
  SCLHigh();
  udelay(DELAY*2); /* 2 delays help you see acks on a scope */

  /* read the ack here, a sent ack is a 0. */
  i = readSDA();
  SCLLow();

  return i;
}

/*
 * function writei2c
 * This function accepts an address, a character buffer and buffer length.
 * It starts communication, sends the address, then the buffer.
 * If an ack error occurs, it tells you (well it prints to the console).
 */
unsigned char writei2c(unsigned address, unsigned char *buffer, 
                       unsigned length)
{
  unsigned char i, error;

  startCommunication();

  error = sendByte(address & 0xFE);
    /* 0xFE? The last bit of the I2C address is a read/nWrite bit */

  if (error) /* didn't get an ack at the address */
  {
    stopCommunication();
    printk("no ack at address 0x%2X\n",address);
    return error;
  }

/* sending the buffer here */
  for (i = 0; i < length; i++)
  {
    error = sendByte(buffer[i]);

    if (error) /* didn't get an ack for that byte sent */
    {
      stopCommunication();
      printk("no ack at buffer byte %d\n",i);
      return error;
    }
  }

/* we're done */
  stopCommunication();
  return 0;
}

/*
 * function readi2c
 * This function accepts an address, a character buffer and buffer length.
 * It starts communication, sends the address, then reads the reply into 
 * buffer. If an ack error occurs, it tells you (well it prints to the 
 * console).
 */
unsigned char readi2c(unsigned address, unsigned char *buffer, 
                      unsigned length)
{
  unsigned char i, error;
  startCommunication();

  error = sendByte(address | 0x01);

  if (error) /* didn't get an ack at the address */
  {
    stopCommunication();
    printk("no ack at address 0x%2X\n",address);
    return error;
  }

  /* receiving bytes for the buffer here */
  for (i = 0; i < length; i++)
  {
    buffer[i] = receiveByte();
  }

/* we're done */
  stopCommunication();
  return 0;
}

/*
 * function proc_write_display
 * This function gets called when the user writes something to
 * /proc/trailblazer/temperaturedisplay0. It contains a mapping array
 * from numbers (0-9,a-f) to what segments to turn on
 */
static int proc_write_temperaturedisplay0(struct file *file, 
                                          const char *buffer,
                                          unsigned long count, void *data)
{
  unsigned char e, displaybuffer[5];

/* seg is a segment mapping table. Element 0, 0xFC, tells the
 * SAA1064 to turn on the segments to display a 0. Likewise,
 * seg's other entries map to 1 through 9 and a through f.
 */
  unsigned char seg[] = { 0xFC, 0x60, 0xDA, 0xF2, 0x66,
                          0xB6, 0xBE, 0xE0, 0xFE, 0xF6,
                          0xEE, 0x3E, 0x9C, 0x7A, 0x9E, 0x8E } ;

  if (count >= 4)
  {
    displaybuffer[0] = 0x01;
    displaybuffer[1] = seg[buffer[0]-'0']; /* subtracting '0' shifts the   */
    displaybuffer[2] = seg[buffer[1]-'0']; /* ascii numbers the user wrote */
    displaybuffer[3] = seg[buffer[2]-'0']; /* to the device file to their  */
    displaybuffer[4] = seg[buffer[3]-'0']; /* numeric equivalent           */

    e = writei2c(SAA1064ADDRESS, displaybuffer, 5);
/* for debugging    printk("proc_write_display = %d\n",e); */
  }

  return 1;
}

/*
 * function init_SAA1064_rpxcllf
 * This function creates the /proc directory entries: trailblazer and
 * trailblazer/temperaturedisplay0. It find the IMMR then configures
 * PB30 and PB31 as general I/O, open drain and outputs.
 * It initializes the SAA1064 and has it displays the middle segment '-'
 * as a sign that the driver loaded successfully.
 */
static int __init init_SAA1064_rpxcllf(void)
{
  unsigned char e,buffer[5];

  int rv = 0;

/* Create the trailblazer /proc entry */
  tb_dir = proc_mkdir("trailblazer", NULL);
  if(tb_dir == NULL) {
          rv = -ENOMEM;
          goto out;
  }
  tb_dir->owner = THIS_MODULE;

/* Create temperaturedisplay0 and make it readable by all - 0444 */
  temperaturedisplay0_file = create_proc_entry("temperaturedisplay0", 0444,
                                               tb_dir);
  if(temperaturedisplay0_file == NULL) {
          rv = -ENOMEM;
          goto no_temperaturedisplay0;
  }
  temperaturedisplay0_file->data = NULL;
  temperaturedisplay0_file->read_proc = NULL;
  temperaturedisplay0_file->write_proc = &proc_write_temperaturedisplay0;
  temperaturedisplay0_file->owner = THIS_MODULE;


  /* get the IMMR */
  immap = (immap_t *)(mfspr(IMMR) & 0xFFFF0000);

  /* make PB30 and PB31 general I/O */
  immap->im_cpm.cp_pbpar &= ~SDA;
  immap->im_cpm.cp_pbpar &= ~SCL;

  /* make PB30 and PB31 open drain */
  immap->im_cpm.cp_pbodr |= SDA;
  immap->im_cpm.cp_pbodr |= SCL;

  /* make PB30 and PB31 outputs    */
  immap->im_cpm.cp_pbdir |= SDA;
  immap->im_cpm.cp_pbdir |= SCL;

  /* display a little info for the happy module loader */
  printk("immr    = 0x%08X\n",immap);
  printk("PBPAR   = 0x%04X\n",immap->im_cpm.cp_pbpar);
  printk("PBDIR   = 0x%04X\n",immap->im_cpm.cp_pbdir);
  printk("PBODR   = 0x%04X\n",immap->im_cpm.cp_pbodr);
  printk("PBDAT   = 0x%04X\n",immap->im_cpm.cp_pbdat);

  buffer[0] = 0x00; /* internal starting SAA1064 register address */
  buffer[1] = 0x46; /* 12mA current, all digits on, dynamic mode  */
  buffer[2] = 0x02; /* turn on '-' segment on each display        */
  buffer[3] = 0x02;
  buffer[4] = 0x02;
  buffer[5] = 0x02;
  if (writei2c(SAA1064ADDRESS, buffer, 6))
  {
    printk("Display initialization failed\n");
    rv = -EREMOTEIO;
    goto no_temperaturedisplay0;
  }
  else
    printk("Display initialization passed\n");

/* everything initialized */
  printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION);
  return 0;

no_temperaturedisplay0:
  remove_proc_entry("temperaturedisplay0", tb_dir);
out:
  return rv;
}

/*
 * function cleanup_SAA1064_rpxcllf
 * This function turns off the SAA1064 display to show that the
 * driver unloaded. It then removes the /proc directory entries
 */
static void __exit cleanup_SAA1064_rpxcllf(void)
{
  unsigned char buffer[5];

  buffer[0] = 0x00;
  buffer[1] = 0x00; /* configuration reg, turn displays off */
  writei2c(SAA1064ADDRESS, buffer, 2);

  remove_proc_entry("temperaturedisplay0", tb_dir);
  remove_proc_entry("trailblazer", NULL);

  printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION);
}

module_init(init_SAA1064_rpxcllf);
module_exit(cleanup_SAA1064_rpxcllf);

MODULE_AUTHOR("Craig Hollabaugh");
MODULE_DESCRIPTION("SAA1064 driver for RPX-CLLF");

EXPORT_NO_SYMBOLS;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -