* Pilote Phoenux pour le noyau 2.4.x * To compile: * gcc -Wall -O2 -c -DMODULE -D__KERNEL__ -I/usr/src/linux/include -o ph.o ph.c * Note : '/usr/src/linux' devra avoir le source du noyau pour pouvoir compiler */ #include #include #include #include #include #include #include "ph24.h" MODULE_LICENSE ("GPL"); MODULE_AUTHOR ("Ajith Kumar, Nuclear Science Centre"); #define DATA 0x378 // Adresses d'E/S du port d'imprimante #define STAT 0x379 #define CTL 0x37A #define ADCLO 0 // Informations d'adresse du matériel de Phoenix #define ADCHI 1 // Décalage pour différentes fonctions #define ADADR 2 #define ADSTRT 3 #define DAC 4 #define SM 5 #define DOUT 6 #define DIN 7 #define ENBIT 8 // Pour éviter les pointes du décodeur #define ADEOC 128 // Connecté à S7 de l'octet d'état #define FALSE 0 #define TRUE 1 u8 read_adc(void); void rotate_motor(int nsteps , int dir); int motor_delay = 10; // 10 msecs u8 adc_timeout=FALSE; static int max_wait = 1000000; // Environ 1 seconde de temps imparti /* * La plupart du temps, le pilote lit les valeurs analogiques et numériques * provenant du boîtier phoenix et les renvoie avec une estampille temporelle. * L'heure est renvoyée dans la structure 'timeval' définie par Linux. * Elle comporte des champs secondes et micro-secondes. Nous définissons une structure * nommée 'adcdata' qui contient 'timeval' et un nombre sur 16 bits, * quoique le CAN n'ait besoin que de 8 bits actuellement. * La même structure est utilisée pour renvoyer des estampilles temporelles uniquement. * Dans ce cas, le champ 'adval' est inutilisé. * * Les programmes utilisateur devront définir la même structure dans leur programme * et passer des pointeurs de tableau de telles structures au pilote. * Pour obtenir des exemples, voir le programme 'phlib.c'. La structure est * définie dans 'ph24.h'. * * Le code tel qu'il est maintenant permet à des processus multiples d'y accéder * simultanément - mais les problèmes de concurrence qui en résultent n'ont PAS * été établis. */ adcdata data[MAXPOINTS]; /* * Génère des impulsions sur n'importe quelle broche numérique de sortie * du moteur pas à pas. * L'appel contient quatre arguments entiers (comme un tableau). Un autre * argument sera utilisé pour stocker les estampilles temporelles. * Le premier argument est le numéro de la broche. 0 à 7 signifie sortie numérique, * et 8 à 11 signifie sorties SM 0 à 3. * Les deuxième et troisième arguments sont HITIME et LOWTIME en micro-secondes. * Le quatrième est le nombre d'impulsions à générer. */ static inline void do_pulseout(u32 *buf, adcdata *s) { u8 port, tmp8; int k; if(buf[0] < 8) { // Une broche numérique de sortie port = DOUT; tmp8 = 1 << buf[0]; } else { // Une broche du pilote de moteur à pas port = SM; tmp8 = 1 << ( buf[0] - 8 ); } #ifdef DEBUG printk("port = %d data = %d\n", port, tmp8); #endif outb(port, CTL); do_gettimeofday(&s[0].t); // Marque l'heure de début for(k = 0; k < buf[3]; ++k) { outb(tmp8, DATA); outb(ENBIT + port, CTL); outb(port, CTL); udelay(buf[1]); outb(0, DATA); outb(ENBIT + port, CTL); outb(port, CTL); udelay(buf[2]); } do_gettimeofday(&s[1].t); // Marque l'heure de fin } /* Attendre un bord tombant sur `bit' */ static inline int falling_edge(adcdata *s, u8 bit) { u8 tmp = (1 << (bit + 3)); volatile int timer; // Pour les mesures numériques de temps timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); // Attend que le BIT devienne HIGH (HAUT) while(timer++ < max_wait) if(inb(STAT) & tmp) break; outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Erreur de temps imparti // Maintenant attend le bord tombant sur BIT0 timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(~inb(STAT) & tmp) break; do_gettimeofday(&s[0].t); if(timer >= max_wait) { // Erreur de temps imparti outb(DIN, CTL); return -EIO; } return TRUE; } /* Attendre un front montant sur 'bit' */ static inline int rising_edge(adcdata *s, u8 bit) { u8 tmp = (1 << (bit + 3)); volatile int timer; // Pour les mesures numériques de temps timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); // Attend que le BIT devienne LOW (BAS) while(timer++ < max_wait) if(~inb(STAT) & tmp) break; outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Erreur de temps imparti // Attend maintenant le front montant timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(inb(STAT) & tmp) break; do_gettimeofday(&s[0].t); if(timer >= max_wait) { // Erreur de temps imparti outb(DIN, CTL); return -EIO; } return TRUE; } static int device_ioctl (struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) { int *up = (void *) ioctl_param; // Notre pointeur sur l'espace utilisateur volatile int timer; // Pour les mesures numériques de temps u32 tmpi, k, buf[4]; u8 tmp8, startbit, stopbit; switch (ioctl_num) { case PULSEOUT: copy_from_user(buf, up, 4 * sizeof(int)); if(buf[0] > 11) return -EIO; // Numéro de broche invalide if(buf[1] > MAXHITIME) return -EIO; if(buf[2] > MAXLOWTIME) return -EIO; if(buf[3] > MAXPULSES) return -EIO; do_pulseout(buf, data); // Renvoie 2 estampilles temporelles copy_to_user(up, data, 2 * sizeof(adcdata)); break; case DIGOUT: // Écrit une valeur, 0 à 255, dans la sortie numérique // printk("DIGOUT=got %x\n", ioctl_param); tmp8 = ioctl_param; outb(tmp8, DATA); outb(DOUT, CTL); outb(ENBIT+ DOUT, CTL); outb(DOUT, CTL); break; case DIGIN: // Lit le mot numérique d'entrée sur 4 bits outb(DIN, CTL); outb(ENBIT+DIN, CTL); tmp8 = (inb(STAT) >> 3) & 15; outb(DIN, CTL); copy_to_user(up, &tmp8, sizeof(u8)); break; case SETDAC: // Accepte un nombre, 0 à 255, // et affecte cette valeur au DAC. tmp8 = ioctl_param; outb(tmp8, DATA); // le DAC va de -5V à 5V pour 0 à 255 outb(DAC, CTL); outb(ENBIT+DAC, CTL); outb(DAC, CTL); break; case SELECTADC: // Sélectionne le canal ADC à utiliser. (0 à 7) tmp8 = ioctl_param; if(tmp8 > 7) return -EIO; outb(tmp8, DATA); outb(ADADR, CTL); outb(ENBIT+ADADR, CTL); outb(ADADR, CTL); break; case READADC: // Numérise la tension à l'entrée sélectionnée. // Renvoie 0-255 adc_timeout = FALSE; tmp8 = read_adc(); if(adc_timeout) return -EIO; copy_to_user(up, &tmp8, sizeof(u8)); break; case MOTORCW: // Fait tourner le moteur de pas dans le sens des aiguilles d'une montre copy_from_user(&tmpi, up, sizeof(int)); rotate_motor(tmpi , 1); break; case MOTORCCW: // Compteur dans le sens des aiguilles d'une montre copy_from_user(&tmpi, up, sizeof(int)); rotate_motor(tmpi , 0); break; case SETMOTOR: // Affecte une valeur spécifique aux 4 broches du pilote // de moteur à pas. Utile pour commander les bobines // de relais. tmp8 = ioctl_param; printk("SETMOTOR: %d\n", tmp8); outb(tmp8, DATA); outb(SM, CTL); outb(ENBIT+ SM, CTL); outb(SM, CTL); break; case READBLOCK: /* Cette requête sert à numériser une forme d'onde. * L'utilisateur spécifie le nombre de points * et le laps de temps entre ceux-ci (en * millisecondes). Si zéro est spécifié, * l'espacement sera d'environ 110 micro-secondes. * Les valeurs de renvoi sont stockées dans un * tableau de structures. Chaque élément contient le temps * processeur (CPU) à l'instant de la numérisation et la valeur * numérisée. */ copy_from_user(buf, up, 2*sizeof(int)); if(buf[0] > MAXPOINTS) return -EIO; if(buf[1] > MAXDELAY) return -EIO; printk("buf[0] = %d, buf[1] = %d\n", buf[0], buf[1]); adc_timeout = FALSE; for(k = 0; k < buf[0]; ++k) { do_gettimeofday(&data[k].t); data[k].adval = read_adc() & 0xff; if(adc_timeout) return -EIO; if(buf[1]) mdelay(buf[1]); } copy_to_user(up, data, buf[0] * sizeof(adcdata)); break; case TREADBLOCK: // Numérise un bloc après avoir détecté un bord tombant // sur l'entrée numérique BIT0. copy_from_user(buf, up, 2*sizeof(int)); if(buf[0] > MAXPOINTS) return -EIO; if(buf[1] > MAXDELAY) return -EIO; adc_timeout = FALSE; // Attend le bord tombant if(falling_edge(data, 0) == -EIO) return -EIO; udelay(buf[1]); for(k = 1; k < buf[0]; ++k) { do_gettimeofday(&data[k].t); data[k].adval = read_adc(); if(adc_timeout) return -EIO; if (buf[1]) udelay(buf[1]); } copy_to_user(up, data, buf[0] * sizeof(adcdata)); break; case PERIOD: { // Renvoie le temps entre deux fronts montants // consécutifs d'une impulsion TTL u8 bit, a, b, edge; copy_from_user(&tmp8, up, sizeof(u8)); if(tmp8 > 3) return -EIO; // Bit0 est à S3 du registre d'état bit = (1 << (tmp8+3)); // Attend jusqu'à ce que le BIT soit LOW timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(~inb(STAT) & bit) break; if(timer >= max_wait) { outb(DIN, CTL); return -EIO; // Erreur de temps imparti } a = 0; edge = 0; timer = 0; while(timer++ < (max_wait*4)) { b = inb(STAT) & bit; if(b > a) // A obtenu un front montant do_gettimeofday(&data[edge++].t); if(edge == 2) break; a = b; } outb(DIN,CTL); if(timer >= max_wait*4) return -EIO; // Renvoie deux estampilles temporelles copy_to_user(up, data, 2 * sizeof(adcdata)); break; } case R2RTIME: /* * Cet appel renvoie le délai entre les fronts montants * du signal TTL appliqué à n'importe laquelle des deux * lignes numériques d'entrée. L'utilisateur spécifie les * deux entrées à surveiller. Celui-ci et les trois appels * suivants servent à mesurer l'intervalle de temps entre deux * événements. Par exemple, le mouvement d'un objet peut être * détecté par une sortie de phototransistor connectée à une des * sorties numériques. Deux entrées de ce type peuvent mesurer * le temps de déplacement de l'objet entre deux capteurs optiques. */ copy_from_user(buf, up, 2 * sizeof(u32)); if( (buf[0] > 3) || (buf[1] > 3) ) return -EIO; // bit 0 to 3 only startbit = (1 << (buf[0]+3) ); // Décalage de 3 bits stopbit = (1 << (buf[1]+3) ); // Attend jusqu'à ce que le BIT 'START' soit LOW timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(~inb(STAT) & startbit) break; outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Erreur de temps imparti // Attend maintenant le front montant sur le BIT START timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(inb(STAT) & startbit) break; do_gettimeofday(&data[0].t); // Première estampille temporelle if(timer >= max_wait) { outb(DIN, CTL); return -EIO; } // Attend maintenant le front montant sur le BIT STOP timer = 0; while(timer++ < max_wait) if(inb(STAT) & stopbit) break; do_gettimeofday(&data[1].t); // Seconde estampille temporelle outb(DIN, CTL); if(timer >= max_wait) return -EIO; copy_to_user(up, data, 2 * sizeof(adcdata)); break; case R2FTIME: /* Mesure le temps depuis le front montant vers le bord * tombant. Peut être utilisé pour calculer la largeur d'une * impulsion TTL. */ copy_from_user(buf, up, 2 * sizeof(u32)); if( (buf[0] > 3) || (buf[1] > 3) ) return -EIO; // Bit 0 à 3 seulement startbit = (1 << (buf[0]+3) ); stopbit = (1 << (buf[1]+3) ); // Attend jusqu'à ce que le BIT START soit LOW timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(~inb(STAT) & startbit) break; outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Erreur de temps imparti // Attend le front montant sur le BIT START timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(inb(STAT) & startbit) break; do_gettimeofday(&data[0].t); // Première estampille temporelle if(timer >= max_wait) { outb(DIN, CTL); return -EIO; } // Maintenant attend le bord tombant sur le BIT STOP (ARRÊT) timer = 0; while(timer++ < max_wait) if(~inb(STAT) & stopbit) break; do_gettimeofday(&data[1].t); // Seconde estampile temporelle outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Erreur de temps imparti copy_to_user(up, data, 2 * sizeof(adcdata)); break; case F2RTIME: // Temps depuis un front montant jusqu'à un front montant copy_from_user(buf, up, 2 * sizeof(u32)); if( (buf[0] > 3) || (buf[1] > 3) ) return -EIO; // Bit 0 à 3 seulement startbit = (1 << (buf[0]+3) ); stopbit = (1 << (buf[1]+3) ); // Attend jusqu'à ce que le BIT START soit HIGH (HAUT) timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(inb(STAT) & startbit) break; outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Maintenant attend le bord tombant sur le BIT START timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(~inb(STAT) & startbit) break; do_gettimeofday(&data[0].t); if(timer >= max_wait) { outb(DIN, CTL); return -EIO; } // Maintenant attend le front montant sur le BIT STOP timer = 0; while(timer++ < max_wait) if(inb(STAT) & stopbit) break; do_gettimeofday(&data[1].t); outb(DIN, CTL); if(timer >= max_wait) return -EIO; copy_to_user(up, data, 2 * sizeof(adcdata)); break; case F2FTIME: // Temps depuis un bord tombant jusqu'à un bord tombant copy_from_user(buf, up, 2 * sizeof(u32)); if( (buf[0] > 3) || (buf[1] > 3) ) return -EIO; // Bit 0 à 3 seulement startbit = (1 << (buf[0]+3) ); stopbit = (1 << (buf[1]+3) ); // Attend jusqu'à ce que le BIT START soit HIGH timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(inb(STAT) & startbit) break; outb(DIN, CTL); if(timer >= max_wait) return -EIO; // Maintenant attend le bord tombant sur le BIT START timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(~inb(STAT) & startbit) break; do_gettimeofday(&data[0].t); if(timer >= max_wait) { outb(DIN, CTL); return -EIO; } // Maintenant attend le bord tombant que le BIT STOP timer = 0; while(timer++ < max_wait) if(~inb(STAT) & stopbit) break; do_gettimeofday(&data[1].t); outb(DIN, CTL); if(timer >= max_wait) return -EIO; copy_to_user(up, data, 2 * sizeof(adcdata)); break; case SETMAXWAIT: /* Les appels de mesure de temps attendent à l'intérieur * du pilote et gèlent le système pendant cette période. * Il est essentiel de fournir les limites maximales pour * attendre. La valeur par défaut est d'environ 1000000 micro-secondes * (1 seconde). * Le programme utilisateur peut changer cela en une valeur comprise * entre 5 msecs et 5 secs. */ copy_from_user(buf, up, sizeof(int)); if(buf[0] < 5000) return -EIO; // Minimum 5 millisecs if(buf[0] > 5000000) return -EIO; // Maximum 5 secs max_wait = buf[0]; break; case DROPTIME: /* Temps pour passer à OFF la bobine SM à un front * montant sur la broche d'entrée 0. Utilisé seulement pour * mesurer la pesanteur par la méthode du temps de vol. */ outb(0, DATA); // Libère le poids outb(SM, CTL); outb(ENBIT+ SM, CTL); outb(SM, CTL); do_gettimeofday(&data[0].t); // Marque l'heure de début // Maintenant attend le front montant sur le BIT 0 timer = 0; outb(DIN, CTL); outb(ENBIT+DIN, CTL); while(timer++ < max_wait) if(inb(STAT) & 8) break; do_gettimeofday(&data[1].t); outb(DIN, CTL); if(timer >= max_wait) return -EIO; copy_to_user(up, data, 2 * sizeof(adcdata)); break; default: return -EIO; } return 0; } int init_module (void); void cleanup_module (void); #define DEVICE_NAME "phoenix" static int Major; static struct file_operations fops = { .ioctl = device_ioctl }; int init_module (void) { Major = register_chrdev (0, DEVICE_NAME, &fops); if (Major < 0) { printk("Déclaration du périphérique caractère"); printk("échoué avec %d\n", Majeur); return Major; } printk ("rm -f /dev/phoenix\n"); printk ("mknod -m 666 /dev/phoenix c %d 0\n", Major); return 0; } void cleanup_module (void) { int ret = unregister_chrdev (Major, DEVICE_NAME); if (ret < 0) printk ("Error in unregister_phoenix: %d\n", ret); } u8 read_adc(void) /* renvoie 0 à 255. */ { u8 lo,hi,dat; volatile int timer = 50; outb(ADSTRT, CTL); outb(ENBIT+ADSTRT, CTL); outb(ADSTRT, CTL); outb(0, CTL); udelay(100); // Il vaut mieux ne pas perturber le PCB pendant 100 micro-secondes while(timer) { if(~inb(STAT) & ADEOC) break; // Bit EOC inversé par Pport --timer; udelay(1); } if(!timer) { adc_timeout = TRUE; // Définit le drapeau d'erreur return 0; } outb(ADCLO, CTL); outb(ENBIT+ADCLO, CTL); lo = (inb(STAT) >> 3) & 15; outb(ADCLO, CTL); outb(ADCHI, CTL); outb(ENBIT+ADCHI, CTL); hi = inb(STAT) >> 3; outb(ADCHI, CTL); // Ceci éclaircit aussi le drapeau ADEOC 74LS74 dat = (hi << 4) | lo; return dat; } void rotate_motor(int nsteps , int dir) { static u8 pos = 0; static u8 seq[4] = {12, 6, 3 ,9}; int i; for( i = 0; i < nsteps; ++i) { if(dir) { if(pos == 3) pos = 0; else ++pos; } else if(pos == 0) pos = 3; else --pos; outb(seq[pos], DATA); outb(SM, CTL); outb(SM+ENBIT, CTL); outb(SM, CTL); outb(0, CTL); mdelay(motor_delay); } } --Boundary-