Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX SES ALT SİSTEMİ 2026

ASoC
ALSA System on Chip Ses Sürücüsü

Codec, platform ve machine üçlü mimarisini anlayarak gömülü Linux ses sürücüsü yaz; DAPM ile güç yönetimini uygula.

00 ASoC neden var — ALSA ile farkı

Standart ALSA sürücüleri masaüstü ses kartları için tasarlanmıştır; gömülü SoC'larda bulunan codec + I2S + DMA üçlüsünü yönetmek için ASoC katmanı geliştirilmiştir.

Standart ALSA'nın yetersiz kaldığı noktalar

Klasik ALSA modeli, ses donanımını tek bir monolitik sürücü olarak ele alır. Gömülü sistemlerde ise ses alt sistemi en az üç ayrı donanım parçasından oluşur:

CodecAnalog/dijital dönüşüm, mikrofon preamp, DAC/ADC — genellikle ayrı bir I2C/SPI aygıtı (WM8960, ES8316 vb.)
Platform (SoC I2S/PCM)SoC içindeki dijital ses arayüzü ve DMA motoru — Raspberry Pi SAI, i.MX SAI, Allwinner DAI vb.
MachineCodec ile platform'u birbirine bağlayan, kart/board özgü yapıştırıcı kod — GPIO, mclk kaynağı, enerji sıralaması

Standart ALSA'da bu üçü tek sürücüde birleşirdi; codec değiştiğinde tüm sürücü yeniden yazılırdı. ASoC, her parçayı ayrı bir modül olarak kayıt ettirir ve bunları dai_link üzerinden çalışma zamanında birleştirir.

Kullanıcı alanı (ALSA lib, PulseAudio, PipeWire)
         |
    ALSA core (PCM, mixer, control)
         |
   ┌─────┴──────┐
   ASoC core
   ├── Codec sürücüsü   (snd_soc_codec_driver)
   ├── Platform sürücüsü (snd_soc_component_driver + DMA)
   └── Machine sürücüsü (snd_soc_card + dai_link)
    

ALSA vs ASoC — temel farklar

ÖzellikKlasik ALSAASoC
Sürücü yapısıTek monolitik sürücüCodec + Platform + Machine üçlüsü
Güç yönetimiManuel suspend/resumeDAPM — widget bazlı otomatik
Codec yeniden kullanımıYok — board bağımlıTam — codec sürücüsü boarddan bağımsız
Çoklu codecÇok zordai_link listesiyle doğal destek
DMA entegrasyonuSürücü içinde elle yazılırdmaengine_pcm ile generic altyapı
Device TreeKısmiaudio-graph-card / simple-audio-card ile tam DT desteği

Kaynak kodun dizin yapısı

sound/
├── core/          # ALSA core — PCM, control, timer
├── soc/           # ASoC framework
│   ├── soc-core.c         # snd_soc_register_card vb.
│   ├── soc-dapm.c         # DAPM motoru
│   ├── soc-pcm.c          # PCM ops köprüsü
│   ├── codecs/            # WM8960, ES8316, TLV320AIC3x ...
│   ├── generic/           # simple-audio-card, audio-graph-card
│   └── <platform>/       # imx/, rockchip/, bcm/ ...
└── drivers/       # USB ses, HDMI ses vb.

01 Codec sürücüsü

Codec sürücüsü, DAC/ADC ve kayıt defterlerini yöneten bileşendir; snd_soc_component_driver ve snd_soc_dai_driver ile çekirdeğe kaydedilir.

Temel veri yapıları

Çekirdek 5.x'ten itibaren snd_soc_codec kaldırılmış, yerini snd_soc_component almıştır. Her codec sürücüsü iki ana yapı tanımlar:

snd_soc_component_driverDAPM widget'ları, route'ları, kcontrol listesi, set_sysclk/set_pll gibi clock callback'leri barındırır
snd_soc_dai_driverDAI (Digital Audio Interface) yeteneklerini — format (I2S, DSP_A), hız, kanal sayısı, ops callback'leri — bildirir
/* include/sound/soc.h'den özet */
struct snd_soc_component_driver {
    const char *name;

    /* DAPM */
    const struct snd_soc_dapm_widget  *dapm_widgets;
    int                                num_dapm_widgets;
    const struct snd_soc_dapm_route   *dapm_routes;
    int                                num_dapm_routes;

