From 769ff2f06c1747487e51a8e878ed08bf9226bfca Mon Sep 17 00:00:00 2001 From: Sumit Bhattacharya Date: Thu, 10 Mar 2011 04:32:57 +0530 Subject: tegra-alsa: Integrate spdif driver with alsa Bug 783013 Bug 788021 Change-Id: I97425d5a24095fe8e8e5b76b9a30eb621999e507 Reviewed-on: http://git-master/r/22330 Reviewed-by: Varun Colbert Tested-by: Sumit Bhattacharya Reviewed-by: Scott Peterson Reviewed-by: Vijay Mali --- sound/soc/tegra/Makefile | 1 + sound/soc/tegra/tegra_generic_codec.c | 30 ++- sound/soc/tegra/tegra_i2s.c | 38 ++-- sound/soc/tegra/tegra_pcm.c | 24 +- sound/soc/tegra/tegra_soc.h | 11 +- sound/soc/tegra/tegra_soc_wm8903.c | 20 +- sound/soc/tegra/tegra_spdif.c | 416 ++++++++++++++++++++++++++++++++++ 7 files changed, 511 insertions(+), 29 deletions(-) create mode 100644 sound/soc/tegra/tegra_spdif.c (limited to 'sound') diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 0ce9b2ef5da1..205c240121db 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -1,6 +1,7 @@ ccflags-y += -DNV_DEBUG=0 obj-$(CONFIG_TEGRA_PCM) += tegra_pcm.o obj-$(CONFIG_TEGRA_I2S) += tegra_i2s.o +obj-$(CONFIG_TEGRA_IEC) += tegra_spdif.o obj-$(CONFIG_TEGRA_JACK) += tegra_wired_jack.o obj-$(CONFIG_TEGRA_ALSA) += tegra_soc_controls.o obj-$(CONFIG_TEGRA_GENERIC_CODEC)+= tegra_generic_codec.o diff --git a/sound/soc/tegra/tegra_generic_codec.c b/sound/soc/tegra/tegra_generic_codec.c index 5da9e8a6294f..ad8209648de8 100644 --- a/sound/soc/tegra/tegra_generic_codec.c +++ b/sound/soc/tegra/tegra_generic_codec.c @@ -93,13 +93,32 @@ struct snd_soc_dai tegra_generic_codec_dai[] = { .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &tegra_generic_codec_stub_ops, + }, + { + .name = "tegra_generic_spdif_codec", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TEGRA_SAMPLE_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = TEGRA_SAMPLE_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra_generic_codec_stub_ops, } }; EXPORT_SYMBOL_GPL(tegra_generic_codec_dai); static int generic_codec_init(struct platform_device *pdev) { - int ret = 0; + int i, ret = 0; tegra_generic_codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (!tegra_generic_codec) @@ -114,7 +133,9 @@ static int generic_codec_init(struct platform_device *pdev) tegra_generic_codec->num_dai = ARRAY_SIZE(tegra_generic_codec_dai); tegra_generic_codec->write = NULL; tegra_generic_codec->read = NULL; - tegra_generic_codec_dai[0].dev = &pdev->dev; + for (i = 0; i < tegra_generic_codec->num_dai; i++) + tegra_generic_codec_dai[i].dev = &pdev->dev; + INIT_LIST_HEAD(&tegra_generic_codec->dapm_widgets); INIT_LIST_HEAD(&tegra_generic_codec->dapm_paths); @@ -143,6 +164,8 @@ codec_err: static int generic_codec_remove(struct platform_device *pdev) { + int i; + if (!tegra_generic_codec) return 0; @@ -150,7 +173,8 @@ static int generic_codec_remove(struct platform_device *pdev) snd_soc_unregister_codec(tegra_generic_codec); kfree(tegra_generic_codec); tegra_generic_codec = NULL; - tegra_generic_codec_dai[0].dev = NULL; + for (i = 0; i < tegra_generic_codec->num_dai; i++) + tegra_generic_codec_dai[i].dev = NULL; return 0; } diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c index 9055a3121219..004384041e99 100644 --- a/sound/soc/tegra/tegra_i2s.c +++ b/sound/soc/tegra/tegra_i2s.c @@ -34,11 +34,12 @@ struct tegra_i2s_info { int irq; /* Control for whole I2S (Data format, etc.) */ unsigned int bit_format; + int ref_count; struct i2s_runtime_data i2s_regs; struct das_regs_cache das_regs; }; -void setup_dma_request(struct snd_pcm_substream *substream, +void setup_i2s_dma_request(struct snd_pcm_substream *substream, struct tegra_dma_req *req, void (*dma_callback)(struct tegra_dma_req *req), void *dma_data) @@ -266,11 +267,6 @@ static int tegra_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai, if (info && info->i2s_clk) { clk_set_rate(info->i2s_clk, pdata->i2s_clk_rate); - if (clk_enable(info->i2s_clk)) { - pr_err("%s: failed to enable i2s-%d clock\n", __func__, - cpu_dai->id+1); - return -EIO; - } } else { pr_err("%s: could not get i2s-%d clock\n", __func__, @@ -357,6 +353,8 @@ int tegra_i2s_suspend(struct snd_soc_dai *cpu_dai) i2s_get_all_regs(cpu_dai->id, &info->i2s_regs); tegra_das_get_all_regs(&info->das_regs); + clk_disable(info->dap_mclk); + return 0; } @@ -364,6 +362,8 @@ int tegra_i2s_resume(struct snd_soc_dai *cpu_dai) { struct tegra_i2s_info *info = cpu_dai->private_data; + clk_enable(info->dap_mclk); + tegra_das_set_all_regs(&info->das_regs); i2s_set_all_regs(cpu_dai->id, &info->i2s_regs); @@ -380,12 +380,14 @@ static int tegra_i2s_startup(struct snd_pcm_substream *substream, { struct tegra_i2s_info *info = dai->private_data; - clk_enable(info->dap_mclk); - clk_enable(info->audio_sync_clk); - - /* set das pins state to normal */ - tegra_das_power_mode(true); + if (!info->ref_count) { + /* set das pins state to normal */ + tegra_das_power_mode(true); + clk_enable(info->audio_sync_clk); + clk_enable(info->i2s_clk); + } + info->ref_count++; return 0; } @@ -394,11 +396,16 @@ static void tegra_i2s_shutdown(struct snd_pcm_substream *substream, { struct tegra_i2s_info *info = dai->private_data; - /* set das pins state to tristate */ - tegra_das_power_mode(false); + if (info->ref_count > 0) + info->ref_count--; - clk_disable(info->dap_mclk); - clk_disable(info->audio_sync_clk); + if (!info->ref_count) { + clk_disable(info->i2s_clk); + clk_disable(info->audio_sync_clk); + + /* set das pins state to tristate */ + tegra_das_power_mode(false); + } return; } @@ -530,6 +537,7 @@ static int tegra_i2s_driver_probe(struct platform_device *pdev) err = PTR_ERR(info->dap_mclk); goto fail_unmap_mem; } + clk_enable(info->dap_mclk); info->audio_sync_clk = i2s_get_clock_by_name( info->pdata->audio_sync_clk); diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c index da2d476b6886..28f6b6b0d09d 100644 --- a/sound/soc/tegra/tegra_pcm.c +++ b/sound/soc/tegra/tegra_pcm.c @@ -205,6 +205,8 @@ static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream) static int tegra_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct tegra_runtime_data *prtd = 0; int i, ret=0; @@ -236,11 +238,23 @@ static int tegra_pcm_open(struct snd_pcm_substream *substream) prtd->state = STATE_INVALID; - for (i = 0; i < DMA_REQ_QCOUNT; i++) { - setup_dma_request(substream, - &prtd->dma_req[i], - dma_complete_callback, - prtd); + if (strcmp(cpu_dai->name, "tegra-spdif") == 0) + { + for (i = 0; i < DMA_REQ_QCOUNT; i++) { + setup_spdif_dma_request(substream, + &prtd->dma_req[i], + dma_complete_callback, + prtd); + } + } + else + { + for (i = 0; i < DMA_REQ_QCOUNT; i++) { + setup_i2s_dma_request(substream, + &prtd->dma_req[i], + dma_complete_callback, + prtd); + } } prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_CONTINUOUS_DOUBLE); diff --git a/sound/soc/tegra/tegra_soc.h b/sound/soc/tegra/tegra_soc.h index 92f621bf87d0..3b88271d976f 100644 --- a/sound/soc/tegra/tegra_soc.h +++ b/sound/soc/tegra/tegra_soc.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -89,12 +90,8 @@ struct tegra_runtime_data { int dma_reqid_tail; volatile int state; int period_index; - int i2s_shutdown; int dma_state; struct tegra_dma_channel *dma_chan; - struct clk *i2s_clk; - struct clk *dap_mclk; - struct clk *audio_sync_clk; }; struct tegra_audio_data { @@ -119,7 +116,11 @@ int tegra_jack_init(struct snd_soc_codec *codec); void tegra_jack_exit(void); void tegra_switch_set_state(int state); -void setup_dma_request(struct snd_pcm_substream *substream, +void setup_i2s_dma_request(struct snd_pcm_substream *substream, + struct tegra_dma_req *req, + void (*dma_callback)(struct tegra_dma_req *req), + void *dma_data); +void setup_spdif_dma_request(struct snd_pcm_substream *substream, struct tegra_dma_req *req, void (*dma_callback)(struct tegra_dma_req *req), void *dma_data); diff --git a/sound/soc/tegra/tegra_soc_wm8903.c b/sound/soc/tegra/tegra_soc_wm8903.c index a409d517dd2a..3fe2dd4dc468 100644 --- a/sound/soc/tegra/tegra_soc_wm8903.c +++ b/sound/soc/tegra/tegra_soc_wm8903.c @@ -21,6 +21,7 @@ static struct platform_device *tegra_snd_device; extern struct snd_soc_dai tegra_i2s_dai[]; +extern struct snd_soc_dai tegra_spdif_dai; extern struct snd_soc_dai tegra_generic_codec_dai[]; extern struct snd_soc_platform tegra_soc_platform; @@ -177,6 +178,12 @@ static int tegra_voice_hw_params(struct snd_pcm_substream *substream, return 0; } +static int tegra_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return 0; +} + static struct snd_soc_ops tegra_hifi_ops = { .hw_params = tegra_hifi_hw_params, }; @@ -185,6 +192,10 @@ static struct snd_soc_ops tegra_voice_ops = { .hw_params = tegra_voice_hw_params, }; +static struct snd_soc_ops tegra_spdif_ops = { + .hw_params = tegra_spdif_hw_params, +}; + static int tegra_codec_init(struct snd_soc_codec *codec) { return tegra_controls_init(codec); @@ -207,7 +218,14 @@ static struct snd_soc_dai_link tegra_soc_dai[] = { .init = tegra_codec_init, .ops = &tegra_voice_ops, }, - + { + .name = "Tegra-spdif", + .stream_name = "Tegra Spdif", + .cpu_dai = &tegra_spdif_dai, + .codec_dai = &tegra_generic_codec_dai[1], + .init = tegra_codec_init, + .ops = &tegra_spdif_ops, + }, }; static struct snd_soc_card tegra_snd_soc = { diff --git a/sound/soc/tegra/tegra_spdif.c b/sound/soc/tegra/tegra_spdif.c new file mode 100644 index 000000000000..d69cbd7cc86c --- /dev/null +++ b/sound/soc/tegra/tegra_spdif.c @@ -0,0 +1,416 @@ +/* + * tegra_spdif.c -- ALSA Soc Audio Layer + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "tegra_soc.h" + +/* spdif controller */ +struct tegra_spdif_info { + struct platform_device *pdev; + struct tegra_audio_platform_data *pdata; + struct clk *spdif_clk; + unsigned long spdif_phys; + unsigned long spdif_base; + + unsigned long dma_req_sel; + int irq; + + int ref_count; + struct spdif_regs_cache spdif_regs; +}; + +void setup_spdif_dma_request(struct snd_pcm_substream *substream, + struct tegra_dma_req *req, + void (*dma_callback)(struct tegra_dma_req *req), + void *dma_data) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct tegra_spdif_info *info = cpu_dai->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + req->to_memory = false; + req->dest_addr = spdif_get_fifo_phy_base(info->spdif_phys, + AUDIO_TX_MODE); + req->dest_wrap = 4; + req->source_wrap = 0; + req->dest_bus_width = 32; + req->source_bus_width = 32; + } else { + req->to_memory = true; + req->dest_addr = spdif_get_fifo_phy_base(info->spdif_phys, + AUDIO_RX_MODE); + req->dest_wrap = 0; + req->source_wrap = 4; + req->dest_bus_width = 32; + req->source_bus_width = 32; + } + req->complete = dma_callback; + req->dev = dma_data; + req->req_sel = info->dma_req_sel; + + return; +} + +/* playback */ +static inline void start_spdif_playback(struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + + spdif_fifo_set_attention_level(info->spdif_base, AUDIO_TX_MODE, + SPDIF_FIFO_ATN_LVL_FOUR_SLOTS); + spdif_fifo_enable(info->spdif_base, AUDIO_TX_MODE, true); +} + +static inline void stop_spdif_playback(struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + + spdif_fifo_enable(info->spdif_base, AUDIO_TX_MODE, false); + while (spdif_get_status(info->spdif_base) & SPDIF_STATUS_0_TX_BSY); +} + +/* capture */ +static inline void start_spdif_capture(struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + + spdif_fifo_set_attention_level(info->spdif_base, AUDIO_RX_MODE, + SPDIF_FIFO_ATN_LVL_FOUR_SLOTS); + spdif_fifo_enable(info->spdif_base, AUDIO_RX_MODE, true); +} + +static inline void stop_spdif_capture(struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + + spdif_fifo_enable(info->spdif_base, AUDIO_RX_MODE, false); + while (spdif_get_status(info->spdif_base) & SPDIF_STATUS_0_RX_BSY); +} + +static int tegra_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + int ret = 0; + int val; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = SPDIF_BIT_MODE_MODE16BIT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = SPDIF_BIT_MODE_MODE24BIT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = SPDIF_BIT_MODE_MODERAW; + break; + default: + ret =-EINVAL; + goto err; + } + + spdif_set_bit_mode(info->spdif_base, val); + spdif_set_fifo_packed(info->spdif_base, 1); + + switch (params_rate(params)) { + case 8000: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + val = params_rate(params); + break; + default: + ret = -EINVAL; + goto err; + } + + spdif_set_sample_rate(info->spdif_base, val); + return 0; + +err: + return ret; +} + + +static int tegra_spdif_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + return 0; +} + +static int tegra_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct tegra_spdif_info* info = cpu_dai->private_data; + struct tegra_audio_platform_data *pdata = info->pdev->dev.platform_data; + + if (info && info->spdif_clk) { + clk_set_rate(info->spdif_clk, pdata->spdif_clk_rate); + } + else { + pr_err("%s: could not get spdif clock\n", __func__); + return -EIO; + } + + return 0; +} + +static int tegra_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + start_spdif_playback(dai); + else + start_spdif_capture(dai); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + stop_spdif_playback(dai); + else + stop_spdif_capture(dai); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +#ifdef CONFIG_PM +int tegra_spdif_suspend(struct snd_soc_dai *cpu_dai) +{ + struct tegra_spdif_info* info = cpu_dai->private_data; + + spdif_get_all_regs(info->spdif_base, &info->spdif_regs); + return 0; +} + +int tegra_spdif_resume(struct snd_soc_dai *cpu_dai) +{ + struct tegra_spdif_info* info = cpu_dai->private_data; + + spdif_set_all_regs(info->spdif_base, &info->spdif_regs); + return 0; +} + +#else +#define tegra_spdif_suspend NULL +#define tegra_spdif_resume NULL +#endif + +static int tegra_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + + if (!info->ref_count) + clk_enable(info->spdif_clk); + + info->ref_count++; + return 0; +} + +static void tegra_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra_spdif_info *info = dai->private_data; + + if (info->ref_count > 0) + info->ref_count--; + + if (!info->ref_count) + clk_disable(info->spdif_clk); + + return; +} + +static int tegra_spdif_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + return 0; +} + +static struct snd_soc_dai_ops tegra_spdif_dai_ops = { + .startup = tegra_spdif_startup, + .shutdown = tegra_spdif_shutdown, + .trigger = tegra_spdif_trigger, + .hw_params = tegra_spdif_hw_params, + .set_fmt = tegra_spdif_set_dai_fmt, + .set_sysclk = tegra_spdif_set_dai_sysclk, +}; + +struct snd_soc_dai tegra_spdif_dai = { + .name = "tegra-spdif", + .id = 0, + .probe = tegra_spdif_probe, + .suspend = tegra_spdif_suspend, + .resume = tegra_spdif_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = TEGRA_SAMPLE_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = TEGRA_SAMPLE_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra_spdif_dai_ops, +}; +EXPORT_SYMBOL_GPL(tegra_spdif_dai); + +static int tegra_spdif_driver_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res, *mem; + struct tegra_spdif_info *info; + + pr_info("%s\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + info->pdata = pdev->dev.platform_data; + info->pdata->driver_data = info; + BUG_ON(!info->pdata); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource!\n"); + err = -ENODEV; + goto fail; + } + + mem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "memory region already claimed!\n"); + err = -EBUSY; + goto fail; + } + + info->spdif_phys = res->start; + info->spdif_base = (unsigned long)ioremap(res->start, res->end - res->start + 1); + if (!info->spdif_base) { + dev_err(&pdev->dev, "cannot remap iomem!\n"); + err = -ENOMEM; + goto fail_release_mem; + } + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "no dma resource!\n"); + err = -ENODEV; + goto fail_unmap_mem; + } + info->dma_req_sel = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource!\n"); + err = -ENODEV; + goto fail_unmap_mem; + } + info->irq = res->start; + + info->spdif_clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(info->spdif_clk)) { + err = PTR_ERR(info->spdif_clk); + goto fail_unmap_mem; + } + clk_set_rate(info->spdif_clk, info->pdata->spdif_clk_rate); + + spdif_initialize(info->spdif_base, AUDIO_TX_MODE); + spdif_initialize(info->spdif_base, AUDIO_RX_MODE); + + tegra_spdif_dai.dev = &pdev->dev; + tegra_spdif_dai.private_data = info; + err = snd_soc_register_dai(&tegra_spdif_dai); + if (err) + goto fail_unmap_mem; + + return 0; + +fail_unmap_mem: + iounmap((void __iomem*)info->spdif_base); + +fail_release_mem: + release_mem_region(mem->start, resource_size(mem)); +fail: + kfree(info); + return err; +} + + +static int __devexit tegra_spdif_driver_remove(struct platform_device *pdev) +{ + struct tegra_spdif_info *info = tegra_spdif_dai.private_data; + + if (info->spdif_base) + iounmap((void __iomem*)info->spdif_base); + + if (info) + kfree(info); + + snd_soc_unregister_dai(&tegra_spdif_dai); + return 0; +} + +static struct platform_driver tegra_spdif_driver = { + .probe = tegra_spdif_driver_probe, + .remove = __devexit_p(tegra_spdif_driver_remove), + .driver = { + .name = "spdif_out", + .owner = THIS_MODULE, + }, +}; + +static int __init tegra_spdif_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&tegra_spdif_driver); + return ret; +} +module_init(tegra_spdif_init); + +static void __exit tegra_spdif_exit(void) +{ + platform_driver_unregister(&tegra_spdif_driver); +} +module_exit(tegra_spdif_exit); + +/* Module information */ +MODULE_DESCRIPTION("Tegra SPDIF SoC interface"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3