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:
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
| Özellik | Klasik ALSA | ASoC |
|---|---|---|
| Sürücü yapısı | Tek monolitik sürücü | Codec + Platform + Machine üçlüsü |
| Güç yönetimi | Manuel suspend/resume | DAPM — widget bazlı otomatik |
| Codec yeniden kullanımı | Yok — board bağımlı | Tam — codec sürücüsü boarddan bağımsız |
| Çoklu codec | Çok zor | dai_link listesiyle doğal destek |
| DMA entegrasyonu | Sürücü içinde elle yazılır | dmaengine_pcm ile generic altyapı |
| Device Tree | Kısmi | audio-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:
/* 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
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
| Parametre | Açıklama | Tipik değer |
|---|---|---|
| buffer_bytes_max | DMA tamponunun azami boyutu | 512 KB – 4 MB |
| period_bytes_min | Tek DMA aktarımının asgari boyutu | 256 B |
| period_bytes_max | Tek DMA aktarımının azami boyutu | 64 KB |
| periods_min | En az kaç dönem (kesme) olacağı | 2 |
| periods_max | En 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
| Sabit | Anlamı |
|---|---|
| SND_SOC_DAIFMT_I2S | Standart I2S — 1 BCLK gecikmeli, simetrik |
| SND_SOC_DAIFMT_LEFT_J | Left Justified — gecikmesiz, MSB önce |
| SND_SOC_DAIFMT_DSP_A | PCM/TDM — tek LRCLK darbesi, A modu |
| SND_SOC_DAIFMT_CBP_CFP | Codec = BCLK üretici & FS üretici (master) |
| SND_SOC_DAIFMT_CBC_CFC | CPU = BCLK üretici & FS üretici (CPU master) |
| SND_SOC_DAIFMT_NB_NF | Normal 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
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
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
/* 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
&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
&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
| Codec | Güç | Kalite | Öne çıkan özellik | Tipik kullanım |
|---|---|---|---|---|
| WM8960 | Orta | Yüksek | Dahili SPK amp, AGC | Raspberry Pi, i.MX tabanlı boardlar |
| ES8316 | Düşük | Orta-Yüksek | Düşük güç, HP algılama | Rockchip SBC, taşınabilir cihaz |
| TLV320AIC3x | Orta | Çok yüksek | MiniDSP, 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
| Hata | Olası neden | Çözüm |
|---|---|---|
-ENODEV kart kayıt | Codec I2C'de bulunamıyor | i2cdetect -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üçük | period_size artır, CPU yükünü düşür |
| Gürültü / distorsyon | MCLK / BCLK oran hatası | set_sysclk frekansını kontrol et, PLL ayarla |
| Capture yok | MICBIAS etkin değil | amixer cset ile MICBIAS kontrolünü aç |
-EINVAL hw_params | Desteklenmeyen format/hız | codec 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"