    /* Kontroller */
    const struct snd_kcontrol_new     *controls;
    int                                num_controls;

    /* Callbacks */
    int  (*probe)  (struct snd_soc_component *);
    void (*remove) (struct snd_soc_component *);
    int  (*set_sysclk)(struct snd_soc_component *, int clk_id,
                       int source, unsigned int freq, int dir);
    int  (*set_pll)   (struct snd_soc_component *, int pll_id,
                       int source, unsigned int freq_in,
                       unsigned int freq_out);

    /* Kayıt defteri */
    bool (*readable_register)  (struct device *, unsigned int);
    bool (*writeable_register) (struct device *, unsigned int);
    bool (*volatile_register)  (struct device *, unsigned int);
};

struct snd_soc_dai_driver {
    const char *name;
    struct snd_soc_dai_ops  ops;         /* hw_params, set_fmt, trigger ... */
    struct snd_soc_pcm_stream playback;  /* rate, formats, channels */
    struct snd_soc_pcm_stream capture;
};

Minimal codec sürücüsü iskeleti

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <sound/soc.h>

#define MYCODEC_REG_CTRL  0x00
#define MYCODEC_REG_VOL   0x01

static const struct regmap_config mycodec_regmap = {
    .reg_bits  = 8,
    .val_bits  = 8,
    .max_register = 0x1F,
};

/* DAI ops ---------------------------------------------------------- */
static int mycodec_hw_params(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params,
                              struct snd_soc_dai *dai)
{
    struct snd_soc_component *component = dai->component;
    unsigned int rate = params_rate(params);
    /* Codec PLL / MCLK oranı burada ayarlanır */
    dev_dbg(component->dev, "hw_params: %u Hz\n", rate);
    return 0;
}

static int mycodec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
    /* I2S / LJ / RJ / DSP format seçimi */
    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
    case SND_SOC_DAIFMT_I2S:
        /* I2S bit pattern yaz */
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

static const struct snd_soc_dai_ops mycodec_dai_ops = {
    .hw_params = mycodec_hw_params,
    .set_fmt   = mycodec_set_fmt,
};

/* DAI sürücüsü ------------------------------------------------------ */
static struct snd_soc_dai_driver mycodec_dai = {
    .name = "mycodec-hifi",
    .playback = {
        .stream_name  = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates        = SNDRV_PCM_RATE_8000_192000,
        .formats      = SNDRV_PCM_FMTBIT_S16_LE |
                        SNDRV_PCM_FMTBIT_S24_LE,
    },
    .capture = {
        .stream_name  = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates        = SNDRV_PCM_RATE_8000_48000,
        .formats      = SNDRV_PCM_FMTBIT_S16_LE,
    },
    .ops = &mycodec_dai_ops,
};

/* Component sürücüsü ------------------------------------------------ */
static const struct snd_soc_component_driver mycodec_component = {
    .name = "mycodec",
    /* DAPM widget/route ve kontroller sonraki bölümlerde eklenir */
};

/* I2C probe --------------------------------------------------------- */
static int mycodec_i2c_probe(struct i2c_client *client)
{
    struct regmap *regmap;

    regmap = devm_regmap_init_i2c(client, &mycodec_regmap);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    return devm_snd_soc_register_component(&client->dev,
                                           &mycodec_component,
                                           &mycodec_dai, 1);
}

static const struct of_device_id mycodec_of_match[] = {
    { .compatible = "myvendor,mycodec" },
    {}
};
MODULE_DEVICE_TABLE(of, mycodec_of_match);

static struct i2c_driver mycodec_driver = {
    .driver = {
        .name           = "mycodec",
        .of_match_table = mycodec_of_match,
    },
    .probe = mycodec_i2c_probe,
};
module_i2c_driver(mycodec_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Örnek ASoC Codec Sürücüsü");

Kayıt defteri erişimi — regmap

Codec kayıtlarına doğrudan i2c_smbus_* yerine regmap API'si kullanılır. Bu sayede önbellek, bulk-read, bit-field güncelleme otomatik yapılır:

/* Tek kayıt yaz */
regmap_write(regmap, MYCODEC_REG_VOL, 0xB0);

/* Bit alanı güncelle (diğer bitlere dokunma) */
regmap_update_bits(regmap, MYCODEC_REG_CTRL,
                   BIT(3) | BIT(2),   /* mask */
                   BIT(3));            /* value */

/* Oku */
unsigned int val;
regmap_read(regmap, MYCODEC_REG_VOL, &val);

02 Platform sürücüsü — I2S ve DMA

Platform sürücüsü, SoC'un dijital ses arayüzünü (I2S, PCM, SAI) ve DMA motorunu yönetir; snd_soc_component_driver ve snd_dmaengine_pcm altyapısıyla kaydedilir.

Temel görevler

I2S / SAI donanımıBit clock (BCLK), frame sync (LRCLK), MCLK üretimi; TX/RX FIFO yönetimi
DMA kanalıSes tamponunu FIFO'ya (playback) veya FIFO'dan belleğe (capture) DMA ile aktarma
PCM opsopen/close/hw_params/prepare/trigger/pointer callback'leri — ALSA PCM makinesiyle konuşur

dmaengine_pcm ile generic platform sürücüsü

Çoğu modern SoC, özel bir PCM sürücüsü yazmak yerine devm_snd_dmaengine_pcm_register() ile hazır altyapıyı kullanır:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>

struct myplat_priv {
    void __iomem   *base;
    struct clk     *mclk;
    struct clk     *bclk;
    /* tx/rx DMA kanalı bilgisi */
    struct snd_dmaengine_dai_dma_data tx_dma_data;
    struct snd_dmaengine_dai_dma_data rx_dma_data;
};

static int myplat_dai_hw_params(struct snd_pcm_substream *substream,
                                 struct snd_pcm_hw_params *params,
                                 struct snd_soc_dai *dai)
{
    struct myplat_priv *priv = snd_soc_dai_get_drvdata(dai);
    unsigned int rate    = params_rate(params);
    unsigned int bclk_hz = rate * params_channels(params) *
                           params_physical_width(params);

    /* BCLK frekansını ayarla */
    clk_set_rate(priv->bclk, bclk_hz);

    /* I2S donanım kayıtlarını yapılandır */
    /* writel(I2S_CTRL_ENABLE | fmt_bits, priv->base + I2S_CTRL); */
    return 0;
}

static int myplat_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
    /* master/slave, I2S/LJ seçimi */
    return 0;
}

static const struct snd_soc_dai_ops myplat_dai_ops = {
    .hw_params = myplat_dai_hw_params,
    .set_fmt   = myplat_dai_set_fmt,
};

static struct snd_soc_dai_driver myplat_dai_drv = {
    .name = "myplat-i2s",
    .playback = {
        .stream_name  = "Playback",
        .channels_min = 1, .channels_max = 8,
        .rates        = SNDRV_PCM_RATE_8000_192000,
        .formats      = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
    },
    .capture = {
        .stream_name  = "Capture",
        .channels_min = 1, .channels_max = 2,
        .rates        = SNDRV_PCM_RATE_8000_48000,
        .formats      = SNDRV_PCM_FMTBIT_S16_LE,
    },
    .ops = &myplat_dai_ops,
};

static const struct snd_soc_component_driver myplat_component = {
    .name = "myplat-i2s",
};

static const struct snd_dmaengine_pcm_config myplat_dmaengine_cfg = {
    .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};

static int myplat_probe(struct platform_device *pdev)
{
    struct myplat_priv *priv;
    struct resource *res;
    int ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    priv->mclk = devm_clk_get(&pdev->dev, "mclk");
    priv->bclk = devm_clk_get(&pdev->dev, "bclk");

    /* DMA adres bilgisi — FIFO TX/RX adresi */
    priv->tx_dma_data.addr        = res->start + I2S_TX_FIFO;
    priv->tx_dma_data.addr_width  = DMA_SLAVE_BUSWIDTH_4_BYTES;
    priv->tx_dma_data.maxburst    = 16;
    priv->rx_dma_data.addr        = res->start + I2S_RX_FIFO;
    priv->rx_dma_data.addr_width  = DMA_SLAVE_BUSWIDTH_4_BYTES;
    priv->rx_dma_data.maxburst    = 16;

    platform_set_drvdata(pdev, priv);

    ret = devm_snd_soc_register_component(&pdev->dev,
                                          &myplat_component,
                                          &myplat_dai_drv, 1);
    if (ret)
        return ret;

    return devm_snd_dmaengine_pcm_register(&pdev->dev,
                                           &myplat_dmaengine_cfg, 0);
}

