S5PV210 WM8960 ASOC 移植

内核版本Linux 3.9.11      编译器版本 4.5.1

Asoc框架是linux内核采用的一种音频子系统框架。他在alsa框架的基础上又封装了一层。实际上就把asoc移植看成alsa移植即可。(虽然2个我都不太懂,初学asoc驱动)
在对实际文件分析后我把alsa框架需要修改的主要为如下几类:

SMDK_xxxx.c文件      --> Machine
I2C子系统驱动         -->codec
I2S控制器驱动     

其中I2S控制器驱动和I2C子系统驱动由内核已经给出。I2S驱动主要是设置I2S相关的寄存器,便于ASOC其他部分(框架理解不是很透彻,这里可以看成PCM数据发送)发送数据。
而I2C子系统驱动则负责配置WM8960的寄存器。SMDK.c文件主要是负责将asoc各个部分通过一个结构体描述连接起来。他们都是由asoc更上层的结构进行调用的。

对于我们移植来说,主要是关心SMDK_xxxx.c这样的Machine部分的文件。如果内核没有的话,就需要自己动手写了,我这里是没有的,所以我去找了个S3c2451的WM8960的Machine文件,接下来说下修改步骤。
想要正确播放出声音,第一步就需要修改machine中的dai结构体:

S5PV210 WM8960 ASOC 移植_第1张图片

下面只说下主要的成员:

Cpu_dai_name         对应i2s控制器驱动
Codec_dai_name     对应于内核提供的codec文件中的同名dai 图3 给出
Platform_name       用于数据传输的平台名称 (我猜的 I2SDMA?)
Codec_name           对应于控制codec的 i2c驱动
ops                         主要成员是.hw_params变量,当通过alsa-util测试播放的时候,应用会通过 这个函数传入一些控制参数来设置i2s和wm8960。 通过 图2 划线的2个参数就能对应到codec和i2s各自的控制函数当中。分别进行设置。参数则通过查看他们对应的函数就能得出。
S5PV210 WM8960 ASOC 移植_第2张图片

                               图2

S5PV210 WM8960 ASOC 移植_第3张图片

                               图3

修改后若使用默认的设置无法播放,则需要检查设置让关键的寄存器值变正确。
IIS设置主要要设置时钟源选I2SCLK,通过EPLL产生时钟,设置可以查看芯片手册中的i2s时钟路线图,然后就是bfs,rfs,psr参数,通常使用bfs=32 rfs=256,因为wm8960中默认设置还会将传入的频率/256和/32,所以为了保证频率正确就这样设置,这两个可以通过应用传参得到。而psr就需要我们通过计算得到了,比如44100hz采样率就是5 采样率是22050时就是10。这是通过公式算出来的。(psr是i2s预分频寄存器)

    N + 1 = (67.7Mhz) / (256 * 44.1Khz) = 5.99
    N + 1 = (67.7Mhz) / (256 * 22.05Khz) = 11

我们只需要让这些值真确写入寄存器就好了。设置通过i2s控制器驱动中的i2s_set_clkdiv函数,当然这些只是设置,并没有实际写入到寄存器,真正写入到寄存器的是config_setup函数,里面调用

set_bfs(i2s, bfs);
set_rfs(i2s, rfs);

写入寄存器,psr在最后进行设置。

if (!(i2s->quirks & QUIRK_NO_MUXPSR)) {
        psr =m_psr;// i2s->rclk_srcrate / i2s->frmclk / rfs;
        printk("i2s %s  setup psr:%d \r\n",__func__,psr);
        writel(((psr ) << 8) | PSR_PSREN, i2s->addr + I2SPSR);
        dev_dbg(&i2s->pdev->dev,
            "RCLK_SRC=%luHz PSR=%u, RCLK=%dfs, BCLK=%dfs\n",
                i2s->rclk_srcrate, psr, rfs, bfs);
    }

