基于IMX6Q的XFM10621六麦驱动实现说明

本文主要针对在IMX6Q平台上实现的XFM10621六麦克阵列驱动做了一些介绍和说明,因为科大讯飞没有一个可参考的通用驱动,所以自己就在IMX6Q上实现了一下,相信可以给很多感兴趣和有需求的朋友作为参考,具体的驱动源码可以参看附件部分。

 

1. 环境介绍

硬件平台

IMX6Q

Android版本

5.1

Linux版本

3.14.52

麦克阵列模块

讯飞XFM10621六麦阵列

 

2. Linux框架简介

音频驱动主要涉及Linux ASoc框架,ASoc基于ALSA框架,由Codec驱动,Platform驱动和Machine驱动组成。Codec驱动负责编解码以及音频硬件的实际控制,Platform驱动主要负责CPU和音频模块的通信接口的控制,Machine驱动协调Codec驱动和Platform驱动对用户层提供声卡接口。

下表描述了我们驱动源码的位置:

驱动

源码位置

Codec

kernel_imx/sound/soc/codecs/ifly-dmic.c

Platform

kernel_imx/sound/soc/fsl/fsl_ssi.c

Machine

kernel_imx/sound/soc/fsl/fsl_dmic.c

DTS

kernel_imx/arch/arm/boot/dts/imx6qdl-sabresd.dtsi

 

3. codec驱动

我们的Codec驱动主要职责是处理唤醒中断,对用户层上报唤醒信息及唤醒角度,向ASoc框架注册Codec驱动。

以I2C驱动为入口,在I2C驱动probe函数中主要做的事情包含:

  1. 初始化codec驱动数据结构;

  2. 解析设备树获取中断引脚的gpio;

  3. 向Linux输入子系统注册一个输入设备,用来上报唤醒信息;

  4. 向Linux ASoc框架注册一个Codec驱动。

主要数据结构及描述如下:

 

struct ifly_dmic_data {

    int irq; //通过gpio转化来的中断号

    int angle; //用来记录唤醒角度

    spinlock_t lock; //中断上半部分保护数据的自旋锁

    struct timespec stamp; //被唤醒的时间戳

    struct work_struct work; //中断下半部分的工作队列

    struct snd_soc_codec *codec; //指向codec数据结构

    struct i2c_client *client; //指向i2c客户端

    struct input_dev *input_dev; //指向输入设备

};

注册codec驱动时提供的dai信息如下:

static struct snd_soc_dai_driver dmic_dai = {

    .name = "dmic-hifi",

    .capture = {

        .stream_name = "Capture",

        .channels_min = 2,

        .channels_max = 2,

        .rates = SNDRV_PCM_RATE_16000,

        .formats = SNDRV_PCM_FMTBIT_S16_LE,

    },

};

在codec驱动的probe函数中会:

(1)在sysfs中创建接口供用户层获取唤醒角度;

(2)保存codec设备指针;

(3)注册唤醒中断;

(4)初始化中断下半部分的工作队列,并使能中断。

唤醒中断被触发后,上半部分会根据上次的时间戳过滤掉抖动,接着调度下半部分的工作队列,在中断下半部分会点亮对应唤醒角度的led灯,并保存唤醒的角度,然后上报KEY_RECORD键值告知用户层可以获取录音数据了。

 

4. platform驱动

platform驱动是飞思卡尔写的,主要负责dma和i2s接口的音频数据传输,每个硬件平台都有针对自己平台的platform驱动。写codec驱动和machine驱动还是需要分析下这边方便定位问题。

 

5. machine驱动

machine驱动以平台虚拟总线驱动的形式注册到系统,其probe函数的主要工作如下:

(1)解析设备树获取codec信息;

(2)配置imx6q的audmux控制器设置总线复用通路;

(3)向Linux ASoc框架注册一个声卡设备。

machine驱动向ASoc注册声卡的时候也需要提供dai信息用来粘合platform驱动和codec驱动。

 

 

 

 

 

 

6. 附件

 

codec驱动源码:

/* sound/soc/codecs/ifly-dmic.c

 * Created by Neo

 * 2017/7/17

 * */

 

#include <linux/i2c.h>

#include <linux/slab.h>

#include <linux/module.h>

#include <linux/delay.h>

#include <sound/core.h>

#include <sound/pcm.h>