static const struct of_device_id myplat_of_match[] = {
    { .compatible = "myvendor,myplat-i2s" },
    {}
};
MODULE_DEVICE_TABLE(of, myplat_of_match);

static struct platform_driver myplat_driver = {
    .driver = { .name = "myplat-i2s", .of_match_table = myplat_of_match },
    .probe  = myplat_probe,
};
module_platform_driver(myplat_driver);

DMA tampon parametreleri

ParametreAçıklamaTipik değer
buffer_bytes_maxDMA tamponunun azami boyutu512 KB – 4 MB
period_bytes_minTek DMA aktarımının asgari boyutu256 B
period_bytes_maxTek DMA aktarımının azami boyutu64 KB
periods_minEn az kaç dönem (kesme) olacağı2
periods_maxEn fazla dönem sayısı128

03 Machine sürücüsü

Machine sürücüsü, codec ve platform bileşenlerini bir snd_soc_card altında dai_link dizisiyle birleştiren, board özgü yapıştırıcı katmandır.

snd_soc_card ve dai_link

#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/soc.h>

/* dai_link ops: format, saatin kaynağı vb. */
static int myboard_hw_params(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd    = substream->private_data;
    struct snd_soc_dai         *cpu_dai   = asoc_rtd_to_cpu(rtd, 0);
    struct snd_soc_dai         *codec_dai = asoc_rtd_to_codec(rtd, 0);
    unsigned int mclk = 12288000; /* 256 * 48000 */

    /* Codec MCLK'ını ayarla */
    int ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN);
    if (ret)
        return ret;

    /* CPU DAI formatı: I2S, codec is master */
    ret = snd_soc_dai_set_fmt(cpu_dai,
              SND_SOC_DAIFMT_I2S         |
              SND_SOC_DAIFMT_NB_NF       |
              SND_SOC_DAIFMT_CBP_CFP);   /* codec BCLK+FS üretiyor */
    if (ret)
        return ret;

    return snd_soc_dai_set_fmt(codec_dai,
              SND_SOC_DAIFMT_I2S         |
              SND_SOC_DAIFMT_NB_NF       |
              SND_SOC_DAIFMT_CBP_CFP);
}

static const struct snd_soc_ops myboard_ops = {
    .hw_params = myboard_hw_params,
};

SND_SOC_DAILINK_DEFS(hifi,
    DAILINK_COMP_ARRAY(COMP_CPU("myplat-i2s")),
    DAILINK_COMP_ARRAY(COMP_CODEC("mycodec.1-001a", "mycodec-hifi")),
    DAILINK_COMP_ARRAY(COMP_PLATFORM("myplat-i2s")));

static struct snd_soc_dai_link myboard_dai_links[] = {
    {
        .name         = "MyBoard HiFi",
        .stream_name  = "HiFi",
        .ops          = &myboard_ops,
        SND_SOC_DAILINK_REG(hifi),
    },
};

static struct snd_soc_card myboard_card = {
    .name      = "MyBoard",
    .owner     = THIS_MODULE,
    .dai_link  = myboard_dai_links,
    .num_links = ARRAY_SIZE(myboard_dai_links),
};

static int myboard_probe(struct platform_device *pdev)
{
    myboard_card.dev = &pdev->dev;
    return devm_snd_soc_register_card(&pdev->dev, &myboard_card);
}

static const struct of_device_id myboard_of_match[] = {
    { .compatible = "myvendor,myboard-audio" },
    {}
};

static struct platform_driver myboard_driver = {
    .driver = { .name = "myboard-audio", .of_match_table = myboard_of_match },
    .probe  = myboard_probe,
};
module_platform_driver(myboard_driver);

DAIFMT sabitleri

