Premiers pas avec Xenomai

Image non disponible


précédentsommairesuivant

XIII. Drivers

XIII-A. Communication série

XIII-A-1. Matériels compatibles

La distribution de Xenomai inclut par défaut le driver RTDM de l'UART 16550A, écrit par Jan Kiszka. Il est compatible avec la grande majorité des composants du marché (8250, 16550n, 16650...).

Pour les autres chipsets, les constructeurs (Moxa, Sunix...) indiquent le plus souvent eux-mêmes cette compatibilité ascendante dans leurs datasheets.

Pour les cartes mixtes RS(EIA) 232/422/485, la configuration électrique se fait généralement au moyen de mini-switches disposés sur la carte. Pensez-y avant de l'installer dans le PC !

XIII-A-2. Récupération de la configuration matérielle de la carte

Contrairement à Linux qui identifie le matériel au démarrage, il faut préciser au driver série RTDM comment sont configurés les ports série (IRQ et adresse de base).

Pour cela, la manière la plus simple est d'installer la carte série sous Linux avec éventuellement son driver et de récupérer sa configuration.

À ce propos, Linux supporte par défaut(40) quatre ports série, et généralement deux sont déjà présents sur la carte mère. En toute logique, si vous utilisez une carte multiport, il se peut que tous ne soient pas visibles. Il vous faut donc repositionner dans la configuration du noyau les paramètres :

Device Drivers/Character devices/Serial drivers/Maximum number of 8250/16550 serial ports ;

Device Drivers/Character devices/Serial drivers/Maximum number of 8250/16550 serial ports to register at runtime.

Puis recompiler le noyau.

Une fois tous les ports visibles, nous allons utiliser setserial, vous devez être root pour le lancer.

Dans l'exemple suivant, nous disposons d'un PC équipé de deux cartes Moxa 4 ports.

 
Sélectionnez
[root@localhost etc]# ls /dev/ttyMI*
/dev/ttyMI0   /dev/ttyMI15  /dev/ttyMI21  /dev/ttyMI28  /dev/ttyMI5
/dev/ttyMI1   /dev/ttyMI16  /dev/ttyMI22  /dev/ttyMI29  /dev/ttyMI6
/dev/ttyMI10  /dev/ttyMI17  /dev/ttyMI23  /dev/ttyMI3   /dev/ttyMI7
/dev/ttyMI11  /dev/ttyMI18  /dev/ttyMI24  /dev/ttyMI30  /dev/ttyMI8
/dev/ttyMI12  /dev/ttyMI19  /dev/ttyMI25  /dev/ttyMI31  /dev/ttyMI9
/dev/ttyMI13  /dev/ttyMI2   /dev/ttyMI26  /dev/ttyMI32
/dev/ttyMI14  /dev/ttyMI20  /dev/ttyMI27  /dev/ttyMI4
 
# Récupération de la configuration des ports grâce à setserial
[root@localhost ~]#setserial /dev/ttyMI0
/dev/ttyMI0, UART: 16550A, Port: 0x1000, IRQ: 10
[root@localhost ~]# setserial /dev/ttyMI1
/dev/ttyMI1, UART: 16550A, Port: 0x1008, IRQ: 10
[root@localhost ~]# setserial /dev/ttyMI2
/dev/ttyMI2, UART: 16550A, Port: 0x1010, IRQ: 10
[root@localhost ~]# setserial /dev/ttyMI3
/dev/ttyMI3, UART: 16550A, Port: 0x1018, IRQ: 10
[root@localhost ~]# setserial /dev/ttyMI8
/dev/ttyMI4, UART: 16550A, Port: 0x1040, IRQ: 11
...

XIII-A-3. Configuration du driver RTDM 16550A

Dans notre exemple, le driver est compilé directement dans le noyau. Sa configuration est faite au démarrage du système.

Nous utilisons Lilo, pour passer des paramètres au noyau lors du démarrage, il faut donc modifier le fichier /etc/lilo.conf :

 
Sélectionnez
image=/boot/vmlinuz-2.6.24
        label="2.6.24_Xenomai"
        root=/dev/hda1
        initrd=/boot/initrd-2.6.24.img
        append="resume=/dev/hda5 xeno_16550A.io=0x1000,0x1008,0x1010,0x1018,0x1040,0x1048,0x1050,0x1058  xeno_16550A.irq=10,10,10,10,11,11,11,11 xeno_16550A.baud_base=921600,921600,921600,921600,921600,921600,921600,921600
        vga=788"

