📄 saa1064_rpxcllf.c
字号:
/* * 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 0x70volatile 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 + -