SabitAnlamı
SND_SOC_DAIFMT_I2SStandart I2S — 1 BCLK gecikmeli, simetrik
SND_SOC_DAIFMT_LEFT_JLeft Justified — gecikmesiz, MSB önce
SND_SOC_DAIFMT_DSP_APCM/TDM — tek LRCLK darbesi, A modu
SND_SOC_DAIFMT_CBP_CFPCodec = BCLK üretici & FS üretici (master)
SND_SOC_DAIFMT_CBC_CFCCPU = BCLK üretici & FS üretici (CPU master)
SND_SOC_DAIFMT_NB_NFNormal BCLK polarity, Normal Frame polarity

04 DAPM — Dynamic Audio Power Management

DAPM, ses sinyali akışını bir yönlü çizge (directed graph) olarak modelleyerek yalnızca aktif yollar üzerindeki bileşenlere güç uygular; güç durumu değişiklikleri tamamen otomatiktir.

DAPM çizge modeli

[Mikrofon] --> [MIC PGA] --> [ADC] --> [AIF TX] --> CPU Capture
CPU Playback --> [AIF RX] --> [DAC] --> [HP Mixer] --> [HP PGA] --> [Kulaklık]
                                     +--> [SPK Mixer] --> [SPK PGA] --> [Hoparlör]
    

Widget türleri

SND_SOC_DAPM_INPUT / OUTPUTFiziksel pin — mikrofon, hoparlör, hat girişi/çıkışı
SND_SOC_DAPM_PGAProgrammable Gain Amplifier — tek girişli yükselteç
SND_SOC_DAPM_MIXERÇok girişli karıştırıcı — her giriş için bir kontrol biti
SND_SOC_DAPM_MUXÇoktan-bire seçici — enum kontrol
SND_SOC_DAPM_ADC / DACAnalog-dijital / Dijital-analog dönüştürücü bloğu
SND_SOC_DAPM_AIF_IN / AIF_OUTDijital ses arayüzü giriş/çıkışı — CPU ile codec arasındaki köprü
SND_SOC_DAPM_SUPPLYGüç kaynağı — aktif yol varsa otomatik enable, yoksa disable
SND_SOC_DAPM_REGULATOR_SUPPLYRegülatör beslemesi — DAPM tarafından açılıp kapatılır

Widget ve route tanımı

/* Codec sürücüsü içinde */
static const struct snd_soc_dapm_widget mycodec_dapm_widgets[] = {
    /* Fiziksel pinler */
    SND_SOC_DAPM_INPUT("MICIN"),
    SND_SOC_DAPM_OUTPUT("HPOUT"),
    SND_SOC_DAPM_OUTPUT("SPKOUT"),

    /* ADC / DAC blokları */
    SND_SOC_DAPM_ADC("ADC", "Capture",  MYCODEC_REG_PWR, 3, 0),
    SND_SOC_DAPM_DAC("DAC", "Playback", MYCODEC_REG_PWR, 2, 0),

    /* Yükselteçler */
    SND_SOC_DAPM_PGA("HP PGA",  MYCODEC_REG_OUT, 7, 0, NULL, 0),
    SND_SOC_DAPM_PGA("MIC PGA", MYCODEC_REG_IN,  7, 0, NULL, 0),

    /* Mixer — birden fazla giriş */
    SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0,
                        hp_mixer_controls,
                        ARRAY_SIZE(hp_mixer_controls)),

    /* Dijital ses arayüzü */
    SND_SOC_DAPM_AIF_IN("AIF RX",  "Playback", 0, SND_SOC_NOPM, 0, 0),
    SND_SOC_DAPM_AIF_OUT("AIF TX", "Capture",  0, SND_SOC_NOPM, 0, 0),
};

/* Route tablosu: { "Hedef widget", "Hedef kontrol (NULL=direkt)", "Kaynak widget" } */
static const struct snd_soc_dapm_route mycodec_dapm_routes[] = {
    /* Capture yolu */
    { "MIC PGA",  NULL,    "MICIN"   },
    { "ADC",      NULL,    "MIC PGA" },
    { "AIF TX",   NULL,    "ADC"     },

    /* Playback yolu */
    { "AIF RX",   NULL,    "DAC"     },  /* DAI <-> DAC */
    { "DAC",      NULL,    "AIF RX"  },
    { "HP Mixer", "DAC Switch", "DAC" },
    { "HP PGA",   NULL,    "HP Mixer"},
    { "HPOUT",    NULL,    "HP PGA"  },
};

DAPM tetikleme mekanizması