#include <sound/soc.h>

#include <sound/soc-dapm.h>

#include <linux/gpio.h>

#include <linux/interrupt.h>

#include <linux/workqueue.h>

#include <linux/of_gpio.h>

#include <linux/time.h>

#include <linux/spinlock.h>

#include <linux/fs.h>

#include <linux/input.h>

 

struct ifly_dmic_data {

    int irq;

    int angle;

    spinlock_t lock;

    struct timespec stamp;

    struct work_struct work;

    struct snd_soc_codec *codec;

    struct i2c_client *client;

    struct input_dev *input_dev;

};

 

static struct snd_soc_dai_driver dmic_dai = {

    .name = "dmic-hifi",

    .capture = {

        .stream_name = "Capture",

        .channels_min = 2,

        .channels_max = 2,

        .rates = SNDRV_PCM_RATE_16000,

        .formats = SNDRV_PCM_FMTBIT_S16_LE,

    },

};

 

static unsigned int ifly_reg_read(struct i2c_client *client, unsigned int reg)

{

    unsigned char buf[4] = {0};

    unsigned int ret = 0;

    struct i2c_msg msgs[] = {

        {

            .addr = client->addr,

            .flags = 0,

            .len = 1,

            .buf = (unsigned char *)®,

        },

        {

            .addr = client->addr,

            .flags = I2C_M_RD,

            .len = 4,

            .buf = buf,

        },

    };

 

    i2c_transfer(client->adapter, msgs, sizeof(msgs)/sizeof(msgs[0]));

    ret = (buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));

 

    return ret;

}

 

static int ifly_reg_write(struct i2c_client *client, unsigned int reg, unsigned int value)

{

    unsigned char buf[5] = {0};

    struct i2c_msg msgs[] = {

       {

            .addr = client->addr,

            .flags = 0,

            .len = 5,

            .buf = buf,

        },

    };

 

    buf[0] = reg & 0xff;

    buf[1] = value & 0xff;

    buf[2] = (value >> 8) & 0xff;

    buf[3] = (value >> 16) & 0xff;

    buf[4] = (value >> 24) & 0xff;

 

    return i2c_transfer(client->adapter, msgs, sizeof(msgs)/sizeof(msgs[0]));

}

 

static int ifly_dmic_get_wakeup_angle(struct i2c_client *client)

{

    int ret = 0;

 

    ret = ifly_reg_write(client, 0x00, 0x1000);

    mdelay(200);

    ret = ifly_reg_read(client, 0x00);

    ret = ifly_reg_read(client, 0x01);

 

    return ret;

}

 

static struct snd_soc_codec_driver soc_dmic;

 

//#define WAKEUP_LED_KERNEL

void ifly_dmic_work(struct work_struct *work)

{

    #ifdef WAKEUP_LED_KERNEL

    struct file *filp = (struct file*)-ENOENT;

    mm_segment_t oldfs;

    char *led_class = "/sys/class/leds";

    char *led_color = "green";

    char *led_path = NULL;

    char *led_seq[] = {"01", "02", "03",

                       "04", "05", "06",

                       "07", "08", "09",

                       "10", "11", "12"};

    #endif

    struct ifly_dmic_data *data = container_of(work, typeof(*data), work);

    int angle = 0;

 

    angle = ifly_dmic_get_wakeup_angle(data->client);

    dev_warn(data->codec->dev, "ifly-dmic wakeup (%d)\n", angle);

 

    if ((angle <= 360) && (angle >= 0)) {

        angle %= 360;

        #ifdef WAKEUP_LED_KERNEL

        oldfs = get_fs();

        set_fs(KERNEL_DS);

        led_path = kasprintf(GFP_KERNEL, "%s/lp55231-%s-%s/brightness", led_class, led_seq[angle/30], led_color);

        if (led_path) {

            filp = filp_open(led_path, O_RDWR, S_IRUSR | S_IWUSR);

            if (!IS_ERR_OR_NULL(filp)) {

                generic_file_llseek(filp, 0, SEEK_SET);

                if (vfs_write(filp, "255", 3, &filp->f_pos) != 3) {

                    dev_err(data->codec->dev, "set led[%s] failed\n", led_seq[angle/30]);

                }

                filp_close(filp, NULL);

                filp = (struct file*)-ENOENT;

            }

            kfree(led_path);

            led_path = NULL;

        }

        if ((angle/30) != (data->angle/30)) {

            led_path = kasprintf(GFP_KERNEL, "%s/lp55231-%s-%s/brightness", led_class, led_seq[data->angle/30], led_color);

            if (led_path) {

                filp = filp_open(led_path, O_RDWR, S_IRUSR | S_IWUSR);

                if (!IS_ERR_OR_NULL(filp)) {

                    generic_file_llseek(filp, 0, SEEK_SET);

                    if (vfs_write(filp, "0", 1, &filp->f_pos) != 1) {

                        dev_err(data->codec->dev, "clear led[%s] failed\n", led_seq[data->angle/30]);

                    }

                    filp_close(filp, NULL);

                    filp = (struct file*)-ENOENT;

                }

                kfree(led_path);

                led_path = NULL;

            }

        }

        set_fs(oldfs);

        #endif

        data->angle = angle;

        input_report_key(data->input_dev, KEY_RECORD, 1);

        input_sync(data->input_dev);

        mdelay(100);

        input_report_key(data->input_dev, KEY_RECORD, 0);

        input_sync(data->input_dev);

    }

}

 