可以看到我用m_psr替换掉了原始的psr计算,这是因为,我在machine中已经计算好了psr的值,传入设置好的时候,不用计算直接写入即可。
并且,i2s驱动中的set_clkdiv可能会少 rfs和psr的设置,加入即可。
(这些修改的部分是通过machine中的.hw_params函数设置的)
以下代码是I2S.c中的代码,添加了2和3两个选项(这是通过调试得到的缺少的部分)。

#define DIV_RCLK 2
#define DIV_PSR 3
static int i2s_set_clkdiv(struct snd_soc_dai *dai,
    int div_id, int div)
{
    struct i2s_dai *i2s = to_info(dai);
    struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;

    switch (div_id) {
    case SAMSUNG_I2S_DIV_BCLK:
        if ((any_active(i2s) && div && (get_bfs(i2s) != div))
            || (other && other->bfs && (other->bfs != div))) {
            dev_err(&i2s->pdev->dev,
                "%s:%d Other DAI busy\n", __func__, __LINE__);
            return -EAGAIN;
        }
        printk(KERN_ERR"bfs:%d\r\n",div);
        i2s->bfs = div;
        break;
    case DIV_RCLK:
        if ((any_active(i2s) && div && (get_rfs(i2s) != div))
            || (other && other->rfs && (other->rfs != div))) {
            dev_err(&i2s->pdev->dev,
                "%s:%d Other DAI busy\n", __func__, __LINE__);
            return -EAGAIN;
        }
        printk(KERN_ERR"rfs:%d\r\n",div);
        i2s->rfs = div;
        break;
        case DIV_PSR:
        {
            m_psr=div;
        }break;
    default:
        dev_err(&i2s->pdev->dev,
            "Invalid clock divider(%d)\n", div_id);
        return -EINVAL;
    }

    return 0;
}

这里还需要注意一个函数就是i2s_txctrl ,这个函数控制着i2s的输出,在函数的末尾可以看到设置IISCON和IISMOD寄存器,如果不出声音可以在此跟踪一下这两个寄存器的状态。
正确的状态不知道的话可以先写下裸机wm8960驱动以后再来验证,裸机驱动wm8960的代码网上还是比较多的。
我这里改成了con |1, 原因可以通过看手册和跟踪寄存器得出。

S5PV210 WM8960 ASOC 移植_第4张图片
到此I2S部分和machine部分就修改完了。还要注意一点,如果iis设置全对,还是没有声音,这时就要考虑codec驱动的初始化问题了,以wm8960.c为例,我是在代码中的wm8960_probe函数中看到了wm8960的一些寄存器初始化函数调用,一些关键的设置并不正确,于是修改为如下:

static int wm8960_probe(struct snd_soc_codec *codec)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    struct wm8960_data *pdata = dev_get_platdata(codec->dev);
    int ret;

    wm8960->set_bias_level = wm8960_set_bias_level_out3;

    if (!pdata) {
        dev_warn(codec->dev, "No platform data supplied\n");
    } else {
        if (pdata->capless)
            wm8960->set_bias_level = wm8960_set_bias_level_capless;
    }

    ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_REGMAP);
    if (ret < 0) {
        dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
        return ret;
    }

    ret = wm8960_reset(codec);
    if (ret < 0) {
        dev_err(codec->dev, "Failed to issue reset\n");
        return ret;
    }

    wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);

    /* Latch the update bits */
    /*
    snd_soc_update_bits(codec, WM8960_LINVOL, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_RINVOL, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_LADC, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_RADC, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_LDAC, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_RDAC, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_LOUT1, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_ROUT1, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_LOUT2, 0x100, 0x100);
    snd_soc_update_bits(codec, WM8960_ROUT2, 0x100, 0x100);
  */
  //power
    snd_soc_update_bits(codec, WM8960_POWER1,0x1c0,0x1c0);
    snd_soc_update_bits(codec, WM8960_POWER2,0x1f8 ,0x1f8);
    snd_soc_update_bits(codec, WM8960_POWER3,0x00c ,0x00c);