Uygulama snd_pcm_start() çağırdığında ASoC çekirdeği aktif akış yollarını hesaplar, yol üzerindeki her widget'ın güç bitini sırayla yazar. Uygulama durdurduğunda aynı işlem ters sırayla gerçekleşir. Geliştiricinin güç yönetimi kodu yazmasına gerek yoktur.

/* Manuel DAPM olayı — nadir durumlarda */
snd_soc_dapm_force_enable_pin(dapm, "HP PGA");
snd_soc_dapm_sync(dapm);

/* Pin durumu sorgula */
int active = snd_soc_dapm_get_pin_status(dapm, "HPOUT");

05 Kontroller — kcontrol ve mixer

ALSA kontrolleri, kullanıcı alanındaki amixer ve ses sunucularının ses düzeyi, EQ, route seçimi gibi parametreleri okumasını/yazmasını sağlar; ASoC'ta snd_kcontrol_new makrolarıyla tanımlanır.

Hazır kontrol makroları

/* SOC_SINGLE: tek kayıt, tek bit alanı */
SOC_SINGLE("Headphone Volume",
           MYCODEC_REG_VOL,  /* kayıt adresi */
           0,                 /* shift */
           63,                /* max değer */
           0)                 /* invert */

/* SOC_DOUBLE_R: sol/sağ kanal için ayrı kayıtlar */
SOC_DOUBLE_R("PCM Playback Volume",
             MYCODEC_REG_DACL, MYCODEC_REG_DACR,
             0, 255, 0)

/* SOC_ENUM_SINGLE: kayıt içinde enum seçimi */
static const char * const mycodec_deemph_text[] = {
    "None", "32kHz", "44.1kHz", "48kHz"
};
static SOC_ENUM_SINGLE_DECL(mycodec_deemph_enum,
                             MYCODEC_REG_CTRL, 1,
                             mycodec_deemph_text);
SOC_ENUM("De-Emphasis", mycodec_deemph_enum)

Özel get/put callback'li kontrol

static int mycodec_vol_get(struct snd_kcontrol *kcontrol,
                            struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_component *component =
        snd_soc_kcontrol_component(kcontrol);
    unsigned int val;
    regmap_read(component->regmap, MYCODEC_REG_VOL, &val);
    ucontrol->value.integer.value[0] = val & 0x3F;
    return 0;
}

static int mycodec_vol_put(struct snd_kcontrol *kcontrol,
                            struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_component *component =
        snd_soc_kcontrol_component(kcontrol);
    long vol = ucontrol->value.integer.value[0];
    regmap_write(component->regmap, MYCODEC_REG_VOL, (unsigned int)vol);
    return 0;
}

static const struct snd_kcontrol_new mycodec_controls[] = {
    SOC_SINGLE_EXT("Custom Volume", 0, 0, 63, 0,
                   mycodec_vol_get, mycodec_vol_put),
};

DAPM içi mixer kontrol örneği

static const struct snd_kcontrol_new hp_mixer_controls[] = {
    SOC_DAPM_SINGLE("DAC Switch",    MYCODEC_REG_HPMIX, 0, 1, 0),
    SOC_DAPM_SINGLE("Line-In Switch",MYCODEC_REG_HPMIX, 1, 1, 0),
};

06 Device Tree ASoC binding

Device Tree, codec ve platform düğümlerini tanımlar; simple-audio-card veya audio-graph-card ile machine sürücüsü yazmadan ses kartı oluşturulabilir.

simple-audio-card

Tek bir DAI link'e sahip basit boardlar için en uygun yaklaşım. Machine sürücüsü yazmaya gerek yoktur; kernel genelindeki simple-audio-card sürücüsü DT'yi okuyarak kartı oluşturur.

/* board.dts */
/ {
    sound {
        compatible = "simple-audio-card";
        simple-audio-card,name = "MyBoard-Audio";
        simple-audio-card,format = "i2s";
        simple-audio-card,mclk-fs = <256>;

        simple-audio-card,cpu {
            sound-dai = <&sai1>;        /* platform DAI */
        };

        simple-audio-card,codec {
            sound-dai = <&wm8960>;      /* codec DAI */
            clocks    = <&mclk>;
        };
    };

    /* I2C üzerindeki codec */
    &i2c1 {
        wm8960: audio-codec@1a {
            compatible   = "wlf,wm8960";
            reg          = <0x1a>;
            #sound-dai-cells = <0>;
            clocks       = <&mclk>;
            clock-names  = "mclk";
            wlf,shared-lrclk;
        };
    };

    /* SoC SAI (I2S) bloku */
    &sai1 {
        status = "okay";
        #sound-dai-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_sai1>;
    };
};