static irqreturn_t ifly_dmic_isr(int irq, void *p)

{

    struct timespec now;

    unsigned long flags = 0;

    struct ifly_dmic_data *data = p;

 

    spin_lock_irqsave(&data->lock, flags);

    getnstimeofday(&now);

    if ((timespec_to_ns(&now) - timespec_to_ns(&data->stamp)) >= 100000000) {

        schedule_work(&data->work);

    }

    getnstimeofday(&data->stamp);

    spin_unlock_irqrestore(&data->lock, flags);

 

    return IRQ_HANDLED;

}

 

static ssize_t angle_show(struct device *dev, struct device_attribute *attr, char *buf)

{

    struct ifly_dmic_data *data = dev_get_drvdata(dev);

 

    sprintf(buf, "%d\n", data->angle);

    return (strlen(buf) > 0) ? (strlen(buf) + 1) : 0;

}

 

static DEVICE_ATTR(angle, 0444, angle_show, NULL);

 

static int ifly_dmic_probe(struct snd_soc_codec *codec)

{

    int ret = 0;

    struct ifly_dmic_data *data = NULL;

 

    data = dev_get_drvdata(codec->dev);

    if (data) {

        device_create_file(codec->dev, &dev_attr_angle);

        data->codec = codec;

        spin_lock_init(&data->lock);

        request_irq(data->irq, ifly_dmic_isr, IRQF_TRIGGER_FALLING | IRQF_SHARED, "ifly-irq", data);

        INIT_WORK(&data->work, ifly_dmic_work);

        enable_irq_wake(data->irq);

    }

 

    return ret;

}

 

static int ifly_dmic_remove(struct snd_soc_codec *codec)

{

    int ret = 0;

    struct ifly_dmic_data *data = NULL;

 

    data = dev_get_drvdata(codec->dev);

    if (data) {

        disable_irq(data->irq);

        cancel_work_sync(&data->work);

        free_irq(data->irq, data);

        data->codec = NULL;

    }

 

    return ret;

}

 

static int dmic_dev_probe(struct i2c_client *client,

const struct i2c_device_id *id)

{

    int gpio = -1;

    int error = 0;

    struct ifly_dmic_data *data = NULL;

    struct input_dev *input_dev = NULL;

 

    memset(&soc_dmic, 0, sizeof(soc_dmic));

    soc_dmic.probe = ifly_dmic_probe;

    soc_dmic.remove = ifly_dmic_remove;

 

    gpio = of_get_named_gpio(client->dev.of_node, "gpio-irq", 0);

    if (!gpio_is_valid(gpio)) {

        dev_warn(&client->dev, "invalid gpio-irq(%d)!!!\n", gpio);

        return -EINVAL;

    }

 

    data = kzalloc(sizeof(*data), GFP_KERNEL);

    if (!data) {

        dev_err(&client->dev, "alloc private data failed!\n");

        error = -ENOMEM;

        goto err_alloc_data;

    }

    input_dev = devm_input_allocate_device(&client->dev);

    if (!input_dev) {

        dev_err(&client->dev, "alloc input device failed!\n");

        error = -ENOMEM;

        goto err_alloc_input;

    }

 

    input_dev->name = "ifly-dmic";

    input_dev->id.bustype = BUS_I2C;

    input_dev->dev.parent = &client->dev;

 

    __set_bit(EV_KEY, input_dev->evbit);

    __set_bit(KEY_RECORD, input_dev->keybit);

 

    data->irq = gpio_to_irq(gpio);

    data->client = client;

    data->input_dev = input_dev;

    dev_set_drvdata(&client->dev, data);

 

    error = input_register_device(data->input_dev);

    if (error) {

        dev_err(&client->dev, "register input device failed!\n");

        goto err_register_input;

    }

 

    return snd_soc_register_codec(&client->dev, &soc_dmic, &dmic_dai, 1);

 

err_register_input:

    input_free_device(input_dev);

    data->input_dev = NULL;

err_alloc_input:

    kfree(data);

    data = NULL;

err_alloc_data:

    return error;

}

 

