mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 22:51:35 +00:00
Support HDMI audio on NVIDIA Tegra20
Merge series from Dmitry Osipenko <digetx@gmail.com>: This series revives Tegra20 S/PDIF driver which was upstreamed long time ago, but never was used. It also turns Tegra DRM HDMI driver into HDMI audio CODEC provider. Finally, HDMI audio is enabled in device-trees. For now the audio is enable only for Acer A500 tablet and Toshiba AC100 netbook because they're already supported by upstream, later on ASUS TF101 tablet will join them. I based S/PDIF patches on Arnd's Bergmann patch from a separate series [1] that removes obsolete slave_id. This eases merging of the patches by removing the merge conflict. This is a note for Mark Brown. I also based this series on top of power management series [2]. I.e. [2] should be applied first, otherwise "Add S/PDIF node to Tegra20 device-tree" patch should have merge conflict. This is a note for Thierry. [1] https://patchwork.ozlabs.org/project/linux-tegra/list/?series=273312 [2] https://patchwork.ozlabs.org/project/linux-tegra/list/?series=274534 Changelog: v4: - Added patches that update multi_v7_defconfig with the enabled S/PDIF and APB DMA drivers. v3: - Renamed S/PDIF device-tree clocks as was suggested by Rob Herring. - Added r-bs and acks that were given by Rob Herring to v2. v2: - Corrected I2S yaml problem that was reported by the DT bot for v1 by removing the non-existent required clock-names property. - Removed assigned-clocks property from S/PDIF yaml since this property is now inherited from the clocks property. - Reordered the "tegra20: spdif: Set FIFO trigger level" patch, making it the first sound/soc patch in the series, like it was suggested by Mark Brown in the comment to v1. Also reworded commit message of this patch to *not* make it looks like it should be backported to stable kernels. Arnd Bergmann (1): ASoC: tegra20-spdif: stop setting slave_id Dmitry Osipenko (21): ASoC: dt-bindings: Add binding for Tegra20 S/PDIF ASoC: dt-bindings: tegra20-i2s: Convert to schema ASoC: dt-bindings: tegra20-i2s: Document new nvidia,fixed-parent-rate property dt-bindings: host1x: Document optional HDMI sound-dai-cells ASoC: tegra20: spdif: Set FIFO trigger level ASoC: tegra20: spdif: Support device-tree ASoC: tegra20: spdif: Improve driver's code ASoC: tegra20: spdif: Use more resource-managed helpers ASoC: tegra20: spdif: Reset hardware ASoC: tegra20: spdif: Support system suspend ASoC: tegra20: spdif: Filter out unsupported rates ASoC: tegra20: i2s: Filter out unsupported rates drm/tegra: hdmi: Unwind tegra_hdmi_init() errors drm/tegra: hdmi: Register audio CODEC on Tegra20 ARM: tegra_defconfig: Enable S/PDIF driver ARM: config: multi v7: Enable NVIDIA Tegra20 S/PDIF driver ARM: config: multi v7: Enable NVIDIA Tegra20 APB DMA driver ARM: tegra: Add S/PDIF node to Tegra20 device-tree ARM: tegra: Add HDMI audio graph to Tegra20 device-tree ARM: tegra: acer-a500: Enable S/PDIF and HDMI audio ARM: tegra: paz00: Enable S/PDIF and HDMI audio .../display/tegra/nvidia,tegra20-host1x.txt | 1 + .../bindings/sound/nvidia,tegra20-i2s.txt | 30 --- .../bindings/sound/nvidia,tegra20-i2s.yaml | 77 +++++++ .../bindings/sound/nvidia,tegra20-spdif.yaml | 85 ++++++++ .../boot/dts/tegra20-acer-a500-picasso.dts | 8 + arch/arm/boot/dts/tegra20-paz00.dts | 8 + arch/arm/boot/dts/tegra20.dtsi | 40 +++- arch/arm/configs/multi_v7_defconfig | 2 + arch/arm/configs/tegra_defconfig | 1 + drivers/gpu/drm/tegra/Kconfig | 3 + drivers/gpu/drm/tegra/hdmi.c | 168 +++++++++++++-- sound/soc/tegra/tegra20_i2s.c | 49 +++++ sound/soc/tegra/tegra20_spdif.c | 197 ++++++++++++------ sound/soc/tegra/tegra20_spdif.h | 1 + sound/soc/tegra/tegra_pcm.c | 6 + sound/soc/tegra/tegra_pcm.h | 1 + 16 files changed, 574 insertions(+), 103 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-i2s.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-spdif.yaml -- 2.33.1
This commit is contained in:
commit
be1d03eecc
@ -1,30 +0,0 @@
|
||||
NVIDIA Tegra 20 I2S controller
|
||||
|
||||
Required properties:
|
||||
- compatible : "nvidia,tegra20-i2s"
|
||||
- reg : Should contain I2S registers location and length
|
||||
- interrupts : Should contain I2S interrupt
|
||||
- resets : Must contain an entry for each entry in reset-names.
|
||||
See ../reset/reset.txt for details.
|
||||
- reset-names : Must include the following entries:
|
||||
- i2s
|
||||
- dmas : Must contain an entry for each entry in clock-names.
|
||||
See ../dma/dma.txt for details.
|
||||
- dma-names : Must include the following entries:
|
||||
- rx
|
||||
- tx
|
||||
- clocks : Must contain one entry, for the module clock.
|
||||
See ../clocks/clock-bindings.txt for details.
|
||||
|
||||
Example:
|
||||
|
||||
i2s@70002800 {
|
||||
compatible = "nvidia,tegra20-i2s";
|
||||
reg = <0x70002800 0x200>;
|
||||
interrupts = < 45 >;
|
||||
clocks = <&tegra_car 11>;
|
||||
resets = <&tegra_car 11>;
|
||||
reset-names = "i2s";
|
||||
dmas = <&apbdma 21>, <&apbdma 21>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
@ -0,0 +1,77 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/nvidia,tegra20-i2s.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NVIDIA Tegra20 I2S Controller
|
||||
|
||||
description: |
|
||||
The I2S Controller streams synchronous serial audio data between system
|
||||
memory and an external audio device. The controller supports the I2S Left
|
||||
Justified Mode, Right Justified Mode, and DSP mode formats.
|
||||
|
||||
maintainers:
|
||||
- Thierry Reding <treding@nvidia.com>
|
||||
- Jon Hunter <jonathanh@nvidia.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: nvidia,tegra20-i2s
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
reset-names:
|
||||
const: i2s
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
minItems: 1
|
||||
|
||||
dmas:
|
||||
minItems: 2
|
||||
|
||||
dma-names:
|
||||
items:
|
||||
- const: rx
|
||||
- const: tx
|
||||
|
||||
nvidia,fixed-parent-rate:
|
||||
description: |
|
||||
Specifies whether board prefers parent clock to stay at a fixed rate.
|
||||
This allows multiple Tegra20 audio components work simultaneously by
|
||||
limiting number of supportable audio rates.
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- resets
|
||||
- reset-names
|
||||
- interrupts
|
||||
- clocks
|
||||
- dmas
|
||||
- dma-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2s@70002800 {
|
||||
compatible = "nvidia,tegra20-i2s";
|
||||
reg = <0x70002800 0x200>;
|
||||
interrupts = <45>;
|
||||
clocks = <&tegra_car 11>;
|
||||
resets = <&tegra_car 11>;
|
||||
reset-names = "i2s";
|
||||
dmas = <&apbdma 21>, <&apbdma 21>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
|
||||
...
|
@ -0,0 +1,85 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/nvidia,tegra20-spdif.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NVIDIA Tegra20 S/PDIF Controller
|
||||
|
||||
description: |
|
||||
The S/PDIF controller supports both input and output in serial audio
|
||||
digital interface format. The input controller can digitally recover
|
||||
a clock from the received stream. The S/PDIF controller is also used
|
||||
to generate the embedded audio for HDMI output channel.
|
||||
|
||||
maintainers:
|
||||
- Thierry Reding <treding@nvidia.com>
|
||||
- Jon Hunter <jonathanh@nvidia.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: nvidia,tegra20-spdif
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
minItems: 2
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: out
|
||||
- const: in
|
||||
|
||||
dmas:
|
||||
minItems: 2
|
||||
|
||||
dma-names:
|
||||
items:
|
||||
- const: rx
|
||||
- const: tx
|
||||
|
||||
"#sound-dai-cells":
|
||||
const: 0
|
||||
|
||||
nvidia,fixed-parent-rate:
|
||||
description: |
|
||||
Specifies whether board prefers parent clock to stay at a fixed rate.
|
||||
This allows multiple Tegra20 audio components work simultaneously by
|
||||
limiting number of supportable audio rates.
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- resets
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- dmas
|
||||
- dma-names
|
||||
- "#sound-dai-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spdif@70002400 {
|
||||
compatible = "nvidia,tegra20-spdif";
|
||||
reg = <0x70002400 0x200>;
|
||||
interrupts = <77>;
|
||||
clocks = <&clk 99>, <&clk 98>;
|
||||
clock-names = "out", "in";
|
||||
resets = <&rst 10>;
|
||||
dmas = <&apbdma 3>, <&apbdma 3>;
|
||||
dma-names = "rx", "tx";
|
||||
#sound-dai-cells = <0>;
|
||||
};
|
||||
|
||||
...
|
@ -727,12 +727,6 @@ static int mmp_pdma_config_write(struct dma_chan *dchan,
|
||||
|
||||
chan->dir = direction;
|
||||
chan->dev_addr = addr;
|
||||
/* FIXME: drivers should be ported over to use the filter
|
||||
* function. Once that's done, the following two lines can
|
||||
* be removed.
|
||||
*/
|
||||
if (cfg->slave_id)
|
||||
chan->drcmr = cfg->slave_id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -909,13 +909,6 @@ static void pxad_get_config(struct pxad_chan *chan,
|
||||
*dcmd |= PXA_DCMD_BURST16;
|
||||
else if (maxburst == 32)
|
||||
*dcmd |= PXA_DCMD_BURST32;
|
||||
|
||||
/* FIXME: drivers should be ported over to use the filter
|
||||
* function. Once that's done, the following two lines can
|
||||
* be removed.
|
||||
*/
|
||||
if (chan->cfg.slave_id)
|
||||
chan->drcmr = chan->cfg.slave_id;
|
||||
}
|
||||
|
||||
static struct dma_async_tx_descriptor *
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dma/qcom_adm.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
@ -140,6 +141,8 @@ struct adm_chan {
|
||||
|
||||
struct adm_async_desc *curr_txd;
|
||||
struct dma_slave_config slave;
|
||||
u32 crci;
|
||||
u32 mux;
|
||||
struct list_head node;
|
||||
|
||||
int error;
|
||||
@ -379,8 +382,8 @@ static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan,
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
crci = achan->slave.slave_id & 0xf;
|
||||
if (!crci || achan->slave.slave_id > 0x1f) {
|
||||
crci = achan->crci & 0xf;
|
||||
if (!crci || achan->crci > 0x1f) {
|
||||
dev_err(adev->dev, "invalid crci value\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
@ -403,9 +406,7 @@ static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan,
|
||||
if (!async_desc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (crci)
|
||||
async_desc->mux = achan->slave.slave_id & ADM_CRCI_MUX_SEL ?
|
||||
ADM_CRCI_CTL_MUX_SEL : 0;
|
||||
async_desc->mux = achan->mux ? ADM_CRCI_CTL_MUX_SEL : 0;
|
||||
async_desc->crci = crci;
|
||||
async_desc->blk_size = blk_size;
|
||||
async_desc->dma_len = single_count * sizeof(struct adm_desc_hw_single) +
|
||||
@ -488,10 +489,13 @@ static int adm_terminate_all(struct dma_chan *chan)
|
||||
static int adm_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg)
|
||||
{
|
||||
struct adm_chan *achan = to_adm_chan(chan);
|
||||
struct qcom_adm_peripheral_config *config = cfg->peripheral_config;
|
||||
unsigned long flag;
|
||||
|
||||
spin_lock_irqsave(&achan->vc.lock, flag);
|
||||
memcpy(&achan->slave, cfg, sizeof(struct dma_slave_config));
|
||||
if (cfg->peripheral_size == sizeof(config))
|
||||
achan->crci = config->crci;
|
||||
spin_unlock_irqrestore(&achan->vc.lock, flag);
|
||||
|
||||
return 0;
|
||||
@ -694,6 +698,45 @@ static void adm_channel_init(struct adm_device *adev, struct adm_chan *achan,
|
||||
achan->vc.desc_free = adm_dma_free_desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* adm_dma_xlate
|
||||
* @dma_spec: pointer to DMA specifier as found in the device tree
|
||||
* @ofdma: pointer to DMA controller data
|
||||
*
|
||||
* This can use either 1-cell or 2-cell formats, the first cell
|
||||
* identifies the slave device, while the optional second cell
|
||||
* contains the crci value.
|
||||
*
|
||||
* Returns pointer to appropriate dma channel on success or NULL on error.
|
||||
*/
|
||||
static struct dma_chan *adm_dma_xlate(struct of_phandle_args *dma_spec,
|
||||
struct of_dma *ofdma)
|
||||
{
|
||||
struct dma_device *dev = ofdma->of_dma_data;
|
||||
struct dma_chan *chan, *candidate = NULL;
|
||||
struct adm_chan *achan;
|
||||
|
||||
if (!dev || dma_spec->args_count > 2)
|
||||
return NULL;
|
||||
|
||||
list_for_each_entry(chan, &dev->channels, device_node)
|
||||
if (chan->chan_id == dma_spec->args[0]) {
|
||||
candidate = chan;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!candidate)
|
||||
return NULL;
|
||||
|
||||
achan = to_adm_chan(candidate);
|
||||
if (dma_spec->args_count == 2)
|
||||
achan->crci = dma_spec->args[1];
|
||||
else
|
||||
achan->crci = 0;
|
||||
|
||||
return dma_get_slave_channel(candidate);
|
||||
}
|
||||
|
||||
static int adm_dma_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct adm_device *adev;
|
||||
@ -838,8 +881,7 @@ static int adm_dma_probe(struct platform_device *pdev)
|
||||
goto err_disable_clks;
|
||||
}
|
||||
|
||||
ret = of_dma_controller_register(pdev->dev.of_node,
|
||||
of_dma_xlate_by_chan_id,
|
||||
ret = of_dma_controller_register(pdev->dev.of_node, adm_dma_xlate,
|
||||
&adev->common);
|
||||
if (ret)
|
||||
goto err_unregister_dma;
|
||||
|
@ -786,14 +786,6 @@ static int shdma_config(struct dma_chan *chan,
|
||||
if (!config)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* overriding the slave_id through dma_slave_config is deprecated,
|
||||
* but possibly some out-of-tree drivers still do it.
|
||||
*/
|
||||
if (WARN_ON_ONCE(config->slave_id &&
|
||||
config->slave_id != schan->real_slave_id))
|
||||
schan->real_slave_id = config->slave_id;
|
||||
|
||||
/*
|
||||
* We could lock this, but you shouldn't be configuring the
|
||||
* channel, while using it...
|
||||
|
@ -795,9 +795,6 @@ static int sprd_dma_fill_desc(struct dma_chan *chan,
|
||||
return dst_datawidth;
|
||||
}
|
||||
|
||||
if (slave_cfg->slave_id)
|
||||
schan->dev_id = slave_cfg->slave_id;
|
||||
|
||||
hw->cfg = SPRD_DMA_DONOT_WAIT_BDONE << SPRD_DMA_WAIT_BDONE_OFFSET;
|
||||
|
||||
/*
|
||||
|
@ -343,12 +343,6 @@ static int tegra_dma_slave_config(struct dma_chan *dc,
|
||||
}
|
||||
|
||||
memcpy(&tdc->dma_sconfig, sconfig, sizeof(*sconfig));
|
||||
if (tdc->slave_id == TEGRA_APBDMA_SLAVE_ID_INVALID &&
|
||||
sconfig->device_fc) {
|
||||
if (sconfig->slave_id > TEGRA_APBDMA_CSR_REQ_SEL_MASK)
|
||||
return -EINVAL;
|
||||
tdc->slave_id = sconfig->slave_id;
|
||||
}
|
||||
tdc->config_init = true;
|
||||
|
||||
return 0;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma/xilinx_dpdma.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -1273,6 +1274,7 @@ static int xilinx_dpdma_config(struct dma_chan *dchan,
|
||||
struct dma_slave_config *config)
|
||||
{
|
||||
struct xilinx_dpdma_chan *chan = to_xilinx_chan(dchan);
|
||||
struct xilinx_dpdma_peripheral_config *pconfig;
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
@ -1282,15 +1284,18 @@ static int xilinx_dpdma_config(struct dma_chan *dchan,
|
||||
* fixed both on the DPDMA side and on the DP controller side.
|
||||
*/
|
||||
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
|
||||
/*
|
||||
* Abuse the slave_id to indicate that the channel is part of a video
|
||||
* group.
|
||||
* Use the peripheral_config to indicate that the channel is part
|
||||
* of a video group. This requires matching use of the custom
|
||||
* structure in each driver.
|
||||
*/
|
||||
if (chan->id <= ZYNQMP_DPDMA_VIDEO2)
|
||||
chan->video_group = config->slave_id != 0;
|
||||
pconfig = config->peripheral_config;
|
||||
if (WARN_ON(pconfig && config->peripheral_size != sizeof(*pconfig)))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
if (chan->id <= ZYNQMP_DPDMA_VIDEO2 && pconfig)
|
||||
chan->video_group = pconfig->video_group;
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
|
||||
return 0;
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma/xilinx_dpdma.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/module.h>
|
||||
@ -1058,14 +1059,18 @@ static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer,
|
||||
zynqmp_disp_avbuf_set_format(layer->disp, layer, layer->disp_fmt);
|
||||
|
||||
/*
|
||||
* Set slave_id for each DMA channel to indicate they're part of a
|
||||
* Set pconfig for each DMA channel to indicate they're part of a
|
||||
* video group.
|
||||
*/
|
||||
for (i = 0; i < info->num_planes; i++) {
|
||||
struct zynqmp_disp_layer_dma *dma = &layer->dmas[i];
|
||||
struct xilinx_dpdma_peripheral_config pconfig = {
|
||||
.video_group = true,
|
||||
};
|
||||
struct dma_slave_config config = {
|
||||
.direction = DMA_MEM_TO_DEV,
|
||||
.slave_id = 1,
|
||||
.peripheral_config = &pconfig,
|
||||
.peripheral_size = sizeof(pconfig),
|
||||
};
|
||||
|
||||
dmaengine_slave_config(dma->chan, &config);
|
||||
|
@ -1293,14 +1293,12 @@ static int bcm2835_add_host(struct bcm2835_host *host)
|
||||
|
||||
host->dma_cfg_tx.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_cfg_tx.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_cfg_tx.slave_id = 13; /* DREQ channel */
|
||||
host->dma_cfg_tx.direction = DMA_MEM_TO_DEV;
|
||||
host->dma_cfg_tx.src_addr = 0;
|
||||
host->dma_cfg_tx.dst_addr = host->phys_addr + SDDATA;
|
||||
|
||||
host->dma_cfg_rx.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_cfg_rx.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_cfg_rx.slave_id = 13; /* DREQ channel */
|
||||
host->dma_cfg_rx.direction = DMA_DEV_TO_MEM;
|
||||
host->dma_cfg_rx.src_addr = host->phys_addr + SDDATA;
|
||||
host->dma_cfg_rx.dst_addr = 0;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/dma/qcom_adm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/module.h>
|
||||
@ -952,6 +953,7 @@ static int prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
|
||||
struct dma_async_tx_descriptor *dma_desc;
|
||||
struct scatterlist *sgl;
|
||||
struct dma_slave_config slave_conf;
|
||||
struct qcom_adm_peripheral_config periph_conf = {};
|
||||
enum dma_transfer_direction dir_eng;
|
||||
int ret;
|
||||
|
||||
@ -983,11 +985,19 @@ static int prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
|
||||
if (read) {
|
||||
slave_conf.src_maxburst = 16;
|
||||
slave_conf.src_addr = nandc->base_dma + reg_off;
|
||||
slave_conf.slave_id = nandc->data_crci;
|
||||
if (nandc->data_crci) {
|
||||
periph_conf.crci = nandc->data_crci;
|
||||
slave_conf.peripheral_config = &periph_conf;
|
||||
slave_conf.peripheral_size = sizeof(periph_conf);
|
||||
}
|
||||
} else {
|
||||
slave_conf.dst_maxburst = 16;
|
||||
slave_conf.dst_addr = nandc->base_dma + reg_off;
|
||||
slave_conf.slave_id = nandc->cmd_crci;
|
||||
if (nandc->cmd_crci) {
|
||||
periph_conf.crci = nandc->cmd_crci;
|
||||
slave_conf.peripheral_config = &periph_conf;
|
||||
slave_conf.peripheral_size = sizeof(periph_conf);
|
||||
}
|
||||
}
|
||||
|
||||
ret = dmaengine_slave_config(nandc->chan, &slave_conf);
|
||||
|
@ -370,7 +370,6 @@ static int pic32_spi_dma_config(struct pic32_spi *pic32s, u32 dma_width)
|
||||
cfg.src_addr_width = dma_width;
|
||||
cfg.dst_addr_width = dma_width;
|
||||
/* tx channel */
|
||||
cfg.slave_id = pic32s->tx_irq;
|
||||
cfg.direction = DMA_MEM_TO_DEV;
|
||||
ret = dmaengine_slave_config(master->dma_tx, &cfg);
|
||||
if (ret) {
|
||||
@ -378,7 +377,6 @@ static int pic32_spi_dma_config(struct pic32_spi *pic32s, u32 dma_width)
|
||||
return ret;
|
||||
}
|
||||
/* rx channel */
|
||||
cfg.slave_id = pic32s->rx_irq;
|
||||
cfg.direction = DMA_DEV_TO_MEM;
|
||||
ret = dmaengine_slave_config(master->dma_rx, &cfg);
|
||||
if (ret)
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/dma/qcom_adm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/module.h>
|
||||
@ -290,6 +291,7 @@ static void msm_request_tx_dma(struct msm_port *msm_port, resource_size_t base)
|
||||
{
|
||||
struct device *dev = msm_port->uart.dev;
|
||||
struct dma_slave_config conf;
|
||||
struct qcom_adm_peripheral_config periph_conf = {};
|
||||
struct msm_dma *dma;
|
||||
u32 crci = 0;
|
||||
int ret;
|
||||
@ -308,7 +310,11 @@ static void msm_request_tx_dma(struct msm_port *msm_port, resource_size_t base)
|
||||
conf.device_fc = true;
|
||||
conf.dst_addr = base + UARTDM_TF;
|
||||
conf.dst_maxburst = UARTDM_BURST_SIZE;
|
||||
conf.slave_id = crci;
|
||||
if (crci) {
|
||||
conf.peripheral_config = &periph_conf;
|
||||
conf.peripheral_size = sizeof(periph_conf);
|
||||
periph_conf.crci = crci;
|
||||
}
|
||||
|
||||
ret = dmaengine_slave_config(dma->chan, &conf);
|
||||
if (ret)
|
||||
@ -333,6 +339,7 @@ static void msm_request_rx_dma(struct msm_port *msm_port, resource_size_t base)
|
||||
{
|
||||
struct device *dev = msm_port->uart.dev;
|
||||
struct dma_slave_config conf;
|
||||
struct qcom_adm_peripheral_config periph_conf = {};
|
||||
struct msm_dma *dma;
|
||||
u32 crci = 0;
|
||||
int ret;
|
||||
@ -355,7 +362,11 @@ static void msm_request_rx_dma(struct msm_port *msm_port, resource_size_t base)
|
||||
conf.device_fc = true;
|
||||
conf.src_addr = base + UARTDM_RF;
|
||||
conf.src_maxburst = UARTDM_BURST_SIZE;
|
||||
conf.slave_id = crci;
|
||||
if (crci) {
|
||||
conf.peripheral_config = &periph_conf;
|
||||
conf.peripheral_size = sizeof(periph_conf);
|
||||
periph_conf.crci = crci;
|
||||
}
|
||||
|
||||
ret = dmaengine_slave_config(dma->chan, &conf);
|
||||
if (ret)
|
||||
|
12
include/linux/dma/qcom_adm.h
Normal file
12
include/linux/dma/qcom_adm.h
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#ifndef __LINUX_DMA_QCOM_ADM_H
|
||||
#define __LINUX_DMA_QCOM_ADM_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct qcom_adm_peripheral_config {
|
||||
u32 crci;
|
||||
u32 mux;
|
||||
};
|
||||
|
||||
#endif /* __LINUX_DMA_QCOM_ADM_H */
|
11
include/linux/dma/xilinx_dpdma.h
Normal file
11
include/linux/dma/xilinx_dpdma.h
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef __LINUX_DMA_XILINX_DPDMA_H
|
||||
#define __LINUX_DMA_XILINX_DPDMA_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct xilinx_dpdma_peripheral_config {
|
||||
bool video_group;
|
||||
};
|
||||
|
||||
#endif /* __LINUX_DMA_XILINX_DPDMA_H */
|
@ -418,9 +418,6 @@ enum dma_slave_buswidth {
|
||||
* @device_fc: Flow Controller Settings. Only valid for slave channels. Fill
|
||||
* with 'true' if peripheral should be flow controller. Direction will be
|
||||
* selected at Runtime.
|
||||
* @slave_id: Slave requester id. Only valid for slave channels. The dma
|
||||
* slave peripheral will have unique id as dma requester which need to be
|
||||
* pass as slave config.
|
||||
* @peripheral_config: peripheral configuration for programming peripheral
|
||||
* for dmaengine transfer
|
||||
* @peripheral_size: peripheral configuration buffer size
|
||||
@ -448,7 +445,6 @@ struct dma_slave_config {
|
||||
u32 src_port_window_size;
|
||||
u32 dst_port_window_size;
|
||||
bool device_fc;
|
||||
unsigned int slave_id;
|
||||
void *peripheral_config;
|
||||
size_t peripheral_size;
|
||||
};
|
||||
|
@ -60,7 +60,6 @@ struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream)
|
||||
* @maxburst: Maximum number of words(note: words, as in units of the
|
||||
* src_addr_width member, not bytes) that can be send to or received from the
|
||||
* DAI in one burst.
|
||||
* @slave_id: Slave requester id for the DMA channel.
|
||||
* @filter_data: Custom DMA channel filter data, this will usually be used when
|
||||
* requesting the DMA channel.
|
||||
* @chan_name: Custom channel name to use when requesting DMA channel.
|
||||
@ -74,7 +73,6 @@ struct snd_dmaengine_dai_dma_data {
|
||||
dma_addr_t addr;
|
||||
enum dma_slave_buswidth addr_width;
|
||||
u32 maxburst;
|
||||
unsigned int slave_id;
|
||||
void *filter_data;
|
||||
const char *chan_name;
|
||||
unsigned int fifo_size;
|
||||
|
@ -91,8 +91,8 @@ EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config);
|
||||
* @dma_data: DAI DMA data
|
||||
* @slave_config: DMA slave configuration
|
||||
*
|
||||
* Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width and
|
||||
* slave_id fields of the DMA slave config from the same fields of the DAI DMA
|
||||
* Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width
|
||||
* fields of the DMA slave config from the same fields of the DAI DMA
|
||||
* data struct. The src and dst fields will be initialized depending on the
|
||||
* direction of the substream. If the substream is a playback stream the dst
|
||||
* fields will be initialized, if it is a capture stream the src fields will be
|
||||
@ -124,7 +124,6 @@ void snd_dmaengine_pcm_set_config_from_dai_data(
|
||||
slave_config->src_addr_width = dma_data->addr_width;
|
||||
}
|
||||
|
||||
slave_config->slave_id = dma_data->slave_id;
|
||||
slave_config->peripheral_config = dma_data->peripheral_config;
|
||||
slave_config->peripheral_size = dma_data->peripheral_size;
|
||||
}
|
||||
|
@ -262,10 +262,59 @@ static int tegra20_i2s_probe(struct snd_soc_dai *dai)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const unsigned int tegra20_i2s_rates[] = {
|
||||
8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000
|
||||
};
|
||||
|
||||
static int tegra20_i2s_filter_rates(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_interval *r = hw_param_interval(params, rule->var);
|
||||
struct snd_soc_dai *dai = rule->private;
|
||||
struct tegra20_i2s *i2s = dev_get_drvdata(dai->dev);
|
||||
struct clk *parent = clk_get_parent(i2s->clk_i2s);
|
||||
long i, parent_rate, valid_rates = 0;
|
||||
|
||||
parent_rate = clk_get_rate(parent);
|
||||
if (parent_rate <= 0) {
|
||||
dev_err(dai->dev, "Can't get parent clock rate: %ld\n",
|
||||
parent_rate);
|
||||
return parent_rate ?: -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tegra20_i2s_rates); i++) {
|
||||
if (parent_rate % (tegra20_i2s_rates[i] * 128) == 0)
|
||||
valid_rates |= BIT(i);
|
||||
}
|
||||
|
||||
/*
|
||||
* At least one rate must be valid, otherwise the parent clock isn't
|
||||
* audio PLL. Nothing should be filtered in this case.
|
||||
*/
|
||||
if (!valid_rates)
|
||||
valid_rates = BIT(ARRAY_SIZE(tegra20_i2s_rates)) - 1;
|
||||
|
||||
return snd_interval_list(r, ARRAY_SIZE(tegra20_i2s_rates),
|
||||
tegra20_i2s_rates, valid_rates);
|
||||
}
|
||||
|
||||
static int tegra20_i2s_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate"))
|
||||
return 0;
|
||||
|
||||
return snd_pcm_hw_rule_add(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
tegra20_i2s_filter_rates, dai,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = {
|
||||
.set_fmt = tegra20_i2s_set_fmt,
|
||||
.hw_params = tegra20_i2s_hw_params,
|
||||
.trigger = tegra20_i2s_trigger,
|
||||
.startup = tegra20_i2s_startup,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dai_driver tegra20_i2s_dai_template = {
|
||||
|
@ -7,12 +7,15 @@
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
@ -22,12 +25,12 @@
|
||||
|
||||
#include "tegra20_spdif.h"
|
||||
|
||||
#define DRV_NAME "tegra20-spdif"
|
||||
|
||||
static __maybe_unused int tegra20_spdif_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra20_spdif *spdif = dev_get_drvdata(dev);
|
||||
|
||||
regcache_cache_only(spdif->regmap, true);
|
||||
|
||||
clk_disable_unprepare(spdif->clk_spdif_out);
|
||||
|
||||
return 0;
|
||||
@ -38,23 +41,45 @@ static __maybe_unused int tegra20_spdif_runtime_resume(struct device *dev)
|
||||
struct tegra20_spdif *spdif = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = reset_control_assert(spdif->reset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(spdif->clk_spdif_out);
|
||||
if (ret) {
|
||||
dev_err(dev, "clk_enable failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
usleep_range(10, 100);
|
||||
|
||||
ret = reset_control_deassert(spdif->reset);
|
||||
if (ret)
|
||||
goto disable_clocks;
|
||||
|
||||
regcache_cache_only(spdif->regmap, false);
|
||||
regcache_mark_dirty(spdif->regmap);
|
||||
|
||||
ret = regcache_sync(spdif->regmap);
|
||||
if (ret)
|
||||
goto disable_clocks;
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clocks:
|
||||
clk_disable_unprepare(spdif->clk_spdif_out);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct device *dev = dai->dev;
|
||||
struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
|
||||
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
|
||||
unsigned int mask = 0, val = 0;
|
||||
int ret, spdifclock;
|
||||
long rate;
|
||||
|
||||
mask |= TEGRA20_SPDIF_CTRL_PACK |
|
||||
TEGRA20_SPDIF_CTRL_BIT_MODE_MASK;
|
||||
@ -69,6 +94,14 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
|
||||
|
||||
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val);
|
||||
|
||||
/*
|
||||
* FIFO trigger level must be bigger than DMA burst or equal to it,
|
||||
* otherwise data is discarded on overflow.
|
||||
*/
|
||||
regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_DATA_FIFO_CSR,
|
||||
TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK,
|
||||
TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL);
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 32000:
|
||||
spdifclock = 4096000;
|
||||
@ -97,10 +130,16 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
|
||||
|
||||
ret = clk_set_rate(spdif->clk_spdif_out, spdifclock);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret);
|
||||
dev_err(dai->dev, "Can't set SPDIF clock rate: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rate = clk_get_rate(spdif->clk_spdif_out);
|
||||
if (rate != spdifclock)
|
||||
dev_warn_once(dai->dev,
|
||||
"SPDIF clock rate %d doesn't match requested rate %lu\n",
|
||||
spdifclock, rate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -118,9 +157,9 @@ static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif)
|
||||
}
|
||||
|
||||
static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
|
||||
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
@ -140,9 +179,62 @@ static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra20_spdif_filter_rates(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_interval *r = hw_param_interval(params, rule->var);
|
||||
struct snd_soc_dai *dai = rule->private;
|
||||
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
|
||||
struct clk *parent = clk_get_parent(spdif->clk_spdif_out);
|
||||
const unsigned int rates[] = { 32000, 44100, 48000 };
|
||||
long i, parent_rate, valid_rates = 0;
|
||||
|
||||
parent_rate = clk_get_rate(parent);
|
||||
if (parent_rate <= 0) {
|
||||
dev_err(dai->dev, "Can't get parent clock rate: %ld\n",
|
||||
parent_rate);
|
||||
return parent_rate ?: -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rates); i++) {
|
||||
if (parent_rate % (rates[i] * 128) == 0)
|
||||
valid_rates |= BIT(i);
|
||||
}
|
||||
|
||||
/*
|
||||
* At least one rate must be valid, otherwise the parent clock isn't
|
||||
* audio PLL. Nothing should be filtered in this case.
|
||||
*/
|
||||
if (!valid_rates)
|
||||
valid_rates = BIT(ARRAY_SIZE(rates)) - 1;
|
||||
|
||||
return snd_interval_list(r, ARRAY_SIZE(rates), rates, valid_rates);
|
||||
}
|
||||
|
||||
static int tegra20_spdif_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate"))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* SPDIF and I2S share audio PLL. HDMI takes audio packets from SPDIF
|
||||
* and audio may not work on some TVs if clock rate isn't precise.
|
||||
*
|
||||
* PLL rate is controlled by I2S side. Filter out audio rates that
|
||||
* don't match PLL rate at the start of stream to allow both SPDIF
|
||||
* and I2S work simultaneously, assuming that PLL rate won't be
|
||||
* changed later on.
|
||||
*/
|
||||
return snd_pcm_hw_rule_add(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
tegra20_spdif_filter_rates, dai,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
}
|
||||
|
||||
static int tegra20_spdif_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
|
||||
struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
|
||||
|
||||
dai->capture_dma_data = NULL;
|
||||
dai->playback_dma_data = &spdif->playback_dma_data;
|
||||
@ -151,26 +243,27 @@ static int tegra20_spdif_probe(struct snd_soc_dai *dai)
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = {
|
||||
.hw_params = tegra20_spdif_hw_params,
|
||||
.trigger = tegra20_spdif_trigger,
|
||||
.hw_params = tegra20_spdif_hw_params,
|
||||
.trigger = tegra20_spdif_trigger,
|
||||
.startup = tegra20_spdif_startup,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver tegra20_spdif_dai = {
|
||||
.name = DRV_NAME,
|
||||
.name = "tegra20-spdif",
|
||||
.probe = tegra20_spdif_probe,
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
|
||||
SNDRV_PCM_RATE_48000,
|
||||
SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &tegra20_spdif_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver tegra20_spdif_component = {
|
||||
.name = DRV_NAME,
|
||||
.name = "tegra20-spdif",
|
||||
};
|
||||
|
||||
static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg)
|
||||
@ -251,7 +344,7 @@ static const struct regmap_config tegra20_spdif_regmap_config = {
|
||||
static int tegra20_spdif_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra20_spdif *spdif;
|
||||
struct resource *mem, *dmareq;
|
||||
struct resource *mem;
|
||||
void __iomem *regs;
|
||||
int ret;
|
||||
|
||||
@ -262,89 +355,77 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev)
|
||||
|
||||
dev_set_drvdata(&pdev->dev, spdif);
|
||||
|
||||
spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out");
|
||||
spdif->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
||||
if (IS_ERR(spdif->reset)) {
|
||||
dev_err(&pdev->dev, "Can't retrieve spdif reset\n");
|
||||
return PTR_ERR(spdif->reset);
|
||||
}
|
||||
|
||||
spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "out");
|
||||
if (IS_ERR(spdif->clk_spdif_out)) {
|
||||
pr_err("Can't retrieve spdif clock\n");
|
||||
ret = PTR_ERR(spdif->clk_spdif_out);
|
||||
return ret;
|
||||
dev_err(&pdev->dev, "Could not retrieve spdif clock\n");
|
||||
return PTR_ERR(spdif->clk_spdif_out);
|
||||
}
|
||||
|
||||
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
|
||||
dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!dmareq) {
|
||||
dev_err(&pdev->dev, "No DMA resource\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
|
||||
&tegra20_spdif_regmap_config);
|
||||
&tegra20_spdif_regmap_config);
|
||||
if (IS_ERR(spdif->regmap)) {
|
||||
dev_err(&pdev->dev, "regmap init failed\n");
|
||||
ret = PTR_ERR(spdif->regmap);
|
||||
return ret;
|
||||
return PTR_ERR(spdif->regmap);
|
||||
}
|
||||
|
||||
spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT;
|
||||
spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
spdif->playback_dma_data.maxburst = 4;
|
||||
spdif->playback_dma_data.slave_id = dmareq->start;
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
ret = devm_pm_runtime_enable(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component,
|
||||
&tegra20_spdif_dai, 1);
|
||||
ret = devm_snd_soc_register_component(&pdev->dev,
|
||||
&tegra20_spdif_component,
|
||||
&tegra20_spdif_dai, 1);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
|
||||
ret = -ENOMEM;
|
||||
goto err_pm_disable;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = tegra_pcm_platform_register(&pdev->dev);
|
||||
ret = devm_tegra_pcm_platform_register(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
|
||||
goto err_unregister_component;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_component:
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
err_pm_disable:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tegra20_spdif_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
tegra_pcm_platform_unregister(&pdev->dev);
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops tegra20_spdif_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend,
|
||||
tegra20_spdif_runtime_resume, NULL)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra20_spdif_of_match[] = {
|
||||
{ .compatible = "nvidia,tegra20-spdif", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tegra20_spdif_of_match);
|
||||
|
||||
static struct platform_driver tegra20_spdif_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.name = "tegra20-spdif",
|
||||
.pm = &tegra20_spdif_pm_ops,
|
||||
.of_match_table = tegra20_spdif_of_match,
|
||||
},
|
||||
.probe = tegra20_spdif_platform_probe,
|
||||
.remove = tegra20_spdif_platform_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(tegra20_spdif_driver);
|
||||
|
||||
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
|
||||
MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
||||
|
@ -451,6 +451,7 @@ struct tegra20_spdif {
|
||||
struct snd_dmaengine_dai_dma_data capture_dma_data;
|
||||
struct snd_dmaengine_dai_dma_data playback_dma_data;
|
||||
struct regmap *regmap;
|
||||
struct reset_control *reset;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -48,6 +48,12 @@ int tegra_pcm_platform_register(struct device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_pcm_platform_register);
|
||||
|
||||
int devm_tegra_pcm_platform_register(struct device *dev)
|
||||
{
|
||||
return devm_snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_tegra_pcm_platform_register);
|
||||
|
||||
int tegra_pcm_platform_register_with_chan_names(struct device *dev,
|
||||
struct snd_dmaengine_pcm_config *config,
|
||||
char *txdmachan, char *rxdmachan)
|
||||
|
@ -32,6 +32,7 @@ int tegra_pcm_hw_params(struct snd_soc_component *component,
|
||||
snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
int tegra_pcm_platform_register(struct device *dev);
|
||||
int devm_tegra_pcm_platform_register(struct device *dev);
|
||||
int tegra_pcm_platform_register_with_chan_names(struct device *dev,
|
||||
struct snd_dmaengine_pcm_config *config,
|
||||
char *txdmachan, char *rxdmachan);
|
||||
|
Loading…
Reference in New Issue
Block a user