audio-graph-card

Birden fazla codec veya karmaşık routing söz konusu olduğunda audio-graph-card tercih edilir. Bağlantılar port / endpoint düğümleriyle graf olarak tanımlanır:

/ {
    sound {
        compatible = "audio-graph-card";
        label = "MyBoard-Multi";
        dais = <&sai1_port>;   /* CPU tarafı portları */
    };

    &sai1 {
        #sound-dai-cells = <0>;
        sai1_port: port {
            sai1_endpoint: endpoint {
                remote-endpoint = <&codec_endpoint>;
                dai-format = "i2s";
                bitclock-master;
                frame-master;
            };
        };
    };

    &i2c1 {
        es8316: audio-codec@11 {
            compatible = "everest,es8316";
            reg = <0x11>;
            #sound-dai-cells = <0>;
            port {
                codec_endpoint: endpoint {
                    remote-endpoint = <&sai1_endpoint>;
                };
            };
        };
    };
};

sound-dai-cells

#sound-dai-cells = <0>Codec tek bir DAI sunuyor — phandle'a argüman eklenmez
#sound-dai-cells = <1>Birden fazla DAI (örn. HDMI + analog) — hangi DAI'nin kullanılacağı sayısal argümanla belirtilir

07 Gerçek dünya codec örnekleri

WM8960, ES8316 ve TLV320AIC3x, gömülü Linux projelerinde en sık kullanılan codec'lerdir; kaynak kodu ve DT binding'leri incelenerek ASoC yapısı somutlaştırılabilir.

WM8960 — Wolfson / Cirrus Logic

Kayıt yolu: sound/soc/codecs/wm8960.c

ArayüzI2C (7-bit adres 0x1A / 0x1B)
ÖzelliklerStereo DAC + stereo ADC, dahili PLL, HP amplifikatör, SPK amplifikatör, AGC
DT compatiblewlf,wm8960
Özel DT özellikleriwlf,shared-lrclk, wlf,hp-cfg, wlf,gpio-cfg
/* WM8960 DT örneği */
&i2c1 {
    wm8960: codec@1a {
        compatible      = "wlf,wm8960";
        reg             = <0x1a>;
        #sound-dai-cells = <0>;
        clocks          = <&clk_out2>;
        clock-names     = "mclk";
        wlf,shared-lrclk;
        wlf,hp-cfg      = <3 2 3>;
    };
};

ES8316 — Everest Semi

Kayıt yolu: sound/soc/codecs/es8316.c

ArayüzI2C (adres 0x10 veya 0x11)
Özellikler24-bit DAC/ADC, dahili LDO, kulaklık algılama, düşük güç tüketimi; Rockchip SBC'lerde yaygın
DT compatibleeverest,es8316
&i2c0 {
    es8316: codec@11 {
        compatible       = "everest,es8316";
        reg              = <0x11>;
        #sound-dai-cells = <0>;
        clocks           = <&cru SCLK_I2S_8CH_OUT>;
        clock-names      = "mclk";
        pinctrl-names    = "default";
        pinctrl-0        = <&headphone_det>;
        hp-det-gpio      = <&gpio0 RK_PB0 GPIO_ACTIVE_LOW>;
    };
};

TLV320AIC3x — Texas Instruments

Kayıt yolu: sound/soc/codecs/tlv320aic3x.c

ArayüzI2C (4 adres seçeneği: 0x18–0x1B)
ÖzelliklerSayfa tabanlı kayıt haritası (256 sayfa), çok kanallı TDM, MiniDSP, gelişmiş EQ
DT compatibleti,tlv320aic3x
Sayfa geçişiKayıt 0x00 yazarak — regmap sayfalama bunu otomatik yönetir
&i2c2 {
    aic3x: codec@18 {
        compatible       = "ti,tlv320aic3x";
        reg              = <0x18>;
        #sound-dai-cells = <0>;
        reset-gpios      = <&gpio3 12 GPIO_ACTIVE_LOW>;
        ai3x-micbias-vg  = <2>;   /* 2.5V */
    };
};

