基于IMX6Q的XFM10621六麦驱动实现说明
来源:互联网 发布:手机淘宝怎么投诉商家 编辑:程序博客网 时间:2024/06/04 20:01
本文主要针对在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函数中主要做的事情包含:
初始化codec驱动数据结构;
解析设备树获取中断引脚的gpio;
向Linux输入子系统注册一个输入设备,用来上报唤醒信息;
向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>;
};
- 基于IMX6Q的XFM10621六麦驱动实现说明
- 科大讯飞麦克风阵列使用感受(六麦,XFM10621)
- imx6q上的背光驱动分析
- imx6q camera驱动添加自己的ioctl
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六) .
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六)
- imx6q can驱动理解
- imx6q中的按键驱动
- imx6q中led驱动分析
- IMX6Q移植ft5x06_ts触摸屏驱动
- 基于imx6q平台移植ffmpeg
- WINCE基于PWM实现的背光驱动
- 基于 platform 平台的驱动框架实现_驱动
- 笔记六:基于数组的栈的实现
- 基于ZK自动切换模式的实现(六)
- webview长按自由复制文字
- bootstrapTable 修改栏位值
- 如何判断网站是不是wordpress做的及WP主题是什么?
- 解决Session 'appname': Error Launching activity
- EventBus全解析
- 基于IMX6Q的XFM10621六麦驱动实现说明
- android 防快速点击的一种实现方式
- python 基础知识补充
- dentry与inode有什么联系和区别
- Android内存优化(使用SparseArray和ArrayMap代替HashMap)
- C语言实现bmp图片全彩转灰度,灰度转伪彩
- svg3dtagcloud生成3D标签云
- EventBus 3.0的用法详解
- angularJS——jquery.bower