| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/input.h> |
| #include <linux/of_device.h> |
| #include <sound/core.h> |
| #include <sound/soc.h> |
| #include <sound/soc-dapm.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/info.h> |
| #include <dsp/audio_notifier.h> |
| #include <dsp/q6afe-v2.h> |
| #include <dsp/q6core.h> |
| #include "device_event.h" |
| #include "msm-pcm-routing-v2.h" |
| #include "codecs/wsa881x.h" |
| |
| #define DRV_NAME "kona-asoc-snd" |
| |
| #define __CHIPSET__ "KONA " |
| #define MSM_DAILINK_NAME(name) (__CHIPSET__#name) |
| |
| #define SAMPLING_RATE_8KHZ 8000 |
| #define SAMPLING_RATE_16KHZ 16000 |
| |
| struct msm_asoc_mach_data { |
| u32 mclk_freq; |
| struct snd_info_entry *codec_root; |
| }; |
| |
| struct dev_config { |
| u32 sample_rate; |
| u32 bit_format; |
| u32 channels; |
| }; |
| |
| enum { |
| PRIM_AUX_PCM = 0, |
| }; |
| |
| static const char *const auxpcm_rate_text[] = {"KHZ_8", "KHZ_16"}; |
| static char const *bit_format_text[] = {"S16_LE", "S24_LE", "S24_3LE", |
| "S32_LE"}; |
| |
| /* Default configuration of aux pcm channels */ |
| static struct dev_config aux_pcm_rx_cfg[] = { |
| [PRIM_AUX_PCM] = {SAMPLING_RATE_8KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, |
| }; |
| |
| static struct dev_config aux_pcm_tx_cfg[] = { |
| [PRIM_AUX_PCM] = {SAMPLING_RATE_8KHZ, SNDRV_PCM_FORMAT_S16_LE, 1}, |
| }; |
| |
| static SOC_ENUM_SINGLE_EXT_DECL(prim_aux_pcm_rx_sample_rate, auxpcm_rate_text); |
| static SOC_ENUM_SINGLE_EXT_DECL(prim_aux_pcm_tx_sample_rate, auxpcm_rate_text); |
| static SOC_ENUM_SINGLE_EXT_DECL(aux_pcm_rx_format, bit_format_text); |
| static SOC_ENUM_SINGLE_EXT_DECL(aux_pcm_tx_format, bit_format_text); |
| |
| static inline int param_is_mask(int p) |
| { |
| return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && |
| (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); |
| } |
| |
| static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, |
| int n) |
| { |
| return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); |
| } |
| |
| static void param_set_mask(struct snd_pcm_hw_params *p, int n, |
| unsigned int bit) |
| { |
| if (bit >= SNDRV_MASK_MAX) |
| return; |
| if (param_is_mask(n)) { |
| struct snd_mask *m = param_to_mask(p, n); |
| |
| m->bits[0] = 0; |
| m->bits[1] = 0; |
| m->bits[bit >> 5] |= (1 << (bit & 31)); |
| } |
| } |
| |
| static int aux_pcm_get_port_idx(struct snd_kcontrol *kcontrol) |
| { |
| int idx = 0; |
| |
| if (strnstr(kcontrol->id.name, "PRIM_AUX_PCM", |
| sizeof("PRIM_AUX_PCM"))) { |
| idx = PRIM_AUX_PCM; |
| } else { |
| pr_err("%s: unsupported port: %s\n", |
| __func__, kcontrol->id.name); |
| idx = -EINVAL; |
| } |
| |
| return idx; |
| } |
| |
| static int aux_pcm_get_format(int value) |
| { |
| int format = 0; |
| |
| switch (value) { |
| case 1: |
| format = SNDRV_PCM_FORMAT_S24_LE; |
| break; |
| case 2: |
| format = SNDRV_PCM_FORMAT_S24_3LE; |
| break; |
| case 3: |
| format = SNDRV_PCM_FORMAT_S32_LE; |
| break; |
| case 0: |
| default: |
| format = SNDRV_PCM_FORMAT_S16_LE; |
| break; |
| } |
| return format; |
| } |
| |
| static int auxpcm_get_format_value(int format) |
| { |
| int value = 0; |
| |
| switch (format) { |
| case SNDRV_PCM_FORMAT_S24_LE: |
| value = 1; |
| break; |
| case SNDRV_PCM_FORMAT_S24_3LE: |
| value = 2; |
| break; |
| case SNDRV_PCM_FORMAT_S32_LE: |
| value = 3; |
| break; |
| case SNDRV_PCM_FORMAT_S16_LE: |
| default: |
| value = 0; |
| break; |
| } |
| return value; |
| } |
| |
| static int aux_pcm_get_sample_rate(int value) |
| { |
| int sample_rate = 0; |
| |
| switch (value) { |
| case 1: |
| sample_rate = SAMPLING_RATE_16KHZ; |
| break; |
| case 0: |
| default: |
| sample_rate = SAMPLING_RATE_8KHZ; |
| break; |
| } |
| return sample_rate; |
| } |
| |
| static int aux_pcm_get_sample_rate_val(int sample_rate) |
| { |
| int sample_rate_val = 0; |
| |
| switch (sample_rate) { |
| case SAMPLING_RATE_16KHZ: |
| sample_rate_val = 1; |
| break; |
| case SAMPLING_RATE_8KHZ: |
| default: |
| sample_rate_val = 0; |
| break; |
| } |
| return sample_rate_val; |
| } |
| |
| static int msm_aux_pcm_rx_format_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| ucontrol->value.enumerated.item[0] = |
| auxpcm_get_format_value(aux_pcm_rx_cfg[idx].bit_format); |
| |
| pr_debug("%s: idx[%d]_rx_format = %d, item = %d\n", __func__, |
| idx, aux_pcm_rx_cfg[idx].bit_format, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int msm_aux_pcm_rx_format_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| aux_pcm_rx_cfg[idx].bit_format = |
| aux_pcm_get_format(ucontrol->value.enumerated.item[0]); |
| |
| pr_debug("%s: idx[%d]_rx_format = %d, item = %d\n", __func__, |
| idx, aux_pcm_rx_cfg[idx].bit_format, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int msm_aux_pcm_tx_format_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| ucontrol->value.enumerated.item[0] = |
| auxpcm_get_format_value(aux_pcm_tx_cfg[idx].bit_format); |
| |
| pr_debug("%s: idx[%d]_tx_format = %d, item = %d\n", __func__, |
| idx, aux_pcm_tx_cfg[idx].bit_format, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int msm_aux_pcm_tx_format_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| aux_pcm_tx_cfg[idx].bit_format = |
| aux_pcm_get_format(ucontrol->value.enumerated.item[0]); |
| |
| pr_debug("%s: idx[%d]_tx_format = %d, item = %d\n", __func__, |
| idx, aux_pcm_tx_cfg[idx].bit_format, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int aux_pcm_rx_sample_rate_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| aux_pcm_rx_cfg[idx].sample_rate = |
| aux_pcm_get_sample_rate(ucontrol->value.enumerated.item[0]); |
| |
| pr_debug("%s: idx[%d]_rx_sample_rate = %d, item = %d\n", __func__, |
| idx, aux_pcm_rx_cfg[idx].sample_rate, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int aux_pcm_rx_sample_rate_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| ucontrol->value.enumerated.item[0] = |
| aux_pcm_get_sample_rate_val(aux_pcm_rx_cfg[idx].sample_rate); |
| |
| pr_debug("%s: idx[%d]_rx_sample_rate = %d, item = %d\n", __func__, |
| idx, aux_pcm_rx_cfg[idx].sample_rate, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int aux_pcm_tx_sample_rate_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| aux_pcm_tx_cfg[idx].sample_rate = |
| aux_pcm_get_sample_rate(ucontrol->value.enumerated.item[0]); |
| |
| pr_debug("%s: idx[%d]_tx_sample_rate = %d, item = %d\n", __func__, |
| idx, aux_pcm_tx_cfg[idx].sample_rate, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static int aux_pcm_tx_sample_rate_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int idx = aux_pcm_get_port_idx(kcontrol); |
| |
| if (idx < 0) |
| return idx; |
| |
| ucontrol->value.enumerated.item[0] = |
| aux_pcm_get_sample_rate_val(aux_pcm_tx_cfg[idx].sample_rate); |
| |
| pr_debug("%s: idx[%d]_tx_sample_rate = %d, item = %d\n", __func__, |
| idx, aux_pcm_tx_cfg[idx].sample_rate, |
| ucontrol->value.enumerated.item[0]); |
| |
| return 0; |
| } |
| |
| static const struct snd_kcontrol_new msm_snd_controls[] = { |
| SOC_ENUM_EXT("PRIM_AUX_PCM_RX Format", aux_pcm_rx_format, |
| msm_aux_pcm_rx_format_get, msm_aux_pcm_rx_format_put), |
| SOC_ENUM_EXT("PRIM_AUX_PCM_TX Format", aux_pcm_tx_format, |
| msm_aux_pcm_tx_format_get, msm_aux_pcm_tx_format_put), |
| SOC_ENUM_EXT("PRIM_AUX_PCM_RX SampleRate", prim_aux_pcm_rx_sample_rate, |
| aux_pcm_rx_sample_rate_get, |
| aux_pcm_rx_sample_rate_put), |
| SOC_ENUM_EXT("PRIM_AUX_PCM_TX SampleRate", prim_aux_pcm_tx_sample_rate, |
| aux_pcm_tx_sample_rate_get, |
| aux_pcm_tx_sample_rate_put), |
| }; |
| |
| static int msm_populate_dai_link_component_of_node( |
| struct snd_soc_card *card) |
| { |
| int i, index, ret = 0; |
| struct device *cdev = card->dev; |
| struct snd_soc_dai_link *dai_link = card->dai_link; |
| struct device_node *np; |
| |
| if (!cdev) { |
| dev_err(cdev, "%s: Sound card device memory NULL\n", __func__); |
| return -ENODEV; |
| } |
| |
| for (i = 0; i < card->num_links; i++) { |
| if (dai_link[i].platform_of_node && dai_link[i].cpu_of_node) |
| continue; |
| |
| /* populate platform_of_node for snd card dai links */ |
| if (dai_link[i].platform_name && |
| !dai_link[i].platform_of_node) { |
| index = of_property_match_string(cdev->of_node, |
| "asoc-platform-names", |
| dai_link[i].platform_name); |
| if (index < 0) { |
| dev_err(cdev, "%s: No match found for platform name: %s\n", |
| __func__, dai_link[i].platform_name); |
| ret = index; |
| goto err; |
| } |
| np = of_parse_phandle(cdev->of_node, "asoc-platform", |
| index); |
| if (!np) { |
| dev_err(cdev, "%s: retrieving phandle for platform %s, index %d failed\n", |
| __func__, dai_link[i].platform_name, |
| index); |
| ret = -ENODEV; |
| goto err; |
| } |
| dai_link[i].platform_of_node = np; |
| dai_link[i].platform_name = NULL; |
| } |
| |
| /* populate cpu_of_node for snd card dai links */ |
| if (dai_link[i].cpu_dai_name && !dai_link[i].cpu_of_node) { |
| index = of_property_match_string(cdev->of_node, |
| "asoc-cpu-names", |
| dai_link[i].cpu_dai_name); |
| if (index >= 0) { |
| np = of_parse_phandle(cdev->of_node, "asoc-cpu", |
| index); |
| if (!np) { |
| dev_err(cdev, "%s: retrieving phandle for cpu dai %s failed\n", |
| __func__, |
| dai_link[i].cpu_dai_name); |
| ret = -ENODEV; |
| goto err; |
| } |
| dai_link[i].cpu_of_node = np; |
| dai_link[i].cpu_dai_name = NULL; |
| } |
| } |
| |
| /* populate codec_of_node for snd card dai links */ |
| if (dai_link[i].codec_name && !dai_link[i].codec_of_node) { |
| index = of_property_match_string(cdev->of_node, |
| "asoc-codec-names", |
| dai_link[i].codec_name); |
| if (index < 0) |
| continue; |
| np = of_parse_phandle(cdev->of_node, "asoc-codec", |
| index); |
| if (!np) { |
| dev_err(cdev, "%s: retrieving phandle for codec %s failed\n", |
| __func__, dai_link[i].codec_name); |
| ret = -ENODEV; |
| goto err; |
| } |
| dai_link[i].codec_of_node = np; |
| dai_link[i].codec_name = NULL; |
| } |
| } |
| |
| err: |
| return ret; |
| } |
| |
| static int msm_audrx_stub_init(struct snd_soc_pcm_runtime *rtd) |
| { |
| int ret = -EINVAL; |
| struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, "msm-stub-codec"); |
| |
| if (!component) { |
| pr_err("* %s: No match for msm-stub-codec component\n", __func__); |
| return ret; |
| } |
| |
| ret = snd_soc_add_component_controls(component, msm_snd_controls, |
| ARRAY_SIZE(msm_snd_controls)); |
| if (ret < 0) { |
| dev_err(component->dev, |
| "%s: add_codec_controls failed, err = %d\n", |
| __func__, ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int msm_snd_stub_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params) |
| { |
| return 0; |
| } |
| |
| static int msm_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, |
| struct snd_pcm_hw_params *params) |
| { |
| struct snd_soc_dai_link *dai_link = rtd->dai_link; |
| struct snd_interval *rate = hw_param_interval(params, |
| SNDRV_PCM_HW_PARAM_RATE); |
| struct snd_interval *channels = hw_param_interval(params, |
| SNDRV_PCM_HW_PARAM_CHANNELS); |
| int rc = 0; |
| |
| pr_debug("%s: format = %d, rate = %d\n", |
| __func__, params_format(params), params_rate(params)); |
| |
| switch (dai_link->id) { |
| case MSM_BACKEND_DAI_AUXPCM_RX: |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, |
| aux_pcm_rx_cfg[PRIM_AUX_PCM].bit_format); |
| rate->min = rate->max = |
| aux_pcm_rx_cfg[PRIM_AUX_PCM].sample_rate; |
| channels->min = channels->max = |
| aux_pcm_rx_cfg[PRIM_AUX_PCM].channels; |
| break; |
| case MSM_BACKEND_DAI_AUXPCM_TX: |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, |
| aux_pcm_tx_cfg[PRIM_AUX_PCM].bit_format); |
| rate->min = rate->max = |
| aux_pcm_tx_cfg[PRIM_AUX_PCM].sample_rate; |
| channels->min = channels->max = |
| aux_pcm_tx_cfg[PRIM_AUX_PCM].channels; |
| break; |
| default: |
| rate->min = rate->max = SAMPLING_RATE_8KHZ; |
| break; |
| } |
| |
| return rc; |
| } |
| |
| static struct snd_soc_ops msm_stub_be_ops = { |
| .hw_params = msm_snd_stub_hw_params, |
| }; |
| |
| struct snd_soc_card snd_soc_card_stub_msm = { |
| .name = "kona-stub-snd-card", |
| }; |
| |
| static struct snd_soc_dai_link msm_stub_fe_dai_links[] = { |
| /* FrontEnd DAI Links */ |
| { |
| .name = "MSMSTUB Media1", |
| .stream_name = "MultiMedia1", |
| .cpu_dai_name = "MultiMedia1", |
| .platform_name = "msm-pcm-dsp.0", |
| .dynamic = 1, |
| .async_ops = ASYNC_DPCM_SND_SOC_PREPARE, |
| .dpcm_playback = 1, |
| .dpcm_capture = 1, |
| .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
| SND_SOC_DPCM_TRIGGER_POST}, |
| .codec_dai_name = "snd-soc-dummy-dai", |
| .codec_name = "snd-soc-dummy", |
| .ignore_suspend = 1, |
| /* this dainlink has playback support */ |
| .ignore_pmdown_time = 1, |
| .id = MSM_FRONTEND_DAI_MULTIMEDIA1 |
| }, |
| }; |
| |
| static struct snd_soc_dai_link msm_stub_be_dai_links[] = { |
| /* Backend DAI Links */ |
| { |
| .name = LPASS_BE_AUXPCM_RX, |
| .stream_name = "AUX PCM Playback", |
| .cpu_dai_name = "msm-dai-q6-auxpcm.1", |
| .platform_name = "msm-pcm-routing", |
| .codec_name = "msm-stub-codec.1", |
| .codec_dai_name = "msm-stub-rx", |
| .no_pcm = 1, |
| .dpcm_playback = 1, |
| .id = MSM_BACKEND_DAI_AUXPCM_RX, |
| .init = &msm_audrx_stub_init, |
| .be_hw_params_fixup = msm_be_hw_params_fixup, |
| .ignore_pmdown_time = 1, |
| .ignore_suspend = 1, |
| .ops = &msm_stub_be_ops, |
| }, |
| { |
| .name = LPASS_BE_AUXPCM_TX, |
| .stream_name = "AUX PCM Capture", |
| .cpu_dai_name = "msm-dai-q6-auxpcm.1", |
| .platform_name = "msm-pcm-routing", |
| .codec_name = "msm-stub-codec.1", |
| .codec_dai_name = "msm-stub-tx", |
| .no_pcm = 1, |
| .dpcm_capture = 1, |
| .id = MSM_BACKEND_DAI_AUXPCM_TX, |
| .be_hw_params_fixup = msm_be_hw_params_fixup, |
| .ignore_suspend = 1, |
| .ops = &msm_stub_be_ops, |
| }, |
| }; |
| |
| static struct snd_soc_dai_link msm_stub_dai_links[ |
| ARRAY_SIZE(msm_stub_fe_dai_links) + |
| ARRAY_SIZE(msm_stub_be_dai_links)]; |
| |
| static const struct of_device_id kona_asoc_machine_of_match[] = { |
| { .compatible = "qcom,kona-asoc-snd-stub", |
| .data = "stub_codec"}, |
| {}, |
| }; |
| |
| static struct snd_soc_card *populate_snd_card_dailinks(struct device *dev) |
| { |
| struct snd_soc_card *card = NULL; |
| struct snd_soc_dai_link *dailink; |
| int len_1, len_2; |
| int total_links; |
| const struct of_device_id *match; |
| |
| match = of_match_node(kona_asoc_machine_of_match, dev->of_node); |
| if (!match) { |
| dev_err(dev, "%s: No DT match found for sound card\n", |
| __func__); |
| return NULL; |
| } |
| |
| if (!strcmp(match->data, "stub_codec")) { |
| card = &snd_soc_card_stub_msm; |
| len_1 = ARRAY_SIZE(msm_stub_fe_dai_links); |
| len_2 = len_1 + ARRAY_SIZE(msm_stub_be_dai_links); |
| |
| memcpy(msm_stub_dai_links, |
| msm_stub_fe_dai_links, |
| sizeof(msm_stub_fe_dai_links)); |
| memcpy(msm_stub_dai_links + len_1, |
| msm_stub_be_dai_links, |
| sizeof(msm_stub_be_dai_links)); |
| |
| dailink = msm_stub_dai_links; |
| total_links = len_2; |
| } |
| |
| if (card) { |
| card->dai_link = dailink; |
| card->num_links = total_links; |
| } |
| |
| return card; |
| } |
| |
| static int msm_asoc_machine_probe(struct platform_device *pdev) |
| { |
| struct snd_soc_card *card; |
| struct msm_asoc_mach_data *pdata; |
| const struct of_device_id *match; |
| int ret; |
| |
| if (!pdev->dev.of_node) { |
| dev_err(&pdev->dev, "%s: No platform supplied from device tree\n", __func__); |
| return -EINVAL; |
| } |
| |
| pdata = devm_kzalloc(&pdev->dev, |
| sizeof(struct msm_asoc_mach_data), GFP_KERNEL); |
| if (!pdata) |
| return -ENOMEM; |
| |
| card = populate_snd_card_dailinks(&pdev->dev); |
| if (!card) { |
| dev_err(&pdev->dev, "%s: Card uninitialized\n", __func__); |
| ret = -EINVAL; |
| goto err; |
| } |
| card->dev = &pdev->dev; |
| platform_set_drvdata(pdev, card); |
| snd_soc_card_set_drvdata(card, pdata); |
| |
| ret = snd_soc_of_parse_card_name(card, "qcom,model"); |
| if (ret) { |
| dev_err(&pdev->dev, "%s: parse card name failed, err:%d\n", |
| __func__, ret); |
| goto err; |
| } |
| |
| match = of_match_node(kona_asoc_machine_of_match, |
| pdev->dev.of_node); |
| if (!match) { |
| dev_err(&pdev->dev, "%s: no matched codec is found.\n", |
| __func__); |
| goto err; |
| } |
| |
| ret = msm_populate_dai_link_component_of_node(card); |
| if (ret) { |
| ret = -EPROBE_DEFER; |
| goto err; |
| } |
| |
| ret = devm_snd_soc_register_card(&pdev->dev, card); |
| if (ret) { |
| dev_err(&pdev->dev, "%s: snd_soc_register_card failed (%d)\n", |
| __func__, ret); |
| goto err; |
| } |
| dev_info(&pdev->dev, "%s: Sound card %s registered\n", |
| __func__, card->name); |
| |
| return 0; |
| err: |
| devm_kfree(&pdev->dev, pdata); |
| return ret; |
| } |
| |
| static int msm_asoc_machine_remove(struct platform_device *pdev) |
| { |
| struct snd_soc_card *card = platform_get_drvdata(pdev); |
| |
| snd_soc_unregister_card(card); |
| return 0; |
| } |
| |
| static struct platform_driver kona_asoc_machine_driver = { |
| .driver = { |
| .name = DRV_NAME, |
| .owner = THIS_MODULE, |
| .pm = &snd_soc_pm_ops, |
| .of_match_table = kona_asoc_machine_of_match, |
| }, |
| .probe = msm_asoc_machine_probe, |
| .remove = msm_asoc_machine_remove, |
| }; |
| module_platform_driver(kona_asoc_machine_driver); |
| |
| MODULE_DESCRIPTION("ALSA SoC msm"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:" DRV_NAME); |
| MODULE_DEVICE_TABLE(of, kona_asoc_machine_of_match); |