static int dmic_dev_remove(struct i2c_client *client)

{

    struct ifly_dmic_data *data = NULL;

 

    data = dev_get_drvdata(&client->dev);

 

    input_unregister_device(data->input_dev);

    input_free_device(data->input_dev);

    data->input_dev = NULL;

    snd_soc_unregister_codec(&client->dev);

 

    kfree(data);

    data = NULL;

 

    return 0;

}

 

static const struct i2c_device_id dmic_id[] = {

    {"ifly-dmic", 0},

    {},

};

 

MODULE_DEVICE_TABLE(i2c, dmic_id);

 

static const struct of_device_id dmic_dt_ids[] = {

    { .compatible = "fsl,ifly-dmic", },

    { /* sentinel */ }

};

MODULE_DEVICE_TABLE(of, dmic_dt_ids);

 

static struct i2c_driver dmic_driver = {

    .driver = {

        .name = "ifly-dmic",

        .owner = THIS_MODULE,

        .of_match_table = dmic_dt_ids,

    },

    .probe = dmic_dev_probe,

    .remove = dmic_dev_remove,

    .id_table = dmic_id,

};

 

module_i2c_driver(dmic_driver);

 

MODULE_DESCRIPTION("iFly DMIC driver");

MODULE_AUTHOR("Neo <neo.hou@qq.com>");

MODULE_LICENSE("GPL");

 

 

machine驱动源码:

/* sound/soc/fsl/fsl_dmic.c

 * Neo neo.hou@qq.com

 * */

 

#include <linux/init.h>

#include <linux/module.h>

#include <linux/platform_device.h>

#include <linux/of.h>

#include <linux/of_platform.h>

#include <sound/soc.h>

 

#include "imx-audmux.h"

 

#define DAI_NAME_SIZE 32

 

struct fsl_dmic_data {

        struct snd_soc_dai_link dai;

        struct snd_soc_card card;

        struct platform_device *codec_dev;

        char codec_dai_name[DAI_NAME_SIZE];

        char platform_name[DAI_NAME_SIZE];

        unsigned int clk_frequency;

};

 

static int fsl_dmic_audmux_config(struct platform_device *pdev)

{

    struct device_node *np = pdev->dev.of_node;

    int int_port, ext_port;

    int ret;

 

    ret = of_property_read_u32(np, "mux-int-port", &int_port);

    if (ret) {

        dev_err(&pdev->dev, "mux-int-port missing or invalid\n");

        return ret;

    }

    ret = of_property_read_u32(np, "mux-ext-port", &ext_port);

    if (ret) {

        dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");

        return ret;

    }

 

    /*

     * The port numbering in the hardware manual starts at 1, while

     * the audmux API expects it starts at 0.

     */

    int_port--;

    ext_port--;

    ret = imx_audmux_v2_configure_port(int_port,

              IMX_AUDMUX_V2_PTCR_SYN |

              IMX_AUDMUX_V2_PTCR_RFSEL(ext_port | 0x08) |

              IMX_AUDMUX_V2_PTCR_RCSEL(ext_port | 0x08) |

              IMX_AUDMUX_V2_PTCR_TFSEL(ext_port | 0x08) |

              IMX_AUDMUX_V2_PTCR_TCSEL(ext_port | 0x08) |

              IMX_AUDMUX_V2_PTCR_TFSDIR |

              IMX_AUDMUX_V2_PTCR_TCLKDIR |

              IMX_AUDMUX_V2_PTCR_RFSDIR |

              IMX_AUDMUX_V2_PTCR_RCLKDIR,

              IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));

    if (ret) {

        dev_err(&pdev->dev, "audmux internal port setup failed\n");

        return ret;

    }

 

    ret = imx_audmux_v2_configure_port(ext_port, 0, IMX_AUDMUX_V2_PDCR_RXD(int_port));

    if (ret) {

        dev_err(&pdev->dev, "audmux external port setup failed\n");

        return ret;

    }

 

    return 0;

}

 