L'information baud_base est la vitesse maximale de transmission de la carte, elle est disponible dans sa datasheet.

La ligne append dans lilo.conf est limitée à 255 caractères.

Après un redémarrage, on vérifie que nos ports sont bien installés :

 
Sélectionnez
# ls /proc/xenomai/rtdm/
fildes         open_fildes       rtser0/  rtser2/  rtser4/   rtser6/
named_devices  protocol_devices  rtser1/  rtser3/   rtser5/   rtser7/

Si le driver est compilé en tant que module noyau, les paramètres sont des options de la commande de chargement insmod. Ils sont alors débarrassés de leur préfixe xeno_16550A.

 
Sélectionnez
# insmod /lib/modules/`uname -r`/kernel/drivers/xenomai/serial/xeno_16550A.ko io=0x1000 irq=10 baudrate=921600

Les autres paramètres possibles sont le redimensionnement du buffer de transmission (tx_fifo, positionné à 16 octets par défaut(41)) et le numéro de départ des rtsern.

XIII-A-4. Configuration de la liaison avec l'ioctlRTSER_RTIOC_SET_CONFIG

Cet ioctl utilise la structure rtser_config, dont les champs sont décrits ci-dessous :

config_mask Les paramètres de la liaison série ne sont pas positionnables séparément puisqu'à chaque fois la structure rtser_config est passée en argument. Ce masque permet de choisir les champs mis à jour.
baud_rate Vitesse de la liaison.
parity Parité.
data_bits Nombre de bits de données.
stop_bits Nombre de bits de stop.
handshake Handshaking.
fifo_depth Seuil de réception des données.
rx_timeout Temps d'attente des données lors de l'appel à rt_dev_read (en nanosecondes).
RTSER_TIMEOUT_INFINITE provoque une lecture bloquante.
RTSER_TIMEOUT_NONE sort immédiatement si aucune donnée n'a été reçue, dans ce cas rt_dev_read retourne -EAGAIN.
tx_timeout Temps de transmission des données (en nanosecondes). Si dans le rt_dev_write, les données n'ont pas été transmises pendant ce laps de temps, alors -ETIMEDOUT est retourné.
event_timeout Utilisé par l'ioctl RTSER_RTIOC_WAIT_EVENT. Détermine le timeout sur la réception de l'évènement.
timestamp_history Utilisé par l'ioctl RTSER_RTIOC_WAIT_EVENT. Active la datation des données reçues.
event_mask Utilisé par l'ioctl RTSER_RTIOC_WAIT_EVENT. Événement sur lequel devient passant cet ioctl : état de la ligne, réception de données, erreurs.

XIII-B. Comment écrire un driver PCI ?

Ce chapitre a pour but de présenter de façon simple et succincte comment interfacer des périphériques PCI sous Xenomai.

XIII-B-1. Documentation de référence

Les documents suivants ont servi à la rédaction de ce chapitre :

Web : http://en.wikipedia.org/wiki/Conventional_PCI ;

Web : http://en.wikipedia.org/wiki/PCI_configuration_space ;

Livre : Essential Linux Device Drivers (ISBN-13: 978-0132396554) ;

Texte : <Linux Sources>/Documentation/PCI/pci.txt.

XIII-B-2. Configuration vs initialisation

Au démarrage du système, sur x86, le BIOS se charge de configurer les périphériques PCI ; sur d'autres plateformes, c'est le noyau Linux qui se charge de ce travail.

Mais, quelle que soit la cible matérielle, lorsque vous arrivez pour initialiser votre driver Linux/Xenomai, le périphérique PCI est déjà « configuré », vous n'avez pas à le faire. On entend par périphérique PCI « configuré », le fait que sa plage d'adresse mémoire est déjà réservée, que son IRQ lui est attribué, etc. ; le périphérique n'est pas encore initialisé !

L'initialisation est en effet spécifique à chaque périphérique et c'est le rôle du driver. Un driver PCI Xenomai va utiliser Linux pour découvrir la configuration du périphérique, récupérer son IRQ et les plages d'adresses d'IO ou mémoire qu'il utilise. On ne va pas chercher à ce que Linux puisse communiquer avec ce périphérique via notre driver. On utilise Linux ici comme un moyen de « découvrir » le bus PCI, rien d'autre.

