[alsa-devel] [PATCH v4 0/15] Those patches is used for dw_hdmi audio support
We found Designware hdmi driver only support audio clock config, we can not play sound through it. To add Designware HDMI Audio support, we make those patch set: 1): fixed dw_hdmi irq bug, add irq control to suspend/resume interfaces. 2): add suspend/resume callback for dw_hdmi rockchip driver. 3): Warp irq control in functions. 4): Combine hdmi_set_clock_regenerator_n() and hdmi_regenerate_cts() 5): Adjust n/cts config order. 6): Set ncts_atomic_write & cts_manual, according to dw_hdmi document. 7): Add audio support for more display resolutions(eg. 800x600). 8): Add audio support for No-CEA display resolutions. 9): Add Audio Sample Channel Status config interfaces to dw_hdmi driver. 10): add audio clock control interfaces to dw_hdmi driver. 11): creat "dw_hdmi-audio" platform device in dw_hdmi driver. 12): add codec driver for hdmi audio, callback dw_hdmi audio config functions. 13): add sound driver for hdmi audio, creat hdmi audio sound card. 14): add dt-bings file and add hdmi_audio node to corresponding dt file.
Changes in v4: -Correct phy_type assignment bug - Combine CTS3 registers setting, reduce register operate times - Combine N3 registers setting - Add hdmi audio support when monitor support audio - Give HDMI_FC_AUD_SCHNL8 an readable value - Rename "hdmi_audio_*" to "dw_hdmi_audio_*" - Replace delaywork with irq thread, and add suspend/resume interfaces, Replace "dw-hdmi-audio" with consecutive strings. - Add ".pm = &snd_soc_pm_ops,"
Changes in v3: - Clear Hotplug interrupts before dw_hdmi_fb_register - Wrap irq control in fucntions - Setting the .pm member instead of suspend/resume - Add ID registers parse and record - Combine hdmi_set_clock_regenerator_n() and hdmi_regenerate_cts() - Only adjust the n/cts setting order - Set ncts_atomic_write & cts_manual - Determine whether sample channel should set by desig_id. - Delete hdmi_audio_config interface and modify audio clock control functions - Remove audio_config & get_connect_status callback functions and add write/read/mod register callback functions - Keep audio format config function in dw-hdmi-audio driver and remove audio_config & get_connect_status functions, move jack control to dw-hdmi-audio completely. - Delete the operation of jack in rockchip-hdmi-audio driver, get ready to switch to simple-audio-card driver. - modify cpu-of-node to i2s-controller
Changes in v2: - Add irq control to suspend/resume interfaces - Add suspend/resume support for dw_hdmi_rockchip driver - add more n/cts combinations for more display resolutions - Enable audio support for No-CEA display mode - Add audio sample channel status setting - Add audio config interfaces to dw_hdmi driver - Update the audio control interfaces - Update dw_hdmi audio control interfaces, and adjust jack report process - give "codec-name" & "codec-dai-name" an const name - remove codec-name and codec-dai-name - rename rockchip,rockchip-hdmi-audio.txt to rockchip,rockchip-dw-hdmi-audio.txt
Daniel Kurtz (3): drm: bridge/dw_hdmi: adjust n/cts setting order drm: bridge/dw_hdmi: set ncts_atomic_write & cts_manual drm: bridge/dw_hdmi: add audio sample channel status setting
Yakir Yang (12): drm: bridge/dw_hdmi: add irq control to suspend/resume drm: bridge/dw_hdmi: wrap irq control in fucntions drm: rockchip/dw_hdmi_rockchip: add resume/suspend support drm: bridge/dw_hdmi: add identification registers parse and record drm: bridge/dw_hdmi: combine hdmi_set_clock_regenerator_n() and hdmi_regenerate_cts() drm: bridge/dw_hdmi: add audio support for more display resolutions drm: bridge/dw_hdmi: enable audio support for No-CEA display resolutions drm: bridge/dw_hdmi: add enable/disable to dw_hdmi_audio callbacks drm: bridge/dw_hdmi: creat dw-hdmi-audio platform device ASoC: codec/dw-hdmi-audio: add codec driver for dw hdmi audio ASoC: rockchip/rockchip-hdmi-audio: add sound driver for hdmi audio dt-bindings: Add documentation for Rockchip dw-hdmi-audio
.../sound/rockchip,rockchip-dw-hdmi-audio.txt | 12 + drivers/gpu/drm/bridge/dw_hdmi.c | 385 ++++++++++++++++++--- drivers/gpu/drm/bridge/dw_hdmi.h | 53 ++- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 16 + include/drm/bridge/dw_hdmi.h | 17 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/dw-hdmi-audio.c | 379 ++++++++++++++++++++ sound/soc/codecs/dw-hdmi-audio.h | 74 ++++ sound/soc/rockchip/Kconfig | 9 + sound/soc/rockchip/Makefile | 2 + sound/soc/rockchip/rockchip_hdmi_audio.c | 169 +++++++++ 12 files changed, 1076 insertions(+), 46 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/rockchip,rockchip-dw-hdmi-audio.txt create mode 100644 sound/soc/codecs/dw-hdmi-audio.c create mode 100644 sound/soc/codecs/dw-hdmi-audio.h create mode 100644 sound/soc/rockchip/rockchip_hdmi_audio.c
creat dw-hdmi-audio device dynamically in probe function, and transfer some interfaces to dw-hdmi-audio driver for setting hdmi audio format & control hdmi audio clock.
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v4: None Changes in v3: - Remove audio_config & get_connect_status callback functions and add write/read/mod register callback functions
Changes in v2: - Update the audio control interfaces
drivers/gpu/drm/bridge/dw_hdmi.c | 29 +++++++++++++++++++++++++++++ include/drm/bridge/dw_hdmi.h | 15 +++++++++++++++ 2 files changed, 44 insertions(+)
diff --git a/drivers/gpu/drm/bridge/dw_hdmi.c b/drivers/gpu/drm/bridge/dw_hdmi.c index 54185e2..2ffd3e7 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.c +++ b/drivers/gpu/drm/bridge/dw_hdmi.c @@ -125,6 +125,8 @@ struct dw_hdmi { struct drm_encoder *encoder; struct drm_bridge *bridge;
+ struct platform_device *audio_pdev; + enum dw_hdmi_devtype dev_type; struct device *dev; struct clk *isfr_clk; @@ -512,6 +514,12 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock); }
+static void hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int sample_rate) +{ + hdmi->sample_rate = sample_rate; + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock); +} + /* * this submodule is responsible for the video data synchronization. * for example, for RGB 4:4:4 input, the data map is defined as @@ -1801,6 +1809,8 @@ int dw_hdmi_bind(struct device *dev, struct device *master, struct resource *iores, int irq, const struct dw_hdmi_plat_data *plat_data) { + struct platform_device_info pdevinfo; + struct dw_hdmi_audio_data audio; struct drm_device *drm = data; struct device_node *np = dev->of_node; struct device_node *ddc_node; @@ -1920,6 +1930,25 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
dev_set_drvdata(dev, hdmi);
+ memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_NONE; + + audio.irq = irq; + audio.dw = hdmi; + audio.mod = hdmi_modb; + audio.read = hdmi_readb; + audio.write = hdmi_writeb; + audio.enable = dw_hdmi_audio_enable; + audio.disable = dw_hdmi_audio_disable; + audio.set_sample_rate = hdmi_set_sample_rate; + + pdevinfo.name = "dw-hdmi-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio_pdev = platform_device_register_full(&pdevinfo); + return 0;
err_iahb: diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index e8cfe1c..23ca491 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -12,6 +12,8 @@
#include <drm/drmP.h>
+struct dw_hdmi; + enum { DW_HDMI_RES_8, DW_HDMI_RES_10, @@ -44,6 +46,19 @@ struct dw_hdmi_sym_term { u16 term; /*transmission termination value*/ };
+struct dw_hdmi_audio_data { + int irq; + struct dw_hdmi *dw; + + u8 (*read)(struct dw_hdmi *hdmi, int offset); + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); + void (*mod)(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg); + + void (*enable)(struct dw_hdmi *hdmi); + void (*disable)(struct dw_hdmi *hdmi); + void (*set_sample_rate)(struct dw_hdmi *hdmi, unsigned int rate); +}; + struct dw_hdmi_plat_data { enum dw_hdmi_devtype dev_type; const struct dw_hdmi_mpll_config *mpll_cfg;
codec driver creat an standard alsa device, than config audio and report jack status through some callback interfaces that dw_hdmi driver support.
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v4: - Replace delaywork with irq thread, and add suspend/resume interfaces, Replace "dw-hdmi-audio" with consecutive strings.
Changes in v3: - Keep audio format config function in dw-hdmi-audio driver and remove audio_config & get_connect_status functions, move jack control to dw-hdmi-audio completely.
Changes in v2: - Update dw_hdmi audio control interfaces, and adjust jack report process
sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/dw-hdmi-audio.c | 379 +++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/dw-hdmi-audio.h | 74 ++++++++ 4 files changed, 459 insertions(+) create mode 100644 sound/soc/codecs/dw-hdmi-audio.c create mode 100644 sound/soc/codecs/dw-hdmi-audio.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8349f98..b34dd12 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -75,6 +75,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C select SND_SOC_HDMI_CODEC + select SND_SOC_DW_HDMI_AUDIO select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER select SND_SOC_PCM3008 @@ -459,6 +460,9 @@ config SND_SOC_MAX98095 config SND_SOC_MAX9850 tristate
+config SND_SOC_DW_HDMI_AUDIO + tristate + config SND_SOC_PCM1681 tristate "Texas Instruments PCM1681 CODEC" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bbdfd1e..0ebb664 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -68,6 +68,7 @@ snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o snd-soc-hdmi-codec-objs := hdmi.o +snd-soc-dw-hdmi-audio-objs := dw-hdmi-audio.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o @@ -249,6 +250,7 @@ obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o +obj-$(CONFIG_SND_SOC_DW_HDMI_AUDIO) += snd-soc-dw-hdmi-audio.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o diff --git a/sound/soc/codecs/dw-hdmi-audio.c b/sound/soc/codecs/dw-hdmi-audio.c new file mode 100644 index 0000000..352a4c3 --- /dev/null +++ b/sound/soc/codecs/dw-hdmi-audio.c @@ -0,0 +1,379 @@ +/* + * dw-hdmi-codec.c + * + * DesignerWare ALSA SoC DAI driver for DW HDMI audio. + * Copyright (c) 2014, CORPORATION. All rights reserved. + * Authors: Yakir Yang ykk@rock-chips.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/.* + * + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/moduleparam.h> + +#include <sound/pcm.h> + +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> + +#include <drm/bridge/dw_hdmi.h> +#include "dw-hdmi-audio.h" + +#define DRV_NAME "dw-hdmi-audio" + +struct snd_dw_hdmi { + struct device *dev; + struct dw_hdmi_audio_data data; + + u8 jack_status; + bool is_jack_ready; + struct snd_soc_jack jack; + + bool is_playback_status; + struct hdmi_audio_fmt fmt; +}; + +int snd_dw_hdmi_jack_detect(struct snd_dw_hdmi *hdmi) +{ + u8 jack_status; + + if (!hdmi->is_jack_ready) + return -EINVAL; + + jack_status = !!(hdmi->data.read(hdmi->data.dw, HDMI_PHY_STAT0)& + HDMI_PHY_HPD) ? SND_JACK_LINEOUT : 0; + + if (jack_status != hdmi->jack_status) { + snd_soc_jack_report(&hdmi->jack, jack_status, + SND_JACK_LINEOUT); + hdmi->jack_status = jack_status; + + dev_info(hdmi->dev, "jack report [%d]\n", hdmi->jack_status); + } + + return 0; +} + +/* we don't want this irq mark with IRQF_ONESHOT flags, + * so we build an irq_default_primary_handler here */ +static irqreturn_t snd_dw_hdmi_hardirq(int irq, void *dev_id) +{ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t snd_dw_hdmi_irq(int irq, void *dev_id) +{ + struct snd_dw_hdmi *hdmi = dev_id; + + snd_dw_hdmi_jack_detect(hdmi); + + return IRQ_HANDLED; +} + +static void dw_hdmi_audio_set_fmt(struct snd_dw_hdmi *hdmi, + const struct hdmi_audio_fmt *fmt) +{ + hdmi->data.mod(hdmi->data.dw, fmt->input_type, + AUDIO_CONF0_INTERFACE_MSK, HDMI_AUD_CONF0); + + hdmi->data.mod(hdmi->data.dw, fmt->chan_num, + AUDIO_CONF0_I2SINEN_MSK, HDMI_AUD_CONF0); + + hdmi->data.mod(hdmi->data.dw, fmt->word_length, + AUDIO_CONF1_DATWIDTH_MSK, HDMI_AUD_CONF1); + + hdmi->data.mod(hdmi->data.dw, fmt->dai_fmt, + AUDIO_CONF1_DATAMODE_MSK, HDMI_AUD_CONF1); + + hdmi->data.write(hdmi->data.dw, 0, HDMI_AUD_INPUTCLKFS); + + hdmi->data.set_sample_rate(hdmi->data.dw, fmt->sample_rate); +} + +static void hdmi_audio_set_fmt(struct snd_dw_hdmi *hdmi, + const struct hdmi_audio_fmt *fmt) +{ + if (fmt) + hdmi->fmt = *fmt; + dw_hdmi_audio_set_fmt(hdmi, &hdmi->fmt); +} + +static int snd_dw_hdmi_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_dw_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + + dev_info(codec_dai->dev, "startup.\n"); + + hdmi->is_playback_status = true; + hdmi->data.enable(hdmi->data.dw); + + return 0; +} + +static int snd_dw_hdmi_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_dw_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hdmi_audio_fmt hdmi_fmt; + unsigned int fmt, rate, chan, width; + + fmt = rtd->dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK; + switch (fmt) { + case SND_SOC_DAIFMT_I2S: + hdmi_fmt.dai_fmt = AUDIO_DAIFMT_IIS; + break; + case SND_SOC_DAIFMT_LEFT_J: + hdmi_fmt.dai_fmt = AUDIO_DAIFMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + hdmi_fmt.dai_fmt = AUDIO_DAIFMT_RIGHT_J; + break; + default: + dev_err(codec_dai->dev, "DAI format unsupported"); + return -EINVAL; + } + dev_dbg(codec_dai->dev, "[codec_dai]: dai_fmt = %d.\n", fmt); + + width = params_width(params); + switch (width) { + case 16: + case 24: + hdmi_fmt.word_length = width; + break; + default: + dev_err(codec_dai->dev, "width[%d] not support!\n", width); + return -EINVAL; + } + dev_dbg(codec_dai->dev, "[codec_dai]: word_length = %d.\n", width); + + chan = params_channels(params); + switch (chan) { + case 2: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_2; + break; + case 4: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_4; + break; + case 6: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_6; + break; + case 8: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_8; + break; + default: + dev_err(codec_dai->dev, "channel[%d] not support!\n", chan); + return -EINVAL; + } + dev_dbg(codec_dai->dev, "[codec_dai]: chan_num = %d.\n", chan); + + rate = params_rate(params); + switch (rate) { + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + hdmi_fmt.sample_rate = rate; + break; + default: + dev_err(codec_dai->dev, "rate[%d] not support!\n", rate); + return -EINVAL; + } + dev_dbg(codec_dai->dev, "[codec_dai]: sample_rate = %d.\n", rate); + + hdmi_fmt.input_type = AUDIO_INPUTTYPE_IIS; + + hdmi_audio_set_fmt(hdmi, &hdmi_fmt); + + return 0; +} + +static void snd_dw_hdmi_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_dw_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + + dev_info(codec_dai->dev, "shutdown.\n"); + + hdmi->is_playback_status = false; + hdmi->data.disable(hdmi->data.dw); +} + +static int snd_dw_hdmi_audio_probe(struct snd_soc_codec *codec) +{ + struct snd_dw_hdmi *hdmi = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = snd_soc_jack_new(codec, "HDMI Jack", SND_JACK_LINEOUT, + &hdmi->jack); + if (ret) { + dev_err(hdmi->dev, "jack new failed (%d)\n", ret); + hdmi->is_jack_ready = false; + return ret; + } + + hdmi->is_jack_ready = true; + + return snd_dw_hdmi_jack_detect(hdmi); +} + +static const struct snd_soc_dapm_widget snd_dw_hdmi_audio_widgets[] = { + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route snd_dw_hdmi_audio_routes[] = { + { "TX", NULL, "Playback" }, +}; + +static const struct snd_soc_dai_ops dw_hdmi_dai_ops = { + .startup = snd_dw_hdmi_dai_startup, + .hw_params = snd_dw_hdmi_dai_hw_params, + .shutdown = snd_dw_hdmi_dai_shutdown, +}; + +static struct snd_soc_dai_driver dw_hdmi_audio_dai = { + .name = "dw-hdmi-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &dw_hdmi_dai_ops, +}; + +static const struct snd_soc_codec_driver dw_hdmi_audio = { + .probe = snd_dw_hdmi_audio_probe, + .dapm_widgets = snd_dw_hdmi_audio_widgets, + .num_dapm_widgets = ARRAY_SIZE(snd_dw_hdmi_audio_widgets), + .dapm_routes = snd_dw_hdmi_audio_routes, + .num_dapm_routes = ARRAY_SIZE(snd_dw_hdmi_audio_routes), +}; + +static int dw_hdmi_audio_probe(struct platform_device *pdev) +{ + struct dw_hdmi_audio_data *data = pdev->dev.platform_data; + struct snd_dw_hdmi *hdmi; + int ret; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->data = *data; + hdmi->dev = &pdev->dev; + hdmi->is_jack_ready = false; + platform_set_drvdata(pdev, hdmi); + + ret = devm_request_threaded_irq(&pdev->dev, hdmi->data.irq, + snd_dw_hdmi_hardirq, snd_dw_hdmi_irq, + IRQF_SHARED, DRV_NAME, hdmi); + if (ret) { + dev_err(&pdev->dev, "request irq failed (%d)\n", ret); + goto free_hdmi_data; + } + + ret = snd_soc_register_codec(&pdev->dev, &dw_hdmi_audio, + &dw_hdmi_audio_dai, 1); + if (ret) { + dev_err(&pdev->dev, "register codec failed (%d)\n", ret); + goto free_irq; + } + + dev_info(&pdev->dev, "hdmi audio init success.\n"); + + return 0; + +free_irq: + devm_free_irq(&pdev->dev, hdmi->data.irq, hdmi); +free_hdmi_data: + devm_kfree(&pdev->dev, hdmi); + + return ret; +} + +static int dw_hdmi_audio_remove(struct platform_device *pdev) +{ + struct snd_dw_hdmi *hdmi = platform_get_drvdata(pdev); + + snd_soc_unregister_codec(&pdev->dev); + devm_free_irq(&pdev->dev, hdmi->data.irq, hdmi); + devm_kfree(&pdev->dev, hdmi); + + return 0; +} + +#ifdef CONFIG_PM +static int dw_hdmi_audio_resume(struct device *dev) +{ + struct snd_dw_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->is_playback_status) { + dw_hdmi_audio_set_fmt(hdmi, &hdmi->fmt); + hdmi->data.enable(hdmi->data.dw); + } + + return 0; +} + +static int dw_hdmi_audio_suspend(struct device *dev) +{ + struct snd_dw_hdmi *hdmi = dev_get_drvdata(dev); + + hdmi->data.disable(hdmi->data.dw); + + return 0; +} +#endif + +static const struct dev_pm_ops dw_hdmi_audio_pm = { + SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_audio_suspend, dw_hdmi_audio_resume) +}; + +static const struct of_device_id dw_hdmi_audio_ids[] = { + { .compatible = DRV_NAME, }, + { } +}; + +static struct platform_driver dw_hdmi_audio_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &dw_hdmi_audio_pm, + .of_match_table = of_match_ptr(dw_hdmi_audio_ids), + }, + .probe = dw_hdmi_audio_probe, + .remove = dw_hdmi_audio_remove, +}; +module_platform_driver(dw_hdmi_audio_driver); + +MODULE_AUTHOR("Yakir Yang ykk@rock-chips.com"); +MODULE_DESCRIPTION("DW HDMI Audio ASoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, dw_hdmi_audio_ids); diff --git a/sound/soc/codecs/dw-hdmi-audio.h b/sound/soc/codecs/dw-hdmi-audio.h new file mode 100644 index 0000000..3ed46ba --- /dev/null +++ b/sound/soc/codecs/dw-hdmi-audio.h @@ -0,0 +1,74 @@ +/* + * dw-hdmi-audio.h -- DW HDMI ALSA SoC Audio driver + * + * Copyright 2011-2012 DesignerWare Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DW_HDMI_AUDIO_H +#define _DW_HDMI_AUDIO_H + +enum hdmi_audio_reg { + HDMI_PHY_STAT0 = 0x3004, + HDMI_AUD_CONF0 = 0x3100, + HDMI_AUD_CONF1 = 0x3101, + HDMI_AUD_INPUTCLKFS = 0x3206, + HDMI_MC_CLKDIS = 0x4001, +}; + +enum { + HDMI_PHY_HPD = 0x2, + HDMI_MC_CLKDIS_AUDCLK_DISABLE = 0x8, +}; + +enum hdmi_audio_samplerate { + AUDIO_SAMPLERATE_32K = 32000, + AUDIO_SAMPLERATE_44K1 = 44100, + AUDIO_SAMPLERATE_48K = 48000, + AUDIO_SAMPLERATE_88K2 = 88200, + AUDIO_SAMPLERATE_96K = 96000, + AUDIO_SAMPLERATE_176K4 = 176400, + AUDIO_SAMPLERATE_192K = 192000, +}; + +#define AUDIO_CONF1_DATWIDTH_MSK 0x1F +enum hdmi_audio_wordlength { + AUDIO_WORDLENGTH_16BIT = 16, + AUDIO_WORDLENGTH_24BIT = 24, +}; + +#define AUDIO_CONF1_DATAMODE_MSK 0xE0 +enum hdmi_audio_daifmt { + AUDIO_DAIFMT_IIS = 0x00, + AUDIO_DAIFMT_RIGHT_J = 0x20, + AUDIO_DAIFMT_LEFT_J = 0x40, + AUDIO_DAIFMT_BURST_1 = 0x60, + AUDIO_DAIFMT_BURST_2 = 0x80, +}; + +#define AUDIO_CONF0_INTERFACE_MSK 0x20 +enum hdmi_audio_inputtype { + AUDIO_INPUTTYPE_IIS = 0x20, + AUDIO_INPUTTYPE_SPDIF = 0x00, +}; + +#define AUDIO_CONF0_I2SINEN_MSK 0x0F +enum hdmi_audio_channelnum { + AUDIO_CHANNELNUM_2 = 0x01, + AUDIO_CHANNELNUM_4 = 0x03, + AUDIO_CHANNELNUM_6 = 0x07, + AUDIO_CHANNELNUM_8 = 0x0F, +}; + +struct hdmi_audio_fmt { + enum hdmi_audio_inputtype input_type; + enum hdmi_audio_channelnum chan_num; + enum hdmi_audio_samplerate sample_rate; + enum hdmi_audio_wordlength word_length; + enum hdmi_audio_daifmt dai_fmt; +}; + +#endif
On Sat, 2015-02-28 at 21:59 -0500, Yakir Yang wrote:
--- /dev/null +++ b/sound/soc/codecs/dw-hdmi-audio.c @@ -0,0 +1,379 @@ +/*
- dw-hdmi-codec.c
Doesn't match the filename. Is this line needed?
- DesignerWare ALSA SoC DAI driver for DW HDMI audio.
- Copyright (c) 2014, CORPORATION. All rights reserved.
- Authors: Yakir Yang ykk@rock-chips.com
- This program is free software; you can redistribute it and/or modify it
- under the terms and conditions of the GNU General Public License,
- version 2, as published by the Free Software Foundation.
- This program is distributed in the hope it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see http://www.gnu.org/licenses/.*
- */
This states that the license is plain GPL v2.
(Missing empty line here.)
+#include <linux/init.h>
[...]
+MODULE_LICENSE("GPL");
So you probably want MODULE_LICENSE("GPL v2");
here.
Paul Bolle
在 2015/3/2 17:15, Paul Bolle 写道:
On Sat, 2015-02-28 at 21:59 -0500, Yakir Yang wrote:
--- /dev/null +++ b/sound/soc/codecs/dw-hdmi-audio.c @@ -0,0 +1,379 @@ +/*
- dw-hdmi-codec.c
Doesn't match the filename. Is this line needed?
Thanks, this comment are good for read, and seems others codec driver also content this comment, so I think we can keep this comment. I will correct the file name it in next version.
Thanks for you reply :)
- DesignerWare ALSA SoC DAI driver for DW HDMI audio.
- Copyright (c) 2014, CORPORATION. All rights reserved.
- Authors: Yakir Yang ykk@rock-chips.com
- This program is free software; you can redistribute it and/or modify it
- under the terms and conditions of the GNU General Public License,
- version 2, as published by the Free Software Foundation.
- This program is distributed in the hope it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see http://www.gnu.org/licenses/.*
- */
This states that the license is plain GPL v2.
(Missing empty line here.)
Okay, thanks, correct it in next version.
Thanks :)
+#include <linux/init.h>
[...]
+MODULE_LICENSE("GPL");
So you probably want MODULE_LICENSE("GPL v2");
here.
Okay, thanks, correct it in next version.
Thanks :)
Yakir Yang Best regards.
Paul Bolle
On Sat, Feb 28, 2015 at 09:59:20PM -0500, Yakir Yang wrote:
codec driver creat an standard alsa device, than config audio and report jack status through some callback interfaces that dw_hdmi driver support.
Looking at this it's not althogether clear to me how specific this is to the Designware hardware - it looks like it's all callbacks into the main driver doing pretty generic things apart from the fact that we request an interrupt here (but then use it to do another callback into the driver).
Please also try to only CC relevant people on mails - you've got a *very* large list of people there and for a lot of them it's hard to understand why you've copied them. Copying people adds to the amount of mail they need to read so it's good to try to stay relevant.
- if (jack_status != hdmi->jack_status) {
snd_soc_jack_report(&hdmi->jack, jack_status,
SND_JACK_LINEOUT);
We may need a new jack type here, or perhaps we ought to just be reporting the jack status via extcon?
hdmi->jack_status = jack_status;
dev_info(hdmi->dev, "jack report [%d]\n", hdmi->jack_status);
Please remove this and all the other prints, it's far too noisy.
+/* we don't want this irq mark with IRQF_ONESHOT flags,
- so we build an irq_default_primary_handler here */
+static irqreturn_t snd_dw_hdmi_hardirq(int irq, void *dev_id) +{
- return IRQ_WAKE_THREAD;
+}
Why do we not want to use IRQF_ONESHOT?
+static int dw_hdmi_audio_remove(struct platform_device *pdev) +{
- struct snd_dw_hdmi *hdmi = platform_get_drvdata(pdev);
- snd_soc_unregister_codec(&pdev->dev);
- devm_free_irq(&pdev->dev, hdmi->data.irq, hdmi);
- devm_kfree(&pdev->dev, hdmi);
Explicitly freeing devm_ things seems to be missing the point a bit...
+static const struct of_device_id dw_hdmi_audio_ids[] = {
- { .compatible = DRV_NAME, },
- { }
+};
Your driver name didn't have a vendor prefix, this is broken - you should probably just remove DRV_NAME and use the string directly in the few places it's used. It's also not clear to me that this is a separate device from the parent device and should therefore appear separately in DT at all.
+static struct platform_driver dw_hdmi_audio_driver = {
- .driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
No need to assign owner any more.
Add a sound driver that combines rockchip-i2s cpu_dai and dw-hdmi-codec as codec_dai to provide hdmi audio output on rk3288 platforms.
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v4: - Add ".pm = &snd_soc_pm_ops,"
Changes in v3: - Delete the operation of jack in rockchip-hdmi-audio driver, get ready to switch to simple-audio-card driver.
Changes in v2: - give "codec-name" & "codec-dai-name" an const name
sound/soc/rockchip/Kconfig | 9 ++ sound/soc/rockchip/Makefile | 2 + sound/soc/rockchip/rockchip_hdmi_audio.c | 169 +++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 sound/soc/rockchip/rockchip_hdmi_audio.c
diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index e181826..ed2b7f0 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -14,3 +14,12 @@ config SND_SOC_ROCKCHIP_I2S Say Y or M if you want to add support for I2S driver for Rockchip I2S device. The device supports upto maximum of 8 channels each for play and record. + +config SND_SOC_ROCKCHIP_HDMI_AUDIO + tristate "ASoC support for Rockchip HDMI audio" + depends on SND_SOC_ROCKCHIP + select SND_SOC_ROCKCHIP_I2S + select SND_SOC_DW_HDMI_AUDIO + help + Say Y or M here if you want to add support for SoC audio on Rockchip + HDMI, such as rk3288 hdmi. diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile index b921909..b9185b3 100644 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -1,4 +1,6 @@ # ROCKCHIP Platform Support snd-soc-i2s-objs := rockchip_i2s.o +snd-soc-rockchip-hdmi-audio-objs := rockchip_hdmi_audio.o
obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-i2s.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_HDMI_AUDIO) += snd-soc-rockchip-hdmi-audio.o diff --git a/sound/soc/rockchip/rockchip_hdmi_audio.c b/sound/soc/rockchip/rockchip_hdmi_audio.c new file mode 100644 index 0000000..cf95037 --- /dev/null +++ b/sound/soc/rockchip/rockchip_hdmi_audio.c @@ -0,0 +1,169 @@ +/* + * rockchip-hdmi-card.c + * + * ROCKCHIP ALSA SoC DAI driver for HDMI audio on rockchip processors. + * Copyright (c) 2014, ROCKCHIP CORPORATION. All rights reserved. + * Authors: Yakir Yang ykk@rock-chips.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/.* + * + */ +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/core.h> +#include <sound/pcm_params.h> + +#include "rockchip_i2s.h" + +#define DRV_NAME "rockchip-hdmi-audio" + +static int rockchip_hdmi_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int dai_fmt = rtd->dai_link->dai_fmt; + int mclk, ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to set cpu_dai fmt.\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to set cpu_dai sysclk.\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops hdmi_audio_dai_ops = { + .hw_params = rockchip_hdmi_audio_hw_params, +}; + +static struct snd_soc_dai_link hdmi_audio_dai = { + .name = "RockchipHDMI", + .stream_name = "RockchipHDMI", + .codec_name = "dw-hdmi-audio", + .codec_dai_name = "dw-hdmi-hifi", + .ops = &hdmi_audio_dai_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card rockchip_hdmi_audio_card = { + .name = "RockchipHDMI", + .owner = THIS_MODULE, + .dai_link = &hdmi_audio_dai, + .num_links = 1, +}; + +static int rockchip_hdmi_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &rockchip_hdmi_audio_card; + struct device_node *np = pdev->dev.of_node; + int ret; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + hdmi_audio_dai.cpu_of_node = of_parse_phandle(np, "i2s-controller", 0); + if (!hdmi_audio_dai.cpu_of_node) { + dev_err(&pdev->dev, "Property 'i2s-controller' missing !\n"); + goto free_priv_data; + } + + hdmi_audio_dai.platform_of_node = hdmi_audio_dai.cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "register card failed (%d)\n", ret); + card->dev = NULL; + goto free_cpu_of_node; + } + + dev_info(&pdev->dev, "hdmi audio init success.\n"); + + return 0; + +free_cpu_of_node: + hdmi_audio_dai.cpu_of_node = NULL; + hdmi_audio_dai.platform_of_node = NULL; +free_priv_data: + snd_soc_card_set_drvdata(card, NULL); + platform_set_drvdata(pdev, NULL); + card->dev = NULL; + + return ret; +} + +static int rockchip_hdmi_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + snd_soc_card_set_drvdata(card, NULL); + platform_set_drvdata(pdev, NULL); + card->dev = NULL; + + return 0; +} + +static const struct of_device_id rockchip_hdmi_audio_of_match[] = { + { .compatible = "rockchip,rk3288-hdmi-audio", }, + {}, +}; + +static struct platform_driver rockchip_hdmi_audio_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = rockchip_hdmi_audio_of_match, + }, + .probe = rockchip_hdmi_audio_probe, + .remove = rockchip_hdmi_audio_remove, +}; +module_platform_driver(rockchip_hdmi_audio_driver); + +MODULE_AUTHOR("Yakir Yang ykk@rock-chips.com"); +MODULE_DESCRIPTION("Rockchip HDMI Audio ASoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_hdmi_audio_of_match);
On Sat, 2015-02-28 at 22:04 -0500, Yakir Yang wrote:
--- /dev/null +++ b/sound/soc/rockchip/rockchip_hdmi_audio.c @@ -0,0 +1,169 @@ +/*
- rockchip-hdmi-card.c
Doesn't match the filename. Is this line needed anyway?
- ROCKCHIP ALSA SoC DAI driver for HDMI audio on rockchip processors.
- Copyright (c) 2014, ROCKCHIP CORPORATION. All rights reserved.
- Authors: Yakir Yang ykk@rock-chips.com
- This program is free software; you can redistribute it and/or modify it
- under the terms and conditions of the GNU General Public License,
- version 2, as published by the Free Software Foundation.
- This program is distributed in the hope it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see http://www.gnu.org/licenses/.*
- */
This states the license is plain GPL v2.
+MODULE_LICENSE("GPL");
So you probably want MODULE_LICENSE("GPL v2");
here.
Paul Bolle
在 2015/3/2 17:07, Paul Bolle 写道:
On Sat, 2015-02-28 at 22:04 -0500, Yakir Yang wrote:
--- /dev/null +++ b/sound/soc/rockchip/rockchip_hdmi_audio.c @@ -0,0 +1,169 @@ +/*
- rockchip-hdmi-card.c
Doesn't match the filename. Is this line needed anyway?
Thanks, this comment are good for read, and seems others codec driver also content this comment, so I think we can keep this comment. I will correct the file name it in next version.
Thanks for you reply :)
- ROCKCHIP ALSA SoC DAI driver for HDMI audio on rockchip processors.
- Copyright (c) 2014, ROCKCHIP CORPORATION. All rights reserved.
- Authors: Yakir Yang ykk@rock-chips.com
- This program is free software; you can redistribute it and/or modify it
- under the terms and conditions of the GNU General Public License,
- version 2, as published by the Free Software Foundation.
- This program is distributed in the hope it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see http://www.gnu.org/licenses/.*
- */
This states the license is plain GPL v2.
Okay, thanks, correct it in next version.
Thanks
Yakir Yang Best regards.
+MODULE_LICENSE("GPL");
So you probably want MODULE_LICENSE("GPL v2");
here.
Paul Bolle
On Sat, Feb 28, 2015 at 10:04:30PM -0500, Yakir Yang wrote:
- ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt);
- if (ret < 0) {
dev_err(cpu_dai->dev, "failed to set cpu_dai fmt.\n");
return ret;
- }
You've already set this in the dai_link, no need to do it again.
- dev_info(&pdev->dev, "hdmi audio init success.\n");
Please remove noisy prints like this.
+free_cpu_of_node:
- hdmi_audio_dai.cpu_of_node = NULL;
- hdmi_audio_dai.platform_of_node = NULL;
+free_priv_data:
- snd_soc_card_set_drvdata(card, NULL);
- platform_set_drvdata(pdev, NULL);
- card->dev = NULL;
If any of these assignments is doing anything there's a problem with the code.
+{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- snd_soc_unregister_card(card);
devm_snd_soc_register_card() and you can remove this function entirely.
+static const struct of_device_id rockchip_hdmi_audio_of_match[] = {
- { .compatible = "rockchip,rk3288-hdmi-audio", },
- {},
+};
There is no documentation for this binding, binding documentation is mandatory. Based on the compatible string this looks like it's specific to the SoC rather than a design for a board - is the whole card part of the SoC?
Hi Mark,
On 2015年03月27日 02:16, Mark Brown wrote:
On Sat, Feb 28, 2015 at 10:04:30PM -0500, Yakir Yang wrote:
- ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt);
- if (ret < 0) {
dev_err(cpu_dai->dev, "failed to set cpu_dai fmt.\n");
return ret;
- }
You've already set this in the dai_link, no need to do it again.
Okay, correct it in next v5.
- dev_info(&pdev->dev, "hdmi audio init success.\n");
Please remove noisy prints like this.
Okay, turn it to dev_debug(...)
+free_cpu_of_node:
- hdmi_audio_dai.cpu_of_node = NULL;
- hdmi_audio_dai.platform_of_node = NULL;
+free_priv_data:
- snd_soc_card_set_drvdata(card, NULL);
- platform_set_drvdata(pdev, NULL);
- card->dev = NULL;
If any of these assignments is doing anything there's a problem with the code.
Yes, when probe failed, program will goto this code.
+{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- snd_soc_unregister_card(card);
devm_snd_soc_register_card() and you can remove this function entirely.
do you mean that when I take devm_snd_soc_register_card() to register card, then I do not need unregister card any more(destroy with device) ?
+static const struct of_device_id rockchip_hdmi_audio_of_match[] = {
- { .compatible = "rockchip,rk3288-hdmi-audio", },
- {},
+};
There is no documentation for this binding, binding documentation is mandatory. Based on the compatible string this looks like it's specific to the SoC rather than a design for a board - is the whole card part of the SoC?
It's my fault, cause the dts patch have not CC you, I will correct it in next v5
Thanks :) Yakir
On Fri, Mar 27, 2015 at 09:16:17AM +0800, yakir wrote:
On 2015年03月27日 02:16, Mark Brown wrote:
+free_cpu_of_node:
- hdmi_audio_dai.cpu_of_node = NULL;
- hdmi_audio_dai.platform_of_node = NULL;
+free_priv_data:
- snd_soc_card_set_drvdata(card, NULL);
- platform_set_drvdata(pdev, NULL);
- card->dev = NULL;
If any of these assignments is doing anything there's a problem with the code.
Yes, when probe failed, program will goto this code.
You're missing the point, these don't do anything useful.
+{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- snd_soc_unregister_card(card);
devm_snd_soc_register_card() and you can remove this function entirely.
do you mean that when I take devm_snd_soc_register_card() to register card, then I do not need unregister card any more(destroy with device) ?
Yes, that is the whole point of the devm_ APIs.
participants (4)
-
Mark Brown
-
Paul Bolle
-
yakir
-
Yakir Yang