diff options
101 files changed, 7570 insertions, 1776 deletions
diff --git a/Documentation/devicetree/bindings/sound/adi,axi-i2s.txt b/Documentation/devicetree/bindings/sound/adi,axi-i2s.txt index 4248b662deff..229ad1392cdc 100644 --- a/Documentation/devicetree/bindings/sound/adi,axi-i2s.txt +++ b/Documentation/devicetree/bindings/sound/adi,axi-i2s.txt @@ -1,5 +1,8 @@ ADI AXI-I2S controller +The core can be generated with transmit (playback), only receive +(capture) or both directions enabled. + Required properties: - compatible : Must be "adi,axi-i2s-1.00.a" - reg : Must contain I2S core's registers location and length @@ -9,8 +12,8 @@ Required properties: - clock-names : "axi" for the clock to the AXI interface, "ref" for the sample rate reference clock. - dmas: Pairs of phandle and specifier for the DMA channels that are used by - the core. The core expects two dma channels, one for transmit and one for - receive. + the core. The core expects two dma channels if both transmit and receive are + enabled, one channel otherwise. - dma-names : "tx" for the transmit channel, "rx" for the receive channel. For more details on the 'dma', 'dma-names', 'clock' and 'clock-names' properties diff --git a/Documentation/devicetree/bindings/sound/cirrus,lochnagar.txt b/Documentation/devicetree/bindings/sound/cirrus,lochnagar.txt new file mode 100644 index 000000000000..41ae2699f07a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,lochnagar.txt @@ -0,0 +1,39 @@ +Cirrus Logic Lochnagar Audio Development Board + +Lochnagar is an evaluation and development board for Cirrus Logic +Smart CODEC and Amp devices. It allows the connection of most Cirrus +Logic devices on mini-cards, as well as allowing connection of +various application processor systems to provide a full evaluation +platform. Audio system topology, clocking and power can all be +controlled through the Lochnagar, allowing the device under test +to be used in a variety of possible use cases. + +This binding document describes the binding for the audio portion +of the driver. + +This binding must be part of the Lochnagar MFD binding: + [4] ../mfd/cirrus,lochnagar.txt + +Required properties: + + - compatible : One of the following strings: + "cirrus,lochnagar2-soundcard" + + - #sound-dai-cells : Must be set to 1. + + - clocks : Contains an entry for each entry in clock-names. + - clock-names : Must include the following clocks: + "mclk" Master clock source for the sound card, should normally + be set to LOCHNAGAR_SOUNDCARD_MCLK provided by the Lochnagar + clock driver. + +Example: + +lochnagar-sc { + compatible = "cirrus,lochnagar2-soundcard"; + + #sound-dai-cells = <1>; + + clocks = <&lochnagar_clk LOCHNAGAR_SOUNDCARD_MCLK>; + clock-names = "mclk"; +}; diff --git a/Documentation/devicetree/bindings/sound/da7219.txt b/Documentation/devicetree/bindings/sound/da7219.txt index e9d0baeb94e2..add1caf26ac2 100644 --- a/Documentation/devicetree/bindings/sound/da7219.txt +++ b/Documentation/devicetree/bindings/sound/da7219.txt @@ -23,8 +23,8 @@ Optional properties: interrupt is to be used to wake system, otherwise "irq" should be used. - wakeup-source: Flag to indicate this device can wake system (suspend/resume). -- #clock-cells : Should be set to '<0>', only one clock source provided; -- clock-output-names : Name given for DAI clocks output; +- #clock-cells : Should be set to '<1>', two clock sources provided; +- clock-output-names : Names given for DAI clock outputs (WCLK & BCLK); - clocks : phandle and clock specifier for codec MCLK. - clock-names : Clock name string for 'clocks' attribute, should be "mclk". @@ -84,8 +84,8 @@ Example: VDDMIC-supply = <®_audio>; VDDIO-supply = <®_audio>; - #clock-cells = <0>; - clock-output-names = "dai-clks"; + #clock-cells = <1>; + clock-output-names = "dai-wclk", "dai-bclk"; clocks = <&clks 201>; clock-names = "mclk"; diff --git a/Documentation/devicetree/bindings/sound/fsl,audmix.txt b/Documentation/devicetree/bindings/sound/fsl,audmix.txt new file mode 100644 index 000000000000..45f807ec21a5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,audmix.txt @@ -0,0 +1,54 @@ +NXP Audio Mixer (AUDMIX). + +The Audio Mixer is a on-chip functional module that allows mixing of two +audio streams into a single audio stream. Audio Mixer has two input serial +audio interfaces. These are driven by two Synchronous Audio interface +modules (SAI). Each input serial interface carries 8 audio channels in its +frame in TDM manner. Mixer mixes audio samples of corresponding channels +from two interfaces into a single sample. Before mixing, audio samples of +two inputs can be attenuated based on configuration. The output of the +Audio Mixer is also a serial audio interface. Like input interfaces it has +the same TDM frame format. This output is used to drive the serial DAC TDM +interface of audio codec and also sent to the external pins along with the +receive path of normal audio SAI module for readback by the CPU. + +The output of Audio Mixer can be selected from any of the three streams + - serial audio input 1 + - serial audio input 2 + - mixed audio + +Mixing operation is independent of audio sample rate but the two audio +input streams must have same audio sample rate with same number of channels +in TDM frame to be eligible for mixing. + +Device driver required properties: +================================= + - compatible : Compatible list, contains "fsl,imx8qm-audmix" + + - reg : Offset and length of the register set for the device. + + - clocks : Must contain an entry for each entry in clock-names. + + - clock-names : Must include the "ipg" for register access. + + - power-domains : Must contain the phandle to AUDMIX power domain node + + - dais : Must contain a list of phandles to AUDMIX connected + DAIs. The current implementation requires two phandles + to SAI interfaces to be provided, the first SAI in the + list being used to route the AUDMIX output. + + - model : Must contain machine driver name which will configure + and instantiate the appropriate audio card. + +Device driver configuration example: +====================================== + audmix: audmix@59840000 { + compatible = "fsl,imx8qm-audmix"; + reg = <0x0 0x59840000 0x0 0x10000>; + clocks = <&clk IMX8QXP_AUD_AUDMIX_IPG>; + clock-names = "ipg"; + power-domains = <&pd_audmix>; + dais = <&sai4>, <&sai5>; + model = "imx-audmix"; + }; diff --git a/Documentation/devicetree/bindings/sound/mchp-i2s-mcc.txt b/Documentation/devicetree/bindings/sound/mchp-i2s-mcc.txt new file mode 100644 index 000000000000..91ec83a6faed --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mchp-i2s-mcc.txt @@ -0,0 +1,43 @@ +* Microchip I2S Multi-Channel Controller + +Required properties: +- compatible: Should be "microchip,sam9x60-i2smcc". +- reg: Should be the physical base address of the controller and the + length of memory mapped region. +- interrupts: Should contain the interrupt for the controller. +- dmas: Should be one per channel name listed in the dma-names property, + as described in atmel-dma.txt and dma.txt files. +- dma-names: Identifier string for each DMA request line in the dmas property. + Two dmas have to be defined, "tx" and "rx". +- clocks: Must contain an entry for each entry in clock-names. + Please refer to clock-bindings.txt. +- clock-names: Should be one of each entry matching the clocks phandles list: + - "pclk" (peripheral clock) Required. + - "gclk" (generated clock) Optional (1). + +Optional properties: +- pinctrl-0: Should specify pin control groups used for this controller. +- princtrl-names: Should contain only one value - "default". + + +(1) : Only the peripheral clock is required. The generated clock is optional + and should be set mostly when Master Mode is required. + +Example: + + i2s@f001c000 { + compatible = "microchip,sam9x60-i2smcc"; + reg = <0xf001c000 0x100>; + interrupts = <34 IRQ_TYPE_LEVEL_HIGH 7>; + dmas = <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(36))>, + <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1) | + AT91_XDMAC_DT_PERID(37))>; + dma-names = "tx", "rx"; + clocks = <&i2s_clk>, <&i2s_gclk>; + clock-names = "pclk", "gclk"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2s_default>; + }; diff --git a/Documentation/devicetree/bindings/sound/mt8183-da7219-max98357.txt b/Documentation/devicetree/bindings/sound/mt8183-da7219-max98357.txt new file mode 100644 index 000000000000..92ac86f83822 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mt8183-da7219-max98357.txt @@ -0,0 +1,15 @@ +MT8183 with MT6358, DA7219 and MAX98357 CODECS + +Required properties: +- compatible : "mediatek,mt8183_da7219_max98357" +- mediatek,headset-codec: the phandles of da7219 codecs +- mediatek,platform: the phandle of MT8183 ASoC platform + +Example: + + sound { + compatible = "mediatek,mt8183_da7219_max98357"; + mediatek,headset-codec = <&da7219>; + mediatek,platform = <&afe>; + }; + diff --git a/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt b/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt new file mode 100644 index 000000000000..d6d5207fa996 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt @@ -0,0 +1,15 @@ +MT8183 with MT6358, TS3A227 and MAX98357 CODECS + +Required properties: +- compatible : "mediatek,mt8183_mt6358_ts3a227_max98357" +- mediatek,headset-codec: the phandles of ts3a227 codecs +- mediatek,platform: the phandle of MT8183 ASoC platform + +Example: + + sound { + compatible = "mediatek,mt8183_mt6358_ts3a227_max98357"; + mediatek,headset-codec = <&ts3a227>; + mediatek,platform = <&afe>; + }; + diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt index 648d43e1b1e9..b34afa66fb00 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt @@ -282,7 +282,12 @@ Required properties: - reg : Should contain the register physical address. required register is SRU/ADG/SSI if generation1 - SRU/ADG/SSIU/SSI if generation2 + SRU/ADG/SSIU/SSI/AUDIO-DMAC-periperi if generation2/generation3 + Select extended AUDIO-DMAC-periperi address if SoC has it, + otherwise select normal AUDIO-DMAC-periperi address. +- reg-names : Should contain the register names. + scu/adg/ssi if generation1 + scu/adg/ssiu/ssi/audmapp if generation2/generation3 - rcar_sound,ssi : Should contain SSI feature. The number of SSI subnode should be same as HW. see below for detail. diff --git a/Documentation/devicetree/bindings/sound/rt5651.txt b/Documentation/devicetree/bindings/sound/rt5651.txt index a41199a5cd79..56e736a1cba9 100644 --- a/Documentation/devicetree/bindings/sound/rt5651.txt +++ b/Documentation/devicetree/bindings/sound/rt5651.txt @@ -22,6 +22,11 @@ Optional properties: 2: Use JD1_2 pin for jack-detect 3: Use JD2 pin for jack-detect +- realtek,jack-detect-not-inverted + bool. Normal jack-detect switches give an inverted (active-low) signal, + set this bool in the rare case you've a jack-detect switch which is not + inverted. + - realtek,over-current-threshold-microamp u32, micbias over-current detection threshold in µA, valid values are 600, 1500 and 2000µA. diff --git a/Documentation/devicetree/bindings/sound/simple-amplifier.txt b/Documentation/devicetree/bindings/sound/simple-amplifier.txt index 7182ac4f1e65..b1b097cc9b68 100644 --- a/Documentation/devicetree/bindings/sound/simple-amplifier.txt +++ b/Documentation/devicetree/bindings/sound/simple-amplifier.txt @@ -2,9 +2,9 @@ Simple Amplifier Audio Driver Required properties: - compatible : "dioo,dio2125" or "simple-audio-amplifier" -- enable-gpios : the gpio connected to the enable pin of the simple amplifier Optional properties: +- enable-gpios : the gpio connected to the enable pin of the simple amplifier - VCC-supply : power supply for the device, as covered in Documentation/devicetree/bindings/regulator/regulator.txt diff --git a/MAINTAINERS b/MAINTAINERS index 43b36dbed48e..b6ed758ffd47 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3799,6 +3799,7 @@ F: drivers/clk/clk-lochnagar.c F: drivers/mfd/lochnagar-i2c.c F: drivers/pinctrl/cirrus/pinctrl-lochnagar.c F: drivers/regulator/lochnagar-regulator.c +F: sound/soc/codecs/lochnagar-sc.c F: include/dt-bindings/clk/lochnagar.h F: include/dt-bindings/pinctrl/lochnagar.h F: include/linux/mfd/lochnagar* @@ -3806,6 +3807,7 @@ F: Documentation/devicetree/bindings/mfd/cirrus,lochnagar.txt F: Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt F: Documentation/devicetree/bindings/pinctrl/cirrus,lochnagar.txt F: Documentation/devicetree/bindings/regulator/cirrus,lochnagar.txt +F: Documentation/devicetree/bindings/sound/cirrus,lochnagar.txt CISCO FCOE HBA DRIVER M: Satish Kharat <satishkh@cisco.com> diff --git a/include/sound/da7219.h b/include/sound/da7219.h index 1bfcb16f2d10..4a36954c86c5 100644 --- a/include/sound/da7219.h +++ b/include/sound/da7219.h @@ -33,10 +33,16 @@ enum da7219_mic_amp_in_sel { struct da7219_aad_pdata; +enum da7219_dai_clks { + DA7219_DAI_WCLK_IDX = 0, + DA7219_DAI_BCLK_IDX, + DA7219_DAI_NUM_CLKS, +}; + struct da7219_pdata { bool wakeup_source; - const char *dai_clks_name; + const char *dai_clk_names[DA7219_DAI_NUM_CLKS]; /* Mic */ enum da7219_micbias_voltage micbias_lvl; diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 7afe45389972..67dc3ee6ed01 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -10,10 +10,10 @@ #include <sound/soc.h> -#define asoc_simple_card_init_hp(card, sjack, prefix) \ - asoc_simple_card_init_jack(card, sjack, 1, prefix) -#define asoc_simple_card_init_mic(card, sjack, prefix) \ - asoc_simple_card_init_jack(card, sjack, 0, prefix) +#define asoc_simple_init_hp(card, sjack, prefix) \ + asoc_simple_init_jack(card, sjack, 1, prefix) +#define asoc_simple_init_mic(card, sjack, prefix) \ + asoc_simple_init_jack(card, sjack, 0, prefix) struct asoc_simple_dai { const char *name; @@ -26,7 +26,7 @@ struct asoc_simple_dai { struct clk *clk; }; -struct asoc_simple_card_data { +struct asoc_simple_data { u32 convert_rate; u32 convert_channels; }; @@ -37,96 +37,178 @@ struct asoc_simple_jack { struct snd_soc_jack_gpio gpio; }; -int asoc_simple_card_parse_daifmt(struct device *dev, - struct device_node *node, - struct device_node *codec, - char *prefix, - unsigned int *retfmt); +struct asoc_simple_priv { + struct snd_soc_card snd_card; + struct simple_dai_props { + struct asoc_simple_dai *cpu_dai; + struct asoc_simple_dai *codec_dai; + struct snd_soc_dai_link_component codecs; /* single codec */ + struct snd_soc_dai_link_component platforms; + struct asoc_simple_data adata; + struct snd_soc_codec_conf *codec_conf; + unsigned int mclk_fs; + } *dai_props; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dais; + struct snd_soc_codec_conf *codec_conf; + struct gpio_desc *pa_gpio; +}; +#define simple_priv_to_card(priv) (&(priv)->snd_card) +#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) + +struct link_info { + int dais; /* number of dai */ + int link; /* number of link */ + int conf; /* number of codec_conf */ + int cpu; /* turn for CPU / Codec */ +}; + +int asoc_simple_parse_daifmt(struct device *dev, + struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt); __printf(3, 4) -int asoc_simple_card_set_dailink_name(struct device *dev, - struct snd_soc_dai_link *dai_link, - const char *fmt, ...); -int asoc_simple_card_parse_card_name(struct snd_soc_card *card, - char *prefix); - -#define asoc_simple_card_parse_clk_cpu(dev, node, dai_link, simple_dai) \ - asoc_simple_card_parse_clk(dev, node, dai_link->cpu_of_node, simple_dai, \ +int asoc_simple_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...); +int asoc_simple_parse_card_name(struct snd_soc_card *card, + char *prefix); + +#define asoc_simple_parse_clk_cpu(dev, node, dai_link, simple_dai) \ + asoc_simple_parse_clk(dev, node, dai_link->cpu_of_node, simple_dai, \ dai_link->cpu_dai_name, NULL) -#define asoc_simple_card_parse_clk_codec(dev, node, dai_link, simple_dai) \ - asoc_simple_card_parse_clk(dev, node, dai_link->codec_of_node, simple_dai,\ +#define asoc_simple_parse_clk_codec(dev, node, dai_link, simple_dai) \ + asoc_simple_parse_clk(dev, node, dai_link->codec_of_node, simple_dai,\ dai_link->codec_dai_name, dai_link->codecs) -int asoc_simple_card_parse_clk(struct device *dev, - struct device_node *node, - struct device_node *dai_of_node, - struct asoc_simple_dai *simple_dai, - const char *dai_name, - struct snd_soc_dai_link_component *dlc); -int asoc_simple_card_clk_enable(struct asoc_simple_dai *dai); -void asoc_simple_card_clk_disable(struct asoc_simple_dai *dai); - -#define asoc_simple_card_parse_cpu(node, dai_link, \ - list_name, cells_name, is_single_link) \ - asoc_simple_card_parse_dai(node, NULL, \ - &dai_link->cpu_of_node, \ - &dai_link->cpu_dai_name, list_name, cells_name, is_single_link) -#define asoc_simple_card_parse_codec(node, dai_link, list_name, cells_name) \ - asoc_simple_card_parse_dai(node, dai_link->codecs, \ +int asoc_simple_parse_clk(struct device *dev, + struct device_node *node, + struct device_node *dai_of_node, + struct asoc_simple_dai *simple_dai, + const char *dai_name, + struct snd_soc_dai_link_component *dlc); +int asoc_simple_startup(struct snd_pcm_substream *substream); +void asoc_simple_shutdown(struct snd_pcm_substream *substream); +int asoc_simple_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); +int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd); +int asoc_simple_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params); + +#define asoc_simple_parse_cpu(node, dai_link, is_single_link) \ + asoc_simple_parse_dai(node, NULL, \ + &dai_link->cpu_of_node, \ + &dai_link->cpu_dai_name, is_single_link) +#define asoc_simple_parse_codec(node, dai_link) \ + asoc_simple_parse_dai(node, dai_link->codecs, \ &dai_link->codec_of_node, \ - &dai_link->codec_dai_name, \ - list_name, cells_name, NULL) -#define asoc_simple_card_parse_platform(node, dai_link, list_name, cells_name) \ - asoc_simple_card_parse_dai(node, dai_link->platforms, \ - &dai_link->platform_of_node, \ - NULL, list_name, cells_name, NULL) -int asoc_simple_card_parse_dai(struct device_node *node, - struct snd_soc_dai_link_component *dlc, - struct device_node **endpoint_np, - const char **dai_name, - const char *list_name, - const char *cells_name, - int *is_single_links); - -#define asoc_simple_card_parse_graph_cpu(ep, dai_link) \ - asoc_simple_card_parse_graph_dai(ep, NULL, \ - &dai_link->cpu_of_node, \ - &dai_link->cpu_dai_name) -#define asoc_simple_card_parse_graph_codec(ep, dai_link) \ - asoc_simple_card_parse_graph_dai(ep, dai_link->codecs, \ - &dai_link->codec_of_node, \ - &dai_link->codec_dai_name) -int asoc_simple_card_parse_graph_dai(struct device_node *ep, - struct snd_soc_dai_link_component *dlc, - struct device_node **endpoint_np, - const char **dai_name); - -#define asoc_simple_card_of_parse_tdm(np, dai) \ + &dai_link->codec_dai_name, NULL) +#define asoc_simple_parse_platform(node, dai_link) \ + asoc_simple_parse_dai(node, dai_link->platforms, \ + &dai_link->platform_of_node, NULL, NULL) + +#define asoc_simple_parse_tdm(np, dai) \ snd_soc_of_parse_tdm_slot(np, &(dai)->tx_slot_mask, \ &(dai)->rx_slot_mask, \ &(dai)->slots, \ &(dai)->slot_width); -int asoc_simple_card_init_dai(struct snd_soc_dai *dai, - struct asoc_simple_dai *simple_dai); - -void asoc_simple_card_canonicalize_platform(struct snd_soc_dai_link *dai_link); -void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, +void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link); +void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link, int is_single_links); -int asoc_simple_card_clean_reference(struct snd_soc_card *card); +int asoc_simple_clean_reference(struct snd_soc_card *card); -void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, +void asoc_simple_convert_fixup(struct asoc_simple_data *data, struct snd_pcm_hw_params *params); -void asoc_simple_card_parse_convert(struct device *dev, - struct device_node *np, char *prefix, - struct asoc_simple_card_data *data); +void asoc_simple_parse_convert(struct device *dev, + struct device_node *np, char *prefix, + struct asoc_simple_data *data); -int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, +int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix); -int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, +int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix); -int asoc_simple_card_init_jack(struct snd_soc_card *card, +int asoc_simple_init_jack(struct snd_soc_card *card, struct asoc_simple_jack *sjack, int is_hp, char *prefix); +int asoc_simple_init_priv(struct asoc_simple_priv *priv, + struct link_info *li); + +#ifdef DEBUG +inline void asoc_simple_debug_dai(struct asoc_simple_priv *priv, + char *name, + struct asoc_simple_dai *dai) +{ + struct device *dev = simple_priv_to_dev(priv); + + if (dai->name) + dev_dbg(dev, "%s dai name = %s\n", + name, dai->name); + if (dai->sysclk) + dev_dbg(dev, "%s sysclk = %d\n", + name, dai->sysclk); + + dev_dbg(dev, "%s direction = %s\n", + name, dai->clk_direction ? "OUT" : "IN"); + + if (dai->slots) + dev_dbg(dev, "%s slots = %d\n", name, dai->slots); + if (dai->slot_width) + dev_dbg(dev, "%s slot width = %d\n", name, dai->slot_width); + if (dai->tx_slot_mask) + dev_dbg(dev, "%s tx slot mask = %d\n", name, dai->tx_slot_mask); + if (dai->rx_slot_mask) + dev_dbg(dev, "%s rx slot mask = %d\n", name, dai->rx_slot_mask); + if (dai->clk) + dev_dbg(dev, "%s clk %luHz\n", name, clk_get_rate(dai->clk)); +} + +inline void asoc_simple_debug_info(struct asoc_simple_priv *priv) +{ + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device *dev = simple_priv_to_dev(priv); + + int i; + + if (card->name) + dev_dbg(dev, "Card Name: %s\n", card->name); + + for (i = 0; i < card->num_links; i++) { + struct simple_dai_props *props = simple_priv_to_props(priv, i); + struct snd_soc_dai_link *link = simple_priv_to_link(priv, i); + + dev_dbg(dev, "DAI%d\n", i); + + asoc_simple_debug_dai(priv, "cpu", props->cpu_dai); + asoc_simple_debug_dai(priv, "codec", props->codec_dai); + + if (link->name) + dev_dbg(dev, "dai name = %s\n", link->name); + + dev_dbg(dev, "dai format = %04x\n", link->dai_fmt); + + if (props->adata.convert_rate) + dev_dbg(dev, "convert_rate = %d\n", + props->adata.convert_rate); + if (props->adata.convert_channels) + dev_dbg(dev, "convert_channels = %d\n", + props->adata.convert_channels); + if (props->codec_conf && props->codec_conf->name_prefix) + dev_dbg(dev, "name prefix = %s\n", + props->codec_conf->name_prefix); + if (props->mclk_fs) + dev_dbg(dev, "mclk-fs = %d\n", + props->mclk_fs); + } +} +#else +#define asoc_simple_debug_info(priv) +#endif /* DEBUG */ #endif /* __SIMPLE_CARD_UTILS_H */ diff --git a/include/sound/soc.h b/include/sound/soc.h index eb7db605955b..1e2be35ed36f 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1083,6 +1083,8 @@ struct snd_soc_card { struct mutex mutex; struct mutex dapm_mutex; + spinlock_t dpcm_lock; + bool instantiated; bool topology_shortname_created; diff --git a/sound/soc/adi/axi-i2s.c b/sound/soc/adi/axi-i2s.c index 4c23381727a1..273c543e8ff3 100644 --- a/sound/soc/adi/axi-i2s.c +++ b/sound/soc/adi/axi-i2s.c @@ -43,6 +43,9 @@ struct axi_i2s { struct clk *clk; struct clk *clk_ref; + bool has_capture; + bool has_playback; + struct snd_soc_dai_driver dai_driver; struct snd_dmaengine_dai_dma_data capture_dma_data; @@ -136,8 +139,10 @@ static int axi_i2s_dai_probe(struct snd_soc_dai *dai) { struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); - snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, - &i2s->capture_dma_data); + snd_soc_dai_init_dma_data( + dai, + i2s->has_playback ? &i2s->playback_dma_data : NULL, + i2s->has_capture ? &i2s->capture_dma_data : NULL); return 0; } @@ -151,18 +156,6 @@ static const struct snd_soc_dai_ops axi_i2s_dai_ops = { static struct snd_soc_dai_driver axi_i2s_dai = { .probe = axi_i2s_dai_probe, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, - }, .ops = &axi_i2s_dai_ops, .symmetric_rates = 1, }; @@ -178,6 +171,19 @@ static const struct regmap_config axi_i2s_regmap_config = { .max_register = AXI_I2S_REG_STATUS, }; +static void axi_i2s_parse_of(struct axi_i2s *i2s, const struct device_node *np) +{ + struct property *dma_names; + const char *dma_name; + + of_property_for_each_string(np, "dma-names", dma_names, dma_name) { + if (strcmp(dma_name, "rx") == 0) + i2s->has_capture = true; + if (strcmp(dma_name, "tx") == 0) + i2s->has_playback = true; + } +} + static int axi_i2s_probe(struct platform_device *pdev) { struct resource *res; @@ -191,6 +197,8 @@ static int axi_i2s_probe(struct platform_device *pdev) platform_set_drvdata(pdev, i2s); + axi_i2s_parse_of(i2s, pdev->dev.of_node); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) @@ -213,13 +221,29 @@ static int axi_i2s_probe(struct platform_device *pdev) if (ret) return ret; - i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; - i2s->playback_dma_data.addr_width = 4; - i2s->playback_dma_data.maxburst = 1; + if (i2s->has_playback) { + axi_i2s_dai.playback.channels_min = 2; + axi_i2s_dai.playback.channels_max = 2; + axi_i2s_dai.playback.rates = SNDRV_PCM_RATE_KNOT; + axi_i2s_dai.playback.formats = + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE; + + i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; + i2s->playback_dma_data.addr_width = 4; + i2s->playback_dma_data.maxburst = 1; + } + + if (i2s->has_capture) { + axi_i2s_dai.capture.channels_min = 2; + axi_i2s_dai.capture.channels_max = 2; + axi_i2s_dai.capture.rates = SNDRV_PCM_RATE_KNOT; + axi_i2s_dai.capture.formats = + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE; - i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; - i2s->capture_dma_data.addr_width = 4; - i2s->capture_dma_data.maxburst = 1; + i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; + i2s->capture_dma_data.addr_width = 4; + i2s->capture_dma_data.maxburst = 1; + } i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME; i2s->ratnum.den_step = 1; @@ -240,6 +264,10 @@ static int axi_i2s_probe(struct platform_device *pdev) if (ret) goto err_clk_disable; + dev_info(&pdev->dev, "probed, capture %s, playback %s\n", + i2s->has_capture ? "enabled" : "disabled", + i2s->has_playback ? "enabled" : "disabled"); + return 0; err_clk_disable: diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index a5daad973ce5..8a619a75b3a9 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -47,7 +47,7 @@ static struct snd_soc_jack cz_jack; static struct clk *da7219_dai_clk; -extern int bt_uart_enable; +extern bool bt_uart_enable; static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) { @@ -72,7 +72,7 @@ static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) return ret; } - da7219_dai_clk = clk_get(component->dev, "da7219-dai-clks"); + da7219_dai_clk = clk_get(component->dev, "da7219-dai-bclk"); ret = snd_soc_card_jack_new(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_LINEOUT | diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 64f86f0b87e5..c473b9e463ab 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -109,4 +109,18 @@ config SND_SOC_MIKROE_PROTO using I2C over SDA (MPU Data Input) and SCL (MPU Clock Input) pins. Both playback and capture are supported. +config SND_MCHP_SOC_I2S_MCC + tristate "Microchip ASoC driver for boards using I2S MCC" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for I2S Multi-Channel ASoC + driver on the following Microchip platforms: + - sam9x60 + + The I2SMCC complies with the Inter-IC Sound (I2S) bus specification + and supports a Time Division Multiplexed (TDM) interface with + external multi-channel audio codecs. + endif diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 9f41bfa0fea3..1f6890ed3738 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -4,11 +4,13 @@ snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o snd-soc-atmel-i2s-objs := atmel-i2s.o +snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o +obj-$(CONFIG_SND_MCHP_SOC_I2S_MCC) += snd-soc-mchp-i2s-mcc.o # AT91 Machine Support snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c new file mode 100644 index 000000000000..86495883ca3f --- /dev/null +++ b/sound/soc/atmel/mchp-i2s-mcc.c @@ -0,0 +1,974 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for Microchip I2S Multi-channel controller +// +// Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries +// +// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com> + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/lcm.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +/* + * ---- I2S Controller Register map ---- + */ +#define MCHP_I2SMCC_CR 0x0000 /* Control Register */ +#define MCHP_I2SMCC_MRA 0x0004 /* Mode Register A */ +#define MCHP_I2SMCC_MRB 0x0008 /* Mode Register B */ +#define MCHP_I2SMCC_SR 0x000C /* Status Register */ +#define MCHP_I2SMCC_IERA 0x0010 /* Interrupt Enable Register A */ +#define MCHP_I2SMCC_IDRA 0x0014 /* Interrupt Disable Register A */ +#define MCHP_I2SMCC_IMRA 0x0018 /* Interrupt Mask Register A */ +#define MCHP_I2SMCC_ISRA 0X001C /* Interrupt Status Register A */ + +#define MCHP_I2SMCC_IERB 0x0020 /* Interrupt Enable Register B */ +#define MCHP_I2SMCC_IDRB 0x0024 /* Interrupt Disable Register B */ +#define MCHP_I2SMCC_IMRB 0x0028 /* Interrupt Mask Register B */ +#define MCHP_I2SMCC_ISRB 0X002C /* Interrupt Status Register B */ + +#define MCHP_I2SMCC_RHR 0x0030 /* Receiver Holding Register */ +#define MCHP_I2SMCC_THR 0x0034 /* Transmitter Holding Register */ + +#define MCHP_I2SMCC_RHL0R 0x0040 /* Receiver Holding Left 0 Register */ +#define MCHP_I2SMCC_RHR0R 0x0044 /* Receiver Holding Right 0 Register */ + +#define MCHP_I2SMCC_RHL1R 0x0048 /* Receiver Holding Left 1 Register */ +#define MCHP_I2SMCC_RHR1R 0x004C /* Receiver Holding Right 1 Register */ + +#define MCHP_I2SMCC_RHL2R 0x0050 /* Receiver Holding Left 2 Register */ +#define MCHP_I2SMCC_RHR2R 0x0054 /* Receiver Holding Right 2 Register */ + +#define MCHP_I2SMCC_RHL3R 0x0058 /* Receiver Holding Left 3 Register */ +#define MCHP_I2SMCC_RHR3R 0x005C /* Receiver Holding Right 3 Register */ + +#define MCHP_I2SMCC_THL0R 0x0060 /* Transmitter Holding Left 0 Register */ +#define MCHP_I2SMCC_THR0R 0x0064 /* Transmitter Holding Right 0 Register */ + +#define MCHP_I2SMCC_THL1R 0x0068 /* Transmitter Holding Left 1 Register */ +#define MCHP_I2SMCC_THR1R 0x006C /* Transmitter Holding Right 1 Register */ + +#define MCHP_I2SMCC_THL2R 0x0070 /* Transmitter Holding Left 2 Register */ +#define MCHP_I2SMCC_THR2R 0x0074 /* Transmitter Holding Right 2 Register */ + +#define MCHP_I2SMCC_THL3R 0x0078 /* Transmitter Holding Left 3 Register */ +#define MCHP_I2SMCC_THR3R 0x007C /* Transmitter Holding Right 3 Register */ + +#define MCHP_I2SMCC_VERSION 0x00FC /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define MCHP_I2SMCC_CR_RXEN BIT(0) /* Receiver Enable */ +#define MCHP_I2SMCC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define MCHP_I2SMCC_CR_CKEN BIT(2) /* Clock Enable */ +#define MCHP_I2SMCC_CR_CKDIS BIT(3) /* Clock Disable */ +#define MCHP_I2SMCC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define MCHP_I2SMCC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define MCHP_I2SMCC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register A (Read/Write) ---- + */ +#define MCHP_I2SMCC_MRA_MODE_MASK GENMASK(0, 0) +#define MCHP_I2SMCC_MRA_MODE_SLAVE (0 << 0) +#define MCHP_I2SMCC_MRA_MODE_MASTER (1 << 0) + +#define MCHP_I2SMCC_MRA_DATALENGTH_MASK GENMASK(3, 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_32_BITS (0 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_24_BITS (1 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_20_BITS (2 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_18_BITS (3 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_16_BITS (4 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_16_BITS_COMPACT (5 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS (6 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS_COMPACT (7 << 1) + +#define MCHP_I2SMCC_MRA_WIRECFG_MASK GENMASK(5, 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_1_TDM_0 (0 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1 (1 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2 (2 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_TDM_3 (3 << 4) + +#define MCHP_I2SMCC_MRA_FORMAT_MASK GENMASK(7, 6) +#define MCHP_I2SMCC_MRA_FORMAT_I2S (0 << 6) +#define MCHP_I2SMCC_MRA_FORMAT_LJ (1 << 6) /* Left Justified */ +#define MCHP_I2SMCC_MRA_FORMAT_TDM (2 << 6) +#define MCHP_I2SMCC_MRA_FORMAT_TDMLJ (3 << 6) + +/* Transmitter uses one DMA channel ... */ +/* Left audio samples duplicated to right audio channel */ +#define MCHP_I2SMCC_MRA_RXMONO BIT(8) + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define MCHP_I2SMCC_MRA_RXLOOP BIT(9) + +/* Receiver uses one DMA channel ... */ +/* Left audio samples duplicated to right audio channel */ +#define MCHP_I2SMCC_MRA_TXMONO BIT(10) + +/* x sample transmitted when underrun */ +#define MCHP_I2SMCC_MRA_TXSAME_ZERO (0 << 11) /* Zero sample */ +#define MCHP_I2SMCC_MRA_TXSAME_PREVIOUS (1 << 11) /* Previous sample */ + +/* select between peripheral clock and generated clock */ +#define MCHP_I2SMCC_MRA_SRCCLK_PCLK (0 << 12) +#define MCHP_I2SMCC_MRA_SRCCLK_GCLK (1 << 12) + +/* Number of TDM Channels - 1 */ +#define MCHP_I2SMCC_MRA_NBCHAN_MASK GENMASK(15, 13) +#define MCHP_I2SMCC_MRA_NBCHAN(ch) \ + ((((ch) - 1) << 13) & MCHP_I2SMCC_MRA_NBCHAN_MASK) + +/* Selected Clock to I2SMCC Master Clock ratio */ +#define MCHP_I2SMCC_MRA_IMCKDIV_MASK GENMASK(21, 16) +#define MCHP_I2SMCC_MRA_IMCKDIV(div) \ + (((div) << 16) & MCHP_I2SMCC_MRA_IMCKDIV_MASK) + +/* TDM Frame Synchronization */ +#define MCHP_I2SMCC_MRA_TDMFS_MASK GENMASK(23, 22) +#define MCHP_I2SMCC_MRA_TDMFS_SLOT (0 << 22) +#define MCHP_I2SMCC_MRA_TDMFS_HALF (1 << 22) +#define MCHP_I2SMCC_MRA_TDMFS_BIT (2 << 22) + +/* Selected Clock to I2SMC Serial Clock ratio */ +#define MCHP_I2SMCC_MRA_ISCKDIV_MASK GENMASK(29, 24) +#define MCHP_I2SMCC_MRA_ISCKDIV(div) \ + (((div) << 24) & MCHP_I2SMCC_MRA_ISCKDIV_MASK) + +/* Master Clock mode */ +#define MCHP_I2SMCC_MRA_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated*/ +#define MCHP_I2SMCC_MRA_IMCKMODE_NONE (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define MCHP_I2SMCC_MRA_IMCKMODE_GEN (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define MCHP_I2SMCC_MRA_IWS BIT(31) + +/* + * ---- Mode Register B (Read/Write) ---- + */ +/* all enabled I2S left channels are filled first, then I2S right channels */ +#define MCHP_I2SMCC_MRB_CRAMODE_LEFT_FIRST (0 << 0) +/* + * an enabled I2S left channel is filled, then the corresponding right + * channel, until all channels are filled + */ +#define MCHP_I2SMCC_MRB_CRAMODE_REGULAR (1 << 0) + +#define MCHP_I2SMCC_MRB_FIFOEN BIT(1) + +#define MCHP_I2SMCC_MRB_DMACHUNK_MASK GENMASK(9, 8) +#define MCHP_I2SMCC_MRB_DMACHUNK(no_words) \ + (((fls(no_words) - 1) << 8) & MCHP_I2SMCC_MRB_DMACHUNK_MASK) + +#define MCHP_I2SMCC_MRB_CLKSEL_MASK GENMASK(16, 16) +#define MCHP_I2SMCC_MRB_CLKSEL_EXT (0 << 16) +#define MCHP_I2SMCC_MRB_CLKSEL_INT (1 << 16) + +/* + * ---- Status Registers (Read-only) ---- + */ +#define MCHP_I2SMCC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define MCHP_I2SMCC_SR_TXEN BIT(4) /* Transmitter Enabled */ + +/* + * ---- Interrupt Enable/Disable/Mask/Status Registers A ---- + */ +#define MCHP_I2SMCC_INT_TXRDY_MASK(ch) GENMASK((ch) - 1, 0) +#define MCHP_I2SMCC_INT_TXRDYCH(ch) BIT(ch) +#define MCHP_I2SMCC_INT_TXUNF_MASK(ch) GENMASK((ch) + 7, 8) +#define MCHP_I2SMCC_INT_TXUNFCH(ch) BIT((ch) + 8) +#define MCHP_I2SMCC_INT_RXRDY_MASK(ch) GENMASK((ch) + 15, 16) +#define MCHP_I2SMCC_INT_RXRDYCH(ch) BIT((ch) + 16) +#define MCHP_I2SMCC_INT_RXOVF_MASK(ch) GENMASK((ch) + 23, 24) +#define MCHP_I2SMCC_INT_RXOVFCH(ch) BIT((ch) + 24) + +/* + * ---- Interrupt Enable/Disable/Mask/Status Registers B ---- + */ +#define MCHP_I2SMCC_INT_WERR BIT(0) +#define MCHP_I2SMCC_INT_TXFFRDY BIT(8) +#define MCHP_I2SMCC_INT_TXFFEMP BIT(9) +#define MCHP_I2SMCC_INT_RXFFRDY BIT(12) +#define MCHP_I2SMCC_INT_RXFFFUL BIT(13) + +/* + * ---- Version Register (Read-only) ---- + */ +#define MCHP_I2SMCC_VERSION_MASK GENMASK(11, 0) + +#define MCHP_I2SMCC_MAX_CHANNELS 8 +#define MCHP_I2MCC_TDM_SLOT_WIDTH 32 + +static const struct regmap_config mchp_i2s_mcc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = MCHP_I2SMCC_VERSION, +}; + +struct mchp_i2s_mcc_dev { + struct wait_queue_head wq_txrdy; + struct wait_queue_head wq_rxrdy; + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + unsigned int sysclk; + unsigned int frame_length; + int tdm_slots; + int channels; + int gclk_use:1; + int gclk_running:1; + int tx_rdy:1; + int rx_rdy:1; +}; + +static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id) +{ + struct mchp_i2s_mcc_dev *dev = dev_id; + u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, MCHP_I2SMCC_IMRA, &imra); + regmap_read(dev->regmap, MCHP_I2SMCC_ISRA, &sra); + pendinga = imra & sra; + + regmap_read(dev->regmap, MCHP_I2SMCC_IMRB, &imrb); + regmap_read(dev->regmap, MCHP_I2SMCC_ISRB, &srb); + pendingb = imrb & srb; + + if (!pendinga && !pendingb) + return IRQ_NONE; + + /* + * Tx/Rx ready interrupts are enabled when stopping only, to assure + * availability and to disable clocks if necessary + */ + idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) | + MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); + if (idra) + ret = IRQ_HANDLED; + + if ((imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) { + dev->tx_rdy = 1; + wake_up_interruptible(&dev->wq_txrdy); + } + if ((imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) { + dev->rx_rdy = 1; + wake_up_interruptible(&dev->wq_rxrdy); + } + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); + + return ret; +} + +static int mchp_i2s_mcc_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() clk_id=%d freq=%u dir=%d\n", + __func__, clk_id, freq, dir); + + /* We do not need SYSCLK */ + if (dir == SND_SOC_CLOCK_IN) + return 0; + + dev->sysclk = freq; + + return 0; +} + +static int mchp_i2s_mcc_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() ratio=%u\n", __func__, ratio); + + dev->frame_length = ratio; + + return 0; +} + +static int mchp_i2s_mcc_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() fmt=%#x\n", __func__, fmt); + + /* We don't support any kind of clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + /* We can't generate only FSYNC */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFS) + return -EINVAL; + + /* We can only reconfigure the IP when it's stopped */ + if (fmt & SND_SOC_DAIFMT_CONT) + return -EINVAL; + + dev->fmt = fmt; + + return 0; +} + +static int mchp_i2s_mcc_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, + "%s() tx_mask=0x%08x rx_mask=0x%08x slots=%d width=%d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + if (slots < 0 || slots > MCHP_I2SMCC_MAX_CHANNELS || + slot_width != MCHP_I2MCC_TDM_SLOT_WIDTH) + return -EINVAL; + + if (slots) { + /* We do not support daisy chain */ + if (rx_mask != GENMASK(slots - 1, 0) || + rx_mask != tx_mask) + return -EINVAL; + } + + dev->tdm_slots = slots; + dev->frame_length = slots * MCHP_I2MCC_TDM_SLOT_WIDTH; + + return 0; +} + +static int mchp_i2s_mcc_clk_get_rate_diff(struct clk *clk, + unsigned long rate, + struct clk **best_clk, + unsigned long *best_rate, + unsigned long *best_diff_rate) +{ + long round_rate; + unsigned int diff_rate; + + round_rate = clk_round_rate(clk, rate); + if (round_rate < 0) + return (int)round_rate; + + diff_rate = abs(rate - round_rate); + if (diff_rate < *best_diff_rate) { + *best_clk = clk; + *best_diff_rate = diff_rate; + *best_rate = rate; + } + + return 0; +} + +static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev, + unsigned int bclk, unsigned int *mra) +{ + unsigned long clk_rate; + unsigned long lcm_rate; + unsigned long best_rate = 0; + unsigned long best_diff_rate = ~0; + unsigned int sysclk; + struct clk *best_clk = NULL; + int ret; + + /* For code simplification */ + if (!dev->sysclk) + sysclk = bclk; + else + sysclk = dev->sysclk; + + /* + * MCLK is Selected CLK / (2 * IMCKDIV), + * BCLK is Selected CLK / (2 * ISCKDIV); + * if IMCKDIV or ISCKDIV are 0, MCLK or BCLK = Selected CLK + */ + lcm_rate = lcm(sysclk, bclk); + if ((lcm_rate / sysclk % 2 == 1 && lcm_rate / sysclk > 2) || + (lcm_rate / bclk % 2 == 1 && lcm_rate / bclk > 2)) + lcm_rate *= 2; + + for (clk_rate = lcm_rate; + (clk_rate == sysclk || clk_rate / (sysclk * 2) <= GENMASK(5, 0)) && + (clk_rate == bclk || clk_rate / (bclk * 2) <= GENMASK(5, 0)); + clk_rate += lcm_rate) { + ret = mchp_i2s_mcc_clk_get_rate_diff(dev->gclk, clk_rate, + &best_clk, &best_rate, + &best_diff_rate); + if (ret) { + dev_err(dev->dev, "gclk error for rate %lu: %d", + clk_rate, ret); + } else { + if (!best_diff_rate) { + dev_dbg(dev->dev, "found perfect rate on gclk: %lu\n", + clk_rate); + break; + } + } + + ret = mchp_i2s_mcc_clk_get_rate_diff(dev->pclk, clk_rate, + &best_clk, &best_rate, + &best_diff_rate); + if (ret) { + dev_err(dev->dev, "pclk error for rate %lu: %d", + clk_rate, ret); + } else { + if (!best_diff_rate) { + dev_dbg(dev->dev, "found perfect rate on pclk: %lu\n", + clk_rate); + break; + } + } + } + + /* check if clocks returned only errors */ + if (!best_clk) { + dev_err(dev->dev, "unable to change rate to clocks\n"); + return -EINVAL; + } + + dev_dbg(dev->dev, "source CLK is %s with rate %lu, diff %lu\n", + best_clk == dev->pclk ? "pclk" : "gclk", + best_rate, best_diff_rate); + + /* set the rate */ + ret = clk_set_rate(best_clk, best_rate); + if (ret) { + dev_err(dev->dev, "unable to set rate %lu to %s: %d\n", + best_rate, best_clk == dev->pclk ? "PCLK" : "GCLK", + ret); + return ret; + } + + /* Configure divisors */ + if (dev->sysclk) + *mra |= MCHP_I2SMCC_MRA_IMCKDIV(best_rate / (2 * sysclk)); + *mra |= MCHP_I2SMCC_MRA_ISCKDIV(best_rate / (2 * bclk)); + + if (best_clk == dev->gclk) { + *mra |= MCHP_I2SMCC_MRA_SRCCLK_GCLK; + ret = clk_prepare(dev->gclk); + if (ret < 0) + dev_err(dev->dev, "unable to prepare GCLK: %d\n", ret); + else + dev->gclk_use = 1; + } else { + *mra |= MCHP_I2SMCC_MRA_SRCCLK_PCLK; + dev->gclk_use = 0; + } + + return 0; +} + +static int mchp_i2s_mcc_is_running(struct mchp_i2s_mcc_dev *dev) +{ + u32 sr; + + regmap_read(dev->regmap, MCHP_I2SMCC_SR, &sr); + return !!(sr & (MCHP_I2SMCC_SR_TXEN | MCHP_I2SMCC_SR_RXEN)); +} + +static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + u32 mra = 0; + u32 mrb = 0; + unsigned int channels = params_channels(params); + unsigned int frame_length = dev->frame_length; + unsigned int bclk_rate; + int set_divs = 0; + int ret; + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n", + __func__, params_rate(params), params_format(params), + params_width(params), params_channels(params)); + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + if (dev->tdm_slots) { + dev_err(dev->dev, "I2S with TDM is not supported\n"); + return -EINVAL; + } + mra |= MCHP_I2SMCC_MRA_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + if (dev->tdm_slots) { + dev_err(dev->dev, "Left-Justified with TDM is not supported\n"); + return -EINVAL; + } + mra |= MCHP_I2SMCC_MRA_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + mra |= MCHP_I2SMCC_MRA_FORMAT_TDM; + break; + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* cpu is BCLK and LRC master */ + mra |= MCHP_I2SMCC_MRA_MODE_MASTER; + if (dev->sysclk) + mra |= MCHP_I2SMCC_MRA_IMCKMODE_GEN; + set_divs = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /* cpu is BCLK master */ + mrb |= MCHP_I2SMCC_MRB_CLKSEL_INT; + set_divs = 1; + /* fall through */ + case SND_SOC_DAIFMT_CBM_CFM: + /* cpu is slave */ + mra |= MCHP_I2SMCC_MRA_MODE_SLAVE; + if (dev->sysclk) + dev_warn(dev->dev, "Unable to generate MCLK in Slave mode\n"); + break; + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + if (dev->fmt & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) { + switch (channels) { + case 1: + if (is_playback) + mra |= MCHP_I2SMCC_MRA_TXMONO; + else + mra |= MCHP_I2SMCC_MRA_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + if (!frame_length) + frame_length = 2 * params_physical_width(params); + } else if (dev->fmt & SND_SOC_DAIFMT_DSP_A) { + if (dev->tdm_slots) { + if (channels % 2 && channels * 2 <= dev->tdm_slots) { + /* + * Duplicate data for even-numbered channels + * to odd-numbered channels + */ + if (is_playback) + mra |= MCHP_I2SMCC_MRA_TXMONO; + else + mra |= MCHP_I2SMCC_MRA_RXMONO; + } + channels = dev->tdm_slots; + } + + mra |= MCHP_I2SMCC_MRA_NBCHAN(channels); + if (!frame_length) + frame_length = channels * MCHP_I2MCC_TDM_SLOT_WIDTH; + } + + /* + * We must have the same burst size configured + * in the DMA transfer and in out IP + */ + mrb |= MCHP_I2SMCC_MRB_DMACHUNK(channels); + if (is_playback) + dev->playback.maxburst = 1 << (fls(channels) - 1); + else + dev->capture.maxburst = 1 << (fls(channels) - 1); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_8_BITS; + break; + case SNDRV_PCM_FORMAT_S16_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_16_BITS; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_18_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_20_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_24_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_24_BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_32_BITS; + break; + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + /* + * If we are already running, the wanted setup must be + * the same with the one that's currently ongoing + */ + if (mchp_i2s_mcc_is_running(dev)) { + u32 mra_cur; + u32 mrb_cur; + + regmap_read(dev->regmap, MCHP_I2SMCC_MRA, &mra_cur); + regmap_read(dev->regmap, MCHP_I2SMCC_MRB, &mrb_cur); + if (mra != mra_cur || mrb != mrb_cur) + return -EINVAL; + + return 0; + } + + /* Save the number of channels to know what interrupts to enable */ + dev->channels = channels; + + if (set_divs) { + bclk_rate = frame_length * params_rate(params); + ret = mchp_i2s_mcc_config_divs(dev, bclk_rate, &mra); + if (ret) { + dev_err(dev->dev, "unable to configure the divisors: %d\n", + ret); + return ret; + } + } + + ret = regmap_write(dev->regmap, MCHP_I2SMCC_MRA, mra); + if (ret < 0) + return ret; + return regmap_write(dev->regmap, MCHP_I2SMCC_MRB, mrb); +} + +static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + long err; + + if (is_playback) { + err = wait_event_interruptible_timeout(dev->wq_txrdy, + dev->tx_rdy, + msecs_to_jiffies(500)); + } else { + err = wait_event_interruptible_timeout(dev->wq_rxrdy, + dev->rx_rdy, + msecs_to_jiffies(500)); + } + + if (err == 0) { + u32 idra; + + dev_warn_once(dev->dev, "Timeout waiting for %s\n", + is_playback ? "Tx ready" : "Rx ready"); + if (is_playback) + idra = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); + else + idra = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); + } + + if (!mchp_i2s_mcc_is_running(dev)) { + regmap_write(dev->regmap, MCHP_I2SMCC_CR, MCHP_I2SMCC_CR_CKDIS); + + if (dev->gclk_running) { + clk_disable_unprepare(dev->gclk); + dev->gclk_running = 0; + } + } + + return 0; +} + +static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 cr = 0; + u32 iera = 0; + u32 sr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (is_playback) + cr = MCHP_I2SMCC_CR_TXEN | MCHP_I2SMCC_CR_CKEN; + else + cr = MCHP_I2SMCC_CR_RXEN | MCHP_I2SMCC_CR_CKEN; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_read(dev->regmap, MCHP_I2SMCC_SR, &sr); + if (is_playback && (sr & MCHP_I2SMCC_SR_TXEN)) { + cr = MCHP_I2SMCC_CR_TXDIS; + dev->tx_rdy = 0; + /* + * Enable Tx Ready interrupts on all channels + * to assure all data is sent + */ + iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); + } else if (!is_playback && (sr & MCHP_I2SMCC_SR_RXEN)) { + cr = MCHP_I2SMCC_CR_RXDIS; + dev->rx_rdy = 0; + /* + * Enable Rx Ready interrupts on all channels + * to assure all data is received + */ + iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); + } + break; + default: + return -EINVAL; + } + + if ((cr & MCHP_I2SMCC_CR_CKEN) && dev->gclk_use && + !dev->gclk_running) { + err = clk_enable(dev->gclk); + if (err) { + dev_err_once(dev->dev, "failed to enable GCLK: %d\n", + err); + } else { + dev->gclk_running = 1; + } + } + + regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera); + regmap_write(dev->regmap, MCHP_I2SMCC_CR, cr); + + return 0; +} + +static int mchp_i2s_mcc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + /* Software reset the IP if it's not running */ + if (!mchp_i2s_mcc_is_running(dev)) { + return regmap_write(dev->regmap, MCHP_I2SMCC_CR, + MCHP_I2SMCC_CR_SWRST); + } + + return 0; +} + +static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .set_sysclk = mchp_i2s_mcc_set_sysclk, + .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio, + .startup = mchp_i2s_mcc_startup, + .trigger = mchp_i2s_mcc_trigger, + .hw_params = mchp_i2s_mcc_hw_params, + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, +}; + +static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + init_waitqueue_head(&dev->wq_txrdy); + init_waitqueue_head(&dev->wq_rxrdy); + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + + return 0; +} + +#define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 + +#define MCHP_I2SMCC_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mchp_i2s_mcc_dai = { + .probe = mchp_i2s_mcc_dai_probe, + .playback = { + .stream_name = "I2SMCC-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = MCHP_I2SMCC_RATES, + .formats = MCHP_I2SMCC_FORMATS, + }, + .capture = { + .stream_name = "I2SMCC-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = MCHP_I2SMCC_RATES, + .formats = MCHP_I2SMCC_FORMATS, + }, + .ops = &mchp_i2s_mcc_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver mchp_i2s_mcc_component = { + .name = "mchp-i2s-mcc", +}; + +#ifdef CONFIG_OF +static const struct of_device_id mchp_i2s_mcc_dt_ids[] = { + { + .compatible = "microchip,sam9x60-i2smcc", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mchp_i2s_mcc_dt_ids); +#endif + +static int mchp_i2s_mcc_probe(struct platform_device *pdev) +{ + struct mchp_i2s_mcc_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + u32 version; + int irq; + int err; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &mchp_i2s_mcc_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(&pdev->dev, irq, mchp_i2s_mcc_interrupt, 0, + dev_name(&pdev->dev), dev); + if (err) + return err; + + dev->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(dev->pclk)) { + err = PTR_ERR(dev->pclk); + dev_err(&pdev->dev, + "failed to get the peripheral clock: %d\n", err); + return err; + } + + /* Get the optional generated clock */ + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, + "generated clock not found: %d\n", err); + dev->gclk = NULL; + } + + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + err = clk_prepare_enable(dev->pclk); + if (err) { + dev_err(&pdev->dev, + "failed to enable the peripheral clock: %d\n", err); + return err; + } + + err = devm_snd_soc_register_component(&pdev->dev, + &mchp_i2s_mcc_component, + &mchp_i2s_mcc_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + dev->playback.addr = (dma_addr_t)mem->start + MCHP_I2SMCC_THR; + dev->capture.addr = (dma_addr_t)mem->start + MCHP_I2SMCC_RHR; + + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Get IP version. */ + regmap_read(dev->regmap, MCHP_I2SMCC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#lx\n", + version & MCHP_I2SMCC_VERSION_MASK); + + return 0; +} + +static int mchp_i2s_mcc_remove(struct platform_device *pdev) +{ + struct mchp_i2s_mcc_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver mchp_i2s_mcc_driver = { + .driver = { + .name = "mchp_i2s_mcc", + .of_match_table = of_match_ptr(mchp_i2s_mcc_dt_ids), + }, + .probe = mchp_i2s_mcc_probe, + .remove = mchp_i2s_mcc_remove, +}; +module_platform_driver(mchp_i2s_mcc_driver); + +MODULE_DESCRIPTION("Microchip I2S Multi-Channel Controller driver"); +MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 419114edfd57..9981d40ef45b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -94,6 +94,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_JZ4725B_CODEC select SND_SOC_LM4857 if I2C select SND_SOC_LM49453 if I2C + select SND_SOC_LOCHNAGAR_SC if MFD_LOCHNAGAR select SND_SOC_MAX98088 if I2C select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C @@ -179,8 +180,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC23_SPI if SPI_MASTER select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC31XX if I2C - select SND_SOC_TLV320AIC32X4_I2C if I2C - select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER + select SND_SOC_TLV320AIC32X4_I2C if I2C && COMMON_CLK + select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER && COMMON_CLK select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -688,6 +689,13 @@ config SND_SOC_ISABELLE config SND_SOC_LM49453 tristate +config SND_SOC_LOCHNAGAR_SC + tristate "Lochnagar Sound Card" + depends on MFD_LOCHNAGAR + help + This driver support the sound card functionality of the Cirrus + Logic Lochnagar audio development board. + config SND_SOC_MAX98088 tristate "Maxim MAX98088/9 Low-Power, Stereo Audio Codec" depends on I2C @@ -1097,15 +1105,18 @@ config SND_SOC_TLV320AIC31XX config SND_SOC_TLV320AIC32X4 tristate + depends on COMMON_CLK config SND_SOC_TLV320AIC32X4_I2C tristate "Texas Instruments TLV320AIC32x4 audio CODECs - I2C" depends on I2C + depends on COMMON_CLK select SND_SOC_TLV320AIC32X4 config SND_SOC_TLV320AIC32X4_SPI tristate "Texas Instruments TLV320AIC32x4 audio CODECs - SPI" depends on SPI_MASTER + depends on COMMON_CLK select SND_SOC_TLV320AIC32X4 config SND_SOC_TLV320AIC3X diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index aab2ad95a137..aa7720a7a0aa 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -91,6 +91,7 @@ snd-soc-jz4725b-codec-objs := jz4725b.o snd-soc-l3-objs := l3.o snd-soc-lm4857-objs := lm4857.o snd-soc-lm49453-objs := lm49453.o +snd-soc-lochnagar-sc-objs := lochnagar-sc.o snd-soc-max9759-objs := max9759.o snd-soc-max9768-objs := max9768.o snd-soc-max98088-objs := max98088.o @@ -192,7 +193,7 @@ snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o -snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o +snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o tlv320aic32x4-clk.o snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o snd-soc-tlv320aic32x4-spi-objs := tlv320aic32x4-spi.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o @@ -364,6 +365,7 @@ obj-$(CONFIG_SND_SOC_JZ4725B_CODEC) += snd-soc-jz4725b-codec.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o +obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC) += snd-soc-lochnagar-sc.o obj-$(CONFIG_SND_SOC_MAX9759) += snd-soc-max9759.o obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 03bbbcd3b6c1..87616b126018 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -2129,6 +2129,7 @@ static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) dev_err(dai->component->dev, "%s: ERROR: The device is either a master or a slave.\n", __func__); + /* fall through */ default: dev_err(dai->component->dev, "%s: ERROR: Unsupporter master mask 0x%x\n", diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index fd2bd74024c1..80da3cd73e04 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -464,6 +464,13 @@ static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) return snd_soc_component_write(component, CS42L51_DAC_OUT_CTL, reg); } +static int cs42l51_of_xlate_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + /* return dai id 0, whatever the endpoint index */ + return 0; +} + static const struct snd_soc_dai_ops cs42l51_dai_ops = { .hw_params = cs42l51_hw_params, .set_sysclk = cs42l51_set_dai_sysclk, @@ -526,6 +533,7 @@ static const struct snd_soc_component_driver soc_component_device_cs42l51 = { .num_dapm_widgets = ARRAY_SIZE(cs42l51_dapm_widgets), .dapm_routes = cs42l51_routes, .num_dapm_routes = ARRAY_SIZE(cs42l51_routes), + .of_xlate_dai_id = cs42l51_of_xlate_dai_id, .idle_bias_on = 1, .use_pmdown_time = 1, .endianness = 1, diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c index b16832a6a9af..eebbf02e1c39 100644 --- a/sound/soc/codecs/cs47l24.c +++ b/sound/soc/codecs/cs47l24.c @@ -75,7 +75,9 @@ static int cs47l24_adsp_power_ev(struct snd_soc_dapm_widget *w, v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; - return wm_adsp2_early_event(w, kcontrol, event, v); + wm_adsp2_set_dspclk(w, v); + + return wm_adsp_early_event(w, kcontrol, event); } static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c index 121a8190f93e..5f5fa3416af3 100644 --- a/sound/soc/codecs/da7219.c +++ b/sound/soc/codecs/da7219.c @@ -797,6 +797,7 @@ static int da7219_dai_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct clk *bclk = da7219->dai_clks[DA7219_DAI_BCLK_IDX]; u8 pll_ctrl, pll_status; int i = 0, ret; bool srm_lock = false; @@ -805,11 +806,11 @@ static int da7219_dai_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMU: if (da7219->master) { /* Enable DAI clks for master mode */ - if (da7219->dai_clks) { - ret = clk_prepare_enable(da7219->dai_clks); + if (bclk) { + ret = clk_prepare_enable(bclk); if (ret) { dev_err(component->dev, - "Failed to enable dai_clks\n"); + "Failed to enable DAI clks\n"); return ret; } } else { @@ -852,8 +853,8 @@ static int da7219_dai_event(struct snd_soc_dapm_widget *w, /* Disable DAI clks if in master mode */ if (da7219->master) { - if (da7219->dai_clks) - clk_disable_unprepare(da7219->dai_clks); + if (bclk) + clk_disable_unprepare(bclk); else snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, @@ -1385,17 +1386,50 @@ static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return 0; } +static int da7219_set_bclks_per_wclk(struct snd_soc_component *component, + unsigned long factor) +{ + u8 bclks_per_wclk; + + switch (factor) { + case 32: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; + break; + case 64: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; + break; + case 128: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_128; + break; + case 256: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_256; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_BCLKS_PER_WCLK_MASK, + bclks_per_wclk); + + return 0; +} + static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_component *component = dai->component; struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct clk *wclk = da7219->dai_clks[DA7219_DAI_WCLK_IDX]; + struct clk *bclk = da7219->dai_clks[DA7219_DAI_BCLK_IDX]; unsigned int ch_mask; - u8 dai_bclks_per_wclk, slot_offset; + unsigned long sr, bclk_rate; + u8 slot_offset; u16 offset; __le16 dai_offset; u32 frame_size; + int ret; /* No channels enabled so disable TDM */ if (!tx_mask) { @@ -1432,28 +1466,26 @@ static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, */ if (da7219->master) { frame_size = slots * slot_width; - switch (frame_size) { - case 32: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; - break; - case 64: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; - break; - case 128: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_128; - break; - case 256: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_256; - break; - default: - dev_err(component->dev, "Invalid frame size %d\n", - frame_size); - return -EINVAL; - } - snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, - DA7219_DAI_BCLKS_PER_WCLK_MASK, - dai_bclks_per_wclk); + if (bclk) { + sr = clk_get_rate(wclk); + bclk_rate = sr * frame_size; + ret = clk_set_rate(bclk, bclk_rate); + if (ret) { + dev_err(component->dev, + "Failed to set TDM BCLK rate %lu: %d\n", + bclk_rate, ret); + return ret; + } + } else { + ret = da7219_set_bclks_per_wclk(component, frame_size); + if (ret) { + dev_err(component->dev, + "Failed to set TDM BCLKs per WCLK %d: %d\n", + frame_size, ret); + return ret; + } + } } dai_offset = cpu_to_le16(offset); @@ -1471,44 +1503,12 @@ static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } -static int da7219_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int da7219_set_sr(struct snd_soc_component *component, + unsigned long rate) { - struct snd_soc_component *component = dai->component; - struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); - u8 dai_ctrl = 0, dai_bclks_per_wclk = 0, fs; - unsigned int channels; - int word_len = params_width(params); - int frame_size; - - switch (word_len) { - case 16: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S16_LE; - break; - case 20: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S20_LE; - break; - case 24: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S24_LE; - break; - case 32: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S32_LE; - break; - default: - return -EINVAL; - } - - channels = params_channels(params); - if ((channels < 1) || (channels > DA7219_DAI_CH_NUM_MAX)) { - dev_err(component->dev, - "Invalid number of channels, only 1 to %d supported\n", - DA7219_DAI_CH_NUM_MAX); - return -EINVAL; - } - dai_ctrl |= channels << DA7219_DAI_CH_NUM_SHIFT; + u8 fs; - switch (params_rate(params)) { + switch (rate) { case 8000: fs = DA7219_SR_8000; break; @@ -1546,28 +1546,103 @@ static int da7219_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + snd_soc_component_write(component, DA7219_SR, fs); + + return 0; +} + +static int da7219_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct clk *wclk = da7219->dai_clks[DA7219_DAI_WCLK_IDX]; + struct clk *bclk = da7219->dai_clks[DA7219_DAI_BCLK_IDX]; + u8 dai_ctrl = 0; + unsigned int channels; + unsigned long sr, bclk_rate; + int word_len = params_width(params); + int frame_size, ret; + + switch (word_len) { + case 16: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S16_LE; + break; + case 20: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S20_LE; + break; + case 24: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S24_LE; + break; + case 32: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S32_LE; + break; + default: + return -EINVAL; + } + + channels = params_channels(params); + if ((channels < 1) || (channels > DA7219_DAI_CH_NUM_MAX)) { + dev_err(component->dev, + "Invalid number of channels, only 1 to %d supported\n", + DA7219_DAI_CH_NUM_MAX); + return -EINVAL; + } + dai_ctrl |= channels << DA7219_DAI_CH_NUM_SHIFT; + + sr = params_rate(params); + if (da7219->master && wclk) { + ret = clk_set_rate(wclk, sr); + if (ret) { + dev_err(component->dev, + "Failed to set WCLK SR %lu: %d\n", sr, ret); + return ret; + } + } else { + ret = da7219_set_sr(component, sr); + if (ret) { + dev_err(component->dev, + "Failed to set SR %lu: %d\n", sr, ret); + return ret; + } + } + /* * If we're master, then we have a limited set of BCLK rates we * support. For slave mode this isn't the case and the codec can detect * the BCLK rate automatically. */ if (da7219->master && !da7219->tdm_en) { - frame_size = word_len * 2; - if (frame_size <= 32) - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; + if ((word_len * DA7219_DAI_CH_NUM_MAX) <= 32) + frame_size = 32; else - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; - - snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, - DA7219_DAI_BCLKS_PER_WCLK_MASK, - dai_bclks_per_wclk); + frame_size = 64; + + if (bclk) { + bclk_rate = frame_size * sr; + ret = clk_set_rate(bclk, bclk_rate); + if (ret) { + dev_err(component->dev, + "Failed to set BCLK rate %lu: %d\n", + bclk_rate, ret); + return ret; + } + } else { + ret = da7219_set_bclks_per_wclk(component, frame_size); + if (ret) { + dev_err(component->dev, + "Failed to set BCLKs per WCLK %d: %d\n", + frame_size, ret); + return ret; + } + } } snd_soc_component_update_bits(component, DA7219_DAI_CTRL, DA7219_DAI_WORD_LENGTH_MASK | DA7219_DAI_CH_NUM_MASK, dai_ctrl); - snd_soc_component_write(component, DA7219_SR, fs); return 0; } @@ -1672,11 +1747,14 @@ static struct da7219_pdata *da7219_fw_to_pdata(struct snd_soc_component *compone pdata->wakeup_source = device_property_read_bool(dev, "wakeup-source"); - pdata->dai_clks_name = "da7219-dai-clks"; - if (device_property_read_string(dev, "clock-output-names", - &pdata->dai_clks_name)) - dev_warn(dev, "Using default clk name: %s\n", - pdata->dai_clks_name); + pdata->dai_clk_names[DA7219_DAI_WCLK_IDX] = "da7219-dai-wclk"; + pdata->dai_clk_names[DA7219_DAI_BCLK_IDX] = "da7219-dai-bclk"; + if (device_property_read_string_array(dev, "clock-output-names", + pdata->dai_clk_names, + DA7219_DAI_NUM_CLKS) < 0) + dev_warn(dev, "Using default DAI clk names: %s, %s\n", + pdata->dai_clk_names[DA7219_DAI_WCLK_IDX], + pdata->dai_clk_names[DA7219_DAI_BCLK_IDX]); if (device_property_read_u32(dev, "dlg,micbias-lvl", &of_val32) >= 0) pdata->micbias_lvl = da7219_fw_micbias_lvl(dev, of_val32); @@ -1793,12 +1871,16 @@ static int da7219_handle_supplies(struct snd_soc_component *component) } #ifdef CONFIG_COMMON_CLK -static int da7219_dai_clks_prepare(struct clk_hw *hw) +static int da7219_wclk_prepare(struct clk_hw *hw) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; + if (!da7219->master) + return -EINVAL; + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, DA7219_DAI_CLK_EN_MASK, DA7219_DAI_CLK_EN_MASK); @@ -1806,36 +1888,48 @@ static int da7219_dai_clks_prepare(struct clk_hw *hw) return 0; } -static void da7219_dai_clks_unprepare(struct clk_hw *hw) +static void da7219_wclk_unprepare(struct clk_hw *hw) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; + if (!da7219->master) + return; + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, DA7219_DAI_CLK_EN_MASK, 0); } -static int da7219_dai_clks_is_prepared(struct clk_hw *hw) +static int da7219_wclk_is_prepared(struct clk_hw *hw) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; u8 clk_reg; + if (!da7219->master) + return -EINVAL; + clk_reg = snd_soc_component_read32(component, DA7219_DAI_CLK_MODE); return !!(clk_reg & DA7219_DAI_CLK_EN_MASK); } -static unsigned long da7219_dai_clks_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) +static unsigned long da7219_wclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; u8 fs = snd_soc_component_read32(component, DA7219_SR); + if (!da7219->master) + return 0; + switch (fs & DA7219_SR_MASK) { case DA7219_SR_8000: return 8000; @@ -1864,11 +1958,151 @@ static unsigned long da7219_dai_clks_recalc_rate(struct clk_hw *hw, } } -static const struct clk_ops da7219_dai_clks_ops = { - .prepare = da7219_dai_clks_prepare, - .unprepare = da7219_dai_clks_unprepare, - .is_prepared = da7219_dai_clks_is_prepared, - .recalc_rate = da7219_dai_clks_recalc_rate, +static long da7219_wclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); + + if (!da7219->master) + return -EINVAL; + + if (rate < 11025) + return 8000; + else if (rate < 12000) + return 11025; + else if (rate < 16000) + return 12000; + else if (rate < 22050) + return 16000; + else if (rate < 24000) + return 22050; + else if (rate < 32000) + return 24000; + else if (rate < 44100) + return 32000; + else if (rate < 48000) + return 44100; + else if (rate < 88200) + return 48000; + else if (rate < 96000) + return 88200; + else + return 96000; +} + +static int da7219_wclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); + struct snd_soc_component *component = da7219->component; + + if (!da7219->master) + return -EINVAL; + + return da7219_set_sr(component, rate); +} + +static unsigned long da7219_bclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_BCLK_IDX]); + struct snd_soc_component *component = da7219->component; + u8 bclks_per_wclk = snd_soc_component_read32(component, + DA7219_DAI_CLK_MODE); + + if (!da7219->master) + return 0; + + switch (bclks_per_wclk & DA7219_DAI_BCLKS_PER_WCLK_MASK) { + case DA7219_DAI_BCLKS_PER_WCLK_32: + return parent_rate * 32; + case DA7219_DAI_BCLKS_PER_WCLK_64: + return parent_rate * 64; + case DA7219_DAI_BCLKS_PER_WCLK_128: + return parent_rate * 128; + case DA7219_DAI_BCLKS_PER_WCLK_256: + return parent_rate * 256; + default: + return 0; + } +} + +static unsigned long da7219_bclk_get_factor(unsigned long rate, + unsigned long parent_rate) +{ + unsigned long factor; + + factor = rate / parent_rate; + if (factor < 64) + return 32; + else if (factor < 128) + return 64; + else if (factor < 256) + return 128; + else + return 256; +} + +static long da7219_bclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_BCLK_IDX]); + unsigned long factor; + + if (!*parent_rate || !da7219->master) + return -EINVAL; + + /* + * We don't allow changing the parent rate as some BCLK rates can be + * derived from multiple parent WCLK rates (BCLK rates are set as a + * multiplier of WCLK in HW). We just do some rounding down based on the + * parent WCLK rate set and find the appropriate multiplier of BCLK to + * get the rounded down BCLK value. + */ + factor = da7219_bclk_get_factor(rate, *parent_rate); + + return *parent_rate * factor; +} + +static int da7219_bclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_BCLK_IDX]); + struct snd_soc_component *component = da7219->component; + unsigned long factor; + + if (!da7219->master) + return -EINVAL; + + factor = da7219_bclk_get_factor(rate, parent_rate); + + return da7219_set_bclks_per_wclk(component, factor); +} + +static const struct clk_ops da7219_dai_clk_ops[DA7219_DAI_NUM_CLKS] = { + [DA7219_DAI_WCLK_IDX] = { + .prepare = da7219_wclk_prepare, + .unprepare = da7219_wclk_unprepare, + .is_prepared = da7219_wclk_is_prepared, + .recalc_rate = da7219_wclk_recalc_rate, + .round_rate = da7219_wclk_round_rate, + .set_rate = da7219_wclk_set_rate, + }, + [DA7219_DAI_BCLK_IDX] = { + .recalc_rate = da7219_bclk_recalc_rate, + .round_rate = da7219_bclk_round_rate, + .set_rate = da7219_bclk_set_rate, + }, }; static int da7219_register_dai_clks(struct snd_soc_component *component) @@ -1876,47 +2110,81 @@ static int da7219_register_dai_clks(struct snd_soc_component *component) struct device *dev = component->dev; struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); struct da7219_pdata *pdata = da7219->pdata; - struct clk_init_data init = {}; - struct clk *dai_clks; - struct clk_lookup *dai_clks_lookup; const char *parent_name; + int i, ret; - if (da7219->mclk) { - parent_name = __clk_get_name(da7219->mclk); - init.parent_names = &parent_name; - init.num_parents = 1; - } else { - init.parent_names = NULL; - init.num_parents = 0; - } + for (i = 0; i < DA7219_DAI_NUM_CLKS; ++i) { + struct clk_init_data init = {}; + struct clk *dai_clk; + struct clk_lookup *dai_clk_lookup; + struct clk_hw *dai_clk_hw = &da7219->dai_clks_hw[i]; - init.name = pdata->dai_clks_name; - init.ops = &da7219_dai_clks_ops; - init.flags = CLK_GET_RATE_NOCACHE; - da7219->dai_clks_hw.init = &init; + switch (i) { + case DA7219_DAI_WCLK_IDX: + /* + * If we can, make MCLK the parent of WCLK to ensure + * it's enabled as required. + */ + if (da7219->mclk) { + parent_name = __clk_get_name(da7219->mclk); + init.parent_names = &parent_name; + init.num_parents = 1; + } else { + init.parent_names = NULL; + init.num_parents = 0; + } + break; + case DA7219_DAI_BCLK_IDX: + /* Make WCLK the parent of BCLK */ + parent_name = __clk_get_name(da7219->dai_clks[DA7219_DAI_WCLK_IDX]); + init.parent_names = &parent_name; + init.num_parents = 1; + break; + default: + dev_err(dev, "Invalid clock index\n"); + ret = -EINVAL; + goto err; + } - dai_clks = devm_clk_register(dev, &da7219->dai_clks_hw); - if (IS_ERR(dai_clks)) { - dev_warn(dev, "Failed to register DAI clocks: %ld\n", - PTR_ERR(dai_clks)); - return PTR_ERR(dai_clks); - } - da7219->dai_clks = dai_clks; + init.name = pdata->dai_clk_names[i]; + init.ops = &da7219_dai_clk_ops[i]; + init.flags = CLK_GET_RATE_NOCACHE | CLK_SET_RATE_GATE; + dai_clk_hw->init = &init; + + dai_clk = devm_clk_register(dev, dai_clk_hw); + if (IS_ERR(dai_clk)) { + dev_warn(dev, "Failed to register %s: %ld\n", + init.name, PTR_ERR(dai_clk)); + ret = PTR_ERR(dai_clk); + goto err; + } + da7219->dai_clks[i] = dai_clk; - /* If we're using DT, then register as provider accordingly */ - if (dev->of_node) { - devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, - &da7219->dai_clks_hw); - } else { - dai_clks_lookup = clkdev_create(dai_clks, pdata->dai_clks_name, - "%s", dev_name(dev)); - if (!dai_clks_lookup) - return -ENOMEM; - else - da7219->dai_clks_lookup = dai_clks_lookup; + /* If we're using DT, then register as provider accordingly */ + if (dev->of_node) { + devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + dai_clk_hw); + } else { + dai_clk_lookup = clkdev_create(dai_clk, init.name, + "%s", dev_name(dev)); + if (!dai_clk_lookup) { + ret = -ENOMEM; + goto err; + } else { + da7219->dai_clks_lookup[i] = dai_clk_lookup; + } + } } return 0; + +err: + do { + if (da7219->dai_clks_lookup[i]) + clkdev_drop(da7219->dai_clks_lookup[i]); + } while (i-- > 0); + + return ret; } #else static inline int da7219_register_dai_clks(struct snd_soc_component *component) @@ -2080,12 +2348,15 @@ err_disable_reg: static void da7219_remove(struct snd_soc_component *component) { struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int i; da7219_aad_exit(component); #ifdef CONFIG_COMMON_CLK - if (da7219->dai_clks_lookup) - clkdev_drop(da7219->dai_clks_lookup); + for (i = DA7219_DAI_NUM_CLKS - 1; i >= 0; --i) { + if (da7219->dai_clks_lookup[i]) + clkdev_drop(da7219->dai_clks_lookup[i]); + } #endif /* Supplies */ diff --git a/sound/soc/codecs/da7219.h b/sound/soc/codecs/da7219.h index 018819c631fb..f3b180bc986f 100644 --- a/sound/soc/codecs/da7219.h +++ b/sound/soc/codecs/da7219.h @@ -820,10 +820,10 @@ struct da7219_priv { struct mutex pll_lock; #ifdef CONFIG_COMMON_CLK - struct clk_hw dai_clks_hw; + struct clk_hw dai_clks_hw[DA7219_DAI_NUM_CLKS]; #endif - struct clk_lookup *dai_clks_lookup; - struct clk *dai_clks; + struct clk_lookup *dai_clks_lookup[DA7219_DAI_NUM_CLKS]; + struct clk *dai_clks[DA7219_DAI_NUM_CLKS]; struct clk *mclk; unsigned int mclk_rate; diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 6d4a323f786b..ec2770b3f77d 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -43,6 +43,7 @@ struct es8316_priv { unsigned int sysclk; unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS]; struct snd_pcm_hw_constraint_list sysclk_constraints; + bool jd_inverted; }; /* @@ -577,6 +578,9 @@ static irqreturn_t es8316_irq(int irq, void *data) if (!es8316->jack) goto out; + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; + dev_dbg(comp->dev, "gpio flags %#04x\n", flags); if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) { /* Jack removed, or spurious IRQ? */ @@ -592,6 +596,8 @@ static irqreturn_t es8316_irq(int irq, void *data) /* Jack inserted, determine type */ es8316_enable_micbias_for_mic_gnd_short_detect(comp); regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; dev_dbg(comp->dev, "gpio flags %#04x\n", flags); if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) { /* Jack unplugged underneath us */ @@ -633,6 +639,14 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, { struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); + /* + * Init es8316->jd_inverted here and not in the probe, as we cannot + * guarantee that the bytchr-es8316 driver, which might set this + * property, will probe before us. + */ + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + mutex_lock(&es8316->lock); es8316->jack = jack; diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index ffecdaaa8cf2..f889d94c8e3c 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -38,6 +38,9 @@ static void hdac_hda_dai_close(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); static int hdac_hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); +static int hdac_hda_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); static int hdac_hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); static int hdac_hda_dai_set_tdm_slot(struct snd_soc_dai *dai, @@ -50,6 +53,7 @@ static const struct snd_soc_dai_ops hdac_hda_dai_ops = { .startup = hdac_hda_dai_open, .shutdown = hdac_hda_dai_close, .prepare = hdac_hda_dai_prepare, + .hw_params = hdac_hda_dai_hw_params, .hw_free = hdac_hda_dai_hw_free, .set_tdm_slot = hdac_hda_dai_set_tdm_slot, }; @@ -139,6 +143,39 @@ static int hdac_hda_dai_set_tdm_slot(struct snd_soc_dai *dai, return 0; } +static int hdac_hda_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct hdac_hda_priv *hda_pvt; + unsigned int format_val; + unsigned int maxbps; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + maxbps = dai->driver->playback.sig_bits; + else + maxbps = dai->driver->capture.sig_bits; + + hda_pvt = snd_soc_component_get_drvdata(component); + format_val = snd_hdac_calc_stream_format(params_rate(params), + params_channels(params), + params_format(params), + maxbps, + 0); + if (!format_val) { + dev_err(dai->dev, + "invalid format_val, rate=%d, ch=%d, format=%d, maxbps=%d\n", + params_rate(params), params_channels(params), + params_format(params), maxbps); + + return -EINVAL; + } + + hda_pvt->pcm[dai->id].format_val[substream->stream] = format_val; + return 0; +} + static int hdac_hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -162,10 +199,9 @@ static int hdac_hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_component *component = dai->component; + struct hda_pcm_stream *hda_stream; struct hdac_hda_priv *hda_pvt; - struct snd_pcm_runtime *runtime = substream->runtime; struct hdac_device *hdev; - struct hda_pcm_stream *hda_stream; unsigned int format_val; struct hda_pcm *pcm; unsigned int stream; @@ -179,19 +215,8 @@ static int hdac_hda_dai_prepare(struct snd_pcm_substream *substream, hda_stream = &pcm->stream[substream->stream]; - format_val = snd_hdac_calc_stream_format(runtime->rate, - runtime->channels, - runtime->format, - hda_stream->maxbps, - 0); - if (!format_val) { - dev_err(&hdev->dev, - "invalid format_val, rate=%d, ch=%d, format=%d\n", - runtime->rate, runtime->channels, runtime->format); - return -EINVAL; - } - stream = hda_pvt->pcm[dai->id].stream_tag[substream->stream]; + format_val = hda_pvt->pcm[dai->id].format_val[substream->stream]; ret = snd_hda_codec_prepare(&hda_pvt->codec, hda_stream, stream, format_val, substream); diff --git a/sound/soc/codecs/hdac_hda.h b/sound/soc/codecs/hdac_hda.h index e444ef593360..6b1bd4f428e7 100644 --- a/sound/soc/codecs/hdac_hda.h +++ b/sound/soc/codecs/hdac_hda.h @@ -8,6 +8,7 @@ struct hdac_hda_pcm { int stream_tag[2]; + unsigned int format_val[2]; }; struct hdac_hda_priv { diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index e5b6769b9797..35df73e42cbc 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -484,9 +484,6 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, params_width(params), params_rate(params), params_channels(params)); - if (params_width(params) > 24) - params->msbits = 24; - ret = snd_pcm_create_iec958_consumer_hw_params(params, hp.iec.status, sizeof(hp.iec.status)); if (ret < 0) { @@ -529,73 +526,71 @@ static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, { struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); struct hdmi_codec_daifmt cf = { 0 }; - int ret = 0; dev_dbg(dai->dev, "%s()\n", __func__); - if (dai->id == DAI_ID_SPDIF) { - cf.fmt = HDMI_SPDIF; - } else { - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - cf.bit_clk_master = 1; - cf.frame_clk_master = 1; - break; - case SND_SOC_DAIFMT_CBS_CFM: - cf.frame_clk_master = 1; - break; - case SND_SOC_DAIFMT_CBM_CFS: - cf.bit_clk_master = 1; - break; - case SND_SOC_DAIFMT_CBS_CFS: - break; - default: - return -EINVAL; - } + if (dai->id == DAI_ID_SPDIF) + return 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cf.bit_clk_master = 1; + cf.frame_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + cf.frame_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + cf.bit_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - break; - case SND_SOC_DAIFMT_NB_IF: - cf.frame_clk_inv = 1; - break; - case SND_SOC_DAIFMT_IB_NF: - cf.bit_clk_inv = 1; - break; - case SND_SOC_DAIFMT_IB_IF: - cf.frame_clk_inv = 1; - cf.bit_clk_inv = 1; - break; - } + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + cf.frame_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + cf.bit_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_IF: + cf.frame_clk_inv = 1; + cf.bit_clk_inv = 1; + break; + } - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - cf.fmt = HDMI_I2S; - break; - case SND_SOC_DAIFMT_DSP_A: - cf.fmt = HDMI_DSP_A; - break; - case SND_SOC_DAIFMT_DSP_B: - cf.fmt = HDMI_DSP_B; - break; - case SND_SOC_DAIFMT_RIGHT_J: - cf.fmt = HDMI_RIGHT_J; - break; - case SND_SOC_DAIFMT_LEFT_J: - cf.fmt = HDMI_LEFT_J; - break; - case SND_SOC_DAIFMT_AC97: - cf.fmt = HDMI_AC97; - break; - default: - dev_err(dai->dev, "Invalid DAI interface format\n"); - return -EINVAL; - } + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cf.fmt = HDMI_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + cf.fmt = HDMI_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + cf.fmt = HDMI_DSP_B; + break; + case SND_SOC_DAIFMT_RIGHT_J: + cf.fmt = HDMI_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + cf.fmt = HDMI_LEFT_J; + break; + case SND_SOC_DAIFMT_AC97: + cf.fmt = HDMI_AC97; + break; + default: + dev_err(dai->dev, "Invalid DAI interface format\n"); + return -EINVAL; } hcp->daifmt[dai->id] = cf; - return ret; + return 0; } static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) @@ -792,8 +787,10 @@ static int hdmi_codec_probe(struct platform_device *pdev) i++; } - if (hcd->spdif) + if (hcd->spdif) { hcp->daidrv[i] = hdmi_spdif_dai; + hcp->daifmt[DAI_ID_SPDIF].fmt = HDMI_SPDIF; + } dev_set_drvdata(dev, hcp); diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c new file mode 100644 index 000000000000..3209b39e46af --- /dev/null +++ b/sound/soc/codecs/lochnagar-sc.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Lochnagar sound card driver +// +// Copyright (c) 2017-2019 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. +// +// Author: Charles Keepax <ckeepax@opensource.cirrus.com> +// Piotr Stankiewicz <piotrs@opensource.cirrus.com> + +#include <linux/clk.h> +#include <linux/module.h> +#include <sound/soc.h> + +#include <linux/mfd/lochnagar.h> +#include <linux/mfd/lochnagar1_regs.h> +#include <linux/mfd/lochnagar2_regs.h> + +struct lochnagar_sc_priv { + struct clk *mclk; +}; + +static const struct snd_soc_dapm_widget lochnagar_sc_widgets[] = { + SND_SOC_DAPM_LINE("Line Jack", NULL), + SND_SOC_DAPM_LINE("USB Audio", NULL), +}; + +static const struct snd_soc_dapm_route lochnagar_sc_routes[] = { + { "Line Jack", NULL, "AIF1 Playback" }, + { "AIF1 Capture", NULL, "Line Jack" }, + + { "USB Audio", NULL, "USB1 Playback" }, + { "USB Audio", NULL, "USB2 Playback" }, + { "USB1 Capture", NULL, "USB Audio" }, + { "USB2 Capture", NULL, "USB Audio" }, +}; + +static const unsigned int lochnagar_sc_chan_vals[] = { + 4, 8, +}; + +static const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = { + .count = ARRAY_SIZE(lochnagar_sc_chan_vals), + .list = lochnagar_sc_chan_vals, +}; + +static const unsigned int lochnagar_sc_rate_vals[] = { + 8000, 16000, 24000, 32000, 48000, 96000, 192000, + 22050, 44100, 88200, 176400, +}; + +static const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = { + .count = ARRAY_SIZE(lochnagar_sc_rate_vals), + .list = lochnagar_sc_rate_vals, +}; + +static int lochnagar_sc_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval range = { + .min = 8000, + .max = 24576000 / hw_param_interval(params, rule->deps[0])->max, + }; + + return snd_interval_refine(hw_param_interval(params, rule->var), + &range); +} + +static int lochnagar_sc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &lochnagar_sc_rate_constraint); + if (ret) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + lochnagar_sc_hw_rule_rate, priv, + SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); +} + +static int lochnagar_sc_line_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); + int ret; + + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + + ret = lochnagar_sc_startup(substream, dai); + if (ret) + return ret; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &lochnagar_sc_chan_constraint); +} + +static void lochnagar_sc_line_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); + + clk_disable_unprepare(priv->mclk); +} + +static int lochnagar_sc_check_fmt(struct snd_soc_dai *dai, unsigned int fmt, + unsigned int tar) +{ + tar |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + + if ((fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) != tar) + return -EINVAL; + + return 0; +} + +static int lochnagar_sc_set_line_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBS_CFS); +} + +static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBM_CFM); +} + +static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, +}; + +static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, +}; + +static struct snd_soc_dai_driver lochnagar_sc_dai[] = { + { + .name = "lochnagar-line", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 4, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 4, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &lochnagar_sc_line_ops, + .symmetric_rates = true, + .symmetric_samplebits = true, + }, + { + .name = "lochnagar-usb1", + .playback = { + .stream_name = "USB1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "USB1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &lochnagar_sc_usb_ops, + .symmetric_rates = true, + .symmetric_samplebits = true, + }, + { + .name = "lochnagar-usb2", + .playback = { + .stream_name = "USB2 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "USB2 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &lochnagar_sc_usb_ops, + .symmetric_rates = true, + .symmetric_samplebits = true, + }, +}; + +static const struct snd_soc_component_driver lochnagar_sc_driver = { + .non_legacy_dai_naming = 1, + + .dapm_widgets = lochnagar_sc_widgets, + .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets), + .dapm_routes = lochnagar_sc_routes, + .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes), +}; + +static int lochnagar_sc_probe(struct platform_device *pdev) +{ + struct lochnagar_sc_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(priv->mclk)) { + ret = PTR_ERR(priv->mclk); + dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, priv); + + return devm_snd_soc_register_component(&pdev->dev, + &lochnagar_sc_driver, + lochnagar_sc_dai, + ARRAY_SIZE(lochnagar_sc_dai)); +} + +static const struct of_device_id lochnagar_of_match[] = { + { .compatible = "cirrus,lochnagar2-soundcard" }, + {} +}; +MODULE_DEVICE_TABLE(of, lochnagar_of_match); + +static struct platform_driver lochnagar_sc_codec_driver = { + .driver = { + .name = "lochnagar-soundcard", + .of_match_table = of_match_ptr(lochnagar_of_match), + }, + + .probe = lochnagar_sc_probe, +}; +module_platform_driver(lochnagar_sc_codec_driver); + +MODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver"); +MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:lochnagar-soundcard"); diff --git a/sound/soc/codecs/nau8810.c b/sound/soc/codecs/nau8810.c index bfd74b86c9d2..dd82c65cfa7f 100644 --- a/sound/soc/codecs/nau8810.c +++ b/sound/soc/codecs/nau8810.c @@ -411,9 +411,9 @@ static const struct snd_soc_dapm_widget nau8810_dapm_widgets[] = { SND_SOC_DAPM_MIXER("Mono Mixer", NAU8810_REG_POWER3, NAU8810_MOUTMX_EN_SFT, 0, &nau8810_mono_mixer_controls[0], ARRAY_SIZE(nau8810_mono_mixer_controls)), - SND_SOC_DAPM_DAC("DAC", "HiFi Playback", NAU8810_REG_POWER3, + SND_SOC_DAPM_DAC("DAC", "Playback", NAU8810_REG_POWER3, NAU8810_DAC_EN_SFT, 0), - SND_SOC_DAPM_ADC("ADC", "HiFi Capture", NAU8810_REG_POWER2, + SND_SOC_DAPM_ADC("ADC", "Capture", NAU8810_REG_POWER2, NAU8810_ADC_EN_SFT, 0), SND_SOC_DAPM_PGA("SpkN Out", NAU8810_REG_POWER3, NAU8810_NSPK_EN_SFT, 0, NULL, 0), @@ -493,7 +493,7 @@ static int nau8810_set_sysclk(struct snd_soc_dai *dai, return 0; } -static int nau88l0_calc_pll(unsigned int pll_in, +static int nau8810_calc_pll(unsigned int pll_in, unsigned int fs, struct nau8810_pll *pll_param) { u64 f2, f2_max, pll_ratio; @@ -505,7 +505,8 @@ static int nau88l0_calc_pll(unsigned int pll_in, f2_max = 0; scal_sel = ARRAY_SIZE(nau8810_mclk_scaler); for (i = 0; i < ARRAY_SIZE(nau8810_mclk_scaler); i++) { - f2 = 256 * fs * 4 * nau8810_mclk_scaler[i] / 10; + f2 = 256ULL * fs * 4 * nau8810_mclk_scaler[i]; + f2 = div_u64(f2, 10); if (f2 > NAU_PLL_FREQ_MIN && f2 < NAU_PLL_FREQ_MAX && f2_max < f2) { f2_max = f2; @@ -542,7 +543,7 @@ static int nau8810_set_pll(struct snd_soc_dai *codec_dai, int pll_id, int ret, fs; fs = freq_out / 256; - ret = nau88l0_calc_pll(freq_in, fs, pll_param); + ret = nau8810_calc_pll(freq_in, fs, pll_param); if (ret < 0) { dev_err(nau8810->dev, "Unsupported input clock %d\n", freq_in); return ret; @@ -667,6 +668,24 @@ static int nau8810_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); int val_len = 0, val_rate = 0, ret = 0; + unsigned int ctrl_val, bclk_fs, bclk_div; + + /* Select BCLK configuration if the codec as master. */ + regmap_read(nau8810->regmap, NAU8810_REG_CLOCK, &ctrl_val); + if (ctrl_val & NAU8810_CLKIO_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / params_rate(params); + if (bclk_fs <= 32) + bclk_div = NAU8810_BCLKDIV_8; + else if (bclk_fs <= 64) + bclk_div = NAU8810_BCLKDIV_4; + else if (bclk_fs <= 128) + bclk_div = NAU8810_BCLKDIV_2; + else + return -EINVAL; + regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK, + NAU8810_BCLKSEL_MASK, bclk_div); + } switch (params_width(params)) { case 16: diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index 87ed3dc496dc..5ab05e75edea 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -681,8 +681,8 @@ static const struct snd_soc_dapm_widget nau8824_dapm_widgets[] = { SND_SOC_DAPM_ADC("ADCR", NULL, NAU8824_REG_ANALOG_ADC_2, NAU8824_ADCR_EN_SFT, 0), - SND_SOC_DAPM_AIF_OUT("AIFTX", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_IN("AIFRX", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DACL", NULL, NAU8824_REG_RDAC, NAU8824_DACL_EN_SFT, 0), @@ -831,6 +831,36 @@ static void nau8824_int_status_clear_all(struct regmap *regmap) } } +static void nau8824_dapm_disable_pin(struct nau8824 *nau8824, const char *pin) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + const char *prefix = dapm->component->name_prefix; + char prefixed_pin[80]; + + if (prefix) { + snprintf(prefixed_pin, sizeof(prefixed_pin), "%s %s", + prefix, pin); + snd_soc_dapm_disable_pin(dapm, prefixed_pin); + } else { + snd_soc_dapm_disable_pin(dapm, pin); + } +} + +static void nau8824_dapm_enable_pin(struct nau8824 *nau8824, const char *pin) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + const char *prefix = dapm->component->name_prefix; + char prefixed_pin[80]; + + if (prefix) { + snprintf(prefixed_pin, sizeof(prefixed_pin), "%s %s", + prefix, pin); + snd_soc_dapm_force_enable_pin(dapm, prefixed_pin); + } else { + snd_soc_dapm_force_enable_pin(dapm, pin); + } +} + static void nau8824_eject_jack(struct nau8824 *nau8824) { struct snd_soc_dapm_context *dapm = nau8824->dapm; @@ -839,8 +869,8 @@ static void nau8824_eject_jack(struct nau8824 *nau8824) /* Clear all interruption status */ nau8824_int_status_clear_all(regmap); - snd_soc_dapm_disable_pin(dapm, "SAR"); - snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + nau8824_dapm_disable_pin(nau8824, "SAR"); + nau8824_dapm_disable_pin(nau8824, "MICBIAS"); snd_soc_dapm_sync(dapm); /* Enable the insertion interruption, disable the ejection @@ -870,8 +900,8 @@ static void nau8824_jdet_work(struct work_struct *work) struct regmap *regmap = nau8824->regmap; int adc_value, event = 0, event_mask = 0; - snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); - snd_soc_dapm_force_enable_pin(dapm, "SAR"); + nau8824_dapm_enable_pin(nau8824, "MICBIAS"); + nau8824_dapm_enable_pin(nau8824, "SAR"); snd_soc_dapm_sync(dapm); msleep(100); @@ -882,8 +912,8 @@ static void nau8824_jdet_work(struct work_struct *work) if (adc_value < HEADSET_SARADC_THD) { event |= SND_JACK_HEADPHONE; - snd_soc_dapm_disable_pin(dapm, "SAR"); - snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + nau8824_dapm_disable_pin(nau8824, "SAR"); + nau8824_dapm_disable_pin(nau8824, "MICBIAS"); snd_soc_dapm_sync(dapm); } else { event |= SND_JACK_HEADSET; diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index 29b2d60076b0..cb8252ff31cb 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -1645,7 +1645,10 @@ static bool rt5651_jack_inserted(struct snd_soc_component *component) break; } - return val == 0; + if (rt5651->jd_active_high) + return val != 0; + else + return val == 0; } /* Jack detect and button-press timings */ @@ -1868,20 +1871,47 @@ static void rt5651_enable_jack_detect(struct snd_soc_component *component, case RT5651_JD1_1: snd_soc_component_update_bits(component, RT5651_JD_CTRL2, RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD1_1); - snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, - RT5651_JD1_1_IRQ_EN, RT5651_JD1_1_IRQ_EN); + /* active-low is normal, set inv flag for active-high */ + if (rt5651->jd_active_high) + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_1_IRQ_EN | RT5651_JD1_1_INV, + RT5651_JD1_1_IRQ_EN | RT5651_JD1_1_INV); + else + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_1_IRQ_EN | RT5651_JD1_1_INV, + RT5651_JD1_1_IRQ_EN); break; case RT5651_JD1_2: snd_soc_component_update_bits(component, RT5651_JD_CTRL2, RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD1_2); - snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, - RT5651_JD1_2_IRQ_EN, RT5651_JD1_2_IRQ_EN); + /* active-low is normal, set inv flag for active-high */ + if (rt5651->jd_active_high) + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_2_IRQ_EN | RT5651_JD1_2_INV, + RT5651_JD1_2_IRQ_EN | RT5651_JD1_2_INV); + else + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_2_IRQ_EN | RT5651_JD1_2_INV, + RT5651_JD1_2_IRQ_EN); break; case RT5651_JD2: snd_soc_component_update_bits(component, RT5651_JD_CTRL2, RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD2); - snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, - RT5651_JD2_IRQ_EN, RT5651_JD2_IRQ_EN); + /* active-low is normal, set inv flag for active-high */ + if (rt5651->jd_active_high) + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD2_IRQ_EN | RT5651_JD2_INV, + RT5651_JD2_IRQ_EN | RT5651_JD2_INV); + else + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD2_IRQ_EN | RT5651_JD2_INV, + RT5651_JD2_IRQ_EN); break; default: dev_err(component->dev, "Currently only JD1_1 / JD1_2 / JD2 are supported\n"); @@ -1986,6 +2016,9 @@ static void rt5651_apply_properties(struct snd_soc_component *component) "realtek,jack-detect-source", &val) == 0) rt5651->jd_src = val; + if (device_property_read_bool(component->dev, "realtek,jack-detect-not-inverted")) + rt5651->jd_active_high = true; + /* * Testing on various boards has shown that good defaults for the OVCD * threshold and scale-factor are 2000µA and 0.75. For an effective diff --git a/sound/soc/codecs/rt5651.h b/sound/soc/codecs/rt5651.h index 41fcb8b5eb40..05b0f6f8b95d 100644 --- a/sound/soc/codecs/rt5651.h +++ b/sound/soc/codecs/rt5651.h @@ -2083,6 +2083,7 @@ struct rt5651_priv { int release_count; int poll_count; unsigned int jd_src; + bool jd_active_high; unsigned int ovcd_th; unsigned int ovcd_sf; diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index 84501c2020c7..167a02773a0b 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -25,6 +25,7 @@ #include <linux/sysfs.h> #include <linux/clk.h> #include <linux/firmware.h> +#include <linux/acpi.h> #include "rt5677-spi.h" @@ -226,9 +227,16 @@ static int rt5677_spi_probe(struct spi_device *spi) return 0; } +static const struct acpi_device_id rt5677_spi_acpi_id[] = { + { "RT5677AA", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, rt5677_spi_acpi_id); + static struct spi_driver rt5677_spi_driver = { .driver = { .name = "rt5677", + .acpi_match_table = ACPI_PTR(rt5677_spi_acpi_id), }, .probe = rt5677_spi_probe, }; diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c index 9d5acd2d04ab..86a7fa31c294 100644 --- a/sound/soc/codecs/rt5682.c +++ b/sound/soc/codecs/rt5682.c @@ -910,13 +910,21 @@ static int rt5682_headset_detect(struct snd_soc_component *component, int jack_insert) { struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); unsigned int val, count; if (jack_insert) { - snd_soc_dapm_force_enable_pin(dapm, "CBJ Power"); - snd_soc_dapm_sync(dapm); + + snd_soc_component_update_bits(component, RT5682_PWR_ANLG_1, + RT5682_PWR_VREF2 | RT5682_PWR_MB, + RT5682_PWR_VREF2 | RT5682_PWR_MB); + snd_soc_component_update_bits(component, + RT5682_PWR_ANLG_1, RT5682_PWR_FV2, 0); + usleep_range(15000, 20000); + snd_soc_component_update_bits(component, + RT5682_PWR_ANLG_1, RT5682_PWR_FV2, RT5682_PWR_FV2); + snd_soc_component_update_bits(component, RT5682_PWR_ANLG_3, + RT5682_PWR_CBJ, RT5682_PWR_CBJ); + snd_soc_component_update_bits(component, RT5682_CBJ_CTRL_1, RT5682_TRIG_JD_MASK, RT5682_TRIG_JD_HIGH); @@ -944,8 +952,10 @@ static int rt5682_headset_detect(struct snd_soc_component *component, rt5682_enable_push_button_irq(component, false); snd_soc_component_update_bits(component, RT5682_CBJ_CTRL_1, RT5682_TRIG_JD_MASK, RT5682_TRIG_JD_LOW); - snd_soc_dapm_disable_pin(dapm, "CBJ Power"); - snd_soc_dapm_sync(dapm); + snd_soc_component_update_bits(component, RT5682_PWR_ANLG_1, + RT5682_PWR_VREF2 | RT5682_PWR_MB, 0); + snd_soc_component_update_bits(component, RT5682_PWR_ANLG_3, + RT5682_PWR_CBJ, 0); rt5682->jack_type = 0; } @@ -1198,7 +1208,7 @@ static int set_filter_clk(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); - int ref, val, reg, sft, mask, idx = -EINVAL; + int ref, val, reg, idx = -EINVAL; static const int div_f[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48}; static const int div_o[] = {1, 2, 4, 6, 8, 12, 16, 24, 32, 48}; @@ -1212,15 +1222,10 @@ static int set_filter_clk(struct snd_soc_dapm_widget *w, idx = rt5682_div_sel(rt5682, ref, div_f, ARRAY_SIZE(div_f)); - if (w->shift == RT5682_PWR_ADC_S1F_BIT) { + if (w->shift == RT5682_PWR_ADC_S1F_BIT) reg = RT5682_PLL_TRACK_3; - sft = RT5682_ADC_OSR_SFT; - mask = RT5682_ADC_OSR_MASK; - } else { + else reg = RT5682_PLL_TRACK_2; - sft = RT5682_DAC_OSR_SFT; - mask = RT5682_DAC_OSR_MASK; - } snd_soc_component_update_bits(component, reg, RT5682_FILTER_CLK_DIV_MASK, idx << RT5682_FILTER_CLK_DIV_SFT); @@ -1232,7 +1237,8 @@ static int set_filter_clk(struct snd_soc_dapm_widget *w, } snd_soc_component_update_bits(component, RT5682_ADDA_CLK_1, - mask, idx << sft); + RT5682_ADC_OSR_MASK | RT5682_DAC_OSR_MASK, + (idx << RT5682_ADC_OSR_SFT) | (idx << RT5682_DAC_OSR_SFT)); return 0; } @@ -1591,8 +1597,6 @@ static const struct snd_soc_dapm_widget rt5682_dapm_widgets[] = { 0, NULL, 0), SND_SOC_DAPM_SUPPLY("Vref1", RT5682_PWR_ANLG_1, RT5682_PWR_VREF1_BIT, 0, rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), - SND_SOC_DAPM_SUPPLY("Vref2", RT5682_PWR_ANLG_1, RT5682_PWR_VREF2_BIT, 0, - rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), /* ASRC */ SND_SOC_DAPM_SUPPLY_S("DAC STO1 ASRC", 1, RT5682_PLL_TRACK_1, @@ -1627,9 +1631,6 @@ static const struct snd_soc_dapm_widget rt5682_dapm_widgets[] = { SND_SOC_DAPM_PGA("BST1 CBJ", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_SUPPLY("CBJ Power", RT5682_PWR_ANLG_3, - RT5682_PWR_CBJ_BIT, 0, NULL, 0), - /* REC Mixer */ SND_SOC_DAPM_MIXER("RECMIX1L", SND_SOC_NOPM, 0, 0, rt5682_rec1_l_mix, ARRAY_SIZE(rt5682_rec1_l_mix)), @@ -1792,17 +1793,13 @@ static const struct snd_soc_dapm_route rt5682_dapm_routes[] = { /*Vref*/ {"MICBIAS1", NULL, "Vref1"}, - {"MICBIAS1", NULL, "Vref2"}, {"MICBIAS2", NULL, "Vref1"}, - {"MICBIAS2", NULL, "Vref2"}, {"CLKDET SYS", NULL, "CLKDET"}, {"IN1P", NULL, "LDO2"}, {"BST1 CBJ", NULL, "IN1P"}, - {"BST1 CBJ", NULL, "CBJ Power"}, - {"CBJ Power", NULL, "Vref2"}, {"RECMIX1L", "CBJ Switch", "BST1 CBJ"}, {"RECMIX1L", NULL, "RECMIX1L Power"}, @@ -1912,9 +1909,7 @@ static const struct snd_soc_dapm_route rt5682_dapm_routes[] = { {"HP Amp", NULL, "Capless"}, {"HP Amp", NULL, "Charge Pump"}, {"HP Amp", NULL, "CLKDET SYS"}, - {"HP Amp", NULL, "CBJ Power"}, {"HP Amp", NULL, "Vref1"}, - {"HP Amp", NULL, "Vref2"}, {"HPOL Playback", "Switch", "HP Amp"}, {"HPOR Playback", "Switch", "HP Amp"}, {"HPOL", NULL, "HPOL Playback"}, @@ -2303,16 +2298,13 @@ static int rt5682_set_bias_level(struct snd_soc_component *component, switch (level) { case SND_SOC_BIAS_PREPARE: regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, - RT5682_PWR_MB | RT5682_PWR_BG, - RT5682_PWR_MB | RT5682_PWR_BG); + RT5682_PWR_BG, RT5682_PWR_BG); regmap_update_bits(rt5682->regmap, RT5682_PWR_DIG_1, RT5682_DIG_GATE_CTRL | RT5682_PWR_LDO, RT5682_DIG_GATE_CTRL | RT5682_PWR_LDO); break; case SND_SOC_BIAS_STANDBY: - regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, - RT5682_PWR_MB, RT5682_PWR_MB); regmap_update_bits(rt5682->regmap, RT5682_PWR_DIG_1, RT5682_DIG_GATE_CTRL, RT5682_DIG_GATE_CTRL); break; @@ -2320,7 +2312,7 @@ static int rt5682_set_bias_level(struct snd_soc_component *component, regmap_update_bits(rt5682->regmap, RT5682_PWR_DIG_1, RT5682_DIG_GATE_CTRL | RT5682_PWR_LDO, 0); regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, - RT5682_PWR_MB | RT5682_PWR_BG, 0); + RT5682_PWR_BG, 0); break; default: @@ -2363,6 +2355,8 @@ static int rt5682_resume(struct snd_soc_component *component) regcache_cache_only(rt5682->regmap, false); regcache_sync(rt5682->regmap); + rt5682_irq(0, rt5682); + return 0; } #else diff --git a/sound/soc/codecs/simple-amplifier.c b/sound/soc/codecs/simple-amplifier.c index c07e8a80b4b7..351aa55c384e 100644 --- a/sound/soc/codecs/simple-amplifier.c +++ b/sound/soc/codecs/simple-amplifier.c @@ -89,7 +89,8 @@ static int simple_amp_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, priv); - priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + priv->gpiod_enable = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); if (IS_ERR(priv->gpiod_enable)) { err = PTR_ERR(priv->gpiod_enable); if (err != -EPROBE_DEFER) diff --git a/sound/soc/codecs/sirf-audio-codec.c b/sound/soc/codecs/sirf-audio-codec.c index e424499a8450..e0af21050078 100644 --- a/sound/soc/codecs/sirf-audio-codec.c +++ b/sound/soc/codecs/sirf-audio-codec.c @@ -461,9 +461,6 @@ static int sirf_audio_codec_driver_probe(struct platform_device *pdev) struct sirf_audio_codec *sirf_audio_codec; void __iomem *base; struct resource *mem_res; - const struct of_device_id *match; - - match = of_match_node(sirf_audio_codec_of_match, pdev->dev.of_node); sirf_audio_codec = devm_kzalloc(&pdev->dev, sizeof(struct sirf_audio_codec), GFP_KERNEL); diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index c544a1e35f5e..fa47bfcb93e9 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -25,6 +25,7 @@ #include <linux/of_gpio.h> #include <linux/slab.h> #include <sound/core.h> +#include <sound/jack.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -89,6 +90,7 @@ static bool aic31xx_volatile(struct device *dev, unsigned int reg) case AIC31XX_INTRADCFLAG: /* Sticky interrupt flags */ case AIC31XX_INTRDACFLAG2: case AIC31XX_INTRADCFLAG2: + case AIC31XX_HSDETECT: return true; } return false; @@ -163,6 +165,7 @@ struct aic31xx_priv { struct aic31xx_pdata pdata; struct regulator_bulk_data supplies[AIC31XX_NUM_SUPPLIES]; struct aic31xx_disable_nb disable_nb[AIC31XX_NUM_SUPPLIES]; + struct snd_soc_jack *jack; unsigned int sysclk; u8 p_div; int rate_div_line; @@ -1261,6 +1264,20 @@ static int aic31xx_set_bias_level(struct snd_soc_component *component, return 0; } +int aic31xx_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + + aic31xx->jack = jack; + + /* Enable/Disable jack detection */ + regmap_write(aic31xx->regmap, AIC31XX_HSDETECT, + jack ? AIC31XX_HSD_ENABLE : 0); + + return 0; +} + static int aic31xx_codec_probe(struct snd_soc_component *component) { struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); @@ -1301,6 +1318,7 @@ static int aic31xx_codec_probe(struct snd_soc_component *component) static const struct snd_soc_component_driver soc_codec_driver_aic31xx = { .probe = aic31xx_codec_probe, + .set_jack = aic31xx_set_jack, .set_bias_level = aic31xx_set_bias_level, .controls = common31xx_snd_controls, .num_controls = ARRAY_SIZE(common31xx_snd_controls), @@ -1405,8 +1423,47 @@ static irqreturn_t aic31xx_irq(int irq, void *data) dev_err(dev, "Short circuit on Left output is detected\n"); if (value & AIC31XX_HPRSCDETECT) dev_err(dev, "Short circuit on Right output is detected\n"); + if (value & (AIC31XX_HSPLUG | AIC31XX_BUTTONPRESS)) { + unsigned int val; + int status = 0; + + ret = regmap_read(aic31xx->regmap, AIC31XX_INTRDACFLAG2, + &val); + if (ret) { + dev_err(dev, "Failed to read interrupt mask: %d\n", + ret); + goto exit; + } + + if (val & AIC31XX_BUTTONPRESS) + status |= SND_JACK_BTN_0; + + ret = regmap_read(aic31xx->regmap, AIC31XX_HSDETECT, &val); + if (ret) { + dev_err(dev, "Failed to read headset type: %d\n", ret); + goto exit; + } + + switch ((val & AIC31XX_HSD_TYPE_MASK) >> + AIC31XX_HSD_TYPE_SHIFT) { + case AIC31XX_HSD_HP: + status |= SND_JACK_HEADPHONE; + break; + case AIC31XX_HSD_HS: + status |= SND_JACK_HEADSET; + break; + default: + break; + } + + if (aic31xx->jack) + snd_soc_jack_report(aic31xx->jack, status, + AIC31XX_JACK_MASK); + } if (value & ~(AIC31XX_HPLSCDETECT | - AIC31XX_HPRSCDETECT)) + AIC31XX_HPRSCDETECT | + AIC31XX_HSPLUG | + AIC31XX_BUTTONPRESS)) dev_err(dev, "Unknown DAC interrupt flags: 0x%08x\n", value); read_overflow: @@ -1518,6 +1575,8 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c, AIC31XX_GPIO1_FUNC_SHIFT); regmap_write(aic31xx->regmap, AIC31XX_INT1CTRL, + AIC31XX_HSPLUGDET | + AIC31XX_BUTTONPRESSDET | AIC31XX_SC | AIC31XX_ENGINE); diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index 2636f2c6bc79..cb024955c978 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -20,6 +20,10 @@ #define AIC31XX_MINIDSP_BIT BIT(2) #define DAC31XX_BIT BIT(3) +#define AIC31XX_JACK_MASK (SND_JACK_HEADPHONE | \ + SND_JACK_HEADSET | \ + SND_JACK_BTN_0) + enum aic31xx_type { AIC3100 = 0, AIC3110 = AIC31XX_STEREO_CLASS_D_BIT, @@ -220,6 +224,14 @@ struct aic31xx_pdata { /* AIC31XX_DACMUTE */ #define AIC31XX_DACMUTE_MASK GENMASK(3, 2) +/* AIC31XX_HSDETECT */ +#define AIC31XX_HSD_ENABLE BIT(7) +#define AIC31XX_HSD_TYPE_MASK GENMASK(6, 5) +#define AIC31XX_HSD_TYPE_SHIFT 5 +#define AIC31XX_HSD_NONE 0x00 +#define AIC31XX_HSD_HP 0x01 +#define AIC31XX_HSD_HS 0x03 + /* AIC31XX_MICBIAS */ #define AIC31XX_MICBIAS_MASK GENMASK(1, 0) #define AIC31XX_MICBIAS_SHIFT 0 diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c new file mode 100644 index 000000000000..667ec2c03508 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-clk.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond + * + * Author: Annaliese McDermond <nh6z@nh6z.net> + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/regmap.h> +#include <linux/device.h> + +#include "tlv320aic32x4.h" + +#define to_clk_aic32x4(_hw) container_of(_hw, struct clk_aic32x4, hw) +struct clk_aic32x4 { + struct clk_hw hw; + struct device *dev; + struct regmap *regmap; + unsigned int reg; +}; + +/* + * struct clk_aic32x4_pll_muldiv - Multiplier/divider settings + * @p: Divider + * @r: first multiplier + * @j: integer part of second multiplier + * @d: decimal part of second multiplier + */ +struct clk_aic32x4_pll_muldiv { + u8 p; + u16 r; + u8 j; + u16 d; +}; + +struct aic32x4_clkdesc { + const char *name; + const char * const *parent_names; + unsigned int num_parents; + const struct clk_ops *ops; + unsigned int reg; +}; + +static int clk_aic32x4_pll_prepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + return regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLLEN, AIC32X4_PLLEN); +} + +static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLLEN, 0); +} + +static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + unsigned int val; + int ret; + + ret = regmap_read(pll->regmap, AIC32X4_PLLPR, &val); + if (ret < 0) + return ret; + + return !!(val & AIC32X4_PLLEN); +} + +static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + struct clk_aic32x4_pll_muldiv *settings) +{ + /* Change to use regmap_bulk_read? */ + unsigned int val; + int ret; + + ret = regmap_read(pll->regmap, AIC32X4_PLLPR, &val); + if (ret) + return ret; + settings->r = val & AIC32X4_PLL_R_MASK; + settings->p = (val & AIC32X4_PLL_P_MASK) >> AIC32X4_PLL_P_SHIFT; + + ret = regmap_read(pll->regmap, AIC32X4_PLLJ, &val); + if (ret < 0) + return ret; + settings->j = val; + + ret = regmap_read(pll->regmap, AIC32X4_PLLDMSB, &val); + if (ret < 0) + return ret; + settings->d = val << 8; + + ret = regmap_read(pll->regmap, AIC32X4_PLLDLSB, &val); + if (ret < 0) + return ret; + settings->d |= val; + + return 0; +} + +static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + struct clk_aic32x4_pll_muldiv *settings) +{ + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLL_P_MASK, + settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_write(pll->regmap, AIC32X4_PLLJ, settings->j); + if (ret < 0) + return ret; + + ret = regmap_write(pll->regmap, AIC32X4_PLLDMSB, (settings->d >> 8)); + if (ret < 0) + return ret; + ret = regmap_write(pll->regmap, AIC32X4_PLLDLSB, (settings->d & 0xff)); + if (ret < 0) + return ret; + + return 0; +} + +static unsigned long clk_aic32x4_pll_calc_rate( + struct clk_aic32x4_pll_muldiv *settings, + unsigned long parent_rate) +{ + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ + rate = (u64) parent_rate * settings->r * + ((settings->j * 10000) + settings->d); + + return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); +} + +static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + unsigned long rate, unsigned long parent_rate) +{ + u64 multiplier; + + settings->p = parent_rate / AIC32X4_MAX_PLL_CLKIN + 1; + if (settings->p > 8) + return -1; + + /* + * We scale this figure by 10000 so that we can get the decimal part + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ + multiplier = (u64) rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ + settings->r = ((u32) multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); + + /* + * J can't be < 1. + */ + if (multiplier < 10000) + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ + settings->j = (u32) multiplier / 10000; + settings->d = (u32) multiplier % 10000; + + return 0; +} + +static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; + int ret; + + ret = clk_aic32x4_pll_get_muldiv(pll, &settings); + if (ret < 0) + return 0; + + return clk_aic32x4_pll_calc_rate(&settings, parent_rate); +} + +static long clk_aic32x4_pll_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_aic32x4_pll_muldiv settings; + int ret; + + ret = clk_aic32x4_pll_calc_muldiv(&settings, rate, *parent_rate); + if (ret < 0) + return 0; + + return clk_aic32x4_pll_calc_rate(&settings, *parent_rate); +} + +static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; + int ret; + + ret = clk_aic32x4_pll_calc_muldiv(&settings, rate, parent_rate); + if (ret < 0) + return -EINVAL; + + return clk_aic32x4_pll_set_muldiv(pll, &settings); +} + +static int clk_aic32x4_pll_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + return regmap_update_bits(pll->regmap, + AIC32X4_CLKMUX, + AIC32X4_PLL_CLKIN_MASK, + index << AIC32X4_PLL_CLKIN_SHIFT); +} + +static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + unsigned int val; + + regmap_read(pll->regmap, AIC32X4_PLLPR, &val); + + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; +} + + +static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, + .is_prepared = clk_aic32x4_pll_is_prepared, + .recalc_rate = clk_aic32x4_pll_recalc_rate, + .round_rate = clk_aic32x4_pll_round_rate, + .set_rate = clk_aic32x4_pll_set_rate, + .set_parent = clk_aic32x4_pll_set_parent, + .get_parent = clk_aic32x4_pll_get_parent, +}; + +static int clk_aic32x4_codec_clkin_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + + return regmap_update_bits(mux->regmap, + AIC32X4_CLKMUX, + AIC32X4_CODEC_CLKIN_MASK, index << AIC32X4_CODEC_CLKIN_SHIFT); +} + +static u8 clk_aic32x4_codec_clkin_get_parent(struct clk_hw *hw) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + unsigned int val; + + regmap_read(mux->regmap, AIC32X4_CLKMUX, &val); + + return (val & AIC32X4_CODEC_CLKIN_MASK) >> AIC32X4_CODEC_CLKIN_SHIFT; +} + +static const struct clk_ops aic32x4_codec_clkin_ops = { + .set_parent = clk_aic32x4_codec_clkin_set_parent, + .get_parent = clk_aic32x4_codec_clkin_get_parent, +}; + +static int clk_aic32x4_div_prepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + return regmap_update_bits(div->regmap, div->reg, + AIC32X4_DIVEN, AIC32X4_DIVEN); +} + +static void clk_aic32x4_div_unprepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, + AIC32X4_DIVEN, 0); +} + +static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; + + divisor = DIV_ROUND_UP(parent_rate, rate); + if (divisor > 128) + return -EINVAL; + + return regmap_update_bits(div->regmap, div->reg, + AIC32X4_DIV_MASK, divisor); +} + +static long clk_aic32x4_div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long divisor; + + divisor = DIV_ROUND_UP(*parent_rate, rate); + if (divisor > 128) + return -EINVAL; + + return DIV_ROUND_UP(*parent_rate, divisor); +} + +static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + unsigned int val; + + regmap_read(div->regmap, div->reg, &val); + + return DIV_ROUND_UP(parent_rate, val & AIC32X4_DIV_MASK); +} + +static const struct clk_ops aic32x4_div_ops = { + .prepare = clk_aic32x4_div_prepare, + .unprepare = clk_aic32x4_div_unprepare, + .set_rate = clk_aic32x4_div_set_rate, + .round_rate = clk_aic32x4_div_round_rate, + .recalc_rate = clk_aic32x4_div_recalc_rate, +}; + +static int clk_aic32x4_bdiv_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + + return regmap_update_bits(mux->regmap, AIC32X4_IFACE3, + AIC32X4_BDIVCLK_MASK, index); +} + +static u8 clk_aic32x4_bdiv_get_parent(struct clk_hw *hw) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + unsigned int val; + + regmap_read(mux->regmap, AIC32X4_IFACE3, &val); + + return val & AIC32X4_BDIVCLK_MASK; +} + +static const struct clk_ops aic32x4_bdiv_ops = { + .prepare = clk_aic32x4_div_prepare, + .unprepare = clk_aic32x4_div_unprepare, + .set_parent = clk_aic32x4_bdiv_set_parent, + .get_parent = clk_aic32x4_bdiv_get_parent, + .set_rate = clk_aic32x4_div_set_rate, + .round_rate = clk_aic32x4_div_round_rate, + .recalc_rate = clk_aic32x4_div_recalc_rate, +}; + +static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = + (const char* []) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, + }, + { + .name = "codec_clkin", + .parent_names = + (const char *[]) { "mclk", "bclk", "gpio", "pll" }, + .num_parents = 4, + .ops = &aic32x4_codec_clkin_ops, + .reg = 0, + }, + { + .name = "ndac", + .parent_names = (const char * []) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", + .parent_names = (const char * []) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", + .parent_names = (const char * []) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", + .parent_names = (const char * []) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, + }, + { + .name = "bdiv", + .parent_names = + (const char *[]) { "ndac", "mdac", "nadc", "madc" }, + .num_parents = 4, + .ops = &aic32x4_bdiv_ops, + .reg = AIC32X4_BCLKN, + }, +}; + +static struct clk *aic32x4_register_clk(struct device *dev, + struct aic32x4_clkdesc *desc) +{ + struct clk_init_data init; + struct clk_aic32x4 *priv; + const char *devname = dev_name(dev); + + init.ops = desc->ops; + init.name = desc->name; + init.parent_names = desc->parent_names; + init.num_parents = desc->num_parents; + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); + if (priv == NULL) + return (struct clk *) -ENOMEM; + + priv->dev = dev; + priv->hw.init = &init; + priv->regmap = dev_get_regmap(dev, NULL); + priv->reg = desc->reg; + + clk_hw_register_clkdev(&priv->hw, desc->name, devname); + return devm_clk_register(dev, &priv->hw); +} + +int aic32x4_register_clocks(struct device *dev, const char *mclk_name) +{ + int i; + + /* + * These lines are here to preserve the current functionality of + * the driver with regard to the DT. These should eventually be set + * by DT nodes so that the connections can be set up in configuration + * rather than code. + */ + aic32x4_clkdesc_array[0].parent_names = + (const char* []) { mclk_name, "bclk", "gpio", "din" }; + aic32x4_clkdesc_array[1].parent_names = + (const char *[]) { mclk_name, "bclk", "gpio", "pll" }; + + for (i = 0; i < ARRAY_SIZE(aic32x4_clkdesc_array); ++i) + aic32x4_register_clk(dev, &aic32x4_clkdesc_array[i]); + + return 0; +} +EXPORT_SYMBOL_GPL(aic32x4_register_clocks); diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 96f1526cb258..75443efeda69 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -14,7 +14,7 @@ * * 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 + * 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 @@ -33,6 +33,7 @@ #include <linux/cdev.h> #include <linux/slab.h> #include <linux/clk.h> +#include <linux/of_clk.h> #include <linux/regulator/consumer.h> #include <sound/tlv320aic32x4.h> @@ -46,29 +47,13 @@ #include "tlv320aic32x4.h" -struct aic32x4_rate_divs { - u32 mclk; - u32 rate; - u8 p_val; - u8 pll_j; - u16 pll_d; - u16 dosr; - u8 ndac; - u8 mdac; - u8 aosr; - u8 nadc; - u8 madc; - u8 blck_N; -}; - struct aic32x4_priv { struct regmap *regmap; - u32 sysclk; u32 power_cfg; u32 micpga_routing; bool swapdacs; int rstn_gpio; - struct clk *mclk; + const char *mclk_name; struct regulator *supply_ldo; struct regulator *supply_iov; @@ -305,38 +290,6 @@ static const struct snd_kcontrol_new aic32x4_snd_controls[] = { 0, 0x0F, 0), }; -static const struct aic32x4_rate_divs aic32x4_divs[] = { - /* 8k rate */ - {12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24}, - {24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24}, - {25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24}, - /* 11.025k rate */ - {12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16}, - {24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16}, - /* 16k rate */ - {12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12}, - {24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12}, - {25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12}, - /* 22.05k rate */ - {12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8}, - {24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8}, - {25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8}, - /* 32k rate */ - {12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6}, - {24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6}, - /* 44.1k rate */ - {12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4}, - {24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4}, - {25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4}, - /* 48k rate */ - {12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, - {24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, - {25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}, - - /* 96k rate */ - {25000000, 96000, 2, 7, 8643, 64, 4, 4, 64, 4, 4, 1}, -}; - static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0), SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0), @@ -391,7 +344,7 @@ static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), }; -/* Right mixer pins */ +/* Right mixer pins */ static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, resistor_text); static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text); static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text); @@ -593,7 +546,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { static const struct regmap_range_cfg aic32x4_regmap_pages[] = { { .selector_reg = 0, - .selector_mask = 0xff, + .selector_mask = 0xff, .window_start = 0, .window_len = 128, .range_min = 0, @@ -608,35 +561,17 @@ const struct regmap_config aic32x4_regmap_config = { }; EXPORT_SYMBOL(aic32x4_regmap_config); -static inline int aic32x4_get_divs(int mclk, int rate) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) { - if ((aic32x4_divs[i].rate == rate) - && (aic32x4_divs[i].mclk == mclk)) { - return i; - } - } - printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n"); - return -EINVAL; -} - static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_component *component = codec_dai->component; - struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + struct clk *mclk; + struct clk *pll; - switch (freq) { - case 12000000: - case 24000000: - case 25000000: - aic32x4->sysclk = freq; - return 0; - } - printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n"); - return -EINVAL; + pll = devm_clk_get(component->dev, "pll"); + mclk = clk_get_parent(pll); + + return clk_set_rate(mclk, freq); } static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) @@ -686,103 +621,179 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) } snd_soc_component_update_bits(component, AIC32X4_IFACE1, - AIC32X4_IFACE1_DATATYPE_MASK | - AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); + AIC32X4_IFACE1_DATATYPE_MASK | + AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); snd_soc_component_update_bits(component, AIC32X4_IFACE2, - AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + AIC32X4_DATA_OFFSET_MASK, iface_reg_2); snd_soc_component_update_bits(component, AIC32X4_IFACE3, - AIC32X4_BCLKINV_MASK, iface_reg_3); + AIC32X4_BCLKINV_MASK, iface_reg_3); return 0; } -static int aic32x4_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) { - struct snd_soc_component *component = dai->component; - struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); - u8 iface1_reg = 0; - u8 dacsetup_reg = 0; - int i; - - i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params)); - if (i < 0) { - printk(KERN_ERR "aic32x4: sampling rate not supported\n"); - return i; - } + return snd_soc_component_write(component, AIC32X4_AOSR, aosr); +} - /* MCLK as PLL_CLKIN */ - snd_soc_component_update_bits(component, AIC32X4_CLKMUX, AIC32X4_PLL_CLKIN_MASK, - AIC32X4_PLL_CLKIN_MCLK << AIC32X4_PLL_CLKIN_SHIFT); - /* PLL as CODEC_CLKIN */ - snd_soc_component_update_bits(component, AIC32X4_CLKMUX, AIC32X4_CODEC_CLKIN_MASK, - AIC32X4_CODEC_CLKIN_PLL << AIC32X4_CODEC_CLKIN_SHIFT); - /* DAC_MOD_CLK as BDIV_CLKIN */ - snd_soc_component_update_bits(component, AIC32X4_IFACE3, AIC32X4_BDIVCLK_MASK, - AIC32X4_DACMOD2BCLK << AIC32X4_BDIVCLK_SHIFT); +static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) +{ + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); + snd_soc_component_write(component, AIC32X4_DOSRLSB, + (dosr & 0xff)); - /* We will fix R value to 1 and will make P & J=K.D as variable */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, AIC32X4_PLL_R_MASK, 0x01); + return 0; +} - /* PLL P value */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, AIC32X4_PLL_P_MASK, - aic32x4_divs[i].p_val << AIC32X4_PLL_P_SHIFT); +static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + u8 r_block, u8 p_block) +{ + if (r_block > 18 || p_block > 25) + return -EINVAL; - /* PLL J value */ - snd_soc_component_write(component, AIC32X4_PLLJ, aic32x4_divs[i].pll_j); + snd_soc_component_write(component, AIC32X4_ADCSPB, r_block); + snd_soc_component_write(component, AIC32X4_DACSPB, p_block); - /* PLL D value */ - snd_soc_component_write(component, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8)); - snd_soc_component_write(component, AIC32X4_PLLDLSB, (aic32x4_divs[i].pll_d & 0xff)); + return 0; +} - /* NDAC divider value */ - snd_soc_component_update_bits(component, AIC32X4_NDAC, - AIC32X4_NDAC_MASK, aic32x4_divs[i].ndac); +static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate) +{ + u8 aosr; + u16 dosr; + u8 adc_resource_class, dac_resource_class; + u8 madc, nadc, mdac, ndac, max_nadc, min_mdac, max_ndac; + u8 dosr_increment; + u16 max_dosr, min_dosr; + unsigned long mclk_rate, adc_clock_rate, dac_clock_rate; + int ret; + struct clk *mclk; - /* MDAC divider value */ - snd_soc_component_update_bits(component, AIC32X4_MDAC, - AIC32X4_MDAC_MASK, aic32x4_divs[i].mdac); + struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, + { .id = "ndac" }, + { .id = "mdac" }, + { .id = "bdiv" }, + }; + ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); + if (ret) + return ret; - /* DOSR MSB & LSB values */ - snd_soc_component_write(component, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8); - snd_soc_component_write(component, AIC32X4_DOSRLSB, (aic32x4_divs[i].dosr & 0xff)); + mclk = clk_get_parent(clocks[1].clk); + mclk_rate = clk_get_rate(mclk); + + if (sample_rate <= 48000) { + aosr = 128; + adc_resource_class = 6; + dac_resource_class = 8; + dosr_increment = 8; + aic32x4_set_processing_blocks(component, 1, 1); + } else if (sample_rate <= 96000) { + aosr = 64; + adc_resource_class = 6; + dac_resource_class = 8; + dosr_increment = 4; + aic32x4_set_processing_blocks(component, 1, 9); + } else if (sample_rate == 192000) { + aosr = 32; + adc_resource_class = 3; + dac_resource_class = 4; + dosr_increment = 2; + aic32x4_set_processing_blocks(component, 13, 19); + } else { + dev_err(component->dev, "Sampling rate not supported\n"); + return -EINVAL; + } - /* NADC divider value */ - snd_soc_component_update_bits(component, AIC32X4_NADC, - AIC32X4_NADC_MASK, aic32x4_divs[i].nadc); + madc = DIV_ROUND_UP((32 * adc_resource_class), aosr); + max_dosr = (AIC32X4_MAX_DOSR_FREQ / sample_rate / dosr_increment) * + dosr_increment; + min_dosr = (AIC32X4_MIN_DOSR_FREQ / sample_rate / dosr_increment) * + dosr_increment; + max_nadc = AIC32X4_MAX_CODEC_CLKIN_FREQ / (madc * aosr * sample_rate); + + for (nadc = max_nadc; nadc > 0; --nadc) { + adc_clock_rate = nadc * madc * aosr * sample_rate; + for (dosr = max_dosr; dosr >= min_dosr; + dosr -= dosr_increment) { + min_mdac = DIV_ROUND_UP((32 * dac_resource_class), dosr); + max_ndac = AIC32X4_MAX_CODEC_CLKIN_FREQ / + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { + dac_clock_rate = ndac * mdac * dosr * + sample_rate; + if (dac_clock_rate == adc_clock_rate) { + if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) + continue; + + clk_set_rate(clocks[0].clk, + dac_clock_rate); + + clk_set_rate(clocks[1].clk, + sample_rate * aosr * + madc); + clk_set_rate(clocks[2].clk, + sample_rate * aosr); + aic32x4_set_aosr(component, + aosr); + + clk_set_rate(clocks[3].clk, + sample_rate * dosr * + mdac); + clk_set_rate(clocks[4].clk, + sample_rate * dosr); + aic32x4_set_dosr(component, + dosr); + + clk_set_rate(clocks[5].clk, + sample_rate * 32); + return 0; + } + } + } + } + } - /* MADC divider value */ - snd_soc_component_update_bits(component, AIC32X4_MADC, - AIC32X4_MADC_MASK, aic32x4_divs[i].madc); + dev_err(component->dev, + "Could not set clocks to support sample rate.\n"); + return -EINVAL; +} - /* AOSR value */ - snd_soc_component_write(component, AIC32X4_AOSR, aic32x4_divs[i].aosr); +static int aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + u8 iface1_reg = 0; + u8 dacsetup_reg = 0; - /* BCLK N divider */ - snd_soc_component_update_bits(component, AIC32X4_BCLKN, - AIC32X4_BCLK_MASK, aic32x4_divs[i].blck_N); + aic32x4_setup_clocks(component, params_rate(params)); switch (params_width(params)) { case 16: iface1_reg |= (AIC32X4_WORD_LEN_16BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; case 20: iface1_reg |= (AIC32X4_WORD_LEN_20BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; case 24: iface1_reg |= (AIC32X4_WORD_LEN_24BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; case 32: iface1_reg |= (AIC32X4_WORD_LEN_32BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; } snd_soc_component_update_bits(component, AIC32X4_IFACE1, - AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); if (params_channels(params) == 1) { dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; @@ -793,7 +804,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; } snd_soc_component_update_bits(component, AIC32X4_DACSETUP, - AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + AIC32X4_DAC_CHAN_MASK, dacsetup_reg); return 0; } @@ -803,7 +814,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute) struct snd_soc_component *component = dai->component; snd_soc_component_update_bits(component, AIC32X4_DACMUTE, - AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); return 0; } @@ -811,41 +822,25 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute) static int aic32x4_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { - struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); int ret; + struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, + }; + + ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); + if (ret) + return ret; + switch (level) { case SND_SOC_BIAS_ON: - /* Switch on master clock */ - ret = clk_prepare_enable(aic32x4->mclk); + ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); if (ret) { - dev_err(component->dev, "Failed to enable master clock\n"); + dev_err(component->dev, "Failed to enable clocks\n"); return ret; } - - /* Switch on PLL */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, - AIC32X4_PLLEN, AIC32X4_PLLEN); - - /* Switch on NDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NDAC, - AIC32X4_NDACEN, AIC32X4_NDACEN); - - /* Switch on MDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MDAC, - AIC32X4_MDACEN, AIC32X4_MDACEN); - - /* Switch on NADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NADC, - AIC32X4_NADCEN, AIC32X4_NADCEN); - - /* Switch on MADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MADC, - AIC32X4_MADCEN, AIC32X4_MADCEN); - - /* Switch on BCLK_N Divider */ - snd_soc_component_update_bits(component, AIC32X4_BCLKN, - AIC32X4_BCLKEN, AIC32X4_BCLKEN); break; case SND_SOC_BIAS_PREPARE: break; @@ -854,32 +849,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) break; - /* Switch off BCLK_N Divider */ - snd_soc_component_update_bits(component, AIC32X4_BCLKN, - AIC32X4_BCLKEN, 0); - - /* Switch off MADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MADC, - AIC32X4_MADCEN, 0); - - /* Switch off NADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NADC, - AIC32X4_NADCEN, 0); - - /* Switch off MDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MDAC, - AIC32X4_MDACEN, 0); - - /* Switch off NDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NDAC, - AIC32X4_NDACEN, 0); - - /* Switch off PLL */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, - AIC32X4_PLLEN, 0); - - /* Switch off master clock */ - clk_disable_unprepare(aic32x4->mclk); + clk_bulk_disable_unprepare(ARRAY_SIZE(clocks), clocks); break; case SND_SOC_BIAS_OFF: break; @@ -887,8 +857,8 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, return 0; } -#define AIC32X4_RATES SNDRV_PCM_RATE_8000_96000 -#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_192000 +#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops aic32x4_ops = { @@ -901,17 +871,17 @@ static const struct snd_soc_dai_ops aic32x4_ops = { static struct snd_soc_dai_driver aic32x4_dai = { .name = "tlv320aic32x4-hifi", .playback = { - .stream_name = "Playback", - .channels_min = 1, - .channels_max = 2, - .rates = AIC32X4_RATES, - .formats = AIC32X4_FORMATS,}, + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = AIC32X4_RATES, - .formats = AIC32X4_FORMATS,}, + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, .ops = &aic32x4_ops, .symmetric_rates = 1, }; @@ -924,7 +894,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP1 */ if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_DINCTL, - aic32x4->setup->gpio_func[0]); + aic32x4->setup->gpio_func[0]); snd_soc_add_component_controls(component, aic32x4_mfp1, ARRAY_SIZE(aic32x4_mfp1)); } @@ -932,7 +902,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP2 */ if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_DOUTCTL, - aic32x4->setup->gpio_func[1]); + aic32x4->setup->gpio_func[1]); snd_soc_add_component_controls(component, aic32x4_mfp2, ARRAY_SIZE(aic32x4_mfp2)); } @@ -940,7 +910,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP3 */ if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_SCLKCTL, - aic32x4->setup->gpio_func[2]); + aic32x4->setup->gpio_func[2]); snd_soc_add_component_controls(component, aic32x4_mfp3, ARRAY_SIZE(aic32x4_mfp3)); } @@ -948,7 +918,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP4 */ if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_MISOCTL, - aic32x4->setup->gpio_func[3]); + aic32x4->setup->gpio_func[3]); snd_soc_add_component_controls(component, aic32x4_mfp4, ARRAY_SIZE(aic32x4_mfp4)); } @@ -956,7 +926,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP5 */ if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_GPIOCTL, - aic32x4->setup->gpio_func[4]); + aic32x4->setup->gpio_func[4]); snd_soc_add_component_controls(component, aic32x4_mfp5, ARRAY_SIZE(aic32x4_mfp5)); } @@ -966,6 +936,18 @@ static int aic32x4_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; + int ret; + + struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, + { .id = "mdac" }, + }; + + ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); + if (ret) + return ret; if (gpio_is_valid(aic32x4->rstn_gpio)) { ndelay(10); @@ -978,10 +960,13 @@ static int aic32x4_component_probe(struct snd_soc_component *component) if (aic32x4->setup) aic32x4_setup_gpios(component); + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); + /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { - snd_soc_component_write(component, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN | - AIC32X4_MICBIAS_2075V); + snd_soc_component_write(component, AIC32X4_MICBIAS, + AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); } if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); @@ -1044,12 +1029,18 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) { struct aic32x4_setup_data *aic32x4_setup; + int ret; aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), GFP_KERNEL); if (!aic32x4_setup) return -ENOMEM; + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; + aic32x4->mclk_name = of_clk_get_parent_name(np, ret); + aic32x4->swapdacs = false; aic32x4->micpga_routing = 0; aic32x4->rstn_gpio = of_get_named_gpio(np, "reset-gpios", 0); @@ -1171,7 +1162,7 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap) return PTR_ERR(regmap); aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), - GFP_KERNEL); + GFP_KERNEL); if (aic32x4 == NULL) return -ENOMEM; @@ -1183,6 +1174,7 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap) aic32x4->swapdacs = pdata->swapdacs; aic32x4->micpga_routing = pdata->micpga_routing; aic32x4->rstn_gpio = pdata->rstn_gpio; + aic32x4->mclk_name = "mclk"; } else if (np) { ret = aic32x4_parse_dt(aic32x4, np); if (ret) { @@ -1194,13 +1186,12 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap) aic32x4->swapdacs = false; aic32x4->micpga_routing = 0; aic32x4->rstn_gpio = -1; + aic32x4->mclk_name = "mclk"; } - aic32x4->mclk = devm_clk_get(dev, "mclk"); - if (IS_ERR(aic32x4->mclk)) { - dev_err(dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); - return PTR_ERR(aic32x4->mclk); - } + ret = aic32x4_register_clocks(dev, aic32x4->mclk_name); + if (ret) + return ret; if (gpio_is_valid(aic32x4->rstn_gpio)) { ret = devm_gpio_request_one(dev, aic32x4->rstn_gpio, diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index c2d74025bf4b..88205bc97198 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -16,6 +16,7 @@ struct regmap_config; extern const struct regmap_config aic32x4_regmap_config; int aic32x4_probe(struct device *dev, struct regmap *regmap); int aic32x4_remove(struct device *dev); +int aic32x4_register_clocks(struct device *dev, const char *mclk_name); /* tlv320aic32x4 register space (in decimal to match datasheet) */ @@ -205,4 +206,14 @@ int aic32x4_remove(struct device *dev); #define AIC32X4_RMICPGANIN_IN1L_10K 0x10 #define AIC32X4_RMICPGANIN_CM1R_10K 0x40 +/* Common mask and enable for all of the dividers */ +#define AIC32X4_DIVEN BIT(7) +#define AIC32X4_DIV_MASK GENMASK(6, 0) + +/* Clock Limits */ +#define AIC32X4_MAX_DOSR_FREQ 6200000 +#define AIC32X4_MIN_DOSR_FREQ 2800000 +#define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 +#define AIC32X4_MAX_PLL_CLKIN 20000000 + #endif /* _TLV320AIC32X4_H */ diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 283583d1db60..516d17cb2182 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1609,7 +1609,6 @@ static int aic3x_probe(struct snd_soc_component *component) struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); int ret, i; - INIT_LIST_HEAD(&aic3x->list); aic3x->component = component; for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { @@ -1873,6 +1872,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, if (ret != 0) goto err_gpio; + INIT_LIST_HEAD(&aic3x->list); list_add(&aic3x->list, &reset_list); return 0; @@ -1889,6 +1889,8 @@ static int aic3x_i2c_remove(struct i2c_client *client) { struct aic3x_priv *aic3x = i2c_get_clientdata(client); + list_del(&aic3x->list); + if (gpio_is_valid(aic3x->gpio_reset) && !aic3x_is_shared_reset(aic3x)) { gpio_set_value(aic3x->gpio_reset, 0); diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 981f88a5f615..a04a7cedd99d 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5188,6 +5188,7 @@ static int wcd9335_slim_status(struct slim_device *sdev, wcd->slim = sdev; wcd->slim_ifc_dev = of_slim_get_device(sdev->ctrl, ifc_dev_np); + of_node_put(ifc_dev_np); if (!wcd->slim_ifc_dev) { dev_err(dev, "Unable to get SLIM Interface device\n"); return -EINVAL; diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 4466e195b66d..b32e8313954d 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -646,6 +646,8 @@ static int wm5102_adsp_power_ev(struct snd_soc_dapm_widget *w, return ret; } } + + wm_adsp2_set_dspclk(w, v); break; case SND_SOC_DAPM_POST_PMD: @@ -659,7 +661,7 @@ static int wm5102_adsp_power_ev(struct snd_soc_dapm_widget *w, break; } - return wm_adsp2_early_event(w, kcontrol, event, v); + return wm_adsp_early_event(w, kcontrol, event); } static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index b25877fa529d..1f500cc8d96a 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -211,7 +211,9 @@ static int wm5110_adsp_power_ev(struct snd_soc_dapm_widget *w, v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; - return wm_adsp2_early_event(w, kcontrol, event, v); + wm_adsp2_set_dspclk(w, v); + + return wm_adsp_early_event(w, kcontrol, event); } static const struct reg_sequence wm5110_no_dre_left_enable[] = { diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index b93fdc8d2d6f..0aa62b26f61c 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -227,6 +227,89 @@ */ #define WM_ADSP_FW_EVENT_SHUTDOWN 0x000001 +/* + * HALO system info + */ +#define HALO_AHBM_WINDOW_DEBUG_0 0x02040 +#define HALO_AHBM_WINDOW_DEBUG_1 0x02044 + +/* + * HALO core + */ +#define HALO_SCRATCH1 0x005c0 +#define HALO_SCRATCH2 0x005c8 +#define HALO_SCRATCH3 0x005d0 +#define HALO_SCRATCH4 0x005d8 +#define HALO_CCM_CORE_CONTROL 0x41000 +#define HALO_CORE_SOFT_RESET 0x00010 +#define HALO_WDT_CONTROL 0x47000 + +/* + * HALO MPU banks + */ +#define HALO_MPU_XMEM_ACCESS_0 0x43000 +#define HALO_MPU_YMEM_ACCESS_0 0x43004 +#define HALO_MPU_WINDOW_ACCESS_0 0x43008 +#define HALO_MPU_XREG_ACCESS_0 0x4300C +#define HALO_MPU_YREG_ACCESS_0 0x43014 +#define HALO_MPU_XMEM_ACCESS_1 0x43018 +#define HALO_MPU_YMEM_ACCESS_1 0x4301C +#define HALO_MPU_WINDOW_ACCESS_1 0x43020 +#define HALO_MPU_XREG_ACCESS_1 0x43024 +#define HALO_MPU_YREG_ACCESS_1 0x4302C +#define HALO_MPU_XMEM_ACCESS_2 0x43030 +#define HALO_MPU_YMEM_ACCESS_2 0x43034 +#define HALO_MPU_WINDOW_ACCESS_2 0x43038 +#define HALO_MPU_XREG_ACCESS_2 0x4303C +#define HALO_MPU_YREG_ACCESS_2 0x43044 +#define HALO_MPU_XMEM_ACCESS_3 0x43048 +#define HALO_MPU_YMEM_ACCESS_3 0x4304C +#define HALO_MPU_WINDOW_ACCESS_3 0x43050 +#define HALO_MPU_XREG_ACCESS_3 0x43054 +#define HALO_MPU_YREG_ACCESS_3 0x4305C +#define HALO_MPU_XM_VIO_ADDR 0x43100 +#define HALO_MPU_XM_VIO_STATUS 0x43104 +#define HALO_MPU_YM_VIO_ADDR 0x43108 +#define HALO_MPU_YM_VIO_STATUS 0x4310C +#define HALO_MPU_PM_VIO_ADDR 0x43110 +#define HALO_MPU_PM_VIO_STATUS 0x43114 +#define HALO_MPU_LOCK_CONFIG 0x43140 + +/* + * HALO_AHBM_WINDOW_DEBUG_1 + */ +#define HALO_AHBM_CORE_ERR_ADDR_MASK 0x0fffff00 +#define HALO_AHBM_CORE_ERR_ADDR_SHIFT 8 +#define HALO_AHBM_FLAGS_ERR_MASK 0x000000ff + +/* + * HALO_CCM_CORE_CONTROL + */ +#define HALO_CORE_EN 0x00000001 + +/* + * HALO_CORE_SOFT_RESET + */ +#define HALO_CORE_SOFT_RESET_MASK 0x00000001 + +/* + * HALO_WDT_CONTROL + */ +#define HALO_WDT_EN_MASK 0x00000001 + +/* + * HALO_MPU_?M_VIO_STATUS + */ +#define HALO_MPU_VIO_STS_MASK 0x007e0000 +#define HALO_MPU_VIO_STS_SHIFT 17 +#define HALO_MPU_VIO_ERR_WR_MASK 0x00008000 +#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff +#define HALO_MPU_VIO_ERR_SRC_SHIFT 0 + +static struct wm_adsp_ops wm_adsp1_ops; +static struct wm_adsp_ops wm_adsp2_ops[]; +static struct wm_adsp_ops wm_halo_ops; + struct wm_adsp_buf { struct list_head list; void *buf; @@ -306,6 +389,12 @@ struct wm_adsp_system_config_xm_hdr { __be32 build_job_number; }; +struct wm_halo_system_config_xm_hdr { + __be32 halo_heartbeat; + __be32 build_job_name[3]; + __be32 build_job_number; +}; + struct wm_adsp_alg_xm_struct { __be32 magic; __be32 smoothing; @@ -532,12 +621,18 @@ static const char *wm_adsp_mem_region_name(unsigned int type) switch (type) { case WMFW_ADSP1_PM: return "PM"; + case WMFW_HALO_PM_PACKED: + return "PM_PACKED"; case WMFW_ADSP1_DM: return "DM"; case WMFW_ADSP2_XM: return "XM"; + case WMFW_HALO_XM_PACKED: + return "XM_PACKED"; case WMFW_ADSP2_YM: return "YM"; + case WMFW_HALO_YM_PACKED: + return "YM_PACKED"; case WMFW_ADSP1_ZM: return "ZM"; default: @@ -769,17 +864,12 @@ static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp, static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *mem, unsigned int offset) { - if (WARN_ON(!mem)) - return offset; switch (mem->type) { case WMFW_ADSP1_PM: return mem->base + (offset * 3); case WMFW_ADSP1_DM: - return mem->base + (offset * 2); case WMFW_ADSP2_XM: - return mem->base + (offset * 2); case WMFW_ADSP2_YM: - return mem->base + (offset * 2); case WMFW_ADSP1_ZM: return mem->base + (offset * 2); default: @@ -788,49 +878,72 @@ static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *mem, } } -static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) +static unsigned int wm_halo_region_to_reg(struct wm_adsp_region const *mem, + unsigned int offset) +{ + switch (mem->type) { + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + return mem->base + (offset * 4); + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + return (mem->base + (offset * 3)) & ~0x3; + case WMFW_HALO_PM_PACKED: + return mem->base + (offset * 5); + default: + WARN(1, "Unknown memory region type"); + return offset; + } +} + +static void wm_adsp_read_fw_status(struct wm_adsp *dsp, + int noffs, unsigned int *offs) { - unsigned int scratch[4]; - unsigned int addr = dsp->base + ADSP2_SCRATCH0; unsigned int i; int ret; - for (i = 0; i < ARRAY_SIZE(scratch); ++i) { - ret = regmap_read(dsp->regmap, addr + i, &scratch[i]); + for (i = 0; i < noffs; ++i) { + ret = regmap_read(dsp->regmap, dsp->base + offs[i], &offs[i]); if (ret) { adsp_err(dsp, "Failed to read SCRATCH%u: %d\n", i, ret); return; } } +} + +static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) +{ + unsigned int offs[] = { + ADSP2_SCRATCH0, ADSP2_SCRATCH1, ADSP2_SCRATCH2, ADSP2_SCRATCH3, + }; + + wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - scratch[0], scratch[1], scratch[2], scratch[3]); + offs[0], offs[1], offs[2], offs[3]); } static void wm_adsp2v2_show_fw_status(struct wm_adsp *dsp) { - unsigned int scratch[2]; - int ret; + unsigned int offs[] = { ADSP2V2_SCRATCH0_1, ADSP2V2_SCRATCH2_3 }; - ret = regmap_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH0_1, - &scratch[0]); - if (ret) { - adsp_err(dsp, "Failed to read SCRATCH0_1: %d\n", ret); - return; - } + wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); - ret = regmap_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH2_3, - &scratch[1]); - if (ret) { - adsp_err(dsp, "Failed to read SCRATCH2_3: %d\n", ret); - return; - } + adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + offs[0] & 0xFFFF, offs[0] >> 16, + offs[1] & 0xFFFF, offs[1] >> 16); +} + +static void wm_halo_show_fw_status(struct wm_adsp *dsp) +{ + unsigned int offs[] = { + HALO_SCRATCH1, HALO_SCRATCH2, HALO_SCRATCH3, HALO_SCRATCH4, + }; + + wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - scratch[0] & 0xFFFF, - scratch[0] >> 16, - scratch[1] & 0xFFFF, - scratch[1] >> 16); + offs[0], offs[1], offs[2], offs[3]); } static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) @@ -851,7 +964,7 @@ static int wm_coeff_base_reg(struct wm_coeff_ctl *ctl, unsigned int *reg) return -EINVAL; } - *reg = wm_adsp_region_to_reg(mem, ctl->alg_region.base + ctl->offset); + *reg = dsp->ops->region_to_reg(mem, ctl->alg_region.base + ctl->offset); return 0; } @@ -1339,28 +1452,33 @@ static int wm_adsp_create_control(struct wm_adsp *dsp, case 1: snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %x", dsp->name, region_name, alg_region->alg); + subname = NULL; /* don't append subname */ break; - default: + case 2: ret = snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s%c %.12s %x", dsp->name, *region_name, wm_adsp_fw_text[dsp->fw], alg_region->alg); + break; + default: + ret = snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "%s %.12s %x", dsp->name, + wm_adsp_fw_text[dsp->fw], alg_region->alg); + break; + } - /* Truncate the subname from the start if it is too long */ - if (subname) { - int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; - int skip = 0; + if (subname) { + int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; + int skip = 0; - if (dsp->component->name_prefix) - avail -= strlen(dsp->component->name_prefix) + 1; + if (dsp->component->name_prefix) + avail -= strlen(dsp->component->name_prefix) + 1; - if (subname_len > avail) - skip = subname_len - avail; + /* Truncate the subname from the start if it is too long */ + if (subname_len > avail) + skip = subname_len - avail; - snprintf(name + ret, - SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, " %.*s", - subname_len - skip, subname + skip); - } - break; + snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, + " %.*s", subname_len - skip, subname + skip); } list_for_each_entry(ctl, &dsp->ctl_list, list) { @@ -1647,6 +1765,62 @@ static int wm_adsp_parse_coeff(struct wm_adsp *dsp, return 0; } +static unsigned int wm_adsp1_parse_sizes(struct wm_adsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware) +{ + const struct wmfw_adsp1_sizes *adsp1_sizes; + + adsp1_sizes = (void *)&firmware->data[pos]; + + adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", file, + le32_to_cpu(adsp1_sizes->dm), le32_to_cpu(adsp1_sizes->pm), + le32_to_cpu(adsp1_sizes->zm)); + + return pos + sizeof(*adsp1_sizes); +} + +static unsigned int wm_adsp2_parse_sizes(struct wm_adsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware) +{ + const struct wmfw_adsp2_sizes *adsp2_sizes; + + adsp2_sizes = (void *)&firmware->data[pos]; + + adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", file, + le32_to_cpu(adsp2_sizes->xm), le32_to_cpu(adsp2_sizes->ym), + le32_to_cpu(adsp2_sizes->pm), le32_to_cpu(adsp2_sizes->zm)); + + return pos + sizeof(*adsp2_sizes); +} + +static bool wm_adsp_validate_version(struct wm_adsp *dsp, unsigned int version) +{ + switch (version) { + case 0: + adsp_warn(dsp, "Deprecated file format %d\n", version); + return true; + case 1: + case 2: + return true; + default: + return false; + } +} + +static bool wm_halo_validate_version(struct wm_adsp *dsp, unsigned int version) +{ + switch (version) { + case 3: + return true; + default: + return false; + } +} + static int wm_adsp_load(struct wm_adsp *dsp) { LIST_HEAD(buf_list); @@ -1655,7 +1829,6 @@ static int wm_adsp_load(struct wm_adsp *dsp) unsigned int pos = 0; const struct wmfw_header *header; const struct wmfw_adsp1_sizes *adsp1_sizes; - const struct wmfw_adsp2_sizes *adsp2_sizes; const struct wmfw_footer *footer; const struct wmfw_region *region; const struct wm_adsp_region *mem; @@ -1664,7 +1837,7 @@ static int wm_adsp_load(struct wm_adsp *dsp) struct wm_adsp_buf *buf; unsigned int reg; int regions = 0; - int ret, offset, type, sizes; + int ret, offset, type; file = kzalloc(PAGE_SIZE, GFP_KERNEL); if (file == NULL) @@ -1695,15 +1868,7 @@ static int wm_adsp_load(struct wm_adsp *dsp) goto out_fw; } - switch (header->ver) { - case 0: - adsp_warn(dsp, "%s: Depreciated file format %d\n", - file, header->ver); - break; - case 1: - case 2: - break; - default: + if (!dsp->ops->validate_version(dsp, header->ver)) { adsp_err(dsp, "%s: unknown file format %d\n", file, header->ver); goto out_fw; @@ -1718,39 +1883,13 @@ static int wm_adsp_load(struct wm_adsp *dsp) goto out_fw; } - switch (dsp->type) { - case WMFW_ADSP1: - pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); - adsp1_sizes = (void *)&(header[1]); - footer = (void *)&(adsp1_sizes[1]); - sizes = sizeof(*adsp1_sizes); - - adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", - file, le32_to_cpu(adsp1_sizes->dm), - le32_to_cpu(adsp1_sizes->pm), - le32_to_cpu(adsp1_sizes->zm)); - break; - - case WMFW_ADSP2: - pos = sizeof(*header) + sizeof(*adsp2_sizes) + sizeof(*footer); - adsp2_sizes = (void *)&(header[1]); - footer = (void *)&(adsp2_sizes[1]); - sizes = sizeof(*adsp2_sizes); - - adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", - file, le32_to_cpu(adsp2_sizes->xm), - le32_to_cpu(adsp2_sizes->ym), - le32_to_cpu(adsp2_sizes->pm), - le32_to_cpu(adsp2_sizes->zm)); - break; + pos = sizeof(*header); + pos = dsp->ops->parse_sizes(dsp, file, pos, firmware); - default: - WARN(1, "Unknown DSP type"); - goto out_fw; - } + footer = (void *)&firmware->data[pos]; + pos += sizeof(*footer); - if (le32_to_cpu(header->len) != sizeof(*header) + - sizes + sizeof(*footer)) { + if (le32_to_cpu(header->len) != pos) { adsp_err(dsp, "%s: unexpected header length %d\n", file, le32_to_cpu(header->len)); goto out_fw; @@ -1767,7 +1906,6 @@ static int wm_adsp_load(struct wm_adsp *dsp) text = NULL; offset = le32_to_cpu(region->offset) & 0xffffff; type = be32_to_cpu(region->type) & 0xff; - mem = wm_adsp_find_region(dsp, type); switch (type) { case WMFW_NAME_TEXT: @@ -1795,8 +1933,17 @@ static int wm_adsp_load(struct wm_adsp *dsp) case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: case WMFW_ADSP1_ZM: + case WMFW_HALO_PM_PACKED: + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + mem = wm_adsp_find_region(dsp, type); + if (!mem) { + adsp_err(dsp, "No region of type: %x\n", type); + goto out_fw; + } + region_name = wm_adsp_mem_region_name(type); - reg = wm_adsp_region_to_reg(mem, offset); + reg = dsp->ops->region_to_reg(mem, offset); break; default: adsp_warn(dsp, @@ -1909,7 +2056,7 @@ static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, } /* Read the terminator first to validate the length */ - reg = wm_adsp_region_to_reg(mem, pos + len); + reg = dsp->ops->region_to_reg(mem, pos + len); ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); if (ret != 0) { @@ -1929,7 +2076,7 @@ static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, if (!alg) return ERR_PTR(-ENOMEM); - reg = wm_adsp_region_to_reg(mem, pos); + reg = dsp->ops->region_to_reg(mem, pos); ret = regmap_raw_read(dsp->regmap, reg, alg, len); if (ret != 0) { @@ -1989,6 +2136,47 @@ static void wm_adsp_free_alg_regions(struct wm_adsp *dsp) } } +static void wmfw_parse_id_header(struct wm_adsp *dsp, + struct wmfw_id_hdr *fw, int nalgs) +{ + dsp->fw_id = be32_to_cpu(fw->id); + dsp->fw_id_version = be32_to_cpu(fw->ver); + + adsp_info(dsp, "Firmware: %x v%d.%d.%d, %d algorithms\n", + dsp->fw_id, (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, + nalgs); +} + +static void wmfw_v3_parse_id_header(struct wm_adsp *dsp, + struct wmfw_v3_id_hdr *fw, int nalgs) +{ + dsp->fw_id = be32_to_cpu(fw->id); + dsp->fw_id_version = be32_to_cpu(fw->ver); + dsp->fw_vendor_id = be32_to_cpu(fw->vendor_id); + + adsp_info(dsp, "Firmware: %x vendor: 0x%x v%d.%d.%d, %d algorithms\n", + dsp->fw_id, dsp->fw_vendor_id, + (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, + nalgs); +} + +static int wm_adsp_create_regions(struct wm_adsp *dsp, __be32 id, int nregions, + int *type, __be32 *base) +{ + struct wm_adsp_alg_region *alg_region; + int i; + + for (i = 0; i < nregions; i++) { + alg_region = wm_adsp_create_region(dsp, type[i], id, base[i]); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + } + + return 0; +} + static int wm_adsp1_setup_algs(struct wm_adsp *dsp) { struct wmfw_adsp1_id_hdr adsp1_id; @@ -2012,13 +2200,8 @@ static int wm_adsp1_setup_algs(struct wm_adsp *dsp) } n_algs = be32_to_cpu(adsp1_id.n_algs); - dsp->fw_id = be32_to_cpu(adsp1_id.fw.id); - adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n", - dsp->fw_id, - (be32_to_cpu(adsp1_id.fw.ver) & 0xff0000) >> 16, - (be32_to_cpu(adsp1_id.fw.ver) & 0xff00) >> 8, - be32_to_cpu(adsp1_id.fw.ver) & 0xff, - n_algs); + + wmfw_parse_id_header(dsp, &adsp1_id.fw, n_algs); alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_ZM, adsp1_id.fw.id, adsp1_id.zm); @@ -2118,14 +2301,8 @@ static int wm_adsp2_setup_algs(struct wm_adsp *dsp) } n_algs = be32_to_cpu(adsp2_id.n_algs); - dsp->fw_id = be32_to_cpu(adsp2_id.fw.id); - dsp->fw_id_version = be32_to_cpu(adsp2_id.fw.ver); - adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n", - dsp->fw_id, - (dsp->fw_id_version & 0xff0000) >> 16, - (dsp->fw_id_version & 0xff00) >> 8, - dsp->fw_id_version & 0xff, - n_algs); + + wmfw_parse_id_header(dsp, &adsp2_id.fw, n_algs); alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_XM, adsp2_id.fw.id, adsp2_id.xm); @@ -2230,6 +2407,78 @@ out: return ret; } +static int wm_halo_create_regions(struct wm_adsp *dsp, __be32 id, + __be32 xm_base, __be32 ym_base) +{ + int types[] = { + WMFW_ADSP2_XM, WMFW_HALO_XM_PACKED, + WMFW_ADSP2_YM, WMFW_HALO_YM_PACKED + }; + __be32 bases[] = { xm_base, xm_base, ym_base, ym_base }; + + return wm_adsp_create_regions(dsp, id, ARRAY_SIZE(types), types, bases); +} + +static int wm_halo_setup_algs(struct wm_adsp *dsp) +{ + struct wmfw_halo_id_hdr halo_id; + struct wmfw_halo_alg_hdr *halo_alg; + const struct wm_adsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &halo_id, + sizeof(halo_id)); + if (ret != 0) { + adsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(halo_id.n_algs); + + wmfw_v3_parse_id_header(dsp, &halo_id.fw, n_algs); + + ret = wm_halo_create_regions(dsp, halo_id.fw.id, + halo_id.ym_base, halo_id.ym_base); + if (ret) + return ret; + + /* Calculate offset and length in DSP words */ + pos = sizeof(halo_id) / sizeof(u32); + len = (sizeof(*halo_alg) * n_algs) / sizeof(u32); + + halo_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(halo_alg)) + return PTR_ERR(halo_alg); + + for (i = 0; i < n_algs; i++) { + adsp_info(dsp, + "%d: ID %x v%d.%d.%d XM@%x YM@%x\n", + i, be32_to_cpu(halo_alg[i].alg.id), + (be32_to_cpu(halo_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(halo_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(halo_alg[i].alg.ver) & 0xff, + be32_to_cpu(halo_alg[i].xm_base), + be32_to_cpu(halo_alg[i].ym_base)); + + ret = wm_halo_create_regions(dsp, halo_alg[i].alg.id, + halo_alg[i].xm_base, + halo_alg[i].ym_base); + if (ret) + goto out; + } + +out: + kfree(halo_alg); + return ret; +} + static int wm_adsp_load_coeff(struct wm_adsp *dsp) { LIST_HEAD(buf_list); @@ -2324,7 +2573,7 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) adsp_err(dsp, "No ZM\n"); break; } - reg = wm_adsp_region_to_reg(mem, 0); + reg = dsp->ops->region_to_reg(mem, 0); } else { region_name = "register"; @@ -2336,6 +2585,9 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) case WMFW_ADSP1_ZM: case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + case WMFW_HALO_PM_PACKED: adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n", file, blocks, le32_to_cpu(blk->len), type, le32_to_cpu(blk->id)); @@ -2350,7 +2602,7 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) le32_to_cpu(blk->id)); if (alg_region) { reg = alg_region->base; - reg = wm_adsp_region_to_reg(mem, reg); + reg = dsp->ops->region_to_reg(mem, reg); reg += offset; } else { adsp_err(dsp, "No %x for algorithm %x\n", @@ -2464,6 +2716,8 @@ static int wm_adsp_common_init(struct wm_adsp *dsp) int wm_adsp1_init(struct wm_adsp *dsp) { + dsp->ops = &wm_adsp1_ops; + return wm_adsp_common_init(dsp); } EXPORT_SYMBOL_GPL(wm_adsp1_init); @@ -2583,23 +2837,11 @@ err_mutex: } EXPORT_SYMBOL_GPL(wm_adsp1_event); -static int wm_adsp2_ena(struct wm_adsp *dsp) +static int wm_adsp2v2_enable_core(struct wm_adsp *dsp) { unsigned int val; int ret, count; - switch (dsp->rev) { - case 0: - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, ADSP2_SYS_ENA); - if (ret != 0) - return ret; - break; - default: - break; - } - /* Wait for the RAM to start, should be near instantaneous */ for (count = 0; count < 10; ++count) { ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, &val); @@ -2622,7 +2864,78 @@ static int wm_adsp2_ena(struct wm_adsp *dsp) return 0; } -static void wm_adsp2_boot_work(struct work_struct *work) +static int wm_adsp2_enable_core(struct wm_adsp *dsp) +{ + int ret; + + ret = regmap_update_bits_async(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, ADSP2_SYS_ENA); + if (ret != 0) + return ret; + + return wm_adsp2v2_enable_core(dsp); +} + +static int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct regmap *regmap = dsp->regmap; + unsigned int code0, code1, lock_reg; + + if (!(lock_regions & WM_ADSP2_REGION_ALL)) + return 0; + + lock_regions &= WM_ADSP2_REGION_ALL; + lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + + while (lock_regions) { + code0 = code1 = 0; + if (lock_regions & BIT(0)) { + code0 = ADSP2_LOCK_CODE_0; + code1 = ADSP2_LOCK_CODE_1; + } + if (lock_regions & BIT(1)) { + code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; + code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + } + regmap_write(regmap, lock_reg, code0); + regmap_write(regmap, lock_reg, code1); + lock_regions >>= 2; + lock_reg += 2; + } + + return 0; +} + +static int wm_adsp2_enable_memory(struct wm_adsp *dsp) +{ + return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, ADSP2_MEM_ENA); +} + +static void wm_adsp2_disable_memory(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); +} + +static void wm_adsp2_disable_core(struct wm_adsp *dsp) +{ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); +} + +static void wm_adsp2v2_disable_core(struct wm_adsp *dsp) +{ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); +} + +static void wm_adsp_boot_work(struct work_struct *work) { struct wm_adsp *dsp = container_of(work, struct wm_adsp, @@ -2631,20 +2944,23 @@ static void wm_adsp2_boot_work(struct work_struct *work) mutex_lock(&dsp->pwr_lock); - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, ADSP2_MEM_ENA); - if (ret != 0) - goto err_mutex; + if (dsp->ops->enable_memory) { + ret = dsp->ops->enable_memory(dsp); + if (ret != 0) + goto err_mutex; + } - ret = wm_adsp2_ena(dsp); - if (ret != 0) - goto err_mem; + if (dsp->ops->enable_core) { + ret = dsp->ops->enable_core(dsp); + if (ret != 0) + goto err_mem; + } ret = wm_adsp_load(dsp); if (ret != 0) goto err_ena; - ret = wm_adsp2_setup_algs(dsp); + ret = dsp->ops->setup_algs(dsp); if (ret != 0) goto err_ena; @@ -2657,17 +2973,8 @@ static void wm_adsp2_boot_work(struct work_struct *work) if (ret != 0) goto err_ena; - switch (dsp->rev) { - case 0: - /* Turn DSP back off until we are ready to run */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); - if (ret != 0) - goto err_ena; - break; - default: - break; - } + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); dsp->booted = true; @@ -2676,35 +2983,62 @@ static void wm_adsp2_boot_work(struct work_struct *work) return; err_ena: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); err_mem: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); + if (dsp->ops->disable_memory) + dsp->ops->disable_memory(dsp); err_mutex: mutex_unlock(&dsp->pwr_lock); } -static void wm_adsp2_set_dspclk(struct wm_adsp *dsp, unsigned int freq) +static int wm_halo_configure_mpu(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct reg_sequence config[] = { + { dsp->base + HALO_MPU_LOCK_CONFIG, 0x5555 }, + { dsp->base + HALO_MPU_LOCK_CONFIG, 0xAAAA }, + { dsp->base + HALO_MPU_XMEM_ACCESS_0, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_0, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_1, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_1, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_2, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_2, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_3, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_3, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_LOCK_CONFIG, 0 }, + }; + + return regmap_multi_reg_write(dsp->regmap, config, ARRAY_SIZE(config)); +} + +int wm_adsp2_set_dspclk(struct snd_soc_dapm_widget *w, unsigned int freq) { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct wm_adsp *dsp = &dsps[w->shift]; int ret; - switch (dsp->rev) { - case 0: - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CLOCKING, - ADSP2_CLK_SEL_MASK, - freq << ADSP2_CLK_SEL_SHIFT); - if (ret) { - adsp_err(dsp, "Failed to set clock rate: %d\n", ret); - return; - } - break; - default: - /* clock is handled by parent codec driver */ - break; - } + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CLOCKING, + ADSP2_CLK_SEL_MASK, + freq << ADSP2_CLK_SEL_SHIFT); + if (ret) + adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + + return ret; } +EXPORT_SYMBOL_GPL(wm_adsp2_set_dspclk); int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -2751,19 +3085,18 @@ EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); static void wm_adsp_stop_watchdog(struct wm_adsp *dsp) { - switch (dsp->rev) { - case 0: - case 1: - return; - default: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, - ADSP2_WDT_ENA_MASK, 0); - } + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, + ADSP2_WDT_ENA_MASK, 0); } -int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event, - unsigned int freq) +static void wm_halo_stop_watchdog(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + HALO_WDT_CONTROL, + HALO_WDT_EN_MASK, 0); +} + +int wm_adsp_early_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); @@ -2772,7 +3105,6 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - wm_adsp2_set_dspclk(dsp, freq); queue_work(system_unbound_wq, &dsp->boot_work); break; case SND_SOC_DAPM_PRE_PMD: @@ -2785,8 +3117,8 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, dsp->booted = false; - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); + if (dsp->ops->disable_memory) + dsp->ops->disable_memory(dsp); list_for_each_entry(ctl, &dsp->ctl_list, list) ctl->enabled = 0; @@ -2803,10 +3135,23 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, return 0; } -EXPORT_SYMBOL_GPL(wm_adsp2_early_event); +EXPORT_SYMBOL_GPL(wm_adsp_early_event); + +static int wm_adsp2_start_core(struct wm_adsp *dsp) +{ + return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, + ADSP2_CORE_ENA | ADSP2_START); +} + +static void wm_adsp2_stop_core(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, 0); +} -int wm_adsp2_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +int wm_adsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); @@ -2824,23 +3169,31 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, goto err; } - ret = wm_adsp2_ena(dsp); - if (ret != 0) - goto err; + if (dsp->ops->enable_core) { + ret = dsp->ops->enable_core(dsp); + if (ret != 0) + goto err; + } /* Sync set controls */ ret = wm_coeff_sync_controls(dsp); if (ret != 0) goto err; - wm_adsp2_lock(dsp, dsp->lock_regions); + if (dsp->ops->lock_memory) { + ret = dsp->ops->lock_memory(dsp, dsp->lock_regions); + if (ret != 0) { + adsp_err(dsp, "Error configuring MPU: %d\n", + ret); + goto err; + } + } - ret = regmap_update_bits(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA | ADSP2_START, - ADSP2_CORE_ENA | ADSP2_START); - if (ret != 0) - goto err; + if (dsp->ops->start_core) { + ret = dsp->ops->start_core(dsp); + if (ret != 0) + goto err; + } if (wm_adsp_fw[dsp->fw].num_caps != 0) { ret = wm_adsp_buffer_init(dsp); @@ -2851,60 +3204,33 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, dsp->running = true; mutex_unlock(&dsp->pwr_lock); - break; case SND_SOC_DAPM_PRE_PMD: /* Tell the firmware to cleanup */ wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN); - wm_adsp_stop_watchdog(dsp); + if (dsp->ops->stop_watchdog) + dsp->ops->stop_watchdog(dsp); /* Log firmware state, it can be useful for analysis */ - switch (dsp->rev) { - case 0: - wm_adsp2_show_fw_status(dsp); - break; - default: - wm_adsp2v2_show_fw_status(dsp); - break; - } + if (dsp->ops->show_fw_status) + dsp->ops->show_fw_status(dsp); mutex_lock(&dsp->pwr_lock); dsp->running = false; - regmap_update_bits(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA | ADSP2_START, 0); - - /* Make sure DMAs are quiesced */ - switch (dsp->rev) { - case 0: - regmap_write(dsp->regmap, - dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2_WDMA_CONFIG_2, 0); - - regmap_update_bits(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); - break; - default: - regmap_write(dsp->regmap, - dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); - break; - } + if (dsp->ops->stop_core) + dsp->ops->stop_core(dsp); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); if (wm_adsp_fw[dsp->fw].num_caps != 0) wm_adsp_buffer_free(dsp); + dsp->fatal_error = false; + mutex_unlock(&dsp->pwr_lock); adsp_dbg(dsp, "Execution stopped\n"); @@ -2916,12 +3242,31 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, return 0; err: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); + if (dsp->ops->stop_core) + dsp->ops->stop_core(dsp); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); mutex_unlock(&dsp->pwr_lock); return ret; } -EXPORT_SYMBOL_GPL(wm_adsp2_event); +EXPORT_SYMBOL_GPL(wm_adsp_event); + +static int wm_halo_start_core(struct wm_adsp *dsp) +{ + return regmap_update_bits(dsp->regmap, + dsp->base + HALO_CCM_CORE_CONTROL, + HALO_CORE_EN, HALO_CORE_EN); +} + +static void wm_halo_stop_core(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + HALO_CCM_CORE_CONTROL, + HALO_CORE_EN, 0); + + /* reset halo core with CORE_SOFT_RESET */ + regmap_update_bits(dsp->regmap, dsp->base + HALO_CORE_SOFT_RESET, + HALO_CORE_SOFT_RESET_MASK, 1); +} int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component) { @@ -2967,17 +3312,39 @@ int wm_adsp2_init(struct wm_adsp *dsp) "Failed to clear memory retention: %d\n", ret); return ret; } + + dsp->ops = &wm_adsp2_ops[0]; + break; + case 1: + dsp->ops = &wm_adsp2_ops[1]; break; default: + dsp->ops = &wm_adsp2_ops[2]; break; } - INIT_WORK(&dsp->boot_work, wm_adsp2_boot_work); + INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); return 0; } EXPORT_SYMBOL_GPL(wm_adsp2_init); +int wm_halo_init(struct wm_adsp *dsp) +{ + int ret; + + ret = wm_adsp_common_init(dsp); + if (ret) + return ret; + + dsp->ops = &wm_halo_ops; + + INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_halo_init); + void wm_adsp2_remove(struct wm_adsp *dsp) { struct wm_coeff_ctl *ctl; @@ -3000,6 +3367,9 @@ static int wm_adsp_compr_attach(struct wm_adsp_compr *compr) { struct wm_adsp_compr_buf *buf = NULL, *tmp; + if (compr->dsp->fatal_error) + return -EINVAL; + list_for_each_entry(tmp, &compr->dsp->buffer_list, list) { if (!tmp->name || !strcmp(compr->name, tmp->name)) { buf = tmp; @@ -3219,7 +3589,7 @@ static int wm_adsp_read_data_block(struct wm_adsp *dsp, int mem_type, if (!mem) return -EINVAL; - reg = wm_adsp_region_to_reg(mem, mem_addr); + reg = dsp->ops->region_to_reg(mem, mem_addr); ret = regmap_raw_read(dsp->regmap, reg, data, sizeof(*data) * num_words); @@ -3247,7 +3617,7 @@ static int wm_adsp_write_data_word(struct wm_adsp *dsp, int mem_type, if (!mem) return -EINVAL; - reg = wm_adsp_region_to_reg(mem, mem_addr); + reg = dsp->ops->region_to_reg(mem, mem_addr); data = cpu_to_be32(data & 0x00ffffffu); @@ -3358,7 +3728,7 @@ static int wm_adsp_buffer_parse_legacy(struct wm_adsp *dsp) return -ENOMEM; alg_region = wm_adsp_find_alg_region(dsp, WMFW_ADSP2_XM, dsp->fw_id); - xmalg = sizeof(struct wm_adsp_system_config_xm_hdr) / sizeof(__be32); + xmalg = dsp->ops->sys_config_size / sizeof(__be32); addr = alg_region->base + xmalg + ALG_XM_FIELD(magic); ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, &magic); @@ -3535,11 +3905,11 @@ static int wm_adsp_buffer_get_error(struct wm_adsp_compr_buf *buf) ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(error), &buf->error); if (ret < 0) { - adsp_err(buf->dsp, "Failed to check buffer error: %d\n", ret); + compr_err(buf, "Failed to check buffer error: %d\n", ret); return ret; } if (buf->error != 0) { - adsp_err(buf->dsp, "Buffer error occurred: %d\n", buf->error); + compr_err(buf, "Buffer error occurred: %d\n", buf->error); return -EIO; } @@ -3571,8 +3941,6 @@ int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd) if (ret < 0) break; - wm_adsp_buffer_clear(compr->buf); - /* Trigger the IRQ at one fragment of data */ ret = wm_adsp_buffer_write(compr->buf, HOST_BUFFER_FIELD(high_water_mark), @@ -3584,6 +3952,7 @@ int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd) } break; case SNDRV_PCM_TRIGGER_STOP: + wm_adsp_buffer_clear(compr->buf); break; default: ret = -EINVAL; @@ -3886,36 +4255,20 @@ int wm_adsp_compr_copy(struct snd_compr_stream *stream, char __user *buf, } EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); -int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) +static void wm_adsp_fatal_error(struct wm_adsp *dsp) { - struct regmap *regmap = dsp->regmap; - unsigned int code0, code1, lock_reg; - - if (!(lock_regions & WM_ADSP2_REGION_ALL)) - return 0; + struct wm_adsp_compr *compr; - lock_regions &= WM_ADSP2_REGION_ALL; - lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + dsp->fatal_error = true; - while (lock_regions) { - code0 = code1 = 0; - if (lock_regions & BIT(0)) { - code0 = ADSP2_LOCK_CODE_0; - code1 = ADSP2_LOCK_CODE_1; - } - if (lock_regions & BIT(1)) { - code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; - code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + list_for_each_entry(compr, &dsp->compr_list, list) { + if (compr->stream) { + snd_compr_stop_error(compr->stream, + SNDRV_PCM_STATE_XRUN); + snd_compr_fragment_elapsed(compr->stream); } - regmap_write(regmap, lock_reg, code0); - regmap_write(regmap, lock_reg, code1); - lock_regions >>= 2; - lock_reg += 2; } - - return 0; } -EXPORT_SYMBOL_GPL(wm_adsp2_lock); irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) { @@ -3923,16 +4276,19 @@ irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) struct regmap *regmap = dsp->regmap; int ret = 0; + mutex_lock(&dsp->pwr_lock); + ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); if (ret) { adsp_err(dsp, "Failed to read Region Lock Ctrl register: %d\n", ret); - return IRQ_HANDLED; + goto error; } if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { adsp_err(dsp, "watchdog timeout error\n"); wm_adsp_stop_watchdog(dsp); + wm_adsp_fatal_error(dsp); } if (val & (ADSP2_SLAVE_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { @@ -3946,7 +4302,7 @@ irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) adsp_err(dsp, "Failed to read Bus Err Addr register: %d\n", ret); - return IRQ_HANDLED; + goto error; } adsp_err(dsp, "bus error address = 0x%x\n", @@ -3959,7 +4315,7 @@ irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) adsp_err(dsp, "Failed to read Pmem Xmem Err Addr register: %d\n", ret); - return IRQ_HANDLED; + goto error; } adsp_err(dsp, "xmem error address = 0x%x\n", @@ -3972,8 +4328,166 @@ irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) regmap_update_bits(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, ADSP2_CTRL_ERR_EINT, ADSP2_CTRL_ERR_EINT); +error: + mutex_unlock(&dsp->pwr_lock); + return IRQ_HANDLED; } EXPORT_SYMBOL_GPL(wm_adsp2_bus_error); +irqreturn_t wm_halo_bus_error(struct wm_adsp *dsp) +{ + struct regmap *regmap = dsp->regmap; + unsigned int fault[6]; + struct reg_sequence clear[] = { + { dsp->base + HALO_MPU_XM_VIO_STATUS, 0x0 }, + { dsp->base + HALO_MPU_YM_VIO_STATUS, 0x0 }, + { dsp->base + HALO_MPU_PM_VIO_STATUS, 0x0 }, + }; + int ret; + + mutex_lock(&dsp->pwr_lock); + + ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_1, + fault); + if (ret) { + adsp_warn(dsp, "Failed to read AHB DEBUG_1: %d\n", ret); + goto exit_unlock; + } + + adsp_warn(dsp, "AHB: STATUS: 0x%x ADDR: 0x%x\n", + *fault & HALO_AHBM_FLAGS_ERR_MASK, + (*fault & HALO_AHBM_CORE_ERR_ADDR_MASK) >> + HALO_AHBM_CORE_ERR_ADDR_SHIFT); + + ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_0, + fault); + if (ret) { + adsp_warn(dsp, "Failed to read AHB DEBUG_0: %d\n", ret); + goto exit_unlock; + } + + adsp_warn(dsp, "AHB: SYS_ADDR: 0x%x\n", *fault); + + ret = regmap_bulk_read(regmap, dsp->base + HALO_MPU_XM_VIO_ADDR, + fault, ARRAY_SIZE(fault)); + if (ret) { + adsp_warn(dsp, "Failed to read MPU fault info: %d\n", ret); + goto exit_unlock; + } + + adsp_warn(dsp, "XM: STATUS:0x%x ADDR:0x%x\n", fault[1], fault[0]); + adsp_warn(dsp, "YM: STATUS:0x%x ADDR:0x%x\n", fault[3], fault[2]); + adsp_warn(dsp, "PM: STATUS:0x%x ADDR:0x%x\n", fault[5], fault[4]); + + ret = regmap_multi_reg_write(dsp->regmap, clear, ARRAY_SIZE(clear)); + if (ret) + adsp_warn(dsp, "Failed to clear MPU status: %d\n", ret); + +exit_unlock: + mutex_unlock(&dsp->pwr_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_halo_bus_error); + +irqreturn_t wm_halo_wdt_expire(int irq, void *data) +{ + struct wm_adsp *dsp = data; + + mutex_lock(&dsp->pwr_lock); + + adsp_warn(dsp, "WDT Expiry Fault\n"); + wm_halo_stop_watchdog(dsp); + wm_adsp_fatal_error(dsp); + + mutex_unlock(&dsp->pwr_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_halo_wdt_expire); + +static struct wm_adsp_ops wm_adsp1_ops = { + .validate_version = wm_adsp_validate_version, + .parse_sizes = wm_adsp1_parse_sizes, + .region_to_reg = wm_adsp_region_to_reg, +}; + +static struct wm_adsp_ops wm_adsp2_ops[] = { + { + .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_adsp_validate_version, + .setup_algs = wm_adsp2_setup_algs, + .region_to_reg = wm_adsp_region_to_reg, + + .show_fw_status = wm_adsp2_show_fw_status, + + .enable_memory = wm_adsp2_enable_memory, + .disable_memory = wm_adsp2_disable_memory, + + .enable_core = wm_adsp2_enable_core, + .disable_core = wm_adsp2_disable_core, + + .start_core = wm_adsp2_start_core, + .stop_core = wm_adsp2_stop_core, + + }, + { + .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_adsp_validate_version, + .setup_algs = wm_adsp2_setup_algs, + .region_to_reg = wm_adsp_region_to_reg, + + .show_fw_status = wm_adsp2v2_show_fw_status, + + .enable_memory = wm_adsp2_enable_memory, + .disable_memory = wm_adsp2_disable_memory, + .lock_memory = wm_adsp2_lock, + + .enable_core = wm_adsp2v2_enable_core, + .disable_core = wm_adsp2v2_disable_core, + + .start_core = wm_adsp2_start_core, + .stop_core = wm_adsp2_stop_core, + }, + { + .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_adsp_validate_version, + .setup_algs = wm_adsp2_setup_algs, + .region_to_reg = wm_adsp_region_to_reg, + + .show_fw_status = wm_adsp2v2_show_fw_status, + .stop_watchdog = wm_adsp_stop_watchdog, + + .enable_memory = wm_adsp2_enable_memory, + .disable_memory = wm_adsp2_disable_memory, + .lock_memory = wm_adsp2_lock, + + .enable_core = wm_adsp2v2_enable_core, + .disable_core = wm_adsp2v2_disable_core, + + .start_core = wm_adsp2_start_core, + .stop_core = wm_adsp2_stop_core, + }, +}; + +static struct wm_adsp_ops wm_halo_ops = { + .sys_config_size = sizeof(struct wm_halo_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_halo_validate_version, + .setup_algs = wm_halo_setup_algs, + .region_to_reg = wm_halo_region_to_reg, + + .show_fw_status = wm_halo_show_fw_status, + .stop_watchdog = wm_halo_stop_watchdog, + + .lock_memory = wm_halo_configure_mpu, + + .start_core = wm_halo_start_core, + .stop_core = wm_halo_stop_core, +}; + MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 59e07ad16329..3631c9200c5d 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -54,6 +54,7 @@ struct wm_adsp_alg_region { struct wm_adsp_compr; struct wm_adsp_compr_buf; +struct wm_adsp_ops; struct wm_adsp { const char *part; @@ -66,7 +67,10 @@ struct wm_adsp { struct regmap *regmap; struct snd_soc_component *component; + struct wm_adsp_ops *ops; + unsigned int base; + unsigned int base_sysinfo; unsigned int sysclk_reg; unsigned int sysclk_mask; unsigned int sysclk_shift; @@ -75,6 +79,7 @@ struct wm_adsp { unsigned int fw_id; unsigned int fw_id_version; + unsigned int fw_vendor_id; const struct wm_adsp_region *mem; int num_mems; @@ -85,6 +90,7 @@ struct wm_adsp { bool preloaded; bool booted; bool running; + bool fatal_error; struct list_head ctl_list; @@ -105,6 +111,32 @@ struct wm_adsp { }; +struct wm_adsp_ops { + unsigned int sys_config_size; + + bool (*validate_version)(struct wm_adsp *dsp, unsigned int version); + unsigned int (*parse_sizes)(struct wm_adsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware); + int (*setup_algs)(struct wm_adsp *dsp); + unsigned int (*region_to_reg)(struct wm_adsp_region const *mem, + unsigned int offset); + + void (*show_fw_status)(struct wm_adsp *dsp); + void (*stop_watchdog)(struct wm_adsp *dsp); + + int (*enable_memory)(struct wm_adsp *dsp); + void (*disable_memory)(struct wm_adsp *dsp); + int (*lock_memory)(struct wm_adsp *dsp, unsigned int lock_regions); + + int (*enable_core)(struct wm_adsp *dsp); + void (*disable_core)(struct wm_adsp *dsp); + + int (*start_core)(struct wm_adsp *dsp); + void (*stop_core)(struct wm_adsp *dsp); +}; + #define WM_ADSP1(wname, num) \ SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, num, 0, NULL, 0, \ wm_adsp1_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) @@ -120,7 +152,7 @@ struct wm_adsp { .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD, \ .subseq = 100, /* Ensure we run after SYSCLK supply widget */ }, \ { .id = snd_soc_dapm_out_drv, .name = wname, \ - .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp2_event, \ + .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp_event, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD } #define WM_ADSP_FW_CONTROL(dspname, num) \ @@ -134,17 +166,22 @@ int wm_adsp2_init(struct wm_adsp *dsp); void wm_adsp2_remove(struct wm_adsp *dsp); int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component); int wm_adsp2_component_remove(struct wm_adsp *dsp, struct snd_soc_component *component); +int wm_halo_init(struct wm_adsp *dsp); + int wm_adsp1_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); -int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event, - unsigned int freq); -int wm_adsp2_lock(struct wm_adsp *adsp, unsigned int regions); +int wm_adsp_early_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + irqreturn_t wm_adsp2_bus_error(struct wm_adsp *adsp); +irqreturn_t wm_halo_bus_error(struct wm_adsp *dsp); +irqreturn_t wm_halo_wdt_expire(int irq, void *data); -int wm_adsp2_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event); +int wm_adsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +int wm_adsp2_set_dspclk(struct snd_soc_dapm_widget *w, unsigned int freq); int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); diff --git a/sound/soc/codecs/wmfw.h b/sound/soc/codecs/wmfw.h index 0c3f50acb8b1..14b2d1a2fc59 100644 --- a/sound/soc/codecs/wmfw.h +++ b/sound/soc/codecs/wmfw.h @@ -73,6 +73,14 @@ struct wmfw_id_hdr { __be32 ver; } __packed; +struct wmfw_v3_id_hdr { + __be32 core_id; + __be32 block_rev; + __be32 vendor_id; + __be32 id; + __be32 ver; +} __packed; + struct wmfw_adsp1_id_hdr { struct wmfw_id_hdr fw; __be32 zm; @@ -88,6 +96,15 @@ struct wmfw_adsp2_id_hdr { __be32 n_algs; } __packed; +struct wmfw_halo_id_hdr { + struct wmfw_v3_id_hdr fw; + __be32 xm_base; + __be32 xm_size; + __be32 ym_base; + __be32 ym_size; + __be32 n_algs; +} __packed; + struct wmfw_alg_hdr { __be32 id; __be32 ver; @@ -106,6 +123,14 @@ struct wmfw_adsp2_alg_hdr { __be32 ym; } __packed; +struct wmfw_halo_alg_hdr { + struct wmfw_alg_hdr alg; + __be32 xm_base; + __be32 xm_size; + __be32 ym_base; + __be32 ym_size; +} __packed; + struct wmfw_adsp_alg_data { __le32 id; u8 name[WMFW_MAX_ALG_NAME]; @@ -154,6 +179,7 @@ struct wmfw_coeff_item { #define WMFW_ADSP1 1 #define WMFW_ADSP2 2 +#define WMFW_HALO 4 #define WMFW_ABSOLUTE 0xf0 #define WMFW_ALGORITHM_DATA 0xf2 @@ -169,4 +195,8 @@ struct wmfw_coeff_item { #define WMFW_ADSP2_XM 5 #define WMFW_ADSP2_YM 6 +#define WMFW_HALO_PM_PACKED 0x10 +#define WMFW_HALO_XM_PACKED 0x11 +#define WMFW_HALO_YM_PACKED 0x12 + #endif diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 7b1d9970be8b..d87c842806bd 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -24,6 +24,13 @@ config SND_SOC_FSL_SAI This option is only useful for out-of-tree drivers since in-tree drivers select it automatically. +config SND_SOC_FSL_AUDMIX + tristate "Audio Mixer (AUDMIX) module support" + select REGMAP_MMIO + help + Say Y if you want to add Audio Mixer (AUDMIX) + support for the NXP iMX CPUs. + config SND_SOC_FSL_SSI tristate "Synchronous Serial Interface module (SSI) support" select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n @@ -296,6 +303,15 @@ config SND_SOC_FSL_ASOC_CARD CS4271, CS4272 and SGTL5000. Say Y if you want to add support for Freescale Generic ASoC Sound Card. +config SND_SOC_IMX_AUDMIX + tristate "SoC Audio support for i.MX boards with AUDMIX" + select SND_SOC_FSL_AUDMIX + select SND_SOC_FSL_SAI + help + SoC Audio support for i.MX boards with Audio Mixer + Say Y if you want to add support for SoC audio on an i.MX board with + an Audio Mixer. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 3c0ff315b971..c0dd04422fe9 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -12,6 +12,7 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support +snd-soc-fsl-audmix-objs := fsl_audmix.o snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o snd-soc-fsl-sai-objs := fsl_sai.o @@ -22,6 +23,8 @@ snd-soc-fsl-esai-objs := fsl_esai.o snd-soc-fsl-micfil-objs := fsl_micfil.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o + +obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o @@ -59,6 +62,7 @@ snd-soc-imx-es8328-objs := imx-es8328.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o +snd-soc-imx-audmix-objs := imx-audmix.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o @@ -68,3 +72,4 @@ obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o +obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index 191426a6d9ad..30a3d68b5c03 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -118,13 +118,13 @@ static int eukrea_tlv320_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "fsl,mux-int-port node missing or invalid.\n"); - return ret; + goto err; } ret = of_property_read_u32(np, "fsl,mux-ext-port", &ext_port); if (ret) { dev_err(&pdev->dev, "fsl,mux-ext-port node missing or invalid.\n"); - return ret; + goto err; } /* diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c new file mode 100644 index 000000000000..dabde0342c95 --- /dev/null +++ b/sound/soc/fsl/fsl_audmix.c @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2017 NXP + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "fsl_audmix.h" + +#define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \ + SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts) + +static const char + *tdm_sel[] = { "TDM1", "TDM2", }, + *mode_sel[] = { "Disabled", "TDM1", "TDM2", "Mixed", }, + *width_sel[] = { "16b", "18b", "20b", "24b", "32b", }, + *endis_sel[] = { "Disabled", "Enabled", }, + *updn_sel[] = { "Downward", "Upward", }, + *mask_sel[] = { "Unmask", "Mask", }; + +static const struct soc_enum fsl_audmix_enum[] = { +/* FSL_AUDMIX_CTR enums */ +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MIXCLK_SHIFT, tdm_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTSRC_SHIFT, mode_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTWIDTH_SHIFT, width_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKRTDF_SHIFT, mask_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKCKDF_SHIFT, mask_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCMODE_SHIFT, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCSRC_SHIFT, tdm_sel), +/* FSL_AUDMIX_ATCR0 enums */ +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 0, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 1, updn_sel), +/* FSL_AUDMIX_ATCR1 enums */ +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 0, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 1, updn_sel), +}; + +struct fsl_audmix_state { + u8 tdms; + u8 clk; + char msg[64]; +}; + +static const struct fsl_audmix_state prms[4][4] = {{ + /* DIS->DIS, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" }, + /* DIS->TDM1*/ + { .tdms = 1, .clk = 1, .msg = "DIS->TDM1: TDM1 not started!\n" }, + /* DIS->TDM2*/ + { .tdms = 2, .clk = 2, .msg = "DIS->TDM2: TDM2 not started!\n" }, + /* DIS->MIX */ + { .tdms = 3, .clk = 0, .msg = "DIS->MIX: Please start both TDMs!\n" } +}, { /* TDM1->DIS */ + { .tdms = 1, .clk = 0, .msg = "TDM1->DIS: TDM1 not started!\n" }, + /* TDM1->TDM1, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" }, + /* TDM1->TDM2 */ + { .tdms = 3, .clk = 2, .msg = "TDM1->TDM2: Please start both TDMs!\n" }, + /* TDM1->MIX */ + { .tdms = 3, .clk = 0, .msg = "TDM1->MIX: Please start both TDMs!\n" } +}, { /* TDM2->DIS */ + { .tdms = 2, .clk = 0, .msg = "TDM2->DIS: TDM2 not started!\n" }, + /* TDM2->TDM1 */ + { .tdms = 3, .clk = 1, .msg = "TDM2->TDM1: Please start both TDMs!\n" }, + /* TDM2->TDM2, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" }, + /* TDM2->MIX */ + { .tdms = 3, .clk = 0, .msg = "TDM2->MIX: Please start both TDMs!\n" } +}, { /* MIX->DIS */ + { .tdms = 3, .clk = 0, .msg = "MIX->DIS: Please start both TDMs!\n" }, + /* MIX->TDM1 */ + { .tdms = 3, .clk = 1, .msg = "MIX->TDM1: Please start both TDMs!\n" }, + /* MIX->TDM2 */ + { .tdms = 3, .clk = 2, .msg = "MIX->TDM2: Please start both TDMs!\n" }, + /* MIX->MIX, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" } +}, }; + +static int fsl_audmix_state_trans(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr, + const struct fsl_audmix_state prm) +{ + struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all required TDMs are started */ + if ((priv->tdms & prm.tdms) != prm.tdms) { + dev_dbg(comp->dev, "%s", prm.msg); + return -EINVAL; + } + + switch (prm.clk) { + case 1: + case 2: + /* Set mix clock */ + (*mask) |= FSL_AUDMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AUDMIX_CTR_MIXCLK(prm.clk - 1); + break; + default: + break; + } + + return 0; +} + +static int fsl_audmix_put_mix_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int reg_val, val, mix_clk; + int ret = 0; + + /* Get current state */ + ret = snd_soc_component_read(comp, FSL_AUDMIX_CTR, ®_val); + if (ret) + return ret; + + mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK) + >> FSL_AUDMIX_CTR_MIXCLK_SHIFT); + val = snd_soc_enum_item_to_val(e, item[0]); + + dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n", priv->tdms, val); + + /** + * Ensure the current selected mixer clock is available + * for configuration propagation + */ + if (!(priv->tdms & BIT(mix_clk))) { + dev_err(comp->dev, + "Started TDM%d needed for config propagation!\n", + mix_clk + 1); + return -EINVAL; + } + + if (!(priv->tdms & BIT(val))) { + dev_err(comp->dev, + "The selected clock source has no TDM%d enabled!\n", + val + 1); + return -EINVAL; + } + + return snd_soc_put_enum_double(kcontrol, ucontrol); +} + +static int fsl_audmix_put_out_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + u32 out_src, mix_clk; + unsigned int reg_val, val, mask = 0, ctr = 0; + int ret = 0; + + /* Get current state */ + ret = snd_soc_component_read(comp, FSL_AUDMIX_CTR, ®_val); + if (ret) + return ret; + + /* "From" state */ + out_src = ((reg_val & FSL_AUDMIX_CTR_OUTSRC_MASK) + >> FSL_AUDMIX_CTR_OUTSRC_SHIFT); + mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK) + >> FSL_AUDMIX_CTR_MIXCLK_SHIFT); + + /* "To" state */ + val = snd_soc_enum_item_to_val(e, item[0]); + + dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n", priv->tdms, val); + + /* Check if state is changing ... */ + if (out_src == val) + return 0; + /** + * Ensure the current selected mixer clock is available + * for configuration propagation + */ + if (!(priv->tdms & BIT(mix_clk))) { + dev_err(comp->dev, + "Started TDM%d needed for config propagation!\n", + mix_clk + 1); + return -EINVAL; + } + + /* Check state transition constraints */ + ret = fsl_audmix_state_trans(comp, &mask, &ctr, prms[out_src][val]); + if (ret) + return ret; + + /* Complete transition to new state */ + mask |= FSL_AUDMIX_CTR_OUTSRC_MASK; + ctr |= FSL_AUDMIX_CTR_OUTSRC(val); + + return snd_soc_component_update_bits(comp, FSL_AUDMIX_CTR, mask, ctr); +} + +static const struct snd_kcontrol_new fsl_audmix_snd_controls[] = { + /* FSL_AUDMIX_CTR controls */ + SOC_ENUM_EXT("Mixing Clock Source", fsl_audmix_enum[0], + snd_soc_get_enum_double, fsl_audmix_put_mix_clk_src), + SOC_ENUM_EXT("Output Source", fsl_audmix_enum[1], + snd_soc_get_enum_double, fsl_audmix_put_out_src), + SOC_ENUM("Output Width", fsl_audmix_enum[2]), + SOC_ENUM("Frame Rate Diff Error", fsl_audmix_enum[3]), + SOC_ENUM("Clock Freq Diff Error", fsl_audmix_enum[4]), + SOC_ENUM("Sync Mode Config", fsl_audmix_enum[5]), + SOC_ENUM("Sync Mode Clk Source", fsl_audmix_enum[6]), + /* TDM1 Attenuation controls */ + SOC_ENUM("TDM1 Attenuation", fsl_audmix_enum[7]), + SOC_ENUM("TDM1 Attenuation Direction", fsl_audmix_enum[8]), + SOC_SINGLE("TDM1 Attenuation Step Divider", FSL_AUDMIX_ATCR0, + 2, 0x00fff, 0), + SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AUDMIX_ATIVAL0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT0, + 0, 0x3ffff, 0), + /* TDM2 Attenuation controls */ + SOC_ENUM("TDM2 Attenuation", fsl_audmix_enum[9]), + SOC_ENUM("TDM2 Attenuation Direction", fsl_audmix_enum[10]), + SOC_SINGLE("TDM2 Attenuation Step Divider", FSL_AUDMIX_ATCR1, + 2, 0x00fff, 0), + SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AUDMIX_ATIVAL1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT1, + 0, 0x3ffff, 0), +}; + +static int fsl_audmix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *comp = dai->component; + u32 mask = 0, ctr = 0; + + /* AUDMIX is working in DSP_A format only */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + default: + return -EINVAL; + } + + /* For playback the AUDMIX is slave, and for record is master */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + /* Output data will be written on positive edge of the clock */ + ctr |= FSL_AUDMIX_CTR_OUTCKPOL(0); + break; + case SND_SOC_DAIFMT_NB_NF: + /* Output data will be written on negative edge of the clock */ + ctr |= FSL_AUDMIX_CTR_OUTCKPOL(1); + break; + default: + return -EINVAL; + } + + mask |= FSL_AUDMIX_CTR_OUTCKPOL_MASK; + + return snd_soc_component_update_bits(comp, FSL_AUDMIX_CTR, mask, ctr); +} + +static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops fsl_audmix_dai_ops = { + .set_fmt = fsl_audmix_dai_set_fmt, + .trigger = fsl_audmix_dai_trigger, +}; + +static struct snd_soc_dai_driver fsl_audmix_dai[] = { + { + .id = 0, + .name = "audmix-0", + .playback = { + .stream_name = "AUDMIX-Playback-0", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .capture = { + .stream_name = "AUDMIX-Capture-0", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .ops = &fsl_audmix_dai_ops, + }, + { + .id = 1, + .name = "audmix-1", + .playback = { + .stream_name = "AUDMIX-Playback-1", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .capture = { + .stream_name = "AUDMIX-Capture-1", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .ops = &fsl_audmix_dai_ops, + }, +}; + +static const struct snd_soc_component_driver fsl_audmix_component = { + .name = "fsl-audmix-dai", + .controls = fsl_audmix_snd_controls, + .num_controls = ARRAY_SIZE(fsl_audmix_snd_controls), +}; + +static bool fsl_audmix_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_AUDMIX_CTR: + case FSL_AUDMIX_STR: + case FSL_AUDMIX_ATCR0: + case FSL_AUDMIX_ATIVAL0: + case FSL_AUDMIX_ATSTPUP0: + case FSL_AUDMIX_ATSTPDN0: + case FSL_AUDMIX_ATSTPTGT0: + case FSL_AUDMIX_ATTNVAL0: + case FSL_AUDMIX_ATSTP0: + case FSL_AUDMIX_ATCR1: + case FSL_AUDMIX_ATIVAL1: + case FSL_AUDMIX_ATSTPUP1: + case FSL_AUDMIX_ATSTPDN1: + case FSL_AUDMIX_ATSTPTGT1: + case FSL_AUDMIX_ATTNVAL1: + case FSL_AUDMIX_ATSTP1: + return true; + default: + return false; + } +} + +static bool fsl_audmix_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_AUDMIX_CTR: + case FSL_AUDMIX_ATCR0: + case FSL_AUDMIX_ATIVAL0: + case FSL_AUDMIX_ATSTPUP0: + case FSL_AUDMIX_ATSTPDN0: + case FSL_AUDMIX_ATSTPTGT0: + case FSL_AUDMIX_ATCR1: + case FSL_AUDMIX_ATIVAL1: + case FSL_AUDMIX_ATSTPUP1: + case FSL_AUDMIX_ATSTPDN1: + case FSL_AUDMIX_ATSTPTGT1: + return true; + default: + return false; + } +} + +static const struct reg_default fsl_audmix_reg[] = { + { FSL_AUDMIX_CTR, 0x00060 }, + { FSL_AUDMIX_STR, 0x00003 }, + { FSL_AUDMIX_ATCR0, 0x00000 }, + { FSL_AUDMIX_ATIVAL0, 0x3FFFF }, + { FSL_AUDMIX_ATSTPUP0, 0x2AAAA }, + { FSL_AUDMIX_ATSTPDN0, 0x30000 }, + { FSL_AUDMIX_ATSTPTGT0, 0x00010 }, + { FSL_AUDMIX_ATTNVAL0, 0x00000 }, + { FSL_AUDMIX_ATSTP0, 0x00000 }, + { FSL_AUDMIX_ATCR1, 0x00000 }, + { FSL_AUDMIX_ATIVAL1, 0x3FFFF }, + { FSL_AUDMIX_ATSTPUP1, 0x2AAAA }, + { FSL_AUDMIX_ATSTPDN1, 0x30000 }, + { FSL_AUDMIX_ATSTPTGT1, 0x00010 }, + { FSL_AUDMIX_ATTNVAL1, 0x00000 }, + { FSL_AUDMIX_ATSTP1, 0x00000 }, +}; + +static const struct regmap_config fsl_audmix_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = FSL_AUDMIX_ATSTP1, + .reg_defaults = fsl_audmix_reg, + .num_reg_defaults = ARRAY_SIZE(fsl_audmix_reg), + .readable_reg = fsl_audmix_readable_reg, + .writeable_reg = fsl_audmix_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int fsl_audmix_probe(struct platform_device *pdev) +{ + struct fsl_audmix *priv; + struct resource *res; + void __iomem *regs; + int ret; + const char *sprop; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Get the addresses */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "ipg", regs, + &fsl_audmix_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap\n"); + return PTR_ERR(priv->regmap); + } + + priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(priv->ipg_clk)) { + dev_err(&pdev->dev, "failed to get ipg clock\n"); + return PTR_ERR(priv->ipg_clk); + } + + platform_set_drvdata(pdev, priv); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_audmix_component, + fsl_audmix_dai, + ARRAY_SIZE(fsl_audmix_dai)); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + sprop = of_get_property(pdev->dev.of_node, "model", NULL); + if (sprop) { + priv->pdev = platform_device_register_data(&pdev->dev, sprop, 0, + NULL, 0); + if (IS_ERR(priv->pdev)) { + ret = PTR_ERR(priv->pdev); + dev_err(&pdev->dev, + "failed to register platform %s: %d\n", sprop, + ret); + } + } else { + dev_err(&pdev->dev, "[model] attribute missing.\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fsl_audmix_remove(struct platform_device *pdev) +{ + struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev); + + if (priv->pdev) + platform_device_unregister(priv->pdev); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_audmix_runtime_resume(struct device *dev) +{ + struct fsl_audmix *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->ipg_clk); + if (ret) { + dev_err(dev, "Failed to enable IPG clock: %d\n", ret); + return ret; + } + + regcache_cache_only(priv->regmap, false); + regcache_mark_dirty(priv->regmap); + + return regcache_sync(priv->regmap); +} + +static int fsl_audmix_runtime_suspend(struct device *dev) +{ + struct fsl_audmix *priv = dev_get_drvdata(dev); + + regcache_cache_only(priv->regmap, true); + + clk_disable_unprepare(priv->ipg_clk); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops fsl_audmix_pm = { + SET_RUNTIME_PM_OPS(fsl_audmix_runtime_suspend, + fsl_audmix_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct of_device_id fsl_audmix_ids[] = { + { .compatible = "fsl,imx8qm-audmix", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_audmix_ids); + +static struct platform_driver fsl_audmix_driver = { + .probe = fsl_audmix_probe, + .remove = fsl_audmix_remove, + .driver = { + .name = "fsl-audmix", + .of_match_table = fsl_audmix_ids, + .pm = &fsl_audmix_pm, + }, +}; +module_platform_driver(fsl_audmix_driver); + +MODULE_DESCRIPTION("NXP AUDMIX ASoC DAI driver"); +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_ALIAS("platform:fsl-audmix"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_audmix.h b/sound/soc/fsl/fsl_audmix.h new file mode 100644 index 000000000000..7812ffec45c5 --- /dev/null +++ b/sound/soc/fsl/fsl_audmix.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2017 NXP + */ + +#ifndef __FSL_AUDMIX_H +#define __FSL_AUDMIX_H + +#define FSL_AUDMIX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) +/* AUDMIX Registers */ +#define FSL_AUDMIX_CTR 0x200 /* Control */ +#define FSL_AUDMIX_STR 0x204 /* Status */ + +#define FSL_AUDMIX_ATCR0 0x208 /* Attenuation Control */ +#define FSL_AUDMIX_ATIVAL0 0x20c /* Attenuation Initial Value */ +#define FSL_AUDMIX_ATSTPUP0 0x210 /* Attenuation step up factor */ +#define FSL_AUDMIX_ATSTPDN0 0x214 /* Attenuation step down factor */ +#define FSL_AUDMIX_ATSTPTGT0 0x218 /* Attenuation step target */ +#define FSL_AUDMIX_ATTNVAL0 0x21c /* Attenuation Value */ +#define FSL_AUDMIX_ATSTP0 0x220 /* Attenuation step number */ + +#define FSL_AUDMIX_ATCR1 0x228 /* Attenuation Control */ +#define FSL_AUDMIX_ATIVAL1 0x22c /* Attenuation Initial Value */ +#define FSL_AUDMIX_ATSTPUP1 0x230 /* Attenuation step up factor */ +#define FSL_AUDMIX_ATSTPDN1 0x234 /* Attenuation step down factor */ +#define FSL_AUDMIX_ATSTPTGT1 0x238 /* Attenuation step target */ +#define FSL_AUDMIX_ATTNVAL1 0x23c /* Attenuation Value */ +#define FSL_AUDMIX_ATSTP1 0x240 /* Attenuation step number */ + +/* AUDMIX Control Register */ +#define FSL_AUDMIX_CTR_MIXCLK_SHIFT 0 +#define FSL_AUDMIX_CTR_MIXCLK_MASK BIT(FSL_AUDMIX_CTR_MIXCLK_SHIFT) +#define FSL_AUDMIX_CTR_MIXCLK(i) ((i) << FSL_AUDMIX_CTR_MIXCLK_SHIFT) +#define FSL_AUDMIX_CTR_OUTSRC_SHIFT 1 +#define FSL_AUDMIX_CTR_OUTSRC_MASK (0x3 << FSL_AUDMIX_CTR_OUTSRC_SHIFT) +#define FSL_AUDMIX_CTR_OUTSRC(i) (((i) << FSL_AUDMIX_CTR_OUTSRC_SHIFT)\ + & FSL_AUDMIX_CTR_OUTSRC_MASK) +#define FSL_AUDMIX_CTR_OUTWIDTH_SHIFT 3 +#define FSL_AUDMIX_CTR_OUTWIDTH_MASK (0x7 << FSL_AUDMIX_CTR_OUTWIDTH_SHIFT) +#define FSL_AUDMIX_CTR_OUTWIDTH(i) (((i) << FSL_AUDMIX_CTR_OUTWIDTH_SHIFT)\ + & FSL_AUDMIX_CTR_OUTWIDTH_MASK) +#define FSL_AUDMIX_CTR_OUTCKPOL_SHIFT 6 +#define FSL_AUDMIX_CTR_OUTCKPOL_MASK BIT(FSL_AUDMIX_CTR_OUTCKPOL_SHIFT) +#define FSL_AUDMIX_CTR_OUTCKPOL(i) ((i) << FSL_AUDMIX_CTR_OUTCKPOL_SHIFT) +#define FSL_AUDMIX_CTR_MASKRTDF_SHIFT 7 +#define FSL_AUDMIX_CTR_MASKRTDF_MASK BIT(FSL_AUDMIX_CTR_MASKRTDF_SHIFT) +#define FSL_AUDMIX_CTR_MASKRTDF(i) ((i) << FSL_AUDMIX_CTR_MASKRTDF_SHIFT) +#define FSL_AUDMIX_CTR_MASKCKDF_SHIFT 8 +#define FSL_AUDMIX_CTR_MASKCKDF_MASK BIT(FSL_AUDMIX_CTR_MASKCKDF_SHIFT) +#define FSL_AUDMIX_CTR_MASKCKDF(i) ((i) << FSL_AUDMIX_CTR_MASKCKDF_SHIFT) +#define FSL_AUDMIX_CTR_SYNCMODE_SHIFT 9 +#define FSL_AUDMIX_CTR_SYNCMODE_MASK BIT(FSL_AUDMIX_CTR_SYNCMODE_SHIFT) +#define FSL_AUDMIX_CTR_SYNCMODE(i) ((i) << FSL_AUDMIX_CTR_SYNCMODE_SHIFT) +#define FSL_AUDMIX_CTR_SYNCSRC_SHIFT 10 +#define FSL_AUDMIX_CTR_SYNCSRC_MASK BIT(FSL_AUDMIX_CTR_SYNCSRC_SHIFT) +#define FSL_AUDMIX_CTR_SYNCSRC(i) ((i) << FSL_AUDMIX_CTR_SYNCSRC_SHIFT) + +/* AUDMIX Status Register */ +#define FSL_AUDMIX_STR_RATEDIFF BIT(0) +#define FSL_AUDMIX_STR_CLKDIFF BIT(1) +#define FSL_AUDMIX_STR_MIXSTAT_SHIFT 2 +#define FSL_AUDMIX_STR_MIXSTAT_MASK (0x3 << FSL_AUDMIX_STR_MIXSTAT_SHIFT) +#define FSL_AUDMIX_STR_MIXSTAT(i) (((i) & FSL_AUDMIX_STR_MIXSTAT_MASK) \ + >> FSL_AUDMIX_STR_MIXSTAT_SHIFT) +/* AUDMIX Attenuation Control Register */ +#define FSL_AUDMIX_ATCR_AT_EN BIT(0) +#define FSL_AUDMIX_ATCR_AT_UPDN BIT(1) +#define FSL_AUDMIX_ATCR_ATSTPDIF_SHIFT 2 +#define FSL_AUDMIX_ATCR_ATSTPDFI_MASK \ + (0xfff << FSL_AUDMIX_ATCR_ATSTPDIF_SHIFT) + +/* AUDMIX Attenuation Initial Value Register */ +#define FSL_AUDMIX_ATIVAL_ATINVAL_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Up Factor Register */ +#define FSL_AUDMIX_ATSTPUP_ATSTEPUP_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Down Factor Register */ +#define FSL_AUDMIX_ATSTPDN_ATSTEPDN_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Target Register */ +#define FSL_AUDMIX_ATSTPTGT_ATSTPTG_MASK 0x3FFFF + +/* AUDMIX Attenuation Value Register */ +#define FSL_AUDMIX_ATTNVAL_ATCURVAL_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Number Register */ +#define FSL_AUDMIX_ATSTP_STPCTR_MASK 0x3FFFF + +#define FSL_AUDMIX_MAX_DAIS 2 +struct fsl_audmix { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *ipg_clk; + u8 tdms; +}; + +#endif /* __FSL_AUDMIX_H */ diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c index 9981668ab590..040d06b89f00 100644 --- a/sound/soc/fsl/fsl_utils.c +++ b/sound/soc/fsl/fsl_utils.c @@ -71,6 +71,7 @@ int fsl_asoc_get_dma_channel(struct device_node *ssi_np, iprop = of_get_property(dma_np, "cell-index", NULL); if (!iprop) { of_node_put(dma_np); + of_node_put(dma_channel_np); return -EINVAL; } *dma_id = be32_to_cpup(iprop); diff --git a/sound/soc/fsl/imx-audmix.c b/sound/soc/fsl/imx-audmix.c new file mode 100644 index 000000000000..7983bd339c01 --- /dev/null +++ b/sound/soc/fsl/imx-audmix.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/pm_runtime.h> +#include "fsl_sai.h" +#include "fsl_audmix.h" + +struct imx_audmix { + struct platform_device *pdev; + struct snd_soc_card card; + struct platform_device *audmix_pdev; + struct platform_device *out_pdev; + struct clk *cpu_mclk; + int num_dai; + struct snd_soc_dai_link *dai; + int num_dai_conf; + struct snd_soc_codec_conf *dai_conf; + int num_dapm_routes; + struct snd_soc_dapm_route *dapm_routes; +}; + +static const u32 imx_audmix_rates[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 64000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list imx_audmix_rate_constraints = { + .count = ARRAY_SIZE(imx_audmix_rates), + .list = imx_audmix_rates, +}; + +static int imx_audmix_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_audmix *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = rtd->card->dev; + unsigned long clk_rate = clk_get_rate(priv->cpu_mclk); + int ret; + + if (clk_rate % 24576000 == 0) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &imx_audmix_rate_constraints); + if (ret < 0) + return ret; + } else { + dev_warn(dev, "mclk may be not supported %lu\n", clk_rate); + } + + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 8); + if (ret < 0) + return ret; + + return snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, + FSL_AUDMIX_FORMATS); +} + +static int imx_audmix_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; + u32 channels = params_channels(params); + int ret, dir; + + /* For playback the AUDMIX is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_MAST1, 0, dir); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + /* + * Per datasheet, AUDMIX expects 8 slots and 32 bits + * for every slot in TDM mode. + */ + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1, + BIT(channels) - 1, 8, 32); + if (ret) + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + + return ret; +} + +static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; + int ret; + + if (!tx) + return 0; + + /* For playback the AUDMIX is slave */ + fmt |= SND_SOC_DAIFMT_CBM_CFM; + + /* set AUDMIX DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) + dev_err(dev, "failed to set AUDMIX DAI fmt: %d\n", ret); + + return ret; +} + +static struct snd_soc_ops imx_audmix_fe_ops = { + .startup = imx_audmix_fe_startup, + .hw_params = imx_audmix_fe_hw_params, +}; + +static struct snd_soc_ops imx_audmix_be_ops = { + .hw_params = imx_audmix_be_hw_params, +}; + +static int imx_audmix_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *audmix_np = NULL, *out_cpu_np = NULL; + struct platform_device *audmix_pdev = NULL; + struct platform_device *cpu_pdev; + struct of_phandle_args args; + struct imx_audmix *priv; + int i, num_dai, ret; + const char *fe_name_pref = "HiFi-AUDMIX-FE-"; + char *be_name, *be_pb, *be_cp, *dai_name, *capture_dai_name; + + if (pdev->dev.parent) { + audmix_np = pdev->dev.parent->of_node; + } else { + dev_err(&pdev->dev, "Missing parent device.\n"); + return -EINVAL; + } + + if (!audmix_np) { + dev_err(&pdev->dev, "Missing DT node for parent device.\n"); + return -EINVAL; + } + + audmix_pdev = of_find_device_by_node(audmix_np); + if (!audmix_pdev) { + dev_err(&pdev->dev, "Missing AUDMIX platform device for %s\n", + np->full_name); + return -EINVAL; + } + + num_dai = of_count_phandle_with_args(audmix_np, "dais", NULL); + if (num_dai != FSL_AUDMIX_MAX_DAIS) { + dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n", + audmix_np->full_name); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->num_dai = 2 * num_dai; + priv->dai = devm_kzalloc(&pdev->dev, priv->num_dai * + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + if (!priv->dai) + return -ENOMEM; + + priv->num_dai_conf = num_dai; + priv->dai_conf = devm_kzalloc(&pdev->dev, priv->num_dai_conf * + sizeof(struct snd_soc_codec_conf), + GFP_KERNEL); + if (!priv->dai_conf) + return -ENOMEM; + + priv->num_dapm_routes = 3 * num_dai; + priv->dapm_routes = devm_kzalloc(&pdev->dev, priv->num_dapm_routes * + sizeof(struct snd_soc_dapm_route), + GFP_KERNEL); + if (!priv->dapm_routes) + return -ENOMEM; + + for (i = 0; i < num_dai; i++) { + ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, i, + &args); + if (ret < 0) { + dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n"); + return ret; + } + + cpu_pdev = of_find_device_by_node(args.np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + + dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s", + fe_name_pref, args.np->full_name + 1); + + dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name); + + if (i == 0) { + out_cpu_np = args.np; + capture_dai_name = + devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", + dai_name, "CPU-Capture"); + } + + priv->dai[i].name = dai_name; + priv->dai[i].stream_name = "HiFi-AUDMIX-FE"; + priv->dai[i].codec_dai_name = "snd-soc-dummy-dai"; + priv->dai[i].codec_name = "snd-soc-dummy"; + priv->dai[i].cpu_of_node = args.np; + priv->dai[i].cpu_dai_name = dev_name(&cpu_pdev->dev); + priv->dai[i].platform_of_node = args.np; + priv->dai[i].dynamic = 1; + priv->dai[i].dpcm_playback = 1; + priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0); + priv->dai[i].ignore_pmdown_time = 1; + priv->dai[i].ops = &imx_audmix_fe_ops; + + /* Add AUDMIX Backend */ + be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "audmix-%d", i); + be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "AUDMIX-Playback-%d", i); + be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "AUDMIX-Capture-%d", i); + + priv->dai[num_dai + i].name = be_name; + priv->dai[num_dai + i].codec_dai_name = "snd-soc-dummy-dai"; + priv->dai[num_dai + i].codec_name = "snd-soc-dummy"; + priv->dai[num_dai + i].cpu_of_node = audmix_np; + priv->dai[num_dai + i].cpu_dai_name = be_name; + priv->dai[num_dai + i].platform_name = "snd-soc-dummy"; + priv->dai[num_dai + i].no_pcm = 1; + priv->dai[num_dai + i].dpcm_playback = 1; + priv->dai[num_dai + i].dpcm_capture = 1; + priv->dai[num_dai + i].ignore_pmdown_time = 1; + priv->dai[num_dai + i].ops = &imx_audmix_be_ops; + + priv->dai_conf[i].of_node = args.np; + priv->dai_conf[i].name_prefix = dai_name; + + priv->dapm_routes[i].source = + devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", + dai_name, "CPU-Playback"); + priv->dapm_routes[i].sink = be_pb; + priv->dapm_routes[num_dai + i].source = be_pb; + priv->dapm_routes[num_dai + i].sink = be_cp; + priv->dapm_routes[2 * num_dai + i].source = be_cp; + priv->dapm_routes[2 * num_dai + i].sink = capture_dai_name; + } + + cpu_pdev = of_find_device_by_node(out_cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1"); + if (IS_ERR(priv->cpu_mclk)) { + ret = PTR_ERR(priv->cpu_mclk); + dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret); + return -EINVAL; + } + + priv->audmix_pdev = audmix_pdev; + priv->out_pdev = cpu_pdev; + + priv->card.dai_link = priv->dai; + priv->card.num_links = priv->num_dai; + priv->card.codec_conf = priv->dai_conf; + priv->card.num_configs = priv->num_dai_conf; + priv->card.dapm_routes = priv->dapm_routes; + priv->card.num_dapm_routes = priv->num_dapm_routes; + priv->card.dev = pdev->dev.parent; + priv->card.owner = THIS_MODULE; + priv->card.name = "imx-audmix"; + + platform_set_drvdata(pdev, &priv->card); + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(pdev->dev.parent, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed\n"); + return ret; + } + + return ret; +} + +static struct platform_driver imx_audmix_driver = { + .probe = imx_audmix_probe, + .driver = { + .name = "imx-audmix", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(imx_audmix_driver); + +MODULE_DESCRIPTION("NXP AUDMIX ASoC machine driver"); +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_ALIAS("platform:imx-audmix"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index bb12351330e8..8114b5ea9319 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -20,37 +20,6 @@ #include <linux/string.h> #include <sound/simple_card_utils.h> -struct graph_priv { - struct snd_soc_card snd_card; - struct graph_dai_props { - struct asoc_simple_dai *cpu_dai; - struct asoc_simple_dai *codec_dai; - struct snd_soc_dai_link_component codecs; /* single codec */ - struct snd_soc_dai_link_component platforms; - struct asoc_simple_card_data adata; - struct snd_soc_codec_conf *codec_conf; - unsigned int mclk_fs; - } *dai_props; - struct asoc_simple_jack hp_jack; - struct asoc_simple_jack mic_jack; - struct snd_soc_dai_link *dai_link; - struct asoc_simple_dai *dais; - struct snd_soc_codec_conf *codec_conf; - struct gpio_desc *pa_gpio; -}; - -struct link_info { - int dais; /* number of dai */ - int link; /* number of link */ - int conf; /* number of codec_conf */ - int cpu; /* turn for CPU / Codec */ -}; - -#define graph_priv_to_card(priv) (&(priv)->snd_card) -#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) -#define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev) -#define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i)) - #define PREFIX "audio-graph-card," static int graph_outdrv_event(struct snd_soc_dapm_widget *w, @@ -58,7 +27,7 @@ static int graph_outdrv_event(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_dapm_context *dapm = w->dapm; - struct graph_priv *priv = snd_soc_card_get_drvdata(dapm->card); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(dapm->card); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -80,127 +49,156 @@ static const struct snd_soc_dapm_widget graph_dapm_widgets[] = { SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), }; -static int graph_startup(struct snd_pcm_substream *substream) +static const struct snd_soc_ops graph_ops = { + .startup = asoc_simple_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; + +static int graph_get_dai_id(struct device_node *ep) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + struct device_node *node; + struct device_node *endpoint; + struct of_endpoint info; + int i, id; int ret; - ret = asoc_simple_card_clk_enable(dai_props->cpu_dai); - if (ret) + /* use driver specified DAI ID if exist */ + ret = snd_soc_get_dai_id(ep); + if (ret != -ENOTSUPP) return ret; - ret = asoc_simple_card_clk_enable(dai_props->codec_dai); - if (ret) - asoc_simple_card_clk_disable(dai_props->cpu_dai); + /* use endpoint/port reg if exist */ + ret = of_graph_parse_endpoint(ep, &info); + if (ret == 0) { + /* + * Because it will count port/endpoint if it doesn't have "reg". + * But, we can't judge whether it has "no reg", or "reg = <0>" + * only of_graph_parse_endpoint(). + * We need to check "reg" property + */ + if (of_get_property(ep, "reg", NULL)) + return info.id; - return ret; -} + node = of_get_parent(ep); + of_node_put(node); + if (of_get_property(node, "reg", NULL)) + return info.port; + } + node = of_graph_get_port_parent(ep); -static void graph_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + /* + * Non HDMI sound case, counting port/endpoint on its DT + * is enough. Let's count it. + */ + i = 0; + id = -1; + for_each_endpoint_of_node(node, endpoint) { + if (endpoint == ep) + id = i; + i++; + } - asoc_simple_card_clk_disable(dai_props->cpu_dai); + of_node_put(node); + + if (id < 0) + return -ENODEV; - asoc_simple_card_clk_disable(dai_props->codec_dai); + return id; } -static int graph_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) +static int asoc_simple_parse_dai(struct device_node *ep, + struct snd_soc_dai_link_component *dlc, + struct device_node **dai_of_node, + const char **dai_name, + int *is_single_link) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); - unsigned int mclk, mclk_fs = 0; - int ret = 0; - - if (dai_props->mclk_fs) - mclk_fs = dai_props->mclk_fs; - - if (mclk_fs) { - mclk = params_rate(params) * mclk_fs; - ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, - SND_SOC_CLOCK_IN); - if (ret && ret != -ENOTSUPP) - goto err; - - ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, - SND_SOC_CLOCK_OUT); - if (ret && ret != -ENOTSUPP) - goto err; + struct device_node *node; + struct of_phandle_args args; + int ret; + + /* + * Use snd_soc_dai_link_component instead of legacy style. + * It is only for codec, but cpu will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + */ + if (dlc) { + dai_name = &dlc->dai_name; + dai_of_node = &dlc->of_node; } - return 0; -err: - return ret; -} -static const struct snd_soc_ops graph_ops = { - .startup = graph_startup, - .shutdown = graph_shutdown, - .hw_params = graph_hw_params, -}; + if (!ep) + return 0; + if (!dai_name) + return 0; -static int graph_dai_init(struct snd_soc_pcm_runtime *rtd) -{ - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); - int ret = 0; + node = of_graph_get_port_parent(ep); - ret = asoc_simple_card_init_dai(rtd->codec_dai, - dai_props->codec_dai); - if (ret < 0) - return ret; + /* Get dai->name */ + args.np = node; + args.args[0] = graph_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); - ret = asoc_simple_card_init_dai(rtd->cpu_dai, - dai_props->cpu_dai); + ret = snd_soc_get_dai_name(&args, dai_name); if (ret < 0) return ret; - return 0; -} - -static int graph_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + *dai_of_node = node; - asoc_simple_card_convert_fixup(&dai_props->adata, params); + if (is_single_link) + *is_single_link = of_graph_get_endpoint_count(node) == 1; return 0; } -static void graph_get_conversion(struct device *dev, - struct device_node *ep, - struct asoc_simple_card_data *adata) +static void graph_parse_convert(struct device *dev, + struct device_node *ep, + struct asoc_simple_data *adata) { struct device_node *top = dev->of_node; struct device_node *port = of_get_parent(ep); struct device_node *ports = of_get_parent(port); struct device_node *node = of_graph_get_port_parent(ep); - asoc_simple_card_parse_convert(dev, top, NULL, adata); - asoc_simple_card_parse_convert(dev, node, PREFIX, adata); - asoc_simple_card_parse_convert(dev, ports, NULL, adata); - asoc_simple_card_parse_convert(dev, port, NULL, adata); - asoc_simple_card_parse_convert(dev, ep, NULL, adata); + asoc_simple_parse_convert(dev, top, NULL, adata); + asoc_simple_parse_convert(dev, node, PREFIX, adata); + asoc_simple_parse_convert(dev, ports, NULL, adata); + asoc_simple_parse_convert(dev, port, NULL, adata); + asoc_simple_parse_convert(dev, ep, NULL, adata); + + of_node_put(port); + of_node_put(ports); + of_node_put(node); } -static int graph_dai_link_of_dpcm(struct graph_priv *priv, +static void graph_parse_mclk_fs(struct device_node *top, + struct device_node *ep, + struct simple_dai_props *props) +{ + struct device_node *port = of_get_parent(ep); + struct device_node *ports = of_get_parent(port); + struct device_node *node = of_graph_get_port_parent(ep); + + of_property_read_u32(top, "mclk-fs", &props->mclk_fs); + of_property_read_u32(ports, "mclk-fs", &props->mclk_fs); + of_property_read_u32(port, "mclk-fs", &props->mclk_fs); + of_property_read_u32(ep, "mclk-fs", &props->mclk_fs); + + of_node_put(port); + of_node_put(ports); + of_node_put(node); +} + +static int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li, int dup_codec) { - struct device *dev = graph_priv_to_dev(priv); - struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, li->link); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, li->link); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); struct device_node *top = dev->of_node; struct device_node *ep = li->cpu ? cpu_ep : codec_ep; struct device_node *port; @@ -222,18 +220,12 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, dev_dbg(dev, "link_of DPCM (%pOF)\n", ep); - of_property_read_u32(top, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(ports, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(port, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(ep, "mclk-fs", &dai_props->mclk_fs); - - graph_get_conversion(dev, ep, &dai_props->adata); - of_node_put(ports); of_node_put(port); of_node_put(node); if (li->cpu) { + int is_single_links = 0; /* BE is dummy */ codecs->of_node = NULL; @@ -247,23 +239,22 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, dai = dai_props->cpu_dai = &priv->dais[li->dais++]; - ret = asoc_simple_card_parse_graph_cpu(ep, dai_link); + ret = asoc_simple_parse_cpu(ep, dai_link, &is_single_links); if (ret) return ret; - ret = asoc_simple_card_parse_clk_cpu(dev, ep, dai_link, dai); + ret = asoc_simple_parse_clk_cpu(dev, ep, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "fe.%s", - dai_link->cpu_dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); if (ret < 0) return ret; /* card->num_links includes Codec */ - asoc_simple_card_canonicalize_cpu(dai_link, - of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); + asoc_simple_canonicalize_cpu(dai_link, is_single_links); } else { struct snd_soc_codec_conf *cconf; @@ -274,7 +265,7 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, /* BE settings */ dai_link->no_pcm = 1; - dai_link->be_hw_params_fixup = graph_be_hw_params_fixup; + dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; dai = dai_props->codec_dai = &priv->dais[li->dais++]; @@ -282,17 +273,17 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, cconf = dai_props->codec_conf = &priv->codec_conf[li->conf++]; - ret = asoc_simple_card_parse_graph_codec(ep, dai_link); + ret = asoc_simple_parse_codec(ep, dai_link); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(dev, ep, dai_link, dai); + ret = asoc_simple_parse_clk_codec(dev, ep, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "be.%s", - codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "be.%s", + codecs->dai_name); if (ret < 0) return ret; @@ -307,51 +298,45 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, "prefix"); } - asoc_simple_card_canonicalize_platform(dai_link); + graph_parse_convert(dev, ep, &dai_props->adata); + graph_parse_mclk_fs(top, ep, dai_props); - ret = asoc_simple_card_of_parse_tdm(ep, dai); + asoc_simple_canonicalize_platform(dai_link); + + ret = asoc_simple_parse_tdm(ep, dai); if (ret) return ret; - ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, - NULL, &dai_link->dai_fmt); + ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep, + NULL, &dai_link->dai_fmt); if (ret < 0) return ret; dai_link->dpcm_playback = 1; dai_link->dpcm_capture = 1; dai_link->ops = &graph_ops; - dai_link->init = graph_dai_init; + dai_link->init = asoc_simple_dai_init; return 0; } -static int graph_dai_link_of(struct graph_priv *priv, +static int graph_dai_link_of(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li) { - struct device *dev = graph_priv_to_dev(priv); - struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, li->link); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, li->link); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); struct device_node *top = dev->of_node; - struct device_node *cpu_port; - struct device_node *cpu_ports; - struct device_node *codec_port; - struct device_node *codec_ports; struct asoc_simple_dai *cpu_dai; struct asoc_simple_dai *codec_dai; - int ret; + int ret, single_cpu; /* Do it only CPU turn */ if (!li->cpu) return 0; - cpu_port = of_get_parent(cpu_ep); - cpu_ports = of_get_parent(cpu_port); - codec_port = of_get_parent(codec_ep); - codec_ports = of_get_parent(codec_port); - dev_dbg(dev, "link_of (%pOF)\n", cpu_ep); li->link++; @@ -362,84 +347,74 @@ static int graph_dai_link_of(struct graph_priv *priv, dai_props->codec_dai = &priv->dais[li->dais++]; /* Factor to mclk, used in hw_params() */ - of_property_read_u32(top, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(cpu_ports, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(codec_ports, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(cpu_port, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(codec_port, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(cpu_ep, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(codec_ep, "mclk-fs", &dai_props->mclk_fs); - of_node_put(cpu_port); - of_node_put(cpu_ports); - of_node_put(codec_port); - of_node_put(codec_ports); - - ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, - NULL, &dai_link->dai_fmt); + graph_parse_mclk_fs(top, cpu_ep, dai_props); + graph_parse_mclk_fs(top, codec_ep, dai_props); + + ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep, + NULL, &dai_link->dai_fmt); if (ret < 0) return ret; - ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); + ret = asoc_simple_parse_cpu(cpu_ep, dai_link, &single_cpu); if (ret < 0) return ret; - ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); + ret = asoc_simple_parse_codec(codec_ep, dai_link); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_tdm(cpu_ep, cpu_dai); + ret = asoc_simple_parse_tdm(cpu_ep, cpu_dai); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_tdm(codec_ep, codec_dai); + ret = asoc_simple_parse_tdm(codec_ep, codec_dai); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); + ret = asoc_simple_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); + ret = asoc_simple_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "%s-%s", - dai_link->cpu_dai_name, - dai_link->codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codecs->dai_name); if (ret < 0) return ret; dai_link->ops = &graph_ops; - dai_link->init = graph_dai_init; + dai_link->init = asoc_simple_dai_init; - asoc_simple_card_canonicalize_platform(dai_link); - asoc_simple_card_canonicalize_cpu(dai_link, - of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); + asoc_simple_canonicalize_cpu(dai_link, single_cpu); + asoc_simple_canonicalize_platform(dai_link); return 0; } -static int graph_for_each_link(struct graph_priv *priv, +static int graph_for_each_link(struct asoc_simple_priv *priv, struct link_info *li, - int (*func_noml)(struct graph_priv *priv, + int (*func_noml)(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li), - int (*func_dpcm)(struct graph_priv *priv, + int (*func_dpcm)(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li, int dup_codec)) { struct of_phandle_iterator it; - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); struct device_node *node = dev->of_node; struct device_node *cpu_port; struct device_node *cpu_ep; struct device_node *codec_ep; struct device_node *codec_port; struct device_node *codec_port_old = NULL; - struct asoc_simple_card_data adata; + struct asoc_simple_data adata; int rc, ret; /* loop for all listed CPU port */ @@ -462,8 +437,8 @@ static int graph_for_each_link(struct graph_priv *priv, /* get convert-xxx property */ memset(&adata, 0, sizeof(adata)); - graph_get_conversion(dev, codec_ep, &adata); - graph_get_conversion(dev, cpu_ep, &adata); + graph_parse_convert(dev, codec_ep, &adata); + graph_parse_convert(dev, cpu_ep, &adata); /* * It is DPCM @@ -488,17 +463,17 @@ static int graph_for_each_link(struct graph_priv *priv, return 0; } -static int graph_parse_of(struct graph_priv *priv) +static int graph_parse_of(struct asoc_simple_priv *priv) { - struct snd_soc_card *card = graph_priv_to_card(priv); + struct snd_soc_card *card = simple_priv_to_card(priv); struct link_info li; int ret; - ret = asoc_simple_card_of_parse_widgets(card, NULL); + ret = asoc_simple_parse_widgets(card, NULL); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_routing(card, NULL); + ret = asoc_simple_parse_routing(card, NULL); if (ret < 0) return ret; @@ -523,15 +498,15 @@ static int graph_parse_of(struct graph_priv *priv) return ret; } - return asoc_simple_card_parse_card_name(card, NULL); + return asoc_simple_parse_card_name(card, NULL); } -static int graph_count_noml(struct graph_priv *priv, +static int graph_count_noml(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li) { - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); li->link += 1; /* 1xCPU-Codec */ li->dais += 2; /* 1xCPU + 1xCodec */ @@ -541,13 +516,13 @@ static int graph_count_noml(struct graph_priv *priv, return 0; } -static int graph_count_dpcm(struct graph_priv *priv, +static int graph_count_dpcm(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li, int dup_codec) { - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); li->link++; /* 1xCPU-dummy */ li->dais++; /* 1xCPU */ @@ -563,10 +538,10 @@ static int graph_count_dpcm(struct graph_priv *priv, return 0; } -static void graph_get_dais_count(struct graph_priv *priv, +static void graph_get_dais_count(struct asoc_simple_priv *priv, struct link_info *li) { - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); /* * link_num : number of links. @@ -623,14 +598,14 @@ static void graph_get_dais_count(struct graph_priv *priv, static int graph_card_probe(struct snd_soc_card *card) { - struct graph_priv *priv = snd_soc_card_get_drvdata(card); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card); int ret; - ret = asoc_simple_card_init_hp(card, &priv->hp_jack, NULL); + ret = asoc_simple_init_hp(card, &priv->hp_jack, NULL); if (ret < 0) return ret; - ret = asoc_simple_card_init_mic(card, &priv->mic_jack, NULL); + ret = asoc_simple_init_mic(card, &priv->mic_jack, NULL); if (ret < 0) return ret; @@ -639,22 +614,18 @@ static int graph_card_probe(struct snd_soc_card *card) static int graph_probe(struct platform_device *pdev) { - struct graph_priv *priv; - struct snd_soc_dai_link *dai_link; - struct graph_dai_props *dai_props; - struct asoc_simple_dai *dais; + struct asoc_simple_priv *priv; struct device *dev = &pdev->dev; struct snd_soc_card *card; - struct snd_soc_codec_conf *cconf; struct link_info li; - int ret, i; + int ret; /* Allocate the private data and the DAI link array */ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - card = graph_priv_to_card(priv); + card = simple_priv_to_card(priv); card->owner = THIS_MODULE; card->dev = dev; card->dapm_widgets = graph_dapm_widgets; @@ -666,25 +637,9 @@ static int graph_probe(struct platform_device *pdev) if (!li.link || !li.dais) return -EINVAL; - dai_props = devm_kcalloc(dev, li.link, sizeof(*dai_props), GFP_KERNEL); - dai_link = devm_kcalloc(dev, li.link, sizeof(*dai_link), GFP_KERNEL); - dais = devm_kcalloc(dev, li.dais, sizeof(*dais), GFP_KERNEL); - cconf = devm_kcalloc(dev, li.conf, sizeof(*cconf), GFP_KERNEL); - if (!dai_props || !dai_link || !dais) - return -ENOMEM; - - /* - * Use snd_soc_dai_link_component instead of legacy style - * It is codec only. but cpu/platform will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - for (i = 0; i < li.link; i++) { - dai_link[i].codecs = &dai_props[i].codecs; - dai_link[i].num_codecs = 1; - dai_link[i].platforms = &dai_props[i].platforms; - dai_link[i].num_platforms = 1; - } + ret = asoc_simple_init_priv(priv, &li); + if (ret < 0) + return ret; priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); if (IS_ERR(priv->pa_gpio)) { @@ -693,16 +648,6 @@ static int graph_probe(struct platform_device *pdev) return ret; } - priv->dai_props = dai_props; - priv->dai_link = dai_link; - priv->dais = dais; - priv->codec_conf = cconf; - - card->dai_link = dai_link; - card->num_links = li.link; - card->codec_conf = cconf; - card->num_configs = li.conf; - ret = graph_parse_of(priv); if (ret < 0) { if (ret != -EPROBE_DEFER) @@ -712,13 +657,15 @@ static int graph_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, priv); + asoc_simple_debug_info(priv); + ret = devm_snd_soc_register_card(dev, card); if (ret < 0) goto err; return 0; err: - asoc_simple_card_clean_reference(card); + asoc_simple_clean_reference(card); return ret; } @@ -727,7 +674,7 @@ static int graph_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - return asoc_simple_card_clean_reference(card); + return asoc_simple_clean_reference(card); } static const struct of_device_id graph_of_match[] = { diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 5c1424f03620..db1458a19985 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -14,8 +14,8 @@ #include <sound/jack.h> #include <sound/simple_card_utils.h> -void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, - struct snd_pcm_hw_params *params) +void asoc_simple_convert_fixup(struct asoc_simple_data *data, + struct snd_pcm_hw_params *params) { struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); @@ -30,12 +30,12 @@ void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, channels->min = channels->max = data->convert_channels; } -EXPORT_SYMBOL_GPL(asoc_simple_card_convert_fixup); +EXPORT_SYMBOL_GPL(asoc_simple_convert_fixup); -void asoc_simple_card_parse_convert(struct device *dev, - struct device_node *np, - char *prefix, - struct asoc_simple_card_data *data) +void asoc_simple_parse_convert(struct device *dev, + struct device_node *np, + char *prefix, + struct asoc_simple_data *data) { char prop[128]; @@ -49,17 +49,14 @@ void asoc_simple_card_parse_convert(struct device *dev, /* channels transfer */ snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-channels"); of_property_read_u32(np, prop, &data->convert_channels); - - dev_dbg(dev, "convert_rate %d\n", data->convert_rate); - dev_dbg(dev, "convert_channels %d\n", data->convert_channels); } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_convert); +EXPORT_SYMBOL_GPL(asoc_simple_parse_convert); -int asoc_simple_card_parse_daifmt(struct device *dev, - struct device_node *node, - struct device_node *codec, - char *prefix, - unsigned int *retfmt) +int asoc_simple_parse_daifmt(struct device *dev, + struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt) { struct device_node *bitclkmaster = NULL; struct device_node *framemaster = NULL; @@ -93,15 +90,13 @@ int asoc_simple_card_parse_daifmt(struct device *dev, *retfmt = daifmt; - dev_dbg(dev, "format : %04x\n", daifmt); - return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_daifmt); +EXPORT_SYMBOL_GPL(asoc_simple_parse_daifmt); -int asoc_simple_card_set_dailink_name(struct device *dev, - struct snd_soc_dai_link *dai_link, - const char *fmt, ...) +int asoc_simple_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...) { va_list ap; char *name = NULL; @@ -116,16 +111,14 @@ int asoc_simple_card_set_dailink_name(struct device *dev, dai_link->name = name; dai_link->stream_name = name; - - dev_dbg(dev, "name : %s\n", name); } return ret; } -EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name); +EXPORT_SYMBOL_GPL(asoc_simple_set_dailink_name); -int asoc_simple_card_parse_card_name(struct snd_soc_card *card, - char *prefix) +int asoc_simple_parse_card_name(struct snd_soc_card *card, + char *prefix) { int ret; @@ -146,34 +139,30 @@ int asoc_simple_card_parse_card_name(struct snd_soc_card *card, if (!card->name && card->dai_link) card->name = card->dai_link->name; - dev_dbg(card->dev, "Card Name: %s\n", card->name ? card->name : ""); - return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); +EXPORT_SYMBOL_GPL(asoc_simple_parse_card_name); -int asoc_simple_card_clk_enable(struct asoc_simple_dai *dai) +static int asoc_simple_clk_enable(struct asoc_simple_dai *dai) { if (dai) return clk_prepare_enable(dai->clk); return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_clk_enable); -void asoc_simple_card_clk_disable(struct asoc_simple_dai *dai) +static void asoc_simple_clk_disable(struct asoc_simple_dai *dai) { if (dai) clk_disable_unprepare(dai->clk); } -EXPORT_SYMBOL_GPL(asoc_simple_card_clk_disable); - -int asoc_simple_card_parse_clk(struct device *dev, - struct device_node *node, - struct device_node *dai_of_node, - struct asoc_simple_dai *simple_dai, - const char *dai_name, - struct snd_soc_dai_link_component *dlc) + +int asoc_simple_parse_clk(struct device *dev, + struct device_node *node, + struct device_node *dai_of_node, + struct asoc_simple_dai *simple_dai, + const char *dai_name, + struct snd_soc_dai_link_component *dlc) { struct clk *clk; u32 val; @@ -184,10 +173,8 @@ int asoc_simple_card_parse_clk(struct device *dev, * see * soc-core.c :: snd_soc_init_multicodec() */ - if (dlc) { + if (dlc) dai_of_node = dlc->of_node; - dai_name = dlc->dai_name; - } /* * Parse dai->sysclk come from "clocks = <&xxx>" @@ -211,158 +198,113 @@ int asoc_simple_card_parse_clk(struct device *dev, if (of_property_read_bool(node, "system-clock-direction-out")) simple_dai->clk_direction = SND_SOC_CLOCK_OUT; - dev_dbg(dev, "%s : sysclk = %d, direction %d\n", dai_name, - simple_dai->sysclk, simple_dai->clk_direction); - return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_clk); - -int asoc_simple_card_parse_dai(struct device_node *node, - struct snd_soc_dai_link_component *dlc, - struct device_node **dai_of_node, - const char **dai_name, - const char *list_name, - const char *cells_name, - int *is_single_link) +EXPORT_SYMBOL_GPL(asoc_simple_parse_clk); + +int asoc_simple_startup(struct snd_pcm_substream *substream) { - struct of_phandle_args args; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); int ret; - if (!node) - return 0; - - /* - * Use snd_soc_dai_link_component instead of legacy style. - * It is only for codec, but cpu will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - if (dlc) { - dai_name = &dlc->dai_name; - dai_of_node = &dlc->of_node; - } - - /* - * Get node via "sound-dai = <&phandle port>" - * it will be used as xxx_of_node on soc_bind_dai_link() - */ - ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args); + ret = asoc_simple_clk_enable(dai_props->cpu_dai); if (ret) return ret; - /* Get dai->name */ - if (dai_name) { - ret = snd_soc_of_get_dai_name(node, dai_name); - if (ret < 0) - return ret; - } - - *dai_of_node = args.np; - - if (is_single_link) - *is_single_link = !args.args_count; + ret = asoc_simple_clk_enable(dai_props->codec_dai); + if (ret) + asoc_simple_clk_disable(dai_props->cpu_dai); - return 0; + return ret; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_dai); +EXPORT_SYMBOL_GPL(asoc_simple_startup); -static int asoc_simple_card_get_dai_id(struct device_node *ep) +void asoc_simple_shutdown(struct snd_pcm_substream *substream) { - struct device_node *node; - struct device_node *endpoint; - struct of_endpoint info; - int i, id; - int ret; - - /* use driver specified DAI ID if exist */ - ret = snd_soc_get_dai_id(ep); - if (ret != -ENOTSUPP) - return ret; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); - /* use endpoint/port reg if exist */ - ret = of_graph_parse_endpoint(ep, &info); - if (ret == 0) { - /* - * Because it will count port/endpoint if it doesn't have "reg". - * But, we can't judge whether it has "no reg", or "reg = <0>" - * only of_graph_parse_endpoint(). - * We need to check "reg" property - */ - if (of_get_property(ep, "reg", NULL)) - return info.id; + asoc_simple_clk_disable(dai_props->cpu_dai); - node = of_get_parent(ep); - of_node_put(node); - if (of_get_property(node, "reg", NULL)) - return info.port; - } - node = of_graph_get_port_parent(ep); + asoc_simple_clk_disable(dai_props->codec_dai); +} +EXPORT_SYMBOL_GPL(asoc_simple_shutdown); - /* - * Non HDMI sound case, counting port/endpoint on its DT - * is enough. Let's count it. - */ - i = 0; - id = -1; - for_each_endpoint_of_node(node, endpoint) { - if (endpoint == ep) - id = i; - i++; - } +static int asoc_simple_set_clk_rate(struct asoc_simple_dai *simple_dai, + unsigned long rate) +{ + if (!simple_dai) + return 0; - of_node_put(node); + if (!simple_dai->clk) + return 0; - if (id < 0) - return -ENODEV; + if (clk_get_rate(simple_dai->clk) == rate) + return 0; - return id; + return clk_set_rate(simple_dai->clk, rate); } -int asoc_simple_card_parse_graph_dai(struct device_node *ep, - struct snd_soc_dai_link_component *dlc, - struct device_node **dai_of_node, - const char **dai_name) +int asoc_simple_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { - struct device_node *node; - struct of_phandle_args args; - int ret; - - /* - * Use snd_soc_dai_link_component instead of legacy style. - * It is only for codec, but cpu will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - if (dlc) { - dai_name = &dlc->dai_name; - dai_of_node = &dlc->of_node; - } + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + + ret = asoc_simple_set_clk_rate(dai_props->codec_dai, mclk); + if (ret < 0) + return ret; - if (!ep) - return 0; - if (!dai_name) - return 0; + ret = asoc_simple_set_clk_rate(dai_props->cpu_dai, mclk); + if (ret < 0) + return ret; - node = of_graph_get_port_parent(ep); + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; - /* Get dai->name */ - args.np = node; - args.args[0] = asoc_simple_card_get_dai_id(ep); - args.args_count = (of_graph_get_endpoint_count(node) > 1); + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} +EXPORT_SYMBOL_GPL(asoc_simple_hw_params); - ret = snd_soc_get_dai_name(&args, dai_name); - if (ret < 0) - return ret; +int asoc_simple_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); - *dai_of_node = node; + asoc_simple_convert_fixup(&dai_props->adata, params); return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_graph_dai); +EXPORT_SYMBOL_GPL(asoc_simple_be_hw_params_fixup); -int asoc_simple_card_init_dai(struct snd_soc_dai *dai, - struct asoc_simple_dai *simple_dai) +static int asoc_simple_init_dai(struct snd_soc_dai *dai, + struct asoc_simple_dai *simple_dai) { int ret; @@ -392,18 +334,37 @@ int asoc_simple_card_init_dai(struct snd_soc_dai *dai, return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_init_dai); -void asoc_simple_card_canonicalize_platform(struct snd_soc_dai_link *dai_link) +int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_init_dai(rtd->codec_dai, + dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_init_dai(rtd->cpu_dai, + dai_props->cpu_dai); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_dai_init); + +void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link) { /* Assumes platform == cpu */ if (!dai_link->platforms->of_node) dai_link->platforms->of_node = dai_link->cpu_of_node; } -EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_platform); +EXPORT_SYMBOL_GPL(asoc_simple_canonicalize_platform); -void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, - int is_single_links) +void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link, + int is_single_links) { /* * In soc_bind_dai_link() will check cpu name after @@ -417,9 +378,9 @@ void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, if (is_single_links) dai_link->cpu_dai_name = NULL; } -EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_cpu); +EXPORT_SYMBOL_GPL(asoc_simple_canonicalize_cpu); -int asoc_simple_card_clean_reference(struct snd_soc_card *card) +int asoc_simple_clean_reference(struct snd_soc_card *card) { struct snd_soc_dai_link *dai_link; int i; @@ -430,10 +391,10 @@ int asoc_simple_card_clean_reference(struct snd_soc_card *card) } return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_clean_reference); +EXPORT_SYMBOL_GPL(asoc_simple_clean_reference); -int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, - char *prefix) +int asoc_simple_parse_routing(struct snd_soc_card *card, + char *prefix) { struct device_node *node = card->dev->of_node; char prop[128]; @@ -448,10 +409,10 @@ int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, return snd_soc_of_parse_audio_routing(card, prop); } -EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_routing); +EXPORT_SYMBOL_GPL(asoc_simple_parse_routing); -int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, - char *prefix) +int asoc_simple_parse_widgets(struct snd_soc_card *card, + char *prefix) { struct device_node *node = card->dev->of_node; char prop[128]; @@ -467,11 +428,11 @@ int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, /* no widgets is not error */ return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_widgets); +EXPORT_SYMBOL_GPL(asoc_simple_parse_widgets); -int asoc_simple_card_init_jack(struct snd_soc_card *card, - struct asoc_simple_jack *sjack, - int is_hp, char *prefix) +int asoc_simple_init_jack(struct snd_soc_card *card, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) { struct device *dev = card->dev; enum of_gpio_flags flags; @@ -522,7 +483,61 @@ int asoc_simple_card_init_jack(struct snd_soc_card *card, return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_init_jack); +EXPORT_SYMBOL_GPL(asoc_simple_init_jack); + +int asoc_simple_init_priv(struct asoc_simple_priv *priv, + struct link_info *li) +{ + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link; + struct simple_dai_props *dai_props; + struct asoc_simple_dai *dais; + struct snd_soc_codec_conf *cconf = NULL; + int i; + + dai_props = devm_kcalloc(dev, li->link, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, li->link, sizeof(*dai_link), GFP_KERNEL); + dais = devm_kcalloc(dev, li->dais, sizeof(*dais), GFP_KERNEL); + if (!dai_props || !dai_link || !dais) + return -ENOMEM; + + if (li->conf) { + cconf = devm_kcalloc(dev, li->conf, sizeof(*cconf), GFP_KERNEL); + if (!cconf) + return -ENOMEM; + } + + /* + * Use snd_soc_dai_link_component instead of legacy style + * It is codec only. but cpu/platform will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + * + * "platform" might be removed + * see + * simple-card-utils.c :: asoc_simple_canonicalize_platform() + */ + for (i = 0; i < li->link; i++) { + dai_link[i].codecs = &dai_props[i].codecs; + dai_link[i].num_codecs = 1; + dai_link[i].platforms = &dai_props[i].platforms; + dai_link[i].num_platforms = 1; + } + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + priv->dais = dais; + priv->codec_conf = cconf; + + card->dai_link = priv->dai_link; + card->num_links = li->link; + card->codec_conf = cconf; + card->num_configs = li->conf; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_init_priv); /* Module information */ MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 7147bba45a2a..dd9ac60d61f1 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -15,179 +15,98 @@ #include <sound/soc-dai.h> #include <sound/soc.h> -struct simple_priv { - struct snd_soc_card snd_card; - struct simple_dai_props { - struct asoc_simple_dai *cpu_dai; - struct asoc_simple_dai *codec_dai; - struct snd_soc_dai_link_component codecs; /* single codec */ - struct snd_soc_dai_link_component platforms; - struct asoc_simple_card_data adata; - struct snd_soc_codec_conf *codec_conf; - unsigned int mclk_fs; - } *dai_props; - struct asoc_simple_jack hp_jack; - struct asoc_simple_jack mic_jack; - struct snd_soc_dai_link *dai_link; - struct asoc_simple_dai *dais; - struct snd_soc_codec_conf *codec_conf; -}; - -struct link_info { - int dais; /* number of dai */ - int link; /* number of link */ - int conf; /* number of codec_conf */ - int cpu; /* turn for CPU / Codec */ -}; - -#define simple_priv_to_card(priv) (&(priv)->snd_card) -#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) -#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) -#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) - #define DAI "sound-dai" #define CELL "#sound-dai-cells" #define PREFIX "simple-audio-card," -static int simple_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = - simple_priv_to_props(priv, rtd->num); - int ret; - - ret = asoc_simple_card_clk_enable(dai_props->cpu_dai); - if (ret) - return ret; - - ret = asoc_simple_card_clk_enable(dai_props->codec_dai); - if (ret) - asoc_simple_card_clk_disable(dai_props->cpu_dai); - - return ret; -} - -static void simple_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = - simple_priv_to_props(priv, rtd->num); - - asoc_simple_card_clk_disable(dai_props->cpu_dai); - - asoc_simple_card_clk_disable(dai_props->codec_dai); -} +static const struct snd_soc_ops simple_ops = { + .startup = asoc_simple_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; -static int simple_set_clk_rate(struct asoc_simple_dai *simple_dai, - unsigned long rate) +static int asoc_simple_parse_dai(struct device_node *node, + struct snd_soc_dai_link_component *dlc, + struct device_node **dai_of_node, + const char **dai_name, + int *is_single_link) { - if (!simple_dai) - return 0; - - if (!simple_dai->clk) - return 0; + struct of_phandle_args args; + int ret; - if (clk_get_rate(simple_dai->clk) == rate) + if (!node) return 0; - return clk_set_rate(simple_dai->clk, rate); -} - -static int simple_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 *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = - simple_priv_to_props(priv, rtd->num); - unsigned int mclk, mclk_fs = 0; - int ret = 0; - - if (dai_props->mclk_fs) - mclk_fs = dai_props->mclk_fs; - - if (mclk_fs) { - mclk = params_rate(params) * mclk_fs; + /* + * Use snd_soc_dai_link_component instead of legacy style. + * It is only for codec, but cpu will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + */ + if (dlc) { + dai_name = &dlc->dai_name; + dai_of_node = &dlc->of_node; + } - ret = simple_set_clk_rate(dai_props->codec_dai, mclk); - if (ret < 0) - return ret; + /* + * Get node via "sound-dai = <&phandle port>" + * it will be used as xxx_of_node on soc_bind_dai_link() + */ + ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args); + if (ret) + return ret; - ret = simple_set_clk_rate(dai_props->cpu_dai, mclk); + /* Get dai->name */ + if (dai_name) { + ret = snd_soc_of_get_dai_name(node, dai_name); if (ret < 0) return ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, - SND_SOC_CLOCK_IN); - if (ret && ret != -ENOTSUPP) - goto err; - - ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, - SND_SOC_CLOCK_OUT); - if (ret && ret != -ENOTSUPP) - goto err; } - return 0; -err: - return ret; -} - -static const struct snd_soc_ops simple_ops = { - .startup = simple_startup, - .shutdown = simple_shutdown, - .hw_params = simple_hw_params, -}; - -static int simple_dai_init(struct snd_soc_pcm_runtime *rtd) -{ - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); - int ret; - ret = asoc_simple_card_init_dai(rtd->codec_dai, - dai_props->codec_dai); - if (ret < 0) - return ret; + *dai_of_node = args.np; - ret = asoc_simple_card_init_dai(rtd->cpu_dai, - dai_props->cpu_dai); - if (ret < 0) - return ret; + if (is_single_link) + *is_single_link = !args.args_count; return 0; } -static int simple_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) +static void simple_parse_convert(struct device *dev, + struct device_node *np, + struct asoc_simple_data *adata) { - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); + struct device_node *top = dev->of_node; + struct device_node *node = of_get_parent(np); - asoc_simple_card_convert_fixup(&dai_props->adata, params); + asoc_simple_parse_convert(dev, top, PREFIX, adata); + asoc_simple_parse_convert(dev, node, PREFIX, adata); + asoc_simple_parse_convert(dev, node, NULL, adata); + asoc_simple_parse_convert(dev, np, NULL, adata); - return 0; + of_node_put(node); } -static void simple_get_conversion(struct device *dev, - struct device_node *np, - struct asoc_simple_card_data *adata) +static void simple_parse_mclk_fs(struct device_node *top, + struct device_node *cpu, + struct device_node *codec, + struct simple_dai_props *props, + char *prefix) { - struct device_node *top = dev->of_node; - struct device_node *node = of_get_parent(np); + struct device_node *node = of_get_parent(cpu); + char prop[128]; + + snprintf(prop, sizeof(prop), "%smclk-fs", PREFIX); + of_property_read_u32(top, prop, &props->mclk_fs); - asoc_simple_card_parse_convert(dev, top, PREFIX, adata); - asoc_simple_card_parse_convert(dev, node, PREFIX, adata); - asoc_simple_card_parse_convert(dev, node, NULL, adata); - asoc_simple_card_parse_convert(dev, np, NULL, adata); + snprintf(prop, sizeof(prop), "%smclk-fs", prefix); + of_property_read_u32(node, prop, &props->mclk_fs); + of_property_read_u32(cpu, prop, &props->mclk_fs); + of_property_read_u32(codec, prop, &props->mclk_fs); of_node_put(node); } -static int simple_dai_link_of_dpcm(struct simple_priv *priv, +static int simple_dai_link_of_dpcm(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, @@ -200,7 +119,6 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, struct snd_soc_dai_link_component *codecs = dai_link->codecs; struct device_node *top = dev->of_node; struct device_node *node = of_get_parent(np); - char prop[128]; char *prefix = ""; int ret; @@ -238,22 +156,21 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, dai = dai_props->cpu_dai = &priv->dais[li->dais++]; - ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL, - &is_single_links); + ret = asoc_simple_parse_cpu(np, dai_link, &is_single_links); if (ret) return ret; - ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai); + ret = asoc_simple_parse_clk_cpu(dev, np, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "fe.%s", - dai_link->cpu_dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); if (ret < 0) return ret; - asoc_simple_card_canonicalize_cpu(dai_link, is_single_links); + asoc_simple_canonicalize_cpu(dai_link, is_single_links); } else { struct snd_soc_codec_conf *cconf; @@ -264,7 +181,7 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, /* BE settings */ dai_link->no_pcm = 1; - dai_link->be_hw_params_fixup = simple_be_hw_params_fixup; + dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; dai = dai_props->codec_dai = &priv->dais[li->dais++]; @@ -272,17 +189,17 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, cconf = dai_props->codec_conf = &priv->codec_conf[li->conf++]; - ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL); + ret = asoc_simple_parse_codec(np, dai_link); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai); + ret = asoc_simple_parse_clk_codec(dev, np, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "be.%s", - codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "be.%s", + codecs->dai_name); if (ret < 0) return ret; @@ -295,33 +212,29 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, "prefix"); } - simple_get_conversion(dev, np, &dai_props->adata); + simple_parse_convert(dev, np, &dai_props->adata); + simple_parse_mclk_fs(top, np, codec, dai_props, prefix); - asoc_simple_card_canonicalize_platform(dai_link); + asoc_simple_canonicalize_platform(dai_link); - ret = asoc_simple_card_of_parse_tdm(np, dai); + ret = asoc_simple_parse_tdm(np, dai); if (ret) return ret; - snprintf(prop, sizeof(prop), "%smclk-fs", prefix); - of_property_read_u32(top, PREFIX "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(node, prop, &dai_props->mclk_fs); - of_property_read_u32(np, prop, &dai_props->mclk_fs); - - ret = asoc_simple_card_parse_daifmt(dev, node, codec, - prefix, &dai_link->dai_fmt); + ret = asoc_simple_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); if (ret < 0) return ret; dai_link->dpcm_playback = 1; dai_link->dpcm_capture = 1; dai_link->ops = &simple_ops; - dai_link->init = simple_dai_init; + dai_link->init = asoc_simple_dai_init; return 0; } -static int simple_dai_link_of(struct simple_priv *priv, +static int simple_dai_link_of(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, @@ -367,58 +280,53 @@ static int simple_dai_link_of(struct simple_priv *priv, codec_dai = dai_props->codec_dai = &priv->dais[li->dais++]; - ret = asoc_simple_card_parse_daifmt(dev, node, codec, - prefix, &dai_link->dai_fmt); + ret = asoc_simple_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); if (ret < 0) goto dai_link_of_err; - snprintf(prop, sizeof(prop), "%smclk-fs", prefix); - of_property_read_u32(top, PREFIX "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(node, prop, &dai_props->mclk_fs); - of_property_read_u32(cpu, prop, &dai_props->mclk_fs); - of_property_read_u32(codec, prop, &dai_props->mclk_fs); + simple_parse_mclk_fs(top, cpu, codec, dai_props, prefix); - ret = asoc_simple_card_parse_cpu(cpu, dai_link, - DAI, CELL, &single_cpu); + ret = asoc_simple_parse_cpu(cpu, dai_link, &single_cpu); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_codec(codec, dai_link, DAI, CELL); + ret = asoc_simple_parse_codec(codec, dai_link); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_platform(plat, dai_link, DAI, CELL); + ret = asoc_simple_parse_platform(plat, dai_link); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_of_parse_tdm(cpu, cpu_dai); + ret = asoc_simple_parse_tdm(cpu, cpu_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_of_parse_tdm(codec, codec_dai); + ret = asoc_simple_parse_tdm(codec, codec_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); + ret = asoc_simple_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_clk_codec(dev, codec, dai_link, codec_dai); + ret = asoc_simple_parse_clk_codec(dev, codec, dai_link, codec_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "%s-%s", - dai_link->cpu_dai_name, - dai_link->codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codecs->dai_name); if (ret < 0) goto dai_link_of_err; dai_link->ops = &simple_ops; - dai_link->init = simple_dai_init; + dai_link->init = asoc_simple_dai_init; - asoc_simple_card_canonicalize_cpu(dai_link, single_cpu); - asoc_simple_card_canonicalize_platform(dai_link); + asoc_simple_canonicalize_cpu(dai_link, single_cpu); + asoc_simple_canonicalize_platform(dai_link); dai_link_of_err: of_node_put(plat); @@ -427,13 +335,13 @@ dai_link_of_err: return ret; } -static int simple_for_each_link(struct simple_priv *priv, +static int simple_for_each_link(struct asoc_simple_priv *priv, struct link_info *li, - int (*func_noml)(struct simple_priv *priv, + int (*func_noml)(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top), - int (*func_dpcm)(struct simple_priv *priv, + int (*func_dpcm)(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top)) @@ -453,7 +361,7 @@ static int simple_for_each_link(struct simple_priv *priv, /* loop for all dai-link */ do { - struct asoc_simple_card_data adata; + struct asoc_simple_data adata; struct device_node *codec; struct device_node *np; int num = of_get_child_count(node); @@ -471,7 +379,7 @@ static int simple_for_each_link(struct simple_priv *priv, /* get convert-xxx property */ memset(&adata, 0, sizeof(adata)); for_each_child_of_node(node, np) - simple_get_conversion(dev, np, &adata); + simple_parse_convert(dev, np, &adata); /* loop for all CPU/Codec node */ for_each_child_of_node(node, np) { @@ -502,7 +410,7 @@ static int simple_for_each_link(struct simple_priv *priv, } static int simple_parse_aux_devs(struct device_node *node, - struct simple_priv *priv) + struct asoc_simple_priv *priv) { struct device *dev = simple_priv_to_dev(priv); struct device_node *aux_node; @@ -532,7 +440,7 @@ static int simple_parse_aux_devs(struct device_node *node, return 0; } -static int simple_parse_of(struct simple_priv *priv) +static int simple_parse_of(struct asoc_simple_priv *priv) { struct device *dev = simple_priv_to_dev(priv); struct device_node *top = dev->of_node; @@ -543,11 +451,11 @@ static int simple_parse_of(struct simple_priv *priv) if (!top) return -EINVAL; - ret = asoc_simple_card_of_parse_widgets(card, PREFIX); + ret = asoc_simple_parse_widgets(card, PREFIX); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_routing(card, PREFIX); + ret = asoc_simple_parse_routing(card, PREFIX); if (ret < 0) return ret; @@ -573,7 +481,7 @@ static int simple_parse_of(struct simple_priv *priv) return ret; } - ret = asoc_simple_card_parse_card_name(card, PREFIX); + ret = asoc_simple_parse_card_name(card, PREFIX); if (ret < 0) return ret; @@ -582,7 +490,7 @@ static int simple_parse_of(struct simple_priv *priv) return ret; } -static int simple_count_noml(struct simple_priv *priv, +static int simple_count_noml(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top) @@ -594,7 +502,7 @@ static int simple_count_noml(struct simple_priv *priv, return 0; } -static int simple_count_dpcm(struct simple_priv *priv, +static int simple_count_dpcm(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top) @@ -607,7 +515,7 @@ static int simple_count_dpcm(struct simple_priv *priv, return 0; } -static void simple_get_dais_count(struct simple_priv *priv, +static void simple_get_dais_count(struct asoc_simple_priv *priv, struct link_info *li) { struct device *dev = simple_priv_to_dev(priv); @@ -676,14 +584,14 @@ static void simple_get_dais_count(struct simple_priv *priv, static int simple_soc_probe(struct snd_soc_card *card) { - struct simple_priv *priv = snd_soc_card_get_drvdata(card); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card); int ret; - ret = asoc_simple_card_init_hp(card, &priv->hp_jack, PREFIX); + ret = asoc_simple_init_hp(card, &priv->hp_jack, PREFIX); if (ret < 0) return ret; - ret = asoc_simple_card_init_mic(card, &priv->mic_jack, PREFIX); + ret = asoc_simple_init_mic(card, &priv->mic_jack, PREFIX); if (ret < 0) return ret; @@ -692,16 +600,12 @@ static int simple_soc_probe(struct snd_soc_card *card) static int simple_probe(struct platform_device *pdev) { - struct simple_priv *priv; - struct snd_soc_dai_link *dai_link; - struct simple_dai_props *dai_props; - struct asoc_simple_dai *dais; + struct asoc_simple_priv *priv; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct snd_soc_card *card; - struct snd_soc_codec_conf *cconf; struct link_info li; - int ret, i; + int ret; /* Allocate the private data and the DAI link array */ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -718,35 +622,9 @@ static int simple_probe(struct platform_device *pdev) if (!li.link || !li.dais) return -EINVAL; - dai_props = devm_kcalloc(dev, li.link, sizeof(*dai_props), GFP_KERNEL); - dai_link = devm_kcalloc(dev, li.link, sizeof(*dai_link), GFP_KERNEL); - dais = devm_kcalloc(dev, li.dais, sizeof(*dais), GFP_KERNEL); - cconf = devm_kcalloc(dev, li.conf, sizeof(*cconf), GFP_KERNEL); - if (!dai_props || !dai_link || !dais) - return -ENOMEM; - - /* - * Use snd_soc_dai_link_component instead of legacy style - * It is codec only. but cpu/platform will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - for (i = 0; i < li.link; i++) { - dai_link[i].codecs = &dai_props[i].codecs; - dai_link[i].num_codecs = 1; - dai_link[i].platforms = &dai_props[i].platforms; - dai_link[i].num_platforms = 1; - } - - priv->dai_props = dai_props; - priv->dai_link = dai_link; - priv->dais = dais; - priv->codec_conf = cconf; - - card->dai_link = priv->dai_link; - card->num_links = li.link; - card->codec_conf = cconf; - card->num_configs = li.conf; + ret = asoc_simple_init_priv(priv, &li); + if (ret < 0) + return ret; if (np && of_device_is_available(np)) { @@ -761,6 +639,9 @@ static int simple_probe(struct platform_device *pdev) struct asoc_simple_card_info *cinfo; struct snd_soc_dai_link_component *codecs; struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dai_link = priv->dai_link; + struct simple_dai_props *dai_props = priv->dai_props; + int dai_idx = 0; cinfo = dev->platform_data; @@ -793,22 +674,24 @@ static int simple_probe(struct platform_device *pdev) dai_link->stream_name = cinfo->name; dai_link->cpu_dai_name = cinfo->cpu_dai.name; dai_link->dai_fmt = cinfo->daifmt; - dai_link->init = simple_dai_init; - memcpy(priv->dai_props->cpu_dai, &cinfo->cpu_dai, - sizeof(*priv->dai_props->cpu_dai)); - memcpy(priv->dai_props->codec_dai, &cinfo->codec_dai, - sizeof(*priv->dai_props->codec_dai)); + dai_link->init = asoc_simple_dai_init; + memcpy(dai_props->cpu_dai, &cinfo->cpu_dai, + sizeof(*dai_props->cpu_dai)); + memcpy(dai_props->codec_dai, &cinfo->codec_dai, + sizeof(*dai_props->codec_dai)); } snd_soc_card_set_drvdata(card, priv); + asoc_simple_debug_info(priv); + ret = devm_snd_soc_register_card(dev, card); if (ret < 0) goto err; return 0; err: - asoc_simple_card_clean_reference(card); + asoc_simple_clean_reference(card); return ret; } @@ -817,7 +700,7 @@ static int simple_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - return asoc_simple_card_clean_reference(card); + return asoc_simple_clean_reference(card); } static const struct of_device_id simple_of_match[] = { diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index b744add01d12..21c6675abd19 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -79,14 +79,15 @@ enum { #define BYT_RT5651_SSP0_AIF2 BIT(21) #define BYT_RT5651_HP_LR_SWAPPED BIT(22) #define BYT_RT5651_MONO_SPEAKER BIT(23) +#define BYT_RT5651_JD_NOT_INV BIT(24) #define BYT_RT5651_DEFAULT_QUIRKS (BYT_RT5651_MCLK_EN | \ BYT_RT5651_JD1_1 | \ BYT_RT5651_OVCD_TH_2000UA | \ BYT_RT5651_OVCD_SF_0P75) -/* jack-detect-source + dmic-en + ovcd-th + -sf + terminating empty entry */ -#define MAX_NO_PROPS 5 +/* jack-detect-source + inv + dmic-en + ovcd-th + -sf + terminating entry */ +#define MAX_NO_PROPS 6 struct byt_rt5651_private { struct clk *mclk; @@ -137,6 +138,8 @@ static void log_quirks(struct device *dev) dev_info(dev, "quirk SSP0_AIF2 enabled\n"); if (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV) + dev_info(dev, "quirk JD_NOT_INV enabled\n"); } #define BYT_CODEC_DAI1 "rt5651-aif1" @@ -415,6 +418,18 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { BYT_RT5651_MONO_SPEAKER), }, { + /* Complet Electro Serv MY8307 */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Complet Electro Serv"), + DMI_MATCH(DMI_PRODUCT_NAME, "MY8307"), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_MONO_SPEAKER | + BYT_RT5651_JD_NOT_INV), + }, + { /* I.T.Works TW701, Ployer Momo7w and Trekstor ST70416-6 * (these all use the same mainboard) */ .callback = byt_rt5651_quirk_cb, @@ -525,6 +540,9 @@ static int byt_rt5651_add_codec_device_props(struct device *i2c_dev) if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN) props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,dmic-en"); + if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV) + props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted"); + return device_add_properties(i2c_dev, props); } diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c index 1ae83f4ccc36..56099db8f86d 100644 --- a/sound/soc/intel/skylake/skl-pcm.c +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -181,6 +181,7 @@ int skl_pcm_link_dma_prepare(struct device *dev, struct skl_pipe_params *params) struct hdac_stream *hstream; struct hdac_ext_stream *stream; struct hdac_ext_link *link; + unsigned char stream_tag; hstream = snd_hdac_get_stream(bus, params->stream, params->link_dma_id + 1); @@ -199,10 +200,13 @@ int skl_pcm_link_dma_prepare(struct device *dev, struct skl_pipe_params *params) snd_hdac_ext_link_stream_setup(stream, format_val); - list_for_each_entry(link, &bus->hlink_list, list) { - if (link->index == params->link_index) - snd_hdac_ext_link_set_stream_id(link, - hstream->stream_tag); + stream_tag = hstream->stream_tag; + if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { + list_for_each_entry(link, &bus->hlink_list, list) { + if (link->index == params->link_index) + snd_hdac_ext_link_set_stream_id(link, + stream_tag); + } } stream->link_prepared = 1; @@ -645,6 +649,7 @@ static int skl_link_hw_free(struct snd_pcm_substream *substream, struct hdac_ext_stream *link_dev = snd_soc_dai_get_dma_data(dai, substream); struct hdac_ext_link *link; + unsigned char stream_tag; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); @@ -654,7 +659,11 @@ static int skl_link_hw_free(struct snd_pcm_substream *substream, if (!link) return -EINVAL; - snd_hdac_ext_link_clear_stream_id(link, hdac_stream(link_dev)->stream_tag); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK); return 0; } diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig index 1a354a6b6e87..b3f9c41b4319 100644 --- a/sound/soc/jz4740/Kconfig +++ b/sound/soc/jz4740/Kconfig @@ -1,6 +1,6 @@ config SND_JZ4740_SOC tristate "SoC Audio for Ingenic JZ4740 SoC" - depends on MACH_JZ4740 || COMPILE_TEST + depends on MIPS || COMPILE_TEST select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M if you want to add support for codecs attached to diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index b35410e4020e..874404bcccfd 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -116,6 +116,32 @@ config SND_SOC_MT8183 Select Y if you have such device. If unsure select "N". +config SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A + tristate "ASoC Audio driver for MT8183 with MT6358 TS3A227E MAX98357A codec" + depends on SND_SOC_MT8183 + select SND_SOC_MT6358 + select SND_SOC_MAX98357A + select SND_SOC_BT_SCO + select SND_SOC_TS3A227E + help + This adds ASoC driver for Mediatek MT8183 boards + with the MT6358 TS3A227E MAX98357A audio codec. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8183_DA7219_MAX98357A + tristate "ASoC Audio driver for MT8183 with DA7219 MAX98357A codec" + depends on SND_SOC_MT8183 + select SND_SOC_MT6358 + select SND_SOC_MAX98357A + select SND_SOC_DA7219 + select SND_SOC_BT_SCO + help + This adds ASoC driver for Mediatek MT8183 boards + with the DA7219 MAX98357A audio codec. + Select Y if you have such device. + If unsure select "N". + config SND_SOC_MTK_BTCVSD tristate "ALSA BT SCO CVSD/MSBC Driver" help diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index cf4978be062f..fded11d14cde 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -18,11 +18,11 @@ static int mtk_regmap_update_bits(struct regmap *map, int reg, unsigned int mask, - unsigned int val) + unsigned int val, int shift) { - if (reg < 0) + if (reg < 0 || WARN_ON_ONCE(shift < 0)) return 0; - return regmap_update_bits(map, reg, mask, val); + return regmap_update_bits(map, reg, mask << shift, val << shift); } static int mtk_regmap_write(struct regmap *map, int reg, unsigned int val) @@ -49,8 +49,7 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); /* enable agent */ mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, - 1 << memif->data->agent_disable_shift, - 0 << memif->data->agent_disable_shift); + 1, 0, memif->data->agent_disable_shift); snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware); @@ -105,8 +104,7 @@ void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, irq_id = memif->irq_usage; mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, - 1 << memif->data->agent_disable_shift, - 1 << memif->data->agent_disable_shift); + 1, 1, memif->data->agent_disable_shift); if (!memif->const_irq) { mtk_dynamic_irq_release(afe, irq_id); @@ -144,16 +142,14 @@ int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, /* set MSB to 33-bit */ mtk_regmap_update_bits(afe->regmap, memif->data->msb_reg, - 1 << memif->data->msb_shift, - msb_at_bit33 << memif->data->msb_shift); + 1, msb_at_bit33, memif->data->msb_shift); /* set channel */ if (memif->data->mono_shift >= 0) { unsigned int mono = (params_channels(params) == 1) ? 1 : 0; mtk_regmap_update_bits(afe->regmap, memif->data->mono_reg, - 1 << memif->data->mono_shift, - mono << memif->data->mono_shift); + 1, mono, memif->data->mono_shift); } /* set rate */ @@ -166,8 +162,8 @@ int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, return -EINVAL; mtk_regmap_update_bits(afe->regmap, memif->data->fs_reg, - memif->data->fs_maskbit << memif->data->fs_shift, - fs << memif->data->fs_shift); + memif->data->fs_maskbit, fs, + memif->data->fs_shift); return 0; } @@ -197,17 +193,14 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - if (memif->data->enable_shift >= 0) - mtk_regmap_update_bits(afe->regmap, - memif->data->enable_reg, - 1 << memif->data->enable_shift, - 1 << memif->data->enable_shift); + mtk_regmap_update_bits(afe->regmap, + memif->data->enable_reg, + 1, 1, memif->data->enable_shift); /* set irq counter */ mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, - irq_data->irq_cnt_maskbit - << irq_data->irq_cnt_shift, - counter << irq_data->irq_cnt_shift); + irq_data->irq_cnt_maskbit, counter, + irq_data->irq_cnt_shift); /* set irq fs */ fs = afe->irq_fs(substream, runtime->rate); @@ -216,24 +209,21 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, return -EINVAL; mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, - irq_data->irq_fs_maskbit - << irq_data->irq_fs_shift, - fs << irq_data->irq_fs_shift); + irq_data->irq_fs_maskbit, fs, + irq_data->irq_fs_shift); /* enable interrupt */ mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, - 1 << irq_data->irq_en_shift, - 1 << irq_data->irq_en_shift); + 1, 1, irq_data->irq_en_shift); return 0; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: mtk_regmap_update_bits(afe->regmap, memif->data->enable_reg, - 1 << memif->data->enable_shift, 0); + 1, 0, memif->data->enable_shift); /* disable interrupt */ mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, - 1 << irq_data->irq_en_shift, - 0 << irq_data->irq_en_shift); + 1, 0, irq_data->irq_en_shift); /* and clear pending IRQ */ mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, 1 << irq_data->irq_clr_shift); @@ -270,8 +260,7 @@ int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, } mtk_regmap_update_bits(afe->regmap, memif->data->hd_reg, - 1 << memif->data->hd_shift, - hd_audio << memif->data->hd_shift); + 1, hd_audio, memif->data->hd_shift); return 0; } diff --git a/sound/soc/mediatek/common/mtk-btcvsd.c b/sound/soc/mediatek/common/mtk-btcvsd.c index 1b8bcdaf02d1..9a163d7064d1 100644 --- a/sound/soc/mediatek/common/mtk-btcvsd.c +++ b/sound/soc/mediatek/common/mtk-btcvsd.c @@ -49,6 +49,7 @@ enum bt_sco_state { BT_SCO_STATE_IDLE, BT_SCO_STATE_RUNNING, BT_SCO_STATE_ENDING, + BT_SCO_STATE_LOOPBACK, }; enum bt_sco_direct { @@ -486,7 +487,8 @@ static irqreturn_t mtk_btcvsd_snd_irq_handler(int irq_id, void *dev) if (bt->rx->state != BT_SCO_STATE_RUNNING && bt->rx->state != BT_SCO_STATE_ENDING && bt->tx->state != BT_SCO_STATE_RUNNING && - bt->tx->state != BT_SCO_STATE_ENDING) { + bt->tx->state != BT_SCO_STATE_ENDING && + bt->tx->state != BT_SCO_STATE_LOOPBACK) { dev_warn(bt->dev, "%s(), in idle state: rx->state: %d, tx->state: %d\n", __func__, bt->rx->state, bt->tx->state); goto irq_handler_exit; @@ -512,6 +514,42 @@ static irqreturn_t mtk_btcvsd_snd_irq_handler(int irq_id, void *dev) buf_cnt_tx = btsco_packet_info[packet_type][2]; buf_cnt_rx = btsco_packet_info[packet_type][3]; + if (bt->tx->state == BT_SCO_STATE_LOOPBACK) { + u8 *src, *dst; + unsigned long connsys_addr_rx, ap_addr_rx; + unsigned long connsys_addr_tx, ap_addr_tx; + + connsys_addr_rx = *bt->bt_reg_pkt_r; + ap_addr_rx = (unsigned long)bt->bt_sram_bank2_base + + (connsys_addr_rx & 0xFFFF); + + connsys_addr_tx = *bt->bt_reg_pkt_w; + ap_addr_tx = (unsigned long)bt->bt_sram_bank2_base + + (connsys_addr_tx & 0xFFFF); + + if (connsys_addr_tx == 0xdeadfeed || + connsys_addr_rx == 0xdeadfeed) { + /* bt return 0xdeadfeed if read reg during bt sleep */ + dev_warn(bt->dev, "%s(), connsys_addr_tx == 0xdeadfeed\n", + __func__); + goto irq_handler_exit; + } + + src = (u8 *)ap_addr_rx; + dst = (u8 *)ap_addr_tx; + + mtk_btcvsd_snd_data_transfer(BT_SCO_DIRECT_BT2ARM, src, + bt->tx->temp_packet_buf, + packet_length, + packet_num); + mtk_btcvsd_snd_data_transfer(BT_SCO_DIRECT_ARM2BT, + bt->tx->temp_packet_buf, dst, + packet_length, + packet_num); + bt->rx->rw_cnt++; + bt->tx->rw_cnt++; + } + if (bt->rx->state == BT_SCO_STATE_RUNNING || bt->rx->state == BT_SCO_STATE_ENDING) { if (bt->rx->xrun) { @@ -1067,6 +1105,33 @@ static int btcvsd_band_set(struct snd_kcontrol *kcontrol, return 0; } +static int btcvsd_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); + bool lpbk_en = bt->tx->state == BT_SCO_STATE_LOOPBACK; + + ucontrol->value.integer.value[0] = lpbk_en; + return 0; +} + +static int btcvsd_loopback_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); + + if (ucontrol->value.integer.value[0]) { + mtk_btcvsd_snd_set_state(bt, bt->tx, BT_SCO_STATE_LOOPBACK); + mtk_btcvsd_snd_set_state(bt, bt->rx, BT_SCO_STATE_LOOPBACK); + } else { + mtk_btcvsd_snd_set_state(bt, bt->tx, BT_SCO_STATE_RUNNING); + mtk_btcvsd_snd_set_state(bt, bt->rx, BT_SCO_STATE_RUNNING); + } + return 0; +} + static int btcvsd_tx_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1202,6 +1267,8 @@ static int btcvsd_tx_timestamp_get(struct snd_kcontrol *kcontrol, static const struct snd_kcontrol_new mtk_btcvsd_snd_controls[] = { SOC_ENUM_EXT("BTCVSD Band", btcvsd_enum[0], btcvsd_band_get, btcvsd_band_set), + SOC_SINGLE_BOOL_EXT("BTCVSD Loopback Switch", 0, + btcvsd_loopback_get, btcvsd_loopback_set), SOC_SINGLE_BOOL_EXT("BTCVSD Tx Mute Switch", 0, btcvsd_tx_mute_get, btcvsd_tx_mute_set), SOC_SINGLE_BOOL_EXT("BTCVSD Tx Irq Received Switch", 0, diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index 968fba4d7533..7064a9fd6f74 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -994,7 +994,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 6, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL2", @@ -1013,7 +1012,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 7, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL3", @@ -1032,7 +1030,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 8, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL4", @@ -1051,7 +1048,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 9, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL5", @@ -1070,7 +1066,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 10, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DLM", @@ -1089,7 +1084,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 12, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL1", @@ -1108,7 +1102,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 0, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL2", @@ -1127,7 +1120,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 1, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL3", @@ -1146,7 +1138,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 2, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL4", @@ -1165,7 +1156,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 3, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL5", @@ -1184,7 +1174,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 4, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DLBT", @@ -1203,7 +1192,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 13, .msb_reg = -1, - .msb_shift = -1, }, { .name = "ULBT", @@ -1222,7 +1210,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 16, .msb_reg = -1, - .msb_shift = -1, }, }; diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c index bff7d71d0742..08a6532da322 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c @@ -401,9 +401,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL1_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_DL2] = { .name = "DL2", @@ -420,9 +418,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_DL3] = { .name = "DL3", @@ -439,9 +435,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL3_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_VUL] = { .name = "VUL", @@ -458,9 +452,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = VUL_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_AWB] = { .name = "AWB", @@ -477,9 +469,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = AWB_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_VUL12] = { .name = "VUL12", @@ -496,9 +486,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = VUL_DATA2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_DAI] = { .name = "DAI", @@ -515,9 +503,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DAI_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_MOD_DAI] = { .name = "MOD_DAI", @@ -534,9 +520,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = MOD_DAI_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 166aed28330d..0382896c162e 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -714,13 +714,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 21, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 1, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 0, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "DL2", .id = MT8173_AFE_MEMIF_DL2, @@ -732,13 +730,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 22, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 2, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 1, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "VUL", .id = MT8173_AFE_MEMIF_VUL, @@ -750,13 +746,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 27, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 3, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 6, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "DAI", .id = MT8173_AFE_MEMIF_DAI, @@ -768,13 +762,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = -1, .mono_shift = -1, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 4, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 5, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "AWB", .id = MT8173_AFE_MEMIF_AWB, @@ -786,13 +778,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 24, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 6, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 3, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "MOD_DAI", .id = MT8173_AFE_MEMIF_MOD_DAI, @@ -804,13 +794,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 30, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 7, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 4, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "HDMI", .id = MT8173_AFE_MEMIF_HDMI, @@ -822,13 +810,10 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = -1, .mono_shift = -1, .hd_reg = -1, - .hd_shift = -1, .enable_reg = -1, - .enable_shift = -1, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 8, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, }; @@ -914,7 +899,6 @@ static const struct mtk_base_irq_data irq_data[MT8173_AFE_IRQ_NUM] = { .irq_en_reg = AFE_IRQ_MCU_CON, .irq_en_shift = 12, .irq_fs_reg = -1, - .irq_fs_shift = -1, .irq_fs_maskbit = -1, .irq_clr_reg = AFE_IRQ_CLR, .irq_clr_shift = 4, diff --git a/sound/soc/mediatek/mt8183/Makefile b/sound/soc/mediatek/mt8183/Makefile index f3ee6ac98fe8..c0a3bbc2c1f6 100644 --- a/sound/soc/mediatek/mt8183/Makefile +++ b/sound/soc/mediatek/mt8183/Makefile @@ -11,3 +11,5 @@ snd-soc-mt8183-afe-objs := \ mt8183-dai-adda.o obj-$(CONFIG_SND_SOC_MT8183) += snd-soc-mt8183-afe.o +obj-$(CONFIG_SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A) += mt8183-mt6358-ts3a227-max98357.o +obj-$(CONFIG_SND_SOC_MT8183_DA7219_MAX98357A) += mt8183-da7219-max98357.o diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index f523ad103acc..48e81c5d52fc 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -605,6 +605,10 @@ void mt8183_mck_disable(struct mtk_base_afe *afe, int mck_id) int m_sel_id = mck_div[mck_id].m_sel_id; int div_clk_id = mck_div[mck_id].div_clk_id; + /* i2s5 mck not support */ + if (mck_id == MT8183_I2S5_MCK) + return; + clk_disable_unprepare(afe_priv->clk[div_clk_id]); if (m_sel_id >= 0) clk_disable_unprepare(afe_priv->clk[m_sel_id]); diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c index 4e045dd305a7..43be51bf0329 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c @@ -421,9 +421,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL1_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_DL2] = { .name = "DL2", @@ -440,9 +438,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_DL3] = { .name = "DL3", @@ -459,9 +455,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL3_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_VUL2] = { .name = "VUL2", @@ -478,9 +472,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = VUL2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_AWB] = { .name = "AWB", @@ -497,9 +489,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = AWB_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_AWB2] = { .name = "AWB2", @@ -516,9 +506,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = AWB2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_VUL12] = { .name = "VUL12", @@ -535,9 +523,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = VUL12_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_MOD_DAI] = { .name = "MOD_DAI", @@ -554,9 +540,7 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = MOD_DAI_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT8183_MEMIF_HDMI] = { .name = "HDMI", @@ -569,13 +553,10 @@ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = { .mono_reg = -1, .mono_shift = -1, .enable_reg = -1, /* control in tdm for sync start */ - .enable_shift = -1, .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = HDMI_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, }; @@ -690,7 +671,6 @@ static const struct mtk_base_irq_data irq_data[MT8183_IRQ_NUM] = { .irq_cnt_shift = 0, .irq_cnt_maskbit = 0x3ffff, .irq_fs_reg = -1, - .irq_fs_shift = -1, .irq_fs_maskbit = -1, .irq_en_reg = AFE_IRQ_MCU_CON0, .irq_en_shift = IRQ8_MCU_ON_SFT, diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c new file mode 100644 index 000000000000..93536659a859 --- /dev/null +++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt8183-da7219-max98357.c +// -- MT8183-DA7219-MAX98357 ALSA SoC machine driver +// +// Copyright (c) 2018 MediaTek Inc. +// Author: Shunli Wang <shunli.wang@mediatek.com> + +#include <linux/module.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/pinctrl/consumer.h> + +#include "mt8183-afe-common.h" +#include "../../codecs/da7219-aad.h" +#include "../../codecs/da7219.h" + +static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin headset_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_dai_link_component +mt8183_da7219_max98357_external_codecs[] = { + { + .name = "max98357a", + .dai_name = "HiFi", + }, + { + .name = "da7219.5-001a", + .dai_name = "da7219-hifi", + }, +}; + +static int mt8183_mt6358_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int mclk_fs_ratio = 128; + unsigned int mclk_fs = rate * mclk_fs_ratio; + + return snd_soc_dai_set_sysclk(rtd->cpu_dai, + 0, mclk_fs, SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops mt8183_mt6358_i2s_ops = { + .hw_params = mt8183_mt6358_i2s_hw_params, +}; + +static int mt8183_da7219_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int mclk_fs_ratio = 256; + unsigned int mclk_fs = rate * mclk_fs_ratio; + unsigned int freq; + int ret = 0, j; + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, + mclk_fs, SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(rtd->dev, "failed to set cpu dai sysclk\n"); + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + + if (!strcmp(codec_dai->component->name, "da7219.5-001a")) { + ret = snd_soc_dai_set_sysclk(codec_dai, + DA7219_CLKSRC_MCLK, + mclk_fs, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "failed to set sysclk\n"); + + if ((rate % 8000) == 0) + freq = DA7219_PLL_FREQ_OUT_98304; + else + freq = DA7219_PLL_FREQ_OUT_90316; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, + 0, freq); + if (ret) + dev_err(rtd->dev, "failed to start PLL: %d\n", + ret); + } + } + + return ret; +} + +static int mt8183_da7219_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0, j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + + if (!strcmp(codec_dai->component->name, "da7219.5-001a")) { + ret = snd_soc_dai_set_pll(codec_dai, + 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(rtd->dev, "failed to stop PLL: %d\n", + ret); + break; + } + } + } + + return ret; +} + +static const struct snd_soc_ops mt8183_da7219_i2s_ops = { + .hw_params = mt8183_da7219_i2s_hw_params, + .hw_free = mt8183_da7219_hw_free, +}; + +static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + + return 0; +} + +static const struct snd_soc_dapm_widget +mt8183_da7219_max98357_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("IT6505_8CH"), +}; + +static const struct snd_soc_dapm_route mt8183_da7219_max98357_dapm_routes[] = { + {"IT6505_8CH", NULL, "TDM"}, +}; + +static struct snd_soc_dai_link mt8183_da7219_max98357_dai_links[] = { + /* FE */ + { + .name = "Playback_1", + .stream_name = "Playback_1", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_2", + .stream_name = "Playback_2", + .cpu_dai_name = "DL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_3", + .stream_name = "Playback_3", + .cpu_dai_name = "DL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Capture_1", + .stream_name = "Capture_1", + .cpu_dai_name = "UL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_2", + .stream_name = "Capture_2", + .cpu_dai_name = "UL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_3", + .stream_name = "Capture_3", + .cpu_dai_name = "UL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_Mono_1", + .stream_name = "Capture_Mono_1", + .cpu_dai_name = "UL_MONO_1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Playback_HDMI", + .stream_name = "Playback_HDMI", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + /* BE */ + { + .name = "Primary Codec", + .cpu_dai_name = "ADDA", + .codec_dai_name = "mt6358-snd-codec-aif1", + .codec_name = "mt6358-sound", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 1", + .cpu_dai_name = "PCM 1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 2", + .cpu_dai_name = "PCM 2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "I2S0", + .cpu_dai_name = "I2S0", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S1", + .cpu_dai_name = "I2S1", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S2", + .cpu_dai_name = "I2S2", + .codec_dai_name = "da7219-hifi", + .codec_name = "da7219.5-001a", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_da7219_i2s_ops, + }, + { + .name = "I2S3", + .cpu_dai_name = "I2S3", + .codecs = mt8183_da7219_max98357_external_codecs, + .num_codecs = + ARRAY_SIZE(mt8183_da7219_max98357_external_codecs), + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_da7219_i2s_ops, + }, + { + .name = "I2S5", + .cpu_dai_name = "I2S5", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "TDM", + .cpu_dai_name = "TDM", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + }, +}; + +static int +mt8183_da7219_max98357_headset_init(struct snd_soc_component *component); + +static struct snd_soc_aux_dev mt8183_da7219_max98357_headset_dev = { + .name = "Headset Chip", + .init = mt8183_da7219_max98357_headset_init, +}; + +static struct snd_soc_codec_conf mt6358_codec_conf[] = { + { + .dev_name = "mt6358-sound", + .name_prefix = "Mt6358", + }, +}; + +static struct snd_soc_card mt8183_da7219_max98357_card = { + .name = "mt8183_da7219_max98357", + .owner = THIS_MODULE, + .dai_link = mt8183_da7219_max98357_dai_links, + .num_links = ARRAY_SIZE(mt8183_da7219_max98357_dai_links), + .aux_dev = &mt8183_da7219_max98357_headset_dev, + .num_aux_devs = 1, + .codec_conf = mt6358_codec_conf, + .num_configs = ARRAY_SIZE(mt6358_codec_conf), +}; + +static int +mt8183_da7219_max98357_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&mt8183_da7219_max98357_card, + "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + da7219_aad_jack_det(component, &headset_jack); + + return ret; +} + +static int mt8183_da7219_max98357_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8183_da7219_max98357_card; + struct device_node *platform_node; + struct snd_soc_dai_link *dai_link; + struct pinctrl *default_pins; + int ret, i; + + card->dev = &pdev->dev; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + /* In the alsa soc-core, the "platform" will be + * allocated by devm_kzalloc if null. + * There is a special case that registerring + * sound card is failed at the first time, but + * the "platform" will not null when probe is trying + * again. It's not expected normally. + */ + dai_link->platform = NULL; + + if (dai_link->platform_name) + continue; + dai_link->platform_of_node = platform_node; + } + + mt8183_da7219_max98357_headset_dev.codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "mediatek,headset-codec", 0); + if (!mt8183_da7219_max98357_headset_dev.codec_of_node) { + dev_err(&pdev->dev, + "Property 'mediatek,headset-codec' missing/invalid\n"); + return -EINVAL; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; + } + + default_pins = + devm_pinctrl_get_select(&pdev->dev, PINCTRL_STATE_DEFAULT); + if (IS_ERR(default_pins)) { + dev_err(&pdev->dev, "%s set pins failed\n", + __func__); + return PTR_ERR(default_pins); + } + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt8183_da7219_max98357_dt_match[] = { + {.compatible = "mediatek,mt8183_da7219_max98357",}, + {} +}; +#endif + +static struct platform_driver mt8183_da7219_max98357_driver = { + .driver = { + .name = "mt8183_da7219_max98357", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = mt8183_da7219_max98357_dt_match, +#endif + }, + .probe = mt8183_da7219_max98357_dev_probe, +}; + +module_platform_driver(mt8183_da7219_max98357_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8183-DA7219-MAX98357 ALSA SoC machine driver"); +MODULE_AUTHOR("Shunli Wang <shunli.wang@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt8183_da7219_max98357 soc card"); + diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c new file mode 100644 index 000000000000..2da56232a9e1 --- /dev/null +++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt8183-mt6358.c -- +// MT8183-MT6358-TS3A227-MAX98357 ALSA SoC machine driver +// +// Copyright (c) 2018 MediaTek Inc. +// Author: Shunli Wang <shunli.wang@mediatek.com> + +#include <linux/module.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/pinctrl/consumer.h> + +#include "mt8183-afe-common.h" +#include "../../codecs/ts3a227e.h" + +static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin headset_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + +}; + +static int mt8183_mt6358_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int mclk_fs_ratio = 128; + unsigned int mclk_fs = rate * mclk_fs_ratio; + + return snd_soc_dai_set_sysclk(rtd->cpu_dai, + 0, mclk_fs, SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops mt8183_mt6358_i2s_ops = { + .hw_params = mt8183_mt6358_i2s_hw_params, +}; + +static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + dev_dbg(rtd->dev, "%s(), fix format to 32bit\n", __func__); + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +} + +static const struct snd_soc_dapm_widget +mt8183_mt6358_ts3a227_max98357_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("IT6505_8CH"), +}; + +static const struct snd_soc_dapm_route +mt8183_mt6358_ts3a227_max98357_dapm_routes[] = { + {"IT6505_8CH", NULL, "TDM"}, +}; + +static struct snd_soc_dai_link +mt8183_mt6358_ts3a227_max98357_dai_links[] = { + /* FE */ + { + .name = "Playback_1", + .stream_name = "Playback_1", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_2", + .stream_name = "Playback_2", + .cpu_dai_name = "DL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_3", + .stream_name = "Playback_3", + .cpu_dai_name = "DL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Capture_1", + .stream_name = "Capture_1", + .cpu_dai_name = "UL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_2", + .stream_name = "Capture_2", + .cpu_dai_name = "UL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_3", + .stream_name = "Capture_3", + .cpu_dai_name = "UL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_Mono_1", + .stream_name = "Capture_Mono_1", + .cpu_dai_name = "UL_MONO_1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Playback_HDMI", + .stream_name = "Playback_HDMI", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + /* BE */ + { + .name = "Primary Codec", + .cpu_dai_name = "ADDA", + .codec_dai_name = "mt6358-snd-codec-aif1", + .codec_name = "mt6358-sound", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 1", + .cpu_dai_name = "PCM 1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 2", + .cpu_dai_name = "PCM 2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "I2S0", + .cpu_dai_name = "I2S0", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S1", + .cpu_dai_name = "I2S1", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S2", + .cpu_dai_name = "I2S2", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S3", + .cpu_dai_name = "I2S3", + .codec_dai_name = "HiFi", + .codec_name = "max98357a", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S5", + .cpu_dai_name = "I2S5", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "TDM", + .cpu_dai_name = "TDM", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + }, +}; + +static int +mt8183_mt6358_ts3a227_max98357_headset_init(struct snd_soc_component *cpnt); + +static struct snd_soc_aux_dev mt8183_mt6358_ts3a227_max98357_headset_dev = { + .name = "Headset Chip", + .init = mt8183_mt6358_ts3a227_max98357_headset_init, +}; + +static struct snd_soc_card mt8183_mt6358_ts3a227_max98357_card = { + .name = "mt8183_mt6358_ts3a227_max98357", + .owner = THIS_MODULE, + .dai_link = mt8183_mt6358_ts3a227_max98357_dai_links, + .num_links = ARRAY_SIZE(mt8183_mt6358_ts3a227_max98357_dai_links), + .aux_dev = &mt8183_mt6358_ts3a227_max98357_headset_dev, + .num_aux_devs = 1, +}; + +static int +mt8183_mt6358_ts3a227_max98357_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&mt8183_mt6358_ts3a227_max98357_card, + "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + ret = ts3a227e_enable_jack_detect(component, &headset_jack); + + return ret; +} + +static int +mt8183_mt6358_ts3a227_max98357_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8183_mt6358_ts3a227_max98357_card; + struct device_node *platform_node; + struct snd_soc_dai_link *dai_link; + struct pinctrl *default_pins; + int ret, i; + + card->dev = &pdev->dev; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + /* In the alsa soc-core, the "platform" will be + * allocated by devm_kzalloc if null. + * There is a special case that registerring + * sound card is failed at the first time, but + * the "platform" will not null when probe is trying + * again. It's not expected normally. + */ + dai_link->platform = NULL; + + if (dai_link->platform_name) + continue; + dai_link->platform_of_node = platform_node; + } + + mt8183_mt6358_ts3a227_max98357_headset_dev.codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "mediatek,headset-codec", 0); + if (!mt8183_mt6358_ts3a227_max98357_headset_dev.codec_of_node) { + dev_err(&pdev->dev, + "Property 'mediatek,headset-codec' missing/invalid\n"); + return -EINVAL; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + default_pins = + devm_pinctrl_get_select(&pdev->dev, PINCTRL_STATE_DEFAULT); + if (IS_ERR(default_pins)) { + dev_err(&pdev->dev, "%s set pins failed\n", + __func__); + return PTR_ERR(default_pins); + } + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt8183_mt6358_ts3a227_max98357_dt_match[] = { + {.compatible = "mediatek,mt8183_mt6358_ts3a227_max98357",}, + {} +}; +#endif + +static struct platform_driver mt8183_mt6358_ts3a227_max98357_driver = { + .driver = { + .name = "mt8183_mt6358_ts3a227_max98357", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = mt8183_mt6358_ts3a227_max98357_dt_match, +#endif + }, + .probe = mt8183_mt6358_ts3a227_max98357_dev_probe, +}; + +module_platform_driver(mt8183_mt6358_ts3a227_max98357_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8183-MT6358-TS3A227-MAX98357 ALSA SoC machine driver"); +MODULE_AUTHOR("Shunli Wang <shunli.wang@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt8183_mt6358_ts3a227_max98357 soc card"); + diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 75ceb04d8bf0..b1764af858ba 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -98,7 +98,7 @@ config SND_SOC_MSM8996 config SND_SOC_SDM845 tristate "SoC Machine driver for SDM845 boards" - depends on QCOM_APR && MFD_CROS_EC + depends on QCOM_APR && MFD_CROS_EC && I2C select SND_SOC_QDSP6 select SND_SOC_QCOM_COMMON select SND_SOC_RT5663 diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 4231001226f4..9722940da6a4 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -88,12 +88,6 @@ struct samsung_i2s_priv { struct platform_device *pdev; struct platform_device *pdev_sec; - /* Memory mapped SFR region */ - void __iomem *addr; - - /* Spinlock protecting access to the device's registers */ - spinlock_t lock; - /* Lock for cross interface checks */ spinlock_t pcm_lock; @@ -122,6 +116,15 @@ struct samsung_i2s_priv { /* The clock provider's data */ struct clk *clk_table[3]; struct clk_onecell_data clk_data; + + /* Spinlock protecting member fields below */ + spinlock_t lock; + + /* Memory mapped SFR region */ + void __iomem *addr; + + /* A flag indicating the I2S slave mode operation */ + bool slave_mode; }; /* Returns true if this is the 'overlay' stereo DAI */ @@ -130,15 +133,6 @@ static inline bool is_secondary(struct i2s_dai *i2s) return i2s->drv->id == SAMSUNG_I2S_ID_SECONDARY; } -/* If operating in SoC-Slave mode */ -static inline bool is_slave(struct i2s_dai *i2s) -{ - struct samsung_i2s_priv *priv = i2s->priv; - - u32 mod = readl(priv->addr + I2SMOD); - return (mod & (1 << priv->variant_regs->mss_off)) ? true : false; -} - /* If this interface of the controller is transmitting data */ static inline bool tx_active(struct i2s_dai *i2s) { @@ -715,6 +709,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) mod &= ~(sdf_mask | lrp_rlow | mod_slave); mod |= tmp; writel(mod, priv->addr + I2SMOD); + priv->slave_mode = (mod & mod_slave); spin_unlock_irqrestore(&priv->lock, flags); pm_runtime_put(dai->dev); @@ -917,7 +912,7 @@ static int config_setup(struct i2s_dai *i2s) set_rfs(i2s, rfs); /* Don't bother with PSR in Slave mode */ - if (is_slave(i2s)) + if (priv->slave_mode) return 0; if (!(priv->quirks & QUIRK_NO_MUXPSR)) { @@ -1130,11 +1125,11 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { }; static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { - { "Playback Mixer", NULL, "Primary" }, - { "Playback Mixer", NULL, "Secondary" }, + { "Playback Mixer", NULL, "Primary Playback" }, + { "Playback Mixer", NULL, "Secondary Playback" }, { "Mixer DAI TX", NULL, "Playback Mixer" }, - { "Playback Mixer", NULL, "Mixer DAI RX" }, + { "Primary Capture", NULL, "Mixer DAI RX" }, }; static const struct snd_soc_component_driver samsung_i2s_component = { @@ -1155,7 +1150,8 @@ static int i2s_alloc_dais(struct samsung_i2s_priv *priv, int num_dais) { static const char *dai_names[] = { "samsung-i2s", "samsung-i2s-sec" }; - static const char *stream_names[] = { "Primary", "Secondary" }; + static const char *stream_names[] = { "Primary Playback", + "Secondary Playback" }; struct snd_soc_dai_driver *dai_drv; struct i2s_dai *dai; int i; @@ -1201,6 +1197,7 @@ static int i2s_alloc_dais(struct samsung_i2s_priv *priv, dai_drv->capture.channels_max = 2; dai_drv->capture.rates = i2s_dai_data->pcm_rates; dai_drv->capture.formats = SAMSUNG_I2S_FMTS; + dai_drv->capture.stream_name = "Primary Capture"; return 0; } diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c index 694512f980fd..1dc54c4206f0 100644 --- a/sound/soc/samsung/odroid.c +++ b/sound/soc/samsung/odroid.c @@ -91,11 +91,11 @@ static int odroid_card_be_hw_params(struct snd_pcm_substream *substream, return ret; /* - * We add 1 to the rclk_freq value in order to avoid too low clock + * We add 2 to the rclk_freq value in order to avoid too low clock * frequency values due to the EPLL output frequency not being exact * multiple of the audio sampling rate. */ - rclk_freq = params_rate(params) * rfs + 1; + rclk_freq = params_rate(params) * rfs + 2; ret = clk_set_rate(priv->sclk_i2s, rclk_freq); if (ret < 0) diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 022996d2db13..4fe83e611c01 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -110,6 +110,8 @@ static const struct of_device_id rsnd_of_match[] = { { .compatible = "renesas,rcar_sound-gen1", .data = (void *)RSND_GEN1 }, { .compatible = "renesas,rcar_sound-gen2", .data = (void *)RSND_GEN2 }, { .compatible = "renesas,rcar_sound-gen3", .data = (void *)RSND_GEN3 }, + /* Special Handling */ + { .compatible = "renesas,rcar_sound-r8a77990", .data = (void *)(RSND_GEN3 | RSND_SOC_E) }, {}, }; MODULE_DEVICE_TABLE(of, rsnd_of_match); diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 90625c57847b..0e6ef4e18400 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -607,6 +607,8 @@ struct rsnd_priv { #define RSND_GEN1 (1 << 0) #define RSND_GEN2 (2 << 0) #define RSND_GEN3 (3 << 0) +#define RSND_SOC_MASK (0xFF << 4) +#define RSND_SOC_E (1 << 4) /* E1/E2/E3 */ /* * below value will be filled on rsnd_gen_probe() @@ -679,6 +681,9 @@ struct rsnd_priv { #define rsnd_is_gen1(priv) (((priv)->flags & RSND_GEN_MASK) == RSND_GEN1) #define rsnd_is_gen2(priv) (((priv)->flags & RSND_GEN_MASK) == RSND_GEN2) #define rsnd_is_gen3(priv) (((priv)->flags & RSND_GEN_MASK) == RSND_GEN3) +#define rsnd_is_e3(priv) (((priv)->flags & \ + (RSND_GEN_MASK | RSND_SOC_MASK)) == \ + (RSND_GEN3 | RSND_SOC_E)) #define rsnd_flags_has(p, f) ((p)->flags & (f)) #define rsnd_flags_set(p, f) ((p)->flags |= (f)) diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index db81e066b92e..585ffba0244b 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -14,7 +14,6 @@ */ #include "rsnd.h" -#include <linux/sys_soc.h> #define SRC_NAME "src" @@ -135,7 +134,7 @@ unsigned int rsnd_src_get_rate(struct rsnd_priv *priv, return rate; } -const static u32 bsdsr_table_pattern1[] = { +static const u32 bsdsr_table_pattern1[] = { 0x01800000, /* 6 - 1/6 */ 0x01000000, /* 6 - 1/4 */ 0x00c00000, /* 6 - 1/3 */ @@ -144,7 +143,7 @@ const static u32 bsdsr_table_pattern1[] = { 0x00400000, /* 6 - 1 */ }; -const static u32 bsdsr_table_pattern2[] = { +static const u32 bsdsr_table_pattern2[] = { 0x02400000, /* 6 - 1/6 */ 0x01800000, /* 6 - 1/4 */ 0x01200000, /* 6 - 1/3 */ @@ -153,7 +152,7 @@ const static u32 bsdsr_table_pattern2[] = { 0x00600000, /* 6 - 1 */ }; -const static u32 bsisr_table[] = { +static const u32 bsisr_table[] = { 0x00100060, /* 6 - 1/6 */ 0x00100040, /* 6 - 1/4 */ 0x00100030, /* 6 - 1/3 */ @@ -162,7 +161,7 @@ const static u32 bsisr_table[] = { 0x00100020, /* 6 - 1 */ }; -const static u32 chan288888[] = { +static const u32 chan288888[] = { 0x00000006, /* 1 to 2 */ 0x000001fe, /* 1 to 8 */ 0x000001fe, /* 1 to 8 */ @@ -171,7 +170,7 @@ const static u32 chan288888[] = { 0x000001fe, /* 1 to 8 */ }; -const static u32 chan244888[] = { +static const u32 chan244888[] = { 0x00000006, /* 1 to 2 */ 0x0000001e, /* 1 to 4 */ 0x0000001e, /* 1 to 4 */ @@ -180,7 +179,7 @@ const static u32 chan244888[] = { 0x000001fe, /* 1 to 8 */ }; -const static u32 chan222222[] = { +static const u32 chan222222[] = { 0x00000006, /* 1 to 2 */ 0x00000006, /* 1 to 2 */ 0x00000006, /* 1 to 2 */ @@ -189,18 +188,12 @@ const static u32 chan222222[] = { 0x00000006, /* 1 to 2 */ }; -static const struct soc_device_attribute ov_soc[] = { - { .soc_id = "r8a77990" }, /* E3 */ - { /* sentinel */ } -}; - static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io, struct rsnd_mod *mod) { struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct device *dev = rsnd_priv_to_dev(priv); struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); - const struct soc_device_attribute *soc = soc_device_match(ov_soc); int is_play = rsnd_io_is_play(io); int use_src = 0; u32 fin, fout; @@ -307,7 +300,7 @@ static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io, /* * E3 need to overwrite */ - if (soc) + if (rsnd_is_e3(priv)) switch (rsnd_mod_id(mod)) { case 0: case 4: diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 93d316d5bf8e..6f4842977b8d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1974,10 +1974,13 @@ static void soc_check_tplg_fes(struct snd_soc_card *card) continue; /* for this machine ? */ + if (!strcmp(component->driver->ignore_machine, + card->dev->driver->name)) + goto match; if (strcmp(component->driver->ignore_machine, - card->dev->driver->name)) + dev_name(card->dev))) continue; - +match: /* machine matches, so override the rtd data */ for_each_card_prelinks(card, i, dai_link) { @@ -2797,6 +2800,7 @@ int snd_soc_register_card(struct snd_soc_card *card) ret = soc_init_dai_link(card, link); if (ret) { + soc_cleanup_platform(card); dev_err(card->dev, "ASoC: failed to init link %s\n", link->name); mutex_unlock(&client_mutex); @@ -2819,6 +2823,7 @@ int snd_soc_register_card(struct snd_soc_card *card) card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); + spin_lock_init(&card->dpcm_lock); return snd_soc_bind_card(card); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1ec06ef6d161..74a6f5705f66 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -4038,7 +4038,7 @@ snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd, struct snd_soc_dapm_widget template; struct snd_soc_dapm_widget *w; const char **w_param_text; - unsigned long private_value; + unsigned long private_value = 0; char *link_name; int ret; diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 0d5ec68a1e50..7fe5321000e8 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -954,10 +954,13 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, codec_params = *params; /* fixup params based on TDM slot masks */ - if (codec_dai->tx_mask) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + codec_dai->tx_mask) soc_pcm_codec_params_fixup(&codec_params, codec_dai->tx_mask); - if (codec_dai->rx_mask) + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + codec_dai->rx_mask) soc_pcm_codec_params_fixup(&codec_params, codec_dai->rx_mask); @@ -1213,6 +1216,7 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream) { struct snd_soc_dpcm *dpcm; + unsigned long flags; /* only add new dpcms */ for_each_dpcm_be(fe, stream, dpcm) { @@ -1228,8 +1232,10 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, dpcm->fe = fe; be->dpcm[stream].runtime = fe->dpcm[stream].runtime; dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW; + spin_lock_irqsave(&fe->card->dpcm_lock, flags); list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients); list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients); + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n", stream ? "capture" : "playback", fe->dai_link->name, @@ -1275,6 +1281,7 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm, *d; + unsigned long flags; for_each_dpcm_be_safe(fe, stream, dpcm, d) { dev_dbg(fe->dev, "ASoC: BE %s disconnect check for %s\n", @@ -1294,8 +1301,10 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) #ifdef CONFIG_DEBUG_FS debugfs_remove(dpcm->debugfs_state); #endif + spin_lock_irqsave(&fe->card->dpcm_lock, flags); list_del(&dpcm->list_be); list_del(&dpcm->list_fe); + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); kfree(dpcm); } } @@ -1547,10 +1556,13 @@ int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; + unsigned long flags; + spin_lock_irqsave(&fe->card->dpcm_lock, flags); for_each_dpcm_be(fe, stream, dpcm) dpcm->be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); } static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, @@ -2571,6 +2583,7 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) struct snd_soc_dpcm *dpcm; enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; int ret; + unsigned long flags; dev_dbg(fe->dev, "ASoC: runtime %s open on FE %s\n", stream ? "capture" : "playback", fe->dai_link->name); @@ -2640,11 +2653,13 @@ close: dpcm_be_dai_shutdown(fe, stream); disconnect: /* disconnect any non started BEs */ + spin_lock_irqsave(&fe->card->dpcm_lock, flags); for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); return ret; } @@ -3221,7 +3236,10 @@ int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, { struct snd_soc_dpcm *dpcm; int state; + int ret = 1; + unsigned long flags; + spin_lock_irqsave(&fe->card->dpcm_lock, flags); for_each_dpcm_fe(be, stream, dpcm) { if (dpcm->fe == fe) @@ -3230,12 +3248,15 @@ int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, state = dpcm->fe->dpcm[stream].state; if (state == SND_SOC_DPCM_STATE_START || state == SND_SOC_DPCM_STATE_PAUSED || - state == SND_SOC_DPCM_STATE_SUSPEND) - return 0; + state == SND_SOC_DPCM_STATE_SUSPEND) { + ret = 0; + break; + } } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); /* it's safe to free/stop this BE DAI */ - return 1; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop); @@ -3248,7 +3269,10 @@ int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, { struct snd_soc_dpcm *dpcm; int state; + int ret = 1; + unsigned long flags; + spin_lock_irqsave(&fe->card->dpcm_lock, flags); for_each_dpcm_fe(be, stream, dpcm) { if (dpcm->fe == fe) @@ -3258,12 +3282,15 @@ int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, if (state == SND_SOC_DPCM_STATE_START || state == SND_SOC_DPCM_STATE_PAUSED || state == SND_SOC_DPCM_STATE_SUSPEND || - state == SND_SOC_DPCM_STATE_PREPARE) - return 0; + state == SND_SOC_DPCM_STATE_PREPARE) { + ret = 0; + break; + } } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); /* it's safe to change hw_params */ - return 1; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params); @@ -3302,6 +3329,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; struct snd_soc_dpcm *dpcm; ssize_t offset = 0; + unsigned long flags; /* FE state */ offset += snprintf(buf + offset, size - offset, @@ -3329,6 +3357,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, goto out; } + spin_lock_irqsave(&fe->card->dpcm_lock, flags); for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; params = &dpcm->hw_params; @@ -3349,7 +3378,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, params_channels(params), params_rate(params)); } - + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); out: return offset; } diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 25fca7055464..472f7705da93 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -535,6 +535,8 @@ static void remove_dai(struct snd_soc_component *comp, if (dai->driver == dai_drv) dai->driver = NULL; + kfree(dai_drv->playback.stream_name); + kfree(dai_drv->capture.stream_name); kfree(dai_drv->name); list_del(&dobj->list); kfree(dai_drv); @@ -894,19 +896,20 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, continue; } + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc, &mc->hdr); + /* pass control to driver for optional further init */ err = soc_tplg_init_kcontrol(tplg, &kc, (struct snd_soc_tplg_ctl_hdr *) mc); if (err < 0) { dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); + soc_tplg_free_tlv(tplg, &kc); kfree(sm); continue; } - /* create any TLV data */ - soc_tplg_create_tlv(tplg, &kc, &mc->hdr); - /* register control here */ err = soc_tplg_add_kcontrol(tplg, &kc, &sm->dobj.control.kcontrol); @@ -993,8 +996,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, for (i = 0; i < count; i++) { ec = (struct snd_soc_tplg_enum_control *)tplg->pos; - tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + - ec->priv.size); /* validate kcontrol */ if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == @@ -1005,6 +1006,9 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, if (se == NULL) return -ENOMEM; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %d\n", ec->hdr.name, ec->items); @@ -1281,14 +1285,14 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( if (sm == NULL) goto err; - tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + - mc->priv.size); - /* validate kcontrol */ if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) goto err_str; + tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + + mc->priv.size); + dev_dbg(tplg->dev, " adding DAPM widget mixer control %s at %d\n", mc->hdr.name, i); @@ -1324,18 +1328,19 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( continue; } + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc[i], &mc->hdr); + /* pass control to driver for optional further init */ err = soc_tplg_init_kcontrol(tplg, &kc[i], (struct snd_soc_tplg_ctl_hdr *)mc); if (err < 0) { dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); + soc_tplg_free_tlv(tplg, &kc[i]); kfree(sm); continue; } - - /* create any TLV data */ - soc_tplg_create_tlv(tplg, &kc[i], &mc->hdr); } return kc; @@ -1373,6 +1378,9 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( if (se == NULL) goto err; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + dev_dbg(tplg->dev, " adding DAPM widget enum control %s\n", ec->hdr.name); @@ -1437,9 +1445,6 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( ec->hdr.name); goto err_se; } - - tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + - ec->priv.size); } return kc; @@ -1806,6 +1811,9 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, ret = soc_tplg_dai_load(tplg, dai_drv, pcm, NULL); if (ret < 0) { dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n"); + kfree(dai_drv->playback.stream_name); + kfree(dai_drv->capture.stream_name); + kfree(dai_drv->name); kfree(dai_drv); return ret; } @@ -1876,6 +1884,9 @@ static int soc_tplg_fe_link_create(struct soc_tplg *tplg, ret = soc_tplg_dai_link_load(tplg, link, NULL); if (ret < 0) { dev_err(tplg->comp->dev, "ASoC: FE link loading failed\n"); + kfree(link->name); + kfree(link->stream_name); + kfree(link->cpu_dai_name); kfree(link); return ret; } diff --git a/sound/soc/sprd/Kconfig b/sound/soc/sprd/Kconfig index 43ece7daf0e9..3b1eb320dab4 100644 --- a/sound/soc/sprd/Kconfig +++ b/sound/soc/sprd/Kconfig @@ -1,6 +1,7 @@ config SND_SOC_SPRD tristate "SoC Audio for the Spreadtrum SoC chips" depends on ARCH_SPRD || COMPILE_TEST + select SND_SOC_COMPRESS help Say Y or M if you want to add support for codecs attached to the Spreadtrum SoCs' Audio interfaces. diff --git a/sound/soc/sprd/Makefile b/sound/soc/sprd/Makefile index 47620e57a9f2..fa1d5476b8da 100644 --- a/sound/soc/sprd/Makefile +++ b/sound/soc/sprd/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 # Spreadtrum Audio Support -obj-$(CONFIG_SND_SOC_SPRD) += sprd-pcm-dma.o +obj-$(CONFIG_SND_SOC_SPRD) += sprd-pcm-dma.o sprd-pcm-compress.o diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c new file mode 100644 index 000000000000..dc84257cffc2 --- /dev/null +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -0,0 +1,675 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Spreadtrum Communications Inc. + +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/dma/sprd-dma.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> + +#include "sprd-pcm-dma.h" + +#define SPRD_COMPR_DMA_CHANS 2 + +/* Default values if userspace does not set */ +#define SPRD_COMPR_MIN_FRAGMENT_SIZE SZ_8K +#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_128K +#define SPRD_COMPR_MIN_NUM_FRAGMENTS 4 +#define SPRD_COMPR_MAX_NUM_FRAGMENTS 64 + +/* DSP FIFO size */ +#define SPRD_COMPR_MCDT_EMPTY_WMK 0 +#define SPRD_COMPR_MCDT_FIFO_SIZE 512 + +/* Stage 0 IRAM buffer size definition */ +#define SPRD_COMPR_IRAM_BUF_SIZE SZ_32K +#define SPRD_COMPR_IRAM_INFO_SIZE (sizeof(struct sprd_compr_playinfo)) +#define SPRD_COMPR_IRAM_LINKLIST_SIZE (1024 - SPRD_COMPR_IRAM_INFO_SIZE) +#define SPRD_COMPR_IRAM_SIZE (SPRD_COMPR_IRAM_BUF_SIZE + \ + SPRD_COMPR_IRAM_INFO_SIZE + \ + SPRD_COMPR_IRAM_LINKLIST_SIZE) + +/* Stage 1 DDR buffer size definition */ +#define SPRD_COMPR_AREA_BUF_SIZE SZ_2M +#define SPRD_COMPR_AREA_LINKLIST_SIZE 1024 +#define SPRD_COMPR_AREA_SIZE (SPRD_COMPR_AREA_BUF_SIZE + \ + SPRD_COMPR_AREA_LINKLIST_SIZE) + +struct sprd_compr_dma { + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + dma_addr_t phys; + void *virt; + int trans_len; +}; + +/* + * The Spreadtrum Audio compress offload mode will use 2-stage DMA transfer to + * save power. That means we can request 2 dma channels, one for source channel, + * and another one for destination channel. Once the source channel's transaction + * is done, it will trigger the destination channel's transaction automatically + * by hardware signal. + * + * For 2-stage DMA transfer, we can allocate 2 buffers: IRAM buffer (always + * power-on) and DDR buffer. The source channel will transfer data from IRAM + * buffer to the DSP fifo to decoding/encoding, once IRAM buffer is empty by + * transferring done, the destination channel will start to transfer data from + * DDR buffer to IRAM buffer. + * + * Since the DSP fifo is only 512B, IRAM buffer is allocated by 32K, and DDR + * buffer is larger to 2M. That means only the IRAM 32k data is transferred + * done, we can wake up the AP system to transfer data from DDR to IRAM, and + * other time the AP system can be suspended to save power. + */ +struct sprd_compr_stream { + struct snd_compr_stream *cstream; + struct sprd_compr_ops *compr_ops; + struct sprd_compr_dma dma[SPRD_COMPR_DMA_CHANS]; + + /* DMA engine channel number */ + int num_channels; + + /* Stage 0 IRAM buffer */ + struct snd_dma_buffer iram_buffer; + /* Stage 1 DDR buffer */ + struct snd_dma_buffer compr_buffer; + + /* DSP play information IRAM buffer */ + dma_addr_t info_phys; + void *info_area; + int info_size; + + /* Data size copied to IRAM buffer */ + int copied_total; + /* Total received data size from userspace */ + int received_total; + /* Stage 0 IRAM buffer received data size */ + int received_stage0; + /* Stage 1 DDR buffer received data size */ + int received_stage1; + /* Stage 1 DDR buffer pointer */ + int stage1_pointer; +}; + +static int sprd_platform_compr_trigger(struct snd_compr_stream *cstream, + int cmd); + +static void sprd_platform_compr_drain_notify(void *arg) +{ + struct snd_compr_stream *cstream = arg; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + + memset(stream->info_area, 0, sizeof(struct sprd_compr_playinfo)); + + if (cstream) + snd_compr_drain_notify(cstream); +} + +static void sprd_platform_compr_dma_complete(void *data) +{ + struct snd_compr_stream *cstream = data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct sprd_compr_dma *dma = &stream->dma[1]; + + /* Update data size copied to IRAM buffer */ + stream->copied_total += dma->trans_len; + if (stream->copied_total > stream->received_total) + stream->copied_total = stream->received_total; + + snd_compr_fragment_elapsed(cstream); +} + +static int sprd_platform_compr_dma_config(struct snd_compr_stream *cstream, + struct snd_compr_params *params, + int channel) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct sprd_compr_data *data = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct sprd_pcm_dma_params *dma_params = data->dma_params; + struct sprd_compr_dma *dma = &stream->dma[channel]; + struct dma_slave_config config = { }; + struct sprd_dma_linklist link = { }; + enum dma_transfer_direction dir; + struct scatterlist *sg, *sgt; + enum dma_slave_buswidth bus_width; + int period, period_cnt, sg_num = 2; + dma_addr_t src_addr, dst_addr; + unsigned long flags; + int ret, j; + + if (!dma_params) { + dev_err(dev, "no dma parameters setting\n"); + return -EINVAL; + } + + dma->chan = dma_request_slave_channel(dev, + dma_params->chan_name[channel]); + if (!dma->chan) { + dev_err(dev, "failed to request dma channel\n"); + return -ENODEV; + } + + sgt = sg = devm_kcalloc(dev, sg_num, sizeof(*sg), GFP_KERNEL); + if (!sg) { + ret = -ENOMEM; + goto sg_err; + } + + switch (channel) { + case 0: + bus_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + period = (SPRD_COMPR_MCDT_FIFO_SIZE - SPRD_COMPR_MCDT_EMPTY_WMK) * 4; + period_cnt = params->buffer.fragment_size / period; + src_addr = stream->iram_buffer.addr; + dst_addr = dma_params->dev_phys[channel]; + flags = SPRD_DMA_FLAGS(SPRD_DMA_SRC_CHN1, + SPRD_DMA_TRANS_DONE_TRG, + SPRD_DMA_FRAG_REQ, + SPRD_DMA_TRANS_INT); + break; + + case 1: + bus_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + period = params->buffer.fragment_size; + period_cnt = params->buffer.fragments; + src_addr = stream->compr_buffer.addr; + dst_addr = stream->iram_buffer.addr; + flags = SPRD_DMA_FLAGS(SPRD_DMA_DST_CHN1, + SPRD_DMA_TRANS_DONE_TRG, + SPRD_DMA_FRAG_REQ, + SPRD_DMA_TRANS_INT); + break; + + default: + ret = -EINVAL; + goto config_err; + } + + dma->trans_len = period * period_cnt; + + config.src_maxburst = period; + config.src_addr_width = bus_width; + config.dst_addr_width = bus_width; + if (cstream->direction == SND_COMPRESS_PLAYBACK) { + config.src_addr = src_addr; + config.dst_addr = dst_addr; + dir = DMA_MEM_TO_DEV; + } else { + config.src_addr = dst_addr; + config.dst_addr = src_addr; + dir = DMA_DEV_TO_MEM; + } + + sg_init_table(sgt, sg_num); + for (j = 0; j < sg_num; j++, sgt++) { + sg_dma_len(sgt) = dma->trans_len; + sg_dma_address(sgt) = dst_addr; + } + + /* + * Configure the link-list address for the DMA engine link-list + * mode. + */ + link.virt_addr = (unsigned long)dma->virt; + link.phy_addr = dma->phys; + + ret = dmaengine_slave_config(dma->chan, &config); + if (ret) { + dev_err(dev, + "failed to set slave configuration: %d\n", ret); + goto config_err; + } + + /* + * We configure the DMA request mode, interrupt mode, channel + * mode and channel trigger mode by the flags. + */ + dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan, sg, + sg_num, dir, + flags, &link); + if (!dma->desc) { + dev_err(dev, "failed to prepare slave sg\n"); + ret = -ENOMEM; + goto config_err; + } + + /* Only channel 1 transfer can wake up the AP system. */ + if (!params->no_wake_mode && channel == 1) { + dma->desc->callback = sprd_platform_compr_dma_complete; + dma->desc->callback_param = cstream; + } + + devm_kfree(dev, sg); + + return 0; + +config_err: + devm_kfree(dev, sg); +sg_err: + dma_release_channel(dma->chan); + return ret; +} + +static int sprd_platform_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct sprd_compr_params compr_params = { }; + int ret; + + /* + * Configure the DMA engine 2-stage transfer mode. Channel 1 set as the + * destination channel, and channel 0 set as the source channel, that + * means once the source channel's transaction is done, it will trigger + * the destination channel's transaction automatically. + */ + ret = sprd_platform_compr_dma_config(cstream, params, 1); + if (ret) { + dev_err(dev, "faied to config stage 1 DMA: %d\n", ret); + return ret; + } + + ret = sprd_platform_compr_dma_config(cstream, params, 0); + if (ret) { + dev_err(dev, "faied to config stage 0 DMA: %d\n", ret); + goto config_err; + } + + compr_params.direction = cstream->direction; + compr_params.sample_rate = params->codec.sample_rate; + compr_params.channels = stream->num_channels; + compr_params.info_phys = stream->info_phys; + compr_params.info_size = stream->info_size; + compr_params.rate = params->codec.bit_rate; + compr_params.format = params->codec.id; + + ret = stream->compr_ops->set_params(cstream->direction, &compr_params); + if (ret) { + dev_err(dev, "failed to set parameters: %d\n", ret); + goto params_err; + } + + return 0; + +params_err: + dma_release_channel(stream->dma[0].chan); +config_err: + dma_release_channel(stream->dma[1].chan); + return ret; +} + +static int sprd_platform_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct sprd_compr_data *data = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct sprd_compr_stream *stream; + struct sprd_compr_callback cb; + int stream_id = cstream->direction, ret; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + stream = devm_kzalloc(dev, sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->cstream = cstream; + stream->num_channels = 2; + stream->compr_ops = data->ops; + + /* + * Allocate the stage 0 IRAM buffer size, including the DMA 0 + * link-list size and play information of DSP address size. + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_IRAM, dev, + SPRD_COMPR_IRAM_SIZE, &stream->iram_buffer); + if (ret < 0) + goto err_iram; + + /* Use to save link-list configuration for DMA 0. */ + stream->dma[0].virt = stream->iram_buffer.area + SPRD_COMPR_IRAM_SIZE; + stream->dma[0].phys = stream->iram_buffer.addr + SPRD_COMPR_IRAM_SIZE; + + /* Use to update the current data offset of DSP. */ + stream->info_phys = stream->iram_buffer.addr + SPRD_COMPR_IRAM_SIZE + + SPRD_COMPR_IRAM_LINKLIST_SIZE; + stream->info_area = stream->iram_buffer.area + SPRD_COMPR_IRAM_SIZE + + SPRD_COMPR_IRAM_LINKLIST_SIZE; + stream->info_size = SPRD_COMPR_IRAM_INFO_SIZE; + + /* + * Allocate the stage 1 DDR buffer size, including the DMA 1 link-list + * size. + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, + SPRD_COMPR_AREA_SIZE, &stream->compr_buffer); + if (ret < 0) + goto err_compr; + + /* Use to save link-list configuration for DMA 1. */ + stream->dma[1].virt = stream->compr_buffer.area + SPRD_COMPR_AREA_SIZE; + stream->dma[1].phys = stream->compr_buffer.addr + SPRD_COMPR_AREA_SIZE; + + cb.drain_notify = sprd_platform_compr_drain_notify; + cb.drain_data = cstream; + ret = stream->compr_ops->open(stream_id, &cb); + if (ret) { + dev_err(dev, "failed to open compress platform: %d\n", ret); + goto err_open; + } + + runtime->private_data = stream; + return 0; + +err_open: + snd_dma_free_pages(&stream->compr_buffer); +err_compr: + snd_dma_free_pages(&stream->iram_buffer); +err_iram: + devm_kfree(dev, stream); + + return ret; +} + +static int sprd_platform_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int stream_id = cstream->direction, i; + + for (i = 0; i < stream->num_channels; i++) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) { + dma_release_channel(dma->chan); + dma->chan = NULL; + } + } + + snd_dma_free_pages(&stream->compr_buffer); + snd_dma_free_pages(&stream->iram_buffer); + + stream->compr_ops->close(stream_id); + + devm_kfree(dev, stream); + return 0; +} + +static int sprd_platform_compr_trigger(struct snd_compr_stream *cstream, + int cmd) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int channels = stream->num_channels, ret = 0, i; + int stream_id = cstream->direction; + + if (cstream->direction != SND_COMPRESS_PLAYBACK) { + dev_err(dev, "unsupported compress direction\n"); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (!dma->desc) + continue; + + dma->cookie = dmaengine_submit(dma->desc); + ret = dma_submit_error(dma->cookie); + if (ret) { + dev_err(dev, "failed to submit request: %d\n", + ret); + return ret; + } + } + + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dma_async_issue_pending(dma->chan); + } + + ret = stream->compr_ops->start(stream_id); + break; + + case SNDRV_PCM_TRIGGER_STOP: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_terminate_async(dma->chan); + } + + stream->copied_total = 0; + stream->stage1_pointer = 0; + stream->received_total = 0; + stream->received_stage0 = 0; + stream->received_stage1 = 0; + + ret = stream->compr_ops->stop(stream_id); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_pause(dma->chan); + } + + ret = stream->compr_ops->pause(stream_id); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_resume(dma->chan); + } + + ret = stream->compr_ops->pause_release(stream_id); + break; + + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_DRAIN: + ret = stream->compr_ops->drain(stream->received_total); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int sprd_platform_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct sprd_compr_playinfo *info = + (struct sprd_compr_playinfo *)stream->info_area; + + tstamp->copied_total = stream->copied_total; + tstamp->pcm_io_frames = info->current_data_offset; + + return 0; +} + +static int sprd_platform_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + int avail_bytes, data_count = count; + void *dst; + + /* + * We usually set fragment size as 32K, and the stage 0 IRAM buffer + * size is 32K too. So if now the received data size of the stage 0 + * IRAM buffer is less than 32K, that means we have some available + * spaces for the stage 0 IRAM buffer. + */ + if (stream->received_stage0 < runtime->fragment_size) { + avail_bytes = runtime->fragment_size - stream->received_stage0; + dst = stream->iram_buffer.area + stream->received_stage0; + + if (avail_bytes >= data_count) { + /* + * Copy data to the stage 0 IRAM buffer directly if + * spaces are enough. + */ + if (copy_from_user(dst, buf, data_count)) + return -EFAULT; + + stream->received_stage0 += data_count; + stream->copied_total += data_count; + goto copy_done; + } else { + /* + * If the data count is larger than the available spaces + * of the the stage 0 IRAM buffer, we should copy one + * partial data to the stage 0 IRAM buffer, and copy + * the left to the stage 1 DDR buffer. + */ + if (copy_from_user(dst, buf, avail_bytes)) + return -EFAULT; + + data_count -= avail_bytes; + stream->received_stage0 += avail_bytes; + stream->copied_total += avail_bytes; + buf += avail_bytes; + } + } + + /* + * Copy data to the stage 1 DDR buffer if no spaces for the stage 0 IRAM + * buffer. + */ + dst = stream->compr_buffer.area + stream->stage1_pointer; + if (data_count < stream->compr_buffer.bytes - stream->stage1_pointer) { + if (copy_from_user(dst, buf, data_count)) + return -EFAULT; + + stream->stage1_pointer += data_count; + } else { + avail_bytes = stream->compr_buffer.bytes - stream->stage1_pointer; + + if (copy_from_user(dst, buf, avail_bytes)) + return -EFAULT; + + if (copy_from_user(stream->compr_buffer.area, buf + avail_bytes, + data_count - avail_bytes)) + return -EFAULT; + + stream->stage1_pointer = data_count - avail_bytes; + } + + stream->received_stage1 += data_count; + +copy_done: + /* Update the copied data size. */ + stream->received_total += count; + return count; +} + +static int sprd_platform_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->direction = cstream->direction; + caps->min_fragment_size = SPRD_COMPR_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = SPRD_COMPR_MAX_FRAGMENT_SIZE; + caps->min_fragments = SPRD_COMPR_MIN_NUM_FRAGMENTS; + caps->max_fragments = SPRD_COMPR_MAX_NUM_FRAGMENTS; + caps->num_codecs = 2; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + + return 0; +} + +static int +sprd_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + switch (codec->codec) { + case SND_AUDIOCODEC_MP3: + codec->num_descriptors = 2; + codec->descriptor[0].max_ch = 2; + codec->descriptor[0].bit_rate[0] = 320; + codec->descriptor[0].bit_rate[1] = 128; + codec->descriptor[0].num_bitrates = 2; + codec->descriptor[0].profiles = 0; + codec->descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO; + codec->descriptor[0].formats = 0; + break; + + case SND_AUDIOCODEC_AAC: + codec->num_descriptors = 2; + codec->descriptor[1].max_ch = 2; + codec->descriptor[1].bit_rate[0] = 320; + codec->descriptor[1].bit_rate[1] = 128; + codec->descriptor[1].num_bitrates = 2; + codec->descriptor[1].profiles = 0; + codec->descriptor[1].modes = 0; + codec->descriptor[1].formats = 0; + break; + + default: + return -EINVAL; + } + + return 0; +} + +const struct snd_compr_ops sprd_platform_compr_ops = { + .open = sprd_platform_compr_open, + .free = sprd_platform_compr_free, + .set_params = sprd_platform_compr_set_params, + .trigger = sprd_platform_compr_trigger, + .pointer = sprd_platform_compr_pointer, + .copy = sprd_platform_compr_copy, + .get_caps = sprd_platform_compr_get_caps, + .get_codec_caps = sprd_platform_compr_get_codec_caps, +}; + +MODULE_DESCRIPTION("Spreadtrum ASoC Compress Platform Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:compress-platform"); diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index cbb27c4abeba..9be6d4b2bf74 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -13,7 +13,6 @@ #include "sprd-pcm-dma.h" -#define DRV_NAME "sprd_pcm_dma" #define SPRD_PCM_DMA_LINKLIST_SIZE 64 #define SPRD_PCM_DMA_BRUST_LEN 640 @@ -524,6 +523,7 @@ static void sprd_pcm_free(struct snd_pcm *pcm) static const struct snd_soc_component_driver sprd_soc_component = { .name = DRV_NAME, .ops = &sprd_pcm_ops, + .compr_ops = &sprd_platform_compr_ops, .pcm_new = sprd_pcm_new, .pcm_free = sprd_pcm_free, }; diff --git a/sound/soc/sprd/sprd-pcm-dma.h b/sound/soc/sprd/sprd-pcm-dma.h index d85a34f1461d..08e9fdba82f1 100644 --- a/sound/soc/sprd/sprd-pcm-dma.h +++ b/sound/soc/sprd/sprd-pcm-dma.h @@ -3,8 +3,11 @@ #ifndef __SPRD_PCM_DMA_H #define __SPRD_PCM_DMA_H +#define DRV_NAME "sprd_pcm_dma" #define SPRD_PCM_CHANNEL_MAX 2 +extern const struct snd_compr_ops sprd_platform_compr_ops; + struct sprd_pcm_dma_params { dma_addr_t dev_phys[SPRD_PCM_CHANNEL_MAX]; u32 datawidth[SPRD_PCM_CHANNEL_MAX]; @@ -12,4 +15,44 @@ struct sprd_pcm_dma_params { const char *chan_name[SPRD_PCM_CHANNEL_MAX]; }; +struct sprd_compr_playinfo { + int total_time; + int current_time; + int total_data_length; + int current_data_offset; +}; + +struct sprd_compr_params { + u32 direction; + u32 rate; + u32 sample_rate; + u32 channels; + u32 format; + u32 period; + u32 periods; + u32 info_phys; + u32 info_size; +}; + +struct sprd_compr_callback { + void (*drain_notify)(void *data); + void *drain_data; +}; + +struct sprd_compr_ops { + int (*open)(int str_id, struct sprd_compr_callback *cb); + int (*close)(int str_id); + int (*start)(int str_id); + int (*stop)(int str_id); + int (*pause)(int str_id); + int (*pause_release)(int str_id); + int (*drain)(int received_total); + int (*set_params)(int str_id, struct sprd_compr_params *params); +}; + +struct sprd_compr_data { + struct sprd_compr_ops *ops; + struct sprd_pcm_dma_params *dma_params; +}; + #endif /* __SPRD_PCM_DMA_H */ diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c index 47901983a6ff..1f20ff1ccb57 100644 --- a/sound/soc/stm/stm32_adfsdm.c +++ b/sound/soc/stm/stm32_adfsdm.c @@ -41,7 +41,7 @@ struct stm32_adfsdm_priv { static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_PAUSE, + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_PAUSE, .formats = SNDRV_PCM_FMTBIT_S32_LE, .rate_min = 8000, diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index 47c334de6b09..952634258b02 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -179,7 +179,6 @@ enum i2s_datlen { I2S_I2SMOD_DATLEN_32, }; -#define STM32_I2S_DAI_NAME_SIZE 20 #define STM32_I2S_FIFO_SIZE 16 #define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER) @@ -202,7 +201,6 @@ enum i2s_datlen { * @phys_addr: I2S registers physical base address * @lock_fd: lock to manage race conditions in full duplex mode * @irq_lock: prevent race condition with IRQ - * @dais_name: DAI name * @mclk_rate: master clock frequency (Hz) * @fmt: DAI protocol * @refcount: keep count of opened streams on I2S @@ -224,7 +222,6 @@ struct stm32_i2s_data { dma_addr_t phys_addr; spinlock_t lock_fd; /* Manage race conditions for full duplex */ spinlock_t irq_lock; /* used to prevent race condition with IRQ */ - char dais_name[STM32_I2S_DAI_NAME_SIZE]; unsigned int mclk_rate; unsigned int fmt; int refcount; @@ -281,7 +278,6 @@ static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg) case STM32_I2S_CFG2_REG: case STM32_I2S_IER_REG: case STM32_I2S_SR_REG: - case STM32_I2S_TXDR_REG: case STM32_I2S_RXDR_REG: case STM32_I2S_CGFR_REG: return true; @@ -293,7 +289,7 @@ static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg) static bool stm32_i2s_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { - case STM32_I2S_TXDR_REG: + case STM32_I2S_SR_REG: case STM32_I2S_RXDR_REG: return true; default: @@ -496,12 +492,6 @@ static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai, unsigned int fthlv; int ret; - if ((params_channels(params) == 1) && - ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A)) { - dev_err(cpu_dai->dev, "Mono mode supported only by DSP_A\n"); - return -EINVAL; - } - switch (format) { case 16: cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16); @@ -551,6 +541,10 @@ static int stm32_i2s_startup(struct snd_pcm_substream *substream, i2s->substream = substream; spin_unlock_irqrestore(&i2s->irq_lock, flags); + if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A) + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + ret = clk_prepare_enable(i2s->i2sclk); if (ret < 0) { dev_err(cpu_dai->dev, "Failed to enable clock: %d\n", ret); @@ -593,7 +587,8 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* Enable i2s */ - dev_dbg(cpu_dai->dev, "start I2S\n"); + dev_dbg(cpu_dai->dev, "start I2S %s\n", + playback_flg ? "playback" : "capture"); cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, @@ -638,6 +633,9 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev_dbg(cpu_dai->dev, "stop I2S %s\n", + playback_flg ? "playback" : "capture"); + if (playback_flg) regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, I2S_IER_UDRIE, @@ -654,8 +652,6 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, break; } - dev_dbg(cpu_dai->dev, "stop I2S\n"); - ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, I2S_CR1_SPE, 0); if (ret < 0) { @@ -771,12 +767,8 @@ static int stm32_i2s_dais_init(struct platform_device *pdev, if (!dai_ptr) return -ENOMEM; - snprintf(i2s->dais_name, STM32_I2S_DAI_NAME_SIZE, - "%s", dev_name(&pdev->dev)); - dai_ptr->probe = stm32_i2s_dai_probe; dai_ptr->ops = &stm32_i2s_pcm_dai_ops; - dai_ptr->name = i2s->dais_name; dai_ptr->id = 1; stm32_i2s_dai_init(&dai_ptr->playback, "playback"); stm32_i2s_dai_init(&dai_ptr->capture, "capture"); diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index 14c9591aae42..7550d5f08be3 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -21,6 +21,7 @@ #include <linux/delay.h> #include <linux/module.h> #include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> #include <linux/reset.h> #include <sound/dmaengine_pcm.h> @@ -44,20 +45,41 @@ static const struct of_device_id stm32_sai_ids[] = { {} }; -static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci) +static int stm32_sai_pclk_disable(struct device *dev) +{ + struct stm32_sai_data *sai = dev_get_drvdata(dev); + + clk_disable_unprepare(sai->pclk); + + return 0; +} + +static int stm32_sai_pclk_enable(struct device *dev) { + struct stm32_sai_data *sai = dev_get_drvdata(dev); int ret; - /* Enable peripheral clock to allow GCR register access */ ret = clk_prepare_enable(sai->pclk); if (ret) { dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret); return ret; } + return 0; +} + +static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci) +{ + int ret; + + /* Enable peripheral clock to allow GCR register access */ + ret = stm32_sai_pclk_enable(&sai->pdev->dev); + if (ret) + return ret; + writel_relaxed(FIELD_PREP(SAI_GCR_SYNCIN_MASK, (synci - 1)), sai->base); - clk_disable_unprepare(sai->pclk); + stm32_sai_pclk_disable(&sai->pdev->dev); return 0; } @@ -68,11 +90,9 @@ static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco) int ret; /* Enable peripheral clock to allow GCR register access */ - ret = clk_prepare_enable(sai->pclk); - if (ret) { - dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret); + ret = stm32_sai_pclk_enable(&sai->pdev->dev); + if (ret) return ret; - } dev_dbg(&sai->pdev->dev, "Set %pOFn%s as synchro provider\n", sai->pdev->dev.of_node, @@ -83,13 +103,13 @@ static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco) dev_err(&sai->pdev->dev, "%pOFn%s already set as sync provider\n", sai->pdev->dev.of_node, prev_synco == STM_SAI_SYNC_OUT_A ? "A" : "B"); - clk_disable_unprepare(sai->pclk); + stm32_sai_pclk_disable(&sai->pdev->dev); return -EINVAL; } writel_relaxed(FIELD_PREP(SAI_GCR_SYNCOUT_MASK, synco), sai->base); - clk_disable_unprepare(sai->pclk); + stm32_sai_pclk_disable(&sai->pdev->dev); return 0; } @@ -105,6 +125,7 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, if (!pdev) { dev_err(&sai_client->pdev->dev, "Device not found for node %pOFn\n", np_provider); + of_node_put(np_provider); return -ENODEV; } @@ -113,19 +134,20 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, dev_err(&sai_client->pdev->dev, "SAI sync provider data not found\n"); ret = -EINVAL; - goto out_put_dev; + goto error; } /* Configure sync client */ ret = stm32_sai_sync_conf_client(sai_client, synci); if (ret < 0) - goto out_put_dev; + goto error; /* Configure sync provider */ ret = stm32_sai_sync_conf_provider(sai_provider, synco); -out_put_dev: +error: put_device(&pdev->dev); + of_node_put(np_provider); return ret; } @@ -193,12 +215,54 @@ static int stm32_sai_probe(struct platform_device *pdev) return devm_of_platform_populate(&pdev->dev); } +#ifdef CONFIG_PM_SLEEP +/* + * When pins are shared by two sai sub instances, pins have to be defined + * in sai parent node. In this case, pins state is not managed by alsa fw. + * These pins are managed in suspend/resume callbacks. + */ +static int stm32_sai_suspend(struct device *dev) +{ + struct stm32_sai_data *sai = dev_get_drvdata(dev); + int ret; + + ret = stm32_sai_pclk_enable(dev); + if (ret) + return ret; + + sai->gcr = readl_relaxed(sai->base); + stm32_sai_pclk_disable(dev); + + return pinctrl_pm_select_sleep_state(dev); +} + +static int stm32_sai_resume(struct device *dev) +{ + struct stm32_sai_data *sai = dev_get_drvdata(dev); + int ret; + + ret = stm32_sai_pclk_enable(dev); + if (ret) + return ret; + + writel_relaxed(sai->gcr, sai->base); + stm32_sai_pclk_disable(dev); + + return pinctrl_pm_select_default_state(dev); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_sai_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_sai_suspend, stm32_sai_resume) +}; + MODULE_DEVICE_TABLE(of, stm32_sai_ids); static struct platform_driver stm32_sai_driver = { .driver = { .name = "st,stm32-sai", .of_match_table = stm32_sai_ids, + .pm = &stm32_sai_pm_ops, }, .probe = stm32_sai_probe, }; diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h index 08de899c766b..9c36a393ab7b 100644 --- a/sound/soc/stm/stm32_sai.h +++ b/sound/soc/stm/stm32_sai.h @@ -268,6 +268,7 @@ struct stm32_sai_conf { * @version: SOC version * @irq: SAI interrupt line * @set_sync: pointer to synchro mode configuration callback + * @gcr: SAI Global Configuration Register */ struct stm32_sai_data { struct platform_device *pdev; @@ -279,4 +280,5 @@ struct stm32_sai_data { int irq; int (*set_sync)(struct stm32_sai_data *sai, struct device_node *np_provider, int synco, int synci); + u32 gcr; }; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index f9297228c41c..2a90641cf164 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -100,8 +100,9 @@ * @slot_mask: rx or tx active slots mask. set at init or at runtime * @data_size: PCM data width. corresponds to PCM substream width. * @spdif_frm_cnt: S/PDIF playback frame counter - * @snd_aes_iec958: iec958 data + * @iec958: iec958 data * @ctrl_lock: control lock + * @irq_lock: prevent race condition with IRQ */ struct stm32_sai_sub_data { struct platform_device *pdev; @@ -133,6 +134,7 @@ struct stm32_sai_sub_data { unsigned int spdif_frm_cnt; struct snd_aes_iec958 iec958; struct mutex ctrl_lock; /* protect resources accessed by controls */ + spinlock_t irq_lock; /* used to prevent race condition with IRQ */ }; enum stm32_sai_fifo_th { @@ -166,6 +168,7 @@ static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case STM_SAI_DR_REGX: + case STM_SAI_SR_REGX: return true; default: return false; @@ -180,7 +183,6 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) case STM_SAI_FRCR_REGX: case STM_SAI_SLOTR_REGX: case STM_SAI_IMR_REGX: - case STM_SAI_SR_REGX: case STM_SAI_CLRFR_REGX: case STM_SAI_DR_REGX: case STM_SAI_PDMCR_REGX: @@ -200,6 +202,7 @@ static const struct regmap_config stm32_sai_sub_regmap_config_f4 = { .volatile_reg = stm32_sai_sub_volatile_reg, .writeable_reg = stm32_sai_sub_writeable_reg, .fast_io = true, + .cache_type = REGCACHE_FLAT, }; static const struct regmap_config stm32_sai_sub_regmap_config_h7 = { @@ -211,6 +214,7 @@ static const struct regmap_config stm32_sai_sub_regmap_config_h7 = { .volatile_reg = stm32_sai_sub_volatile_reg, .writeable_reg = stm32_sai_sub_writeable_reg, .fast_io = true, + .cache_type = REGCACHE_FLAT, }; static int snd_pcm_iec958_info(struct snd_kcontrol *kcontrol, @@ -439,8 +443,8 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid) if (!flags) return IRQ_NONE; - regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, - SAI_XCLRFR_MASK); + regmap_write_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, + SAI_XCLRFR_MASK); if (!sai->substream) { dev_err(&pdev->dev, "Device stopped. Spurious IRQ 0x%x\n", sr); @@ -474,8 +478,10 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid) status = SNDRV_PCM_STATE_XRUN; } - if (status != SNDRV_PCM_STATE_RUNNING) + spin_lock(&sai->irq_lock); + if (status != SNDRV_PCM_STATE_RUNNING && sai->substream) snd_pcm_stop_xrun(sai->substream); + spin_unlock(&sai->irq_lock); return IRQ_HANDLED; } @@ -679,8 +685,19 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream, { struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int imr, cr2, ret; + unsigned long flags; + spin_lock_irqsave(&sai->irq_lock, flags); sai->substream = substream; + spin_unlock_irqrestore(&sai->irq_lock, flags); + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_FMTBIT_S32_LE); + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + } ret = clk_prepare_enable(sai->sai_ck); if (ret < 0) { @@ -689,9 +706,8 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream, } /* Enable ITs */ - - regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, - SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); + regmap_write_bits(sai->regmap, STM_SAI_CLRFR_REGX, + SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); imr = SAI_XIMR_OVRUDRIE; if (STM_SAI_IS_CAPTURE(sai)) { @@ -723,10 +739,10 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, * SAI fifo threshold is set to half fifo, to keep enough space * for DMA incoming bursts. */ - regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX, - SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, - SAI_XCR2_FFLUSH | - SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF)); + regmap_write_bits(sai->regmap, STM_SAI_CR2_REGX, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, + SAI_XCR2_FFLUSH | + SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF)); /* DS bits in CR1 not set for SPDIF (size forced to 24 bits).*/ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { @@ -898,7 +914,7 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, struct snd_pcm_hw_params *params) { struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); - int div = 0; + int div = 0, cr1 = 0; int sai_clk_rate, mclk_ratio, den; unsigned int rate = params_rate(params); @@ -943,13 +959,19 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, } else { if (sai->mclk_rate) { mclk_ratio = sai->mclk_rate / rate; - if ((mclk_ratio != 512) && - (mclk_ratio != 256)) { + if (mclk_ratio == 512) { + cr1 = SAI_XCR1_OSR; + } else if (mclk_ratio != 256) { dev_err(cpu_dai->dev, "Wrong mclk ratio %d\n", mclk_ratio); return -EINVAL; } + + regmap_update_bits(sai->regmap, + STM_SAI_CR1_REGX, + SAI_XCR1_OSR, cr1); + div = stm32_sai_get_clk_div(sai, sai_clk_rate, sai->mclk_rate); if (div < 0) @@ -1051,6 +1073,7 @@ static void stm32_sai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0); @@ -1061,18 +1084,21 @@ static void stm32_sai_shutdown(struct snd_pcm_substream *substream, clk_rate_exclusive_put(sai->sai_mclk); + spin_lock_irqsave(&sai->irq_lock, flags); sai->substream = NULL; + spin_unlock_irqrestore(&sai->irq_lock, flags); } static int stm32_sai_pcm_new(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *cpu_dai) { struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + struct snd_kcontrol_new knew = iec958_ctls; if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { dev_dbg(&sai->pdev->dev, "%s: register iec controls", __func__); - return snd_ctl_add(rtd->pcm->card, - snd_ctl_new1(&iec958_ctls, sai)); + knew.device = rtd->pcm->device; + return snd_ctl_add(rtd->pcm->card, snd_ctl_new1(&knew, sai)); } return 0; @@ -1081,7 +1107,7 @@ static int stm32_sai_pcm_new(struct snd_soc_pcm_runtime *rtd, static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) { struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); - int cr1 = 0, cr1_mask; + int cr1 = 0, cr1_mask, ret; sai->cpu_dai = cpu_dai; @@ -1111,8 +1137,10 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) /* Configure synchronization */ if (sai->sync == SAI_SYNC_EXTERNAL) { /* Configure synchro client and provider */ - sai->pdata->set_sync(sai->pdata, sai->np_sync_provider, - sai->synco, sai->synci); + ret = sai->pdata->set_sync(sai->pdata, sai->np_sync_provider, + sai->synco, sai->synci); + if (ret) + return ret; } cr1_mask |= SAI_XCR1_SYNCEN_MASK; @@ -1424,6 +1452,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) sai->pdev = pdev; mutex_init(&sai->ctrl_lock); + spin_lock_init(&sai->irq_lock); platform_set_drvdata(pdev, sai); sai->pdata = dev_get_drvdata(pdev->dev.parent); @@ -1464,10 +1493,34 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int stm32_sai_sub_suspend(struct device *dev) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(dev); + + regcache_cache_only(sai->regmap, true); + regcache_mark_dirty(sai->regmap); + return 0; +} + +static int stm32_sai_sub_resume(struct device *dev) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(dev); + + regcache_cache_only(sai->regmap, false); + return regcache_sync(sai->regmap); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_sai_sub_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_sai_sub_suspend, stm32_sai_sub_resume) +}; + static struct platform_driver stm32_sai_sub_driver = { .driver = { .name = "st,stm32-sai-sub", .of_match_table = stm32_sai_sub_ids, + .pm = &stm32_sai_sub_pm_ops, }, .probe = stm32_sai_sub_probe, }; diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 373df4f24be1..b4c3d983e195 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -21,6 +21,7 @@ #include <linux/delay.h> #include <linux/module.h> #include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> #include <linux/regmap.h> #include <linux/reset.h> @@ -471,6 +472,8 @@ static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) memset(spdifrx->cs, 0, SPDIFRX_CS_BYTES_NB); memset(spdifrx->ub, 0, SPDIFRX_UB_BYTES_NB); + pinctrl_pm_select_default_state(&spdifrx->pdev->dev); + ret = stm32_spdifrx_dma_ctrl_start(spdifrx); if (ret < 0) return ret; @@ -502,6 +505,7 @@ static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) end: clk_disable_unprepare(spdifrx->kclk); + pinctrl_pm_select_sleep_state(&spdifrx->pdev->dev); return ret; } @@ -611,10 +615,15 @@ static bool stm32_spdifrx_readable_reg(struct device *dev, unsigned int reg) static bool stm32_spdifrx_volatile_reg(struct device *dev, unsigned int reg) { - if (reg == STM32_SPDIFRX_DR) + switch (reg) { + case STM32_SPDIFRX_DR: + case STM32_SPDIFRX_CSR: + case STM32_SPDIFRX_SR: + case STM32_SPDIFRX_DIR: return true; - - return false; + default: + return false; + } } static bool stm32_spdifrx_writeable_reg(struct device *dev, unsigned int reg) @@ -638,6 +647,7 @@ static const struct regmap_config stm32_h7_spdifrx_regmap_conf = { .volatile_reg = stm32_spdifrx_volatile_reg, .writeable_reg = stm32_spdifrx_writeable_reg, .fast_io = true, + .cache_type = REGCACHE_FLAT, }; static irqreturn_t stm32_spdifrx_isr(int irq, void *devid) @@ -983,10 +993,36 @@ static int stm32_spdifrx_remove(struct platform_device *pdev) MODULE_DEVICE_TABLE(of, stm32_spdifrx_ids); +#ifdef CONFIG_PM_SLEEP +static int stm32_spdifrx_suspend(struct device *dev) +{ + struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(dev); + + regcache_cache_only(spdifrx->regmap, true); + regcache_mark_dirty(spdifrx->regmap); + + return 0; +} + +static int stm32_spdifrx_resume(struct device *dev) +{ + struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(dev); + + regcache_cache_only(spdifrx->regmap, false); + + return regcache_sync(spdifrx->regmap); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_spdifrx_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_spdifrx_suspend, stm32_spdifrx_resume) +}; + static struct platform_driver stm32_spdifrx_driver = { .driver = { .name = "st,stm32-spdifrx", .of_match_table = stm32_spdifrx_ids, + .pm = &stm32_spdifrx_pm_ops, }, .probe = stm32_spdifrx_probe, .remove = stm32_spdifrx_remove, diff --git a/sound/soc/ti/Kconfig b/sound/soc/ti/Kconfig index 4bf3c15d4e51..ee7c202c69b7 100644 --- a/sound/soc/ti/Kconfig +++ b/sound/soc/ti/Kconfig @@ -21,8 +21,8 @@ config SND_SOC_DAVINCI_ASP config SND_SOC_DAVINCI_MCASP tristate "Multichannel Audio Serial Port (McASP) support" - select SND_SOC_TI_EDMA_PCM if TI_EDMA - select SND_SOC_TI_SDMA_PCM if DMA_OMAP + select SND_SOC_TI_EDMA_PCM + select SND_SOC_TI_SDMA_PCM help Say Y or M here if you want to have support for McASP IP found in various Texas Instruments SoCs like: diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c index 4dce494dfbd3..b9611db14c86 100644 --- a/sound/soc/ti/ams-delta.c +++ b/sound/soc/ti/ams-delta.c @@ -200,7 +200,7 @@ static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol, return 0; } -static const SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum, +static SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum, ams_delta_audio_mode); static const struct snd_kcontrol_new ams_delta_audio_controls[] = { diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index a3a67a8f0f54..9fbc759fdefe 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -45,6 +45,7 @@ #define MCASP_MAX_AFIFO_DEPTH 64 +#ifdef CONFIG_PM static u32 context_regs[] = { DAVINCI_MCASP_TXFMCTL_REG, DAVINCI_MCASP_RXFMCTL_REG, @@ -68,6 +69,7 @@ struct davinci_mcasp_context { u32 *xrsr_regs; /* for serializer configuration */ bool pm_state; }; +#endif struct davinci_mcasp_ruledata { struct davinci_mcasp *mcasp; diff --git a/sound/soc/ti/edma-pcm.c b/sound/soc/ti/edma-pcm.c index 59e588abe54b..fdffb801b185 100644 --- a/sound/soc/ti/edma-pcm.c +++ b/sound/soc/ti/edma-pcm.c @@ -23,7 +23,6 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/dmaengine_pcm.h> -#include <linux/edma.h> #include "edma-pcm.h" @@ -43,14 +42,12 @@ static const struct snd_pcm_hardware edma_pcm_hardware = { static const struct snd_dmaengine_pcm_config edma_dmaengine_pcm_config = { .pcm_hardware = &edma_pcm_hardware, .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, - .compat_filter_fn = edma_filter_fn, .prealloc_buffer_size = 128 * 1024, }; int edma_pcm_platform_register(struct device *dev) { - return devm_snd_dmaengine_pcm_register(dev, &edma_dmaengine_pcm_config, - SND_DMAENGINE_PCM_FLAG_COMPAT); + return devm_snd_dmaengine_pcm_register(dev, &edma_dmaengine_pcm_config, 0); } EXPORT_SYMBOL_GPL(edma_pcm_platform_register); diff --git a/sound/soc/ti/sdma-pcm.c b/sound/soc/ti/sdma-pcm.c index 21a9c2499d48..a236350beb10 100644 --- a/sound/soc/ti/sdma-pcm.c +++ b/sound/soc/ti/sdma-pcm.c @@ -11,7 +11,6 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/dmaengine_pcm.h> -#include <linux/omap-dmaengine.h> #include "sdma-pcm.h" @@ -31,7 +30,6 @@ static const struct snd_pcm_hardware sdma_pcm_hardware = { static const struct snd_dmaengine_pcm_config sdma_dmaengine_pcm_config = { .pcm_hardware = &sdma_pcm_hardware, .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, - .compat_filter_fn = omap_dma_filter_fn, .prealloc_buffer_size = 128 * 1024, }; @@ -39,13 +37,12 @@ int sdma_pcm_platform_register(struct device *dev, char *txdmachan, char *rxdmachan) { struct snd_dmaengine_pcm_config *config; - unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT; + unsigned int flags = 0; /* Standard names for the directions: 'tx' and 'rx' */ if (!txdmachan && !rxdmachan) return devm_snd_dmaengine_pcm_register(dev, - &sdma_dmaengine_pcm_config, - flags); + &sdma_dmaengine_pcm_config, 0); config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); if (!config) @@ -65,7 +62,7 @@ int sdma_pcm_platform_register(struct device *dev, config->chan_names[0] = txdmachan; config->chan_names[1] = rxdmachan; - return devm_snd_dmaengine_pcm_register(dev, config, flags); + return devm_snd_dmaengine_pcm_register(dev, config, 0); } EXPORT_SYMBOL_GPL(sdma_pcm_platform_register); |