// clock  CLKSEL = 0 : no PLL -> SYSCLK using MCLK
    snd_soc_update_bits(codec, 0x4,0, 0x0); 

    // no mute  ok
    snd_soc_update_bits(codec, 0x5,0, 0x0); // set no mute
    //snd_soc_update_bits(codec, 0x5, 0x08);// set mute 

    // audio interface
    snd_soc_update_bits(codec, 0x7,0x2, 0x2);// 00 = 16bits,  10 = IIS format

    // volume +6db  ok
    snd_soc_update_bits(codec, 0x2,0x1ff, 0x60 | 0x100);// WM8960_LOUT1
    snd_soc_update_bits(codec, 0x3,0x1ff, 0x60 | 0x100);// WM8960_ROUT1

    snd_soc_update_bits(codec, 0xa,0x1ff, 0xFF | 0x100);// Left DAC volume
    snd_soc_update_bits(codec, 0xb,0x1ff, 0xFF | 0x100);// Right DAC volume

    // mixer control
    snd_soc_update_bits(codec, 0x22,0x180, 1<<8 | 1<<7);        // Left output mixer control
    snd_soc_update_bits(codec, 0x25,0x180, 1<<8 | 1<<7);        // Right output mixer control

    printk(KERN_ERR"wm8960 init done!\r\n");

    snd_soc_add_codec_controls(codec, wm8960_snd_controls,
                     ARRAY_SIZE(wm8960_snd_controls));
    wm8960_add_widgets(codec);
    return 0;
}

注释掉的是原来的,wm8960寄存器地址在wm8960.h中都有定义的,我为了省事直接把裸机代码拿过来用了,效果是一样的。主要设置了电源,时钟,音量和声音输出,这些缺1不可(测试过)。
这样修改完后声音应该能放出来了。
如果出声音了,速率不对,偏快或偏慢,那是因为EPLL时钟设置不对,IIS需要67.7M的时钟频率才能正确播放。,当然80M的频率播放也蛮有意思的,音调被改变,一样的音乐,不一样的感受。^_^

另外提一下asoc驱动移植注意。

smdk_8960.c是我手动添加的,还需要修改Makefile和Kconfig文件,才能被配置和编译。
首先需要在smdk_s5pv210.c中的device __init数值,确定iis0和i2c0设备在数组中。(我的wm8960 用的是iis0和i2c0),将wm8960作为i2c子设备添加到 i2c0中,配置内核支持i2c,asoc然后启动后确定i2c0,wm8960,asoc正确被加载(看输出),i2s0会在wm8960匹配成功后会出现如下类似输出:

soc-audio soc-audio:  wm8960-hifi <-> samsung-i2s.0 mapping ok

最终成功时会有如下输出:

ALSA device list:
  #0: SMDK-I2S

也就是成功注册了一个声卡设备。
在/dev目录下会出现这些设备节点:

controlC0   -->   用于声卡的控制,例如通道选择,混音,麦克风的控制等
pcmC0D0c    -->   用于录音的pcm设备
pcmC0D0p    -->   用于播放的pcm设备
timer       -->   定时器

最后通过alsa-util调用alsa-lib库 播放wav文件 如果需要播放MP3还需要libmad(MP3解码库)的支持,直接让alsa-lib使用libmad库我还没找到方法,但可以通过自己写一个程序,调用libmad和alsa-lib来播放MP3,代码网上可以搜到。

附上测试播放调试图:
WAV文件:
S5PV210 WM8960 ASOC 移植_第5张图片

MP3文件:

S5PV210 WM8960 ASOC 移植_第6张图片

可以看到,播放MP3和WAV的驱动输出基本是一样的,最原始的音频数据是WAV,播放MP3只是通过软解码转换成WAV文件流而已。

心得体会

对于这些带框架的驱动学习,如果遇到问题不知道出在哪里可以先去调试一下裸机代码,确定寄存器的正确配置后再来调驱动框架会有事半功倍的效果。^_^

你可能感兴趣的:(S5PV210 WM8960 ASOC 移植)