Architecture DMA du driver Platform ASoC sur Rockchip RK3399

Architecture des drivers Platform ASoC

Dans l'écosystème ALSA System on Chip (ASoC), le driver Platform joue un rôle crucial. Il se concentre principalement sur la gestion des flux de données audio via le DMA (Direct Memory Access) et les interfaces numériques du processeur (CPU DAI). Sa mission est double :

  • Driver DMA : Transférer les données audio du tampon mémoire (DMA buffer) vers le FIFO d'émission de l'interface I2S.
  • Driver CPU DAI : Acheminer ces données depuis le FIFO I2S vers le Codec pour la conversion analogique.

Le framework ASoC utilise des structures unifiées pour gérer ces composants, notamment snd_soc_component et snd_soc_dai_driver. Le driver Platform doit impérativement définir ses capacités via ces structures pour s'intégrer au cœur d'ALSA.

Principes fondamentaux du framwork DMA

Concept du DMA

Le DMA permet aux périphériques d'accéder directement à la mémoire système sans solliciter continuellement le processeur. Cela réduit drastiquement la charge CPU lors du transfert de gros volumes de données, comme les flux audio haute fidélité. Le contrôleur DMA gère les adresses sources, les destinations et la longueur des transferts.

Cohérence du cache

L'utilisation du DMA introduit des défis de cohérence de cache. Si le CPU modifie une donnée en cache mais pas en RAM, le DMA risque de lire une valeur obsolète. Deux stratégies de mappage existent dans le noyau Linux :

  • Mapping cohérent : Alloue une zone mémoire non-cachable, garantissant que le CPU et le DMA voient toujours la même donnée.
  • Mapping streaming : Plus complexe, il nécessite des opérations explicites de synchronisation (invalidation et flush) mais offre de meilleures performances globales.

Spécificités du matériel Rockchip RK3399

Le SoC RK3399 intègre deux contrôleurs DMA distincts (DMAC0 et DMAC1) basés sur l'IP ARM PrimeCell.

Contrôleur DMAC0

Le DMAC0 est principalement dédié aux interfaces audio et aux périphériques basse consommation. Il supporte :

  • 6 canaux simultanés.
  • Requêtes pour I2S0, I2S1, I2S2 (TX/RX) et SPDIF.
  • Une taille de burst maximale de 16.

Contrôleur DMAC1

Plus polyvalant, le DMAC1 gère les transferts pour les interfaces de communication série :

  • 8 canaux simultanés.
  • Requêtes pour UART0 à UART4 et SPI0 à SPI5.
  • Une profondeur de FIFO étendue (MFIFO).

Structures de données du DMA Engine

Le noyau Linux abstrait le matériel via le framework DMA Engine. Voici les structures fondamentales remaniées pour illustrer leur rôle :

struct dma_device_info {
    struct device *dev_ptr;
    unsigned int channel_count;
    struct list_head channels_list;
    dma_cap_mask_t capabilities;
    
    /* Fonctions de préparation des transferts */
    struct dma_async_tx_descriptor *(*prepare_memcpy)(struct dma_chan *c, ...);
    struct dma_async_tx_descriptor *(*prepare_cyclic)(struct dma_chan *c, ...);
    
    /* Contrôle du flux */
    int (*config_channel)(struct dma_chan *c, struct dma_slave_config *cfg);
    void (*trigger_pending)(struct dma_chan *c);
};

Le champ cap_mask définit les types de transferts supportés, tels que DMA_MEMCPY pour la copie mémoire à mémoire ou DMA_CYCLIC, essentiel pour l'audio car il permet de boucler sur un tampon circulaire.

Enregistrement d'un contrôleur DMA

Pour rendre un contrôleur opérationnel, le driver doit initialiser une instance de dma_device et l'enregistrer auprès du noyau. Voici une simplification de la logique d'enregistrement :

int register_audio_dma_controller(struct dma_device *dma_dev)
{
    struct dma_chan *channel_item;
    int status;

    /* Initialisation du compteur de références */
    kref_init(&dma_dev->ref);
    
    /* Attribution d'un ID unique via IDA */
    status = get_unique_dma_id(dma_dev);
    if (status < 0) return status;

    /* Enregistrement de chaque canal dans sysfs */
    list_for_each_entry(channel_item, &dma_dev->channels, device_node) {
        status = setup_dma_channel_sysfs(dma_dev, channel_item);
        if (status < 0) goto error_cleanup;
    }

    /* Ajout à la liste globale des contrôleurs */
    mutex_lock(&dma_global_lock);
    list_add_tail_rcu(&dma_dev->global_node, &dma_device_list);
    mutex_unlock(&dma_global_lock);

    return 0;

error_cleanup:
    /* Logique de désengagement en cas d'échec */
    return status;
}

Interacsion avec les clients (ASoC DMA Client)

Le driver Platform audio agit comme un "client" du DMA Engine. Le cycle de vie d'un transfert audio suit généralement ces étapes :

  1. Requête de canal : Utilisation de dma_request_chan() via l'arbre de périphériques (Device Tree).
  2. Configuration : Paramétrage via dmaengine_slave_config() (adresse du FIFO I2S, largeur de bus).
  3. Préparation : Création d'un descripteur cyclique avec dmaengine_prep_dma_cyclic().
  4. Soumission : Envoi du descripteur avec dmaengine_submit().
  5. Lancement : Déclenchement effectif via dma_async_issue_pending().

Intégration ASoC : le composant dmaengine-pcm

Pour simplifier le développement, ASoC fournit une couche générique appelée dmaengine-pcm. Elle permet d'enregistrer un composant audio DMA avec un minimum de code spécifique au matériel.

static const struct snd_soc_component_driver generic_dma_driver = {
    .name           = "snd-dmaengine-pcm",
    .open           = audio_dma_open,
    .hw_params      = audio_dma_hw_params,
    .trigger        = audio_dma_trigger,
    .pointer        = audio_dma_pointer,
};

int rk3399_audio_dma_init(struct platform_device *pdev)
{
    return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
}

Lors de l'appel à hw_params, le framework extrait les configurations DAI (comme l'adresse physique du registre TX de l'I2S) et les injecte dans la configuration dma_slave_config. Par exemple, pour le RK3399, l'adresse cible sera l'adresse de base de l'I2S0 additionnée de l'offset du registre I2S_TXDR.

Étiquettes: Rockchip RK3399 ALSA ASoC DMA Engine

Publié le 7 août à 22h55