L'utilisation de l'API PCI Linux et des fonctions appropriées de manipulation des IO/mémoire permet de créer des drivers PCI multiplateformes.

XIII-B-3. L'écriture d'un driver étape par étape

Un driver PCI Xenomai doit :

 
Sélectionnez
static struct pci_device_id peak_pci_tbl[] = {
      {PEAK_PCI_VENDOR_ID, PEAK_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
      { }
};
MODULE_DEVICE_TABLE (pci, peak_pci_tbl);

Comme pour la majorité des « couches driver » Linux, il convient également d'enregistrer les callbacks qui seront appelées pour piloter notre périphérique PCI. C'est par exemple le cas sur tty, carte réseau, USB, etc. Ici, on se concentre uniquement sur un driver PCI et Xenomai.

Par conséquent, on ne va utiliser que le strict minimum fourni par Linux : les fonctions de « probe » et de « remove » appelées par Linux quand il découvre un périphérique PCI ou à l'arrêt du système. Les autres callbacks ne nous concernent pas et on ne souhaite pas de toute façon faire d'accès direct au matériel via Linux.


Concrètement, dans le code cela donne :

 
Sélectionnez
static struct pci_driver rtcan_peak_pci_driver = {
      .name     = RTCAN_DRV_NAME,
      .id_table = peak_pci_tbl,
      .probe    = peak_pci_init_one,
      .remove   = __devexit_p(peak_pci_remove_one),
};
 
static int __init rtcan_peak_pci_init(void)
{
    return pci_register_driver(&rtcan_peak_pci_driver);
}
 
Sélectionnez
pci_read_config_word(pdev, 0x2e, &sub_sys_id)

Un autre exemple tiré du livre Essential Linux Device Drivers(42), pour lire l'IRQ associé à la carte :

 
Sélectionnez
unsigned char irq ;
pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq);

Vous n'avez donc pas besoin de savoir parmi les 64 octets de la zone de configuration lequel indique l'IRQ attribuée par le BIOS, la macro PCI_INTERRUPT_LINE que l'on trouve dans include/linux/pci_regs.h suffit. Référez-vous à ce fichier pour plus d'informations sur les macros et valeurs récupérables dans la zone de configuration de votre carte PCI.

Chaque périphérique PCI possède jusqu'à six « Base Address Register » ou « BAR ». Qu'est-ce que c'est ?

De façon simple, parmi les 64 octets de configuration, 6*4 octets indiquent des zones mémoire que la carte PCI va utiliser. L'utilisation de ces six plages d'adresses est laissée à la discrétion du constructeur de la carte. Il convient de se reporter à la configuration de la carte pour savoir combien de ces « BAR » sont utilisés.

Parmi ces « BAR », certains font référence à des adresses d'IO, d'autres des adresses mémoire ; encore une fois c'est la documentation de la carte qui l'indique.

  • soit en s'adressant au constructeur.
  • soit en « rétrofitant » à partir des sources Linux.

Pour développer un driver PCI, il vous faut impérativement la documentation « développeur » de la carte. Il y a deux manières de l'obtenir.
L'idéal étant tout de même d'avoir les deux (la théorie et la pratique qui fonctionne).
Cependant, certains constructeurs ne jouent pas forcément le jeu et ne fournissent qu'un module Linux (fichier .ko) avec leur carte. Ceci doit donc faire l'objet d'une attention particulière lors de la sélection du matériel.
À noter également que certains fabricants vendent séparément le code de leurs drivers Linux ou les fournissent gratuitement contre la signature d'un accord de confidentialité (43) (TEWS Technologies par exemple).

Linux fournit dans son API PCI les fonctions pour connaître si un « BAR » est défini et vers quelle plage d'adressage il renvoie. Avec ces fonctions Linux, il est ainsi possible de récupérer lors de la phase d'initialisation du driver les plages d'adresses qui vont nous intéresser et ainsi pouvoir y lire/écrire pour piloter notre matériel. Vous pouvez vous reporter aux fonctions pci_resource_[start|len|end|flags] qui prennent en premier argument le « descripteur PCI » passé par Linux et en second argument le numéro de BAR.

