Commit 2a9e92d3 authored by Srinivas Kandagatla's avatar Srinivas Kandagatla Committed by Mark Brown

ASoC: qdsp6: q6asm: Add q6asm dai driver

This patch adds support to q6asm dai driver which configures Q6ASM streams
to pass pcm data.
Signed-off-by: default avatarSrinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: default avatarRohit kumar <rohitkr@codeaurora.org>
Reviewed-by: default avatarBanajit Goswami <bgoswami@codeaurora.org>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 24c4cbcf
...@@ -62,6 +62,9 @@ config SND_SOC_QDSP6_ROUTING ...@@ -62,6 +62,9 @@ config SND_SOC_QDSP6_ROUTING
config SND_SOC_QDSP6_ASM config SND_SOC_QDSP6_ASM
tristate tristate
config SND_SOC_QDSP6_ASM_DAI
tristate
config SND_SOC_QDSP6 config SND_SOC_QDSP6
tristate "SoC ALSA audio driver for QDSP6" tristate "SoC ALSA audio driver for QDSP6"
depends on QCOM_APR && HAS_DMA depends on QCOM_APR && HAS_DMA
...@@ -72,6 +75,7 @@ config SND_SOC_QDSP6 ...@@ -72,6 +75,7 @@ config SND_SOC_QDSP6
select SND_SOC_QDSP6_ADM select SND_SOC_QDSP6_ADM
select SND_SOC_QDSP6_ROUTING select SND_SOC_QDSP6_ROUTING
select SND_SOC_QDSP6_ASM select SND_SOC_QDSP6_ASM
select SND_SOC_QDSP6_ASM_DAI
help help
To add support for MSM QDSP6 Soc Audio. To add support for MSM QDSP6 Soc Audio.
This will enable sound soc platform specific This will enable sound soc platform specific
......
...@@ -5,3 +5,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o ...@@ -5,3 +5,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o
obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o
obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o
obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o
obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
// Copyright (c) 2018, Linaro Limited
#include <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/component.h>
#include <sound/soc.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/of_device.h>
#include <sound/pcm_params.h>
#include "q6asm.h"
#include "q6routing.h"
#include "q6dsp-errno.h"
#define DRV_NAME "q6asm-fe-dai"
#define PLAYBACK_MIN_NUM_PERIODS 2
#define PLAYBACK_MAX_NUM_PERIODS 8
#define PLAYBACK_MAX_PERIOD_SIZE 65536
#define PLAYBACK_MIN_PERIOD_SIZE 128
#define CAPTURE_MIN_NUM_PERIODS 2
#define CAPTURE_MAX_NUM_PERIODS 8
#define CAPTURE_MAX_PERIOD_SIZE 4096
#define CAPTURE_MIN_PERIOD_SIZE 320
#define SID_MASK_DEFAULT 0xF
enum stream_state {
Q6ASM_STREAM_IDLE = 0,
Q6ASM_STREAM_STOPPED,
Q6ASM_STREAM_RUNNING,
};
struct q6asm_dai_rtd {
struct snd_pcm_substream *substream;
phys_addr_t phys;
unsigned int pcm_size;
unsigned int pcm_count;
unsigned int pcm_irq_pos; /* IRQ position */
unsigned int periods;
uint16_t bits_per_sample;
uint16_t source; /* Encoding source bit mask */
struct audio_client *audio_client;
uint16_t session_id;
enum stream_state state;
};
struct q6asm_dai_data {
long long int sid;
};
static struct snd_pcm_hardware q6asm_dai_hardware_capture = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE),
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 4,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS *
CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
.fifo_size = 0,
};
static struct snd_pcm_hardware q6asm_dai_hardware_playback = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE),
.rates = SNDRV_PCM_RATE_8000_192000,
.rate_min = 8000,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS *
PLAYBACK_MAX_PERIOD_SIZE),
.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = PLAYBACK_MIN_NUM_PERIODS,
.periods_max = PLAYBACK_MAX_NUM_PERIODS,
.fifo_size = 0,
};
#define Q6ASM_FEDAI_DRIVER(num) { \
.playback = { \
.stream_name = "MultiMedia"#num" Playback", \
.rates = (SNDRV_PCM_RATE_8000_192000| \
SNDRV_PCM_RATE_KNOT), \
.formats = (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE), \
.channels_min = 1, \
.channels_max = 8, \
.rate_min = 8000, \
.rate_max = 192000, \
}, \
.capture = { \
.stream_name = "MultiMedia"#num" Capture", \
.rates = (SNDRV_PCM_RATE_8000_48000| \
SNDRV_PCM_RATE_KNOT), \
.formats = (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE), \
.channels_min = 1, \
.channels_max = 4, \
.rate_min = 8000, \
.rate_max = 48000, \
}, \
.name = "MultiMedia"#num, \
.probe = fe_dai_probe, \
.id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \
}
/* Conventional and unconventional sample rate supported */
static unsigned int supported_sample_rates[] = {
8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
88200, 96000, 176400, 192000
};
static struct snd_pcm_hw_constraint_list constraints_sample_rates = {
.count = ARRAY_SIZE(supported_sample_rates),
.list = supported_sample_rates,
.mask = 0,
};
static void event_handler(uint32_t opcode, uint32_t token,
uint32_t *payload, void *priv)
{
struct q6asm_dai_rtd *prtd = priv;
struct snd_pcm_substream *substream = prtd->substream;
switch (opcode) {
case ASM_CLIENT_EVENT_CMD_RUN_DONE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
q6asm_write_async(prtd->audio_client,
prtd->pcm_count, 0, 0, NO_TIMESTAMP);
break;
case ASM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = Q6ASM_STREAM_STOPPED;
break;
case ASM_CLIENT_EVENT_DATA_WRITE_DONE: {
prtd->pcm_irq_pos += prtd->pcm_count;
snd_pcm_period_elapsed(substream);
if (prtd->state == Q6ASM_STREAM_RUNNING)
q6asm_write_async(prtd->audio_client,
prtd->pcm_count, 0, 0, NO_TIMESTAMP);
break;
}
case ASM_CLIENT_EVENT_DATA_READ_DONE:
prtd->pcm_irq_pos += prtd->pcm_count;
snd_pcm_period_elapsed(substream);
if (prtd->state == Q6ASM_STREAM_RUNNING)
q6asm_read(prtd->audio_client);
break;
default:
break;
}
}
static int q6asm_dai_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
struct q6asm_dai_rtd *prtd = runtime->private_data;
struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);
struct q6asm_dai_data *pdata;
int ret, i;
pdata = snd_soc_component_get_drvdata(c);
if (!pdata)
return -EINVAL;
if (!prtd || !prtd->audio_client) {
pr_err("%s: private data null or audio client freed\n",
__func__);
return -EINVAL;
}
prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
prtd->pcm_irq_pos = 0;
/* rate and channels are sent to audio driver */
if (prtd->state) {
/* clear the previous setup if any */
q6asm_cmd(prtd->audio_client, CMD_CLOSE);
q6asm_unmap_memory_regions(substream->stream,
prtd->audio_client);
q6routing_stream_close(soc_prtd->dai_link->id,
substream->stream);
}
ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client,
prtd->phys,
(prtd->pcm_size / prtd->periods),
prtd->periods);
if (ret < 0) {
pr_err("Audio Start: Buffer Allocation failed rc = %d\n",
ret);
return -ENOMEM;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM,
prtd->bits_per_sample);
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM,
prtd->bits_per_sample);
}
if (ret < 0) {
pr_err("%s: q6asm_open_write failed\n", __func__);
q6asm_audio_client_free(prtd->audio_client);
prtd->audio_client = NULL;
return -ENOMEM;
}
prtd->session_id = q6asm_get_session_id(prtd->audio_client);
ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE,
prtd->session_id, substream->stream);
if (ret) {
pr_err("%s: stream reg failed ret:%d\n", __func__, ret);
return ret;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = q6asm_media_format_block_multi_ch_pcm(
prtd->audio_client, runtime->rate,
runtime->channels, NULL,
prtd->bits_per_sample);
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
ret = q6asm_enc_cfg_blk_pcm_format_support(prtd->audio_client,
runtime->rate, runtime->channels,
prtd->bits_per_sample);
/* Queue the buffers */
for (i = 0; i < runtime->periods; i++)
q6asm_read(prtd->audio_client);
}
if (ret < 0)
pr_info("%s: CMD Format block failed\n", __func__);
prtd->state = Q6ASM_STREAM_RUNNING;
return 0;
}
static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
struct snd_pcm_runtime *runtime = substream->runtime;
struct q6asm_dai_rtd *prtd = runtime->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->state = Q6ASM_STREAM_STOPPED;
ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int q6asm_dai_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai;
struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);
struct q6asm_dai_rtd *prtd;
struct q6asm_dai_data *pdata;
struct device *dev = c->dev;
int ret = 0;
int stream_id;
stream_id = cpu_dai->driver->id;
pdata = snd_soc_component_get_drvdata(c);
if (!pdata) {
pr_err("Drv data not found ..\n");
return -EINVAL;
}
prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
prtd->substream = substream;
prtd->audio_client = q6asm_audio_client_alloc(dev,
(q6asm_cb)event_handler, prtd, stream_id,
LEGACY_PCM_MODE);
if (!prtd->audio_client) {
pr_info("%s: Could not allocate memory\n", __func__);
kfree(prtd);
return -ENOMEM;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = q6asm_dai_hardware_playback;
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw = q6asm_dai_hardware_capture;
ret = snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_sample_rates);
if (ret < 0)
pr_info("snd_pcm_hw_constraint_list failed\n");
/* Ensure that buffer size is a multiple of period size */
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
pr_info("snd_pcm_hw_constraint_integer failed\n");
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE,
PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE);
if (ret < 0) {
pr_err("constraint for buffer bytes min max ret = %d\n",
ret);
}
}
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
if (ret < 0) {
pr_err("constraint for period bytes step ret = %d\n",
ret);
}
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
if (ret < 0) {
pr_err("constraint for buffer bytes step ret = %d\n",
ret);
}
runtime->private_data = prtd;
snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback);
runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max;
if (pdata->sid < 0)
prtd->phys = substream->dma_buffer.addr;
else
prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
return 0;
}
static int q6asm_dai_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
struct q6asm_dai_rtd *prtd = runtime->private_data;
if (prtd->audio_client) {
q6asm_cmd(prtd->audio_client, CMD_CLOSE);
q6asm_unmap_memory_regions(substream->stream,
prtd->audio_client);
q6asm_audio_client_free(prtd->audio_client);
prtd->audio_client = NULL;
}
q6routing_stream_close(soc_prtd->dai_link->id,
substream->stream);
kfree(prtd);
return 0;
}
static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct q6asm_dai_rtd *prtd = runtime->private_data;
if (prtd->pcm_irq_pos >= prtd->pcm_size)
prtd->pcm_irq_pos = 0;
return bytes_to_frames(runtime, (prtd->pcm_irq_pos));
}
static int q6asm_dai_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);
struct device *dev = c->dev;
return dma_mmap_coherent(dev, vma,
runtime->dma_area, runtime->dma_addr,
runtime->dma_bytes);
}
static int q6asm_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct q6asm_dai_rtd *prtd = runtime->private_data;
prtd->pcm_size = params_buffer_bytes(params);
prtd->periods = params_periods(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
prtd->bits_per_sample = 16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
prtd->bits_per_sample = 24;
break;
}
return 0;
}
static struct snd_pcm_ops q6asm_dai_ops = {
.open = q6asm_dai_open,
.hw_params = q6asm_dai_hw_params,
.close = q6asm_dai_close,
.ioctl = snd_pcm_lib_ioctl,
.prepare = q6asm_dai_prepare,
.trigger = q6asm_dai_trigger,
.pointer = q6asm_dai_pointer,
.mmap = q6asm_dai_mmap,
};
static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm_substream *psubstream, *csubstream;
struct snd_soc_component *c = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
struct snd_pcm *pcm = rtd->pcm;
struct device *dev;
int size, ret;
dev = c->dev;
size = q6asm_dai_hardware_playback.buffer_bytes_max;
psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (psubstream) {
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
&psubstream->dma_buffer);
if (ret) {
dev_err(dev, "Cannot allocate buffer(s)\n");
return ret;
}
}
csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
if (csubstream) {
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
&csubstream->dma_buffer);
if (ret) {
dev_err(dev, "Cannot allocate buffer(s)\n");
if (psubstream)
snd_dma_free_pages(&psubstream->dma_buffer);
return ret;
}
}
return ret;
}
static void q6asm_dai_pcm_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
int i;
for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
substream = pcm->streams[i].substream;
if (substream) {
snd_dma_free_pages(&substream->dma_buffer);
substream->dma_buffer.area = NULL;
substream->dma_buffer.addr = 0;
}
}
}
static const struct snd_soc_dapm_route afe_pcm_routes[] = {
{"MM_DL1", NULL, "MultiMedia1 Playback" },
{"MM_DL2", NULL, "MultiMedia2 Playback" },
{"MM_DL3", NULL, "MultiMedia3 Playback" },
{"MM_DL4", NULL, "MultiMedia4 Playback" },
{"MM_DL5", NULL, "MultiMedia5 Playback" },
{"MM_DL6", NULL, "MultiMedia6 Playback" },
{"MM_DL7", NULL, "MultiMedia7 Playback" },
{"MM_DL7", NULL, "MultiMedia8 Playback" },
{"MultiMedia1 Capture", NULL, "MM_UL1"},
{"MultiMedia2 Capture", NULL, "MM_UL2"},
{"MultiMedia3 Capture", NULL, "MM_UL3"},
{"MultiMedia4 Capture", NULL, "MM_UL4"},
{"MultiMedia5 Capture", NULL, "MM_UL5"},
{"MultiMedia6 Capture", NULL, "MM_UL6"},
{"MultiMedia7 Capture", NULL, "MM_UL7"},
{"MultiMedia8 Capture", NULL, "MM_UL8"},
};
static int fe_dai_probe(struct snd_soc_dai *dai)
{
struct snd_soc_dapm_context *dapm;
dapm = snd_soc_component_get_dapm(dai->component);
snd_soc_dapm_add_routes(dapm, afe_pcm_routes,
ARRAY_SIZE(afe_pcm_routes));
return 0;
}
static const struct snd_soc_component_driver q6asm_fe_dai_component = {
.name = DRV_NAME,
.ops = &q6asm_dai_ops,
.pcm_new = q6asm_dai_pcm_new,
.pcm_free = q6asm_dai_pcm_free,
};
static struct snd_soc_dai_driver q6asm_fe_dais[] = {
Q6ASM_FEDAI_DRIVER(1),
Q6ASM_FEDAI_DRIVER(2),
Q6ASM_FEDAI_DRIVER(3),
Q6ASM_FEDAI_DRIVER(4),
Q6ASM_FEDAI_DRIVER(5),
Q6ASM_FEDAI_DRIVER(6),
Q6ASM_FEDAI_DRIVER(7),
Q6ASM_FEDAI_DRIVER(8),
};
static int q6asm_dai_bind(struct device *dev, struct device *master, void *data)
{
struct device_node *node = dev->of_node;
struct of_phandle_args args;
struct q6asm_dai_data *pdata;
int rc;
pdata = kzalloc(sizeof(struct q6asm_dai_data), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
if (rc < 0)
pdata->sid = -1;
else
pdata->sid = args.args[0] & SID_MASK_DEFAULT;
dev_set_drvdata(dev, pdata);
return snd_soc_register_component(dev, &q6asm_fe_dai_component,
q6asm_fe_dais,
ARRAY_SIZE(q6asm_fe_dais));
}
static void q6asm_dai_unbind(struct device *dev, struct device *master,
void *data)
{
struct q6asm_dai_data *pdata = dev_get_drvdata(dev);
snd_soc_unregister_component(dev);
kfree(pdata);
}
static const struct component_ops q6asm_dai_comp_ops = {
.bind = q6asm_dai_bind,
.unbind = q6asm_dai_unbind,
};
static int q6asm_dai_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &q6asm_dai_comp_ops);
}
static int q6asm_dai_dev_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &q6asm_dai_comp_ops);
return 0;
}
static struct platform_driver q6asm_dai_platform_driver = {
.driver = {
.name = "q6asm-dai",
},
.probe = q6asm_dai_probe,
.remove = q6asm_dai_dev_remove,
};
module_platform_driver(q6asm_dai_platform_driver);
MODULE_DESCRIPTION("Q6ASM dai driver");
MODULE_LICENSE("GPL v2");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment