ASoC: ti: davinci-i2s: Add features to McBSP

Merge series from Bastien Curutchet <bastien.curutchet@bootlin.com>:

This series aims to add some features to McBSP driver.

Convert bindings from .txt to .yaml.
Add possibility to use an external clock as sample rate generator's
input.
Add handling of new formats (TDM, S24_LE, BP_FC).
Enable the detection of unexpected frame pulses.
Set the clock free-running mode according to SND_SOC_DAIFMT_[GATED/CONT]
configuration in DAI format.
Add ti,T1-framing[tx/rx] properties in DT. They allow to set the data
delay to two bit-clock periods.

This has been tested on a platform designed off of the DAVINCI/OMAP-L138
connected to 3 daisy-chained AD7767. An external clock drives the
sample rate generator through the CLKS pin.
The hardware I have only allowed me to test acquisition side of McBSP.
It is connected to a 6 channels TDM and acts as Bit clock provider and
Frame clock consumer.
This commit is contained in:
Mark Brown 2024-04-09 23:27:01 +01:00
commit 6451246884
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
4 changed files with 333 additions and 123 deletions

View File

@ -1,50 +0,0 @@
Texas Instruments DaVinci McBSP module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This binding describes the "Multi-channel Buffered Serial Port" (McBSP)
audio interface found in some TI DaVinci processors like the OMAP-L138 or AM180x.
Required properties:
~~~~~~~~~~~~~~~~~~~~
- compatible :
"ti,da850-mcbsp" : for DA850, AM180x and OPAM-L138 platforms
- reg : physical base address and length of the controller memory mapped
region(s).
- reg-names : Should contain:
* "mpu" for the main registers (required).
* "dat" for the data FIFO (optional).
- dmas: three element list of DMA controller phandles, DMA request line and
TC channel ordered triplets.
- dma-names: identifier string for each DMA request line in the dmas property.
These strings correspond 1:1 with the ordered pairs in dmas. The dma
identifiers must be "rx" and "tx".
Optional properties:
~~~~~~~~~~~~~~~~~~~~
- interrupts : Interrupt numbers for McBSP
- interrupt-names : Known interrupt names are "rx" and "tx"
- pinctrl-0: Should specify pin control group used for this controller.
- pinctrl-names: Should contain only one value - "default", for more details
please refer to pinctrl-bindings.txt
Example (AM1808):
~~~~~~~~~~~~~~~~~
mcbsp0: mcbsp@1d10000 {
compatible = "ti,da850-mcbsp";
pinctrl-names = "default";
pinctrl-0 = <&mcbsp0_pins>;
reg = <0x00110000 0x1000>,
<0x00310000 0x1000>;
reg-names = "mpu", "dat";
interrupts = <97 98>;
interrupt-names = "rx", "tx";
dmas = <&edma0 3 1
&edma0 2 1>;
dma-names = "tx", "rx";
};

View File

@ -0,0 +1,113 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/sound/davinci-mcbsp.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: McBSP Controller for TI SoCs
maintainers:
- Bastien Curutchet <bastien.curutchet@bootlin.com>
allOf:
- $ref: dai-common.yaml#
properties:
compatible:
enum:
- ti,da850-mcbsp
reg:
minItems: 1
items:
- description: CFG registers
- description: data registers
reg-names:
minItems: 1
items:
- const: mpu
- const: dat
dmas:
items:
- description: transmission DMA channel
- description: reception DMA channel
dma-names:
items:
- const: tx
- const: rx
interrupts:
items:
- description: RX interrupt
- description: TX interrupt
interrupt-names:
items:
- const: rx
- const: tx
clocks:
minItems: 1
items:
- description: functional clock
- description: external input clock for sample rate generator.
clock-names:
minItems: 1
items:
- const: fck
- const: clks
power-domains:
maxItems: 1
"#sound-dai-cells":
const: 0
ti,T1-framing-tx:
$ref: /schemas/types.yaml#/definitions/flag
description:
If the property is present, tx data delay is set to 2 bit clock periods.
McBSP will insert a blank period (high-impedance period) before the first
data bit. This can be used to interface to T1-framing devices.
ti,T1-framing-rx:
$ref: /schemas/types.yaml#/definitions/flag
description:
If the property is present, rx data delay is set to 2 bit clock periods.
McBSP will discard the bit preceding the data stream (called framing bit).
This can be used to interface to T1-framing devices.
required:
- "#sound-dai-cells"
- compatible
- reg
- reg-names
- dmas
- dma-names
- clocks
unevaluatedProperties: false
examples:
- |
mcbsp0@1d10000 {
#sound-dai-cells = <0>;
compatible = "ti,da850-mcbsp";
pinctrl-names = "default";
pinctrl-0 = <&mcbsp0_pins>;
reg = <0x111000 0x1000>,
<0x311000 0x1000>;
reg-names = "mpu", "dat";
interrupts = <97>, <98>;
interrupt-names = "rx", "tx";
dmas = <&edma0 3 1>,
<&edma0 2 1>;
dma-names = "tx", "rx";
clocks = <&psc1 14>;
};

View File

@ -25,16 +25,6 @@ struct davinci_mcasp_pdata {
unsigned sram_size_capture;
struct gen_pool *sram_pool;
/*
* If McBSP peripheral gets the clock from an external pin,
* there are three chooses, that are MCBSP_CLKX, MCBSP_CLKR
* and MCBSP_CLKS.
* Depending on different hardware connections it is possible
* to use this setting to change the behaviour of McBSP
* driver.
*/
int clk_input_pin;
/*
* This flag works when both clock and FS are outputs for the cpu
* and makes clock more accurate (FS is not symmetrical and the
@ -91,11 +81,6 @@ enum {
MCASP_VERSION_OMAP, /* OMAP4/5 */
};
enum mcbsp_clk_input_pin {
MCBSP_CLKR = 0, /* as in DM365 */
MCBSP_CLKS,
};
#define INACTIVE_MODE 0
#define TX_MODE 1
#define RX_MODE 2

View File

@ -19,7 +19,6 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/platform_data/davinci_asp.h>
#include <sound/core.h>
#include <sound/pcm.h>
@ -62,6 +61,9 @@
#define DAVINCI_MCBSP_SPCR_RRST (1 << 0)
#define DAVINCI_MCBSP_SPCR_RINTM(v) ((v) << 4)
#define DAVINCI_MCBSP_SPCR_RJUST(v) ((v) << 13)
#define DAVINCI_MCBSP_SPCR_RJUST_Z_LE DAVINCI_MCBSP_SPCR_RJUST(0)
#define DAVINCI_MCBSP_SPCR_RJUST_S_LE DAVINCI_MCBSP_SPCR_RJUST(1)
#define DAVINCI_MCBSP_SPCR_XRST (1 << 16)
#define DAVINCI_MCBSP_SPCR_XINTM(v) ((v) << 20)
#define DAVINCI_MCBSP_SPCR_GRST (1 << 22)
@ -108,15 +110,10 @@ enum {
DAVINCI_MCBSP_WORD_32,
};
static const unsigned char data_type[SNDRV_PCM_FORMAT_S32_LE + 1] = {
[SNDRV_PCM_FORMAT_S8] = 1,
[SNDRV_PCM_FORMAT_S16_LE] = 2,
[SNDRV_PCM_FORMAT_S32_LE] = 4,
};
static const unsigned char asp_word_length[SNDRV_PCM_FORMAT_S32_LE + 1] = {
[SNDRV_PCM_FORMAT_S8] = DAVINCI_MCBSP_WORD_8,
[SNDRV_PCM_FORMAT_S16_LE] = DAVINCI_MCBSP_WORD_16,
[SNDRV_PCM_FORMAT_S24_LE] = DAVINCI_MCBSP_WORD_24,
[SNDRV_PCM_FORMAT_S32_LE] = DAVINCI_MCBSP_WORD_32,
};
@ -135,6 +132,7 @@ struct davinci_mcbsp_dev {
int mode;
u32 pcr;
struct clk *clk;
struct clk *ext_clk;
/*
* Combining both channels into 1 element will at least double the
* amount of time between servicing the dma channel, increase
@ -159,8 +157,13 @@ struct davinci_mcbsp_dev {
unsigned int fmt;
int clk_div;
int clk_input_pin;
bool i2s_accurate_sck;
int tdm_slots;
int slot_width;
bool tx_framing_bit;
bool rx_framing_bit;
};
static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev,
@ -214,6 +217,63 @@ static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback)
toggle_clock(dev, playback);
}
static int davinci_i2s_tdm_word_length(int tdm_slot_width)
{
switch (tdm_slot_width) {
case 8:
return DAVINCI_MCBSP_WORD_8;
case 12:
return DAVINCI_MCBSP_WORD_12;
case 16:
return DAVINCI_MCBSP_WORD_16;
case 20:
return DAVINCI_MCBSP_WORD_20;
case 24:
return DAVINCI_MCBSP_WORD_24;
case 32:
return DAVINCI_MCBSP_WORD_32;
default:
return -EINVAL;
}
}
static int davinci_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai,
unsigned int tx_mask,
unsigned int rx_mask,
int slots, int slot_width)
{
struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
dev_dbg(dev->dev, "slots %d, slot_width %d\n", slots, slot_width);
if (slots > 128 || !slots) {
dev_err(dev->dev, "Invalid number of slots\n");
return -EINVAL;
}
if (rx_mask != (1 << slots) - 1) {
dev_err(dev->dev, "Invalid RX mask (0x%08x) : all slots must be used by McBSP\n",
rx_mask);
return -EINVAL;
}
if (tx_mask != (1 << slots) - 1) {
dev_err(dev->dev, "Invalid TX mask (0x%08x) : all slots must be used by McBSP\n",
tx_mask);
return -EINVAL;
}
if (davinci_i2s_tdm_word_length(slot_width) < 0) {
dev_err(dev->dev, "%s: Unsupported slot_width %d\n", __func__, slot_width);
return -EINVAL;
}
dev->tdm_slots = slots;
dev->slot_width = slot_width;
return 0;
}
#define DEFAULT_BITPERSAMPLE 16
static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
@ -221,6 +281,7 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
{
struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
unsigned int pcr;
unsigned int spcr;
unsigned int srgr;
bool inv_fs = false;
/* Attention srgr is updated by hw_params! */
@ -229,6 +290,23 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1);
dev->fmt = fmt;
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
case SND_SOC_DAIFMT_CONT:
spcr |= DAVINCI_MCBSP_SPCR_FREE;
dev_dbg(dev->dev, "Free-running mode ON\n");
break;
case SND_SOC_DAIFMT_GATED:
spcr &= ~DAVINCI_MCBSP_SPCR_FREE;
dev_dbg(dev->dev, "Free-running mode OFF\n");
break;
default:
dev_err(dev->dev, "Invalid clock gating\n");
return -EINVAL;
}
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
/* set master/slave audio interface */
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
@ -239,28 +317,30 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
DAVINCI_MCBSP_PCR_CLKRM;
break;
case SND_SOC_DAIFMT_BC_FP:
pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM;
/*
* Selection of the clock input pin that is the
* input for the Sample Rate Generator.
* McBSP FSR and FSX are driven by the Sample Rate
* Generator.
*/
switch (dev->clk_input_pin) {
case MCBSP_CLKS:
pcr |= DAVINCI_MCBSP_PCR_CLKXM |
DAVINCI_MCBSP_PCR_CLKRM;
break;
case MCBSP_CLKR:
pcr |= DAVINCI_MCBSP_PCR_SCLKME;
break;
default:
dev_err(dev->dev, "bad clk_input_pin\n");
if (dev->tdm_slots || dev->slot_width) {
dev_err(dev->dev, "TDM is not supported for BC_FP format\n");
return -EINVAL;
}
/*
* McBSP CLKR pin is the input for the Sample Rate Generator.
* McBSP FSR and FSX are driven by the Sample Rate Generator.
*/
pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM;
pcr |= DAVINCI_MCBSP_PCR_SCLKME;
break;
case SND_SOC_DAIFMT_BP_FC:
/* cpu is bitclock provider */
pcr = DAVINCI_MCBSP_PCR_CLKXM |
DAVINCI_MCBSP_PCR_CLKRM;
break;
case SND_SOC_DAIFMT_BC_FC:
if (dev->tdm_slots || dev->slot_width) {
dev_err(dev->dev, "TDM is not supported for BC_FC format\n");
return -EINVAL;
}
/* codec is master */
pcr = 0;
break;
@ -380,30 +460,58 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
struct snd_interval *i = NULL;
int mcbsp_word_length, master;
unsigned int rcr, xcr, srgr, clk_div, freq, framesize;
unsigned int clk_div, freq, framesize;
unsigned int srgr = 0;
unsigned int rcr = 0;
unsigned int xcr = 0;
u32 spcr;
snd_pcm_format_t fmt;
unsigned element_cnt = 1;
/* general line settings */
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
/* Determine xfer data type */
fmt = params_format(params);
switch (fmt) {
case SNDRV_PCM_FORMAT_S16_LE:
case SNDRV_PCM_FORMAT_S32_LE:
break;
case SNDRV_PCM_FORMAT_S24_LE:
spcr |= DAVINCI_MCBSP_SPCR_RJUST_S_LE;
break;
default:
dev_warn(dev->dev, "davinci-i2s: unsupported PCM format\n");
return -EINVAL;
}
/* general line settings */
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
spcr |= DAVINCI_MCBSP_SPCR_RINTM(3);
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
} else {
spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
spcr |= DAVINCI_MCBSP_SPCR_XINTM(3);
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
}
master = dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
fmt = params_format(params);
mcbsp_word_length = asp_word_length[fmt];
if (dev->slot_width)
mcbsp_word_length = davinci_i2s_tdm_word_length(dev->slot_width);
else
mcbsp_word_length = asp_word_length[fmt];
if (mcbsp_word_length < 0)
return mcbsp_word_length;
switch (master) {
case SND_SOC_DAIFMT_BP_FP:
freq = clk_get_rate(dev->clk);
srgr = DAVINCI_MCBSP_SRGR_FSGM |
DAVINCI_MCBSP_SRGR_CLKSM;
if (dev->ext_clk) {
freq = clk_get_rate(dev->ext_clk);
} else {
freq = clk_get_rate(dev->clk);
srgr = DAVINCI_MCBSP_SRGR_CLKSM;
}
srgr |= DAVINCI_MCBSP_SRGR_FSGM;
srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length *
8 - 1);
if (dev->i2s_accurate_sck) {
@ -434,6 +542,23 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
clk_div &= 0xFF;
srgr |= clk_div;
break;
case SND_SOC_DAIFMT_BP_FC:
if (dev->ext_clk) {
freq = clk_get_rate(dev->ext_clk);
} else {
freq = clk_get_rate(dev->clk);
srgr = DAVINCI_MCBSP_SRGR_CLKSM;
}
if (dev->tdm_slots && dev->slot_width) {
clk_div = freq / (params->rate_num * params->rate_den)
/ (dev->tdm_slots * dev->slot_width) - 1;
} else {
clk_div = freq / (mcbsp_word_length * 16) /
params->rate_num * params->rate_den;
}
clk_div &= 0xFF;
srgr |= clk_div;
break;
case SND_SOC_DAIFMT_BC_FC:
/* Clock and frame sync given from external sources */
i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
@ -450,8 +575,6 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
}
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
rcr = DAVINCI_MCBSP_RCR_RFIG;
xcr = DAVINCI_MCBSP_XCR_XFIG;
if (dev->mode == MOD_DSP_B) {
rcr |= DAVINCI_MCBSP_RCR_RDATDLY(0);
xcr |= DAVINCI_MCBSP_XCR_XDATDLY(0);
@ -459,11 +582,14 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
}
/* Determine xfer data type */
fmt = params_format(params);
if ((fmt > SNDRV_PCM_FORMAT_S32_LE) || !data_type[fmt]) {
printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n");
return -EINVAL;
if (dev->tx_framing_bit) {
xcr &= ~DAVINCI_MCBSP_XCR_XDATDLY(1);
xcr |= DAVINCI_MCBSP_XCR_XDATDLY(2);
}
if (dev->rx_framing_bit) {
rcr &= ~DAVINCI_MCBSP_RCR_RDATDLY(1);
rcr |= DAVINCI_MCBSP_RCR_RDATDLY(2);
}
if (params_channels(params) == 2) {
@ -489,13 +615,17 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
}
mcbsp_word_length = asp_word_length[fmt];
switch (master) {
case SND_SOC_DAIFMT_BP_FP:
case SND_SOC_DAIFMT_BP_FC:
rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0);
xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0);
if (dev->tdm_slots > 0) {
rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(dev->tdm_slots - 1);
xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(dev->tdm_slots - 1);
} else {
rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0);
xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0);
}
break;
case SND_SOC_DAIFMT_BC_FC:
case SND_SOC_DAIFMT_BC_FP:
@ -599,6 +729,7 @@ static void davinci_i2s_shutdown(struct snd_pcm_substream *substream,
#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000
#define DAVINCI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static int davinci_i2s_dai_probe(struct snd_soc_dai *dai)
@ -620,19 +751,20 @@ static const struct snd_soc_dai_ops davinci_i2s_dai_ops = {
.hw_params = davinci_i2s_hw_params,
.set_fmt = davinci_i2s_set_dai_fmt,
.set_clkdiv = davinci_i2s_dai_set_clkdiv,
.set_tdm_slot = davinci_i2s_set_tdm_slot,
};
static struct snd_soc_dai_driver davinci_i2s_dai = {
.playback = {
.channels_min = 2,
.channels_max = 2,
.channels_max = 128,
.rates = DAVINCI_I2S_RATES,
.formats = DAVINCI_I2S_FORMATS,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.channels_max = 128,
.rates = DAVINCI_I2S_RATES,
.formats = DAVINCI_I2S_FORMATS,
},
@ -676,6 +808,9 @@ static int davinci_i2s_probe(struct platform_device *pdev)
dev->base = io_base;
dev->tx_framing_bit = of_property_read_bool(pdev->dev.of_node, "ti,T1-framing-tx");
dev->rx_framing_bit = of_property_read_bool(pdev->dev.of_node, "ti,T1-framing-rx");
/* setup DMA, first TX, then RX */
dma_data = &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK];
dma_data->addr = (dma_addr_t)(mem->start + DAVINCI_MCBSP_DXR_REG);
@ -707,12 +842,36 @@ static int davinci_i2s_probe(struct platform_device *pdev)
return -ENODEV;
}
dev->clk = clk_get(&pdev->dev, NULL);
/*
* The optional is there for backward compatibility.
* If 'fck' is not present, the clk_get(dev, NULL) that follows may find something
*/
dev->clk = devm_clk_get_optional(&pdev->dev, "fck");
if (IS_ERR(dev->clk))
return -ENODEV;
ret = clk_enable(dev->clk);
return dev_err_probe(&pdev->dev, PTR_ERR(dev->clk), "Invalid functional clock\n");
if (!dev->clk) {
dev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dev->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(dev->clk),
"Missing functional clock\n");
}
dev->ext_clk = devm_clk_get_optional(&pdev->dev, "clks");
if (IS_ERR(dev->ext_clk))
return dev_err_probe(&pdev->dev, PTR_ERR(dev->ext_clk), "Invalid external clock\n");
ret = clk_prepare_enable(dev->clk);
if (ret)
goto err_put_clk;
return ret;
if (dev->ext_clk) {
dev_dbg(&pdev->dev, "External clock used for sample rate generator\n");
ret = clk_prepare_enable(dev->ext_clk);
if (ret) {
dev_err_probe(&pdev->dev, ret, "Failed to enable external clock\n");
goto err_disable_clk;
}
}
dev->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, dev);
@ -720,11 +879,11 @@ static int davinci_i2s_probe(struct platform_device *pdev)
ret = snd_soc_register_component(&pdev->dev, &davinci_i2s_component,
&davinci_i2s_dai, 1);
if (ret != 0)
goto err_release_clk;
goto err_disable_ext_clk;
ret = edma_pcm_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "register PCM failed: %d\n", ret);
dev_err_probe(&pdev->dev, ret, "register PCM failed\n");
goto err_unregister_component;
}
@ -732,10 +891,12 @@ static int davinci_i2s_probe(struct platform_device *pdev)
err_unregister_component:
snd_soc_unregister_component(&pdev->dev);
err_release_clk:
clk_disable(dev->clk);
err_put_clk:
clk_put(dev->clk);
err_disable_ext_clk:
if (dev->ext_clk)
clk_disable_unprepare(dev->ext_clk);
err_disable_clk:
clk_disable_unprepare(dev->clk);
return ret;
}
@ -745,9 +906,10 @@ static void davinci_i2s_remove(struct platform_device *pdev)
snd_soc_unregister_component(&pdev->dev);
clk_disable(dev->clk);
clk_put(dev->clk);
dev->clk = NULL;
clk_disable_unprepare(dev->clk);
if (dev->ext_clk)
clk_disable_unprepare(dev->ext_clk);
}
static const struct of_device_id davinci_i2s_match[] __maybe_unused = {