static int asoc_dmic_probe(struct platform_device *pdev)

{

    int ret = 0;

    struct fsl_dmic_data *data = NULL;

    struct device_node *cpu_np = NULL;

    struct device_node *codec_np = NULL;

 

    cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);

    if (!cpu_np) {

        dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");

        ret = -EINVAL;

        goto fail;

    }

    codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);

    if (!codec_np) {

        dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");

        ret = -EINVAL;

        goto fail;

    }

 

    if (strstr(cpu_np->name, "ssi")) {

        ret = fsl_dmic_audmux_config(pdev);

        if (ret)

            goto fail;

    }

 

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

    if (!data) {

        ret = -ENOMEM;

        goto fail;

    }

 

    data->dai.name = "HiFi";

    data->dai.stream_name = "HiFi";

    data->dai.codec_dai_name = "dmic-hifi";

    data->dai.codec_of_node = codec_np;

    data->dai.cpu_of_node = cpu_np;

    data->dai.platform_of_node = cpu_np;

    data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |

                        SND_SOC_DAIFMT_CBM_CFM;

 

    data->card.dai_link = &data->dai;

    data->card.num_links = 1;

    data->card.dev = &pdev->dev;

 

    ret = snd_soc_of_parse_card_name(&data->card, "model");

    if (ret)

        goto err_parse_card_name;

 

    platform_set_drvdata(pdev, &data->card);

    snd_soc_card_set_drvdata(&data->card, data);

 

    ret = devm_snd_soc_register_card(&pdev->dev, &data->card);

    if (ret) {

        dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);

        goto err_register_card;

    }

 

    goto fail;

err_register_card:

    platform_device_unregister(data->codec_dev);

err_register_codec:

err_parse_card_name:

    devm_kfree(&pdev->dev, data);

fail:

    if (cpu_np)

        of_node_put(cpu_np);

 

    return ret;

}

 

static int asoc_dmic_remove(struct platform_device *pdev)

{

    struct snd_soc_card *card = platform_get_drvdata(pdev);

    struct fsl_dmic_data *data = snd_soc_card_get_drvdata(card);

 

    platform_device_unregister(data->codec_dev);

    devm_kfree(&pdev->dev, data);

}

 

static const struct of_device_id fsl_dmic_of_match[] = {

    { .compatible = "fsl,ifly-dmic", },

    { }

};

MODULE_DEVICE_TABLE(of, fsl_dmic_of_match);

 

static struct platform_driver asoc_dmic_driver = {

    .driver = {

        .name = "fsl-dmic",

        .owner = THIS_MODULE,

        .of_match_table = fsl_dmic_of_match,

    },

    .probe = asoc_dmic_probe,

    .remove = asoc_dmic_remove,

};

 

static int __init asoc_dmic_init(void)

{

    return platform_driver_register(&asoc_dmic_driver);

}

 

static void __exit asoc_dmic_exit(void)

{

    platform_driver_unregister(&asoc_dmic_driver);

}

 

module_init(asoc_dmic_init);

module_exit(asoc_dmic_exit);

MODULE_AUTHOR("Neo <neo.hou@qq.com>");

MODULE_DESCRIPTION("Freescale DMIC Machine Driver");

MODULE_LICENSE("GPL");

 

DTS源码:

dmic_codec: ifly-dmic@47 {
    compatible = "fsl,ifly-dmic";
    reg = <0x47>;
    gpio-irq = <&gpio5 20 1>;
};

sound-dmic {
    compatible = "fsl,ifly-dmic";
    model = "fsl-audio-dmic";
    cpu-dai = <&ssi2>;
    audio-codec = <&dmic_codec>;
    mux-int-port = <2>;
    mux-ext-port = <3>;
};

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值