Exemple :

 
Sélectionnez
unsigned long addr;
addr = pci_resource_start(pdev, 0);

Note : le bus PCI 32 bits manipule des adresses de 32 bits, il est donc normal d'utiliser des « unsigned long » au lieu de « pointeurs » qui eux ont une taille qui varie suivant le bus d'adresse du processeur. Les « long » sont définis par la norme ANSI C sur 32 bits et ne varient pas suivant les compilateurs/plateformes. Le code est ainsi portable.

L'accès en lecture/écriture vers le périphérique se fait soit dans les IO (méthode d'accès PIO) soit directement en zone mappée en mémoire (méthode d'accès MMIO). C'est la documentation du périphérique qui donne ces informations. Au niveau de la programmation du driver, la seule chose qui change est l'API qui doit être utilisée.

·     io_base = pci_ressouce_start(my_pci_dev, 0) ; è On obtient ainsi l'adresse de base de BAR0

·     request_region(io_base, 1024, “my_driver_xenomai_pci”); è On annonce à Linux qu'on va utiliser 1024 octets à partir de l'adresse de base de BAR0. Ici, on suppose que la documentation du périphérique nous a donné cette information. Cette fonction sert surtout à vérifier qu'un autre driver n'accède pas déjà à cette zone. Auquel cas il y a conflit (chevauchement) et il convient de retourner un code d'erreur.

·     data = inb(io_base + 5) ; è On accède au 6e octet de la zone IO BAR0. On pourrait y écrire de la même manière : outb(0x01, iobase+5) è On écrit 0x01 dans le 6e octet de la zone IO BAR0.

·     io_base = pci_ressouce_start(my_pci_dev, 0); è On obtient ainsi l'adresse de base de BAR0.

·     io_longueur = pci_resource_length(my_pci_dev, 0); è On obtient la longueur annoncée de la zone mémoire BAR0. Ici, l'information a directement été trouvée dans la configuration PCI. On n'a pas recours à la documentation du périphérique.

·     request_mem_region(io_base, io_longueur, "my_driver_xenomai_pci"); è On annonce  Linux qu'on va utiliser la zone mémoire démarrant à l'adresse de base « io_base » et de longueur « io_longueur » octets. Cette fonction sert surtout à vérifier qu'un autre driver n'accède pas déjà à cette zone. Auquel cas il y a conflit et il convient de retourner un code d'erreur.

Note : pci_request_region est une fonction fournie par Linux qui permet de faire les trois étapes ci-dessus à la fois.

·    L'adresse mémoire « noyau » que vous pourrez accéder dans votre driver n'a pas la même valeur que celle trouvée par BAR0 et c'est tout à fait normal. En effet, le noyau utilise des adresses virtuelles, et c'est la MMU qui fait la translation « adresse noyau à adresse bus ». Pour obtenir l'équivalent « noyau » de notre adresse « bus » il faut utiliser la fonction ioremap().


Exemple :
adresse_noyau = ioremap(io_base, io_longueur) ;


Parfois, ces zones mappées en mémoire et qui correspondent au périphérique PCI, ne supportent pas d'être mises en cache par le processeur. En effet, lors d'un accès au bus mémoire (méthode MMIO), le processeur peut mettre dans son cache interne ces données pour en accélérer l'obtention lors d'un prochain accès. Mais cette « mémoire » est contrôlée directement par le périphérique et peut changer de valeur spontanément, lorsque le périphérique la met à jour. Ainsi, le cache du processeur peut contenir une valeur obsolète qui serait retournée lors du prochain read[b|l|..].

ioremap_nocache() est la fonction identique à ioremap() décrite ci-dessus, mais garantit que l'adresse noyau retournée ne sera pas mise en cache par le CPU.

Note 1 : la fonction pci_resource_flag() donne accès à ce type d'information pour savoir si la zone « BAR » peut être mise en cache ou non par le processeur.

Note 2 : voir lib/iomap.c pour l'utilisation de la fonction pci_iomap() qui regroupe en une seule opération celles décrites jusqu'ici.

·    data = readb(adresse_noyau+5)  è On accède au 6e octet de la zone mémoire BAR0. On pourrait écrire de la même manière : writeb(0x01, adresse_noyau +5) è On écrit 0x01 dans le 6e octet de la zone mémoire BAR0.

Astuce : concernant les « réservations » d'IO ou mémoire faites par le driver, vous pouvez les observer à travers /proc/ioports et /proc/iomem.

Voici l'exemple du driver CAN SJA1000 fourni avec Xenomai : rtan_peak_pci.c. Les fonctions mentionnées ci-dessus y sont surlignées. Ce driver succinct permet de retrouver les étapes qu'il faut pour mettre en œuvre un driver Xenomai PCI. Dans ce driver, la méthode d'accès est MMIO.

Ce code ne contient que la partie PCI « visible », le pilotage de la carte fait appel à d'autres fichiers source.

 
Sélectionnez
/*
 * Copyright (C) 2006 Wolfgang Grandegger <wg@grandegger.com>
 *
 * Derived from the PCAN project file driver/src/pcan_pci.c:
 *
 * Copyright (C) 2001-2006  PEAK System-Technik GmbH
 *
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 */
 
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <asm/io.h>
 
#include <rtdm/rtdm_driver.h>
 
/* CAN device profile */
#include <rtdm/rtcan.h>
#include <rtcan_dev.h>
#include <rtcan_raw.h>
#include <rtcan_sja1000.h>
#include <rtcan_sja1000_regs.h>
 
#define RTCAN_DEV_NAME    "rtcan%d"
#define RTCAN_DRV_NAME    "PEAK-PCI-CAN"
 
static char *peak_pci_board_name = "PEAK-PCI";
 
MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>");
MODULE_DESCRIPTION("RTCAN board driver for PEAK-PCI cards");
MODULE_SUPPORTED_DEVICE("PEAK-PCI card CAN controller");
MODULE_LICENSE("GPL");
 
struct rtcan_peak_pci
{
    struct pci_dev *pci_dev; 
    struct rtcan_device *slave_dev;
    int channel;
    volatile void __iomem *base_addr;  
    volatile void __iomem *conf_addr;
};
 
#define PEAK_PCI_CAN_SYS_CLOCK (16000000 / 2)
 
#define PELICAN_SINGLE  (SJA_CDR_CAN_MODE | SJA_CDR_CBP | 0x07 | SJA_CDR_CLK_OFF)
#define PELICAN_MASTER  (SJA_CDR_CAN_MODE | SJA_CDR_CBP | 0x07            )
#define PELICAN_DEFAULT (SJA_CDR_CAN_MODE                                 )
 
#define CHANNEL_SINGLE 0 /* this is a single channel device */
#define CHANNEL_MASTER 1 /* multi channel device, this device is master */
#define CHANNEL_SLAVE  2 /* multi channel device, this is slave */
 
// important PITA registers
#define PITA_ICR         0x00        // interrupt control register
#define PITA_GPIOICR     0x18        // general purpose IO interface control register
#define PITA_MISC        0x1C        // miscellanoes register
 
#define PEAK_PCI_VENDOR_ID   0x001C  // the PCI device and vendor IDs
#define PEAK_PCI_DEVICE_ID   0x0001
 
#define PCI_CONFIG_PORT_SIZE 0x1000  // size of the config io-memory
#define PCI_PORT_SIZE        0x0400  // size of a channel io-memory
 
static struct pci_device_id peak_pci_tbl[] = {
      {PEAK_PCI_VENDOR_ID, PEAK_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
      { }
};
MODULE_DEVICE_TABLE (pci, peak_pci_tbl);
 
 
static u8 rtcan_peak_pci_read_reg(struct rtcan_device *dev, int port)
{
    struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv;
    return readb(board->base_addr + ((unsigned long)port << 2));
}
 
static void rtcan_peak_pci_write_reg(struct rtcan_device *dev, int port, u8 data)
{
    struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv;
    writeb(data, board->base_addr + ((unsigned long)port << 2));
}
 
static void rtcan_peak_pci_irq_ack(struct rtcan_device *dev)
{
    struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv;
    u16 pita_icr_low;
 
    /* Select and clear in Pita stored interrupt */
    pita_icr_low = readw(board->conf_addr + PITA_ICR);
    if (board->channel == CHANNEL_SLAVE) {
      if (pita_icr_low & 0x0001)
          writew(0x0001, board->conf_addr + PITA_ICR);
    } 
    else {
      if (pita_icr_low & 0x0002)
          writew(0x0002, board->conf_addr + PITA_ICR);
    }
}
 
static void rtcan_peak_pci_del_chan(struct rtcan_device *dev, 
                            int init_step)
{
    struct rtcan_peak_pci *board;
    u16 pita_icr_high;
 
    if (!dev)
      return;
 
    board = (struct rtcan_peak_pci *)dev->board_priv;
 
    switch (init_step) {
    case 0:             /* Full cleanup */
      printk("Removing %s %s device %s\n", 
             peak_pci_board_name, dev->ctrl_name, dev->name);
      rtcan_sja1000_unregister(dev);
    case 5: 
      pita_icr_high = readw(board->conf_addr + PITA_ICR + 2);
      if (board->channel == CHANNEL_SLAVE) {
          pita_icr_high &= ~0x0001;
      } else {
          pita_icr_high &= ~0x0002;
      }
      writew(pita_icr_high, board->conf_addr + PITA_ICR + 2); 
    case 4:
      iounmap((void *)board->base_addr);
    case 3:
      if (board->channel != CHANNEL_SLAVE)
          iounmap((void *)board->conf_addr);
    case 2:
        rtcan_dev_free(dev);    
    case 1:
      break;
    }
 
}
 
static int rtcan_peak_pci_add_chan(struct pci_dev *pdev, int channel, 
                           struct rtcan_device **master_dev)
{
    struct rtcan_device *dev;
    struct rtcan_sja1000 *chip;
    struct rtcan_peak_pci *board;
    u16 pita_icr_high;
    unsigned long addr;
    int ret, init_step = 1;
 
    dev = rtcan_dev_alloc(sizeof(struct rtcan_sja1000),
                    sizeof(struct rtcan_peak_pci));
    if (dev == NULL)
        return -ENOMEM;
    init_step = 2;
    
    chip = (struct rtcan_sja1000 *)dev->priv;
    board = (struct rtcan_peak_pci *)dev->board_priv;
 
    board->pci_dev = pdev;
    board->channel = channel;
 
    if (channel != CHANNEL_SLAVE) {
 
      addr = pci_resource_start(pdev, 0);    
      board->conf_addr = ioremap(addr, PCI_CONFIG_PORT_SIZE); 
      if (board->conf_addr == 0) {
          ret = -ENODEV;
          goto failure;
      }
      init_step = 3;
    
      /* Set GPIO control register */
      writew(0x0005, board->conf_addr + PITA_GPIOICR + 2);  
    
      if (channel == CHANNEL_MASTER)
          writeb(0x00, board->conf_addr + PITA_GPIOICR); /* enable both */
      else
          writeb(0x04, board->conf_addr + PITA_GPIOICR); /* enable single */
      
      writeb(0x05, board->conf_addr + PITA_MISC + 3);  /* toggle reset */
      mdelay(5);
      writeb(0x04, board->conf_addr + PITA_MISC + 3);  /* leave parport mux mode */           
    } else {
      struct rtcan_peak_pci *master_board = 
          (struct rtcan_peak_pci *)(*master_dev)->board_priv;
      master_board->slave_dev = dev;
      board->conf_addr = master_board->conf_addr;
    }
 
    addr = pci_resource_start(pdev, 1);    
    if (channel == CHANNEL_SLAVE)
      addr += 0x400;
    
    board->base_addr = ioremap(addr, PCI_PORT_SIZE);
    if (board->base_addr == 0) {
      ret = -ENODEV;
      goto failure;
    }
    init_step = 4;
 
    dev->board_name = peak_pci_board_name;
 
    chip->read_reg = rtcan_peak_pci_read_reg;
    chip->write_reg = rtcan_peak_pci_write_reg;
    chip->irq_ack = rtcan_peak_pci_irq_ack;
 
    /* Clock frequency in Hz */
    dev->can_sys_clock = PEAK_PCI_CAN_SYS_CLOCK;
 
    /* Output control register */
    chip->ocr = SJA_OCR_MODE_NORMAL | SJA_OCR_TX0_PUSHPULL;
 
    /* Clock divider register */
    if (channel == CHANNEL_MASTER)
      chip->cdr = PELICAN_MASTER;
    else
      chip->cdr = PELICAN_SINGLE;
 
    strncpy(dev->name, RTCAN_DEV_NAME, IFNAMSIZ);
 
    /* Register and setup interrupt handling */
    chip->irq_flags = RTDM_IRQTYPE_SHARED;
    chip->irq_num = pdev->irq;
    pita_icr_high = readw(board->conf_addr + PITA_ICR + 2);
    if (channel == CHANNEL_SLAVE) {
      pita_icr_high |= 0x0001;
    } else {
      pita_icr_high |= 0x0002;
    }
    writew(pita_icr_high, board->conf_addr + PITA_ICR + 2); 
    init_step = 5;
      
    printk("%s: base_addr=%p conf_addr=%p irq=%d\n", RTCAN_DRV_NAME, 
         board->base_addr, board->conf_addr, chip->irq_num);
 
    /* Register SJA1000 device */
    ret = rtcan_sja1000_register(dev);
    if (ret) {
      printk(KERN_ERR
             "ERROR %d while trying to register SJA1000 device!\n", ret);
      goto failure;
    }
 
    if (channel != CHANNEL_SLAVE)
      *master_dev = dev;
 
    return 0;
 
 failure:
    rtcan_peak_pci_del_chan(dev, init_step);
    return ret;
}
 
static int __devinit peak_pci_init_one (struct pci_dev *pdev,
                              const struct pci_device_id *ent)
{
    int ret;
    u16 sub_sys_id;
    struct rtcan_device *master_dev = NULL;
 
    printk("%s: initializing device %04x:%04x\n",
         RTCAN_DRV_NAME,  pdev->vendor, pdev->device);
 
    if ((ret = pci_enable_device (pdev)))
      goto failure;
 
    if ((ret = pci_request_regions(pdev, RTCAN_DRV_NAME)))
      goto failure;
 
    if ((ret = pci_read_config_word(pdev, 0x2e, &sub_sys_id)))
      goto failure_cleanup;
    
    /* Enable memory space */
    if ((ret = pci_write_config_word(pdev, 0x04, 2)))
      goto failure_cleanup;
    
    if ((ret = pci_write_config_word(pdev, 0x44, 0)))
      goto failure_cleanup;
    
    if (sub_sys_id > 3) {
      if ((ret = rtcan_peak_pci_add_chan(pdev, CHANNEL_MASTER, 
                                 &master_dev)))
          goto failure_cleanup;
      if ((ret = rtcan_peak_pci_add_chan(pdev, CHANNEL_SLAVE, 
                                 &master_dev)))
          goto failure_cleanup;
    } else {
      if ((ret = rtcan_peak_pci_add_chan(pdev, CHANNEL_SINGLE,
                                 &master_dev)))
          goto failure_cleanup;
    }
 
    pci_set_drvdata(pdev, master_dev);
    return 0;
 
 failure_cleanup:
    if (master_dev)
      rtcan_peak_pci_del_chan(master_dev, 0);
 
    pci_release_regions(pdev);
    
 failure:
    return ret;
      
}
 
static void __devexit peak_pci_remove_one (struct pci_dev *pdev)
{
    struct rtcan_device *dev = pci_get_drvdata(pdev);
    struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv;
 
    if (board->slave_dev)
      rtcan_peak_pci_del_chan(board->slave_dev, 0);
    rtcan_peak_pci_del_chan(dev, 0);
 
    pci_release_regions(pdev);
    pci_disable_device(pdev);
    pci_set_drvdata(pdev, NULL);
}
 
static struct pci_driver rtcan_peak_pci_driver = {
      .name       = RTCAN_DRV_NAME,
      .id_table   = peak_pci_tbl,
      .probe            = peak_pci_init_one,
      .remove           = __devexit_p(peak_pci_remove_one),
};
 
static int __init rtcan_peak_pci_init(void)
{
    return pci_register_driver(&rtcan_peak_pci_driver);
}
 
 
static void __exit rtcan_peak_pci_exit(void)
{
    pci_unregister_driver(&rtcan_peak_pci_driver);
}
 
module_init(rtcan_peak_pci_init);
module_exit(rtcan_peak_pci_exit);

précédentsommairesuivant
Noyau 2.6.24.
Version 1.4.1.
Éditions Prentice Hall Open Source Software Development, ISBN-13: 978-0132396554.
En anglais NDA : Non Disclosure Agreement.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+