Codec seçim kılavuzu

CodecGüçKaliteÖne çıkan özellikTipik kullanım
WM8960OrtaYüksekDahili SPK amp, AGCRaspberry Pi, i.MX tabanlı boardlar
ES8316DüşükOrta-YüksekDüşük güç, HP algılamaRockchip SBC, taşınabilir cihaz
TLV320AIC3xOrtaÇok yüksekMiniDSP, TDM, çok kanallıEndüstriyel ses, konferans sistemleri

08 Hata ayıklama

/proc/asound/, amixer, aplay ve DAPM grafiği dump araçlarıyla ASoC sorunlarını teşhis et.

/proc/asound/ ile ses alt sistemi durumu

# Kayıtlı ses kartlarını listele
cat /proc/asound/cards

# Aktif PCM akışlarını gör
cat /proc/asound/pcm

# Kart 0'ın tüm kontrol bilgileri
cat /proc/asound/card0/id

# Codec kayıt defterini dump et (regmap)
cat /proc/asound/card0/codec#0/regs    # sürücü destekliyorsa

amixer ile kontrol düzenleme

# Tüm kontrolleri listele
amixer -c 0 contents

# Belirli kontrolü oku
amixer -c 0 cget name='Headphone Volume'

# Kulaklık sesini ayarla
amixer -c 0 cset name='Headphone Volume' 50

# DAPM path kontrolü
amixer -c 0 cset name='HP Mixer DAC Switch' on

aplay / arecord ile ses testi

# Cihazları listele
aplay -l
arecord -l

# 48 kHz, stereo, 16-bit sinüs sesi çal
speaker-test -c 2 -r 48000 -t sine -D hw:0,0

# Dosya kaydet (10 saniye)
arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 -d 10 /tmp/test.wav

# Kaydı çal
aplay /tmp/test.wav

DAPM grafiği dump

# DAPM widget listesi ve güç durumu
cat /sys/kernel/debug/asoc/<card-adı>/dapm_widgets

# Codec bileşeninin tam DAPM grafiği
cat /sys/kernel/debug/asoc/<card-adı>/<codec-adı>/dapm

# Aktif DAPM yolları (power=1 olanlar)
grep -A1 "power=1" /sys/kernel/debug/asoc/<card-adı>/dapm_widgets

# dot formatında grafik dışa aktar — graphviz ile görselleştir
cat /sys/kernel/debug/asoc/dapm/<card-adı>  2>/dev/null | dot -Tpng -o dapm.png

debugfs ile platform PCM bilgileri

ls /sys/kernel/debug/asoc/
# Genellikle: cards  dais  platforms  components

# DAI bağlantılarını gör
cat /sys/kernel/debug/asoc/dais

Sık karşılaşılan hatalar ve çözümleri

HataOlası nedenÇözüm
-ENODEV kart kayıtCodec I2C'de bulunamıyori2cdetect -y <bus> ile adres kontrol et
Ses yok (kayıt tamam)DAPM yolu kapalıamixer ile HP Mixer / DAC Switch'i aç
XRUN (buffer underrun)period_size çok küçükperiod_size artır, CPU yükünü düşür
Gürültü / distorsyonMCLK / BCLK oran hatasıset_sysclk frekansını kontrol et, PLL ayarla
Capture yokMICBIAS etkin değilamixer cset ile MICBIAS kontrolünü aç
-EINVAL hw_paramsDesteklenmeyen format/hızcodec ve platform DAI stream tanımını karşılaştır

Çekirdek dinamik hata ayıklama

# ASoC modülü için tüm debug mesajlarını etkinleştir
echo "module snd_soc_core +p" > /sys/kernel/debug/dynamic_debug/control
echo "module snd_soc_simple_card +p" > /sys/kernel/debug/dynamic_debug/control

# Belirli dosya satırı — DAPM güç değişimlerini izle
echo "file sound/soc/soc-dapm.c +p" > /sys/kernel/debug/dynamic_debug/control

# dmesg ile takip et
dmesg -w | grep -i "soc\|dapm\|i2s"