diff options
76 files changed, 18758 insertions, 336 deletions
diff --git a/Documentation/devicetree/bindings/sound/fsl,asrc.txt b/Documentation/devicetree/bindings/sound/fsl,asrc.txt index 1d4d9f938689..cd2bd3daa7e1 100644 --- a/Documentation/devicetree/bindings/sound/fsl,asrc.txt +++ b/Documentation/devicetree/bindings/sound/fsl,asrc.txt @@ -8,7 +8,8 @@ three substreams within totally 10 channels. Required properties: - - compatible : Contains "fsl,imx35-asrc" or "fsl,imx53-asrc". + - compatible : Contains "fsl,imx35-asrc", "fsl,imx53-asrc", + "fsl,imx8qm-asrc0" or "fsl,imx8qm-asrc1". - reg : Offset and length of the register set for the device. diff --git a/Documentation/devicetree/bindings/sound/fsl,dsp.txt b/Documentation/devicetree/bindings/sound/fsl,dsp.txt new file mode 100644 index 000000000000..84bc228b4e32 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,dsp.txt @@ -0,0 +1,16 @@ +NXP DSP + +The IP is from Cadence. + +Required properties: + + - compatible : Contains "fsl,imx8qxp-dsp". + - reg : Offset and length of the register set for the device. + +Example: + +dsp: dsp@596e8000 { + compatible = "fsl,imx8qxp-dsp"; + reg = <0x0 0x596e8000 0x0 0x88000>; + status = "okay"; +}; diff --git a/Documentation/devicetree/bindings/sound/fsl,easrc.txt b/Documentation/devicetree/bindings/sound/fsl,easrc.txt new file mode 100644 index 000000000000..569ff3f50317 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,easrc.txt @@ -0,0 +1,53 @@ +Freescale Asynchronous Sample Rate Converter (ASRC) Controller + +The Asynchronous Sample Rate Converter (ASRC) converts the sampling rate of a +signal associated with an input clock into a signal associated with a different +output clock. The driver currently works as a Front End of DPCM with other Back +Ends Audio controller such as ESAI, SSI and SAI. It has four context to support +four substreams within totally 32 channels. + +Required properties: + + - compatible : Contains "fsl,imx8mn-easrc". + + - reg : Offset and length of the register set for the device. + + - interrupts : Contains the asrc interrupt. + + - dmas : Generic dma devicetree binding as described in + Documentation/devicetree/bindings/dma/dma.txt. + + - dma-names : Contains "ctx0_rx", "ctx0_tx", "ctx1_rx", "ctx1_tx", + "ctx2_rx", "ctx2_tx", "ctx3_rx", "ctx3_tx". + + - clocks : Contains an entry for each entry in clock-names. + + - clock-names : Contains the following entries + "mem" Peripheral clock to driver module. + + - fsl,easrc-ram-script-name : The coefficient table for the filters + - fsl,asrc-rate : Defines a mutual sample rate used by DPCM Back Ends. + + - fsl,asrc-width : Defines a mutual sample width used by DPCM Back Ends. + +Example: + +easrc: easrc@300C0000 { + compatible = "fsl,imx8mn-easrc"; + reg = <0x0 0x300C0000 0x0 0x10000>; + interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk IMX8MN_CLK_ASRC_ROOT>; + clock-names = "mem"; + dmas = <&sdma2 16 23 0> , <&sdma2 17 23 0>, + <&sdma2 18 23 0> , <&sdma2 19 23 0>, + <&sdma2 20 23 0> , <&sdma2 21 23 0>, + <&sdma2 22 23 0> , <&sdma2 23 23 0>; + dma-names = "ctx0_rx", "ctx0_tx", + "ctx1_rx", "ctx1_tx", + "ctx2_rx", "ctx2_tx", + "ctx3_rx", "ctx3_tx"; + fsl,easrc-ram-script-name = "imx/easrc/easrc-imx8mn.bin"; + fsl,asrc-rate = <8000>; + fsl,asrc-width = <16>; + status = "disabled"; +}; diff --git a/Documentation/devicetree/bindings/sound/fsl,mqs.txt b/Documentation/devicetree/bindings/sound/fsl,mqs.txt new file mode 100644 index 000000000000..092083d3a335 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,mqs.txt @@ -0,0 +1,23 @@ +fsl,mqs audio CODEC + +Required properties: + + - compatible : must contain one of "fsl,imx6sx-mqs", "fsl,codec-mqs" + "fsl,imx8qm-mqs". + + - clocks : a list of phandles + clock-specifiers, one for each entry in + clock-names + + - clock-names : must contain "mclk" + + - gpr : the gpr node. + +Example: + +mqs: mqs { + compatible = "fsl,imx6sx-mqs"; + gpr = <&gpr>; + clocks = <&clks IMX6SX_CLK_SAI1>; + clock-names = "mclk"; + status = "disabled"; +}; diff --git a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt index c60a5732d29c..f749e2744824 100644 --- a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt +++ b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt @@ -28,6 +28,7 @@ The compatible list for this generic sound card currently: (compatible with CS4271 and CS4272) "fsl,imx-audio-wm8962" + (compatible with Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt) "fsl,imx-audio-sgtl5000" (compatible with Documentation/devicetree/bindings/sound/imx-audio-sgtl5000.txt) diff --git a/Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt b/Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt new file mode 100644 index 000000000000..a442d3edd62d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt @@ -0,0 +1,30 @@ +Freescale i.MX audio complex with AK4458 DAC + +Required properties: + +- compatible : "fsl,imx-audio-ak4458", "fsl,imx-audio-ak4458-mq" +- model : The user-visible name of this sound complex +- audio-cpu : The phandle of CPU DAI +- audio-codec : The phandle of the AK4458 audio DAC +- audio-routing : A list of the connections between audio components. Each entry + is a pair of strings, the first being the connection's sink, the second being + the connection's source. Valid names could be power supplies, AK4458 pins, + and the jacks on the board. + +Example: + +sound { + compatible = "fsl,imx-audio-ak4458"; + model = "ak4458-audio"; + audio-cpu = <&sai1>; + audio-codec = <&codec>; + audio-routing = + "AOUTL1", "Playback", + "AOUTR1", "Playback", + "AOUTL2", "Playback", + "AOUTR2", "Playback", + "AOUTL3", "Playback", + "AOUTR3", "Playback", + "AOUTL4", "Playback", + "AOUTR4", "Playback"; +}; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt b/Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt new file mode 100644 index 000000000000..7eeeeeda74f5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt @@ -0,0 +1,27 @@ +Freescale i.MX audio complex with AK4497 DAC + +Required properties: + +- compatible : "fsl,imx-audio-ak4497", "fsl,imx-audio-ak4497-mq" +- model : The user-visible name of this sound complex +- audio-cpu : The phandle of CPU DAI +- audio-codec : The phandle of the ak4497 audio DAC +- audio-routing : A list of the connections between audio components. Each entry + is a pair of strings, the first being the connection's sink, the second being + the connection's source. Valid names could be power supplies, ak4497 pins, + and the jacks on the board. + +Example: + +sound { + compatible = "fsl,imx-audio-ak4497"; + model = "ak4497-audio"; + audio-cpu = <&sai3>; + audio-codec = <&codec>; + audio-routing = + "AOUTLN", "Playback", + "AOUTLP", "Playback", + "AOUTRN", "Playback", + "AOUTRP", "Playback", +}; + diff --git a/Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt b/Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt new file mode 100644 index 000000000000..7b62fbb14f8d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt @@ -0,0 +1,30 @@ +Freescale i.MX audio complex with AK5558 ADC + +Required properties: + +- compatible : "fsl,imx-audio-ak5558", "fsl,imx-audio-ak5558-mq" +- model : The user-visible name of this sound complex +- audio-cpu : The phandle of CPU DAI +- audio-codec : The phandle of the AK5558 audio ADC +- audio-routing : A list of the connections between audio components. Each entry + is a pair of strings, the first being the connection's sink, the second being + the connection's source. Valid names could be power supplies, AK5558 pins, + and the jacks on the board. + +Example: + +sound { + compatible = "fsl,imx-audio-ak5558"; + model = "ak5558-audio"; + audio-cpu = <&sai1>; + audio-codec = <&codec>; + audio-routing = + "AIN1", "Capture", + "AIN2", "Capture", + "AIN3", "Capture", + "AIN4", "Capture", + "AIN5", "Capture", + "AIN6", "Capture", + "AIN7", "Capture", + "AIN8", "Capture"; +}; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt b/Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt new file mode 100644 index 000000000000..af746c4c81df --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt @@ -0,0 +1,25 @@ +Freescale i.MX audio complex with CS42888 codec + +Required properties: +- compatible : "fsl,imx-audio-cs42888" +- model : The user-visible name of this sound complex +- esai-controller : The phandle of the i.MX SSI controller +- audio-codec : The phandle of the CS42888 audio codec + +Optional properties: +- asrc-controller : The phandle of the i.MX ASRC controller +- audio-routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names could be power + supplies, CS42888 pins, and the jacks on the board: + +Example: + +sound { + compatible = "fsl,imx6q-sabresd-wm8962", + "fsl,imx-audio-wm8962"; + model = "cs42888-audio"; + esai-controller = <&esai>; + asrc-controller = <&asrc_p2p>; + audio-codec = <&codec>; +}; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-mqs.txt b/Documentation/devicetree/bindings/sound/imx-audio-mqs.txt new file mode 100644 index 000000000000..c0195c202894 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-mqs.txt @@ -0,0 +1,18 @@ +Freescale i.MX audio complex with mqs codec + +Required properties: +- compatible : "fsl,imx-audio-mqs", "fsl,imx8qm-lpddr4-arm2-mqs". +- model : The user-visible name of this sound complex +- cpu-dai : The phandle of the i.MX sai controller +- audio-codec : The phandle of the mqs audio codec + +Example: + +sound-mqs { + compatible = "fsl,imx6sx-sdb-mqs", + "fsl,imx-audio-mqs"; + model = "mqs-audio"; + cpu-dai = <&sai1>; + audio-codec = <&mqs>; +}; + diff --git a/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt b/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt new file mode 100644 index 000000000000..53cd34afe6b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt @@ -0,0 +1,24 @@ +Freescale i.MX audio complex with si476x codec + +Required properties: +- compatible : "fsl,imx-audio-si476x" +- model : The user-visible name of this sound complex +- ssi-controller : The phandle of the i.MX SSI controller + +- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX) +- mux-ext-port : The external port of the i.MX audio muxer + +Note: The AUDMUX port numbering should start at 1, which is consistent with +hardware manual. + +Example: + +sound { + compatible = "fsl,imx-audio-si476x", + "fsl,imx-tuner-si476x"; + model = "imx-radio-si476x"; + + ssi-controller = <&ssi1>; + mux-int-port = <2>; + mux-ext-port = <5>; +}; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt new file mode 100644 index 000000000000..b3e3c01464bd --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt @@ -0,0 +1,29 @@ +Freescale i.MX audio complex with WM8524 codec + +Required properties: + + - compatible : "fsl,imx-audio-wm8524" + + - model : The user-visible name of this sound complex + + - audio-cpu : The phandle of CPU DAI + + - audio-codec : The phandle of the WM8962 audio codec + + - audio-routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being the + connection's sink, the second being the connection's + source. Valid names could be power supplies, WM8524 + pins, and the jacks on the board: + +Example: + +sound { + compatible = "fsl,imx-audio-wm8524"; + model = "wm8524-audio"; + audio-cpu = <&sai2>; + audio-codec = <&codec>; + audio-routing = + "Line Out Jack", "LINEVOUTL", + "Line Out Jack", "LINEVOUTR"; +}; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt new file mode 100644 index 000000000000..06bc12d4cc76 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt @@ -0,0 +1,61 @@ +Freescale i.MX audio complex with WM8962 codec + +Required properties: + + - compatible : "fsl,imx-audio-wm8962" + + - model : The user-visible name of this sound complex + + - cpu-dai : The phandle of CPU DAI + + - audio-codec : The phandle of the WM8962 audio codec + + - audio-routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being the + connection's sink, the second being the connection's + source. Valid names could be power supplies, WM8962 + pins, and the jacks on the board: + + Power supplies: + * Mic Bias + + Board connectors: + * Mic Jack + * Headphone Jack + * Ext Spk + + - mux-int-port : The internal port of the i.MX audio muxer (AUDMUX) + + - mux-ext-port : The external port of the i.MX audio muxer + +Note: The AUDMUX port numbering should start at 1, which is consistent with +hardware manual. + +Optional properties: +- hp-det-gpios : The gpio pin to detect plug in/out event that happens to + Headphone jack. +- mic-det-gpios: The gpio pin to detect plug in/out event that happens to + Microphone jack. + +Example: + +sound { + compatible = "fsl,imx6q-sabresd-wm8962", + "fsl,imx-audio-wm8962"; + model = "wm8962-audio"; + cpu-dai = <&ssi2>; + audio-codec = <&codec>; + audio-routing = + "Headphone Jack", "HPOUTL", + "Headphone Jack", "HPOUTR", + "Ext Spk", "SPKOUTL", + "Ext Spk", "SPKOUTR", + "MICBIAS", "AMIC", + "IN3R", "MICBIAS", + "DMIC", "MICBIAS", + "DMICDAT", "DMIC"; + mux-int-port = <2>; + mux-ext-port = <3>; + hp-det-gpios = <&gpio7 8 1>; + mic-det-gpios = <&gpio1 9 1>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8962.txt b/Documentation/devicetree/bindings/sound/wm8962.txt index dcfa9a3369fd..c624cebc5372 100644 --- a/Documentation/devicetree/bindings/sound/wm8962.txt +++ b/Documentation/devicetree/bindings/sound/wm8962.txt @@ -13,6 +13,14 @@ Optional properties: of R51 (Class D Control 2) gets set, indicating that the speaker is in mono mode. + - amic-mono: This is a boolean property. If present, indicating that the + analog micphone is hardware mono input, the driver would enable monomix + for it. + + - dmic-mono: This is a boolean property. If present, indicating that the + digital micphone is hardware mono input, the driver would enable monomix + for it. + - mic-cfg : Default register value for R48 (Additional Control 4). If absent, the default should be the register default. diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c index b203296de977..c2490267f254 100644 --- a/drivers/media/radio/radio-si476x.c +++ b/drivers/media/radio/radio-si476x.c @@ -988,6 +988,14 @@ static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) } break; + case V4L2_CID_AUDIO_MUTE: + if (ctrl->val) + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_MUTE, 3); + else + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_MUTE, 0); + break; default: retval = -EINVAL; break; @@ -1515,6 +1523,16 @@ static int si476x_radio_probe(struct platform_device *pdev) goto exit; } + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_AUDIO_MUTE control %d\n", + rval); + goto exit; + } + if (si476x_core_has_diversity(radio->core)) { si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c index c8d28b844def..accdcdc7f23f 100644 --- a/drivers/mfd/si476x-i2c.c +++ b/drivers/mfd/si476x-i2c.c @@ -97,7 +97,7 @@ static int si476x_core_config_pinmux(struct si476x_core *core) static inline void si476x_core_schedule_polling_work(struct si476x_core *core) { - schedule_delayed_work(&core->status_monitor, + queue_delayed_work(system_freezable_wq, &core->status_monitor, usecs_to_jiffies(SI476X_STATUS_POLL_US)); } @@ -294,7 +294,7 @@ int si476x_core_set_power_state(struct si476x_core *core, */ udelay(100); - err = si476x_core_start(core, false); + err = si476x_core_start(core, true); if (err < 0) goto disable_regulators; @@ -303,7 +303,7 @@ int si476x_core_set_power_state(struct si476x_core *core, case SI476X_POWER_DOWN: core->power_state = next_state; - err = si476x_core_stop(core, false); + err = si476x_core_stop(core, true); if (err < 0) core->power_state = SI476X_POWER_INCONSISTENT; disable_regulators: @@ -729,8 +729,15 @@ static int si476x_core_probe(struct i2c_client *client, memcpy(&core->pinmux, &pdata->pinmux, sizeof(struct si476x_pinmux)); } else { - dev_err(&client->dev, "No platform data provided\n"); - return -EINVAL; + dev_warn(&client->dev, "Using default platform data.\n"); + core->power_up_parameters.xcload = 0x28; + core->power_up_parameters.func = SI476X_FUNC_FM_RECEIVER; + core->power_up_parameters.freq = SI476X_FREQ_37P209375_MHZ; + core->diversity_mode = SI476X_PHDIV_DISABLED; + core->pinmux.dclk = SI476X_DCLK_DAUDIO; + core->pinmux.dfs = SI476X_DFS_DAUDIO; + core->pinmux.dout = SI476X_DOUT_I2S_OUTPUT; + core->pinmux.xout = SI476X_XOUT_TRISTATE; } core->supplies[0].supply = "vd"; @@ -789,12 +796,18 @@ static int si476x_core_probe(struct i2c_client *client, core->chip_id = id->driver_data; + /* Power down si476x first */ + si476x_core_stop(core, true); + rval = si476x_core_get_revision_info(core); if (rval < 0) { rval = -ENODEV; goto free_kfifo; } + if (of_property_read_bool(client->dev.of_node, "revision-a10")) + core->revision = SI476X_REVISION_A10; + cell_num = 0; cell = &core->cells[SI476X_RADIO_CELL]; @@ -810,6 +823,7 @@ static int si476x_core_probe(struct i2c_client *client, core->pinmux.xout == SI476X_XOUT_TRISTATE) { cell = &core->cells[SI476X_CODEC_CELL]; cell->name = "si476x-codec"; + cell->of_compatible = "si476x-codec"; cell_num++; } #endif diff --git a/include/linux/busfreq-imx.h b/include/linux/busfreq-imx.h new file mode 100644 index 000000000000..39c71a9f55eb --- /dev/null +++ b/include/linux/busfreq-imx.h @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ASM_ARCH_MXC_BUSFREQ_H__ +#define __ASM_ARCH_MXC_BUSFREQ_H__ + +#include <linux/notifier.h> +#include <linux/regulator/consumer.h> + +/* + * This enumerates busfreq low power mode entry and exit. + */ +enum busfreq_event { + LOW_BUSFREQ_ENTER, + LOW_BUSFREQ_EXIT, +}; + +/* + * This enumerates the system bus and ddr frequencies in various modes. + * BUS_FREQ_HIGH - DDR @ 528MHz, AHB @ 132MHz. + * BUS_FREQ_MED - DDR @ 400MHz, AHB @ 132MHz + * BUS_FREQ_AUDIO - DDR @ 50MHz/100MHz, AHB @ 24MHz. + * BUS_FREQ_LOW - DDR @ 24MHz, AHB @ 24MHz. + * BUS_FREQ_ULTRA_LOW - DDR @ 1MHz, AHB - 3MHz. + * + * Drivers need to request/release the bus/ddr frequencies based on + * their performance requirements. Drivers cannot request/release + * BUS_FREQ_ULTRA_LOW mode as this mode is automatically entered from + * either BUS_FREQ_AUDIO or BUS_FREQ_LOW + * modes. + */ +enum bus_freq_mode { + BUS_FREQ_HIGH, + BUS_FREQ_MED, + BUS_FREQ_AUDIO, + BUS_FREQ_LOW, + BUS_FREQ_ULTRA_LOW, +}; + +#if defined(CONFIG_HAVE_IMX_BUSFREQ) && !defined(CONFIG_ARM64) +extern struct regulator *arm_reg; +extern struct regulator *soc_reg; +void request_bus_freq(enum bus_freq_mode mode); +void release_bus_freq(enum bus_freq_mode mode); +int register_busfreq_notifier(struct notifier_block *nb); +int unregister_busfreq_notifier(struct notifier_block *nb); +int get_bus_freq_mode(void); +#elif defined(CONFIG_HAVE_IMX_BUSFREQ) +void request_bus_freq(enum bus_freq_mode mode); +void release_bus_freq(enum bus_freq_mode mode); +int get_bus_freq_mode(void); +#else +static inline void request_bus_freq(enum bus_freq_mode mode) +{ +} +static inline void release_bus_freq(enum bus_freq_mode mode) +{ +} +static inline int register_busfreq_notifier(struct notifier_block *nb) +{ + return 0; +} +static inline int unregister_busfreq_notifier(struct notifier_block *nb) +{ + return 0; +} +static inline int get_bus_freq_mode(void) +{ + return BUS_FREQ_HIGH; +} +#endif +#endif diff --git a/include/linux/mfd/si476x-core.h b/include/linux/mfd/si476x-core.h index 4708c2b8512a..e591c050dce1 100644 --- a/include/linux/mfd/si476x-core.h +++ b/include/linux/mfd/si476x-core.h @@ -484,6 +484,8 @@ enum si476x_common_receiver_properties { SI476X_PROP_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, + SI476X_PROP_AUDIO_MUTE = 0x0301, + SI476X_PROP_SEEK_BAND_BOTTOM = 0x1100, SI476X_PROP_SEEK_BAND_TOP = 0x1101, SI476X_PROP_SEEK_FREQUENCY_SPACING = 0x1102, diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index c679f6116580..649704b6f349 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -76,6 +76,7 @@ struct snd_dmaengine_dai_dma_data { const char *chan_name; unsigned int fifo_size; unsigned int flags; + unsigned int fifo_num; }; void snd_dmaengine_pcm_set_config_from_dai_data( diff --git a/include/uapi/linux/mxc_asrc.h b/include/uapi/linux/mxc_asrc.h new file mode 100644 index 000000000000..79059c3be2c4 --- /dev/null +++ b/include/uapi/linux/mxc_asrc.h @@ -0,0 +1,165 @@ +/* + * Copyright 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 + * + * @file mxc_asrc.h + * + * @brief i.MX Asynchronous Sample Rate Converter + * + * @ingroup Audio + */ + +#ifndef __MXC_ASRC_UAPI_H__ +#define __MXC_ASRC_UAPI_H__ + +#define ASRC_IOC_MAGIC 'C' + +#define ASRC_REQ_PAIR _IOWR(ASRC_IOC_MAGIC, 0, struct asrc_req) +#define ASRC_CONFIG_PAIR _IOWR(ASRC_IOC_MAGIC, 1, struct asrc_config) +#define ASRC_RELEASE_PAIR _IOW(ASRC_IOC_MAGIC, 2, enum asrc_pair_index) +#define ASRC_CONVERT _IOW(ASRC_IOC_MAGIC, 3, struct asrc_convert_buffer) +#define ASRC_START_CONV _IOW(ASRC_IOC_MAGIC, 4, enum asrc_pair_index) +#define ASRC_STOP_CONV _IOW(ASRC_IOC_MAGIC, 5, enum asrc_pair_index) +#define ASRC_STATUS _IOW(ASRC_IOC_MAGIC, 6, struct asrc_status_flags) +#define ASRC_FLUSH _IOW(ASRC_IOC_MAGIC, 7, enum asrc_pair_index) + +enum asrc_pair_index { + ASRC_INVALID_PAIR = -1, + ASRC_PAIR_A = 0, + ASRC_PAIR_B = 1, + ASRC_PAIR_C = 2, + ASRC_PAIR_D = 3, +}; + +enum asrc_inclk { + INCLK_NONE = 0x03, + INCLK_ESAI_RX = 0x00, + INCLK_SSI1_RX = 0x01, + INCLK_SSI2_RX = 0x02, + INCLK_SSI3_RX = 0x07, + INCLK_SPDIF_RX = 0x04, + INCLK_MLB_CLK = 0x05, + INCLK_PAD = 0x06, + INCLK_ESAI_TX = 0x08, + INCLK_SSI1_TX = 0x09, + INCLK_SSI2_TX = 0x0a, + INCLK_SSI3_TX = 0x0b, + INCLK_SPDIF_TX = 0x0c, + INCLK_ASRCK1_CLK = 0x0f, +/* imx8 */ + INCLK_AUD_PLL_DIV_CLK0 = 0x10, + INCLK_AUD_PLL_DIV_CLK1 = 0x11, + INCLK_AUD_CLK0 = 0x12, + INCLK_AUD_CLK1 = 0x13, + INCLK_ESAI0_RX_CLK = 0x14, + INCLK_ESAI0_TX_CLK = 0x15, + INCLK_SPDIF0_RX = 0x16, + INCLK_SPDIF1_RX = 0x17, + INCLK_SAI0_RX_BCLK = 0x18, + INCLK_SAI0_TX_BCLK = 0x19, + INCLK_SAI1_RX_BCLK = 0x1a, + INCLK_SAI1_TX_BCLK = 0x1b, + INCLK_SAI2_RX_BCLK = 0x1c, + INCLK_SAI3_RX_BCLK = 0x1d, + INCLK_ASRC0_MUX_CLK = 0x1e, + + INCLK_ESAI1_RX_CLK = 0x20, + INCLK_ESAI1_TX_CLK = 0x21, + INCLK_SAI6_TX_BCLK = 0x22, + INCLK_HDMI_RX_SAI0_RX_BCLK = 0x24, + INCLK_HDMI_TX_SAI0_TX_BCLK = 0x25, +}; + +enum asrc_outclk { + OUTCLK_NONE = 0x03, + OUTCLK_ESAI_TX = 0x00, + OUTCLK_SSI1_TX = 0x01, + OUTCLK_SSI2_TX = 0x02, + OUTCLK_SSI3_TX = 0x07, + OUTCLK_SPDIF_TX = 0x04, + OUTCLK_MLB_CLK = 0x05, + OUTCLK_PAD = 0x06, + OUTCLK_ESAI_RX = 0x08, + OUTCLK_SSI1_RX = 0x09, + OUTCLK_SSI2_RX = 0x0a, + OUTCLK_SSI3_RX = 0x0b, + OUTCLK_SPDIF_RX = 0x0c, + OUTCLK_ASRCK1_CLK = 0x0f, + +/* imx8 */ + OUTCLK_AUD_PLL_DIV_CLK0 = 0x10, + OUTCLK_AUD_PLL_DIV_CLK1 = 0x11, + OUTCLK_AUD_CLK0 = 0x12, + OUTCLK_AUD_CLK1 = 0x13, + OUTCLK_ESAI0_RX_CLK = 0x14, + OUTCLK_ESAI0_TX_CLK = 0x15, + OUTCLK_SPDIF0_RX = 0x16, + OUTCLK_SPDIF1_RX = 0x17, + OUTCLK_SAI0_RX_BCLK = 0x18, + OUTCLK_SAI0_TX_BCLK = 0x19, + OUTCLK_SAI1_RX_BCLK = 0x1a, + OUTCLK_SAI1_TX_BCLK = 0x1b, + OUTCLK_SAI2_RX_BCLK = 0x1c, + OUTCLK_SAI3_RX_BCLK = 0x1d, + OUTCLK_ASRCO_MUX_CLK = 0x1e, + + OUTCLK_ESAI1_RX_CLK = 0x20, + OUTCLK_ESAI1_TX_CLK = 0x21, + OUTCLK_SAI6_TX_BCLK = 0x22, + OUTCLK_HDMI_RX_SAI0_RX_BCLK = 0x24, + OUTCLK_HDMI_TX_SAI0_TX_BCLK = 0x25, +}; + +struct asrc_config { + enum asrc_pair_index pair; + unsigned int channel_num; + unsigned int dma_buffer_size; + unsigned int input_sample_rate; + unsigned int output_sample_rate; + snd_pcm_format_t input_format; + snd_pcm_format_t output_format; + enum asrc_inclk inclk; + enum asrc_outclk outclk; +}; + +struct asrc_req { + unsigned int chn_num; + enum asrc_pair_index index; + uint64_t supported_in_format; + uint64_t supported_out_format; +}; + +struct asrc_querybuf { + unsigned int buffer_index; + unsigned int input_length; + unsigned int output_length; + unsigned long input_offset; + unsigned long output_offset; +}; + +struct asrc_convert_buffer { + void *input_buffer_vaddr; + void *output_buffer_vaddr; + unsigned int input_buffer_length; + unsigned int output_buffer_length; +}; + +struct asrc_status_flags { + enum asrc_pair_index index; + unsigned int overload_error; +}; + +enum asrc_error_status { + ASRC_TASK_Q_OVERLOAD = 0x01, + ASRC_OUTPUT_TASK_OVERLOAD = 0x02, + ASRC_INPUT_TASK_OVERLOAD = 0x04, + ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, + ASRC_INPUT_BUFFER_UNDERRUN = 0x10, +}; +#endif/* __MXC_ASRC_UAPI_H__ */ diff --git a/include/uapi/linux/mxc_dsp.h b/include/uapi/linux/mxc_dsp.h new file mode 100644 index 000000000000..040681cc39c8 --- /dev/null +++ b/include/uapi/linux/mxc_dsp.h @@ -0,0 +1,152 @@ +/* + * Copyright 2018 NXP + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MXC_DSP_UAPI_H__ +#define __MXC_DSP_UAPI_H__ + +#define DSP_IOC_MAGIC 'H' +#define DSP_CLIENT_REGISTER _IOW(DSP_IOC_MAGIC, 0, unsigned int) +#define DSP_CLIENT_UNREGISTER _IOW(DSP_IOC_MAGIC, 1, unsigned int) +#define DSP_IPC_MSG_SEND _IOW(DSP_IOC_MAGIC, 2, unsigned int) +#define DSP_IPC_MSG_RECV _IOW(DSP_IOC_MAGIC, 3, unsigned int) +#define DSP_GET_SHMEM_INFO _IOW(DSP_IOC_MAGIC, 4, unsigned int) +#define DSP_LOAD_LIB _IOW(DSP_IOC_MAGIC, 5, unsigned int) +#define DSP_UNLOAD_LIB _IOW(DSP_IOC_MAGIC, 6, unsigned int) + + +#define CODEC_MP3_DEC 1 +#define CODEC_AAC_DEC 2 +#define CODEC_DAB_DEC 3 +#define CODEC_MP2_DEC 4 +#define CODEC_BSAC_DEC 5 +#define CODEC_DRM_DEC 6 +#define CODEC_SBC_DEC 7 +#define CODEC_SBC_ENC 8 +#define CODEC_DEMO_DEC 9 + +#define RENDER_ESAI 0x10 +#define RENDER_SAI 0x11 + +enum DSP_ERROR_TYPE { + XA_SUCCESS = 0, + + XA_ERROR_STREAM, + XA_PARA_ERROR, + XA_INSUFFICIENT_MEM, + XA_ERR_UNKNOWN, + XA_PROFILE_NOT_SUPPORT, + XA_INIT_ERR, + XA_NO_OUTPUT, + + XA_NOT_ENOUGH_DATA = 0x100, + XA_CAPIBILITY_CHANGE = 0x200, + XA_END_OF_STREAM = 0x300, /* no output */ +}; + +/* Parameter type to Set /Get */ +enum DSP_ParaType { +/* Set parmameters */ +/* common */ + XA_SAMPLERATE = 0, + XA_CHANNEL, + XA_FRAMED, /* one whole frame input */ + XA_DEPTH, + XA_CODEC_DATA, + XA_BITRATE, + XA_DOWNMIX_STEREO, + XA_STREAM_TYPE, + XA_CHAN_MAP_TABLE, + //UNIA_CHANNEL_MASK, + XA_TO_STEREO, + +/* dedicate for mp3 dec */ + XA_MP3_DEC_CRC_CHECK = 0x120, + XA_MP3_DEC_MCH_ENABLE, + XA_MP3_DEC_NONSTD_STRM_SUPPORT, + +/* dedicate for bsac dec */ + XA_BSAC_DEC_DECODELAYERS = 0x130, + +/* dedicate for aacplus dec */ + XA_AACPLUS_DEC_BDOWNSAMPLE = 0x140, + XA_AACPLUS_DEC_BBITSTREAMDOWNMIX, + XA_AACPLUS_DEC_CHANROUTING, + +/* dedicate for dabplus dec */ + XA_DABPLUS_DEC_BDOWNSAMPLE = 0x150, + XA_DABPLUS_DEC_BBITSTREAMDOWNMIX, + XA_DABPLUS_DEC_CHANROUTING, + +/* dedicate for sbc enc */ + XA_SBC_ENC_SUBBANDS = 0x160, + XA_SBC_ENC_BLOCKS, + XA_SBC_ENC_SNR, + XA_SBC_ENC_BITPOOL, + XA_SBC_ENC_CHMODE, + +/* Get parmameters */ + XA_CODEC_DESCRIPTION = 0x200, + XA_OUTPUT_PCM_FORMAT, + XA_CONSUMED_LENGTH, + XA_OUTBUF_ALLOC_SIZE, + XA_CONSUMED_CYCLES, + +}; + +#define XA_STREAM_DABPLUS_BASE 0x30 +enum DSP_StreamType { + /* AAC/AACPLUS file format */ + XA_STREAM_UNKNOWN = 0, + XA_STREAM_ADTS, + XA_STREAM_ADIF, + XA_STREAM_RAW, + + XA_STREAM_LATM, + XA_STREAM_LATM_OUTOFBAND_CONFIG, + XA_STREAM_LOAS, + + /* DABPLUS file format */ + XA_STREAM_DABPLUS_RAW_SIDEINFO = XA_STREAM_DABPLUS_BASE, + XA_STREAM_DABPLUS, + + /* BSAC file raw format */ + XA_STREAM_BSAC_RAW, + +}; + +/* sbc_enc-specific channel modes */ +enum DSP_SbcEncChmode { + XA_CHMODE_MONO = 0, + XA_CHMODE_DUAL = 1, + XA_CHMODE_STEREO = 2, + XA_CHMODE_JOINT = 3, +}; + +struct shmem_info { + unsigned int phys_addr; + unsigned int size; +}; + +#endif/* __MXC_DSP_UAPI_H__ */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 229cc89f8c5a..5fda1c26bf1d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -696,6 +696,9 @@ config SND_SOC_ES8328_SPI depends on SPI_MASTER select SND_SOC_ES8328 +config SND_SOC_FSL_MQS + tristate + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c498373dcc5f..8f71ebb862e2 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -86,6 +86,7 @@ snd-soc-es8316-objs := es8316.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o +snd-soc-fsl-mqs-objs := fsl_mqs.o snd-soc-gtm601-objs := gtm601.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-hdac-hda-objs := hdac_hda.o @@ -370,6 +371,7 @@ obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o +obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index 71562154c0b1..48eff8ca722a 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -18,12 +18,20 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/tlv.h> +#include <linux/regulator/consumer.h> #include "ak4458.h" +#define AK4458_NUM_SUPPLIES 2 +static const char *ak4458_supply_names[AK4458_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + struct ak4458_drvdata { struct snd_soc_dai_driver *dai_drv; const struct snd_soc_component_driver *comp_drv; + bool dsd512; /* DSD512 is supported or not */ }; /* AK4458 Codec Private Data */ @@ -32,11 +40,13 @@ struct ak4458_priv { struct regmap *regmap; struct gpio_desc *reset_gpiod; struct gpio_desc *mute_gpiod; + const struct ak4458_drvdata *drvdata; int digfil; /* SSLOW, SD, SLOW bits */ int fs; /* sampling rate */ int fmt; int slots; int slot_width; + struct regulator_bulk_data supplies[AK4458_NUM_SUPPLIES]; }; static const struct reg_default ak4458_reg_defaults[] = { @@ -128,6 +138,9 @@ static const char * const ak4458_ats_select_texts[] = { /* DIF2 bit Audio Interface Format Setting(BICK fs) */ static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",}; +static const char * const ak4497_dsd_input_path_select[] = { + "16_17_19pin", "3_4_5pin"}; + static const struct soc_enum ak4458_dac1_dem_enum = SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1, ARRAY_SIZE(ak4458_dem_select_texts), @@ -167,6 +180,10 @@ static const struct soc_enum ak4458_dif_enum = SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3, ARRAY_SIZE(ak4458_dif_select_texts), ak4458_dif_select_texts); +static const struct soc_enum ak4497_dsdp_enum = + SOC_ENUM_SINGLE(AK4458_09_DSD2, 2, + ARRAY_SIZE(ak4497_dsd_input_path_select), + ak4497_dsd_input_path_select); static int get_digfil(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -274,6 +291,7 @@ static const struct snd_kcontrol_new ak4497_snd_controls[] = { SOC_ENUM("AK4497 Sound Mode", ak4458_sm_enum), SOC_ENUM("AK4497 Attenuation transition Time Setting", ak4458_ats_enum), + SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dsdp_enum), }; /* ak4497 dapm widgets */ @@ -290,6 +308,20 @@ static const struct snd_soc_dapm_route ak4497_intercon[] = { }; +static int ak4458_get_tdm_mode(struct ak4458_priv *ak4458) +{ + switch (ak4458->slots * ak4458->slot_width) { + case 128: + return 1; + case 256: + return 2; + case 512: + return 3; + default: + return 0; + } +} + static int ak4458_rstn_control(struct snd_soc_component *component, int bit) { int ret; @@ -318,11 +350,60 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream, struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); int pcm_width = max(params_physical_width(params), ak4458->slot_width); int nfs1; - u8 format; + u8 format, dsdsel0, dsdsel1, dchn; + int ret, dsd_bclk, channels, channels_max; + bool is_dsd = false; + + channels = params_channels(params); + channels_max = dai->driver->playback.channels_max; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + is_dsd = true; + dsd_bclk = params_rate(params) * params_physical_width(params); + break; + } nfs1 = params_rate(params); ak4458->fs = nfs1; + if (is_dsd) { + switch (dsd_bclk) { + case 2822400: + dsdsel0 = 0; + dsdsel1 = 0; + break; + case 5644800: + dsdsel0 = 1; + dsdsel1 = 0; + break; + case 11289600: + dsdsel0 = 0; + dsdsel1 = 1; + break; + case 22579200: + if (ak4458->drvdata->dsd512) { + dsdsel0 = 1; + dsdsel1 = 1; + } else { + dev_err(dai->dev, "DSD512 not supported.\n"); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, AK4458_06_DSD1, + AK4458_DSDSEL_MASK, dsdsel0); + snd_soc_component_update_bits(component, AK4458_09_DSD2, + AK4458_DSDSEL_MASK, dsdsel1); + } + /* Master Clock Frequency Auto Setting Mode Enable */ snd_soc_component_update_bits(component, AK4458_00_CONTROL1, 0x80, 0x80); @@ -347,6 +428,9 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream, case SND_SOC_DAIFMT_DSP_B: format = AK4458_DIF_32BIT_MSB; break; + case SND_SOC_DAIFMT_PDM: + format = AK4458_DIF_32BIT_MSB; + break; default: return -EINVAL; } @@ -358,8 +442,24 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream, snd_soc_component_update_bits(component, AK4458_00_CONTROL1, AK4458_DIF_MASK, format); - ak4458_rstn_control(component, 0); - ak4458_rstn_control(component, 1); + /* + * Enable/disable Daisy Chain if in TDM mode and the number of played + * channels is bigger than the maximum supported number of channels + */ + dchn = ak4458_get_tdm_mode(ak4458) && + (ak4458->fmt == SND_SOC_DAIFMT_DSP_B) && + (channels > channels_max) ? AK4458_DCHAIN_MASK : 0; + + snd_soc_component_update_bits(component, AK4458_0B_CONTROL7, + AK4458_DCHAIN_MASK, dchn); + + ret = ak4458_rstn_control(component, 0); + if (ret) + return ret; + + ret = ak4458_rstn_control(component, 1); + if (ret) + return ret; return 0; } @@ -368,6 +468,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_component *component = dai->component; struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + u8 dp; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */ @@ -385,6 +486,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_PDM: ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; break; default: @@ -393,6 +495,11 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } + /* DSD mode */ + dp = ak4458->fmt == SND_SOC_DAIFMT_PDM ? AK4458_DP_MASK : 0; + snd_soc_component_update_bits(component, AK4458_02_CONTROL3, + AK4458_DP_MASK, dp); + ak4458_rstn_control(component, 0); ak4458_rstn_control(component, 1); @@ -440,31 +547,20 @@ static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, ak4458->slots = slots; ak4458->slot_width = slot_width; - switch (slots * slot_width) { - case 128: - mode = AK4458_MODE_TDM128; - break; - case 256: - mode = AK4458_MODE_TDM256; - break; - case 512: - mode = AK4458_MODE_TDM512; - break; - default: - mode = AK4458_MODE_NORMAL; - break; - } + mode = ak4458_get_tdm_mode(ak4458) << AK4458_MODE_SHIFT; snd_soc_component_update_bits(component, AK4458_0A_CONTROL6, AK4458_MODE_MASK, mode); - return 0; } #define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ SNDRV_PCM_FMTBIT_S24_LE |\ - SNDRV_PCM_FMTBIT_S32_LE) + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) static const unsigned int ak4458_rates[] = { 8000, 11025, 16000, 22050, @@ -649,11 +745,13 @@ static const struct regmap_config ak4458_regmap = { static const struct ak4458_drvdata ak4458_drvdata = { .dai_drv = &ak4458_dai, .comp_drv = &soc_codec_dev_ak4458, + .dsd512 = false, }; static const struct ak4458_drvdata ak4497_drvdata = { .dai_drv = &ak4497_dai, .comp_drv = &soc_codec_dev_ak4497, + .dsd512 = true, }; static const struct dev_pm_ops ak4458_pm = { @@ -665,8 +763,8 @@ static const struct dev_pm_ops ak4458_pm = { static int ak4458_i2c_probe(struct i2c_client *i2c) { struct ak4458_priv *ak4458; - const struct ak4458_drvdata *drvdata; int ret; + int i; ak4458 = devm_kzalloc(&i2c->dev, sizeof(*ak4458), GFP_KERNEL); if (!ak4458) @@ -679,7 +777,7 @@ static int ak4458_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, ak4458); ak4458->dev = &i2c->dev; - drvdata = of_device_get_match_data(&i2c->dev); + ak4458->drvdata = of_device_get_match_data(&i2c->dev); ak4458->reset_gpiod = devm_gpiod_get_optional(ak4458->dev, "reset", GPIOD_OUT_LOW); @@ -691,8 +789,25 @@ static int ak4458_i2c_probe(struct i2c_client *i2c) if (IS_ERR(ak4458->mute_gpiod)) return PTR_ERR(ak4458->mute_gpiod); - ret = devm_snd_soc_register_component(ak4458->dev, drvdata->comp_drv, - drvdata->dai_drv, 1); + for (i = 0; i < ARRAY_SIZE(ak4458->supplies); i++) + ak4458->supplies[i].supply = ak4458_supply_names[i]; + + ret = devm_regulator_bulk_get(ak4458->dev, ARRAY_SIZE(ak4458->supplies), + ak4458->supplies); + if (ret != 0) { + dev_err(ak4458->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak4458->supplies), + ak4458->supplies); + if (ret != 0) { + dev_err(ak4458->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(ak4458->dev, ak4458->drvdata->comp_drv, + ak4458->drvdata->dai_drv, 1); if (ret < 0) { dev_err(ak4458->dev, "Failed to register CODEC: %d\n", ret); return ret; diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h index f906215f7e4e..f4cf00720dd7 100644 --- a/sound/soc/codecs/ak4458.h +++ b/sound/soc/codecs/ak4458.h @@ -83,4 +83,9 @@ #define AK4458_ATS_SHIFT 6 #define AK4458_ATS_MASK GENMASK(7, 6) -#endif /* _AK4458_H */ +#define AK4458_DSDSEL_MASK (0x1 << 0) +#define AK4458_DP_MASK (0x1 << 7) + +#define AK4458_DCHAIN_MASK (0x1 << 1) + +#endif diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index 8179512129d3..3afac1838541 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -19,9 +19,21 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/tlv.h> +#include <linux/regulator/consumer.h> #include "ak5558.h" +#define AK5558_SLAVE_CKS_AUTO + +/* enable debug */ +/* #define AK5558_DEBUG */ + +#define AK5558_NUM_SUPPLIES 2 +static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + /* AK5558 Codec Private Data */ struct ak5558_priv { struct snd_soc_component component; @@ -30,6 +42,7 @@ struct ak5558_priv { struct gpio_desc *reset_gpiod; /* Reset & Power down GPIO */ int slots; int slot_width; + struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES]; }; /* ak5558 register cache & default register settings */ @@ -350,6 +363,7 @@ static int ak5558_i2c_probe(struct i2c_client *i2c) { struct ak5558_priv *ak5558; int ret = 0; + int i; ak5558 = devm_kzalloc(&i2c->dev, sizeof(*ak5558), GFP_KERNEL); if (!ak5558) @@ -367,6 +381,23 @@ static int ak5558_i2c_probe(struct i2c_client *i2c) if (IS_ERR(ak5558->reset_gpiod)) return PTR_ERR(ak5558->reset_gpiod); + for (i = 0; i < ARRAY_SIZE(ak5558->supplies); i++) + ak5558->supplies[i].supply = ak5558_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak5558->supplies), + ak5558->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak5558->supplies), + ak5558->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + ret = devm_snd_soc_register_component(&i2c->dev, &soc_codec_dev_ak5558, &ak5558_dai, 1); diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 94b1adb088fd..36e893a15c59 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -17,6 +17,7 @@ #include <linux/gpio/consumer.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> +#include <linux/pm_domain.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/tlv.h> @@ -43,7 +44,8 @@ struct cs42xx8_priv { struct regmap *regmap; struct clk *clk; - bool slave_mode; + bool slave_mode[2]; + bool is_tdm; unsigned long sysclk; u32 tx_channels; struct gpio_desc *gpiod_reset; @@ -131,7 +133,6 @@ static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = { SND_SOC_DAPM_INPUT("AIN2L"), SND_SOC_DAPM_INPUT("AIN2R"), - SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0), }; static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = { @@ -145,35 +146,28 @@ static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = { /* Playback */ { "AOUT1L", NULL, "DAC1" }, { "AOUT1R", NULL, "DAC1" }, - { "DAC1", NULL, "PWR" }, { "AOUT2L", NULL, "DAC2" }, { "AOUT2R", NULL, "DAC2" }, - { "DAC2", NULL, "PWR" }, { "AOUT3L", NULL, "DAC3" }, { "AOUT3R", NULL, "DAC3" }, - { "DAC3", NULL, "PWR" }, { "AOUT4L", NULL, "DAC4" }, { "AOUT4R", NULL, "DAC4" }, - { "DAC4", NULL, "PWR" }, /* Capture */ { "ADC1", NULL, "AIN1L" }, { "ADC1", NULL, "AIN1R" }, - { "ADC1", NULL, "PWR" }, { "ADC2", NULL, "AIN2L" }, { "ADC2", NULL, "AIN2R" }, - { "ADC2", NULL, "PWR" }, }; static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = { /* Capture */ { "ADC3", NULL, "AIN3L" }, { "ADC3", NULL, "AIN3R" }, - { "ADC3", NULL, "PWR" }, }; struct cs42xx8_ratios { @@ -218,6 +212,8 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); u32 val; + cs42xx8->is_tdm = false; + /* Set DAI format */ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_LEFT_J: @@ -231,6 +227,7 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, break; case SND_SOC_DAIFMT_DSP_A: val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + cs42xx8->is_tdm = true; break; default: dev_err(component->dev, "unsupported dai format\n"); @@ -241,17 +238,21 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, CS42XX8_INTF_DAC_DIF_MASK | CS42XX8_INTF_ADC_DIF_MASK, val); - /* Set master/slave audio interface */ - switch (format & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - cs42xx8->slave_mode = true; - break; - case SND_SOC_DAIFMT_CBM_CFM: - cs42xx8->slave_mode = false; - break; - default: - dev_err(component->dev, "unsupported master/slave mode\n"); - return -EINVAL; + if (cs42xx8->slave_mode[0] == cs42xx8->slave_mode[1]) { + /* Set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42xx8->slave_mode[0] = true; + cs42xx8->slave_mode[1] = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42xx8->slave_mode[0] = false; + cs42xx8->slave_mode[1] = false; + break; + default: + dev_err(component->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } } return 0; @@ -281,7 +282,7 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, /* Get functional mode for tx and rx according to rate */ for (i = 0; i < 2; i++) { - if (cs42xx8->slave_mode) { + if (cs42xx8->slave_mode[i]) { fm[i] = CS42XX8_FM_AUTO; } else { if (rate[i] < 50000) { @@ -336,6 +337,16 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, cs42xx8->rate[tx] = params_rate(params); + if (cs42xx8->is_tdm && !cs42xx8->slave_mode[tx]) { + dev_err(component->dev, "TDM mode is unsupported in master mode\n"); + return -EINVAL; + } + + if (cs42xx8->is_tdm && (cs42xx8->sysclk < 256 * cs42xx8->rate[tx])) { + dev_err(component->dev, "unsupported sysclk for TDM mode\n"); + return -EINVAL; + } + mask = CS42XX8_FUNCMOD_MFREQ_MASK; val = cs42xx8_ratios[i].mfreq; @@ -459,6 +470,8 @@ const struct regmap_config cs42xx8_regmap_config = { .volatile_reg = cs42xx8_volatile_register, .writeable_reg = cs42xx8_writeable_register, .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, }; EXPORT_SYMBOL_GPL(cs42xx8_regmap_config); @@ -482,7 +495,8 @@ static int cs42xx8_component_probe(struct snd_soc_component *component) /* Mute all DAC channels */ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL); - + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); return 0; } @@ -521,9 +535,11 @@ EXPORT_SYMBOL_GPL(cs42xx8_of_match); int cs42xx8_probe(struct device *dev, struct regmap *regmap) { + struct device_node *np = dev->of_node; const struct of_device_id *of_id; struct cs42xx8_priv *cs42xx8; int ret, val, i; + int num_domains = 0; if (IS_ERR(regmap)) { ret = PTR_ERR(regmap); @@ -563,6 +579,36 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap) cs42xx8->sysclk = clk_get_rate(cs42xx8->clk); + num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + for (i = 0; i < num_domains; i++) { + struct device *pd_dev; + struct device_link *link; + + pd_dev = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(pd_dev)) + return PTR_ERR(pd_dev); + + link = device_link_add(dev, pd_dev, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(link)) + return PTR_ERR(link); + } + + if (of_property_read_bool(np, "fsl,txm-rxs")) { + /* 0 -- rx, 1 -- tx */ + cs42xx8->slave_mode[0] = true; + cs42xx8->slave_mode[1] = false; + } + + if (of_property_read_bool(np, "fsl,txs-rxm")) { + /* 0 -- rx, 1 -- tx */ + cs42xx8->slave_mode[0] = false; + cs42xx8->slave_mode[1] = true; + } + for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++) cs42xx8->supplies[i].supply = cs42xx8_supply_names[i]; diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c new file mode 100644 index 000000000000..c82d889bfabb --- /dev/null +++ b/sound/soc/codecs/fsl_mqs.c @@ -0,0 +1,357 @@ +/* + * ALSA SoC IMX MQS driver + * + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/initval.h> + + +#define REG_MQS_CTRL 0x00 + +#define MQS_EN_MASK (0x1 << 28) +#define MQS_EN_SHIFT (28) +#define MQS_SW_RST_MASK (0x1 << 24) +#define MQS_SW_RST_SHIFT (24) +#define MQS_OVERSAMPLE_MASK (0x1 << 20) +#define MQS_OVERSAMPLE_SHIFT (20) +#define MQS_CLK_DIV_MASK (0xFF << 0) +#define MQS_CLK_DIV_SHIFT (0) + + +/* codec private data */ +struct fsl_mqs { + struct platform_device *pdev; + struct regmap *gpr; + unsigned int reg_iomuxc_gpr2; + + struct regmap *regmap; + unsigned int reg_mqs_ctrl; + + struct clk *mclk; + struct clk *ipg; + + unsigned long mclk_rate; + + int sysclk_rate; + int bclk; + int lrclk; + bool use_gpr; + char name[32]; +}; + +#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int fsl_mqs_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 fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + int div, res; + + mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk); + + mqs_priv->bclk = snd_soc_params_to_bclk(params); + mqs_priv->lrclk = params_rate(params); + + /* + * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate; + * if repeat_rate is 8, mqs can achieve better quality. + * oversample rate is fix to 32 currently. + */ + div = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8); + res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8); + + if (res == 0 && div > 0 && div <= 256) { + if (mqs_priv->use_gpr) { + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_CLK_DIV_MASK, + (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_OVERSAMPLE_MASK, + 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT); + } else { + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_CLK_DIV_MASK, + (div-1) << MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_OVERSAMPLE_MASK, + 0 << MQS_OVERSAMPLE_SHIFT); + } + } else + dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n"); + + return 0; +} + +static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + + mqs_priv->sysclk_rate = freq; + + return 0; +} + +static int fsl_mqs_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + + if (mqs_priv->use_gpr) + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK, + 1 << IMX6SX_GPR2_MQS_EN_SHIFT); + else + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_EN_MASK, + 1 << MQS_EN_SHIFT); + return 0; +} + +static void fsl_mqs_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + + if (mqs_priv->use_gpr) + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_EN_MASK, 0); + else + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_EN_MASK, 0); +} + +static struct snd_soc_component_driver soc_codec_fsl_mqs = { + .idle_bias_on = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct snd_soc_dai_ops fsl_mqs_dai_ops = { + .startup = fsl_mqs_startup, + .shutdown = fsl_mqs_shutdown, + .hw_params = fsl_mqs_hw_params, + .set_fmt = fsl_mqs_set_dai_fmt, + .set_sysclk = fsl_mqs_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver fsl_mqs_dai = { + .name = "fsl-mqs-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_MQS_RATES, + .formats = FSL_MQS_FORMATS, + }, + .ops = &fsl_mqs_dai_ops, +}; + +static const struct regmap_config fsl_mqs_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = REG_MQS_CTRL, + .cache_type = REGCACHE_NONE, +}; + +static int fsl_mqs_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *gpr_np = 0; + struct fsl_mqs *mqs_priv; + struct resource *res; + void __iomem *regs; + int ret = 0; + + mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL); + if (!mqs_priv) + return -ENOMEM; + + mqs_priv->pdev = pdev; + strncpy(mqs_priv->name, np->name, sizeof(mqs_priv->name) - 1); + + if (of_device_is_compatible(np, "fsl,imx8qm-mqs")) + mqs_priv->use_gpr = false; + else + mqs_priv->use_gpr = true; + + if (mqs_priv->use_gpr) { + gpr_np = of_parse_phandle(np, "gpr", 0); + if (IS_ERR(gpr_np)) { + dev_err(&pdev->dev, "failed to get gpr node by phandle\n"); + ret = PTR_ERR(gpr_np); + goto out; + } + + mqs_priv->gpr = syscon_node_to_regmap(gpr_np); + if (IS_ERR(mqs_priv->gpr)) { + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(mqs_priv->gpr); + goto out; + } + } else { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mqs_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_mqs_regmap_config); + if (IS_ERR(mqs_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap: %ld\n", + PTR_ERR(mqs_priv->regmap)); + return PTR_ERR(mqs_priv->regmap); + } + + mqs_priv->ipg = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(mqs_priv->ipg)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->ipg)); + goto out; + } + } + + mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mqs_priv->mclk)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->mclk)); + goto out; + } + + dev_set_drvdata(&pdev->dev, mqs_priv); + pm_runtime_enable(&pdev->dev); + + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_fsl_mqs, + &fsl_mqs_dai, 1); +out: + if (!IS_ERR(gpr_np)) + of_node_put(gpr_np); + + return ret; +} + +static int fsl_mqs_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_PM +static int fsl_mqs_runtime_resume(struct device *dev) +{ + struct fsl_mqs *mqs_priv = dev_get_drvdata(dev); + + if (mqs_priv->ipg) + clk_prepare_enable(mqs_priv->ipg); + + if (mqs_priv->mclk) + clk_prepare_enable(mqs_priv->mclk); + + if (mqs_priv->use_gpr) + regmap_write(mqs_priv->gpr, IOMUXC_GPR2, + mqs_priv->reg_iomuxc_gpr2); + else + regmap_write(mqs_priv->regmap, REG_MQS_CTRL, + mqs_priv->reg_mqs_ctrl); + return 0; +} + +static int fsl_mqs_runtime_suspend(struct device *dev) +{ + struct fsl_mqs *mqs_priv = dev_get_drvdata(dev); + + if (mqs_priv->use_gpr) + regmap_read(mqs_priv->gpr, IOMUXC_GPR2, + &mqs_priv->reg_iomuxc_gpr2); + else + regmap_read(mqs_priv->regmap, REG_MQS_CTRL, + &mqs_priv->reg_mqs_ctrl); + + if (mqs_priv->mclk) + clk_disable_unprepare(mqs_priv->mclk); + + if (mqs_priv->ipg) + clk_disable_unprepare(mqs_priv->ipg); + + return 0; +} +#endif + +static const struct dev_pm_ops fsl_mqs_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_mqs_runtime_suspend, + fsl_mqs_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +static const struct of_device_id fsl_mqs_dt_ids[] = { + { .compatible = "fsl,imx8qm-mqs", }, + { .compatible = "fsl,imx6sx-mqs", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids); + + +static struct platform_driver fsl_mqs_driver = { + .probe = fsl_mqs_probe, + .remove = fsl_mqs_remove, + .driver = { + .name = "fsl-mqs", + .of_match_table = fsl_mqs_dt_ids, + .pm = &fsl_mqs_pm_ops, + }, +}; + +module_platform_driver(fsl_mqs_driver); + +MODULE_AUTHOR("shengjiu wang <shengjiu.wang@freescale.com>"); +MODULE_DESCRIPTION("MQS dummy codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform: fsl-mqs"); diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c index 8d88db9c11a6..8924aeb6bed4 100644 --- a/sound/soc/codecs/si476x.c +++ b/sound/soc/codecs/si476x.c @@ -199,9 +199,28 @@ out: return err; } +static int si476x_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + + if (!si476x_core_is_powered_up(core)) + si476x_core_set_power_state(core, SI476X_POWER_UP_FULL); + return 0; +} + +static void si476x_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + + if (si476x_core_is_powered_up(core)) + si476x_core_set_power_state(core, SI476X_POWER_DOWN); +} + static const struct snd_soc_dai_ops si476x_dai_ops = { .hw_params = si476x_codec_hw_params, .set_fmt = si476x_codec_set_dai_fmt, + .startup = si476x_codec_startup, + .shutdown = si476x_codec_shutdown, }; static struct snd_soc_dai_driver si476x_dai = { diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c index 91e3d1570c45..60ef4c042e1e 100644 --- a/sound/soc/codecs/wm8524.c +++ b/sound/soc/codecs/wm8524.c @@ -61,6 +61,7 @@ static int wm8524_startup(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = substream->private_data; /* The set of sample rates that can be supported depends on the * MCLK supplied to the CODEC - enforce this. @@ -71,9 +72,10 @@ static int wm8524_startup(struct snd_pcm_substream *substream, return -EINVAL; } - snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, - &wm8524->rate_constraint); + if (!rtd->dai_link->be_hw_params_fixup) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8524->rate_constraint); gpiod_set_value_cansleep(wm8524->mute, 1); @@ -159,7 +161,8 @@ static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define WM8524_RATES SNDRV_PCM_RATE_8000_192000 -#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops wm8524_dai_ops = { .startup = wm8524_startup, diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 55112c1bba5e..adb82301ff20 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -608,10 +608,6 @@ static const int bclk_divs[] = { * - lrclk = sysclk / dac_divs * - 10 * bclk = sysclk / bclk_divs * - * If we cannot find an exact match for (sysclk, lrclk, bclk) - * triplet, we relax the bclk such that bclk is chosen as the - * closest available frequency greater than expected bclk. - * * @wm8960_priv: wm8960 codec private data * @mclk: MCLK used to derive sysclk * @sysclk_idx: sysclk_divs index for found sysclk @@ -629,7 +625,7 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, { int sysclk, bclk, lrclk; int i, j, k; - int diff, closest = mclk; + int diff; /* marker for no match */ *bclk_idx = -1; @@ -653,12 +649,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, *bclk_idx = k; break; } - if (diff > 0 && closest > diff) { - *sysclk_idx = i; - *dac_idx = j; - *bclk_idx = k; - closest = diff; - } } if (k != ARRAY_SIZE(bclk_divs)) break; @@ -676,10 +666,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, * - freq_out = sysclk * sysclk_divs * - 10 * sysclk = bclk * bclk_divs * - * If we cannot find an exact match for (sysclk, lrclk, bclk) - * triplet, we relax the bclk such that bclk is chosen as the - * closest available frequency greater than expected bclk. - * * @component: component structure * @freq_in: input frequency used to derive freq out via PLL * @sysclk_idx: sysclk_divs index for found sysclk @@ -697,12 +683,11 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, { struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); int sysclk, bclk, lrclk, freq_out; - int diff, closest, best_freq_out; + int diff, best_freq_out; int i, j, k; bclk = wm8960->bclk; lrclk = wm8960->lrclk; - closest = freq_in; best_freq_out = -EINVAL; *sysclk_idx = *dac_idx = *bclk_idx = -1; @@ -725,13 +710,6 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, *bclk_idx = k; return freq_out; } - if (diff > 0 && closest > diff) { - *sysclk_idx = i; - *dac_idx = j; - *bclk_idx = k; - closest = diff; - best_freq_out = freq_out; - } } } } @@ -860,8 +838,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, wm8960->is_stream_in_use[tx] = true; - if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON && - !wm8960->is_stream_in_use[!tx]) + if (!wm8960->is_stream_in_use[!tx]) return wm8960_configure_clocking(component); return 0; @@ -1120,11 +1097,6 @@ static bool is_pll_freq_available(unsigned int source, unsigned int target) target *= 4; Ndiv = target / source; - if (Ndiv < 6) { - source >>= 1; - Ndiv = target / source; - } - if ((Ndiv < 6) || (Ndiv > 12)) return false; @@ -1235,6 +1207,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, if (pll_id == WM8960_SYSCLK_AUTO) return 0; + if (is_pll_freq_available(freq_in, freq_out)) + return -EINVAL; + return wm8960_set_pll(component, freq_in, freq_out); } @@ -1398,6 +1373,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); struct wm8960_priv *wm8960; int ret; + int repeat_reset = 10; wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), GFP_KERNEL); @@ -1419,7 +1395,11 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, else if (i2c->dev.of_node) wm8960_set_pdata_from_of(i2c, &wm8960->pdata); - ret = wm8960_reset(wm8960->regmap); + do { + ret = wm8960_reset(wm8960->regmap); + repeat_reset--; + } while (repeat_reset > 0 && ret != 0); + if (ret != 0) { dev_err(&i2c->dev, "Failed to issue reset\n"); return ret; diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 3e5c69fbc33a..0b2f84f5cec9 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3,6 +3,7 @@ * wm8962.c -- WM8962 ALSA SoC Audio driver * * Copyright 2010-2 Wolfson Microelectronics plc + * Copyright 2017 NXP * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> */ @@ -82,6 +83,7 @@ struct wm8962_priv { #endif int irq; + u32 cache_clocking2_reg; }; /* We can't use the same notifier block for more than one supply and @@ -1777,8 +1779,11 @@ SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30), SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT, WM8962_ALCR_ENA_SHIFT, 1, 0), -SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4, +SND_SOC_BYTES_MASK("ALC1", WM8962_ALC1, 1, WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK), +SND_SOC_BYTES("ALC2", WM8962_ALC2, 1), +SND_SOC_BYTES("ALC3", WM8962_ALC3, 1), +SND_SOC_BYTES("Noise Gate", WM8962_NOISE_GATE, 1), }; static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2554,11 +2559,17 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + snd_pcm_format_t sample_format = params_format(params); int i; int aif0 = 0; int adctl3 = 0; - wm8962->bclk = snd_soc_params_to_bclk(params); + if (sample_format == SNDRV_PCM_FORMAT_S20_3LE) + wm8962->bclk = params_rate(params) * + params_channels(params) * + params_physical_width(params); + else + wm8962->bclk = snd_soc_params_to_bclk(params); if (params_channels(params) == 1) wm8962->bclk *= 2; @@ -2788,7 +2799,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, if (target % Fref == 0) { fll_div->theta = 0; - fll_div->lambda = 0; + fll_div->lambda = 1; } else { gcd_fll = gcd(target, fratio * Fref); @@ -2858,7 +2869,7 @@ static int wm8962_set_fll(struct snd_soc_component *component, int fll_id, int s return -EINVAL; } - if (fll_div.theta || fll_div.lambda) + if (fll_div.theta) fll1 |= WM8962_FLL_FRAC; /* Stop the FLL while we reconfigure */ @@ -3813,6 +3824,10 @@ static int wm8962_runtime_resume(struct device *dev) regcache_sync(wm8962->regmap); + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_SYSCLK_SRC_MASK, + wm8962->cache_clocking2_reg); + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA); @@ -3842,6 +3857,9 @@ static int wm8962_runtime_suspend(struct device *dev) WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, 0); + regmap_read(wm8962->regmap, WM8962_CLOCKING2, + &wm8962->cache_clocking2_reg); + regcache_cache_only(wm8962->regmap, true); regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), @@ -3854,6 +3872,7 @@ static int wm8962_runtime_suspend(struct device *dev) #endif static const struct dev_pm_ops wm8962_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL) }; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 69401a3c7076..bf044d45fef3 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -74,6 +74,25 @@ config SND_SOC_FSL_MICFIL Say Y if you want to add Pulse Density Modulation microphone interface (MICFIL) support for NXP. +config SND_SOC_FSL_EASRC + tristate "Enhanced ASRC module support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Enhanced ASRC support for NXP. The ASRC is + a digital module that converts audio from a source sample rate to a + destination sample rate. It is a new design module compare with the + old ASRC. + +config SND_SOC_FSL_DSP + tristate "dsp module support" + select SND_SOC_COMPRESS + help + Say Y if you want to add hifi 4 support for the Freescale CPUs. + which is a DSP core for audio processing. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + config SND_SOC_FSL_UTILS tristate @@ -101,7 +120,7 @@ config SND_POWERPC_SOC config SND_IMX_SOC tristate "SoC Audio for Freescale i.MX CPUs" - depends on ARCH_MXC || COMPILE_TEST + depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST help Say Y or M if you want to add support for codecs attached to the i.MX CPUs. @@ -257,6 +276,135 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface +config SND_SOC_IMX_AK4458 + tristate "SoC Audio support for i.MX boards with AK4458" + depends on OF && I2C + select SND_SOC_AK4458_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK4458 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK4458 DAC. + +config SND_SOC_IMX_AK5558 + tristate "SoC Audio support for i.MX boards with AK5558" + depends on OF && I2C + select SND_SOC_AK5558 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK5558 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK5558 ADC. + +config SND_SOC_IMX_AK4497 + tristate "SoC Audio support for i.MX boards with AK4497" + depends on OF && I2C + select SND_SOC_AK4458 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK4497 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK4497 DAC. + +config SND_SOC_IMX_WM8960 + tristate "SoC Audio support for i.MX boards with wm8960" + depends on OF && I2C + select SND_SOC_WM8960 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8960 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8960 codec. + +config SND_SOC_IMX_WM8524 + tristate "SoC Audio support for i.MX boards with wm8524" + depends on OF && I2C + select SND_SOC_WM8524 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8524 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8524 codec. + +config SND_SOC_IMX_SII902X + tristate "SoC Audio support for i.MX boards with sii902x" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with SII902X + Say Y if you want to add support for SoC audio on an i.MX board with + a sii902x. + +config SND_SOC_IMX_WM8958 + tristate "SoC Audio support for i.MX boards with wm8958" + depends on OF && I2C + select MFD_WM8994 + select SND_SOC_WM8994 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8958 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8958 codec. + +config SND_SOC_IMX_CS42888 + tristate "SoC Audio support for i.MX boards with cs42888" + depends on OF && I2C + select SND_SOC_CS42XX8_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_ESAI + select SND_SOC_FSL_ASRC + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with cs42888 + Say Y if you want to add support for SoC audio on an i.MX board with + a cs42888 codec. + +config SND_SOC_IMX_WM8962 + tristate "SoC Audio support for i.MX boards with wm8962" + depends on OF && I2C && INPUT + select SND_SOC_WM8962 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_KCTL_JACK + help + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8962 codec. + +config SND_SOC_IMX_WM8962_ANDROID + tristate "SoC Audio support for i.MX boards with wm8962 in android" + depends on SND_SOC_IMX_WM8962=y + help + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8962 codec in android. + +config SND_SOC_IMX_MICFIL + tristate "SoC Audio support for i.MX boards with micfil" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_MICFIL + help + Soc Audio support for i.MX boards with micfil + Say Y if you want to add support for SoC audio on + an i.MX board with micfil. + config SND_SOC_IMX_ES8328 tristate "SoC Audio support for i.MX boards with the ES8328 codec" depends on OF && (I2C || SPI) @@ -280,6 +428,14 @@ config SND_SOC_IMX_SGTL5000 Say Y if you want to add support for SoC audio on an i.MX board with a sgtl5000 codec. +config SND_SOC_IMX_MQS + tristate "SoC Audio support for i.MX boards with MQS" + depends on OF + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_MQS + select SND_SOC_FSL_UTILS + config SND_SOC_IMX_SPDIF tristate "SoC Audio support for i.MX boards with S/PDIF" select SND_SOC_IMX_PCM_DMA @@ -310,7 +466,7 @@ config SND_SOC_FSL_ASOC_CARD help ALSA SoC Audio support with ASRC feature for Freescale SoCs that have ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888, - CS4271, CS4272 and SGTL5000. + CS4271, CS4272, and SGTL5000. Say Y if you want to add support for Freescale Generic ASoC Sound Card. config SND_SOC_IMX_AUDMIX @@ -322,6 +478,37 @@ config SND_SOC_IMX_AUDMIX Say Y if you want to add support for SoC audio on an i.MX board with an Audio Mixer. +config SND_SOC_IMX_PDM_MIC + tristate "SoC Audio support for i.MX boards with PDM mic on SAI" + depends on OF + select SND_SOC_IMX_PDM_DMA + select SND_SOC_FSL_SAI + help + SoC Audio support for i.MX boards with PDM microphones on SAI + Say Y if you want to add support for SoC Audio support for i.MX boards + with PDM microphones on SAI. + +config SND_SOC_IMX_DSP + tristate "SoC Audio support for i.MX boards with DSP port" + select SND_SOC_FSL_DSP + select SND_SOC_COMPRESS + help + SoC Audio support for i.MX boards with DSP audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX DSP. + +config SND_SOC_IMX_SI476X + tristate "SoC Audio support for i.MX boards with si476x" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_SI476X + help + SoC Audio support for i.MX boards with SI476x + Say Y if you want to add support for Soc audio for the AMFM Tuner chip + SI476x module. + config SND_SOC_IMX_HDMI tristate "SoC Audio support for i.MX boards with HDMI port" depends on MFD_MXC_HDMI diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 6e9d1eff9341..c989f8b74d5d 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -13,8 +13,10 @@ 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-dsp-objs := fsl_dsp.o fsl_dsp_proxy.o fsl_dsp_pool.o \ + fsl_dsp_library_load.o fsl_dsp_xaf_api.o fsl_dsp_cpu.o \ + fsl_dsp_platform_compress.o snd-soc-fsl-sai-objs := fsl_sai.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o @@ -23,11 +25,14 @@ 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 +snd-soc-fsl-easrc-objs := fsl_easrc.o fsl_easrc_dma.o obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o +snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o snd-soc-fsl-hdmi-objs := fsl_hdmi.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o +obj-$(CONFIG_SND_SOC_FSL_DSP) += snd-soc-fsl-dsp.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o @@ -36,6 +41,8 @@ obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o obj-$(CONFIG_SND_SOC_FSL_HDMI) += snd-soc-fsl-hdmi.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o +obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o @@ -53,6 +60,7 @@ obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o +obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o @@ -62,10 +70,24 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o snd-soc-imx-es8328-objs := imx-es8328.o +snd-soc-imx-cs42888-objs := imx-cs42888.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-wm8958-objs := imx-wm8958.o +snd-soc-imx-wm8960-objs := imx-wm8960.o +snd-soc-imx-wm8524-objs := imx-wm8524.o +snd-soc-imx-wm8962-objs := imx-wm8962.o +snd-soc-imx-sii902x-objs := imx-sii902x.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 +snd-soc-imx-mqs-objs := imx-mqs.o +snd-soc-imx-pdm-objs := imx-pdm.o +snd-soc-imx-ak4458-objs := imx-ak4458.o +snd-soc-imx-ak5558-objs := imx-ak5558.o +snd-soc-imx-ak4497-objs := imx-ak4497.o +snd-soc-imx-micfil-objs := imx-micfil.o +snd-soc-imx-dsp-objs := imx-dsp.o +snd-soc-imx-si476x-objs := imx-si476x.o snd-soc-imx-hdmi-objs := imx-hdmi.o snd-soc-imx-cdnhdmi-objs := imx-cdnhdmi.o @@ -74,10 +96,24 @@ obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o +obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o +obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o +obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o +obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o +obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.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 +obj-$(CONFIG_SND_SOC_IMX_MQS) += snd-soc-imx-mqs.o +obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o +obj-$(CONFIG_SND_SOC_IMX_AK4458) += snd-soc-imx-ak4458.o +obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o +obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o +obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o +obj-$(CONFIG_SND_SOC_IMX_DSP) += snd-soc-imx-dsp.o +obj-$(CONFIG_SND_SOC_IMX_SI476X) += snd-soc-imx-si476x.o obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o obj-$(CONFIG_SND_SOC_IMX_CDNHDMI) += snd-soc-imx-cdnhdmi.o diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index cfa40ef6b1ca..34f4cda6fd0f 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -2,7 +2,8 @@ // // Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver // -// Copyright (C) 2014 Freescale Semiconductor, Inc. +// Copyright (C) 2014-2016 Freescale Semiconductor, Inc. +// Copyright 2017 NXP // // Author: Nicolin Chen <nicoleotsuka@gmail.com> @@ -13,6 +14,9 @@ #include <linux/of_platform.h> #include <linux/platform_data/dma-imx.h> #include <linux/pm_runtime.h> +#include <linux/miscdevice.h> +#include <linux/sched/signal.h> +#include <linux/pm_domain.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -23,6 +27,9 @@ #define pair_err(fmt, ...) \ dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) +#define pair_warn(fmt, ...) \ + dev_warn(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + #define pair_dbg(fmt, ...) \ dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) @@ -41,26 +48,58 @@ static struct snd_pcm_hw_constraint_list fsl_asrc_rate_constraints = { * The following tables map the relationship between asrc_inclk/asrc_outclk in * fsl_asrc.h and the registers of ASRCSR */ +#define CLK_MAP_NUM 48 static unsigned char input_clk_map_imx35[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, }; static unsigned char output_clk_map_imx35[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, }; /* i.MX53 uses the same map for input and output */ static unsigned char input_clk_map_imx53[] = { /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, }; static unsigned char output_clk_map_imx53[] = { /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, }; -static unsigned char *clk_map[2]; +/* i.MX8 uses the same map for input and output */ +static unsigned char input_clk_map_imx8_0[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char output_clk_map_imx8_0[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char input_clk_map_imx8_1[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char output_clk_map_imx8_1[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; /** * Select the pre-processing and post-processing options @@ -115,7 +154,7 @@ static void fsl_asrc_sel_proc(int inrate, int outrate, * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A * while pair A and pair C are comparatively independent. */ -static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) +int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) { enum asrc_pair_index index = ASRC_INVALID_PAIR; struct fsl_asrc *asrc_priv = pair->asrc_priv; @@ -158,7 +197,7 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) * * It clears the resource from asrc_priv and releases the occupied channels. */ -static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) +void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) { struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; @@ -260,17 +299,20 @@ static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair, * of struct asrc_config which includes in/output sample rate, width, channel * and clock settings. */ -static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) +static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool p2p_in, bool p2p_out) { struct asrc_config *config = pair->config; struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; u32 inrate, outrate, indiv, outdiv; - u32 clk_index[2], div[2]; + u32 clk_index[2], div[2], rem[2]; + u64 clk_rate; int in, out, channels; int pre_proc, post_proc; struct clk *clk; bool ideal; + enum asrc_word_width input_word_width; + enum asrc_word_width output_word_width; if (!config) { pair_err("invalid pair config\n"); @@ -283,9 +325,32 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) return -EINVAL; } - /* Validate output width */ - if (config->output_word_width == ASRC_WIDTH_8_BIT) { - pair_err("does not support 8bit width output\n"); + switch (snd_pcm_format_width(config->input_format)) { + case 8: + input_word_width = ASRC_WIDTH_8_BIT; + break; + case 16: + input_word_width = ASRC_WIDTH_16_BIT; + break; + case 24: + input_word_width = ASRC_WIDTH_24_BIT; + break; + default: + pair_err("does not support this input format, %d\n", + config->input_format); + return -EINVAL; + } + + switch (snd_pcm_format_width(config->output_format)) { + case 16: + output_word_width = ASRC_WIDTH_16_BIT; + break; + case 24: + output_word_width = ASRC_WIDTH_24_BIT; + break; + default: + pair_err("does not support this output format, %d\n", + config->output_format); return -EINVAL; } @@ -320,13 +385,14 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) } /* Validate input and output clock sources */ - clk_index[IN] = clk_map[IN][config->inclk]; - clk_index[OUT] = clk_map[OUT][config->outclk]; + clk_index[IN] = asrc_priv->clk_map[IN][config->inclk]; + clk_index[OUT] = asrc_priv->clk_map[OUT][config->outclk]; /* We only have output clock for ideal ratio mode */ clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]]; - - div[IN] = clk_get_rate(clk) / inrate; + clk_rate = clk_get_rate(clk); + rem[IN] = do_div(clk_rate, inrate); + div[IN] = (u32)clk_rate; if (div[IN] == 0) { pair_err("failed to support input sample rate %dHz by asrck_%x\n", inrate, clk_index[ideal ? OUT : IN]); @@ -335,11 +401,20 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) clk = asrc_priv->asrck_clk[clk_index[OUT]]; - /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */ - if (ideal) - div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; - else - div[OUT] = clk_get_rate(clk) / outrate; + /* + * When P2P mode, output rate should align with the out samplerate. + * if set too high output rate, there will be lots of Overload. + * When M2M mode, output rate should also need to align with the out + * samplerate, but M2M must use less time to achieve good performance. + */ + clk_rate = clk_get_rate(clk); + if (p2p_out || p2p_in || (!ideal)) { + rem[OUT] = do_div(clk_rate, outrate); + div[OUT] = clk_rate; + } else { + rem[OUT] = do_div(clk_rate, IDEAL_RATIO_RATE); + div[OUT] = clk_rate; + } if (div[OUT] == 0) { pair_err("failed to support output sample rate %dHz by asrck_%x\n", @@ -347,6 +422,23 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) return -EINVAL; } + if (!ideal && (div[IN] > 1024 || div[OUT] > 1024 || + rem[IN] != 0 || rem[OUT] != 0)) { + pair_err("The divider can't be used for non ideal mode\n"); + return -EINVAL; + } + + if (ideal && div[IN] > 1024 && div[OUT] > 1024) { + pair_warn("both divider (%d, %d) are larger than threshold\n", + div[IN], div[OUT]); + } + + if (div[IN] > 1024) + div[IN] = 1024; + + if (div[OUT] > 1024) + div[OUT] = 1024; + /* Set the channel number */ channels = config->channel_num; @@ -361,8 +453,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) /* Default setting: Automatic selection for processing mode */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + + /* Default setting: use internal measured ratio */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_USRi_MASK(index), 0); + ASRCTR_USRi_MASK(index) | ASRCTR_IDRi_MASK(index), + ASRCTR_USR(index)); /* Set the input and output clock sources */ regmap_update_bits(asrc_priv->regmap, REG_ASRCSR, @@ -383,8 +478,8 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) /* Implement word_width configurations */ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index), ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK, - ASRMCR1i_OW16(config->output_word_width) | - ASRMCR1i_IWD(config->input_word_width)); + ASRMCR1i_OW16(output_word_width) | + ASRMCR1i_IWD(input_word_width)); /* Enable BUFFER STALL */ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), @@ -427,7 +522,7 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) { struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; - int reg, retry = 10, i; + int reg, retry = 50, i; /* Enable the current pair */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, @@ -440,6 +535,9 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) reg &= ASRCFG_INIRQi_MASK(index); } while (!reg && --retry); + if (retry == 0) + pair_warn("initialization is not finished\n"); + /* Make the input fifo to ASRC STALL level */ regmap_read(asrc_priv->regmap, REG_ASRCNCR, ®); for (i = 0; i < pair->channels * 4; i++) @@ -492,18 +590,73 @@ static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_RATE, &fsl_asrc_rate_constraints); } +static int fsl_asrc_select_clk(struct fsl_asrc *asrc_priv, + struct fsl_asrc_pair *pair, + int in_rate, + int out_rate) +{ + struct asrc_config *config = pair->config; + int clk_rate; + int clk_index; + int i = 0, j = 0; + int rate[2]; + int select_clk[2]; + bool clk_sel[2]; + + rate[0] = in_rate; + rate[1] = out_rate; + + /*select proper clock for asrc p2p mode*/ + for (j = 0; j < 2; j++) { + for (i = 0; i < CLK_MAP_NUM; i++) { + clk_index = asrc_priv->clk_map[j][i]; + clk_rate = clk_get_rate(asrc_priv->asrck_clk[clk_index]); + if (clk_rate != 0 && (clk_rate / rate[j]) <= 1024 && + (clk_rate % rate[j]) == 0) + break; + } + + if (i == CLK_MAP_NUM) { + select_clk[j] = OUTCLK_ASRCK1_CLK; + clk_sel[j] = false; + } else { + select_clk[j] = i; + clk_sel[j] = true; + } + } + + if (clk_sel[0] != true || clk_sel[1] != true) + select_clk[IN] = INCLK_NONE; + + config->inclk = select_clk[IN]; + config->outclk = select_clk[OUT]; + + /* + * FIXME: workaroud for 176400/192000 with 8 channel input case + * the output sample rate is 48kHz. + * with ideal ratio mode, the asrc seems has performance issue + * that the output sound is not correct. so switch to non-ideal + * ratio mode + */ + if (config->channel_num >= 8 && config->input_sample_rate >= 176400 + && config->inclk == INCLK_NONE) + config->inclk = INCLK_ASRCK1_CLK; + + return 0; +} + static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); - int width = params_width(params); struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; unsigned int channels = params_channels(params); unsigned int rate = params_rate(params); struct asrc_config config; - int word_width, ret; + snd_pcm_format_t format; + int ret; ret = fsl_asrc_request_pair(channels, pair); if (ret) { @@ -511,39 +664,56 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, return ret; } + pair->pair_streams |= BIT(substream->stream); pair->config = &config; - if (width == 16) - width = ASRC_WIDTH_16_BIT; - else - width = ASRC_WIDTH_24_BIT; - if (asrc_priv->asrc_width == 16) - word_width = ASRC_WIDTH_16_BIT; + format = SNDRV_PCM_FORMAT_S16_LE; else - word_width = ASRC_WIDTH_24_BIT; + format = SNDRV_PCM_FORMAT_S24_LE; config.pair = pair->index; config.channel_num = channels; - config.inclk = INCLK_NONE; - config.outclk = OUTCLK_ASRCK1_CLK; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - config.input_word_width = width; - config.output_word_width = word_width; + config.input_format = params_format(params); + config.output_format = format; config.input_sample_rate = rate; config.output_sample_rate = asrc_priv->asrc_rate; + + ret = fsl_asrc_select_clk(asrc_priv, pair, + config.input_sample_rate, + config.output_sample_rate); + if (ret) { + dev_err(dai->dev, "fail to select clock\n"); + return ret; + } + + ret = fsl_asrc_config_pair(pair, false, true); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } + } else { - config.input_word_width = word_width; - config.output_word_width = width; + config.input_format = format; + config.output_format = params_format(params); config.input_sample_rate = asrc_priv->asrc_rate; config.output_sample_rate = rate; - } - ret = fsl_asrc_config_pair(pair); - if (ret) { - dev_err(dai->dev, "fail to config asrc pair\n"); - return ret; + ret = fsl_asrc_select_clk(asrc_priv, pair, + config.input_sample_rate, + config.output_sample_rate); + if (ret) { + dev_err(dai->dev, "fail to select clock\n"); + return ret; + } + + ret = fsl_asrc_config_pair(pair, true, false); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } } return 0; @@ -555,8 +725,10 @@ static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - if (pair) + if (pair && (pair->pair_streams & BIT(substream->stream))) { fsl_asrc_release_pair(pair); + pair->pair_streams &= ~BIT(substream->stream); + } return 0; } @@ -572,6 +744,8 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: fsl_asrc_start_pair(pair); + /* Output enough data to content the DMA burstsize of BE */ + mdelay(1); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -602,9 +776,13 @@ static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) return 0; } -#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ +#define FSL_ASRC_FORMATS_RX (SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S20_3LE) + SNDRV_PCM_FMTBIT_S24_3LE) +#define FSL_ASRC_FORMATS_TX (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S24_3LE) static struct snd_soc_dai_driver fsl_asrc_dai = { .probe = fsl_asrc_dai_probe, @@ -615,7 +793,7 @@ static struct snd_soc_dai_driver fsl_asrc_dai = { .rate_min = 5512, .rate_max = 192000, .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + .formats = FSL_ASRC_FORMATS_TX, }, .capture = { .stream_name = "ASRC-Capture", @@ -624,7 +802,7 @@ static struct snd_soc_dai_driver fsl_asrc_dai = { .rate_min = 5512, .rate_max = 192000, .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + .formats = FSL_ASRC_FORMATS_RX, }, .ops = &fsl_asrc_dai_ops, }; @@ -772,11 +950,15 @@ static const struct regmap_config fsl_asrc_regmap_config = { .cache_type = REGCACHE_FLAT, }; +#include "fsl_asrc_m2m.c" + /** * Initialize ASRC registers with a default configurations */ static int fsl_asrc_init(struct fsl_asrc *asrc_priv) { + unsigned long ipg_rate; + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN); @@ -794,11 +976,12 @@ static int fsl_asrc_init(struct fsl_asrc *asrc_priv) regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1, ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); - /* Set the processing clock for 76KHz to 133M */ - regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6); - - /* Set the processing clock for 56KHz to 133M */ - return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947); + ipg_rate = clk_get_rate(asrc_priv->ipg_clk); + /* Set the period of the 76KHz and 56KHz sampling clocks based on + * the ASRC processing clock. + */ + regmap_write(asrc_priv->regmap, REG_ASR76K, ipg_rate / 76000); + return regmap_write(asrc_priv->regmap, REG_ASR56K, ipg_rate / 56000); } /** @@ -862,6 +1045,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) void __iomem *regs; int irq, ret, i; char tmp[16]; + int num_domains = 0; asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL); if (!asrc_priv) @@ -920,14 +1104,52 @@ static int fsl_asrc_probe(struct platform_device *pdev) } } + num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + for (i = 0; i < num_domains; i++) { + struct device *pd_dev; + struct device_link *link; + + pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i); + if (IS_ERR(pd_dev)) + return PTR_ERR(pd_dev); + + link = device_link_add(&pdev->dev, pd_dev, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(link)) + return PTR_ERR(link); + } + if (of_device_is_compatible(np, "fsl,imx35-asrc")) { asrc_priv->channel_bits = 3; - clk_map[IN] = input_clk_map_imx35; - clk_map[OUT] = output_clk_map_imx35; - } else { + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx35; + asrc_priv->clk_map[OUT] = output_clk_map_imx35; + asrc_priv->dma_type = DMA_SDMA; + } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) { + asrc_priv->channel_bits = 4; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx53; + asrc_priv->clk_map[OUT] = output_clk_map_imx53; + asrc_priv->dma_type = DMA_SDMA; + } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc0")) { asrc_priv->channel_bits = 4; - clk_map[IN] = input_clk_map_imx53; - clk_map[OUT] = output_clk_map_imx53; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx8_0; + asrc_priv->clk_map[OUT] = output_clk_map_imx8_0; + asrc_priv->dma_type = DMA_EDMA; + } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc1")) { + asrc_priv->channel_bits = 4; + strncpy(asrc_priv->name, "mxc_asrc1", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx8_1; + asrc_priv->clk_map[OUT] = output_clk_map_imx8_1; + asrc_priv->dma_type = DMA_EDMA; } ret = fsl_asrc_init(asrc_priv); @@ -961,6 +1183,8 @@ static int fsl_asrc_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); spin_lock_init(&asrc_priv->lock); + regcache_cache_only(asrc_priv->regmap, true); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, &fsl_asrc_dai, 1); if (ret) { @@ -968,6 +1192,12 @@ static int fsl_asrc_probe(struct platform_device *pdev) return ret; } + ret = fsl_asrc_m2m_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + return 0; } @@ -976,6 +1206,9 @@ static int fsl_asrc_runtime_resume(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); int i, ret; + u32 asrctr; + u32 reg; + int retry = 50; ret = clk_prepare_enable(asrc_priv->mem_clk); if (ret) @@ -994,6 +1227,34 @@ static int fsl_asrc_runtime_resume(struct device *dev) goto disable_asrck_clk; } + /* Stop all pairs provisionally */ + regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, 0); + + /* Restore all registers */ + regcache_cache_only(asrc_priv->regmap, false); + regcache_mark_dirty(asrc_priv->regmap); + regcache_sync(asrc_priv->regmap); + + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | + ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); + + /* Restart enabled pairs */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, asrctr); + + /* Wait for status of initialization */ + do { + udelay(5); + regmap_read(asrc_priv->regmap, REG_ASRCFG, ®); + reg = (reg >> ASRCFG_INIRQi_SHIFT(0)) & 0x7; + } while (!(reg == ((asrctr & 0xE) >> 1)) && --retry); + + if (retry == 0) + dev_warn(dev, "initialization is not finished\n"); + return 0; disable_asrck_clk: @@ -1013,6 +1274,11 @@ static int fsl_asrc_runtime_suspend(struct device *dev) struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); int i; + regmap_read(asrc_priv->regmap, REG_ASRCFG, + &asrc_priv->regcache_cfg); + + regcache_cache_only(asrc_priv->regmap, true); + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) clk_disable_unprepare(asrc_priv->asrck_clk[i]); if (!IS_ERR(asrc_priv->spba_clk)) @@ -1028,39 +1294,25 @@ static int fsl_asrc_runtime_suspend(struct device *dev) static int fsl_asrc_suspend(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int ret; - regmap_read(asrc_priv->regmap, REG_ASRCFG, - &asrc_priv->regcache_cfg); + fsl_asrc_m2m_suspend(asrc_priv); - regcache_cache_only(asrc_priv->regmap, true); - regcache_mark_dirty(asrc_priv->regmap); + ret = pm_runtime_force_suspend(dev); - return 0; + return ret; } static int fsl_asrc_resume(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); - u32 asrctr; + int ret; - /* Stop all pairs provisionally */ - regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); - regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_ASRCEi_ALL_MASK, 0); - - /* Restore all registers */ - regcache_cache_only(asrc_priv->regmap, false); - regcache_sync(asrc_priv->regmap); - - regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, - ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | - ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); + ret = pm_runtime_force_resume(dev); - /* Restart enabled pairs */ - regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_ASRCEi_ALL_MASK, asrctr); + fsl_asrc_m2m_resume(asrc_priv); - return 0; + return ret; } #endif /* CONFIG_PM_SLEEP */ @@ -1072,12 +1324,15 @@ static const struct dev_pm_ops fsl_asrc_pm = { static const struct of_device_id fsl_asrc_ids[] = { { .compatible = "fsl,imx35-asrc", }, { .compatible = "fsl,imx53-asrc", }, + { .compatible = "fsl,imx8qm-asrc0", }, + { .compatible = "fsl,imx8qm-asrc1", }, {} }; MODULE_DEVICE_TABLE(of, fsl_asrc_ids); static struct platform_driver fsl_asrc_driver = { .probe = fsl_asrc_probe, + .remove = fsl_asrc_m2m_remove, .driver = { .name = "fsl-asrc", .of_match_table = fsl_asrc_ids, diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h index c60075112570..c3c17769aa7e 100644 --- a/sound/soc/fsl/fsl_asrc.h +++ b/sound/soc/fsl/fsl_asrc.h @@ -2,7 +2,7 @@ /* * fsl_asrc.h - Freescale ASRC ALSA SoC header file * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. * * Author: Nicolin Chen <nicoleotsuka@gmail.com> */ @@ -10,6 +10,12 @@ #ifndef _FSL_ASRC_H #define _FSL_ASRC_H +#include <sound/asound.h> +#include <uapi/linux/mxc_asrc.h> +#include <linux/miscdevice.h> + +#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) + #define IN 0 #define OUT 1 @@ -20,7 +26,8 @@ #define ASRC_FIFO_THRESHOLD_MAX 63 #define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) #define ASRC_MAX_BUFFER_SIZE (1024 * 48) -#define ASRC_OUTPUT_LAST_SAMPLE 8 +#define ASRC_OUTPUT_LAST_SAMPLE_MAX 32 +#define ASRC_OUTPUT_LAST_SAMPLE 4 #define IDEAL_RATIO_RATE 1000000 @@ -283,106 +290,15 @@ #define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) #define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) - -enum asrc_pair_index { - ASRC_INVALID_PAIR = -1, - ASRC_PAIR_A = 0, - ASRC_PAIR_B = 1, - ASRC_PAIR_C = 2, -}; - -#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) - -enum asrc_inclk { - INCLK_NONE = 0x03, - INCLK_ESAI_RX = 0x00, - INCLK_SSI1_RX = 0x01, - INCLK_SSI2_RX = 0x02, - INCLK_SSI3_RX = 0x07, - INCLK_SPDIF_RX = 0x04, - INCLK_MLB_CLK = 0x05, - INCLK_PAD = 0x06, - INCLK_ESAI_TX = 0x08, - INCLK_SSI1_TX = 0x09, - INCLK_SSI2_TX = 0x0a, - INCLK_SSI3_TX = 0x0b, - INCLK_SPDIF_TX = 0x0c, - INCLK_ASRCK1_CLK = 0x0f, -}; - -enum asrc_outclk { - OUTCLK_NONE = 0x03, - OUTCLK_ESAI_TX = 0x00, - OUTCLK_SSI1_TX = 0x01, - OUTCLK_SSI2_TX = 0x02, - OUTCLK_SSI3_TX = 0x07, - OUTCLK_SPDIF_TX = 0x04, - OUTCLK_MLB_CLK = 0x05, - OUTCLK_PAD = 0x06, - OUTCLK_ESAI_RX = 0x08, - OUTCLK_SSI1_RX = 0x09, - OUTCLK_SSI2_RX = 0x0a, - OUTCLK_SSI3_RX = 0x0b, - OUTCLK_SPDIF_RX = 0x0c, - OUTCLK_ASRCK1_CLK = 0x0f, -}; - #define ASRC_CLK_MAX_NUM 16 enum asrc_word_width { ASRC_WIDTH_24_BIT = 0, ASRC_WIDTH_16_BIT = 1, - ASRC_WIDTH_8_BIT = 2, -}; - -struct asrc_config { - enum asrc_pair_index pair; - unsigned int channel_num; - unsigned int buffer_num; - unsigned int dma_buffer_size; - unsigned int input_sample_rate; - unsigned int output_sample_rate; - enum asrc_word_width input_word_width; - enum asrc_word_width output_word_width; - enum asrc_inclk inclk; - enum asrc_outclk outclk; -}; - -struct asrc_req { - unsigned int chn_num; - enum asrc_pair_index index; -}; - -struct asrc_querybuf { - unsigned int buffer_index; - unsigned int input_length; - unsigned int output_length; - unsigned long input_offset; - unsigned long output_offset; -}; - -struct asrc_convert_buffer { - void *input_buffer_vaddr; - void *output_buffer_vaddr; - unsigned int input_buffer_length; - unsigned int output_buffer_length; -}; - -struct asrc_status_flags { - enum asrc_pair_index index; - unsigned int overload_error; -}; - -enum asrc_error_status { - ASRC_TASK_Q_OVERLOAD = 0x01, - ASRC_OUTPUT_TASK_OVERLOAD = 0x02, - ASRC_INPUT_TASK_OVERLOAD = 0x04, - ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, - ASRC_INPUT_BUFFER_UNDERRUN = 0x10, + ASRC_WIDTH_8_BIT = 2, }; struct dma_block { - dma_addr_t dma_paddr; void *dma_vaddr; unsigned int length; }; @@ -413,6 +329,7 @@ struct fsl_asrc_pair { struct dma_chan *dma_chan[2]; struct imx_dma_data dma_data; unsigned int pos; + unsigned int pair_streams; void *private; }; @@ -433,6 +350,7 @@ struct fsl_asrc_pair { * @pair: pair pointers * @channel_bits: width of ASRCNCR register for each pair * @channel_avail: non-occupied channel numbers + * @pair_streams:indicat which substream is running * @asrc_rate: default sample rate for ASoC Back-Ends * @asrc_width: default sample width for ASoC Back-Ends * @regcache_cfg: store register value of REG_ASRCFG @@ -447,19 +365,29 @@ struct fsl_asrc { struct clk *ipg_clk; struct clk *spba_clk; struct clk *asrck_clk[ASRC_CLK_MAX_NUM]; + unsigned char *clk_map[2]; spinlock_t lock; struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM]; + struct miscdevice asrc_miscdev; unsigned int channel_bits; unsigned int channel_avail; int asrc_rate; int asrc_width; + int dma_type; /* 0 is sdma, 1 is edma */ u32 regcache_cfg; + char name[20]; }; +#define DMA_SDMA 0 +#define DMA_EDMA 1 + #define DRV_NAME "fsl-asrc-dai" extern struct snd_soc_component_driver fsl_asrc_component; struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir); +int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair); +void fsl_asrc_release_pair(struct fsl_asrc_pair *pair); + #endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 01052a0808b0..9ec527e03fb6 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -16,16 +16,14 @@ #define FSL_ASRC_DMABUF_SIZE (256 * 1024) -static const struct snd_pcm_hardware snd_imx_hardware = { +static struct snd_pcm_hardware snd_imx_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, + SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ + .period_bytes_max = 65532, /* Limited by SDMA engine */ .periods_min = 2, .periods_max = 255, .fifo_size = 0, @@ -148,6 +146,7 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, struct device *dev_be; u8 dir = tx ? OUT : IN; dma_cap_mask_t mask; + enum sdma_peripheral_type be_peripheral_type; int ret; /* Fetch the Back-End dma_data from DPCM */ @@ -201,19 +200,43 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, /* Get DMA request of Back-End */ tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); - tmp_data = tmp_chan->private; - pair->dma_data.dma_request = tmp_data->dma_request; - dma_release_channel(tmp_chan); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + pair->dma_data.dma_request = tmp_data->dma_request; + be_peripheral_type = tmp_data->peripheral_type; + if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + pair->dma_data.dst_dualfifo = true; + if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + pair->dma_data.src_dualfifo = true; + } + dma_release_channel(tmp_chan); + } /* Get DMA request of Front-End */ tmp_chan = fsl_asrc_get_dma_channel(pair, dir); - tmp_data = tmp_chan->private; - pair->dma_data.dma_request2 = tmp_data->dma_request; - pair->dma_data.peripheral_type = tmp_data->peripheral_type; - pair->dma_data.priority = tmp_data->priority; - dma_release_channel(tmp_chan); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + pair->dma_data.dma_request2 = tmp_data->dma_request; + pair->dma_data.peripheral_type = + tmp_data->peripheral_type; + pair->dma_data.priority = tmp_data->priority; + } + dma_release_channel(tmp_chan); + } + + /* For sdma DEV_TO_DEV, there is two dma request + * But for emda DEV_TO_DEV, there is only one dma request, which is + * from the BE. + */ + if (pair->dma_data.dma_request2 != pair->dma_data.dma_request) + pair->dma_chan[dir] = + dma_request_channel(mask, filter, &pair->dma_data); + else + pair->dma_chan[dir] = + fsl_asrc_get_dma_channel(pair, dir); - pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); if (!pair->dma_chan[dir]) { dev_err(dev, "failed to request DMA channel for Back-End\n"); return -EINVAL; @@ -224,6 +247,8 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, else buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + memset(&config_be, 0, sizeof(config_be)); + config_be.direction = DMA_DEV_TO_DEV; config_be.src_addr_width = buswidth; config_be.src_maxburst = dma_params_be->maxburst; @@ -276,6 +301,16 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) struct device *dev = component->dev; struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); struct fsl_asrc_pair *pair; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 dir = tx ? OUT : IN; + struct dma_slave_caps dma_caps; + struct dma_chan *tmp_chan; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); if (!pair) @@ -285,8 +320,78 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) runtime->private_data = pair; - snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "failed to set pcm hw params periods\n"); + return ret; + } + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + fsl_asrc_request_pair(1, pair); + + tmp_chan = fsl_asrc_get_dma_channel(pair, dir); + if (!tmp_chan) { + dev_err(dev, "can't get dma channel\n"); + return -EINVAL; + } + + ret = dma_get_slave_caps(tmp_chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + snd_imx_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + if (tmp_chan) + dma_release_channel(tmp_chan); + fsl_asrc_release_pair(pair); + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); return 0; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c new file mode 100644 index 000000000000..ea4f863caee1 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -0,0 +1,1046 @@ +/* + * Freescale ASRC Memory to Memory (M2M) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#define FSL_ASRC_INPUTFIFO_WML 0x4 +#define FSL_ASRC_OUTPUTFIFO_WML 0x2 + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +struct fsl_asrc_m2m { + struct fsl_asrc_pair *pair; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int pair_hold; + unsigned int asrc_active; + unsigned int sg_nodes[2]; + struct scatterlist sg[2][4]; + + snd_pcm_format_t word_format[2]; + unsigned int rate[2]; + unsigned int last_period_size; + u32 watermark[2]; + spinlock_t lock; +}; + +static void fsl_asrc_get_status(struct fsl_asrc_pair *pair, + struct asrc_status_flags *flags) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned long lock_flags; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + flags->overload_error = pair->error; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +#define ASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback) + +static void fsl_asrc_input_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[IN]); +} + +static void fsl_asrc_output_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[OUT]); +} + +static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 val; + + regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val); + + val &= ASRFSTi_OUTPUT_FIFO_MASK; + + return val >> ASRFSTi_OUTPUT_FIFO_SHIFT; +} + +static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct dma_block *output = &m2m->dma_block[OUT]; + u32 i, reg, size, t_size = 0, width; + u32 *reg32 = NULL; + u16 *reg16 = NULL; + u8 *reg24 = NULL; + + width = snd_pcm_format_physical_width(m2m->word_format[OUT]); + + if (width == 32) + reg32 = output->dma_vaddr + output->length; + else if (width == 16) + reg16 = output->dma_vaddr + output->length; + else + reg24 = output->dma_vaddr + output->length; + +retry: + size = fsl_asrc_get_output_FIFO_size(pair); + + for (i = 0; i < size * pair->channels; i++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + if (reg32) { + *(reg32) = reg; + reg32++; + } else if (reg16) { + *(reg16) = (u16)reg; + reg16++; + } else { + *reg24++ = (u8)reg; + *reg24++ = (u8)(reg >> 8); + *reg24++ = (u8)(reg >> 16); + } + } + t_size += size; + + if (size) + goto retry; + + if (t_size > m2m->last_period_size) + t_size = m2m->last_period_size; + + if (reg32) + output->length += t_size * pair->channels * 4; + else if (reg16) + output->length += t_size * pair->channels * 2; + else + output->length += t_size * pair->channels * 3; +} + +static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + enum asrc_pair_index index = pair->index; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) { + pair_err("failed to allocate input DMA buffer\n"); + return -ENOMEM; + } + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) { + pair_err("failed to allocate output DMA buffer\n"); + goto exit; + } + + return 0; + +exit: + kfree(input->dma_vaddr); + + return -ENOMEM; +} + +static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, snd_pcm_format_t word_format) +{ + struct dma_async_tx_descriptor *desc = pair->desc[dir]; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + unsigned int sg_nent = m2m->sg_nodes[dir]; + enum asrc_pair_index index = pair->index; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (snd_pcm_format_physical_width(word_format)) { + case 8: + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + break; + case 32: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + pair_err("invalid word width\n"); + return -EINVAL; + } + + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + if (asrc_priv->dma_type == DMA_SDMA) + slave_config.dst_maxburst = + m2m->watermark[IN] * pair->channels; + else + slave_config.dst_maxburst = 1; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + if (asrc_priv->dma_type == DMA_SDMA) + slave_config.src_maxburst = + m2m->watermark[OUT] * pair->channels; + else + slave_config.src_maxburst = 1; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + pair_err("failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + ASRC_MAX_BUFFER_SIZE); + + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + buf_len - ASRC_MAX_BUFFER_SIZE * i); + break; + default: + pair_err("invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(&asrc_priv->pdev->dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, DMA_PREP_INTERRUPT); + if (!desc) { + pair_err("failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + pair->desc[dir] = desc; + pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = ASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = pair; + + return 0; +} + +static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf, bool dir) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int *dma_len = &m2m->dma_block[dir].length; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + struct dma_chan *dma_chan = pair->dma_chan[dir]; + unsigned int buf_len, wm = m2m->watermark[dir]; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + unsigned int last_period_size = m2m->last_period_size; + enum asrc_pair_index index = pair->index; + u32 word_size, fifo_addr; + void __user *buf_vaddr; + + /* Clean the DMA buffer */ + memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE); + + if (dir == IN) { + buf_vaddr = (void __user *)pbuf->input_buffer_vaddr; + buf_len = pbuf->input_buffer_length; + } else { + buf_vaddr = (void __user *)pbuf->output_buffer_vaddr; + buf_len = pbuf->output_buffer_length; + } + + word_size = snd_pcm_format_physical_width(m2m->word_format[dir]) / 8; + + if (buf_len < word_size * pair->channels * wm || + buf_len > ASRC_DMA_BUFFER_SIZE || + (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) { + pair_err("%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + /* Copy origin data into input buffer */ + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + if (dir == OUT) { + *dma_len -= last_period_size * word_size * pair->channels; + *dma_len = *dma_len / (word_size * pair->channels) * + (word_size * pair->channels); + if (asrc_priv->dma_type == DMA_EDMA) + *dma_len = *dma_len / (word_size * pair->channels * m2m->watermark[OUT]) + * (word_size * pair->channels * m2m->watermark[OUT]); + } + + *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1; + + fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index); + + return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, m2m->word_format[dir]); +} + +static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int ret; + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN); + if (ret) { + pair_err("failed to prepare input buffer: %d\n", ret); + return ret; + } + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT); + if (ret) { + pair_err("failed to prepare output buffer: %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_asrc_process_buffer_pre(struct completion *complete, + enum asrc_pair_index index, bool dir) +{ + if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) { + pr_err("%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("%sput task forcibly aborted\n", DIR_STR(dir)); + return -ERESTARTSYS; + } + + return 0; +} + +#define mxc_asrc_dma_umap(dev, m2m) \ + do { \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV); \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM); \ + } while (0) + +int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN); + if (ret) { + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + return ret; + } + + /* ...then output task*/ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT); + if (ret) { + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + return ret; + } + + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + + /* Fetch the remaining data */ + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->pair_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + fsl_asrc_read_last_FIFO(pair); + + /* Update final lengths after getting last FIFO */ + pbuf->input_buffer_length = m2m->dma_block[IN].length; + pbuf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)pbuf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +#ifdef ASRC_POLLING_WITHOUT_DMA +/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */ +static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 *in24 = m2m->dma_block[IN].dma_vaddr; + u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4); + u32 *reg24 = m2m->dma_block[OUT].dma_vaddr; + u32 size, i, j, t_size, reg; + + t_size = 0; + + for (i = 0; i < dma_len; ) { + for (j = 0; j < 2; j++) { + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + i++; + } + udelay(50); + udelay(50 * m2m->rate[OUT] / m2m->rate[IN]); + + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + } + + mdelay(1); + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + + m2m->dma_block[OUT].length = t_size * pair->channels * 4; + + complete(&m2m->complete[OUT]); + complete(&m2m->complete[IN]); +} +#else +static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 size = fsl_asrc_get_output_FIFO_size(pair); + int i; + + /* Read all data in OUTPUT FIFO */ + while (size) { + u32 val; + for (i = 0; i < size * pair->channels; i++) + regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val); + /* Fetch the data every 100us */ + udelay(100); + + size = fsl_asrc_get_output_FIFO_size(pair); + } + + /* Submit DMA request */ + dmaengine_submit(pair->desc[IN]); + dma_async_issue_pending(pair->desc[IN]->chan); + + dmaengine_submit(pair->desc[OUT]); + dma_async_issue_pending(pair->desc[OUT]->chan); + + /* + * Clear DMA request during the stall state of ASRC: + * During STALL state, the remaining in input fifo would never be + * smaller than the input threshold while the output fifo would not + * be bigger than output one. Thus the DMA request would be cleared. + */ + fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN, + ASRC_FIFO_THRESHOLD_MAX); + + /* Update the real input threshold to raise DMA request */ + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); +} +#endif /* ASRC_POLLING_WITHOUT_DMA */ + +static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space: %ld\n", ret); + return ret; + } + + ret = fsl_asrc_request_pair(req.chn_num, pair); + if (ret) { + dev_err(dev, "failed to request pair: %ld\n", ret); + return ret; + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 1; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + pair->channels = req.chn_num; + + req.index = pair->index; + req.supported_in_format = FSL_ASRC_FORMATS_TX; + req.supported_out_format = FSL_ASRC_FORMATS_RX; + if (asrc_priv->dma_type == DMA_EDMA) { + req.supported_in_format &= ~SNDRV_PCM_FMTBIT_S24_3LE; + req.supported_out_format &= ~SNDRV_PCM_FMTBIT_S24_3LE; + } + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_config config; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %ld\n", ret); + return ret; + } + + index = config.pair; + + pair->config = &config; + ret = fsl_asrc_config_pair(pair, false, false); + if (ret) { + pair_err("failed to config pair: %ld\n", ret); + return ret; + } + + m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML; + m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML; + + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); + + m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE; + + m2m->word_format[IN] = config.input_format; + m2m->word_format[OUT] = config.output_format; + + m2m->rate[IN] = config.input_sample_rate; + m2m->rate[OUT] = config.output_sample_rate; + + m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE; + + ret = fsl_allocate_dma_buf(pair); + if (ret) { + pair_err("failed to allocate DMA buffer: %ld\n", ret); + return ret; + } + + /* Request DMA channel for both input and output */ + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + pair_err("failed to send config to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + /* index might be not valid due to some application failure. */ + if (index < 0) + return -EINVAL; + + m2m->asrc_active = 0; + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + fsl_asrc_release_pair(pair); + + return 0; +} + +static long fsl_asrc_calc_last_period_size(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = pair->channels; + unsigned int in_samples, out_samples; + unsigned int last_period_size; + unsigned int remain; + + in_width = snd_pcm_format_physical_width(m2m->word_format[IN]) / 8; + out_width = snd_pcm_format_physical_width(m2m->word_format[OUT]) / 8; + + in_samples = pbuf->input_buffer_length / (in_width * channels); + + out_samples = (m2m->rate[OUT] * in_samples / m2m->rate[IN]); + + out_length = out_samples * out_width * channels; + + last_period_size = pbuf->output_buffer_length / (out_width * channels) + - out_samples; + + m2m->last_period_size = last_period_size + 1 + ASRC_OUTPUT_LAST_SAMPLE; + + if (asrc_priv->dma_type == DMA_EDMA) { + remain = pbuf->output_buffer_length % (out_width * channels * m2m->watermark[OUT]); + if (remain) + m2m->last_period_size += remain / (out_width * channels); + } + + return 0; +} + +static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_convert_buffer buf; + long ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + pair_err("failed to get buf from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_calc_last_period_size(pair, &buf); + + ret = fsl_asrc_prepare_buffer(pair, &buf); + if (ret) { + pair_err("failed to prepare buffer: %ld\n", ret); + return ret; + } + + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + +#ifdef ASRC_POLLING_WITHOUT_DMA + fsl_asrc_polling_debug(pair); +#else + fsl_asrc_submit_dma(pair); +#endif + + ret = fsl_asrc_process_buffer(pair, &buf); + if (ret) { + if (ret != -ERESTARTSYS) + pair_err("failed to process buffer: %ld\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + pair_err("failed to send buf to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + m2m->asrc_active = 1; + fsl_asrc_start_pair(pair); + + return 0; +} + +static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + m2m->asrc_active = 0; + + return 0; +} + +static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_status_flags flags; + long ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + pair_err("failed to get flags from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_get_status(pair, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) { + pair_err("failed to send flags to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Release DMA and request again */ + dma_release_channel(pair->dma_chan[IN]); + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_asrc_ioctl_req_pair(pair, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_asrc_ioctl_config_pair(pair, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_asrc_ioctl_release_pair(pair, user); + break; + case ASRC_CONVERT: + ret = fsl_asrc_ioctl_convert(pair, user); + break; + case ASRC_START_CONV: + ret = fsl_asrc_ioctl_start_conv(pair, user); + break; + case ASRC_STOP_CONV: + ret = fsl_asrc_ioctl_stop_conv(pair, user); + break; + case ASRC_STATUS: + ret = fsl_asrc_ioctl_status(pair, user); + break; + case ASRC_FLUSH: + ret = fsl_asrc_ioctl_flush(pair, user); + break; + default: + dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n"); + break; + } + + return ret; +} + +static int fsl_asrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *asrc_miscdev = file->private_data; + struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev->parent); + struct device *dev = &asrc_priv->pdev->dev; + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL); + if (!m2m) { + dev_err(dev, "failed to allocate m2m resource\n"); + ret = -ENOMEM; + goto out; + } + + pair->private = m2m; + pair->asrc_priv = asrc_priv; + + spin_lock_init(&m2m->lock); + + file->private_data = pair; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(pair); + + return ret; +} + +static int fsl_asrc_close(struct inode *inode, struct file *file) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + + if (m2m->asrc_active) { + m2m->asrc_active = 0; + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + fsl_asrc_input_dma_callback((void *)pair); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->pair_hold) { + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_asrc_release_pair(pair); + } else + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + kfree(m2m); + kfree(pair); + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + file->private_data = NULL; + + pm_runtime_put_sync(dev); + + return 0; +} + +static const struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_asrc_ioctl, + .open = fsl_asrc_open, + .release = fsl_asrc_close, +}; + +static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv) +{ + struct device *dev = &asrc_priv->pdev->dev; + int ret; + + asrc_priv->asrc_miscdev.fops = &asrc_fops; + asrc_priv->asrc_miscdev.parent = dev; + asrc_priv->asrc_miscdev.name = asrc_priv->name; + asrc_priv->asrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&asrc_priv->asrc_miscdev); + if (ret) { + dev_err(dev, "failed to register char device %d\n", ret); + return ret; + } + + return 0; +} + +static int fsl_asrc_m2m_remove(struct platform_device *pdev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(&pdev->dev); + + misc_deregister(&asrc_priv->asrc_miscdev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + pair = asrc_priv->pair[i]; + if (!pair || !pair->private) { + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + continue; + } + m2m = pair->private; + + if (!completion_done(&m2m->complete[IN])) { + if (pair->dma_chan[IN]) + dmaengine_terminate_all(pair->dma_chan[IN]); + fsl_asrc_input_dma_callback((void *)pair); + } + if (!completion_done(&m2m->complete[OUT])) { + if (pair->dma_chan[OUT]) + dmaengine_terminate_all(pair->dma_chan[OUT]); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + } +} + +static void fsl_asrc_m2m_resume(struct fsl_asrc *asrc_priv) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + enum asrc_pair_index index; + int i, j; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + pair = asrc_priv->pair[i]; + if (!pair || !pair->private) { + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + continue; + } + m2m = pair->private; + index = pair->index; + + for (j = 0; j < pair->channels * 4; j++) + regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0); + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + } +} +#endif diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c index c7e4e9757dce..1525235a51d0 100644 --- a/sound/soc/fsl/fsl_audmix.c +++ b/sound/soc/fsl/fsl_audmix.c @@ -286,6 +286,7 @@ 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); + unsigned long lock_flags; /* Capture stream shall not be handled */ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) @@ -295,12 +296,16 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&priv->lock, lock_flags); priv->tdms |= BIT(dai->driver->id); + spin_unlock_irqrestore(&priv->lock, lock_flags); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&priv->lock, lock_flags); priv->tdms &= ~BIT(dai->driver->id); + spin_unlock_irqrestore(&priv->lock, lock_flags); break; default: return -EINVAL; diff --git a/sound/soc/fsl/fsl_audmix.h b/sound/soc/fsl/fsl_audmix.h index 7812ffec45c5..479f05695d53 100644 --- a/sound/soc/fsl/fsl_audmix.h +++ b/sound/soc/fsl/fsl_audmix.h @@ -96,6 +96,7 @@ struct fsl_audmix { struct platform_device *pdev; struct regmap *regmap; struct clk *ipg_clk; + spinlock_t lock; /* Protect tdms */ u8 tdms; }; diff --git a/sound/soc/fsl/fsl_dsp.c b/sound/soc/fsl/fsl_dsp.c new file mode 100644 index 000000000000..b3bcf21730d2 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.c @@ -0,0 +1,1176 @@ +/* + * Freescale DSP driver + * + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018 NXP + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Copyright (c) 2001 William L. Pitts + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/file.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/slab.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <linux/mx8_mu.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +#endif +#include <uapi/linux/mxc_dsp.h> +#include <linux/firmware/imx/svc/misc.h> +#include <dt-bindings/firmware/imx/rsrc.h> + +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "fsl_dsp.h" +#include "fsl_dsp_pool.h" +#include "fsl_dsp_xaf_api.h" + +/* ...allocate new client */ +struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv) +{ + struct xf_client *client; + u32 id; + + id = dsp_priv->xf_client_map[0].next; + + /* ...try to allocate a client handle */ + if (id != 0) { + /* ...allocate client memory */ + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + /* ...advance the head of free clients */ + dsp_priv->xf_client_map[0].next = + dsp_priv->xf_client_map[id].next; + + /* ...put associate client id with given object */ + dsp_priv->xf_client_map[id].client = client; + + /* ...mark client is not yet bound to proxy */ + client->proxy = NULL; + + /* ...save global proxy client identifier */ + client->id = id; + + return client; + } + + /* ...number of clients exceeded */ + return ERR_PTR(-EBUSY); +} + +/* ...recycle client object */ +static inline void xf_client_free(struct xf_client *client) +{ + int id = client->id; + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + + /* ...put proxy client id into free clients list */ + dsp_priv->xf_client_map[id].next = dsp_priv->xf_client_map[0].next; + dsp_priv->xf_client_map[0].next = id; + + /* ...destroy client data */ + kfree(client); +} + +/* ...lookup client basing on id */ +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id) +{ + if ((id >= XF_CFG_MAX_IPC_CLIENTS) || + (dsp_priv->xf_client_map[id].next < XF_CFG_MAX_IPC_CLIENTS) + ) + return NULL; + else + return dsp_priv->xf_client_map[id].client; +} + +/* ...helper function for retrieving the client handle */ +static inline struct xf_client *xf_get_client(struct file *file) +{ + struct xf_client *client; + u32 id; + + client = (struct xf_client *)file->private_data; + if (!client) + return ERR_PTR(-EINVAL); + + id = client->id; + if (id >= XF_CFG_MAX_IPC_CLIENTS) + return ERR_PTR(-EINVAL); + + return client; +} + +static int fsl_dsp_client_register(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + + dsp_priv = (struct fsl_dsp *)client->global; + dev = dsp_priv->dev; + + /* ...make sure client is not registered yet */ + if (client->proxy != NULL) { + pr_err("client-%x already registered", client->id); + return -EBUSY; + } + + /* ...complete association (no communication with remote proxy here) */ + client->proxy = &dsp_priv->proxy; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +/* ...unregister client from shared memory interface */ +static int fsl_dsp_client_unregister(struct xf_client *client) +{ + struct xf_proxy *proxy = client->proxy; + + /* ...make sure client is registered */ + if (proxy == NULL) { + pr_err("client-%x is not registered", client->id); + return -EBUSY; + } + + /* ...just clean proxy reference */ + client->proxy = NULL; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +static int fsl_dsp_ipc_msg_to_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_proxy_message msg; + void *buffer; + unsigned long ret = 0; + + ret = copy_from_user(&msg, user, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to get message from user space\n"); + return -EFAULT; + } + + /* ...make sure message pointer is sane */ + buffer = xf_proxy_a2b(&dsp_priv->proxy, msg.address); + if (buffer == (void *)-1) + return -EFAULT; + + /* ...put current proxy client into message session id */ + msg.session_id = XF_MSG_AP_FROM_USER(msg.session_id, client->id); + + xf_cmd_send(&dsp_priv->proxy, + msg.session_id, + msg.opcode, + buffer, + msg.length); + + return 0; +} + +static int fsl_dsp_ipc_msg_from_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_message *m; + struct xf_proxy_message msg; + unsigned long ret = 0; + + m = xf_cmd_recv(&dsp_priv->proxy, &client->wait, &client->queue, 0); + if (IS_ERR(m)) { + xf_unlock(&dsp_priv->proxy.lock); + dev_err(dev, "receiving failed: %d", (int)PTR_ERR(m)); + return PTR_ERR(m); + } + + /* ...check if there is a response available */ + if (m == NULL) + return -EAGAIN; + + /* ...prepare message parameters (lock is taken) */ + msg.session_id = XF_MSG_AP_TO_USER(m->id); + msg.opcode = m->opcode; + msg.length = m->length; + msg.address = xf_proxy_b2a(&dsp_priv->proxy, m->buffer); + msg.ret = m->ret; + + /* ...return the message back to a pool and release lock */ + xf_msg_free(&dsp_priv->proxy, m); + xf_unlock(&dsp_priv->proxy.lock); + + ret = copy_to_user(user, &msg, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return 0; +} + +static int fsl_dsp_get_shmem_info(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct shmem_info mem_info; + unsigned long ret = 0; + + mem_info.phys_addr = dsp_priv->scratch_buf_phys; + mem_info.size = dsp_priv->scratch_buf_size; + + ret = copy_to_user(user, &mem_info, sizeof(struct shmem_info)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return ret; +} + +static struct miscdevice dsp_miscdev = { + .name = "mxc_hifi4", + .minor = MISC_DYNAMIC_MINOR, +}; + +static long fsl_dsp_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct xf_client *client; + struct fsl_dsp *dsp_priv; + struct xf_proxy *proxy; + struct device *dev; + void __user *user; + long ret = 0; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + dsp_priv = (struct fsl_dsp *)client->global; + proxy = &dsp_priv->proxy; + dev = dsp_priv->dev; + user = (void __user *)arg; + + mutex_lock(&dsp_priv->dsp_mutex); + + if (!proxy->is_ready) { + mutex_unlock(&dsp_priv->dsp_mutex); + dev_err(dev, "dsp firmware is not ready\n"); + return -EFAULT; + } + + switch (cmd) { + case DSP_CLIENT_REGISTER: + ret = fsl_dsp_client_register(client); + break; + case DSP_CLIENT_UNREGISTER: + ret = fsl_dsp_client_unregister(client); + break; + case DSP_IPC_MSG_SEND: + ret = fsl_dsp_ipc_msg_to_dsp(client, user); + break; + case DSP_IPC_MSG_RECV: + ret = fsl_dsp_ipc_msg_from_dsp(client, user); + break; + case DSP_GET_SHMEM_INFO: + ret = fsl_dsp_get_shmem_info(client, user); + break; + default: + break; + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +void resource_release(struct fsl_dsp *dsp_priv) +{ + int i; + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); +} + +int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client) +{ + struct device *dev = dsp_priv->dev; + int ret = 0; + + /* ...initialize waiting queue */ + init_waitqueue_head(&client->wait); + + /* ...initialize client pending message queue */ + xf_msg_queue_init(&client->queue); + + /* ...mark user data is not mapped */ + client->vm_start = 0; + + /* ...reset mappings counter */ + atomic_set(&client->vm_use, 0); + + client->global = (void *)dsp_priv; + dsp_priv->proxy.is_active = 1; + + pm_runtime_get_sync(dev); + + mutex_lock(&dsp_priv->dsp_mutex); + /* increase reference counter when opening device */ + atomic_long_inc(&dsp_priv->refcnt); + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +static int fsl_dsp_open(struct inode *inode, struct file *file) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dsp_miscdev.parent); + struct xf_client *client; + int ret = 0; + + /* ...basic sanity checks */ + if (!inode || !file) + return -EINVAL; + + /* ...allocate new proxy client object */ + client = xf_client_alloc(dsp_priv); + if (IS_ERR(client)) + return PTR_ERR(client); + + fsl_dsp_open_func(dsp_priv, client); + + file->private_data = (void *)client; + + return ret; +} + +int fsl_dsp_close_func(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + struct xf_proxy *proxy; + + /* ...basic sanity checks */ + proxy = client->proxy; + + /* release all pending messages */ + if (proxy) + xf_msg_free_all(proxy, &client->queue); + + dsp_priv = (struct fsl_dsp *)client->global; + dev = dsp_priv->dev; + pm_runtime_put_sync(dev); + + /* ...recycle client id and release memory */ + xf_client_free(client); + + mutex_lock(&dsp_priv->dsp_mutex); + /* decrease reference counter when closing device */ + atomic_long_dec(&dsp_priv->refcnt); + /* If device is free, reinitialize the resource of + * dsp driver and framework + */ + if (atomic_long_read(&dsp_priv->refcnt) <= 0) { + /* we are closing up, wait for proxy processing + * function to finish */ + cancel_work_sync(&dsp_priv->proxy.work); + resource_release(dsp_priv); + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return 0; +} + +static int fsl_dsp_close(struct inode *inode, struct file *file) +{ + struct xf_client *client; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + fsl_dsp_close_func(client); + + return 0; +} + +/* ...wait until data is available in the response queue */ +static unsigned int fsl_dsp_poll(struct file *file, poll_table *wait) +{ + struct xf_proxy *proxy; + struct xf_client *client; + int mask; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...register client waiting queue */ + poll_wait(file, &client->wait, wait); + + /* ...return current queue state */ + mask = (xf_msg_queue_head(&client->queue) ? POLLIN | POLLRDNORM : 0); + + return mask; +} + +/******************************************************************************* + * Low-level mmap interface + ******************************************************************************/ + +/* ...add reference to shared buffer */ +static void dsp_mmap_open(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + /* ...probably just increase counter of open references? - tbd */ + atomic_inc(&client->vm_use); + + pr_debug("xf_mmap_open: vma = %p, client = %p", vma, client); +} + +/* ...close reference to shared buffer */ +static void dsp_mmap_close(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + pr_debug("xf_mmap_close: vma = %p, b = %p", vma, client); + + /* ...decrement number of mapping */ + atomic_dec(&client->vm_use); +} + +/* ...memory map operations */ +static const struct vm_operations_struct dsp_mmap_ops = { + .open = dsp_mmap_open, + .close = dsp_mmap_close, +}; + +/* ...shared memory mapping */ +static int fsl_dsp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct xf_proxy *proxy; + struct xf_client *client; + unsigned long size; + unsigned long pfn; + int r; + struct fsl_dsp *dsp_priv; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...check it was not mapped already */ + if (client->vm_start != 0) + return -EBUSY; + + /* ...check mapping flags (tbd) */ + if ((vma->vm_flags & (VM_READ | VM_WRITE | VM_SHARED)) + != (VM_READ | VM_WRITE | VM_SHARED)) + return -EPERM; + + /* ...set memory map operations */ + vma->vm_ops = &dsp_mmap_ops; + + /* ...assign private data */ + client->vm_start = vma->vm_start; + + /* ...set private memory data */ + vma->vm_private_data = client; + + /* ...set page number of shared memory */ + dsp_priv = (struct fsl_dsp *)client->global; + pfn = dsp_priv->scratch_buf_phys >> PAGE_SHIFT; + size = dsp_priv->scratch_buf_size; + + /* ...remap shared memory to user-space */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (r != 0) { + pr_err("mapping failed: %d", r); + return r; + } + + /* ...system-specific hook for registering shared memory mapping */ + return 0; +} + +void *memset_dsp(void *dest, int c, size_t count) +{ + uint *dl = (uint *)dest; + void *dl_1, *dl_2; + size_t align = 4; + size_t n, n1, n2; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest) & (sizeof(*dl) - 1)) != 0) { + dl = (unsigned int *)(((ulong)dest + align - 1) & + (~(align - 1))); + dl_1 = dest; + dl_2 = (void *)(((ulong)dest + count) & (~(align - 1))); + n1 = (ulong)dl - (ulong)dl_1; + n2 = (ulong)dest + count - (ulong)dl_2; + n = (count - n1 - n2) / align; + + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + while (n2--) { + writeb_relaxed(0, dl_2); + dl_2++; + } + } else { + n = count / align; + n1 = count - n * align; + dl_1 = dest + n * align; + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + } + + return dest; +} + +void *memcpy_dsp(void *dest, const void *src, size_t count) +{ + unsigned int *dl = (unsigned int *)dest, *sl = (unsigned int *)src; + size_t n = round_up(count, 4) / 4; + + if (src == dest) + return dest; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest | (ulong)src) & (sizeof(*dl) - 1)) != 0) + pr_info("dest %p src %p not 4 bytes aligned\n", dest, src); + + while (n--) { + writel_relaxed(*sl, dl); + dl++; + sl++; + } + + return dest; +} + +static void dsp_load_firmware(const struct firmware *fw, void *context) +{ + struct fsl_dsp *dsp_priv = context; + struct device *dev = dsp_priv->dev; + Elf32_Ehdr *ehdr; /* Elf header structure pointer */ + Elf32_Shdr *shdr; /* Section header structure pointer */ + Elf32_Addr sh_addr; + unsigned char *strtab = 0; /* String table pointer */ + unsigned char *image; /* Binary image pointer */ + int i; /* Loop counter */ + unsigned long addr; + + if (!fw) { + dev_info(dev, "external firmware not found\n"); + return; + } + + addr = (unsigned long)fw->data; + ehdr = (Elf32_Ehdr *)addr; + + /* Find the section header string table for output info */ + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (ehdr->e_shstrndx * sizeof(Elf32_Shdr))); + + strtab = (unsigned char *)(addr + shdr->sh_offset); + + /* Load each appropriate section */ + for (i = 0; i < ehdr->e_shnum; ++i) { + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (i * sizeof(Elf32_Shdr))); + + if (!(shdr->sh_flags & SHF_ALLOC) || + shdr->sh_addr == 0 || shdr->sh_size == 0) + continue; + + dev_dbg(dev, "%sing %s @ 0x%08lx (%ld bytes)\n", + (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load", + &strtab[shdr->sh_name], (unsigned long)shdr->sh_addr, + (long)shdr->sh_size); + + sh_addr = shdr->sh_addr; + + if (shdr->sh_type == SHT_NOBITS) { + memset_dsp((void *)(dsp_priv->sdram_vir_addr + + (sh_addr - dsp_priv->sdram_phys_addr)), + 0, + shdr->sh_size); + } else { + image = (unsigned char *)addr + shdr->sh_offset; + if ((!strcmp(&strtab[shdr->sh_name], ".rodata")) || + (!strcmp(&strtab[shdr->sh_name], ".text")) || + (!strcmp(&strtab[shdr->sh_name], ".data")) || + (!strcmp(&strtab[shdr->sh_name], ".bss")) + ) { + memcpy_dsp((void *)(dsp_priv->sdram_vir_addr + + (sh_addr - dsp_priv->sdram_phys_addr)), + (const void *)image, + shdr->sh_size); + } else { + /* sh_addr is from DSP view, we need to + * fixup addr because we load the firmware from + * the ARM core side + */ + sh_addr -= dsp_priv->fixup_offset; + + memcpy_dsp((void *)(dsp_priv->regs + + (sh_addr - dsp_priv->paddr)), + (const void *)image, + shdr->sh_size); + } + } + } + + /* start the core */ + imx_sc_pm_cpu_start(dsp_priv->dsp_ipcHandle, + IMX_SC_R_DSP, true, dsp_priv->iram); +} + +/* Initialization of the MU code. */ +int dsp_mu_init(struct fsl_dsp *dsp_priv) +{ + struct device *dev = dsp_priv->dev; + struct device_node *np; + unsigned int dsp_mu_id; + u32 irq; + int ret = 0; + + /* + * Get the address of MU to be used for communication with the dsp + */ + np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu-dsp"); + if (!np) { + dev_err(dev, "Cannot find MU entry in device tree\n"); + return -EINVAL; + } + dsp_priv->mu_base_virtaddr = of_iomap(np, 0); + WARN_ON(!dsp_priv->mu_base_virtaddr); + + ret = of_property_read_u32_index(np, + "fsl,dsp_ap_mu_id", 0, &dsp_mu_id); + if (ret) { + dev_err(dev, "Cannot get mu_id %d\n", ret); + return -EINVAL; + } + + dsp_priv->dsp_mu_id = dsp_mu_id; + + irq = of_irq_get(np, 0); + + ret = devm_request_irq(dsp_priv->dev, irq, fsl_dsp_mu_isr, + IRQF_EARLY_RESUME, "dsp_mu_isr", &dsp_priv->proxy); + if (ret) { + dev_err(dev, "request_irq failed %d, err = %d\n", irq, ret); + return -EINVAL; + } + + return ret; +} + +static const struct file_operations dsp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_dsp_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = fsl_dsp_ioctl, +#endif + .open = fsl_dsp_open, + .poll = fsl_dsp_poll, + .mmap = fsl_dsp_mmap, + .release = fsl_dsp_close, +}; + +extern struct snd_compr_ops dsp_platform_compr_ops; + +static const struct snd_soc_component_driver dsp_soc_platform_drv = { + .name = FSL_DSP_COMP_NAME, + .compr_ops = &dsp_platform_compr_ops, +}; + +static int fsl_dsp_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *reserved_node; + struct resource reserved_res; + struct fsl_dsp *dsp_priv; + const char *fw_name; + struct resource *res; + void __iomem *regs; + void *buf_virt; + dma_addr_t buf_phys; + int size, offset, i; + int ret; + int num_domains = 0; + char tmp[16]; + + dsp_priv = devm_kzalloc(&pdev->dev, sizeof(*dsp_priv), GFP_KERNEL); + if (!dsp_priv) + return -ENOMEM; + + if (of_device_is_compatible(np, "fsl,imx8qxp-dsp-v1")) + dsp_priv->dsp_board_type = DSP_IMX8QXP_TYPE; + else + dsp_priv->dsp_board_type = DSP_IMX8QM_TYPE; + + dsp_priv->dev = &pdev->dev; + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dsp_priv->paddr = res->start; + dsp_priv->regs = regs; + + dsp_priv->dram0 = dsp_priv->paddr + DRAM0_OFFSET; + dsp_priv->dram1 = dsp_priv->paddr + DRAM1_OFFSET; + dsp_priv->iram = dsp_priv->paddr + IRAM_OFFSET; + dsp_priv->sram = dsp_priv->paddr + SYSRAM_OFFSET; + + ret = imx_scu_get_handle(&dsp_priv->dsp_ipcHandle); + if (ret) { + dev_err(&pdev->dev, "Cannot get scu handle %d\n", ret); + return ret; + }; + + num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + for (i = 0; i < num_domains; i++) { + struct device *pd_dev; + struct device_link *link; + + pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i); + if (IS_ERR(pd_dev)) + return PTR_ERR(pd_dev); + + link = device_link_add(&pdev->dev, pd_dev, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(link)) + return PTR_ERR(link); + } + + if (dsp_priv->dsp_board_type == DSP_IMX8QXP_TYPE) { + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 1); + if (ret) { + dev_err(&pdev->dev, "Error system address offset source select\n"); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_PERIPH, 0x5A); + if (ret) { + dev_err(&pdev->dev, "Error system address offset of PERIPH %d\n", + ret); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_IRQ, 0x51); + if (ret) { + dev_err(&pdev->dev, "Error system address offset of IRQ\n"); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_AUDIO, 0x80); + if (ret) { + dev_err(&pdev->dev, "Error system address offset of AUDIO\n"); + return -EIO; + } + } else { + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 0); + if (ret) { + dev_err(&pdev->dev, "Error system address offset source select\n"); + return -EIO; + } + } + + ret = dsp_mu_init(dsp_priv); + if (ret) + return ret; + + ret = of_property_read_string(np, "fsl,dsp-firmware", &fw_name); + dsp_priv->fw_name = fw_name; + + ret = of_property_read_u32(np, "fixup-offset", &dsp_priv->fixup_offset); + + platform_set_drvdata(pdev, dsp_priv); + pm_runtime_enable(&pdev->dev); + + dsp_miscdev.fops = &dsp_fops, + dsp_miscdev.parent = &pdev->dev, + ret = misc_register(&dsp_miscdev); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device %d\n", ret); + return ret; + } + + reserved_node = of_parse_phandle(np, "reserved-region", 0); + if (!reserved_node) { + dev_err(&pdev->dev, "failed to get reserved region node\n"); + return -ENODEV; + } + + if (of_address_to_resource(reserved_node, 0, &reserved_res)) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + return -EINVAL; + } + + dsp_priv->sdram_phys_addr = reserved_res.start; + dsp_priv->sdram_reserved_size = (reserved_res.end - reserved_res.start) + + 1; + if (dsp_priv->sdram_reserved_size <= 0) { + dev_err(&pdev->dev, "invalid value of reserved region size\n"); + return -EINVAL; + } + + dsp_priv->sdram_vir_addr = ioremap_wc(dsp_priv->sdram_phys_addr, + dsp_priv->sdram_reserved_size); + if (!dsp_priv->sdram_vir_addr) { + dev_err(&pdev->dev, "failed to remap sdram space for dsp firmware\n"); + return -ENXIO; + } + memset_io(dsp_priv->sdram_vir_addr, 0, dsp_priv->sdram_reserved_size); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + + buf_virt = dma_alloc_coherent(&pdev->dev, size, &buf_phys, GFP_KERNEL); + if (!buf_virt) { + dev_err(&pdev->dev, "failed alloc memory.\n"); + return -ENOMEM; + } + + /* msg ring buffer memory */ + dsp_priv->msg_buf_virt = buf_virt; + dsp_priv->msg_buf_phys = buf_phys; + dsp_priv->msg_buf_size = MSG_BUF_SIZE; + offset = MSG_BUF_SIZE; + + /* keep dsp framework's global data when suspend/resume */ + dsp_priv->dsp_config_virt = buf_virt + offset; + dsp_priv->dsp_config_phys = buf_phys + offset; + dsp_priv->dsp_config_size = DSP_CONFIG_SIZE; + + /* scratch memory for dsp framework. The sdram reserved memory + * is split into two equal parts currently. The front part is + * used to keep the dsp firmware, the other part is considered + * as scratch memory for dsp framework. + */ + dsp_priv->scratch_buf_virt = dsp_priv->sdram_vir_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_phys = dsp_priv->sdram_phys_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_size = dsp_priv->sdram_reserved_size / 2; + + /* initialize the reference counter for dsp_priv + * structure + */ + atomic_long_set(&dsp_priv->refcnt, 0); + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); + + /* ...initialize mutex */ + mutex_init(&dsp_priv->dsp_mutex); + + ret = devm_snd_soc_register_component(&pdev->dev, &dsp_soc_platform_drv, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "registering soc platform failed\n"); + return ret; + } + + dsp_priv->esai_ipg_clk = devm_clk_get(&pdev->dev, "esai_ipg"); + if (IS_ERR(dsp_priv->esai_ipg_clk)) + dsp_priv->esai_ipg_clk = NULL; + + dsp_priv->esai_mclk = devm_clk_get(&pdev->dev, "esai_mclk"); + if (IS_ERR(dsp_priv->esai_mclk)) + dsp_priv->esai_mclk = NULL; + + dsp_priv->asrc_mem_clk = devm_clk_get(&pdev->dev, "asrc_mem"); + if (IS_ERR(dsp_priv->asrc_mem_clk)) + dsp_priv->asrc_mem_clk = NULL; + + dsp_priv->asrc_ipg_clk = devm_clk_get(&pdev->dev, "asrc_ipg"); + if (IS_ERR(dsp_priv->asrc_ipg_clk)) + dsp_priv->asrc_ipg_clk = NULL; + + for (i = 0; i < 4; i++) { + sprintf(tmp, "asrck_%x", i); + dsp_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(dsp_priv->asrck_clk[i])) + dsp_priv->asrck_clk[i] = NULL; + } + + return 0; +} + +static int fsl_dsp_remove(struct platform_device *pdev) +{ + struct fsl_dsp *dsp_priv = platform_get_drvdata(pdev); + int size; + + misc_deregister(&dsp_miscdev); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + dma_free_coherent(&pdev->dev, size, dsp_priv->msg_buf_virt, + dsp_priv->msg_buf_phys); + if (dsp_priv->sdram_vir_addr) + iounmap(dsp_priv->sdram_vir_addr); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_dsp_runtime_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + int i; + + ret = clk_prepare_enable(dsp_priv->esai_ipg_clk); + if (ret) { + dev_err(dev, "failed to enable esai ipg clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dsp_priv->esai_mclk); + if (ret) { + dev_err(dev, "failed to enable esai mclk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dsp_priv->asrc_mem_clk); + if (ret < 0) + dev_err(dev, "Failed to enable asrc_mem_clk ret = %d\n", ret); + + ret = clk_prepare_enable(dsp_priv->asrc_ipg_clk); + if (ret < 0) + dev_err(dev, "Failed to enable asrc_ipg_clk ret = %d\n", ret); + + for (i = 0; i < 4; i++) { + ret = clk_prepare_enable(dsp_priv->asrck_clk[i]); + if (ret < 0) + dev_err(dev, "failed to prepare arc clk %d\n", i); + } + + if (!dsp_priv->dsp_mu_init) { + MU_Init(dsp_priv->mu_base_virtaddr); + MU_EnableRxFullInt(dsp_priv->mu_base_virtaddr, 0); + dsp_priv->dsp_mu_init = 1; + } + + if (!proxy->is_ready) { + init_completion(&proxy->cmd_complete); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, dsp_priv->fw_name, + dev, + GFP_KERNEL, dsp_priv, dsp_load_firmware); + + if (ret) { + dev_err(dev, "failed to load firmware\n"); + return ret; + } + + ret = icm_ack_wait(proxy, 0); + if (ret) + return ret; + + dev_info(dev, "dsp driver registered\n"); + } + + return 0; +} + +static int fsl_dsp_runtime_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int i; + + dsp_priv->dsp_mu_init = 0; + proxy->is_ready = 0; + + for (i = 0; i < 4; i++) + clk_disable_unprepare(dsp_priv->asrck_clk[i]); + + clk_disable_unprepare(dsp_priv->asrc_ipg_clk); + clk_disable_unprepare(dsp_priv->asrc_mem_clk); + + clk_disable_unprepare(dsp_priv->esai_mclk); + clk_disable_unprepare(dsp_priv->esai_ipg_clk); + + return 0; +} +#endif /* CONFIG_PM */ + + +#ifdef CONFIG_PM_SLEEP +static int fsl_dsp_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + if (proxy->is_ready) { + ret = xf_cmd_send_suspend(proxy); + if (ret) { + dev_err(dev, "dsp suspend fail\n"); + return ret; + } + } + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_dsp_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + if (proxy->is_ready) { + ret = xf_cmd_send_resume(proxy); + if (ret) { + dev_err(dev, "dsp resume fail\n"); + return ret; + } + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_dsp_pm = { + SET_RUNTIME_PM_OPS(fsl_dsp_runtime_suspend, + fsl_dsp_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_dsp_suspend, fsl_dsp_resume) +}; + +static const struct of_device_id fsl_dsp_ids[] = { + { .compatible = "fsl,imx8qxp-dsp-v1", }, + { .compatible = "fsl,imx8qm-dsp-v1", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_dsp_ids); + +static struct platform_driver fsl_dsp_driver = { + .probe = fsl_dsp_probe, + .remove = fsl_dsp_remove, + .driver = { + .name = "fsl-dsp", + .of_match_table = fsl_dsp_ids, + .pm = &fsl_dsp_pm, + }, +}; +module_platform_driver(fsl_dsp_driver); + +MODULE_DESCRIPTION("Freescale DSP driver"); +MODULE_ALIAS("platform:fsl-dsp"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/fsl/fsl_dsp.h b/sound/soc/fsl/fsl_dsp.h new file mode 100644 index 000000000000..a66b083124ae --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + * + */ + +#ifndef FSL_DSP_H +#define FSL_DSP_H +#include <uapi/linux/mxc_dsp.h> +#include <linux/firmware/imx/ipc.h> +#include "fsl_dsp_proxy.h" +#include "fsl_dsp_platform.h" + + +#define FSL_DSP_COMP_NAME "fsl-dsp-component" + +typedef void (*memcpy_func) (void *dest, const void *src, size_t n); +typedef void (*memset_func) (void *s, int c, size_t n); + +/* ...maximal number of IPC clients per proxy */ +#define XF_CFG_MAX_IPC_CLIENTS (1 << 4) + +enum { + DSP_IMX8QXP_TYPE = 0, + DSP_IMX8QM_TYPE, +}; + +/* ...proxy client data */ +struct xf_client { + /* ...pointer to proxy interface */ + struct xf_proxy *proxy; + + /* ...allocated proxy client id */ + u32 id; + + /* ...pending response queue */ + struct xf_msg_queue queue; + /* ...response waiting queue */ + wait_queue_head_t wait; + + /* ...virtual memory mapping */ + unsigned long vm_start; + /* ...counter of memory mappings (no real use of it yet - tbd) */ + atomic_t vm_use; + + /* ...global structure pointer */ + void *global; + struct xf_message m; + + struct snd_compr_stream *cstream; + + struct work_struct work; + struct completion compr_complete; + + int input_bytes; + int consume_bytes; +}; + +union xf_client_link { + /* ...index of next client in free list */ + u32 next; + + /* ...reference to proxy data for allocated client */ + struct xf_client *client; +}; + +struct fsl_dsp { + struct device *dev; + const char *fw_name; + void __iomem *regs; + void __iomem *mu_base_virtaddr; + struct imx_sc_ipc *dsp_ipcHandle; + unsigned int dsp_mu_id; + int dsp_mu_init; + atomic_long_t refcnt; + unsigned long paddr; + unsigned long dram0; + unsigned long dram1; + unsigned long iram; + unsigned long sram; + void *sdram_vir_addr; + unsigned long sdram_phys_addr; + int sdram_reserved_size; + void *msg_buf_virt; + dma_addr_t msg_buf_phys; + int msg_buf_size; + void *scratch_buf_virt; + dma_addr_t scratch_buf_phys; + int scratch_buf_size; + void *dsp_config_virt; + dma_addr_t dsp_config_phys; + int dsp_config_size; + int dsp_board_type; + unsigned int fixup_offset; + + /* ...proxy data structures */ + struct xf_proxy proxy; + + /* ...mutex lock */ + struct mutex dsp_mutex; + + struct dsp_data dsp_data; + + /* ...global clients pool (item[0] serves as list terminator) */ + union xf_client_link xf_client_map[XF_CFG_MAX_IPC_CLIENTS]; + + struct clk *esai_ipg_clk; + struct clk *esai_mclk; + struct clk *asrc_mem_clk; + struct clk *asrc_ipg_clk; + struct clk *asrck_clk[4]; +}; + +#define IRAM_OFFSET 0x10000 +#define IRAM_SIZE 2048 + +#define DRAM0_OFFSET 0x0 +#define DRAM0_SIZE 0x8000 + +#define DRAM1_OFFSET 0x8000 +#define DRAM1_SIZE 0x8000 + +#define SYSRAM_OFFSET 0x18000 +#define SYSRAM_SIZE 0x40000 + +#define SYSROM_OFFSET 0x58000 +#define SYSROM_SIZE 0x30000 + +#define MSG_BUF_SIZE 8192 +#define INPUT_BUF_SIZE 4096 +#define OUTPUT_BUF_SIZE 16384 +#define DSP_CONFIG_SIZE 4096 + +void *memcpy_dsp(void *dest, const void *src, size_t count); +void *memset_dsp(void *dest, int c, size_t count); +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id); +struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv); + +int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client); +int fsl_dsp_close_func(struct xf_client *client); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_cpu.c b/sound/soc/fsl/fsl_dsp_cpu.c new file mode 100644 index 000000000000..e97d09ae0c45 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_cpu.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DSP Audio platform driver +// +// Copyright 2018 NXP + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/compress_driver.h> + +#include "fsl_dsp_cpu.h" + +static int dsp_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) { + return 0; +} + + +static void dsp_audio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) { +} + +static const struct snd_soc_dai_ops dsp_audio_dai_ops = { + .startup = dsp_audio_startup, + .shutdown = dsp_audio_shutdown, +}; + +static struct snd_soc_dai_driver dsp_audio_dai = { + .name = "dsp-audio-cpu-dai", + .compress_new = snd_soc_new_compress, + .ops = &dsp_audio_dai_ops, + .playback = { + .stream_name = "Compress Playback", + .channels_min = 1, + }, +}; + +static const struct snd_soc_component_driver audio_dsp_component = { + .name = "audio-dsp", +}; + +static int dsp_audio_probe(struct platform_device *pdev) +{ + struct fsl_dsp_audio *dsp_audio; + int ret; + + dsp_audio = devm_kzalloc(&pdev->dev, sizeof(*dsp_audio), GFP_KERNEL); + if (dsp_audio == NULL) + return -ENOMEM; + + dev_dbg(&pdev->dev, "probing DSP device....\n"); + + /* intialise sof device */ + dev_set_drvdata(&pdev->dev, dsp_audio); + + pm_runtime_enable(&pdev->dev); + + /* now register audio DSP platform driver */ + ret = snd_soc_register_component(&pdev->dev, &audio_dsp_component, + &dsp_audio_dai, 1); + if (ret < 0) { + dev_err(&pdev->dev, + "error: failed to register DSP DAI driver %d\n", ret); + goto err; + } + + return 0; + +err: + return ret; +} + +static int dsp_audio_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + return 0; +} + + +static const struct of_device_id dsp_audio_ids[] = { + { .compatible = "fsl,dsp-audio"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dsp_audio_ids); + +static struct platform_driver dsp_audio_driver = { + .driver = { + .name = "dsp-audio", + .of_match_table = dsp_audio_ids, + }, + .probe = dsp_audio_probe, + .remove = dsp_audio_remove, +}; +module_platform_driver(dsp_audio_driver); diff --git a/sound/soc/fsl/fsl_dsp_cpu.h b/sound/soc/fsl/fsl_dsp_cpu.h new file mode 100644 index 000000000000..dac10b4ab9d0 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_cpu.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * DSP Audio DAI header + * + * Copyright 2018 NXP + */ + +#ifndef __FSL_DSP_CPU_H +#define __FSL_DSP_CPU_H + +struct fsl_dsp_audio { + struct platform_device *pdev; +}; + +#endif /*__FSL_DSP_CPU_H*/ + diff --git a/sound/soc/fsl/fsl_dsp_library_load.c b/sound/soc/fsl/fsl_dsp_library_load.c new file mode 100644 index 000000000000..d273f7296e29 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_library_load.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/elf.h> + +#include "fsl_dsp.h" +#include "fsl_dsp_library_load.h" + +static Elf32_Half xtlib_host_half(Elf32_Half v, int byteswap) +{ + return (byteswap) ? (v >> 8) | (v << 8) : v; +} + +static Elf32_Word xtlib_host_word(Elf32_Word v, int byteswap) +{ + if (byteswap) { + v = ((v & 0x00FF00FF) << 8) | ((v & 0xFF00FF00) >> 8); + v = (v >> 16) | (v << 16); + } + return v; +} + +static int xtlib_verify_magic(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Byte magic_no; + + magic_no = header->e_ident[EI_MAG0]; + if (magic_no != 0x7f) + return -1; + + magic_no = header->e_ident[EI_MAG1]; + if (magic_no != 'E') + return -1; + + magic_no = header->e_ident[EI_MAG2]; + if (magic_no != 'L') + return -1; + + magic_no = header->e_ident[EI_MAG3]; + if (magic_no != 'F') + return -1; + + if (header->e_ident[EI_CLASS] != ELFCLASS32) + return -1; + + { + /* determine byte order */ + union { + short s; + char c[sizeof(short)]; + } u; + + u.s = 1; + + if (header->e_ident[EI_DATA] == ELFDATA2LSB) + xtlib_globals->byteswap = u.c[sizeof(short) - 1] == 1; + else if (header->e_ident[EI_DATA] == ELFDATA2MSB) + xtlib_globals->byteswap = u.c[0] == 1; + else + return -1; + } + + return 0; +} + +static void xtlib_load_seg(Elf32_Phdr *pheader, void *src_addr, xt_ptr dst_addr, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Word bytes_to_copy = xtlib_host_word(pheader->p_filesz, + xtlib_globals->byteswap); + Elf32_Word bytes_to_zero = xtlib_host_word(pheader->p_memsz, + xtlib_globals->byteswap) + - bytes_to_copy; + unsigned int i; + char *src_back, *dst_back; + + void *zero_addr = (void *)dst_addr + bytes_to_copy; + + if (bytes_to_copy > 0) { + // memcpy((void *)(dst_addr), src_addr, bytes_to_copy); + src_back = (char *)src_addr; + dst_back = (char *)dst_addr; + for (i = 0; i < bytes_to_copy; i++) + *dst_back++ = *src_back++; + } + + if (bytes_to_zero > 0) { + // memset(zero_addr, 0, bytes_to_zero); + dst_back = (char *)zero_addr; + for (i = 0; i < bytes_to_zero; i++) + *dst_back++ = 0; + } +} + +#define xtlib_xt_half xtlib_host_half +#define xtlib_xt_word xtlib_host_word + +static xt_ptr align_ptr(xt_ptr ptr, xt_uint align) +{ + return (xt_ptr)(((xt_uint)ptr + align - 1) & ~(align - 1)); +} + +static xt_ptr xt_ptr_offs(xt_ptr base, Elf32_Word offs, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + + return (xt_ptr)xtlib_xt_word((xt_uint)base + + xtlib_host_word(offs, xtlib_globals->byteswap), + xtlib_globals->byteswap); +} + +static Elf32_Dyn *find_dynamic_info(Elf32_Ehdr *eheader, + struct lib_info *lib_info) +{ + char *base_addr = (char *)eheader; + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader = (Elf32_Phdr *)(base_addr + + xtlib_host_word(eheader->e_phoff, + xtlib_globals->byteswap)); + + int seg = 0; + int num = xtlib_host_half(eheader->e_phnum, xtlib_globals->byteswap); + + while (seg < num) { + if (xtlib_host_word(pheader[seg].p_type, + xtlib_globals->byteswap) == PT_DYNAMIC) { + return (Elf32_Dyn *)(base_addr + + xtlib_host_word(pheader[seg].p_offset, + xtlib_globals->byteswap)); + } + seg++; + } + return 0; +} + +static int find_align(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Shdr *sheader = (Elf32_Shdr *) (((char *)header) + + xtlib_host_word(header->e_shoff, xtlib_globals->byteswap)); + + int sec = 0; + int num = xtlib_host_half(header->e_shnum, xtlib_globals->byteswap); + + int align = 0; + + while (sec < num) { + if (sheader[sec].sh_type != SHT_NULL && + xtlib_host_word(sheader[sec].sh_size, + xtlib_globals->byteswap) > 0) { + int sec_align = + xtlib_host_word(sheader[sec].sh_addralign, + xtlib_globals->byteswap); + if (sec_align > align) + align = sec_align; + } + sec++; + } + + return align; +} + +static int validate_dynamic(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + + if (xtlib_verify_magic(header, lib_info) != 0) + return XTLIB_NOT_ELF; + + if (xtlib_host_half(header->e_type, + xtlib_globals->byteswap) != ET_DYN) + return XTLIB_NOT_DYNAMIC; + + return XTLIB_NO_ERR; +} + +static int validate_dynamic_splitload(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader; + int err = validate_dynamic(header, lib_info); + + if (err != XTLIB_NO_ERR) + return err; + + /* make sure it's split load pi library, expecting three headers, + * code, data and dynamic, for example: + * + *LOAD off 0x00000094 vaddr 0x00000000 paddr 0x00000000 align 2**0 + * filesz 0x00000081 memsz 0x00000081 flags r-x + *LOAD off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**0 + * filesz 0x000001ab memsz 0x000011bc flags rwx + *DYNAMIC off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**2 + * filesz 0x000000a0 memsz 0x000000a0 flags rw- + */ + + if (xtlib_host_half(header->e_phnum, xtlib_globals->byteswap) != 3) + return XTLIB_NOT_SPLITLOAD; + + pheader = (Elf32_Phdr *)((char *)header + + xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); + + /* LOAD R-X */ + if (xtlib_host_word(pheader[0].p_type, + xtlib_globals->byteswap) != PT_LOAD || + (xtlib_host_word(pheader[0].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_X)) + return XTLIB_NOT_SPLITLOAD; + + /* LOAD RWX */ + if (xtlib_host_word(pheader[1].p_type, + xtlib_globals->byteswap) != PT_LOAD || + (xtlib_host_word(pheader[1].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_W | PF_X)) + return XTLIB_NOT_SPLITLOAD; + + /* DYNAMIC RW- */ + if (xtlib_host_word(pheader[2].p_type, + xtlib_globals->byteswap) != PT_DYNAMIC || + (xtlib_host_word(pheader[2].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_W)) + return XTLIB_NOT_SPLITLOAD; + + return XTLIB_NO_ERR; +} + +static unsigned int +xtlib_split_pi_library_size(struct xtlib_packaged_library *library, + unsigned int *code_size, + unsigned int *data_size, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader; + Elf32_Ehdr *header = (Elf32_Ehdr *)library; + int align; + int err = validate_dynamic_splitload(header, lib_info); + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return err; + } + + align = find_align(header, lib_info); + + pheader = (Elf32_Phdr *)((char *)library + + xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); + + *code_size = xtlib_host_word(pheader[0].p_memsz, + xtlib_globals->byteswap) + align; + *data_size = xtlib_host_word(pheader[1].p_memsz, + xtlib_globals->byteswap) + align; + + return XTLIB_NO_ERR; +} + +static int get_dyn_info(Elf32_Ehdr *eheader, + xt_ptr dst_addr, xt_uint src_offs, + xt_ptr dst_data_addr, xt_uint src_data_offs, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + unsigned int jmprel = 0; + unsigned int pltrelsz = 0; + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Dyn *dyn_entry = find_dynamic_info(eheader, lib_info); + + if (dyn_entry == 0) + return XTLIB_NO_DYNAMIC_SEGMENT; + + info->dst_addr = (xt_uint)xtlib_xt_word((Elf32_Word)dst_addr, + xtlib_globals->byteswap); + info->src_offs = xtlib_xt_word(src_offs, xtlib_globals->byteswap); + info->dst_data_addr = (xt_uint)xtlib_xt_word( + (Elf32_Word)dst_data_addr + src_data_offs, + xtlib_globals->byteswap); + info->src_data_offs = xtlib_xt_word(src_data_offs, + xtlib_globals->byteswap); + + dst_addr -= src_offs; + dst_data_addr = dst_data_addr + src_data_offs - src_data_offs; + + info->start_sym = xt_ptr_offs(dst_addr, eheader->e_entry, lib_info); + + info->align = xtlib_xt_word(find_align(eheader, lib_info), + xtlib_globals->byteswap); + + info->text_addr = 0; + + while (dyn_entry->d_tag != DT_NULL) { + switch ((Elf32_Sword) xtlib_host_word( + (Elf32_Word)dyn_entry->d_tag, + xtlib_globals->byteswap)) { + case DT_RELA: + info->rel = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_RELASZ: + info->rela_count = xtlib_xt_word( + xtlib_host_word(dyn_entry->d_un.d_val, + xtlib_globals->byteswap) / + sizeof(Elf32_Rela), + xtlib_globals->byteswap); + break; + case DT_INIT: + info->init = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_FINI: + info->fini = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_HASH: + info->hash = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_SYMTAB: + info->symtab = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_STRTAB: + info->strtab = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_JMPREL: + jmprel = dyn_entry->d_un.d_val; + break; + case DT_PLTRELSZ: + pltrelsz = dyn_entry->d_un.d_val; + break; + case DT_LOPROC + 2: + info->text_addr = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + + default: + /* do nothing */ + break; + } + dyn_entry++; + } + + return XTLIB_NO_ERR; +} + +static xt_ptr +xtlib_load_split_pi_library_common(struct xtlib_packaged_library *library, + xt_ptr destination_code_address, + xt_ptr destination_data_address, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Ehdr *header = (Elf32_Ehdr *)library; + Elf32_Phdr *pheader; + unsigned int align; + int err = validate_dynamic_splitload(header, lib_info); + xt_ptr destination_code_address_back; + xt_ptr destination_data_address_back; + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return 0; + } + + align = find_align(header, lib_info); + + destination_code_address_back = destination_code_address; + destination_data_address_back = destination_data_address; + + destination_code_address = align_ptr(destination_code_address, align); + destination_data_address = align_ptr(destination_data_address, align); + lib_info->code_buf_virt += (destination_code_address - + destination_code_address_back); + lib_info->data_buf_virt += (destination_data_address - + destination_data_address_back); + + pheader = (Elf32_Phdr *)((char *)library + + xtlib_host_word(header->e_phoff, + xtlib_globals->byteswap)); + + err = get_dyn_info(header, + destination_code_address, + xtlib_host_word(pheader[0].p_paddr, + xtlib_globals->byteswap), + destination_data_address, + xtlib_host_word(pheader[1].p_paddr, + xtlib_globals->byteswap), + info, + lib_info); + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return 0; + } + + /* loading code */ + xtlib_load_seg(&pheader[0], + (char *)library + xtlib_host_word(pheader[0].p_offset, + xtlib_globals->byteswap), + (xt_ptr)lib_info->code_buf_virt, + lib_info); + + if (info->text_addr == 0) + info->text_addr = + (xt_ptr)xtlib_xt_word((Elf32_Word)destination_code_address, + xtlib_globals->byteswap); + + /* loading data */ + xtlib_load_seg(&pheader[1], + (char *)library + xtlib_host_word(pheader[1].p_offset, + xtlib_globals->byteswap), + (xt_ptr)lib_info->data_buf_virt + + xtlib_host_word(pheader[1].p_paddr, + xtlib_globals->byteswap), + lib_info); + + return (xt_ptr)xtlib_host_word((Elf32_Word)info->start_sym, + xtlib_globals->byteswap); +} + +static xt_ptr +xtlib_host_load_split_pi_library(struct xtlib_packaged_library *library, + xt_ptr destination_code_address, + xt_ptr destination_data_address, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + return xtlib_load_split_pi_library_common(library, + destination_code_address, + destination_data_address, + info, + lib_info); +} + +static long +load_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, + struct lib_info *lib_info) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + unsigned char *srambuf; + struct lib_dnld_info_t dpulib; + struct file *file; + struct xf_buffer *buf; + Elf32_Phdr *pheader; + Elf32_Ehdr *header; + loff_t pos = 0; + unsigned int align; + int filesize = 0; + long ret_val = 0; + + file = filp_open(lib_info->filename, O_RDONLY, 0); + if (IS_ERR(file)) + return PTR_ERR(file); + + vfs_llseek(file, 0, SEEK_END); + filesize = (int)file->f_pos; + + srambuf = kmalloc(filesize, GFP_KERNEL); + if (!srambuf) + return -ENOMEM; + + vfs_llseek(file, 0, SEEK_SET); + ret_val = kernel_read(file, srambuf, filesize, &pos); + if (ret_val < 0) + return ret_val; + filp_close(file, NULL); + + ret_val = xtlib_split_pi_library_size( + (struct xtlib_packaged_library *)(srambuf), + (unsigned int *)&dpulib.size_code, + (unsigned int *)&dpulib.size_data, + lib_info); + if (ret_val != XTLIB_NO_ERR) + return -EINVAL; + + lib_info->code_buf_size = dpulib.size_code; + lib_info->data_buf_size = dpulib.size_data; + + header = (Elf32_Ehdr *)srambuf; + pheader = (Elf32_Phdr *)((char *)srambuf + + xtlib_host_word(header->e_phoff, + lib_info->xtlib_globals.byteswap)); + + align = find_align(header, lib_info); + ret_val = xf_pool_alloc(client, proxy, 1, dpulib.size_code + align, + XF_POOL_AUX, &lib_info->code_section_pool); + if (ret_val) { + kfree(srambuf); + pr_err("Allocation failure for loading code section\n"); + return -ENOMEM; + } + + ret_val = xf_pool_alloc(client, proxy, 1, + dpulib.size_data + pheader[1].p_paddr + align, + XF_POOL_AUX, &lib_info->data_section_pool); + if (ret_val) { + kfree(srambuf); + pr_err("Allocation failure for loading data section\n"); + return -ENOMEM; + } + + buf = xf_buffer_get(lib_info->code_section_pool); + lib_info->code_buf_virt = xf_buffer_data(buf); + lib_info->code_buf_phys = ((u64)xf_buffer_data(buf) - + (u64)dsp_priv->scratch_buf_virt) + + dsp_priv->scratch_buf_phys; + lib_info->code_buf_size = dpulib.size_code + align; + xf_buffer_put(buf); + + buf = xf_buffer_get(lib_info->data_section_pool); + lib_info->data_buf_virt = xf_buffer_data(buf); + lib_info->data_buf_phys = ((u64)xf_buffer_data(buf) - + (u64)dsp_priv->scratch_buf_virt) + + dsp_priv->scratch_buf_phys; + lib_info->data_buf_size = dpulib.size_data + align + pheader[1].p_paddr; + xf_buffer_put(buf); + + dpulib.pbuf_code = (unsigned long)lib_info->code_buf_phys; + dpulib.pbuf_data = (unsigned long)lib_info->data_buf_phys; + + dpulib.ppil_inf = &lib_info->pil_info; + xtlib_host_load_split_pi_library((struct xtlib_packaged_library *)srambuf, + (xt_ptr)(dpulib.pbuf_code), + (xt_ptr)(dpulib.pbuf_data), + (struct xtlib_pil_info *)dpulib.ppil_inf, + (void *)lib_info); + kfree(srambuf); + + return ret_val; +} + +static long +unload_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, + struct lib_info *lib_info) +{ + xf_pool_free(client, lib_info->code_section_pool); + xf_pool_free(client, lib_info->data_section_pool); + + return 0; +} + +long xf_load_lib(struct xf_client *client, + struct xf_handle *handle, struct lib_info *lib_info) +{ + void *b = xf_handle_aux(handle); + struct icm_xtlib_pil_info icm_info; + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + long ret_val; + + ret_val = load_dpu_with_library(client, proxy, lib_info); + if (ret_val) + return ret_val; + + memcpy((void *)(&icm_info.pil_info), (void *)(&lib_info->pil_info), + sizeof(struct xtlib_pil_info)); + + icm_info.lib_type = lib_info->lib_type; + + /* ...set message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), __XF_PORT_SPEC2(handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_LOAD_LIB; + msg.buffer = b; + msg.length = sizeof(struct icm_xtlib_pil_info); + msg.ret = 0; + + /* ...copy lib info */ + memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + +// xf_msg_free(proxy, rmsg); +// xf_unlock(&proxy->lock); + + return 0; +} + +long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info) +{ + void *b = xf_handle_aux(handle); + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + struct icm_xtlib_pil_info icm_info; + + memset((void *)&icm_info, 0, sizeof(struct icm_xtlib_pil_info)); + icm_info.lib_type = lib_info->lib_type; + + /* ...set message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),__XF_PORT_SPEC2(handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNLOAD_LIB; + msg.buffer = b; + msg.length = sizeof(struct icm_xtlib_pil_info); + msg.ret = 0; + + /* ...copy lib info */ + memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + +// xf_msg_free(proxy, rmsg); +// xf_unlock(&proxy->lock); + + return unload_dpu_with_library(client, proxy, lib_info); +} diff --git a/sound/soc/fsl/fsl_dsp_library_load.h b/sound/soc/fsl/fsl_dsp_library_load.h new file mode 100644 index 000000000000..8c14dda20b27 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_library_load.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#ifndef FSL_DSP_LIBRARY_LOAD_H +#define FSL_DSP_LIBRARY_LOAD_H + +#include "fsl_dsp_pool.h" + +#define Elf32_Byte unsigned char +#define xt_ptr unsigned long +#define xt_int int +#define xt_uint unsigned int +#define xt_ulong unsigned long + +struct xtlib_packaged_library; + +enum { + XTLIB_NO_ERR = 0, + XTLIB_NOT_ELF = 1, + XTLIB_NOT_DYNAMIC = 2, + XTLIB_NOT_STATIC = 3, + XTLIB_NO_DYNAMIC_SEGMENT = 4, + XTLIB_UNKNOWN_SYMBOL = 5, + XTLIB_NOT_ALIGNED = 6, + XTLIB_NOT_SPLITLOAD = 7, + XTLIB_RELOCATION_ERR = 8 +}; + +enum lib_type { + DSP_CODEC_LIB = 1, + DSP_CODEC_WRAP_LIB +}; + +struct xtlib_loader_globals { + int err; + int byteswap; +}; + +struct xtlib_pil_info { + xt_uint dst_addr; + xt_uint src_offs; + xt_uint dst_data_addr; + xt_uint src_data_offs; + xt_uint start_sym; + xt_uint text_addr; + xt_uint init; + xt_uint fini; + xt_uint rel; + xt_int rela_count; + xt_uint hash; + xt_uint symtab; + xt_uint strtab; + xt_int align; +}; + +struct icm_xtlib_pil_info { + struct xtlib_pil_info pil_info; + unsigned int lib_type; +}; + +struct lib_dnld_info_t { + unsigned long pbuf_code; + unsigned long pbuf_data; + unsigned int size_code; + unsigned int size_data; + struct xtlib_pil_info *ppil_inf; + unsigned int lib_on_dpu; /* 0: not loaded, 1: loaded. */ +}; + +struct lib_info { + struct xtlib_pil_info pil_info; + struct xtlib_loader_globals xtlib_globals; + + struct xf_pool *code_section_pool; + struct xf_pool *data_section_pool; + + void *code_buf_virt; + unsigned int code_buf_phys; + unsigned int code_buf_size; + void *data_buf_virt; + unsigned int data_buf_phys; + unsigned int data_buf_size; + + const char *filename; + unsigned int lib_type; +}; + +long xf_load_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info); +long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_platform.h b/sound/soc/fsl/fsl_dsp_platform.h new file mode 100644 index 000000000000..15d085c9f23d --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_platform.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018 NXP + */ + +#ifndef _FSL_DSP_PLATFORM_H +#define _FSL_DSP_PLATFORM_H + +#include "fsl_dsp_xaf_api.h" + +struct dsp_data { + struct xf_client *client; + struct xaf_pipeline *p_pipe; + struct xaf_pipeline pipeline; + struct xaf_comp component[2]; + int codec_type; + int status; +}; + +#endif /*_FSL_DSP_PLATFORM_H*/ diff --git a/sound/soc/fsl/fsl_dsp_platform_compress.c b/sound/soc/fsl/fsl_dsp_platform_compress.c new file mode 100644 index 000000000000..43b07af3cbfe --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_platform_compress.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DSP driver compress implementation +// +// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. +// Copyright 2018 NXP + +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/compress_driver.h> + +#include "fsl_dsp.h" +#include "fsl_dsp_platform.h" +#include "fsl_dsp_xaf_api.h" + +#define NUM_CODEC 2 +#define MIN_FRAGMENT 1 +#define MAX_FRAGMENT 1 +#define MIN_FRAGMENT_SIZE (4 * 1024) +#define MAX_FRAGMENT_SIZE (4 * 1024) + +void dsp_platform_process(struct work_struct *w) +{ + struct xf_client *client = container_of(w, struct xf_client, work); + struct xf_proxy *proxy = client->proxy; + struct xf_message *rmsg; + + while (1) { + rmsg = xf_cmd_recv(proxy, &client->wait, &client->queue, 1); + + if (!proxy->is_active || IS_ERR(rmsg)) + return; + if (rmsg->opcode == XF_EMPTY_THIS_BUFFER) { + client->consume_bytes += rmsg->length; + snd_compr_fragment_elapsed(client->cstream); + + if (rmsg->buffer == NULL && rmsg->length == 0) + snd_compr_drain_notify(client->cstream); + + } else { + memcpy(&client->m, rmsg, sizeof(struct xf_message)); + complete(&client->compr_complete); + } + + xf_msg_free(proxy, rmsg); + xf_unlock(&proxy->lock); + } +} + +static int dsp_platform_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + + drv->client = xf_client_alloc(dsp_priv); + if (IS_ERR(drv->client)) + return PTR_ERR(drv->client); + + fsl_dsp_open_func(dsp_priv, drv->client); + + drv->client->proxy = &dsp_priv->proxy; + + cpu_dai->driver->ops->startup(NULL, cpu_dai); + + drv->client->cstream = cstream; + + INIT_WORK(&drv->client->work, dsp_platform_process); + + return 0; +} + +static int dsp_platform_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + int ret; + + if (cstream->runtime->state != SNDRV_PCM_STATE_PAUSED && + cstream->runtime->state != SNDRV_PCM_STATE_RUNNING && + cstream->runtime->state != SNDRV_PCM_STATE_DRAINING) { + + ret = xaf_comp_delete(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_delete(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + } + + cpu_dai->driver->ops->shutdown(NULL, cpu_dai); + + drv->client->proxy->is_active = 0; + wake_up(&drv->client->wait); + cancel_work_sync(&drv->client->work); + + fsl_dsp_close_func(drv->client); + + return 0; +} + +static int dsp_platform_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + /* accroding to the params, load the library and create component*/ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_proxy *p_proxy = &dsp_priv->proxy; + struct xf_set_param_msg s_param; + int ret; + + switch (params->codec.id) { + case SND_AUDIOCODEC_MP3: + drv->codec_type = CODEC_MP3_DEC; + break; + case SND_AUDIOCODEC_AAC: + drv->codec_type = CODEC_AAC_DEC; + break; + default: + dev_err(component->dev, "codec not supported, id =%d\n", params->codec.id); + return -EINVAL; + } + + /* ...create auxiliary buffers pool for control commands */ + ret = xf_pool_alloc(drv->client, + p_proxy, + XA_AUX_POOL_SIZE, + XA_AUX_POOL_MSG_LENGTH, + XF_POOL_AUX, + &p_proxy->aux); + if (ret) { + dev_err(component->dev, "xf_pool_alloc failed"); + return ret; + } + + /* ...create pipeline */ + ret = xaf_pipeline_create(&drv->pipeline); + if (ret) { + dev_err(component->dev, "create pipeline error\n"); + goto err_pool_alloc; + } + + /* ...create component */ + ret = xaf_comp_create(drv->client, p_proxy, &drv->component[0], + drv->codec_type); + if (ret) { + dev_err(component->dev, + "create component failed type = %d, err = %d\n", + drv->codec_type, ret); + goto err_pool_alloc; + } + + ret = xaf_comp_create(drv->client, p_proxy, &drv->component[1], + RENDER_ESAI); + if (ret) { + dev_err(component->dev, + "create component failed, type = %d, err = %d\n", + RENDER_ESAI, ret); + goto err_comp0_create; + } + + /* ...add component into pipeline */ + ret = xaf_comp_add(&drv->pipeline, &drv->component[0]); + if (ret) { + dev_err(component->dev, + "add component failed, type = %d, err = %d\n", + drv->codec_type, ret); + goto err_comp1_create; + } + + ret = xaf_comp_add(&drv->pipeline, &drv->component[1]); + if (ret) { + dev_err(component->dev, + "add component failed, type = %d, err = %d\n", + drv->codec_type, ret); + goto err_comp1_create; + } + + drv->client->input_bytes = 0; + drv->client->consume_bytes = 0; + + s_param.id = XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE; + s_param.mixData.value = params->codec.sample_rate; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_CHANNELS; + s_param.mixData.value = params->codec.ch_out; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_PCM_WIDTH; + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + return 0; + +err_comp1_create: + xaf_comp_delete(drv->client, &drv->component[1]); +err_comp0_create: + xaf_comp_delete(drv->client, &drv->component[0]); +err_pool_alloc: + xf_pool_free(drv->client, p_proxy->aux); + + return ret; +} + +static int dsp_platform_compr_trigger_start(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int ret; + + ret = xaf_comp_process(drv->client, + p_comp, + p_comp->inptr, + drv->client->input_bytes, + XF_EMPTY_THIS_BUFFER); + + ret = xaf_connect(drv->client, + &drv->component[0], + &drv->component[1], + 1, + OUTBUF_SIZE); + if (ret) { + dev_err(component->dev, "Failed to connect component, err = %d\n", ret); + return ret; + } + + schedule_work(&drv->client->work); + + return 0; +} + +static int dsp_platform_compr_trigger_stop(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + int ret; + + ret = xaf_comp_flush(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_flush(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_delete(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_delete(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + + return 0; +} + +static int dsp_platform_compr_trigger_drain(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int ret; + + ret = xaf_comp_process(drv->client, p_comp, NULL, 0, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + return 0; +} + +static int dsp_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = dsp_platform_compr_trigger_start(cstream); + break; + case SNDRV_PCM_TRIGGER_STOP: + ret = dsp_platform_compr_trigger_stop(cstream); + break; + case SND_COMPR_TRIGGER_DRAIN: + ret = dsp_platform_compr_trigger_drain(cstream); + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + } + + /*send command*/ + return ret; +} + +static int dsp_platform_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + + tstamp->copied_total = drv->client->input_bytes; + tstamp->byte_offset = drv->client->input_bytes; + tstamp->pcm_frames = 0x900; + tstamp->pcm_io_frames = 0, + tstamp->sampling_rate = 48000; + + return 0; +} + +static int dsp_platform_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (drv->client->input_bytes == drv->client->consume_bytes) { + if (count > INBUF_SIZE){ + ret = copy_from_user(p_comp->inptr, buf, INBUF_SIZE); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = INBUF_SIZE; + } else { + ret = copy_from_user(p_comp->inptr, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + } + drv->client->input_bytes += copied; + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr, copied, + XF_EMPTY_THIS_BUFFER); + schedule_work(&drv->client->work); + } + } + + return copied; +} + +static int dsp_platform_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + + return 0; +} + +static struct snd_compr_codec_caps caps_mp3 = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static struct snd_compr_codec_caps caps_aac = { + .num_descriptors = 2, + .descriptor[1].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[1].bit_rate[0] = 320, + .descriptor[1].bit_rate[1] = 192, + .descriptor[1].num_bitrates = 2, + .descriptor[1].profiles = 0, + .descriptor[1].modes = 0, + .descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | + SND_AUDIOSTREAMFORMAT_RAW), +}; + +static int dsp_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_MP3) + *codec = caps_mp3; + else if (codec->codec == SND_AUDIOCODEC_AAC) + *codec = caps_aac; + else + return -EINVAL; + + return 0; +} + +static int dsp_platform_compr_set_metadata(struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + return 0; +} + +const struct snd_compr_ops dsp_platform_compr_ops = { + .open = dsp_platform_compr_open, + .free = dsp_platform_compr_free, + .set_params = dsp_platform_compr_set_params, + .set_metadata = dsp_platform_compr_set_metadata, + .trigger = dsp_platform_compr_trigger, + .pointer = dsp_platform_compr_pointer, + .copy = dsp_platform_compr_copy, + .get_caps = dsp_platform_compr_get_caps, + .get_codec_caps = dsp_platform_compr_get_codec_caps, +}; diff --git a/sound/soc/fsl/fsl_dsp_pool.c b/sound/soc/fsl/fsl_dsp_pool.c new file mode 100644 index 000000000000..637454d97231 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_pool.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Xtensa buffer pool API +// +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#include <linux/slab.h> + +#include "fsl_dsp_pool.h" +#include "fsl_dsp.h" + +/* ...allocate buffer pool */ +int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, + u32 number, u32 length, xf_pool_type_t type, + struct xf_pool **pool) +{ + struct xf_pool *p; + struct xf_buffer *b; + void *data; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...basic sanity checks; number of buffers is positive */ + if (number <=0) + return -EINVAL; + + /* ...get properly aligned buffer length */ + length = ALIGN(length, XF_PROXY_ALIGNMENT); + + p = kzalloc(offsetof(struct xf_pool, buffer) + + number * sizeof(struct xf_buffer), GFP_KERNEL); + if(!p) + return -ENOMEM; + + /* ...prepare command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_ALLOC; + msg.length = length * number; + msg.buffer = NULL; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) { + kfree(p); + return PTR_ERR(rmsg); + } + + p->p = rmsg->buffer; + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...if operation is failed, do cleanup */ + /* ...set pool parameters */ + p->number = number, p->length = length; + p->proxy = proxy; + + /* ...create individual buffers and link them into free list */ + for (p->free = b = &p->buffer[0], data = p->p; number > 0; + number--, b++) { + /* ...set address of the buffer (no length there) */ + b->address = data; + + /* ...file buffer into the free list */ + b->link.next = b + 1; + + /* ...advance data pointer in contiguous buffer */ + data += length; + } + + /* ...terminate list of buffers (not too good - tbd) */ + b[-1].link.next = NULL; + + /* ...return buffer pointer */ + *pool = p; + + return 0; +} +/* ...buffer pool destruction */ +int xf_pool_free(struct xf_client *client, struct xf_pool *pool) +{ + struct xf_proxy *proxy; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...basic sanity checks; pool is positive */ + if (pool == NULL) + return -EINVAL; + + /* ...get proxy pointer */ + if ((proxy = pool->proxy) == NULL) + return -EINVAL; + + /* ...prepare command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_FREE; + msg.length = pool->length * pool->number; + msg.buffer = pool->p; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + kfree(pool); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + return 0; +} + +/* ...get new buffer from a pool */ +struct xf_buffer *xf_buffer_get(struct xf_pool *pool) +{ + struct xf_buffer *b; + + xf_lock(&pool->proxy->lock); + /* ...take buffer from a head of the free list */ + b = pool->free; + if (b) { + /* ...advance free list head */ + pool->free = b->link.next, b->link.pool = pool; + } + + xf_unlock(&pool->proxy->lock); + return b; +} + +/* ...return buffer back to pool */ +void xf_buffer_put(struct xf_buffer *buffer) +{ + struct xf_pool *pool = buffer->link.pool; + + xf_lock(&pool->proxy->lock); + /* ...use global proxy lock for pool operations protection */ + /* ...put buffer back to a pool */ + buffer->link.next = pool->free, pool->free = buffer; + + xf_unlock(&pool->proxy->lock); +} diff --git a/sound/soc/fsl/fsl_dsp_pool.h b/sound/soc/fsl/fsl_dsp_pool.h new file mode 100644 index 000000000000..4a56262faf7f --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_pool.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Xtensa buffer pool API header + * + * Copyright 2018 NXP + * Copyright (c) 2012-2013 by Tensilica Inc + */ +#ifndef FSL_DSP_POOL_H +#define FSL_DSP_POOL_H + +#include <linux/types.h> +#include "fsl_dsp_proxy.h" + +/* ...buffer pool type */ +typedef u32 xf_pool_type_t; + +/* ...previous declaration of struct */ +struct xf_buffer; +struct xf_pool; +struct xf_handle; +struct xf_message; +struct xf_client; + +/* ...response callback */ +typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg); + +/* ...buffer pool type */ +enum xf_pool_type { + XF_POOL_AUX = 0, + XF_POOL_INPUT = 1, + XF_POOL_OUTPUT = 2 +}; + +/* ...buffer link pointer */ +union xf_buffer_link { + /* ...pointer to next free buffer in a pool (for free buffer) */ + struct xf_buffer *next; + /* ...reference to a buffer pool (for allocated buffer) */ + struct xf_pool *pool; +}; + +/* ...buffer descriptor */ +struct xf_buffer { + /* ...virtual address of contiguous buffer */ + void *address; + /* ...link pointer */ + union xf_buffer_link link; +}; + +/* ...buffer pool */ +struct xf_pool { + /* ...reference to proxy data */ + struct xf_proxy *proxy; + /* ...length of individual buffer in a pool */ + u32 length; + /* ...number of buffers in a pool */ + u32 number; + /* ...pointer to pool memory */ + void *p; + /* ...pointer to first free buffer in a pool */ + struct xf_buffer *free; + /* ...individual buffers */ + struct xf_buffer buffer[0]; +}; + +/* component handle */ +struct xf_handle { + /* ...reference to proxy data */ + struct xf_proxy *proxy; + /* ...auxiliary control buffer for control transactions */ + struct xf_buffer *aux; + /* ...global client-id of the component */ + u32 id; + /* ...local client number (think about merging into "id" field - tbd) */ + u32 client; + /* ...response processing hook */ + xf_response_cb response; +}; + +/* ...accessor to buffer data */ +static inline void *xf_buffer_data(struct xf_buffer *buffer) +{ + return buffer->address; +} + +/* ...length of buffer data */ +static inline size_t xf_buffer_length(struct xf_buffer *buffer) +{ + struct xf_pool *pool = buffer->link.pool; + + return (size_t)pool->length; +} + +/* ...component client-id (global scope) */ +static inline u32 xf_handle_id(struct xf_handle *handle) +{ + return handle->id; +} + +/* ...pointer to auxiliary buffer */ +static inline void *xf_handle_aux(struct xf_handle *handle) +{ + return xf_buffer_data(handle->aux); +} + +int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, u32 number, + u32 length, xf_pool_type_t type, struct xf_pool **pool); +int xf_pool_free(struct xf_client *client, struct xf_pool *pool); + +struct xf_buffer *xf_buffer_get(struct xf_pool *pool); +void xf_buffer_put(struct xf_buffer *buffer); + +#endif /* FSL_DSP_POOL_H */ diff --git a/sound/soc/fsl/fsl_dsp_proxy.c b/sound/soc/fsl/fsl_dsp_proxy.c new file mode 100644 index 000000000000..266a50deab91 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// DSP proxy driver transfers messages between DSP driver and DSP framework +// +// Copyright 2018 NXP +// Copyright (C) 2017 Cadence Design Systems, Inc. + +#include "fsl_dsp_proxy.h" +#include "fsl_dsp.h" + +/* ...initialize message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue) +{ + queue->head = queue->tail = NULL; +} + +/* ...get message queue head */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue) +{ + return queue->head; +} + +/* ...allocate new message from the pool */ +struct xf_message *xf_msg_alloc(struct xf_proxy *proxy) +{ + struct xf_message *m = proxy->free; + + /* ...make sure we have a free message item */ + if (m != NULL) { + /* ...get message from the pool */ + proxy->free = m->next, m->next = NULL; + } + + return m; +} + +/* ...return message to the pool of free items */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m) +{ + /* ...put message into the head of free items list */ + m->next = proxy->free, proxy->free = m; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); +} + +/* ...return all messages from the queue to the pool of free items */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + queue->tail->next = proxy->free; + proxy->free = queue->head; + queue->head = queue->tail = NULL; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); + } +} + +/* ...submit message to a queue */ +int xf_msg_enqueue(struct xf_msg_queue *queue, struct xf_message *m) +{ + int first = (queue->head == NULL); + + /* ...set pointer to next item */ + m->next = NULL; + + /* ...advance head/tail pointer as required */ + if (first) + queue->head = m; + else + queue->tail->next = m; + + /* ...new tail points to this message */ + queue->tail = m; + + return first; +} + +/* ...retrieve next message from the per-task queue */ +struct xf_message *xf_msg_dequeue(struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + /* ...pop message from the head of the list */ + queue->head = m->next; + if (queue->head == NULL) + queue->tail = NULL; + } + + return m; +} + +/* ...helper function for requesting execution message from a pool */ +struct xf_message *xf_msg_available(struct xf_proxy *proxy) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to allocate the message */ + m = xf_msg_alloc(proxy); + if (m == NULL) { + /* ...failed to allocate message; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if successfully allocated */ + return m; +} + +/* ...helper function for receiving a message from per-client queue */ +struct xf_message *xf_msg_received(struct xf_proxy *proxy, + struct xf_msg_queue *queue) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to peek message from the queue */ + m = xf_msg_dequeue(queue); + if (m == NULL) { + /* ...queue is empty; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if message is non-null, lock is held */ + return m; +} + +/* + * MU related functions + */ +u32 icm_intr_send(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg); + return 0; +} + +int icm_intr_extended_send(struct xf_proxy *proxy, + u32 msg, + struct dsp_ext_msg *ext_msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + + msghdr.allbits = msg; + if (msghdr.size != 8) + dev_err(dev, "too much ext msg\n"); + + MU_SendMessage(dsp_priv->mu_base_virtaddr, 1, ext_msg->phys); + MU_SendMessage(dsp_priv->mu_base_virtaddr, 2, ext_msg->size); + MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg); + + return 0; +} + +int send_dpu_ext_msg_addr(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + union icm_header_t msghdr; + struct dsp_ext_msg ext_msg; + struct dsp_mem_msg *dpu_ext_msg = + (struct dsp_mem_msg *)((unsigned char *)dsp_priv->msg_buf_virt + + (MSG_BUF_SIZE / 2)); + int ret_val = 0; + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = ICM_CORE_INIT; + msghdr.size = 8; + ext_msg.phys = dsp_priv->msg_buf_phys + (MSG_BUF_SIZE / 2); + ext_msg.size = sizeof(struct dsp_mem_msg); + + dpu_ext_msg->ext_msg_phys = dsp_priv->msg_buf_phys; + dpu_ext_msg->ext_msg_size = MSG_BUF_SIZE; + dpu_ext_msg->scratch_phys = dsp_priv->scratch_buf_phys; + dpu_ext_msg->scratch_size = dsp_priv->scratch_buf_size; + dpu_ext_msg->dsp_config_phys = dsp_priv->dsp_config_phys; + dpu_ext_msg->dsp_config_size = dsp_priv->dsp_config_size; + dpu_ext_msg->dsp_board_type = dsp_priv->dsp_board_type; + + icm_intr_extended_send(proxy, msghdr.allbits, &ext_msg); + + return ret_val; +} + +long icm_ack_wait(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + int err; + + msghdr.allbits = msg; + /* wait response from mu */ + err = wait_for_completion_timeout(&proxy->cmd_complete, + msecs_to_jiffies(1000)); + if (!err) { + dev_err(dev, "icm ack timeout! %x\n", msg); + return -ETIMEDOUT; + } + + dev_dbg(dev, "Ack recd for message 0x%08x\n", msghdr.allbits); + + return 0; +} + +irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id) +{ + struct xf_proxy *proxy = dev_id; + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + u32 reg; + + MU_ReceiveMsg(dsp_priv->mu_base_virtaddr, 0, ®); + msghdr = (union icm_header_t)reg; + + if (msghdr.intr == 1) { + dev_dbg(dev, "INTR: Received ICM intr, msg 0x%08x\n", + msghdr.allbits); + switch (msghdr.msg) { + case ICM_CORE_EXIT: + break; + case ICM_CORE_READY: + send_dpu_ext_msg_addr(proxy); + proxy->is_ready = 1; + complete(&proxy->cmd_complete); + break; + case XF_SUSPEND: + case XF_RESUME: + complete(&proxy->cmd_complete); + break; + default: + schedule_work(&proxy->work); + break; + } + } else if (msghdr.ack == 1) { + dev_dbg(dev, "INTR: Received ICM ack 0x%08x\n", msghdr.size); + msghdr.ack = 0; + } else { + dev_dbg(dev, "Received false ICM intr 0x%08x\n", + msghdr.allbits); + } + + return IRQ_HANDLED; +} + +/* + * Proxy related functions + */ +/* ...NULL-address specification */ +#define XF_PROXY_NULL (~0U) + +#define XF_PROXY_BADADDR (dsp_priv->scratch_buf_size) + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (b == NULL) + return XF_PROXY_NULL; + else if ((u32)(b - dsp_priv->scratch_buf_virt) < + dsp_priv->scratch_buf_size) + return (u32)(b - dsp_priv->scratch_buf_virt); + else + return XF_PROXY_BADADDR; +} + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (address < dsp_priv->scratch_buf_size) + return dsp_priv->scratch_buf_virt + address; + else if (address == XF_PROXY_NULL) + return NULL; + else + return (void *) -1; +} + +/* ...process association between response received and intended client */ +static void xf_cmap(struct xf_proxy *proxy, struct xf_message *m) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + u32 id = XF_AP_IPC_CLIENT(m->id); + struct xf_client *client; + + /* ...process messages addressed to proxy itself */ + if (id == 0) { + /* ...place message into local response queue */ + xf_msg_enqueue(&proxy->response, m); + wake_up(&proxy->wait); + return; + } + + /* ...make sure the client ID is sane */ + client = xf_client_lookup(dsp_priv, id); + if (!client) { + pr_err("rsp[id:%08x]: client lookup failed", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...make sure client is bound to this proxy interface */ + if (client->proxy != proxy) { + pr_err("rsp[id:%08x]: wrong proxy interface", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...place message into local response queue */ + if (xf_msg_enqueue(&client->queue, m)) + wake_up(&client->wait); +} + +/* ...retrieve pending responses from shared memory ring-buffer */ +static u32 xf_shmem_process_responses(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status; + + status = 0; + + /* ...get current values of read/write pointers in response queue */ + read_idx = XF_PROXY_READ(proxy, rsp_read_idx); + write_idx = XF_PROXY_READ(proxy, rsp_write_idx); + + /* ...process all committed responses */ + while (!XF_QUEUE_EMPTY(read_idx, write_idx)) { + struct xf_proxy_message *response; + + /* ...allocate execution message */ + m = xf_msg_alloc(proxy); + if (m == NULL) + break; + + /* ...mark the interface status has changed */ + status |= (XF_QUEUE_FULL(read_idx, write_idx) ? 0x3 : 0x1); + + /* ...get oldest not yet processed response */ + response = XF_PROXY_RESPONSE(proxy, XF_QUEUE_IDX(read_idx)); + + /* ...fill message parameters */ + m->id = response->session_id; + m->opcode = response->opcode; + m->length = response->length; + m->buffer = xf_proxy_a2b(proxy, response->address); + m->ret = response->ret; + + /* ...advance local reading index copy */ + read_idx = XF_QUEUE_ADVANCE_IDX(read_idx); + + /* ...update shadow copy of reading index */ + XF_PROXY_WRITE(proxy, rsp_read_idx, read_idx); + + /* ...submit message to proper client */ + xf_cmap(proxy, m); + } + + return status; +} + +/* ...put pending commands into shared memory ring-buffer */ +static u32 xf_shmem_process_commands(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status = 0; + + /* ...get current value of peer read pointer */ + write_idx = XF_PROXY_READ(proxy, cmd_write_idx); + read_idx = XF_PROXY_READ(proxy, cmd_read_idx); + + /* ...submit any pending commands */ + while (!XF_QUEUE_FULL(read_idx, write_idx)) { + struct xf_proxy_message *command; + + /* ...check if we have a pending command */ + m = xf_msg_dequeue(&proxy->command); + if (m == NULL) + break; + + /* ...always mark the interface status has changed */ + status |= 0x3; + + /* ...select the place for the command */ + command = XF_PROXY_COMMAND(proxy, XF_QUEUE_IDX(write_idx)); + + /* ...put the response message fields */ + command->session_id = m->id; + command->opcode = m->opcode; + command->length = m->length; + command->address = xf_proxy_b2a(proxy, m->buffer); + command->ret = m->ret; + + /* ...return message back to the pool */ + xf_msg_free(proxy, m); + + /* ...advance local writing index copy */ + write_idx = XF_QUEUE_ADVANCE_IDX(write_idx); + + /* ...update shared copy of queue write pointer */ + XF_PROXY_WRITE(proxy, cmd_write_idx, write_idx); + } + + if (status) + icm_intr_send(proxy, 0); + + return status; +} + +/* ...shared memory interface maintenance routine */ +void xf_proxy_process(struct work_struct *w) +{ + struct xf_proxy *proxy = container_of(w, struct xf_proxy, work); + int status = 0; + + /* ...get exclusive access to internal data */ + xf_lock(&proxy->lock); + + do { + /* ...process outgoing commands first */ + status = xf_shmem_process_commands(proxy); + + /* ...process all pending responses */ + status |= xf_shmem_process_responses(proxy); + + } while (status); + + /* ...unlock internal proxy data */ + xf_unlock(&proxy->lock); +} + +/* ...initialize shared memory interface */ +int xf_proxy_init(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct xf_message *m; + int i; + + /* ...create a list of all messages in a pool; set head pointer */ + proxy->free = &proxy->pool[0]; + + /* ...put all messages into a single-linked list */ + for (i = 0, m = proxy->free; i < XF_CFG_MESSAGE_POOL_SIZE - 1; i++, m++) + m->next = m + 1; + + /* ...set list tail pointer */ + m->next = NULL; + + /* ...initialize proxy lock */ + xf_lock_init(&proxy->lock); + + /* ...initialize proxy thread message queues */ + xf_msg_queue_init(&proxy->command); + xf_msg_queue_init(&proxy->response); + + /* ...initialize global busy queue */ + init_waitqueue_head(&proxy->busy); + init_waitqueue_head(&proxy->wait); + + /* ...create work structure */ + INIT_WORK(&proxy->work, xf_proxy_process); + + /* ...set pointer to shared memory */ + proxy->ipc.shmem = (struct xf_shmem_data *)dsp_priv->msg_buf_virt; + + /* ...initialize shared memory interface */ + XF_PROXY_WRITE(proxy, cmd_read_idx, 0); + XF_PROXY_WRITE(proxy, cmd_write_idx, 0); + XF_PROXY_WRITE(proxy, cmd_invalid, 0); + XF_PROXY_WRITE(proxy, rsp_read_idx, 0); + XF_PROXY_WRITE(proxy, rsp_write_idx, 0); + XF_PROXY_WRITE(proxy, rsp_invalid, 0); + + return 0; +} + +/* ...trigger shared memory interface processing */ +void xf_proxy_notify(struct xf_proxy *proxy) +{ + schedule_work(&proxy->work); +} + +/* ...submit a command to proxy pending queue (lock released upon return) */ +void xf_proxy_command(struct xf_proxy *proxy, struct xf_message *m) +{ + int first; + + /* ...submit message to proxy thread */ + first = xf_msg_enqueue(&proxy->command, m); + + /* ...release the lock */ + xf_unlock(&proxy->lock); + + /* ...notify thread about command reception */ + (first ? xf_proxy_notify(proxy), 1 : 0); +} + +/* + * Proxy cmd send and receive functions + */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + ret = wait_event_interruptible(proxy->busy, + (m = xf_msg_available(proxy)) != NULL); + if (ret) + return -EINTR; + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + return 0; +} + +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait) +{ + struct xf_message *m = NULL; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait + || !proxy->is_active); + if (ret) + return ERR_PTR(-EINTR); + + /* ...return message with a lock taken */ + return m; +} + +struct xf_message *xf_cmd_recv_timeout(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, int wait) +{ + struct xf_message *m; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible_timeout(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait, + msecs_to_jiffies(1000)); + if (ret < 0) + return ERR_PTR(-EINTR); + + if (ret == 0) + return ERR_PTR(-ETIMEDOUT); + + /* ...return message with a lock taken */ + return m; +} + +/* ...helper function for synchronous command execution */ +struct xf_message *xf_cmd_send_recv(struct xf_proxy *proxy, + u32 id, u32 opcode, + void *buffer, + u32 length) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, &proxy->wait, &proxy->response, 1); +} + +struct xf_message *xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, + u32 opcode, void *buffer, u32 length, + wait_queue_head_t *wq, + struct xf_msg_queue *queue) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, wq, queue, 1); +} + +struct xf_message *xf_cmd_send_recv_complete(struct xf_client *client, + struct xf_proxy *proxy, + u32 id, u32 opcode, void *buffer, + u32 length, + struct work_struct *work, + struct completion *completion) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + m = xf_msg_available(proxy); + if (!m) + return ERR_PTR(-EBUSY); + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + init_completion(completion); + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + schedule_work(work); + + /* ...wait for message reception (take lock on success) */ + ret = wait_for_completion_timeout(completion, + msecs_to_jiffies(1000)); + if (!ret) + return ERR_PTR(-ETIMEDOUT); + + m = &client->m; + + /* ...return message with a lock taken */ + return m; +} +/* + * Proxy allocate and free memory functions + */ +/* ...allocate memory buffer for kernel use */ +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...send command to remote proxy */ + m = xf_cmd_send_recv(proxy, id, XF_ALLOC, NULL, length); + if (IS_ERR(m)) { + xf_unlock(&proxy->lock); + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_ALLOC && m->buffer != NULL) { + *buffer = m->buffer; + ret = 0; + } else { + ret = -ENOMEM; + } + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + xf_unlock(&proxy->lock); + + return ret; +} + +/* ...free memory buffer */ +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...synchronously execute freeing command */ + m = xf_cmd_send_recv(proxy, id, XF_FREE, buffer, length); + if (IS_ERR(m)) { + xf_unlock(&proxy->lock); + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_FREE) + ret = 0; + else + ret = -EINVAL; + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + xf_unlock(&proxy->lock); + + return ret; +} + +/* + * suspend & resume functions + */ +int xf_cmd_send_suspend(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_SUSPEND; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +int xf_cmd_send_resume(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_RESUME; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +/* ...open component handle */ +int xf_open(struct xf_client *client, struct xf_proxy *proxy, + struct xf_handle *handle, const char *id, u32 core, + xf_response_cb response) +{ + void *b; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...retrieve auxiliary control buffer from proxy - need I */ + handle->aux = xf_buffer_get(proxy->aux); + + b = xf_handle_aux(handle); + + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_REGISTER; + msg.buffer = b; + msg.length = strlen(id) + 1; + msg.ret = 0; + + /* ...copy component identifier */ + memcpy(b, (void *)id, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if (IS_ERR(rmsg)) { + xf_buffer_put(handle->aux), handle->aux = NULL; + return PTR_ERR(rmsg); + } + /* ...save received component global client-id */ + handle->id = XF_MSG_SRC(rmsg->id); + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...if failed, release buffer handle */ + /* ...operation completed successfully; assign handle data */ + handle->response = response; + handle->proxy = proxy; + + return 0; +} + +/* ...close component handle */ +int xf_close(struct xf_client *client, struct xf_handle *handle) +{ + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...do I need to take component lock here? guess no - tbd */ + + /* ...buffers and stuff? - tbd */ + + /* ...acquire global proxy lock */ + /* ...unregister component from remote DSP proxy (ignore result code) */ + + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), handle->id); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNREGISTER; + msg.buffer = NULL; + msg.length = 0; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if (IS_ERR(rmsg)) { + xf_buffer_put(handle->aux), handle->aux = NULL; + return PTR_ERR(rmsg); + } + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...wipe out proxy pointer */ + handle->proxy = NULL; + + return 0; +} diff --git a/sound/soc/fsl/fsl_dsp_proxy.h b/sound/soc/fsl/fsl_dsp_proxy.h new file mode 100644 index 000000000000..bc9ccf37bc8f --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.h @@ -0,0 +1,520 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * DSP proxy header - commands/responses from DSP driver to DSP ramework + * + * Copyright 2018 NXP + * Copyright (c) 2017 Cadence Design Systems, Inc. + */ + +#ifndef __FSL_DSP_PROXY_H +#define __FSL_DSP_PROXY_H + +#include <linux/wait.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> +#include <linux/compiler.h> +#include <linux/dma-mapping.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/mx8_mu.h> +#include <linux/interrupt.h> + +#include "fsl_dsp_pool.h" +#define XF_CFG_MESSAGE_POOL_SIZE 256 + +struct xf_client; + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_message; +struct xf_handle; +typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg); + +/* ...execution message */ +struct xf_message { + /* ...pointer to next message in a list */ + struct xf_message *next; + + /* ...session-id */ + u32 id; + + /* ...operation code */ + u32 opcode; + + /* ...length of data buffer */ + u32 length; + + /* ...translated data pointer */ + void *buffer; + + /* ...return message status */ + u32 ret; +}; + +/* ...message queue */ +struct xf_msg_queue { + /* ...pointer to list head */ + struct xf_message *head; + + /* ...pointer to list tail */ + struct xf_message *tail; +}; + +struct xf_proxy_message { + /* ...session ID */ + u32 session_id; + + /* ...proxy API command/response code */ + u32 opcode; + + /* ...length of attached buffer */ + u32 length; + + /* ...physical address of message buffer */ + u32 address; + + /* ...return message status */ + u32 ret; +}; +/**********************************************************************/ + +enum icm_action_t { + ICM_CORE_READY = 1, + ICM_CORE_INIT, + ICM_CORE_EXIT, +}; + +/* ...adjust IPC client of message going from user-space */ +#define XF_MSG_AP_FROM_USER(id, client) (((id) & ~(0xF << 2)) | (client << 2)) + + +#define __XF_PORT_SPEC(core, id, port) ((core) | ((id) << 2) | ((port) << 8)) +#define __XF_PORT_SPEC2(id, port) ((id) | ((port) << 8)) + + +/* ...wipe out IPC client from message going to user-space */ +#define XF_MSG_AP_TO_USER(id) ((id) & ~(0xF << 18)) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) + +/* ...message id contains source and destination ports specification */ +#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16)) +#define XF_MSG_SRC(id) (((id) >> 0) & 0xFFFF) +#define XF_MSG_SRC_CORE(id) (((id) >> 0) & 0x3) +#define XF_MSG_SRC_CLIENT(id) (((id) >> 2) & 0x3F) +#define XF_MSG_DST_CLIENT(id) (((id) >> 18) & 0x3F) + +/* ...special treatment of AP-proxy destination field */ +#define XF_AP_IPC_CLIENT(id) (((id) >> 18) & 0xF) +#define XF_AP_CLIENT(id) (((id) >> 22) & 0x1FF) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) +#define __XF_AP_CLIENT(core, client) ((core) | ((client) << 6) | 0x8000) + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + + +/******************************************************************************* + * Ring buffer support + ******************************************************************************/ +/* ...cache-line size on DSP */ +#define XF_PROXY_ALIGNMENT 64 + +/* ...total length of shared memory queue (for commands and responses) */ +#define XF_PROXY_MESSAGE_QUEUE_LENGTH (1 << 6) + +/* ...index mask */ +#define XF_PROXY_MESSAGE_QUEUE_MASK 0x3F + +/* ...ring-buffer index */ +#define __XF_QUEUE_IDX(idx, counter) \ + (((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) | ((counter) << 16)) + +/* ...retrieve ring-buffer index */ +#define XF_QUEUE_IDX(idx) \ + ((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) + +/* ...increment ring-buffer index */ +#define XF_QUEUE_ADVANCE_IDX(idx) \ + (((idx) + 0x10001) & (0xFFFF0000 | XF_PROXY_MESSAGE_QUEUE_MASK)) + +/* ...test if ring buffer is empty */ +#define XF_QUEUE_EMPTY(read, write) \ + ((read) == (write)) + +/* ...test if ring buffer is full */ +#define XF_QUEUE_FULL(read, write) \ + ((write) == (read) + (XF_PROXY_MESSAGE_QUEUE_LENGTH << 16)) + +/* ...basic cache operations */ +#define XF_PROXY_INVALIDATE(addr, len) { } + +#define XF_PROXY_FLUSH(addr, len) { } + +/* ...data managed by host CPU (remote) - in case of shunt it is a IPC layer */ +struct xf_proxy_host_data { + /* ...command queue */ + struct xf_proxy_message command[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into command queue */ + u32 cmd_write_idx; + + /* ...reading index for response queue */ + u32 rsp_read_idx; + + /* ...indicate command queue is valid or not */ + u32 cmd_invalid; +}; + +/* ...data managed by DSP (local) */ +struct xf_proxy_dsp_data { + /* ...response queue */ + struct xf_proxy_message response[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into response queue */ + u32 rsp_write_idx; + + /* ...reading index for command queue */ + u32 cmd_read_idx; + + /* ...indicate response queue is valid or not */ + u32 rsp_invalid; +}; + +/* ...shared memory data */ +struct xf_shmem_data { + /* ...ingoing data (maintained by DSP (local side)) */ + struct xf_proxy_host_data local; + + /* ...outgoing data (maintained by host CPU (remote side)) */ + struct xf_proxy_dsp_data remote; + +}; + +/* ...shared memory data accessor */ +#define XF_SHMEM_DATA(proxy) \ + ((proxy)->ipc.shmem) + +/* ...atomic reading */ +#define __XF_PROXY_READ_ATOMIC(var) \ + ({ XF_PROXY_INVALIDATE(&(var), sizeof(var)); \ + *(u32 *)&(var); }) + +/* ...atomic writing */ +#define __XF_PROXY_WRITE_ATOMIC(var, value) \ + ({*(u32 *)&(var) = (value); \ + XF_PROXY_FLUSH(&(var), sizeof(var)); \ + (value); }) + +/* ...accessors */ +#define XF_PROXY_READ(proxy, field) \ + __XF_PROXY_READ_##field(XF_SHMEM_DATA(proxy)) + +#define XF_PROXY_WRITE(proxy, field, v) \ + __XF_PROXY_WRITE_##field(XF_SHMEM_DATA(proxy), (v)) + +/* ...individual fields reading */ +#define __XF_PROXY_READ_cmd_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_write_idx) + +#define __XF_PROXY_READ_cmd_read_idx(shmem) \ + shmem->remote.cmd_read_idx + +#define __XF_PROXY_READ_cmd_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_invalid) + +#define __XF_PROXY_READ_rsp_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_write_idx) + +#define __XF_PROXY_READ_rsp_read_idx(shmem) \ + shmem->local.rsp_read_idx + +#define __XF_PROXY_READ_rsp_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_invalid) + +/* ...individual fields writings */ +#define __XF_PROXY_WRITE_cmd_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_write_idx, v) + +#define __XF_PROXY_WRITE_cmd_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.cmd_read_idx, v) + +#define __XF_PROXY_WRITE_cmd_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_invalid, v) + +#define __XF_PROXY_WRITE_rsp_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.rsp_read_idx, v) + +#define __XF_PROXY_WRITE_rsp_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_write_idx, v) + +#define __XF_PROXY_WRITE_rsp_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_invalid, v) + +/* ...command buffer accessor */ +#define XF_PROXY_COMMAND(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->local.command[(idx)]) + +/* ...response buffer accessor */ +#define XF_PROXY_RESPONSE(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->remote.response[(idx)]) + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_proxy_ipc_data { + /* ...shared memory data pointer */ + struct xf_shmem_data __iomem *shmem; + + /* ...core identifier */ + u32 core; + + /* ...IPC registers memory */ + void __iomem *regs; +}; + +/* ...proxy data */ +struct xf_proxy { + /* ...IPC layer data */ + struct xf_proxy_ipc_data ipc; + + /* ...shared memory status change processing item */ + struct work_struct work; + + struct completion cmd_complete; + int is_ready; + int is_active; + + /* ...internal lock */ + spinlock_t lock; + + /* ...busy queue (for clients waiting ON NOTIFIcation) */ + wait_queue_head_t busy; + + /* ...waiting queue for synchronous proxy operations */ + wait_queue_head_t wait; + + /* ...submitted commands queue */ + struct xf_msg_queue command; + + /* ...pending responses queue */ + struct xf_msg_queue response; + + /* ...global message pool */ + struct xf_message pool[XF_CFG_MESSAGE_POOL_SIZE]; + + /* ...pointer to first free message in the pool */ + struct xf_message *free; + + /* ...auxiliary buffer pool for clients */ + struct xf_pool *aux; +}; + +union icm_header_t { + struct { + u32 msg:6; + u32 sub_msg:6; // sub_msg will have ICM_MSG + u32 rsvd:3; /* reserved */ + u32 intr:1; /* intr = 1 when sending msg. */ + u32 size:15; /* =size in bytes (excluding header) */ + u32 ack:1; /* response message when ack=1 */ + }; + u32 allbits; +}; + +struct dsp_ext_msg { + u32 phys; + u32 size; +}; + +struct dsp_mem_msg { + u32 ext_msg_phys; + u32 ext_msg_size; + u32 scratch_phys; + u32 scratch_size; + u32 dsp_config_phys; + u32 dsp_config_size; + u32 dsp_board_type; +}; + +static inline void xf_lock_init(spinlock_t *lock) +{ + spin_lock_init(lock); +} + +static inline void xf_lock(spinlock_t *lock) +{ + spin_lock(lock); +} + +static inline void xf_unlock(spinlock_t *lock) +{ + spin_unlock(lock); +} + +/* ...init proxy */ +int xf_proxy_init(struct xf_proxy *proxy); + +/* ...send message to proxy */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length); + +/* ...get message from proxy */ +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait); + +struct xf_message* +xf_cmd_recv_timeout(struct xf_proxy *proxy, wait_queue_head_t *wq, + struct xf_msg_queue *queue, int wait); + +struct xf_message* +xf_cmd_send_recv(struct xf_proxy *proxy, u32 id, u32 opcode, + void *buffer, u32 length); + +struct xf_message* +xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, u32 opcode, void *buffer, + u32 length, wait_queue_head_t *wq, + struct xf_msg_queue *queue); + +struct xf_message* +xf_cmd_send_recv_complete(struct xf_client *client, struct xf_proxy *proxy, + u32 id, u32 opcode, void *buffer, u32 length, + struct work_struct *work, + struct completion *completion); + +/* ...mu interrupt handle */ +irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id); + +/* ...initialize client pending message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue); + +/* ...return current queue state */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue); + +/* ...return the message back to a pool */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m); + +/* ...release all pending messages */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue); + +/* ...wait mu interrupt */ +long icm_ack_wait(struct xf_proxy *proxy, u32 msg); + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b); + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address); + +int xf_cmd_send_suspend(struct xf_proxy *proxy); +int xf_cmd_send_resume(struct xf_proxy *proxy); + +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length); +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length); + +int xf_open(struct xf_client *client, struct xf_proxy *proxy, + struct xf_handle *handle, const char *id, u32 core, + xf_response_cb response); + +int xf_close(struct xf_client *client, struct xf_handle *handle); + + + +/******************************************************************************* + * Opcode composition + ******************************************************************************/ + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...accessors */ +#define XF_OPCODE_CDATA(opcode) ((opcode) & (1 << 31)) +#define XF_OPCODE_RDATA(opcode) ((opcode) & (1 << 30)) +#define XF_OPCODE_TYPE(opcode) ((opcode) & (0x3F)) + +/******************************************************************************* + * Opcode types + ******************************************************************************/ + +/* ...unregister client */ +#define XF_UNREGISTER __XF_OPCODE(0, 0, 0) + +/* ...register client at proxy */ +#define XF_REGISTER __XF_OPCODE(1, 0, 1) + +/* ...port routing command */ +#define XF_ROUTE __XF_OPCODE(1, 0, 2) + +/* ...port unrouting command */ +#define XF_UNROUTE __XF_OPCODE(1, 0, 3) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...set component parameters */ +#define XF_SET_PARAM __XF_OPCODE(1, 0, 6) + +/* ...get component parameters */ +#define XF_GET_PARAM __XF_OPCODE(1, 1, 7) + +/* ...input buffer reception */ +#define XF_EMPTY_THIS_BUFFER __XF_OPCODE(1, 0, 8) + +/* ...output buffer reception */ +#define XF_FILL_THIS_BUFFER __XF_OPCODE(0, 1, 9) + +/* ...flush specific port */ +#define XF_FLUSH __XF_OPCODE(0, 0, 10) + +/* ...start component operation */ +#define XF_START __XF_OPCODE(0, 0, 11) + +/* ...stop component operation */ +#define XF_STOP __XF_OPCODE(0, 0, 12) + +/* ...pause component operation */ +#define XF_PAUSE __XF_OPCODE(0, 0, 13) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + +/* ...load lib for component operation */ +#define XF_LOAD_LIB __XF_OPCODE(0, 0, 16) + +/* ...unload lib for component operation */ +#define XF_UNLOAD_LIB __XF_OPCODE(0, 0, 17) + +/* ...component output eos operation */ +#define XF_OUTPUT_EOS __XF_OPCODE(0, 0, 18) + +/* ...total amount of supported decoder commands */ +#define __XF_OP_NUM 19 + +#endif diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.c b/sound/soc/fsl/fsl_dsp_xaf_api.c new file mode 100644 index 000000000000..4312a6b29ef1 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_xaf_api.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// Xtensa Audio Framework API for communication with DSP +// +// Copyright (C) 2017 Cadence Design Systems, Inc. +// Copyright 2018 NXP + +#include "fsl_dsp.h" +#include "fsl_dsp_xaf_api.h" + +/* ...send a command message to component */ +int xf_command(struct xf_client *client, struct xf_handle *handle, + u32 port, u32 opcode, void *buffer, u32 length) +{ + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + + /* ...fill-in message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(handle->id, port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = opcode; + msg.length = length; + msg.buffer = buffer; + msg.ret = 0; + + /* ...execute command synchronously */ + return xf_cmd_send(proxy, msg.id, msg.opcode, msg.buffer, msg.length); +} + +int xaf_comp_set_config(struct xf_client *client, struct xaf_comp *p_comp, + u32 num_param, void *p_param) +{ + struct xf_handle *p_handle; + struct xf_message msg; + struct xf_message *rmsg; + struct xf_set_param_msg *smsg; + struct xf_set_param_msg *param = (struct xf_set_param_msg *)p_param; + struct xf_proxy *proxy; + u32 i; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + /* ...set persistent stream characteristics */ + smsg = xf_buffer_data(p_handle->aux); + + for (i = 0; i < num_param; i++) { + smsg[i].id = param[i].id; + smsg[i].mixData.value = param[i].mixData.value; + } + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_SET_PARAM; + msg.length = sizeof(*smsg) * num_param; + msg.buffer = smsg; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + /* ...save received component global client-id */ + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); + */ + + /* ...make sure response is expected */ + if ((rmsg->opcode != XF_SET_PARAM) || (rmsg->buffer != smsg)) { + return -EPIPE; + } + + return 0; +} + +int xaf_comp_get_config(struct xf_client *client, struct xaf_comp *p_comp, + u32 num_param, void *p_param) +{ + + struct xf_handle *p_handle; + struct xf_message msg; + struct xf_message *rmsg; + struct xf_get_param_msg *smsg; + struct xf_get_param_msg *param = (struct xf_get_param_msg *)p_param; + struct xf_proxy *proxy; + u32 i; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + /* ...set persistent stream characteristics */ + smsg = xf_buffer_data(p_handle->aux); + + for (i = 0; i < num_param; i++) + smsg[i].id = param[i].id; + + + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_GET_PARAM; + msg.length = sizeof(*smsg) * num_param; + msg.buffer = smsg; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + /* ...save received component global client-id */ + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...make sure response is expected */ + if ((rmsg->opcode != (u32)XF_GET_PARAM) || (rmsg->buffer != smsg)) { + return -EPIPE; + } + + for (i = 0; i < num_param; i++) + param[i].mixData.value = smsg[i].mixData.value; + + return 0; +} + +int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp) +{ + + struct xf_handle *p_handle; + struct xf_proxy *proxy; + struct xf_message msg; + struct xf_message *rmsg; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_FLUSH; + msg.length = 0; + msg.buffer = NULL; + msg.ret = 0; + + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* ...make sure response is expected */ + if ((rmsg->opcode != (u32)XF_FLUSH) || rmsg->buffer) { + return -EPIPE; + } + + return 0; +} + +int xaf_comp_create(struct xf_client *client, struct xf_proxy *proxy, + struct xaf_comp *p_comp, int comp_type) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + char lib_path[200]; + char lib_wrap_path[200]; + struct xf_handle *p_handle; + struct xf_buffer *buf; + int ret = 0; + bool loadlib = true; + + memset((void *)p_comp, 0, sizeof(struct xaf_comp)); + + strcpy(lib_path, "/usr/lib/imx-mm/audio-codec/dsp/"); + strcpy(lib_wrap_path, "/usr/lib/imx-mm/audio-codec/dsp/"); + + p_handle = &p_comp->handle; + + p_comp->comp_type = comp_type; + + if (comp_type == RENDER_ESAI) + loadlib = false; + + if (loadlib) { + p_comp->codec_lib.filename = lib_path; + p_comp->codec_wrap_lib.filename = lib_wrap_path; + p_comp->codec_lib.lib_type = DSP_CODEC_LIB; + } + + switch (comp_type) { + case CODEC_MP3_DEC: + p_comp->dec_id = "audio-decoder/mp3"; + strcat(lib_path, "lib_dsp_mp3_dec.so"); + break; + case CODEC_AAC_DEC: + p_comp->dec_id = "audio-decoder/aac"; + strcat(lib_path, "lib_dsp_aac_dec.so"); + break; + case RENDER_ESAI: + p_comp->dec_id = "renderer/esai"; + break; + + default: + return -EINVAL; + break; + } + + /* ...create decoder component instance (select core-0) */ + ret = xf_open(client, proxy, p_handle, p_comp->dec_id, 0, NULL); + if (ret) { + dev_err(dsp_priv->dev, "create (%s) component error: %d\n", + p_comp->dec_id, ret); + return ret; + } + + if (loadlib) { + strcat(lib_wrap_path, "lib_dsp_codec_wrap.so"); + p_comp->codec_wrap_lib.lib_type = DSP_CODEC_WRAP_LIB; + + /* ...load codec wrapper lib */ + ret = xf_load_lib(client, p_handle, &p_comp->codec_wrap_lib); + if (ret) { + dev_err(dsp_priv->dev, "load codec wrap lib error\n"); + goto err_wrap_load; + } + + /* ...load codec lib */ + ret = xf_load_lib(client, p_handle, &p_comp->codec_lib); + if (ret) { + dev_err(dsp_priv->dev, "load codec lib error\n"); + goto err_codec_load; + } + + /* ...allocate input buffer */ + ret = xf_pool_alloc(client, proxy, 1, INBUF_SIZE, + XF_POOL_INPUT, &p_comp->inpool); + if (ret) { + dev_err(dsp_priv->dev, "alloc input buf error\n"); + goto err_pool_alloc; + } + + /* ...initialize input buffer pointer */ + buf = xf_buffer_get(p_comp->inpool); + p_comp->inptr = xf_buffer_data(buf); + } + + p_comp->active = true; + + return ret; + +err_pool_alloc: + xf_unload_lib(client, p_handle, &p_comp->codec_lib); +err_codec_load: + xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib); +err_wrap_load: + xf_close(client, p_handle); + + return ret; +} + +int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp) +{ + + struct xf_handle *p_handle; + bool loadlib = true; + u32 ret = 0; + + if (!p_comp->active) + return ret; + + /* mark component as unusable from this point */ + p_comp->active = false; + + if (p_comp->comp_type == RENDER_ESAI) + loadlib = false; + + p_handle = &p_comp->handle; + + if (loadlib) { + /* ...unload codec wrapper library */ + xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib); + + /* ...unload codec library */ + xf_unload_lib(client, p_handle, &p_comp->codec_lib); + + xf_pool_free(client, p_comp->inpool); + } + + /* ...delete component */ + xf_close(client, p_handle); + + return ret; +} + +int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, void *p_buf, u32 length, u32 flag) +{ + struct xf_handle *p_handle; + u32 ret = 0; + + p_handle = &p_comp->handle; + + switch (flag) { + case XF_FILL_THIS_BUFFER: + /* ...send message to component output port (port-id=1) */ + ret = xf_command(client, p_handle, 1, XF_FILL_THIS_BUFFER, + p_buf, length); + break; + case XF_EMPTY_THIS_BUFFER: + /* ...send message to component input port (port-id=0) */ + ret = xf_command(client, p_handle, 0, XF_EMPTY_THIS_BUFFER, + p_buf, length); + break; + default: + break; + } + + return ret; +} + +/* ...port binding function */ +int xf_route(struct xf_client *client, struct xf_handle *src, u32 src_port, + struct xf_handle *dst, u32 dst_port, u32 num, u32 size, u32 align) +{ + struct xf_proxy *proxy = src->proxy; + struct xf_buffer *b; + struct xf_route_port_msg *m; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...sanity checks - proxy pointers are same */ + if (proxy != dst->proxy) + return -EINVAL; + + /* ...buffer data is sane */ + if (!(num && size && xf_is_power_of_two(align))) + return -EINVAL; + + /* ...get control buffer */ + if ((b = xf_buffer_get(proxy->aux)) == NULL) + return -EBUSY; + + /* ...get message buffer */ + m = xf_buffer_data(b); + + /* ...fill-in message parameters */ + m->src = __XF_PORT_SPEC2(src->id, src_port); + m->dst = __XF_PORT_SPEC2(dst->id, dst_port); + m->alloc_number = num; + m->alloc_size = size; + m->alloc_align = align; + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), + __XF_PORT_SPEC2(src->id, src_port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_ROUTE; + msg.length = sizeof(*m); + msg.buffer = m; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* ...save received component global client-id */ + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + + /* ...synchronously execute command on remote DSP */ + /* XF_CHK_API(xf_proxy_cmd_exec(proxy, &msg)); */ + + /* ...return buffer to proxy */ + xf_buffer_put(b); + + /* ...check result is successful */ + /* XF_CHK_ERR(msg.opcode == XF_ROUTE, -ENOMEM); */ + + return 0; +} + +/* ...port unbinding function */ +int xf_unroute(struct xf_client *client, struct xf_handle *src, u32 src_port) +{ + struct xf_proxy *proxy = src->proxy; + struct xf_buffer *b; + struct xf_unroute_port_msg *m; + struct xf_message msg; + struct xf_message *rmsg; + int r = 0; + + /* ...get control buffer */ + if((b = xf_buffer_get(proxy->aux)) == NULL) + return -EBUSY; + + /* ...get message buffer */ + m = xf_buffer_data(b); + + /* ...fill-in message parameters */ + m->src = __XF_PORT_SPEC2(src->id, src_port); + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), + __XF_PORT_SPEC2(src->id, src_port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNROUTE; + msg.length = sizeof(*m); + msg.buffer = m; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + /* ...save received component global client-id */ + + /*TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); */ + /* xf_unlock(&proxy->lock); */ + + /* ...return buffer to proxy */ + xf_buffer_put(b); + + return r; +} + +int xaf_connect(struct xf_client *client, + struct xaf_comp *p_src, + struct xaf_comp *p_dest, + u32 num_buf, + u32 buf_length) +{ + /* ...connect p_src output port with p_dest input port */ + return xf_route(client, &p_src->handle, 0, &p_dest->handle, 0, + num_buf, buf_length, 8); +} + +int xaf_disconnect(struct xf_client *client, struct xaf_comp *p_comp) +{ + /* ...disconnect p_src output port with p_dest input port */ + return xf_unroute(client, &p_comp->handle, 0); + +} + +int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp) +{ + int ret = 0; + + p_comp->next = p_pipe->comp_chain; + p_comp->pipeline = p_pipe; + p_pipe->comp_chain = p_comp; + + return ret; +} + +int xaf_pipeline_create(struct xaf_pipeline *p_pipe) +{ + int ret = 0; + + memset(p_pipe, 0, sizeof(struct xaf_pipeline)); + + return ret; +} + +int xaf_pipeline_delete(struct xaf_pipeline *p_pipe) +{ + int ret = 0; + + memset(p_pipe, 0, sizeof(struct xaf_pipeline)); + + return ret; +} diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.h b/sound/soc/fsl/fsl_dsp_xaf_api.h new file mode 100644 index 000000000000..2367b6c9c34d --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_xaf_api.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Xtensa Audio Framework API for communication with DSP + * + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + */ +#ifndef FSL_DSP_XAF_API_H +#define FSL_DSP_XAF_API_H + +#include "fsl_dsp_library_load.h" + +/* ...size of auxiliary pool for communication with DSP */ +#define XA_AUX_POOL_SIZE 32 + +/* ...length of auxiliary pool messages */ +#define XA_AUX_POOL_MSG_LENGTH 128 + +/* ...number of max input buffers */ +#define INBUF_SIZE 4096 +#define OUTBUF_SIZE 16384 + +struct xaf_pipeline; + +struct xaf_info_s { + u32 opcode; + void *buf; + u32 length; + u32 ret; +}; + +struct xaf_comp { + struct xaf_comp *next; + + struct xaf_pipeline *pipeline; + struct xf_handle handle; + + const char *dec_id; + int comp_type; + + struct xf_pool *inpool; + struct xf_pool *outpool; + void *inptr; + void *outptr; + + struct lib_info codec_lib; + struct lib_info codec_wrap_lib; + + int active; /* component fully initialized */ +}; + +struct xaf_pipeline { + struct xaf_comp *comp_chain; + + u32 input_eos; + u32 output_eos; +}; + +int xaf_comp_create(struct xf_client *client, struct xf_proxy *p_proxy, + struct xaf_comp *p_comp, int comp_type); +int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp); +int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp); + +int xaf_comp_set_config(struct xf_client *client,struct xaf_comp *p_comp, + u32 num_param, void *p_param); +int xaf_comp_get_config(struct xf_client *client,struct xaf_comp *p_comp, + u32 num_param, void *p_param); + +int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp); +int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, + void *p_buf, u32 length, u32 flag); +int xaf_comp_get_status(struct xaf_comp *p_comp, struct xaf_info_s *p_info); +int xaf_comp_get_msg_count(struct xaf_comp *p_comp); + +int xaf_connect(struct xf_client *client,struct xaf_comp *p_src, + struct xaf_comp *p_dest, u32 num_buf, u32 buf_length); +int xaf_disconnect(struct xf_client *client,struct xaf_comp *p_comp); + +int xaf_pipeline_create(struct xaf_pipeline *p_pipe); +int xaf_pipeline_delete(struct xaf_pipeline *p_pipe); + +int xaf_pipeline_send_eos(struct xaf_pipeline *p_pipe); + +/* ...port routing command */ +struct __attribute__((__packed__)) xf_route_port_msg { + /* ...source port specification */ + u32 src; + /* ...destination port specification */ + u32 dst; + /* ...number of buffers to allocate */ + u32 alloc_number; + /* ...length of buffer to allocate */ + u32 alloc_size; + /* ...alignment restriction for a buffer */ + u32 alloc_align; +}; + +/* ...port unrouting command */ +struct __attribute__((__packed__)) xf_unroute_port_msg { + /* ...source port specification */ + u32 src; + /* ...destination port specification */ + u32 dst; +}; + +/* ...check if non-zero value is a power-of-two */ +#define xf_is_power_of_two(v) (((v) & ((v) - 1)) == 0) + + +/******************************************************************************* + * bascial message + ******************************************************************************/ +typedef union DATA { + u32 value; + + struct { + u32 size; + u32 channel_table[10]; + } chan_map_tab; + + struct { + u32 samplerate; + u32 width; + u32 depth; + u32 channels; + u32 endian; + u32 interleave; + u32 layout[12]; + u32 chan_pos_set; // indicate if channel position is set outside or use codec default + } outputFormat; +} data_t; + +/* ...component initialization parameter */ +struct __attribute__((__packed__)) xf_set_param_msg { + /* ...index of parameter passed to SET_CONFIG_PARAM call */ + u32 id; + /* ...value of parameter */ + data_t mixData; +}; + +/* ...message body (command/response) */ +struct __attribute__((__packed__)) xf_get_param_msg { + /* ...array of parameters requested */ + u32 id; + /* ...array of parameters values */ + data_t mixData; +}; + +/* ...renderer-specific configuration parameters */ +enum xa_config_param_renderer { + XA_RENDERER_CONFIG_PARAM_CB = 0, + XA_RENDERER_CONFIG_PARAM_STATE = 1, + XA_RENDERER_CONFIG_PARAM_PCM_WIDTH = 2, + XA_RENDERER_CONFIG_PARAM_CHANNELS = 3, + XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE = 4, + XA_RENDERER_CONFIG_PARAM_FRAME_SIZE = 5, + XA_RENDERER_CONFIG_PARAM_NUM = 6, +}; + +#endif /* FSL_DSP_XAF_API_H */ diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c new file mode 100644 index 000000000000..c7917ccf8c82 --- /dev/null +++ b/sound/soc/fsl/fsl_easrc.c @@ -0,0 +1,2533 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/kobject.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/sched/signal.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/gcd.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/core.h> + +#include "fsl_easrc.h" +#include "imx-pcm.h" + +extern struct snd_soc_component_driver fsl_easrc_dma_component; + +#define FSL_EASRC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_FLOAT_LE) + +static int fsl_easrc_iec958_put_bits(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_easrc *easrc = snd_soc_component_get_drvdata(comp); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regval = ucontrol->value.integer.value[0]; + + easrc->bps_iec958[mc->regbase] = regval; + + return 0; +} + +static int fsl_easrc_iec958_get_bits(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_easrc *easrc = snd_soc_component_get_drvdata(comp); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + + ucontrol->value.enumerated.item[0] = easrc->bps_iec958[mc->regbase]; + + return 0; +} + +int fsl_easrc_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regval; + int ret; + + ret = snd_soc_component_read(component, mc->regbase, ®val); + if (ret < 0) + return ret; + + ucontrol->value.integer.value[0] = regval; + + return 0; +} + +int fsl_easrc_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regval = ucontrol->value.integer.value[0]; + int ret; + + ret = snd_soc_component_write(component, mc->regbase, regval); + if (ret < 0) + return ret; + + return 0; +} + +#define SOC_SINGLE_REG_RW(xname, xreg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_xr_sx, .get = fsl_easrc_get_reg, \ + .put = fsl_easrc_set_reg, \ + .private_value = (unsigned long)&(struct soc_mreg_control) \ + { .regbase = xreg, .regcount = 1, .nbits = 32, \ + .invert = 0, .min = 0, .max = 0xffffffff, } } + +#define SOC_SINGLE_VAL_RW(xname, xreg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_xr_sx, .get = fsl_easrc_iec958_get_bits, \ + .put = fsl_easrc_iec958_put_bits, \ + .private_value = (unsigned long)&(struct soc_mreg_control) \ + { .regbase = xreg, .regcount = 1, .nbits = 32, \ + .invert = 0, .min = 0, .max = 2, } } + +static const struct snd_kcontrol_new fsl_easrc_snd_controls[] = { + SOC_SINGLE("Context 0 Dither Switch", REG_EASRC_COC(0), 0, 1, 0), + SOC_SINGLE("Context 1 Dither Switch", REG_EASRC_COC(1), 0, 1, 0), + SOC_SINGLE("Context 2 Dither Switch", REG_EASRC_COC(2), 0, 1, 0), + SOC_SINGLE("Context 3 Dither Switch", REG_EASRC_COC(3), 0, 1, 0), + + SOC_SINGLE("Context 0 IEC958 Validity", REG_EASRC_COC(0), 2, 1, 0), + SOC_SINGLE("Context 1 IEC958 Validity", REG_EASRC_COC(1), 2, 1, 0), + SOC_SINGLE("Context 2 IEC958 Validity", REG_EASRC_COC(2), 2, 1, 0), + SOC_SINGLE("Context 3 IEC958 Validity", REG_EASRC_COC(3), 2, 1, 0), + + SOC_SINGLE_VAL_RW("Context 0 IEC958 Bits Per Sample", 0), + SOC_SINGLE_VAL_RW("Context 1 IEC958 Bits Per Sample", 1), + SOC_SINGLE_VAL_RW("Context 2 IEC958 Bits Per Sample", 2), + SOC_SINGLE_VAL_RW("Context 3 IEC958 Bits Per Sample", 3), + + SOC_SINGLE_REG_RW("Context 0 IEC958 CS0", REG_EASRC_CS0(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS0", REG_EASRC_CS0(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS0", REG_EASRC_CS0(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS0", REG_EASRC_CS0(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS1", REG_EASRC_CS1(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS1", REG_EASRC_CS1(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS1", REG_EASRC_CS1(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS1", REG_EASRC_CS1(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS2", REG_EASRC_CS2(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS2", REG_EASRC_CS2(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS2", REG_EASRC_CS2(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS2", REG_EASRC_CS2(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS3", REG_EASRC_CS3(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS3", REG_EASRC_CS3(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS3", REG_EASRC_CS3(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS3", REG_EASRC_CS3(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS4", REG_EASRC_CS4(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS4", REG_EASRC_CS4(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS4", REG_EASRC_CS4(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS4", REG_EASRC_CS4(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS5", REG_EASRC_CS5(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS5", REG_EASRC_CS5(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS5", REG_EASRC_CS5(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS5", REG_EASRC_CS5(3)), +}; + +/* set_rs_ratio + * + * According to the resample taps, calculate the resample ratio + */ +static int set_rs_ratio(struct fsl_easrc_context *ctx) +{ + struct fsl_easrc *easrc = ctx->easrc; + unsigned int in_rate = ctx->in_params.norm_rate; + unsigned int out_rate = ctx->out_params.norm_rate; + unsigned int int_bits; + unsigned int frac_bits; + u64 val; + u32 *r; + int ret; + + switch (easrc->rs_num_taps) { + case EASRC_RS_32_TAPS: + int_bits = 5; + frac_bits = 39; + break; + case EASRC_RS_64_TAPS: + int_bits = 6; + frac_bits = 38; + break; + case EASRC_RS_128_TAPS: + int_bits = 7; + frac_bits = 37; + break; + default: + return -EINVAL; + } + + val = ((uint64_t)in_rate << frac_bits) / out_rate; + r = (uint32_t *)&val; + ret = regmap_write(easrc->regmap, + REG_EASRC_RRL(ctx->index), + EASRC_RRL_RS_RL(r[0])); + ret |= regmap_write(easrc->regmap, + REG_EASRC_RRH(ctx->index), + EASRC_RRH_RS_RH(r[1])); + + return ret; +} + +/* normalize input and output sample rates */ +static void fsl_easrc_normalize_rates(struct fsl_easrc_context *ctx) +{ + int a, b; + + if (!ctx) + return; + + a = ctx->in_params.sample_rate; + b = ctx->out_params.sample_rate; + + a = gcd(a, b); + + /* divide by gcd to normalize the rate */ + ctx->in_params.norm_rate = ctx->in_params.sample_rate / a; + ctx->out_params.norm_rate = ctx->out_params.sample_rate / a; +} + +/* resets the pointer of the coeff memory pointers */ +static int fsl_coeff_mem_ptr_reset(struct fsl_easrc *easrc, + unsigned int ctx_id, int mem_type) +{ + struct device *dev; + int ret = 0; + u32 reg, mask, val; + + if (!easrc) + return -ENODEV; + + dev = &easrc->pdev->dev; + + switch (mem_type) { + case EASRC_PF_COEFF_MEM: + /* This resets the prefilter memory pointer addr */ + if (ctx_id >= EASRC_CTX_MAX_NUM) { + dev_err(dev, "Invalid context id[%d]\n", ctx_id); + return -EINVAL; + } + + reg = REG_EASRC_CCE1(ctx_id); + mask = EASRC_CCE1_COEF_MEM_RST_MASK; + val = EASRC_CCE1_COEF_MEM_RST; + break; + case EASRC_RS_COEFF_MEM: + /* This resets the resampling memory pointer addr */ + reg = REG_EASRC_CRCC; + mask = EASRC_CRCC_RS_CPR_MASK; + val = EASRC_CRCC_RS_CPR; + break; + default: + dev_err(dev, "Unknown memory type\n"); + return -EINVAL; + } + + /* To reset the write pointer back to zero, the register field + * ASRC_CTX_CTRL_EXT1x[PF_COEFF_MEM_RST] can be toggled from + * 0x0 to 0x1 to 0x0. + */ + ret |= regmap_update_bits(easrc->regmap, reg, mask, 0); + ret |= regmap_update_bits(easrc->regmap, reg, mask, val); + ret |= regmap_update_bits(easrc->regmap, reg, mask, 0); + + return ret; +} + +static inline uint32_t bits_taps_to_val(unsigned int t) +{ + switch (t) { + case EASRC_RS_32_TAPS: + return 32; + case EASRC_RS_64_TAPS: + return 64; + case EASRC_RS_128_TAPS: + return 128; + } + + return 0; +} + +static int fsl_easrc_resampler_config(struct fsl_easrc *easrc) +{ + struct device *dev = &easrc->pdev->dev; + struct asrc_firmware_hdr *hdr = easrc->firmware_hdr; + struct interp_params *interp = easrc->interp; + struct interp_params *selected_interp = NULL; + unsigned int num_coeff; + unsigned int i; + u64 *arr; + u32 r0, r1; + u32 *r; + int ret; + + if (!hdr) { + dev_err(dev, "firmware not loaded!\n"); + return -ENODEV; + } + + for (i = 0; i < hdr->interp_scen; i++) { + if ((interp[i].num_taps - 1) == + bits_taps_to_val(easrc->rs_num_taps)) { + arr = interp[i].coeff; + selected_interp = &interp[i]; + dev_dbg(dev, "Selected interp_filter: %u taps - %u phases\n", + selected_interp->num_taps, + selected_interp->num_phases); + break; + } + } + + if (!selected_interp) { + dev_err(dev, "failed to get interpreter configuration\n"); + return -EINVAL; + } + + /* + * RS_LOW - first half of center tap of the sinc function + * RS_HIGH - second half of center tap of the sinc function + * This is due to the fact the resampling function must be + * symetrical - i.e. odd number of taps + */ + r = (uint32_t *)&selected_interp->center_tap; + r0 = r[0] & EASRC_32b_MASK; + r1 = r[1] & EASRC_32b_MASK; + + ret = regmap_write(easrc->regmap, + REG_EASRC_RCTCL, + EASRC_RCTCL_RS_CL(r0)); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_RCTCH, + EASRC_RCTCH_RS_CH(r1)); + if (ret) + return ret; + + /* Write Number of Resampling Coefficient Taps + * 00b - 32-Tap Resampling Filter + * 01b - 64-Tap Resampling Filter + * 10b - 128-Tap Resampling Filter + * 11b - N/A + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CRCC, + EASRC_CRCC_RS_TAPS_MASK, + EASRC_CRCC_RS_TAPS(easrc->rs_num_taps)); + if (ret) + return ret; + + /* Reset prefilter coefficient pointer back to 0 */ + ret = fsl_coeff_mem_ptr_reset(easrc, 0, EASRC_RS_COEFF_MEM); + if (ret) + return ret; + + /* When the filter is programmed to run in: + * 32-tap mode, 16-taps, 128-phases 4-coefficients per phase + * 64-tap mode, 32-taps, 64-phases 4-coefficients per phase + * 128-tap mode, 64-taps, 32-phases 4-coefficients per phase + * This means the number of writes is constant no matter + * the mode we are using + */ + num_coeff = 16 * 128 * 4; + + for (i = 0; i < num_coeff; i++) { + r = (uint32_t *)&arr[i]; + r0 = r[0] & EASRC_32b_MASK; + r1 = r[1] & EASRC_32b_MASK; + + ret |= regmap_write(easrc->regmap, + REG_EASRC_CRCM, + EASRC_CRCM_RS_CWD(r0)); + + ret |= regmap_write(easrc->regmap, + REG_EASRC_CRCM, + EASRC_CRCM_RS_CWD(r1)); + + if (ret) + return ret; + } + + return 0; +} + +/***************************************************************************** + * Scale filter coefficients (64 bits float) + * For input float32 normalized range (1.0,-1.0) -> output int[16,24,32]: + * scale it by multiplying filter coefficients by 2^31 + * For input int[16, 24, 32] -> output float32 + * scale it by multiplying filter coefficients by 2^-15, 2^-23, 2^-31 + * input: + * easrc: Structure pointer of fsl_easrc + * filterIn : Pointer to non-scaled input filter + * shift: The multiply factor + * output: + * filterOut: scaled filter + *****************************************************************************/ +static int NormalizedFilterForFloat32InIntOut(struct fsl_easrc *easrc, + uint64_t *filterIn, + uint64_t *filterOut, + int shift) +{ + struct device *dev = &easrc->pdev->dev; + uint64_t coef = *filterIn; + int64_t exp = (coef & 0x7ff0000000000000ll) >> 52; + uint64_t coefOut; + + /* + * If exponent is zero (value == 0), or 7ff (value == NaNs) + * dont touch the content + */ + if (((coef & 0x7ff0000000000000ll) == 0) || + ((coef & 0x7ff0000000000000ll) == ((uint64_t)0x7ff << 52))) { + *filterOut = coef; + } else { + if ((shift > 0 && (shift + exp) >= 2047) || + (shift < 0 && (exp + shift) <= 0)) { + dev_err(dev, "coef error\n"); + return -EINVAL; + } + + /* coefficient * 2^shift ==> coefficient_exp + shift */ + exp += shift; + coefOut = (uint64_t)(coef & 0x800FFFFFFFFFFFFFll) + + ((uint64_t)exp << 52); + *filterOut = coefOut; + } + + return 0; +} + +static int write_pf_coeff_mem(struct fsl_easrc *easrc, int ctx_id, + u64 *arr, int n_taps, int shift) +{ + struct device *dev = &easrc->pdev->dev; + int ret = 0; + int i; + u32 *r; + u32 r0, r1; + u64 tmp; + + /* If STx_NUM_TAPS is set to 0x0 then return */ + if (!n_taps) + return 0; + + if (!arr) { + dev_err(dev, "NULL buffer\n"); + return -EINVAL; + } + + /* When switching between stages, the address pointer + * should be reset back to 0x0 before performing a write + */ + ret = fsl_coeff_mem_ptr_reset(easrc, ctx_id, EASRC_PF_COEFF_MEM); + if (ret) + return ret; + + for (i = 0; i < (n_taps + 1) / 2; i++) { + ret = NormalizedFilterForFloat32InIntOut(easrc, + &arr[i], + &tmp, + shift); + if (ret) + return ret; + + r = (uint32_t *)&tmp; + + r0 = r[0] & EASRC_32b_MASK; + r1 = r[1] & EASRC_32b_MASK; + + ret |= regmap_write(easrc->regmap, + REG_EASRC_PCF(ctx_id), + EASRC_PCF_CD(r0)); + + ret |= regmap_write(easrc->regmap, + REG_EASRC_PCF(ctx_id), + EASRC_PCF_CD(r1)); + + if (ret) + return ret; + } + + return 0; +} + +static int fsl_easrc_prefilter_config(struct fsl_easrc *easrc, + unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx; + struct asrc_firmware_hdr *hdr; + struct prefil_params *prefil, *selected_prefil = NULL; + struct device *dev; + u32 inrate, outrate, offset = 0; + int ret, i; + + /* to modify prefilter coeficients, the user must perform + * a write in ASRC_PRE_COEFF_FIFO[COEFF_DATA] while the + * RUN_EN for that context is set to 0 + */ + if (!easrc) + return -ENODEV; + + dev = &easrc->pdev->dev; + + if (ctx_id >= EASRC_CTX_MAX_NUM) { + dev_err(dev, "Invalid context id[%d]\n", ctx_id); + return -EINVAL; + } + + ctx = easrc->ctx[ctx_id]; + + ctx->in_filled_sample = bits_taps_to_val(easrc->rs_num_taps) / 2; + ctx->out_missed_sample = ctx->in_filled_sample * + ctx->out_params.sample_rate / + ctx->in_params.sample_rate; + + ctx->st1_num_taps = 0; + ctx->st2_num_taps = 0; + + ret = regmap_write(easrc->regmap, REG_EASRC_CCE1(ctx_id), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, REG_EASRC_CCE2(ctx_id), 0); + if (ret) + return ret; + + /* prefilter is enabled only when doing downsampling. + * When out_rate >= in_rate, pf will be in bypass mode + */ + if (ctx->out_params.sample_rate >= ctx->in_params.sample_rate) { + if (ctx->out_params.sample_rate == ctx->in_params.sample_rate) { + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_RS_BYPASS_MASK, + EASRC_CCE1_RS_BYPASS); + if (ret) + return ret; + } + + if (ctx->in_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE) { + ctx->st1_num_taps = 1; + ctx->st1_coeff = &easrc->const_coeff; + ctx->st1_num_exp = 1; + ctx->st2_num_taps = 0; + ctx->st1_addexp = 31; + } else if (ctx->in_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE) { + ctx->st1_num_taps = 1; + ctx->st1_coeff = &easrc->const_coeff; + ctx->st1_num_exp = 1; + ctx->st2_num_taps = 0; + ctx->st1_addexp -= ctx->in_params.fmt.addexp; + } else { + ctx->st1_num_taps = 1; + ctx->st1_coeff = &easrc->const_coeff; + ctx->st1_num_exp = 1; + ctx->st2_num_taps = 0; + } + } else { + inrate = ctx->in_params.norm_rate; + outrate = ctx->out_params.norm_rate; + + hdr = easrc->firmware_hdr; + prefil = easrc->prefil; + + for (i = 0; i < hdr->prefil_scen; i++) { + if (inrate == prefil[i].insr && outrate == prefil[i].outsr) { + selected_prefil = &prefil[i]; + dev_dbg(dev, "Selected prefilter: %u insr, %u outsr, %u st1_taps, %u st2_taps\n", + selected_prefil->insr, + selected_prefil->outsr, + selected_prefil->st1_taps, + selected_prefil->st2_taps); + break; + } + } + + if (!selected_prefil) { + dev_err(dev, "Conversion from in ratio %u(%u) to out ratio %u(%u) is not supported\n", + ctx->in_params.sample_rate, + inrate, + ctx->out_params.sample_rate, outrate); + return -EINVAL; + } + + /* in prefilter coeff array, first st1_num_taps represent the + * stage1 prefilter coefficients followed by next st2_num_taps + * representing stage 2 coefficients + */ + ctx->st1_num_taps = selected_prefil->st1_taps; + ctx->st1_coeff = selected_prefil->coeff; + ctx->st1_num_exp = selected_prefil->st1_exp; + + offset = ((selected_prefil->st1_taps + 1) / 2) * + sizeof(selected_prefil->coeff[0]); + ctx->st2_num_taps = selected_prefil->st2_taps; + ctx->st2_coeff = (uint64_t *)((uint64_t)selected_prefil->coeff + offset); + + if (ctx->in_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE) { + /* only change stage2 coefficient for 2 stage case */ + if (ctx->st2_num_taps > 0) + ctx->st2_addexp = 31; + else + ctx->st1_addexp = 31; + } else if (ctx->in_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE) { + if (ctx->st2_num_taps > 0) + ctx->st2_addexp -= ctx->in_params.fmt.addexp; + else + ctx->st1_addexp -= ctx->in_params.fmt.addexp; + } + } + + ctx->in_filled_sample += (ctx->st1_num_taps / 2) * ctx->st1_num_exp + + ctx->st2_num_taps / 2; + ctx->out_missed_sample = ctx->in_filled_sample * + ctx->out_params.sample_rate / + ctx->in_params.sample_rate; + + if (ctx->in_filled_sample * ctx->out_params.sample_rate % + ctx->in_params.sample_rate != 0) + ctx->out_missed_sample += 1; + /* To modify the value of a prefilter coefficient, the user must + * perform a write to the register ASRC_PRE_COEFF_FIFOn[COEFF_DATA] + * while the respective context RUN_EN bit is set to 0b0 + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx_id), + EASRC_CC_EN_MASK, 0); + if (ret) + goto ctx_error; + + if (ctx->st1_num_taps > EASRC_MAX_PF_TAPS) { + dev_err(dev, "ST1 taps [%d] mus be lower than %d\n", + ctx->st1_num_taps, EASRC_MAX_PF_TAPS); + ret = -EINVAL; + goto ctx_error; + } + + /* Update ctx ST1_NUM_TAPS in Context Control Extended 2 register */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE2(ctx_id), + EASRC_CCE2_ST1_TAPS_MASK, + EASRC_CCE2_ST1_TAPS(ctx->st1_num_taps - 1)); + if (ret) + goto ctx_error; + + /* Prefilter Coefficient Write Select to write in ST1 coeff */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_COEF_WS_MASK, + (EASRC_PF_ST1_COEFF_WR << EASRC_CCE1_COEF_WS_SHIFT)); + if (ret) + goto ctx_error; + + ret = write_pf_coeff_mem(easrc, ctx_id, + ctx->st1_coeff, + ctx->st1_num_taps, + ctx->st1_addexp); + if (ret) + goto ctx_error; + + if (ctx->st2_num_taps > 0) { + if (ctx->st2_num_taps + ctx->st1_num_taps > EASRC_MAX_PF_TAPS) { + dev_err(dev, "ST2 taps [%d] mus be lower than %d\n", + ctx->st2_num_taps, EASRC_MAX_PF_TAPS); + ret = -EINVAL; + goto ctx_error; + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_TSEN_MASK, + EASRC_CCE1_PF_TSEN); + if (ret) + goto ctx_error; + /* + * Enable prefilter stage1 writeback floating point + * which is used for FLOAT_LE case + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_ST1_WBFP_MASK, + EASRC_CCE1_PF_ST1_WBFP); + if (ret) + goto ctx_error; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_EXP_MASK, + EASRC_CCE1_PF_EXP(ctx->st1_num_exp - 1)); + if (ret) + goto ctx_error; + + /* Update ctx ST2_NUM_TAPS in Context Control Extended 2 reg */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE2(ctx_id), + EASRC_CCE2_ST2_TAPS_MASK, + EASRC_CCE2_ST2_TAPS(ctx->st2_num_taps - 1)); + if (ret) + goto ctx_error; + + /* Prefilter Coefficient Write Select to write in ST2 coeff */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_COEF_WS_MASK, + EASRC_PF_ST2_COEFF_WR << EASRC_CCE1_COEF_WS_SHIFT); + if (ret) + goto ctx_error; + + ret = write_pf_coeff_mem(easrc, ctx_id, + ctx->st2_coeff, + ctx->st2_num_taps, + ctx->st2_addexp); + if (ret) + goto ctx_error; + } + + return 0; + +ctx_error: + return ret; +} + +static int fsl_easrc_max_ch_for_slot(struct fsl_easrc_context *ctx, + struct fsl_easrc_slot *slot) +{ + int st1_mem_alloc = 0, st2_mem_alloc = 0; + int pf_mem_alloc = 0; + int max_channels = 8 - slot->num_channel; + int channels = 0; + + if (ctx->st1_num_taps > 0) { + if (ctx->st2_num_taps > 0) + st1_mem_alloc = + (ctx->st1_num_taps - 1) * ctx->st1_num_exp + 1; + else + st1_mem_alloc = ctx->st1_num_taps; + } + + if (ctx->st2_num_taps > 0) + st2_mem_alloc = ctx->st2_num_taps; + + pf_mem_alloc = st1_mem_alloc + st2_mem_alloc; + + if (pf_mem_alloc != 0) + channels = (6144 - slot->pf_mem_used) / pf_mem_alloc; + else + channels = 8; + + if (channels < max_channels) + max_channels = channels; + + return max_channels; +} + +static int fsl_easrc_config_one_slot(struct fsl_easrc_context *ctx, + struct fsl_easrc_slot *slot, + unsigned int slot_idx, + unsigned int reg0, + unsigned int reg1, + unsigned int reg2, + unsigned int reg3, + unsigned int *req_channels, + unsigned int *start_channel, + unsigned int *avail_channel) +{ + struct fsl_easrc *easrc = ctx->easrc; + int st1_chanxexp, st1_mem_alloc = 0, st2_mem_alloc = 0; + unsigned int addr; + int ret; + + if (*req_channels <= *avail_channel) { + slot->num_channel = *req_channels; + slot->min_channel = *start_channel; + slot->max_channel = *start_channel + *req_channels - 1; + slot->ctx_index = ctx->index; + slot->busy = true; + *start_channel += *req_channels; + *req_channels = 0; + } else { + slot->num_channel = *avail_channel; + slot->min_channel = *start_channel; + slot->max_channel = *start_channel + *avail_channel - 1; + slot->ctx_index = ctx->index; + slot->busy = true; + *start_channel += *avail_channel; + *req_channels -= *avail_channel; + } + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_MAXCH_MASK, + EASRC_DPCS0R0_MAXCH(slot->max_channel)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_MINCH_MASK, + EASRC_DPCS0R0_MINCH(slot->min_channel)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_NUMCH_MASK, + EASRC_DPCS0R0_NUMCH(slot->num_channel - 1)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_CTXNUM_MASK, + EASRC_DPCS0R0_CTXNUM(slot->ctx_index)); + if (ret) + return ret; + + if (ctx->st1_num_taps > 0) { + if (ctx->st2_num_taps > 0) + st1_mem_alloc = + (ctx->st1_num_taps - 1) * slot->num_channel * + ctx->st1_num_exp + slot->num_channel; + else + st1_mem_alloc = ctx->st1_num_taps * slot->num_channel; + + slot->pf_mem_used = st1_mem_alloc; + ret = regmap_update_bits(easrc->regmap, + reg2, + EASRC_DPCS0R2_ST1_MA_MASK, + EASRC_DPCS0R2_ST1_MA(st1_mem_alloc)); + if (ret) + return ret; + + if (slot_idx == 1) + addr = 0x1800 - st1_mem_alloc; + else + addr = 0; + + ret = regmap_update_bits(easrc->regmap, + reg2, + EASRC_DPCS0R2_ST1_SA_MASK, + EASRC_DPCS0R2_ST1_SA(addr)); + if (ret) + return ret; + } + + if (ctx->st2_num_taps > 0) { + st1_chanxexp = slot->num_channel * (ctx->st1_num_exp - 1); + + ret = regmap_update_bits(easrc->regmap, + reg1, + EASRC_DPCS0R1_ST1_EXP_MASK, + EASRC_DPCS0R1_ST1_EXP(st1_chanxexp)); + if (ret) + return ret; + + st2_mem_alloc = slot->num_channel * ctx->st2_num_taps; + slot->pf_mem_used += st2_mem_alloc; + ret = regmap_update_bits(easrc->regmap, + reg3, + EASRC_DPCS0R3_ST2_MA_MASK, + EASRC_DPCS0R3_ST2_MA(st2_mem_alloc)); + if (ret) + return ret; + + if (slot_idx == 1) + addr = 0x1800 - st1_mem_alloc - st2_mem_alloc; + else + addr = st1_mem_alloc; + + ret = regmap_update_bits(easrc->regmap, + reg3, + EASRC_DPCS0R3_ST2_SA_MASK, + EASRC_DPCS0R3_ST2_SA(addr)); + if (ret) + return ret; + } + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_EN_MASK, + EASRC_DPCS0R0_EN); + if (ret) + return ret; + + return 0; +} + +/* fsl_easrc_config_slot + * + * A single context can be split amongst any of the 4 context processing pipes + * in the design. + * The total number of channels consumed within the context processor must be + * less than or equal to 8. if a single context is configured to contain more + * than 8 channels then it must be distributed across multiple context + * processing pipe slots. + * + */ +static int fsl_easrc_config_slot(struct fsl_easrc *easrc, unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx = easrc->ctx[ctx_id]; + int req_channels = ctx->channels; + int start_channel = 0, avail_channel; + struct fsl_easrc_slot *slot0, *slot1; + int i, ret; + + if (req_channels <= 0) + return -EINVAL; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + slot0 = &easrc->slot[i][0]; + slot1 = &easrc->slot[i][1]; + + if (slot0->busy && slot1->busy) + continue; + + if (!slot0->busy) { + if (slot1->busy && slot1->ctx_index == ctx->index) + continue; + + avail_channel = fsl_easrc_max_ch_for_slot(ctx, slot1); + if (avail_channel <= 0) + continue; + + ret = fsl_easrc_config_one_slot(ctx, + slot0, 0, + REG_EASRC_DPCS0R0(i), + REG_EASRC_DPCS0R1(i), + REG_EASRC_DPCS0R2(i), + REG_EASRC_DPCS0R3(i), + &req_channels, + &start_channel, + &avail_channel); + if (ret) + return ret; + + if (req_channels > 0) + continue; + else + break; + } + + if (slot0->busy && !slot1->busy) { + if (slot0->ctx_index == ctx->index) + continue; + + avail_channel = fsl_easrc_max_ch_for_slot(ctx, slot0); + if (avail_channel <= 0) + continue; + + ret = fsl_easrc_config_one_slot(ctx, + slot1, 1, + REG_EASRC_DPCS1R0(i), + REG_EASRC_DPCS1R1(i), + REG_EASRC_DPCS1R2(i), + REG_EASRC_DPCS1R3(i), + &req_channels, + &start_channel, + &avail_channel); + if (ret) + return ret; + + if (req_channels > 0) + continue; + else + break; + } + } + + if (req_channels > 0) { + dev_err(&easrc->pdev->dev, "no avail slot.\n"); + return -EINVAL; + } + + return 0; +} + +/* fsl_easrc_release_slot + * + * clear the slot configuration + */ +static int fsl_easrc_release_slot(struct fsl_easrc *easrc, unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx = easrc->ctx[ctx_id]; + int i, ret; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->slot[i][0].busy && + easrc->slot[i][0].ctx_index == ctx->index) { + easrc->slot[i][0].busy = false; + easrc->slot[i][0].num_channel = 0; + easrc->slot[i][0].pf_mem_used = 0; + /* set registers */ + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R0(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R1(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R2(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R3(i), 0); + if (ret) + return ret; + } + + if (easrc->slot[i][1].busy && + easrc->slot[i][1].ctx_index == ctx->index) { + easrc->slot[i][1].busy = false; + easrc->slot[i][1].num_channel = 0; + easrc->slot[i][1].pf_mem_used = 0; + /* set registers */ + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R0(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R1(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R2(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R3(i), 0); + if (ret) + return ret; + } + } + + return 0; +} + +/* fsl_easrc_config_context + * + * configure the register relate with context. + */ +int fsl_easrc_config_context(struct fsl_easrc *easrc, unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx; + struct device *dev; + unsigned long lock_flags; + int ret; + + /* to modify prefilter coeficients, the user must perform + * a write in ASRC_PRE_COEFF_FIFO[COEFF_DATA] while the + * RUN_EN for that context is set to 0 + */ + if (!easrc) + return -ENODEV; + + dev = &easrc->pdev->dev; + + if (ctx_id >= EASRC_CTX_MAX_NUM) { + dev_err(dev, "Invalid context id[%d]\n", ctx_id); + return -EINVAL; + } + + ctx = easrc->ctx[ctx_id]; + + fsl_easrc_normalize_rates(ctx); + + ret = set_rs_ratio(ctx); + if (ret) + return ret; + + /* initialize the context coeficients */ + ret = fsl_easrc_prefilter_config(easrc, ctx->index); + if (ret) + return ret; + + spin_lock_irqsave(&easrc->lock, lock_flags); + ret = fsl_easrc_config_slot(easrc, ctx->index); + spin_unlock_irqrestore(&easrc->lock, lock_flags); + if (ret) + return ret; + + /* Both prefilter and resampling filters can use following + * initialization modes: + * 2 - zero-fil mode + * 1 - replication mode + * 0 - software control + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_RS_INIT_MASK, + EASRC_CCE1_RS_INIT(ctx->rs_init_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_INIT_MASK, + EASRC_CCE1_PF_INIT(ctx->pf_init_mode)); + if (ret) + return ret; + + /* Context Input FIFO Watermark */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx_id), + EASRC_CC_FIFO_WTMK_MASK, + EASRC_CC_FIFO_WTMK(ctx->in_params.fifo_wtmk)); + if (ret) + return ret; + + /* Context Output FIFO Watermark */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx_id), + EASRC_COC_FIFO_WTMK_MASK, + EASRC_COC_FIFO_WTMK(ctx->out_params.fifo_wtmk - 1)); + if (ret) + return ret; + + /* number of channels */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx_id), + EASRC_CC_CHEN_MASK, + EASRC_CC_CHEN(ctx->channels - 1)); + return ret; +} + +static int fsl_easrc_process_format(struct fsl_easrc_context *ctx, + struct fsl_easrc_data_fmt *fmt, + snd_pcm_format_t raw_fmt) +{ + struct fsl_easrc *easrc = ctx->easrc; + int ret; + + if (!fmt) + return -EINVAL; + + /* Context Input Floating Point Format + * 0 - Integer Format + * 1 - Single Precision FP Format + */ + fmt->floating_point = !snd_pcm_format_linear(raw_fmt); + fmt->sample_pos = 0; + fmt->iec958 = 0; + + /* get the data width */ + switch (snd_pcm_format_width(raw_fmt)) { + case 16: + fmt->width = EASRC_WIDTH_16_BIT; + fmt->addexp = 15; + break; + case 20: + fmt->width = EASRC_WIDTH_20_BIT; + fmt->addexp = 19; + break; + case 24: + fmt->width = EASRC_WIDTH_24_BIT; + fmt->addexp = 23; + break; + case 32: + fmt->width = EASRC_WIDTH_32_BIT; + fmt->addexp = 31; + break; + default: + return -EINVAL; + } + + switch (raw_fmt) { + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + fmt->width = easrc->bps_iec958[ctx->index]; + fmt->iec958 = 1; + fmt->floating_point = 0; + if (fmt->width == EASRC_WIDTH_16_BIT) { + fmt->sample_pos = 12; + fmt->addexp = 15; + } else if (fmt->width == EASRC_WIDTH_20_BIT) { + fmt->sample_pos = 8; + fmt->addexp = 19; + } else if (fmt->width == EASRC_WIDTH_24_BIT) { + fmt->sample_pos = 4; + fmt->addexp = 23; + } + break; + default: + break; + } + + /* Data Endianness + * 0 - Little-Endian + * 1 - Big-Endian + */ + ret = snd_pcm_format_big_endian(raw_fmt); + if (ret < 0) + return ret; + + fmt->endianness = ret; + /* Input Data sign + * 0b - Signed Format + * 1b - Unsigned Format + */ + fmt->unsign = snd_pcm_format_unsigned(raw_fmt) > 0 ? 1 : 0; + + return 0; +} + +int fsl_easrc_set_ctx_format(struct fsl_easrc_context *ctx, + snd_pcm_format_t *in_raw_format, + snd_pcm_format_t *out_raw_format) +{ + struct fsl_easrc *easrc = ctx->easrc; + struct fsl_easrc_data_fmt *in_fmt = &ctx->in_params.fmt; + struct fsl_easrc_data_fmt *out_fmt = &ctx->out_params.fmt; + int ret; + + /* get the bitfield values for input data format */ + if (in_raw_format && out_raw_format) { + ret = fsl_easrc_process_format(ctx, in_fmt, *in_raw_format); + if (ret) + return ret; + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_BPS_MASK, + EASRC_CC_BPS(in_fmt->width)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_ENDIANNESS_MASK, + in_fmt->endianness << EASRC_CC_ENDIANNESS_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_FMT_MASK, + in_fmt->floating_point << EASRC_CC_FMT_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_INSIGN_MASK, + in_fmt->unsign << EASRC_CC_INSIGN_SHIFT); + + /* In Sample Position */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_SAMPLE_POS_MASK, + EASRC_CC_SAMPLE_POS(in_fmt->sample_pos)); + if (ret) + return ret; + + /* get the bitfield values for input data format */ + if (in_raw_format && out_raw_format) { + ret = fsl_easrc_process_format(ctx, out_fmt, *out_raw_format); + if (ret) + return ret; + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_BPS_MASK, + EASRC_COC_BPS(out_fmt->width)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_ENDIANNESS_MASK, + out_fmt->endianness << EASRC_COC_ENDIANNESS_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_FMT_MASK, + out_fmt->floating_point << EASRC_COC_FMT_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_OUTSIGN_MASK, + out_fmt->unsign << EASRC_COC_OUTSIGN_SHIFT); + if (ret) + return ret; + + /* Out Sample Position */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_SAMPLE_POS_MASK, + EASRC_COC_SAMPLE_POS(out_fmt->sample_pos)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_IEC_EN_MASK, + out_fmt->iec958 << EASRC_COC_IEC_EN_SHIFT); + + return ret; +} + +/* The ASRC provides interleaving support in hardware to ensure that a + * variety of sample sources can be internally combined + * to conform with this format. Interleaving parameters are accessed + * through the ASRC_CTRL_IN_ACCESSa and ASRC_CTRL_OUT_ACCESSa registers + */ +int fsl_easrc_set_ctx_organziation(struct fsl_easrc_context *ctx) +{ + struct device *dev; + struct fsl_easrc *easrc; + int ret; + + if (!ctx) + return -ENODEV; + + easrc = ctx->easrc; + dev = &easrc->pdev->dev; + + /* input interleaving parameters */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CIA(ctx->index), + EASRC_CIA_ITER_MASK, + EASRC_CIA_ITER(ctx->in_params.iterations)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CIA(ctx->index), + EASRC_CIA_GRLEN_MASK, + EASRC_CIA_GRLEN(ctx->in_params.group_len)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CIA(ctx->index), + EASRC_CIA_ACCLEN_MASK, + EASRC_CIA_ACCLEN(ctx->in_params.access_len)); + if (ret) + return ret; + + /* output interleaving parameters */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COA(ctx->index), + EASRC_COA_ITER_MASK, + EASRC_COA_ITER(ctx->out_params.iterations)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COA(ctx->index), + EASRC_COA_GRLEN_MASK, + EASRC_COA_GRLEN(ctx->out_params.group_len)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COA(ctx->index), + EASRC_COA_ACCLEN_MASK, + EASRC_COA_ACCLEN(ctx->out_params.access_len)); + if (ret) + return ret; + + return 0; +} + +/* Request one of the available contexts + * + * Returns a negative number on error and >=0 as context id + * on success + */ +int fsl_easrc_request_context(struct fsl_easrc_context *ctx, + unsigned int channels) +{ + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_easrc *easrc = ctx->easrc; + struct device *dev; + unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + + spin_lock_irqsave(&easrc->lock, lock_flags); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->ctx[i]) + continue; + + index = i; + break; + } + + if (index == ASRC_INVALID_PAIR) { + dev_err(dev, "all contexts are busy\n"); + ret = -EBUSY; + } else if (channels > easrc->chn_avail) { + dev_err(dev, "can't give the required channels: %d\n", + channels); + ret = -EINVAL; + } else { + ctx->index = index; + ctx->channels = channels; + easrc->ctx[index] = ctx; + easrc->chn_avail -= channels; + } + + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + return ret; +} + +/* Release the context + * + * This funciton is mainly doing the revert thing in request context + */ +int fsl_easrc_release_context(struct fsl_easrc_context *ctx) +{ + unsigned long lock_flags; + struct fsl_easrc *easrc; + struct device *dev; + int ret; + + if (!ctx) + return 0; + + easrc = ctx->easrc; + dev = &easrc->pdev->dev; + + spin_lock_irqsave(&easrc->lock, lock_flags); + + ret = fsl_easrc_release_slot(easrc, ctx->index); + + easrc->chn_avail += ctx->channels; + easrc->ctx[ctx->index] = NULL; + + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + return ret; +} + +/* Start the context + * + * Enable the DMA request and context + */ +int fsl_easrc_start_context(struct fsl_easrc_context *ctx) +{ + struct fsl_easrc *easrc = ctx->easrc; + int ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_FWMDE_MASK, + EASRC_CC_FWMDE); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_FWMDE_MASK, + EASRC_COC_FWMDE); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_EN_MASK, + EASRC_CC_EN); + if (ret) + return ret; + + return 0; +} + +/* Stop the context + * + * Disable the DMA request and context + */ +int fsl_easrc_stop_context(struct fsl_easrc_context *ctx) +{ + struct fsl_easrc *easrc = ctx->easrc; + int ret, val, i; + int size = 0; + int retry = 200; + + regmap_read(easrc->regmap, REG_EASRC_CC(ctx->index), &val); + + if (val & EASRC_CC_EN_MASK) { + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_STOP_MASK, EASRC_CC_STOP); + if (ret) + return ret; + + do { + regmap_read(easrc->regmap, REG_EASRC_SFS(ctx->index), &val); + val &= EASRC_SFS_NSGO_MASK; + size = val >> EASRC_SFS_NSGO_SHIFT; + + /* Read FIFO, drop the data */ + for (i = 0; i < size * ctx->channels; i++) + regmap_read(easrc->regmap, REG_EASRC_RDFIFO(ctx->index), &val); + /* Check RUN_STOP_DONE */ + regmap_read(easrc->regmap, REG_EASRC_IRQF, &val); + if (val & EASRC_IRQF_RSD(1 << ctx->index)) { + /*Clear RUN_STOP_DONE*/ + regmap_write_bits(easrc->regmap, + REG_EASRC_IRQF, + EASRC_IRQF_RSD(1 << ctx->index), + EASRC_IRQF_RSD(1 << ctx->index)); + break; + } + udelay(100); + } while (--retry); + + if (retry == 0) + dev_err(&easrc->pdev->dev, "RUN STOP fail\n"); + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_EN_MASK | EASRC_CC_STOP_MASK, 0); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_FWMDE_MASK, 0); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_FWMDE_MASK, 0); + if (ret) + return ret; + + return 0; +} + +struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_easrc_context *ctx, + bool dir) +{ + struct fsl_easrc *easrc = ctx->easrc; + enum asrc_pair_index index = ctx->index; + char name[8]; + + /* example of dma name: ctx0_rx */ + sprintf(name, "ctx%c_%cx", index + '0', dir == IN ? 'r' : 't'); + + return dma_request_slave_channel(&easrc->pdev->dev, name); +}; +EXPORT_SYMBOL_GPL(fsl_easrc_get_dma_channel); + +static const unsigned int easrc_rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, + 48000, 64000, 88200, 96000, + 128000, 176400, 192000, 256000, + 352800, 384000, 705600, 768000, +}; + +static const struct snd_pcm_hw_constraint_list easrc_rate_constraints = { + .count = ARRAY_SIZE(easrc_rates), + .list = easrc_rates, +}; + +static int fsl_easrc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &easrc_rate_constraints); +} + +static int fsl_easrc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_easrc_start_context(ctx); + if (ret) + return ret; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = fsl_easrc_stop_context(ctx); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_easrc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_easrc *easrc = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + snd_pcm_format_t format = params_format(params); + int ret; + + ret = fsl_easrc_request_context(ctx, channels); + if (ret) { + dev_err(dev, "failed to request context\n"); + return ret; + } + + ctx->ctx_streams |= BIT(substream->stream); + + /* set the input and output ratio so we can compute + * the resampling ratio in RS_LOW/HIGH + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ctx->in_params.sample_rate = rate; + ctx->in_params.sample_format = format; + ctx->out_params.sample_rate = easrc->easrc_rate; + ctx->out_params.sample_format = easrc->easrc_format; + } else { + ctx->out_params.sample_rate = rate; + ctx->out_params.sample_format = format; + ctx->in_params.sample_rate = easrc->easrc_rate; + ctx->in_params.sample_format = easrc->easrc_format; + } + + ctx->channels = channels; + ctx->in_params.fifo_wtmk = 0x20; + ctx->out_params.fifo_wtmk = 0x20; + + /* do only rate conversion and keep the same format for input + * and output data + */ + ret = fsl_easrc_set_ctx_format(ctx, + &ctx->in_params.sample_format, + &ctx->out_params.sample_format); + if (ret) { + dev_err(dev, "failed to set format %d", ret); + return ret; + } + + ret = fsl_easrc_config_context(easrc, ctx->index); + if (ret) { + dev_err(dev, "failed to config context\n"); + return ret; + } + + ctx->in_params.iterations = 1; + ctx->in_params.group_len = ctx->channels; + ctx->in_params.access_len = ctx->channels; + ctx->out_params.iterations = 1; + ctx->out_params.group_len = ctx->channels; + ctx->out_params.access_len = ctx->channels; + + ret = fsl_easrc_set_ctx_organziation(ctx); + if (ret) { + dev_err(dev, "failed to set fifo organization\n"); + return ret; + } + + return 0; +} + +static int fsl_easrc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + int ret; + + if (ctx && (ctx->ctx_streams & BIT(substream->stream))) { + ctx->ctx_streams &= ~BIT(substream->stream); + ret = fsl_easrc_release_context(ctx); + if (ret) + return ret; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_easrc_dai_ops = { + .startup = fsl_easrc_startup, + .trigger = fsl_easrc_trigger, + .hw_params = fsl_easrc_hw_params, + .hw_free = fsl_easrc_hw_free, +}; + +static int fsl_easrc_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_easrc *easrc = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_init_dma_data(cpu_dai, + &easrc->dma_params_tx, + &easrc->dma_params_rx); + return 0; +} + +static struct snd_soc_dai_driver fsl_easrc_dai = { + .probe = fsl_easrc_dai_probe, + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_easrc_component = { + .name = "fsl-easrc-dai", + .controls = fsl_easrc_snd_controls, + .num_controls = ARRAY_SIZE(fsl_easrc_snd_controls), +}; + +static const struct reg_default fsl_easrc_reg_defaults[] = { + {REG_EASRC_WRFIFO(0), 0x00000000}, + {REG_EASRC_WRFIFO(1), 0x00000000}, + {REG_EASRC_WRFIFO(2), 0x00000000}, + {REG_EASRC_WRFIFO(3), 0x00000000}, + {REG_EASRC_RDFIFO(0), 0x00000000}, + {REG_EASRC_RDFIFO(1), 0x00000000}, + {REG_EASRC_RDFIFO(2), 0x00000000}, + {REG_EASRC_RDFIFO(3), 0x00000000}, + {REG_EASRC_CC(0), 0x00000000}, + {REG_EASRC_CC(1), 0x00000000}, + {REG_EASRC_CC(2), 0x00000000}, + {REG_EASRC_CC(3), 0x00000000}, + {REG_EASRC_CCE1(0), 0x00000000}, + {REG_EASRC_CCE1(1), 0x00000000}, + {REG_EASRC_CCE1(2), 0x00000000}, + {REG_EASRC_CCE1(3), 0x00000000}, + {REG_EASRC_CCE2(0), 0x00000000}, + {REG_EASRC_CCE2(1), 0x00000000}, + {REG_EASRC_CCE2(2), 0x00000000}, + {REG_EASRC_CCE2(3), 0x00000000}, + {REG_EASRC_CIA(0), 0x00000000}, + {REG_EASRC_CIA(1), 0x00000000}, + {REG_EASRC_CIA(2), 0x00000000}, + {REG_EASRC_CIA(3), 0x00000000}, + {REG_EASRC_DPCS0R0(0), 0x00000000}, + {REG_EASRC_DPCS0R0(1), 0x00000000}, + {REG_EASRC_DPCS0R0(2), 0x00000000}, + {REG_EASRC_DPCS0R0(3), 0x00000000}, + {REG_EASRC_DPCS0R1(0), 0x00000000}, + {REG_EASRC_DPCS0R1(1), 0x00000000}, + {REG_EASRC_DPCS0R1(2), 0x00000000}, + {REG_EASRC_DPCS0R1(3), 0x00000000}, + {REG_EASRC_DPCS0R2(0), 0x00000000}, + {REG_EASRC_DPCS0R2(1), 0x00000000}, + {REG_EASRC_DPCS0R2(2), 0x00000000}, + {REG_EASRC_DPCS0R2(3), 0x00000000}, + {REG_EASRC_DPCS0R3(0), 0x00000000}, + {REG_EASRC_DPCS0R3(1), 0x00000000}, + {REG_EASRC_DPCS0R3(2), 0x00000000}, + {REG_EASRC_DPCS0R3(3), 0x00000000}, + {REG_EASRC_DPCS1R0(0), 0x00000000}, + {REG_EASRC_DPCS1R0(1), 0x00000000}, + {REG_EASRC_DPCS1R0(2), 0x00000000}, + {REG_EASRC_DPCS1R0(3), 0x00000000}, + {REG_EASRC_DPCS1R1(0), 0x00000000}, + {REG_EASRC_DPCS1R1(1), 0x00000000}, + {REG_EASRC_DPCS1R1(2), 0x00000000}, + {REG_EASRC_DPCS1R1(3), 0x00000000}, + {REG_EASRC_DPCS1R2(0), 0x00000000}, + {REG_EASRC_DPCS1R2(1), 0x00000000}, + {REG_EASRC_DPCS1R2(2), 0x00000000}, + {REG_EASRC_DPCS1R2(3), 0x00000000}, + {REG_EASRC_DPCS1R3(0), 0x00000000}, + {REG_EASRC_DPCS1R3(1), 0x00000000}, + {REG_EASRC_DPCS1R3(2), 0x00000000}, + {REG_EASRC_DPCS1R3(3), 0x00000000}, + {REG_EASRC_COC(0), 0x00000000}, + {REG_EASRC_COC(1), 0x00000000}, + {REG_EASRC_COC(2), 0x00000000}, + {REG_EASRC_COC(3), 0x00000000}, + {REG_EASRC_COA(0), 0x00000000}, + {REG_EASRC_COA(1), 0x00000000}, + {REG_EASRC_COA(2), 0x00000000}, + {REG_EASRC_COA(3), 0x00000000}, + {REG_EASRC_SFS(0), 0x00000000}, + {REG_EASRC_SFS(1), 0x00000000}, + {REG_EASRC_SFS(2), 0x00000000}, + {REG_EASRC_SFS(3), 0x00000000}, + {REG_EASRC_RRL(0), 0x00000000}, + {REG_EASRC_RRL(1), 0x00000000}, + {REG_EASRC_RRL(2), 0x00000000}, + {REG_EASRC_RRL(3), 0x00000000}, + {REG_EASRC_RRH(0), 0x00000000}, + {REG_EASRC_RRH(1), 0x00000000}, + {REG_EASRC_RRH(2), 0x00000000}, + {REG_EASRC_RRH(3), 0x00000000}, + {REG_EASRC_RUC(0), 0x00000000}, + {REG_EASRC_RUC(1), 0x00000000}, + {REG_EASRC_RUC(2), 0x00000000}, + {REG_EASRC_RUC(3), 0x00000000}, + {REG_EASRC_RUR(0), 0x7FFFFFFF}, + {REG_EASRC_RUR(1), 0x7FFFFFFF}, + {REG_EASRC_RUR(2), 0x7FFFFFFF}, + {REG_EASRC_RUR(3), 0x7FFFFFFF}, + {REG_EASRC_RCTCL, 0x00000000}, + {REG_EASRC_RCTCH, 0x00000000}, + {REG_EASRC_PCF(0), 0x00000000}, + {REG_EASRC_PCF(1), 0x00000000}, + {REG_EASRC_PCF(2), 0x00000000}, + {REG_EASRC_PCF(3), 0x00000000}, + {REG_EASRC_CRCM, 0x00000000}, + {REG_EASRC_CRCC, 0x00000000}, + {REG_EASRC_IRQC, 0x00000FFF}, + {REG_EASRC_IRQF, 0x00000000}, + {REG_EASRC_CS0(0), 0x00000000}, + {REG_EASRC_CS0(1), 0x00000000}, + {REG_EASRC_CS0(2), 0x00000000}, + {REG_EASRC_CS0(3), 0x00000000}, + {REG_EASRC_CS1(0), 0x00000000}, + {REG_EASRC_CS1(1), 0x00000000}, + {REG_EASRC_CS1(2), 0x00000000}, + {REG_EASRC_CS1(3), 0x00000000}, + {REG_EASRC_CS2(0), 0x00000000}, + {REG_EASRC_CS2(1), 0x00000000}, + {REG_EASRC_CS2(2), 0x00000000}, + {REG_EASRC_CS2(3), 0x00000000}, + {REG_EASRC_CS3(0), 0x00000000}, + {REG_EASRC_CS3(1), 0x00000000}, + {REG_EASRC_CS3(2), 0x00000000}, + {REG_EASRC_CS3(3), 0x00000000}, + {REG_EASRC_CS4(0), 0x00000000}, + {REG_EASRC_CS4(1), 0x00000000}, + {REG_EASRC_CS4(2), 0x00000000}, + {REG_EASRC_CS4(3), 0x00000000}, + {REG_EASRC_CS5(0), 0x00000000}, + {REG_EASRC_CS5(1), 0x00000000}, + {REG_EASRC_CS5(2), 0x00000000}, + {REG_EASRC_CS5(3), 0x00000000}, + {REG_EASRC_DBGC, 0x00000000}, + {REG_EASRC_DBGS, 0x00000000}, +}; + +static bool fsl_easrc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_EASRC_RDFIFO(0): + case REG_EASRC_RDFIFO(1): + case REG_EASRC_RDFIFO(2): + case REG_EASRC_RDFIFO(3): + case REG_EASRC_CC(0): + case REG_EASRC_CC(1): + case REG_EASRC_CC(2): + case REG_EASRC_CC(3): + case REG_EASRC_CCE1(0): + case REG_EASRC_CCE1(1): + case REG_EASRC_CCE1(2): + case REG_EASRC_CCE1(3): + case REG_EASRC_CCE2(0): + case REG_EASRC_CCE2(1): + case REG_EASRC_CCE2(2): + case REG_EASRC_CCE2(3): + case REG_EASRC_CIA(0): + case REG_EASRC_CIA(1): + case REG_EASRC_CIA(2): + case REG_EASRC_CIA(3): + case REG_EASRC_DPCS0R0(0): + case REG_EASRC_DPCS0R0(1): + case REG_EASRC_DPCS0R0(2): + case REG_EASRC_DPCS0R0(3): + case REG_EASRC_DPCS0R1(0): + case REG_EASRC_DPCS0R1(1): + case REG_EASRC_DPCS0R1(2): + case REG_EASRC_DPCS0R1(3): + case REG_EASRC_DPCS0R2(0): + case REG_EASRC_DPCS0R2(1): + case REG_EASRC_DPCS0R2(2): + case REG_EASRC_DPCS0R2(3): + case REG_EASRC_DPCS0R3(0): + case REG_EASRC_DPCS0R3(1): + case REG_EASRC_DPCS0R3(2): + case REG_EASRC_DPCS0R3(3): + case REG_EASRC_DPCS1R0(0): + case REG_EASRC_DPCS1R0(1): + case REG_EASRC_DPCS1R0(2): + case REG_EASRC_DPCS1R0(3): + case REG_EASRC_DPCS1R1(0): + case REG_EASRC_DPCS1R1(1): + case REG_EASRC_DPCS1R1(2): + case REG_EASRC_DPCS1R1(3): + case REG_EASRC_DPCS1R2(0): + case REG_EASRC_DPCS1R2(1): + case REG_EASRC_DPCS1R2(2): + case REG_EASRC_DPCS1R2(3): + case REG_EASRC_DPCS1R3(0): + case REG_EASRC_DPCS1R3(1): + case REG_EASRC_DPCS1R3(2): + case REG_EASRC_DPCS1R3(3): + case REG_EASRC_COC(0): + case REG_EASRC_COC(1): + case REG_EASRC_COC(2): + case REG_EASRC_COC(3): + case REG_EASRC_COA(0): + case REG_EASRC_COA(1): + case REG_EASRC_COA(2): + case REG_EASRC_COA(3): + case REG_EASRC_SFS(0): + case REG_EASRC_SFS(1): + case REG_EASRC_SFS(2): + case REG_EASRC_SFS(3): + case REG_EASRC_RRL(0): + case REG_EASRC_RRL(1): + case REG_EASRC_RRL(2): + case REG_EASRC_RRL(3): + case REG_EASRC_RRH(0): + case REG_EASRC_RRH(1): + case REG_EASRC_RRH(2): + case REG_EASRC_RRH(3): + case REG_EASRC_RUC(0): + case REG_EASRC_RUC(1): + case REG_EASRC_RUC(2): + case REG_EASRC_RUC(3): + case REG_EASRC_RUR(0): + case REG_EASRC_RUR(1): + case REG_EASRC_RUR(2): + case REG_EASRC_RUR(3): /* fallthrough */ + case REG_EASRC_RCTCL: + case REG_EASRC_RCTCH: + case REG_EASRC_PCF(0): + case REG_EASRC_PCF(1): + case REG_EASRC_PCF(2): + case REG_EASRC_PCF(3): /* fallthrough */ + case REG_EASRC_CRCC: + case REG_EASRC_IRQC: + case REG_EASRC_IRQF: + case REG_EASRC_CS0(0): + case REG_EASRC_CS0(1): + case REG_EASRC_CS0(2): + case REG_EASRC_CS0(3): + case REG_EASRC_CS1(0): + case REG_EASRC_CS1(1): + case REG_EASRC_CS1(2): + case REG_EASRC_CS1(3): + case REG_EASRC_CS2(0): + case REG_EASRC_CS2(1): + case REG_EASRC_CS2(2): + case REG_EASRC_CS2(3): + case REG_EASRC_CS3(0): + case REG_EASRC_CS3(1): + case REG_EASRC_CS3(2): + case REG_EASRC_CS3(3): + case REG_EASRC_CS4(0): + case REG_EASRC_CS4(1): + case REG_EASRC_CS4(2): + case REG_EASRC_CS4(3): + case REG_EASRC_CS5(0): + case REG_EASRC_CS5(1): + case REG_EASRC_CS5(2): + case REG_EASRC_CS5(3): /* fallthrough */ + case REG_EASRC_DBGC: + case REG_EASRC_DBGS: + return true; + default: + return false; + } +} + +static bool fsl_easrc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_EASRC_WRFIFO(0): + case REG_EASRC_WRFIFO(1): + case REG_EASRC_WRFIFO(2): + case REG_EASRC_WRFIFO(3): + case REG_EASRC_CC(0): + case REG_EASRC_CC(1): + case REG_EASRC_CC(2): + case REG_EASRC_CC(3): + case REG_EASRC_CCE1(0): + case REG_EASRC_CCE1(1): + case REG_EASRC_CCE1(2): + case REG_EASRC_CCE1(3): + case REG_EASRC_CCE2(0): + case REG_EASRC_CCE2(1): + case REG_EASRC_CCE2(2): + case REG_EASRC_CCE2(3): + case REG_EASRC_CIA(0): + case REG_EASRC_CIA(1): + case REG_EASRC_CIA(2): + case REG_EASRC_CIA(3): + case REG_EASRC_DPCS0R0(0): + case REG_EASRC_DPCS0R0(1): + case REG_EASRC_DPCS0R0(2): + case REG_EASRC_DPCS0R0(3): + case REG_EASRC_DPCS0R1(0): + case REG_EASRC_DPCS0R1(1): + case REG_EASRC_DPCS0R1(2): + case REG_EASRC_DPCS0R1(3): + case REG_EASRC_DPCS0R2(0): + case REG_EASRC_DPCS0R2(1): + case REG_EASRC_DPCS0R2(2): + case REG_EASRC_DPCS0R2(3): + case REG_EASRC_DPCS0R3(0): + case REG_EASRC_DPCS0R3(1): + case REG_EASRC_DPCS0R3(2): + case REG_EASRC_DPCS0R3(3): + case REG_EASRC_DPCS1R0(0): + case REG_EASRC_DPCS1R0(1): + case REG_EASRC_DPCS1R0(2): + case REG_EASRC_DPCS1R0(3): + case REG_EASRC_DPCS1R1(0): + case REG_EASRC_DPCS1R1(1): + case REG_EASRC_DPCS1R1(2): + case REG_EASRC_DPCS1R1(3): + case REG_EASRC_DPCS1R2(0): + case REG_EASRC_DPCS1R2(1): + case REG_EASRC_DPCS1R2(2): + case REG_EASRC_DPCS1R2(3): + case REG_EASRC_DPCS1R3(0): + case REG_EASRC_DPCS1R3(1): + case REG_EASRC_DPCS1R3(2): + case REG_EASRC_DPCS1R3(3): + case REG_EASRC_COC(0): + case REG_EASRC_COC(1): + case REG_EASRC_COC(2): + case REG_EASRC_COC(3): + case REG_EASRC_COA(0): + case REG_EASRC_COA(1): + case REG_EASRC_COA(2): + case REG_EASRC_COA(3): + case REG_EASRC_RRL(0): + case REG_EASRC_RRL(1): + case REG_EASRC_RRL(2): + case REG_EASRC_RRL(3): + case REG_EASRC_RRH(0): + case REG_EASRC_RRH(1): + case REG_EASRC_RRH(2): + case REG_EASRC_RRH(3): + case REG_EASRC_RUC(0): + case REG_EASRC_RUC(1): + case REG_EASRC_RUC(2): + case REG_EASRC_RUC(3): + case REG_EASRC_RUR(0): + case REG_EASRC_RUR(1): + case REG_EASRC_RUR(2): + case REG_EASRC_RUR(3): /* fallthrough */ + case REG_EASRC_RCTCL: + case REG_EASRC_RCTCH: + case REG_EASRC_PCF(0): + case REG_EASRC_PCF(1): + case REG_EASRC_PCF(2): + case REG_EASRC_PCF(3): /* fallthrough */ + case REG_EASRC_CRCM: + case REG_EASRC_CRCC: + case REG_EASRC_IRQC: + case REG_EASRC_IRQF: + case REG_EASRC_CS0(0): + case REG_EASRC_CS0(1): + case REG_EASRC_CS0(2): + case REG_EASRC_CS0(3): + case REG_EASRC_CS1(0): + case REG_EASRC_CS1(1): + case REG_EASRC_CS1(2): + case REG_EASRC_CS1(3): + case REG_EASRC_CS2(0): + case REG_EASRC_CS2(1): + case REG_EASRC_CS2(2): + case REG_EASRC_CS2(3): + case REG_EASRC_CS3(0): + case REG_EASRC_CS3(1): + case REG_EASRC_CS3(2): + case REG_EASRC_CS3(3): + case REG_EASRC_CS4(0): + case REG_EASRC_CS4(1): + case REG_EASRC_CS4(2): + case REG_EASRC_CS4(3): + case REG_EASRC_CS5(0): + case REG_EASRC_CS5(1): + case REG_EASRC_CS5(2): + case REG_EASRC_CS5(3): /* fallthrough */ + case REG_EASRC_DBGC: + return true; + default: + return false; + } +} + +static bool fsl_easrc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_EASRC_RDFIFO(0): + case REG_EASRC_RDFIFO(1): + case REG_EASRC_RDFIFO(2): + case REG_EASRC_RDFIFO(3): + case REG_EASRC_SFS(0): + case REG_EASRC_SFS(1): + case REG_EASRC_SFS(2): + case REG_EASRC_SFS(3): + case REG_EASRC_IRQF: + case REG_EASRC_DBGS: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_easrc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_EASRC_DBGS, + .reg_defaults = fsl_easrc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_easrc_reg_defaults), + .readable_reg = fsl_easrc_readable_reg, + .volatile_reg = fsl_easrc_volatile_reg, + .writeable_reg = fsl_easrc_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +#include "fsl_easrc_m2m.c" + +void easrc_dump_firmware(struct fsl_easrc *easrc) +{ + struct device *dev = &easrc->pdev->dev; + struct asrc_firmware_hdr *firm = easrc->firmware_hdr; + struct interp_params *interp = easrc->interp; + struct prefil_params *prefil = easrc->prefil; + int i; + + if (firm->magic != FIRMWARE_MAGIC) { + dev_err(dev, "Wrong magic. Something went wrong!"); + return; + } + + dev_dbg(dev, "Firmware v%u dump:\n", firm->firmware_version); + pr_debug("Num prefitler scenarios: %u\n", firm->prefil_scen); + pr_debug("Num interpolation scenarios: %u\n", firm->interp_scen); + pr_debug("\nInterpolation scenarios:\n"); + + for (i = 0; i < firm->interp_scen; i++) { + if (interp[i].magic != FIRMWARE_MAGIC) { + pr_debug("%d. wrong interp magic: %x\n", + i, interp[i].magic); + continue; + } + pr_debug("%d. taps: %u, phases: %u, center: %llu\n", i, + interp[i].num_taps, interp[i].num_phases, + interp[i].center_tap); + } + + for (i = 0; i < firm->prefil_scen; i++) { + if (prefil[i].magic != FIRMWARE_MAGIC) { + pr_debug("%d. wrong prefil magic: %x\n", + i, prefil[i].magic); + continue; + } + pr_debug("%d. insr: %u, outsr: %u, st1: %u, st2: %u\n", i, + prefil[i].insr, prefil[i].outsr, + prefil[i].st1_taps, prefil[i].st2_taps); + } + + dev_dbg(dev, "end of firmware dump\n"); +} + +int easrc_get_firmware(struct fsl_easrc *easrc) +{ + u32 pnum, inum, offset; + int ret; + + if (!easrc) + return -EINVAL; + + ret = request_firmware(&easrc->fw, easrc->fw_name, + &easrc->pdev->dev); + if (ret) + return ret; + + easrc->firmware_hdr = (struct asrc_firmware_hdr *)easrc->fw->data; + pnum = easrc->firmware_hdr->prefil_scen; + inum = easrc->firmware_hdr->interp_scen; + + if (inum) { + offset = sizeof(struct asrc_firmware_hdr); + easrc->interp = + (struct interp_params *)(easrc->fw->data + offset); + } + + if (pnum) { + offset = sizeof(struct asrc_firmware_hdr) + + inum * sizeof(struct interp_params); + easrc->prefil = + (struct prefil_params *)(easrc->fw->data + offset); + } + + return 0; +} + +static irqreturn_t fsl_easrc_isr(int irq, void *dev_id) +{ + struct fsl_easrc *easrc = (struct fsl_easrc *)dev_id; + struct device *dev = &easrc->pdev->dev; + + /* TODO treat the interrupt */ + dev_err(dev, "interrupt\n"); + + return IRQ_HANDLED; +} + +static const struct of_device_id fsl_easrc_dt_ids[] = { + { .compatible = "fsl,imx8mn-easrc",}, + {} +}; + +MODULE_DEVICE_TABLE(of, fsl_easrc_dt_ids); + +static int fsl_easrc_probe(struct platform_device *pdev) +{ + struct fsl_easrc *easrc; + struct resource *res; + struct device_node *np; + void __iomem *regs; + int ret, irq; + u32 width; + + easrc = devm_kzalloc(&pdev->dev, sizeof(*easrc), GFP_KERNEL); + if (!easrc) + return -ENOMEM; + + strncpy(easrc->name, "mxc_asrc", sizeof(easrc->name) - 1); + + easrc->pdev = pdev; + np = pdev->dev.of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + dev_err(&pdev->dev, "failed ioremap\n"); + return PTR_ERR(regs); + } + + easrc->paddr = res->start; + + easrc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs, + &fsl_easrc_regmap_config); + if (IS_ERR(easrc->regmap)) { + dev_err(&pdev->dev, "failed to init regmap"); + return PTR_ERR(easrc->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", + dev_name(&pdev->dev)); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_easrc_isr, 0, + dev_name(&pdev->dev), easrc); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + easrc->mem_clk = devm_clk_get(&pdev->dev, "mem"); + if (IS_ERR(easrc->mem_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(easrc->mem_clk); + } + + /*Set default value*/ + easrc->chn_avail = 32; + easrc->rs_num_taps = EASRC_RS_128_TAPS; + easrc->const_coeff = 0x3FF0000000000000; + + ret = of_property_read_u32(np, "fsl,asrc-rate", + &easrc->easrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to asrc rate\n"); + return ret; + } + + ret = of_property_read_u32(np, "fsl,asrc-width", + &width); + if (ret) { + dev_err(&pdev->dev, "failed to asrc width\n"); + return ret; + } + + if (width != 16 && width != 24 && width != 32 && width != 20) { + dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n"); + width = 24; + } + + if (width == 24) + easrc->easrc_format = SNDRV_PCM_FORMAT_S24_LE; + else if (width == 16) + easrc->easrc_format = SNDRV_PCM_FORMAT_S16_LE; + else + easrc->easrc_format = SNDRV_PCM_FORMAT_S32_LE; + + platform_set_drvdata(pdev, easrc); + pm_runtime_enable(&pdev->dev); + + spin_lock_init(&easrc->lock); + + regcache_cache_only(easrc->regmap, true); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_easrc_component, + &fsl_easrc_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &fsl_easrc_dma_component, + NULL, 0); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC platform\n"); + return ret; + } + + ret = fsl_easrc_m2m_init(easrc); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + + ret = of_property_read_string(np, + "fsl,easrc-ram-script-name", + &easrc->fw_name); + if (ret) { + dev_err(&pdev->dev, "failed to get firmware name\n"); + return ret; + } + + return 0; +} + +static int fsl_easrc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_easrc_runtime_suspend(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + + spin_lock_irqsave(&easrc->lock, lock_flags); + easrc->firmware_loaded = 0; + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + return 0; +} + +static int fsl_easrc_runtime_resume(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_context *ctx; + unsigned long lock_flags; + int ret; + int i; + + ret = clk_prepare_enable(easrc->mem_clk); + if (ret) + return ret; + + regcache_cache_only(easrc->regmap, false); + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + + spin_lock_irqsave(&easrc->lock, lock_flags); + if (easrc->firmware_loaded) { + spin_unlock_irqrestore(&easrc->lock, lock_flags); + goto skip_load; + } + easrc->firmware_loaded = 1; + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = easrc_get_firmware(easrc); + if (ret) { + dev_err(dev, "failed to get firmware\n"); + goto disable_mem_clk; + } + + /* Write Resampling Coefficients + * The coefficient RAM must be configured prior to beginning of + * any context processing within the ASRC + */ + ret = fsl_easrc_resampler_config(easrc); + if (ret) { + dev_err(dev, "resampler config failed\n"); + goto disable_mem_clk; + } + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + ctx = easrc->ctx[i]; + if (ctx) { + set_rs_ratio(ctx); + ctx->out_missed_sample = ctx->in_filled_sample * + ctx->out_params.sample_rate / + ctx->in_params.sample_rate; + if (ctx->in_filled_sample * ctx->out_params.sample_rate + % ctx->in_params.sample_rate != 0) + ctx->out_missed_sample += 1; + + ret = write_pf_coeff_mem(easrc, i, + ctx->st1_coeff, + ctx->st1_num_taps, + ctx->st1_addexp); + if (ret) + goto disable_mem_clk; + + ret = write_pf_coeff_mem(easrc, i, + ctx->st2_coeff, + ctx->st2_num_taps, + ctx->st2_addexp); + if (ret) + goto disable_mem_clk; + } + } + +skip_load: + return 0; + +disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +} +#endif /*CONFIG_PM*/ + +#ifdef CONFIG_PM_SLEEP +static int fsl_easrc_suspend(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + int ret; + + fsl_easrc_m2m_suspend(easrc); + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_easrc_resume(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + + fsl_easrc_m2m_resume(easrc); + + return ret; +} +#endif /*CONFIG_PM_SLEEP*/ + +static const struct dev_pm_ops fsl_easrc_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_easrc_runtime_suspend, + fsl_easrc_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_easrc_suspend, + fsl_easrc_resume) +}; + +static struct platform_driver fsl_easrc_driver = { + .probe = fsl_easrc_probe, + .remove = fsl_easrc_remove, + .driver = { + .name = "fsl-easrc", + .pm = &fsl_easrc_pm_ops, + .of_match_table = fsl_easrc_dt_ids, + }, +}; +module_platform_driver(fsl_easrc_driver); + +MODULE_DESCRIPTION("NXP Enhanced Asynchronous Sample Rate (eASRC) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h new file mode 100644 index 000000000000..aeeb8bcdc12a --- /dev/null +++ b/sound/soc/fsl/fsl_easrc.h @@ -0,0 +1,698 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 NXP + */ + +#ifndef _FSL_EASRC_H +#define _FSL_EASRC_H + +#include <sound/asound.h> +#include <uapi/linux/mxc_asrc.h> +#include <linux/miscdevice.h> +#include <linux/platform_data/dma-imx.h> + +/* EASRC Register Map */ + +/* ASRC Input Write FIFO */ +#define REG_EASRC_WRFIFO(ctx) (0x000 + 4 * (ctx)) +/* ASRC Output Read FIFO */ +#define REG_EASRC_RDFIFO(ctx) (0x010 + 4 * (ctx)) +/* ASRC Context Control */ +#define REG_EASRC_CC(ctx) (0x020 + 4 * (ctx)) +/* ASRC Context Control Extended 1 */ +#define REG_EASRC_CCE1(ctx) (0x030 + 4 * (ctx)) +/* ASRC Context Control Extended 2 */ +#define REG_EASRC_CCE2(ctx) (0x040 + 4 * (ctx)) +/* ASRC Control Input Access */ +#define REG_EASRC_CIA(ctx) (0x050 + 4 * (ctx)) +/* ASRC Datapath Processor Control Slot0 */ +#define REG_EASRC_DPCS0R0(ctx) (0x060 + 4 * (ctx)) +#define REG_EASRC_DPCS0R1(ctx) (0x070 + 4 * (ctx)) +#define REG_EASRC_DPCS0R2(ctx) (0x080 + 4 * (ctx)) +#define REG_EASRC_DPCS0R3(ctx) (0x090 + 4 * (ctx)) +/* ASRC Datapath Processor Control Slot1 */ +#define REG_EASRC_DPCS1R0(ctx) (0x0A0 + 4 * (ctx)) +#define REG_EASRC_DPCS1R1(ctx) (0x0B0 + 4 * (ctx)) +#define REG_EASRC_DPCS1R2(ctx) (0x0C0 + 4 * (ctx)) +#define REG_EASRC_DPCS1R3(ctx) (0x0D0 + 4 * (ctx)) +/* ASRC Context Output Control */ +#define REG_EASRC_COC(ctx) (0x0E0 + 4 * (ctx)) +/* ASRC Control Output Access */ +#define REG_EASRC_COA(ctx) (0x0F0 + 4 * (ctx)) +/* ASRC Sample FIFO Status */ +#define REG_EASRC_SFS(ctx) (0x100 + 4 * (ctx)) +/* ASRC Resampling Ratio Low */ +#define REG_EASRC_RRL(ctx) (0x110 + 8 * (ctx)) +/* ASRC Resampling Ratio High */ +#define REG_EASRC_RRH(ctx) (0x114 + 8 * (ctx)) +/* ASRC Resampling Ratio Update Control */ +#define REG_EASRC_RUC(ctx) (0x130 + 4 * (ctx)) +/* ASRC Resampling Ratio Update Rate */ +#define REG_EASRC_RUR(ctx) (0x140 + 4 * (ctx)) +/* ASRC Resampling Center Tap Coefficient Low */ +#define REG_EASRC_RCTCL (0x150) +/* ASRC Resampling Center Tap Coefficient High */ +#define REG_EASRC_RCTCH (0x154) +/* ASRC Prefilter Coefficient FIFO */ +#define REG_EASRC_PCF(ctx) (0x160 + 4 * (ctx)) +/* ASRC Context Resampling Coefficient Memory */ +#define REG_EASRC_CRCM 0x170 +/* ASRC Context Resampling Coefficient Control*/ +#define REG_EASRC_CRCC 0x174 +/* ASRC Interrupt Control */ +#define REG_EASRC_IRQC 0x178 +/* ASRC Interrupt Status Flags */ +#define REG_EASRC_IRQF 0x17C +/* ASRC Channel Status 0 */ +#define REG_EASRC_CS0(ctx) (0x180 + 4 * (ctx)) +/* ASRC Channel Status 1 */ +#define REG_EASRC_CS1(ctx) (0x190 + 4 * (ctx)) +/* ASRC Channel Status 2 */ +#define REG_EASRC_CS2(ctx) (0x1A0 + 4 * (ctx)) +/* ASRC Channel Status 3 */ +#define REG_EASRC_CS3(ctx) (0x1B0 + 4 * (ctx)) +/* ASRC Channel Status 4 */ +#define REG_EASRC_CS4(ctx) (0x1C0 + 4 * (ctx)) +/* ASRC Channel Status 5 */ +#define REG_EASRC_CS5(ctx) (0x1D0 + 4 * (ctx)) +/* ASRC Debug Control Register */ +#define REG_EASRC_DBGC 0x1E0 +/* ASRC Debug Status Register */ +#define REG_EASRC_DBGS 0x1E4 + +#define REG_EASRC_FIFO(x, ctx) (x == IN ? REG_EASRC_WRFIFO(ctx) \ + : REG_EASRC_RDFIFO(ctx)) + +/* ASRC Context Control (CC) */ +#define EASRC_CC_EN_SHIFT 31 +#define EASRC_CC_EN_MASK BIT(EASRC_CC_EN_SHIFT) +#define EASRC_CC_EN BIT(EASRC_CC_EN_SHIFT) +#define EASRC_CC_STOP_SHIFT 29 +#define EASRC_CC_STOP_MASK BIT(EASRC_CC_STOP_SHIFT) +#define EASRC_CC_STOP BIT(EASRC_CC_STOP_SHIFT) +#define EASRC_CC_FWMDE_SHIFT 28 +#define EASRC_CC_FWMDE_MASK BIT(EASRC_CC_FWMDE_SHIFT) +#define EASRC_CC_FWMDE BIT(EASRC_CC_FWMDE_SHIFT) +#define EASRC_CC_FIFO_WTMK_SHIFT 16 +#define EASRC_CC_FIFO_WTMK_WIDTH 7 +#define EASRC_CC_FIFO_WTMK_MASK ((BIT(EASRC_CC_FIFO_WTMK_WIDTH) - 1) \ + << EASRC_CC_FIFO_WTMK_SHIFT) +#define EASRC_CC_FIFO_WTMK(v) (((v) << EASRC_CC_FIFO_WTMK_SHIFT) \ + & EASRC_CC_FIFO_WTMK_MASK) +#define EASRC_CC_SAMPLE_POS_SHIFT 11 +#define EASRC_CC_SAMPLE_POS_WIDTH 5 +#define EASRC_CC_SAMPLE_POS_MASK ((BIT(EASRC_CC_SAMPLE_POS_WIDTH) - 1) \ + << EASRC_CC_SAMPLE_POS_SHIFT) +#define EASRC_CC_SAMPLE_POS(v) (((v) << EASRC_CC_SAMPLE_POS_SHIFT) \ + & EASRC_CC_SAMPLE_POS_MASK) +#define EASRC_CC_ENDIANNESS_SHIFT 10 +#define EASRC_CC_ENDIANNESS_MASK BIT(EASRC_CC_ENDIANNESS_SHIFT) +#define EASRC_CC_ENDIANNESS BIT(EASRC_CC_ENDIANNESS_SHIFT) +#define EASRC_CC_BPS_SHIFT 8 +#define EASRC_CC_BPS_WIDTH 2 +#define EASRC_CC_BPS_MASK ((BIT(EASRC_CC_BPS_WIDTH) - 1) \ + << EASRC_CC_BPS_SHIFT) +#define EASRC_CC_BPS(v) (((v) << EASRC_CC_BPS_SHIFT) \ + & EASRC_CC_BPS_MASK) +#define EASRC_CC_FMT_SHIFT 7 +#define EASRC_CC_FMT_MASK BIT(EASRC_CC_FMT_SHIFT) +#define EASRC_CC_FMT BIT(EASRC_CC_FMT_SHIFT) +#define EASRC_CC_INSIGN_SHIFT 6 +#define EASRC_CC_INSIGN_MASK BIT(EASRC_CC_INSIGN_SHIFT) +#define EASRC_CC_INSIGN BIT(EASRC_CC_INSIGN_SHIFT) +#define EASRC_CC_CHEN_SHIFT 0 +#define EASRC_CC_CHEN_WIDTH 5 +#define EASRC_CC_CHEN_MASK ((BIT(EASRC_CC_CHEN_WIDTH) - 1) \ + << EASRC_CC_CHEN_SHIFT) +#define EASRC_CC_CHEN(v) (((v) << EASRC_CC_CHEN_SHIFT) \ + & EASRC_CC_CHEN_MASK) + +/* ASRC Context Control Extended 1 (CCE1) */ +#define EASRC_CCE1_COEF_WS_SHIFT 25 +#define EASRC_CCE1_COEF_WS_MASK BIT(EASRC_CCE1_COEF_WS_SHIFT) +#define EASRC_CCE1_COEF_WS BIT(EASRC_CCE1_COEF_WS_SHIFT) +#define EASRC_CCE1_COEF_MEM_RST_SHIFT 24 +#define EASRC_CCE1_COEF_MEM_RST_MASK BIT(EASRC_CCE1_COEF_MEM_RST_SHIFT) +#define EASRC_CCE1_COEF_MEM_RST BIT(EASRC_CCE1_COEF_MEM_RST_SHIFT) +#define EASRC_CCE1_PF_EXP_SHIFT 16 +#define EASRC_CCE1_PF_EXP_WIDTH 8 +#define EASRC_CCE1_PF_EXP_MASK ((BIT(EASRC_CCE1_PF_EXP_WIDTH) - 1) \ + << EASRC_CCE1_PF_EXP_SHIFT) +#define EASRC_CCE1_PF_EXP(v) (((v) << EASRC_CCE1_PF_EXP_SHIFT) \ + & EASRC_CCE1_PF_EXP_MASK) +#define EASRC_CCE1_PF_ST1_WBFP_SHIFT 9 +#define EASRC_CCE1_PF_ST1_WBFP_MASK BIT(EASRC_CCE1_PF_ST1_WBFP_SHIFT) +#define EASRC_CCE1_PF_ST1_WBFP BIT(EASRC_CCE1_PF_ST1_WBFP_SHIFT) +#define EASRC_CCE1_PF_TSEN_SHIFT 8 +#define EASRC_CCE1_PF_TSEN_MASK BIT(EASRC_CCE1_PF_TSEN_SHIFT) +#define EASRC_CCE1_PF_TSEN BIT(EASRC_CCE1_PF_TSEN_SHIFT) +#define EASRC_CCE1_RS_BYPASS_SHIFT 7 +#define EASRC_CCE1_RS_BYPASS_MASK BIT(EASRC_CCE1_RS_BYPASS_SHIFT) +#define EASRC_CCE1_RS_BYPASS BIT(EASRC_CCE1_RS_BYPASS_SHIFT) +#define EASRC_CCE1_PF_BYPASS_SHIFT 6 +#define EASRC_CCE1_PF_BYPASS_MASK BIT(EASRC_CCE1_PF_BYPASS_SHIFT) +#define EASRC_CCE1_PF_BYPASS BIT(EASRC_CCE1_PF_BYPASS_SHIFT) +#define EASRC_CCE1_RS_STOP_SHIFT 5 +#define EASRC_CCE1_RS_STOP_MASK BIT(EASRC_CCE1_RS_STOP_SHIFT) +#define EASRC_CCE1_RS_STOP BIT(EASRC_CCE1_RS_STOP_SHIFT) +#define EASRC_CCE1_PF_STOP_SHIFT 4 +#define EASRC_CCE1_PF_STOP_MASK BIT(EASRC_CCE1_PF_STOP_SHIFT) +#define EASRC_CCE1_PF_STOP BIT(EASRC_CCE1_PF_STOP_SHIFT) +#define EASRC_CCE1_RS_INIT_SHIFT 2 +#define EASRC_CCE1_RS_INIT_WIDTH 2 +#define EASRC_CCE1_RS_INIT_MASK ((BIT(EASRC_CCE1_RS_INIT_WIDTH) - 1) \ + << EASRC_CCE1_RS_INIT_SHIFT) +#define EASRC_CCE1_RS_INIT(v) (((v) << EASRC_CCE1_RS_INIT_SHIFT) \ + & EASRC_CCE1_RS_INIT_MASK) +#define EASRC_CCE1_PF_INIT_SHIFT 0 +#define EASRC_CCE1_PF_INIT_WIDTH 2 +#define EASRC_CCE1_PF_INIT_MASK ((BIT(EASRC_CCE1_PF_INIT_WIDTH) - 1) \ + << EASRC_CCE1_PF_INIT_SHIFT) +#define EASRC_CCE1_PF_INIT(v) (((v) << EASRC_CCE1_PF_INIT_SHIFT) \ + & EASRC_CCE1_PF_INIT_MASK) + +/* ASRC Context Control Extended 2 (CCE2) */ +#define EASRC_CCE2_ST2_TAPS_SHIFT 16 +#define EASRC_CCE2_ST2_TAPS_WIDTH 9 +#define EASRC_CCE2_ST2_TAPS_MASK ((BIT(EASRC_CCE2_ST2_TAPS_WIDTH) - 1) \ + << EASRC_CCE2_ST2_TAPS_SHIFT) +#define EASRC_CCE2_ST2_TAPS(v) (((v) << EASRC_CCE2_ST2_TAPS_SHIFT) \ + & EASRC_CCE2_ST2_TAPS_MASK) +#define EASRC_CCE2_ST1_TAPS_SHIFT 0 +#define EASRC_CCE2_ST1_TAPS_WIDTH 9 +#define EASRC_CCE2_ST1_TAPS_MASK ((BIT(EASRC_CCE2_ST1_TAPS_WIDTH) - 1) \ + << EASRC_CCE2_ST1_TAPS_SHIFT) +#define EASRC_CCE2_ST1_TAPS(v) (((v) << EASRC_CCE2_ST1_TAPS_SHIFT) \ + & EASRC_CCE2_ST1_TAPS_MASK) + +/* ASRC Control Input Access (CIA) */ +#define EASRC_CIA_ITER_SHIFT 16 +#define EASRC_CIA_ITER_WIDTH 6 +#define EASRC_CIA_ITER_MASK ((BIT(EASRC_CIA_ITER_WIDTH) - 1) \ + << EASRC_CIA_ITER_SHIFT) +#define EASRC_CIA_ITER(v) (((v) << EASRC_CIA_ITER_SHIFT) \ + & EASRC_CIA_ITER_MASK) +#define EASRC_CIA_GRLEN_SHIFT 8 +#define EASRC_CIA_GRLEN_WIDTH 6 +#define EASRC_CIA_GRLEN_MASK ((BIT(EASRC_CIA_GRLEN_WIDTH) - 1) \ + << EASRC_CIA_GRLEN_SHIFT) +#define EASRC_CIA_GRLEN(v) (((v) << EASRC_CIA_GRLEN_SHIFT) \ + & EASRC_CIA_GRLEN_MASK) +#define EASRC_CIA_ACCLEN_SHIFT 0 +#define EASRC_CIA_ACCLEN_WIDTH 6 +#define EASRC_CIA_ACCLEN_MASK ((BIT(EASRC_CIA_ACCLEN_WIDTH) - 1) \ + << EASRC_CIA_ACCLEN_SHIFT) +#define EASRC_CIA_ACCLEN(v) (((v) << EASRC_CIA_ACCLEN_SHIFT) \ + & EASRC_CIA_ACCLEN_MASK) + +/* ASRC Datapath Processor Control Slot0 Register0 (DPCS0R0) */ +#define EASRC_DPCS0R0_MAXCH_SHIFT 24 +#define EASRC_DPCS0R0_MAXCH_WIDTH 5 +#define EASRC_DPCS0R0_MAXCH_MASK ((BIT(EASRC_DPCS0R0_MAXCH_WIDTH) - 1) \ + << EASRC_DPCS0R0_MAXCH_SHIFT) +#define EASRC_DPCS0R0_MAXCH(v) (((v) << EASRC_DPCS0R0_MAXCH_SHIFT) \ + & EASRC_DPCS0R0_MAXCH_MASK) +#define EASRC_DPCS0R0_MINCH_SHIFT 16 +#define EASRC_DPCS0R0_MINCH_WIDTH 5 +#define EASRC_DPCS0R0_MINCH_MASK ((BIT(EASRC_DPCS0R0_MINCH_WIDTH) - 1) \ + << EASRC_DPCS0R0_MINCH_SHIFT) +#define EASRC_DPCS0R0_MINCH(v) (((v) << EASRC_DPCS0R0_MINCH_SHIFT) \ + & EASRC_DPCS0R0_MINCH_MASK) +#define EASRC_DPCS0R0_NUMCH_SHIFT 8 +#define EASRC_DPCS0R0_NUMCH_WIDTH 5 +#define EASRC_DPCS0R0_NUMCH_MASK ((BIT(EASRC_DPCS0R0_NUMCH_WIDTH) - 1) \ + << EASRC_DPCS0R0_NUMCH_SHIFT) +#define EASRC_DPCS0R0_NUMCH(v) (((v) << EASRC_DPCS0R0_NUMCH_SHIFT) \ + & EASRC_DPCS0R0_NUMCH_MASK) +#define EASRC_DPCS0R0_CTXNUM_SHIFT 1 +#define EASRC_DPCS0R0_CTXNUM_WIDTH 2 +#define EASRC_DPCS0R0_CTXNUM_MASK ((BIT(EASRC_DPCS0R0_CTXNUM_WIDTH) - 1) \ + << EASRC_DPCS0R0_CTXNUM_SHIFT) +#define EASRC_DPCS0R0_CTXNUM(v) (((v) << EASRC_DPCS0R0_CTXNUM_SHIFT) \ + & EASRC_DPCS0R0_CTXNUM_MASK) +#define EASRC_DPCS0R0_EN_SHIFT 0 +#define EASRC_DPCS0R0_EN_MASK BIT(EASRC_DPCS0R0_EN_SHIFT) +#define EASRC_DPCS0R0_EN BIT(EASRC_DPCS0R0_EN_SHIFT) + +/* ASRC Datapath Processor Control Slot0 Register1 (DPCS0R1) */ +#define EASRC_DPCS0R1_ST1_EXP_SHIFT 0 +#define EASRC_DPCS0R1_ST1_EXP_WIDTH 13 +#define EASRC_DPCS0R1_ST1_EXP_MASK ((BIT(EASRC_DPCS0R1_ST1_EXP_WIDTH) - 1) \ + << EASRC_DPCS0R1_ST1_EXP_SHIFT) +#define EASRC_DPCS0R1_ST1_EXP(v) (((v) << EASRC_DPCS0R1_ST1_EXP_SHIFT) \ + & EASRC_DPCS0R1_ST1_EXP_MASK) + +/* ASRC Datapath Processor Control Slot0 Register2 (DPCS0R2) */ +#define EASRC_DPCS0R2_ST1_MA_SHIFT 16 +#define EASRC_DPCS0R2_ST1_MA_WIDTH 13 +#define EASRC_DPCS0R2_ST1_MA_MASK ((BIT(EASRC_DPCS0R2_ST1_MA_WIDTH) - 1) \ + << EASRC_DPCS0R2_ST1_MA_SHIFT) +#define EASRC_DPCS0R2_ST1_MA(v) (((v) << EASRC_DPCS0R2_ST1_MA_SHIFT) \ + & EASRC_DPCS0R2_ST1_MA_MASK) +#define EASRC_DPCS0R2_ST1_SA_SHIFT 0 +#define EASRC_DPCS0R2_ST1_SA_WIDTH 13 +#define EASRC_DPCS0R2_ST1_SA_MASK ((BIT(EASRC_DPCS0R2_ST1_SA_WIDTH) - 1) \ + << EASRC_DPCS0R2_ST1_SA_SHIFT) +#define EASRC_DPCS0R2_ST1_SA(v) (((v) << EASRC_DPCS0R2_ST1_SA_SHIFT) \ + & EASRC_DPCS0R2_ST1_SA_MASK) + +/* ASRC Datapath Processor Control Slot0 Register3 (DPCS0R3) */ +#define EASRC_DPCS0R3_ST2_MA_SHIFT 16 +#define EASRC_DPCS0R3_ST2_MA_WIDTH 13 +#define EASRC_DPCS0R3_ST2_MA_MASK ((BIT(EASRC_DPCS0R3_ST2_MA_WIDTH) - 1) \ + << EASRC_DPCS0R3_ST2_MA_SHIFT) +#define EASRC_DPCS0R3_ST2_MA(v) (((v) << EASRC_DPCS0R3_ST2_MA_SHIFT) \ + & EASRC_DPCS0R3_ST2_MA_MASK) +#define EASRC_DPCS0R3_ST2_SA_SHIFT 0 +#define EASRC_DPCS0R3_ST2_SA_WIDTH 13 +#define EASRC_DPCS0R3_ST2_SA_MASK ((BIT(EASRC_DPCS0R3_ST2_SA_WIDTH) - 1) \ + << EASRC_DPCS0R3_ST2_SA_SHIFT) +#define EASRC_DPCS0R3_ST2_SA(v) (((v) << EASRC_DPCS0R3_ST2_SA_SHIFT) \ + & EASRC_DPCS0R3_ST2_SA_MASK) + +/* ASRC Context Output Control (COC) */ +#define EASRC_COC_FWMDE_SHIFT 28 +#define EASRC_COC_FWMDE_MASK BIT(EASRC_COC_FWMDE_SHIFT) +#define EASRC_COC_FWMDE BIT(EASRC_COC_FWMDE_SHIFT) +#define EASRC_COC_FIFO_WTMK_SHIFT 16 +#define EASRC_COC_FIFO_WTMK_WIDTH 7 +#define EASRC_COC_FIFO_WTMK_MASK ((BIT(EASRC_COC_FIFO_WTMK_WIDTH) - 1) \ + << EASRC_COC_FIFO_WTMK_SHIFT) +#define EASRC_COC_FIFO_WTMK(v) (((v) << EASRC_COC_FIFO_WTMK_SHIFT) \ + & EASRC_COC_FIFO_WTMK_MASK) +#define EASRC_COC_SAMPLE_POS_SHIFT 11 +#define EASRC_COC_SAMPLE_POS_WIDTH 5 +#define EASRC_COC_SAMPLE_POS_MASK ((BIT(EASRC_COC_SAMPLE_POS_WIDTH) - 1) \ + << EASRC_COC_SAMPLE_POS_SHIFT) +#define EASRC_COC_SAMPLE_POS(v) (((v) << EASRC_COC_SAMPLE_POS_SHIFT) \ + & EASRC_COC_SAMPLE_POS_MASK) +#define EASRC_COC_ENDIANNESS_SHIFT 10 +#define EASRC_COC_ENDIANNESS_MASK BIT(EASRC_COC_ENDIANNESS_SHIFT) +#define EASRC_COC_ENDIANNESS BIT(EASRC_COC_ENDIANNESS_SHIFT) +#define EASRC_COC_BPS_SHIFT 8 +#define EASRC_COC_BPS_WIDTH 2 +#define EASRC_COC_BPS_MASK ((BIT(EASRC_COC_BPS_WIDTH) - 1) \ + << EASRC_COC_BPS_SHIFT) +#define EASRC_COC_BPS(v) (((v) << EASRC_COC_BPS_SHIFT) \ + & EASRC_COC_BPS_MASK) +#define EASRC_COC_FMT_SHIFT 7 +#define EASRC_COC_FMT_MASK BIT(EASRC_COC_FMT_SHIFT) +#define EASRC_COC_FMT BIT(EASRC_COC_FMT_SHIFT) +#define EASRC_COC_OUTSIGN_SHIFT 6 +#define EASRC_COC_OUTSIGN_MASK BIT(EASRC_COC_OUTSIGN_SHIFT) +#define EASRC_COC_OUTSIGN_OUT BIT(EASRC_COC_OUTSIGN_SHIFT) +#define EASRC_COC_IEC_VDATA_SHIFT 2 +#define EASRC_COC_IEC_VDATA_MASK BIT(EASRC_COC_IEC_VDATA_SHIFT) +#define EASRC_COC_IEC_VDATA BIT(EASRC_COC_IEC_VDATA_SHIFT) +#define EASRC_COC_IEC_EN_SHIFT 1 +#define EASRC_COC_IEC_EN_MASK BIT(EASRC_COC_IEC_EN_SHIFT) +#define EASRC_COC_IEC_EN BIT(EASRC_COC_IEC_EN_SHIFT) +#define EASRC_COC_DITHER_EN_SHIFT 0 +#define EASRC_COC_DITHER_EN_MASK BIT(EASRC_COC_DITHER_EN_SHIFT) +#define EASRC_COC_DITHER_EN BIT(EASRC_COC_DITHER_EN_SHIFT) + +/* ASRC Control Output Access (COA) */ +#define EASRC_COA_ITER_SHIFT 16 +#define EASRC_COA_ITER_WIDTH 6 +#define EASRC_COA_ITER_MASK ((BIT(EASRC_COA_ITER_WIDTH) - 1) \ + << EASRC_COA_ITER_SHIFT) +#define EASRC_COA_ITER(v) (((v) << EASRC_COA_ITER_SHIFT) \ + & EASRC_COA_ITER_MASK) +#define EASRC_COA_GRLEN_SHIFT 8 +#define EASRC_COA_GRLEN_WIDTH 6 +#define EASRC_COA_GRLEN_MASK ((BIT(EASRC_COA_GRLEN_WIDTH) - 1) \ + << EASRC_COA_GRLEN_SHIFT) +#define EASRC_COA_GRLEN(v) (((v) << EASRC_COA_GRLEN_SHIFT) \ + & EASRC_COA_GRLEN_MASK) +#define EASRC_COA_ACCLEN_SHIFT 0 +#define EASRC_COA_ACCLEN_WIDTH 6 +#define EASRC_COA_ACCLEN_MASK ((BIT(EASRC_COA_ACCLEN_WIDTH) - 1) \ + << EASRC_COA_ACCLEN_SHIFT) +#define EASRC_COA_ACCLEN(v) (((v) << EASRC_COA_ACCLEN_SHIFT) \ + & EASRC_COA_ACCLEN_MASK) + +/* ASRC Sample FIFO Status (SFS) */ +#define EASRC_SFS_IWTMK_SHIFT 23 +#define EASRC_SFS_IWTMK_MASK BIT(EASRC_SFS_IWTMK_SHIFT) +#define EASRC_SFS_IWTMK BIT(EASRC_SFS_IWTMK_SHIFT) +#define EASRC_SFS_NSGI_SHIFT 16 +#define EASRC_SFS_NSGI_WIDTH 7 +#define EASRC_SFS_NSGI_MASK ((BIT(EASRC_SFS_NSGI_WIDTH) - 1) \ + << EASRC_SFS_NSGI_SHIFT) +#define EASRC_SFS_NSGI(v) (((v) << EASRC_SFS_NSGI_SHIFT) \ + & EASRC_SFS_NSGI_MASK) +#define EASRC_SFS_OWTMK_SHIFT 7 +#define EASRC_SFS_OWTMK_MASK BIT(EASRC_SFS_OWTMK_SHIFT) +#define EASRC_SFS_OWTMK BIT(EASRC_SFS_OWTMK_SHIFT) +#define EASRC_SFS_NSGO_SHIFT 0 +#define EASRC_SFS_NSGO_WIDTH 7 +#define EASRC_SFS_NSGO_MASK ((BIT(EASRC_SFS_NSGO_WIDTH) - 1) \ + << EASRC_SFS_NSGO_SHIFT) +#define EASRC_SFS_NSGO(v) (((v) << EASRC_SFS_NSGO_SHIFT) \ + & EASRC_SFS_NSGO_MASK) + +/* ASRC Resampling Ratio Low (RRL) */ +#define EASRC_RRL_RS_RL_SHIFT 0 +#define EASRC_RRL_RS_RL_WIDTH 32 +#define EASRC_RRL_RS_RL_MASK ((BIT(EASRC_RRL_RS_RL_WIDTH) - 1) \ + << EASRC_RRL_RS_RL_SHIFT) +#define EASRC_RRL_RS_RL(v) (((v) << EASRC_RRL_RS_RL_SHIFT) \ + & EASRC_RRL_RS_RL_MASK) + +/* ASRC Resampling Ratio High (RRH) */ +#define EASRC_RRH_RS_VLD_SHIFT 31 +#define EASRC_RRH_RS_VLD_MASK BIT(EASRC_RRH_RS_VLD_SHIFT) +#define EASRC_RRH_RS_VLD BIT(EASRC_RRH_RS_VLD_SHIFT) +#define EASRC_RRH_RS_RH_SHIFT 0 +#define EASRC_RRH_RS_RH_WIDTH 12 +#define EASRC_RRH_RS_RH_MASK ((BIT(EASRC_RRH_RS_RH_WIDTH) - 1) \ + << EASRC_RRH_RS_RH_SHIFT) +#define EASRC_RRH_RS_RH(v) (((v) << EASRC_RRH_RS_RH_SHIFT) \ + & EASRC_RRH_RS_RH_MASK) + +/* ASRC Resampling Ratio Update Control (RSUC) */ +#define EASRC_RSUC_RS_RM_SHIFT 0 +#define EASRC_RSUC_RS_RM_WIDTH 32 +#define EASRC_RSUC_RS_RM_MASK ((BIT(EASRC_RSUC_RS_RM_WIDTH) - 1) \ + << EASRC_RSUC_RS_RM_SHIFT) +#define EASRC_RSUC_RS_RM(v) (((v) << EASRC_RSUC_RS_RM_SHIFT) \ + & EASRC_RSUC_RS_RM_MASK) + +/* ASRC Resampling Ratio Update Rate (RRUR) */ +#define EASRC_RRUR_RRR_SHIFT 0 +#define EASRC_RRUR_RRR_WIDTH 31 +#define EASRC_RRUR_RRR_MASK ((BIT(EASRC_RRUR_RRR_WIDTH) - 1) \ + << EASRC_RRUR_RRR_SHIFT) +#define EASRC_RRUR_RRR(v) (((v) << EASRC_RRUR_RRR_SHIFT) \ + & EASRC_RRUR_RRR_MASK) + +/* ASRC Resampling Center Tap Coefficient Low (RCTCL) */ +#define EASRC_RCTCL_RS_CL_SHIFT 0 +#define EASRC_RCTCL_RS_CL_WIDTH 32 +#define EASRC_RCTCL_RS_CL_MASK ((BIT(EASRC_RCTCL_RS_CL_WIDTH) - 1) \ + << EASRC_RCTCL_RS_CL_SHIFT) +#define EASRC_RCTCL_RS_CL(v) (((v) << EASRC_RCTCL_RS_CL_SHIFT) \ + & EASRC_RCTCL_RS_CL_MASK) + +/* ASRC Resampling Center Tap Coefficient High (RCTCH) */ +#define EASRC_RCTCH_RS_CH_SHIFT 0 +#define EASRC_RCTCH_RS_CH_WIDTH 32 +#define EASRC_RCTCH_RS_CH_MASK ((BIT(EASRC_RCTCH_RS_CH_WIDTH) - 1) \ + << EASRC_RCTCH_RS_CH_SHIFT) +#define EASRC_RCTCH_RS_CH(v) (((v) << EASRC_RCTCH_RS_CH_SHIFT) \ + & EASRC_RCTCH_RS_CH_MASK) + +/* ASRC Prefilter Coefficient FIFO (PCF) */ +#define EASRC_PCF_CD_SHIFT 0 +#define EASRC_PCF_CD_WIDTH 32 +#define EASRC_PCF_CD_MASK ((BIT(EASRC_PCF_CD_WIDTH) - 1) \ + << EASRC_PCF_CD_SHIFT) +#define EASRC_PCF_CD(v) (((v) << EASRC_PCF_CD_SHIFT) \ + & EASRC_PCF_CD_MASK) + +/* ASRC Context Resampling Coefficient Memory (CRCM) */ +#define EASRC_CRCM_RS_CWD_SHIFT 0 +#define EASRC_CRCM_RS_CWD_WIDTH 32 +#define EASRC_CRCM_RS_CWD_MASK ((BIT(EASRC_CRCM_RS_CWD_WIDTH) - 1) \ + << EASRC_CRCM_RS_CWD_SHIFT) +#define EASRC_CRCM_RS_CWD(v) (((v) << EASRC_CRCM_RS_CWD_SHIFT) \ + & EASRC_CRCM_RS_CWD_MASK) + +/* ASRC Context Resampling Coefficient Control (CRCC) */ +#define EASRC_CRCC_RS_CA_SHIFT 16 +#define EASRC_CRCC_RS_CA_WIDTH 11 +#define EASRC_CRCC_RS_CA_MASK ((BIT(EASRC_CRCC_RS_CA_WIDTH) - 1) \ + << EASRC_CRCC_RS_CA_SHIFT) +#define EASRC_CRCC_RS_CA(v) (((v) << EASRC_CRCC_RS_CA_SHIFT) \ + & EASRC_CRCC_RS_CA_MASK) +#define EASRC_CRCC_RS_TAPS_SHIFT 1 +#define EASRC_CRCC_RS_TAPS_WIDTH 2 +#define EASRC_CRCC_RS_TAPS_MASK ((BIT(EASRC_CRCC_RS_TAPS_WIDTH) - 1) \ + << EASRC_CRCC_RS_TAPS_SHIFT) +#define EASRC_CRCC_RS_TAPS(v) (((v) << EASRC_CRCC_RS_TAPS_SHIFT) \ + & EASRC_CRCC_RS_TAPS_MASK) +#define EASRC_CRCC_RS_CPR_SHIFT 0 +#define EASRC_CRCC_RS_CPR_MASK BIT(EASRC_CRCC_RS_CPR_SHIFT) +#define EASRC_CRCC_RS_CPR BIT(EASRC_CRCC_RS_CPR_SHIFT) + +/* ASRC Interrupt_Control (IC) */ +#define EASRC_IRQC_RSDM_SHIFT 8 +#define EASRC_IRQC_RSDM_WIDTH 4 +#define EASRC_IRQC_RSDM_MASK ((BIT(EASRC_IRQC_RSDM_WIDTH) - 1) \ + << EASRC_IRQC_RSDM_SHIFT) +#define EASRC_IRQC_RSDM(v) (((v) << EASRC_IRQC_RSDM_SHIFT) \ + & EASRC_IRQC_RSDM_MASK) +#define EASRC_IRQC_OERM_SHIFT 4 +#define EASRC_IRQC_OERM_WIDTH 4 +#define EASRC_IRQC_OERM_MASK ((BIT(EASRC_IRQC_OERM_WIDTH) - 1) \ + << EASRC_IRQC_OERM_SHIFT) +#define EASRC_IRQC_OERM(v) (((v) << EASRC_IRQC_OERM_SHIFT) \ + & EASRC_IEQC_OERM_MASK) +#define EASRC_IRQC_IOM_SHIFT 0 +#define EASRC_IRQC_IOM_WIDTH 4 +#define EASRC_IRQC_IOM_MASK ((BIT(EASRC_IRQC_IOM_WIDTH) - 1) \ + << EASRC_IRQC_IOM_SHIFT) +#define EASRC_IRQC_IOM(v) (((v) << EASRC_IRQC_IOM_SHIFT) \ + & EASRC_IRQC_IOM_MASK) + +/* ASRC Interrupt Status Flags (ISF) */ +#define EASRC_IRQF_RSD_SHIFT 8 +#define EASRC_IRQF_RSD_WIDTH 4 +#define EASRC_IRQF_RSD_MASK ((BIT(EASRC_IRQF_RSD_WIDTH) - 1) \ + << EASRC_IRQF_RSD_SHIFT) +#define EASRC_IRQF_RSD(v) (((v) << EASRC_IRQF_RSD_SHIFT) \ + & EASRC_IRQF_RSD_MASK) +#define EASRC_IRQF_OER_SHIFT 4 +#define EASRC_IRQF_OER_WIDTH 4 +#define EASRC_IRQF_OER_MASK ((BIT(EASRC_IRQF_OER_WIDTH) - 1) \ + << EASRC_IRQF_OER_SHIFT) +#define EASRC_IRQF_OER(v) (((v) << EASRC_IRQF_OER_SHIFT) \ + & EASRC_IRQF_OER_MASK) +#define EASRC_IRQF_IFO_SHIFT 0 +#define EASRC_IRQF_IFO_WIDTH 4 +#define EASRC_IRQF_IFO_MASK ((BIT(EASRC_IRQF_IFO_WIDTH) - 1) \ + << EASRC_IRQF_IFO_SHIFT) +#define EASRC_IRQF_IFO(v) (((v) << EASRC_IRQF_IFO_SHIFT) \ + & EASRC_IRQF_IFO_MASK) + +/* ASRC Context Channel STAT */ +#define EASRC_CSx_CSx_SHIFT 0 +#define EASRC_CSx_CSx_WIDTH 32 +#define EASRC_CSx_CSx_MASK ((BIT(EASRC_CSx_CSx_WIDTH) - 1) \ + << EASRC_CSx_CSx_SHIFT) +#define EASRC_CSx_CSx(v) (((v) << EASRC_CSx_CSx_SHIFT) \ + & EASRC_CSx_CSx_MASK) + +/* ASRC Debug Control Register */ +#define EASRC_DBGC_DMS_SHIFT 0 +#define EASRC_DBGC_DMS_WIDTH 6 +#define EASRC_DBGC_DMS_MASK ((BIT(EASRC_DBGC_DMS_WIDTH) - 1) \ + << EASRC_DBGC_DMS_SHIFT) +#define EASRC_DBGC_DMS(v) (((v) << EASRC_DBGC_DMS_SHIFT) \ + & EASRC_DBGC_DMS_MASK) + +/* ASRC Debug Status Register */ +#define EASRC_DBGS_DS_SHIFT 0 +#define EASRC_DBGS_DS_WIDTH 32 +#define EASRC_DBGS_DS_MASK ((BIT(EASRC_DBGS_DS_WIDTH) - 1) \ + << EASRC_DBGS_DS_SHIFT) +#define EASRC_DBGS_DS(v) (((v) << EASRC_DBGS_DS_SHIFT) \ + & EASRC_DBGS_DS_MASK) + +/* General Constants */ +#define EASRC_CTX_MAX_NUM 4 +#define EASRC_32b_MASK (BIT(32) - 1) +#define EASRC_64b_MASK (BIT(64) - 1) +#define EASRC_RS_COEFF_MEM 0 +#define EASRC_PF_COEFF_MEM 1 + +/* Prefilter constants */ +#define EASRC_PF_ST1_ONLY 0 +#define EASRC_PF_TWO_STAGE_MODE 1 +#define EASRC_PF_ST1_COEFF_WR 0 +#define EASRC_PF_ST2_COEFF_WR 1 +#define EASRC_MAX_PF_TAPS 384 + +/* Resampling constants */ +#define EASRC_RS_32_TAPS 0 +#define EASRC_RS_64_TAPS 1 +#define EASRC_RS_128_TAPS 2 + +/* Initialization mode */ +#define EASRC_INIT_MODE_SW_CONTROL 0 +#define EASRC_INIT_MODE_REPLICATE 1 +#define EASRC_INIT_MODE_ZERO_FILL 2 + +/* directions */ +#define IN 0 +#define OUT 1 + +/* FIFO watermarks */ +#define FSL_EASRC_INPUTFIFO_WML 0x4 +#define FSL_EASRC_OUTPUTFIFO_WML 0x1 + +#define EASRC_INPUTFIFO_THRESHOLD_MIN 0 +#define EASRC_INPUTFIFO_THRESHOLD_MAX 127 +#define EASRC_OUTPUTFIFO_THRESHOLD_MIN 0 +#define EASRC_OUTPUTFIFO_THRESHOLD_MAX 63 + +#define EASRC_DMA_BUFFER_SIZE (1024 * 48 * 9) +#define EASRC_MAX_BUFFER_SIZE (1024 * 48) + +#define FIRMWARE_MAGIC 0xDEAD +#define FIRMWARE_VERSION 1 + +enum easrc_word_width { + EASRC_WIDTH_16_BIT = 0, + EASRC_WIDTH_20_BIT = 1, + EASRC_WIDTH_24_BIT = 2, + EASRC_WIDTH_32_BIT = 3, +}; + +struct __attribute__((__packed__)) asrc_firmware_hdr { + u32 magic; + u32 interp_scen; + u32 prefil_scen; + u32 firmware_version; +}; + +struct __attribute__((__packed__)) interp_params { + u32 magic; + u32 num_taps; + u32 num_phases; + u64 center_tap; + u64 coeff[8192]; +}; + +struct __attribute__((__packed__)) prefil_params { + u32 magic; + u32 insr; + u32 outsr; + u32 st1_taps; + u32 st2_taps; + u32 st1_exp; + u64 coeff[256]; +}; + +struct dma_block { + void *dma_vaddr; + unsigned int length; + unsigned int max_buf_size; +}; + +struct fsl_easrc_data_fmt { + unsigned int width : 2; + unsigned int endianness : 1; + unsigned int unsign : 1; + unsigned int floating_point : 1; + unsigned int iec958: 1; + unsigned int sample_pos: 5; + unsigned int addexp; +}; + +struct fsl_easrc_io_params { + struct fsl_easrc_data_fmt fmt; + unsigned int group_len; + unsigned int iterations; + unsigned int access_len; + unsigned int fifo_wtmk; + unsigned int sample_rate; + unsigned int sample_format; + unsigned int norm_rate; +}; + +struct fsl_easrc_slot { + bool busy; + int ctx_index; + int num_channel; /*maximum is 8*/ + int min_channel; + int max_channel; + int pf_mem_used; +}; + +struct fsl_easrc_context { + enum asrc_pair_index index; + struct fsl_easrc *easrc; + struct dma_chan *dma_chan[2]; + struct dma_async_tx_descriptor *desc[2]; + struct fsl_easrc_io_params in_params; + struct fsl_easrc_io_params out_params; + struct imx_dma_data dma_data; + unsigned int channels; + unsigned int st1_num_taps; + unsigned int st2_num_taps; + unsigned int st1_num_exp; + unsigned int pf_init_mode; + unsigned int rs_init_mode; + unsigned int ctx_streams; + unsigned int pos; + u64 rs_ratio; + u64 *st1_coeff; + u64 *st2_coeff; + int in_filled_sample; + int out_missed_sample; + int st1_addexp; + int st2_addexp; + void *private_data; +}; + +/** + * fsl_easrc: EASRC private data + * + * name : name of EASRC device + * @pdev: platform device pointer + * @regmap: regmap handler + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @ctx: context pointer + * @slot: slot setting + * @mem_clk: clock source to access register + * @firmware_hdr: the header of firmware + * @interp: pointer to interpolation filter coeff + * @prefil: pointer to prefilter coeff + * @fw: firmware of coeff table + * @fw_name: firmware name + * @paddr: physical address to the base address of registers + * @rs_num_taps: resample filter taps, 32, 64, or 128 + * @bps_i2c958: bits per sample of iec958 + * @chn_avail: available channels, maximum 32 + * @lock: spin lock for resource protection + * @easrc_rate: default sample rate for ASoC Back-Ends + * @easrc_format: default sample format for ASoC Back-Ends + */ + +struct fsl_easrc { + char name[32]; + struct platform_device *pdev; + struct regmap *regmap; + struct miscdevice easrc_miscdev; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct fsl_easrc_context *ctx[EASRC_CTX_MAX_NUM]; + struct fsl_easrc_slot slot[EASRC_CTX_MAX_NUM][2]; + struct clk *mem_clk; + struct asrc_firmware_hdr *firmware_hdr; + struct interp_params *interp; + struct prefil_params *prefil; + const struct firmware *fw; + const char *fw_name; + unsigned long paddr; + unsigned int rs_num_taps; + unsigned int bps_iec958[EASRC_CTX_MAX_NUM]; + unsigned int chn_avail; + u64 *rs_coeff; + u64 const_coeff; + int firmware_loaded; + spinlock_t lock; /* spin lock for resource protection */ + int easrc_rate; + int easrc_format; +}; + +struct dma_chan *fsl_easrc_get_dma_channel( + struct fsl_easrc_context *ctx, bool dir); +int fsl_easrc_request_context( + struct fsl_easrc_context *ctx, unsigned int channels); +int fsl_easrc_release_context(struct fsl_easrc_context *ctx); + +#define DRV_NAME "fsl-easrc-dma" +#endif /* _FSL_EASRC_H */ diff --git a/sound/soc/fsl/fsl_easrc_dma.c b/sound/soc/fsl/fsl_easrc_dma.c new file mode 100644 index 000000000000..d1871b48b3c1 --- /dev/null +++ b/sound/soc/fsl/fsl_easrc_dma.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/platform_data/dma-imx.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_easrc.h" + +#define FSL_ASRC_DMABUF_SIZE (256 * 1024) + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static void fsl_easrc_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + + ctx->pos += snd_pcm_lib_period_bytes(substream); + if (ctx->pos >= snd_pcm_lib_buffer_bytes(substream)) + ctx->pos = 0; + + snd_pcm_period_elapsed(substream); +} + +static int fsl_easrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) +{ + u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + unsigned long flags = DMA_CTRL_ACK; + + /* Prepare and submit Front-End DMA channel */ + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + ctx->pos = 0; + ctx->desc[!dir] = dmaengine_prep_dma_cyclic( + ctx->dma_chan[!dir], runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags); + if (!ctx->desc[!dir]) { + dev_err(dev, "failed to prepare slave DMA for Front-End\n"); + return -ENOMEM; + } + + ctx->desc[!dir]->callback = fsl_easrc_dma_complete; + ctx->desc[!dir]->callback_param = substream; + + dmaengine_submit(ctx->desc[!dir]); + + /* Prepare and submit Back-End DMA channel */ + ctx->desc[dir] = dmaengine_prep_dma_cyclic( + ctx->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0); + if (!ctx->desc[dir]) { + dev_err(dev, "failed to prepare slave DMA for Back-End\n"); + return -ENOMEM; + } + + dmaengine_submit(ctx->desc[dir]); + + return 0; +} + +static int fsl_easrc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_easrc_dma_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(ctx->dma_chan[IN]); + dma_async_issue_pending(ctx->dma_chan[OUT]); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(ctx->dma_chan[OUT]); + dmaengine_terminate_all(ctx->dma_chan[IN]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_easrc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL; + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + struct fsl_easrc *easrc = ctx->easrc; + struct dma_slave_config config_fe, config_be; + enum asrc_pair_index index = ctx->index; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int stream = substream->stream; + struct imx_dma_data *tmp_data; + struct snd_soc_dpcm *dpcm; + struct dma_chan *tmp_chan; + struct device *dev_be; + u8 dir = tx ? OUT : IN; + dma_cap_mask_t mask; + enum sdma_peripheral_type be_peripheral_type; + int ret; + + /* Fetch the Back-End dma_data from DPCM */ + list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *substream_be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + substream_be = snd_soc_dpcm_get_substream(be, stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be); + dev_be = dai->dev; + break; + } + + if (!dma_params_be) { + dev_err(dev, "failed to get the substream of Back-End\n"); + return -EINVAL; + } + + /* Override dma_data of the Front-End and config its dmaengine */ + dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_params_fe->addr = easrc->paddr + REG_EASRC_FIFO(!dir, index); + dma_params_fe->maxburst = dma_params_be->maxburst; + + ctx->dma_chan[!dir] = fsl_easrc_get_dma_channel(ctx, !dir); + if (!ctx->dma_chan[!dir]) { + dev_err(dev, "failed to request DMA channel\n"); + return -EINVAL; + } + + memset(&config_fe, 0, sizeof(config_fe)); + ret = snd_dmaengine_pcm_prepare_slave_config(substream, + params, &config_fe); + if (ret) { + dev_err(dev, "failed to prepare DMA config for Front-End\n"); + return ret; + } + + ret = dmaengine_slave_config(ctx->dma_chan[!dir], &config_fe); + if (ret) { + dev_err(dev, "failed to config DMA channel for Front-End\n"); + return ret; + } + + /* Request and config DMA channel for Back-End */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + /* Get DMA request of Back-End */ + tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + ctx->dma_data.dma_request = tmp_data->dma_request; + be_peripheral_type = tmp_data->peripheral_type; + if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + ctx->dma_data.dst_dualfifo = true; + if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + ctx->dma_data.src_dualfifo = true; + } + dma_release_channel(tmp_chan); + } + + /* Get DMA request of Front-End */ + tmp_chan = fsl_easrc_get_dma_channel(ctx, dir); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + ctx->dma_data.dma_request2 = tmp_data->dma_request; + ctx->dma_data.peripheral_type = + tmp_data->peripheral_type; + ctx->dma_data.priority = tmp_data->priority; + } + dma_release_channel(tmp_chan); + } + + /* For sdma DEV_TO_DEV, there is two dma request + * But for emda DEV_TO_DEV, there is only one dma request, which is + * from the BE. + */ + if (ctx->dma_data.dma_request2 != ctx->dma_data.dma_request) + ctx->dma_chan[dir] = + dma_request_channel(mask, filter, &ctx->dma_data); + else + ctx->dma_chan[dir] = + dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + + if (!ctx->dma_chan[dir]) { + dev_err(dev, "failed to request DMA channel for Back-End\n"); + return -EINVAL; + } + + if (easrc->easrc_format == SNDRV_PCM_FORMAT_S16_LE) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + + memset(&config_be, 0, sizeof(config_be)); + + config_be.direction = DMA_DEV_TO_DEV; + config_be.src_addr_width = buswidth; + config_be.src_maxburst = dma_params_be->maxburst; + config_be.dst_addr_width = buswidth; + config_be.dst_maxburst = dma_params_be->maxburst; + + if (tx) { + config_be.src_addr = easrc->paddr + REG_EASRC_RDFIFO(index); + config_be.dst_addr = dma_params_be->addr; + } else { + config_be.dst_addr = easrc->paddr + REG_EASRC_WRFIFO(index); + config_be.src_addr = dma_params_be->addr; + } + + ret = dmaengine_slave_config(ctx->dma_chan[dir], &config_be); + if (ret) { + dev_err(dev, "failed to config DMA channel for Back-End\n"); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int fsl_easrc_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = NULL; + ctx->dma_chan[OUT] = NULL; + + return 0; +} + +static int fsl_easrc_dma_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct fsl_easrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_context *ctx; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 dir = tx ? OUT : IN; + struct dma_slave_caps dma_caps; + struct dma_chan *tmp_chan; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->easrc = easrc; + + runtime->private_data = ctx; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "failed to set pcm hw params periods\n"); + return ret; + } + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + fsl_easrc_request_context(ctx, 1); + + tmp_chan = fsl_easrc_get_dma_channel(ctx, dir); + if (!tmp_chan) { + dev_err(dev, "can't get dma channel\n"); + return -EINVAL; + } + + ret = dma_get_slave_caps(tmp_chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + snd_imx_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + if (tmp_chan) + dma_release_channel(tmp_chan); + + fsl_easrc_release_context(ctx); + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + return 0; +} + +static int fsl_easrc_dma_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + struct fsl_easrc *easrc; + + if (!ctx) + return 0; + + easrc = ctx->easrc; + + if (easrc->ctx[ctx->index] == ctx) + easrc->ctx[ctx->index] = NULL; + + kfree(ctx); + + return 0; +} + +static snd_pcm_uframes_t fsl_easrc_dma_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + + return bytes_to_frames(substream->runtime, ctx->pos); +} + +static const struct snd_pcm_ops fsl_easrc_dma_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_easrc_dma_hw_params, + .hw_free = fsl_easrc_dma_hw_free, + .trigger = fsl_easrc_dma_trigger, + .open = fsl_easrc_dma_startup, + .close = fsl_easrc_dma_shutdown, + .pointer = fsl_easrc_dma_pcm_pointer, +}; + +static int fsl_easrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + int ret, i; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(card->dev, "failed to set DMA mask\n"); + return ret; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + FSL_ASRC_DMABUF_SIZE, + &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to allocate DMA buffer\n"); + goto err; + } + } + + return 0; + +err: + if (--i == 0 && pcm->streams[i].substream) + snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer); + + return ret; +} + +static void fsl_easrc_dma_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +struct snd_soc_component_driver fsl_easrc_dma_component = { + .name = DRV_NAME, + .ops = &fsl_easrc_dma_pcm_ops, + .pcm_new = fsl_easrc_dma_pcm_new, + .pcm_free = fsl_easrc_dma_pcm_free, +}; +EXPORT_SYMBOL_GPL(fsl_easrc_dma_component); diff --git a/sound/soc/fsl/fsl_easrc_m2m.c b/sound/soc/fsl/fsl_easrc_m2m.c new file mode 100644 index 000000000000..3a9429a41a8b --- /dev/null +++ b/sound/soc/fsl/fsl_easrc_m2m.c @@ -0,0 +1,966 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +struct fsl_easrc_m2m { + struct fsl_easrc *easrc; + struct fsl_easrc_context *ctx; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int ctx_hold; + unsigned int easrc_active; + unsigned int first_convert; + unsigned int sg_nodes[2]; + struct scatterlist sg[2][9]; + struct dma_async_tx_descriptor *desc[2]; + spinlock_t lock; /* protect mem resource */ +}; + +void fsl_easrc_get_status(struct fsl_easrc_context *ctx, + struct asrc_status_flags *flags) +{ + flags->overload_error = 0; +} + +#define mxc_easrc_dma_umap_in(dev, m2m) \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV) \ + +#define mxc_easrc_dma_umap_out(dev, m2m) \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM) \ + +#define EASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_easrc_input_dma_callback \ + : fsl_easrc_output_dma_callback) + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +static void fsl_easrc_input_dma_callback(void *data) +{ + struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data; + + complete(&m2m->complete[IN]); +} + +static void fsl_easrc_output_dma_callback(void *data) +{ + struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data; + + complete(&m2m->complete[OUT]); +} + +static int fsl_allocate_dma_buf(struct fsl_easrc_m2m *m2m) +{ + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) + return -ENOMEM; + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) + goto alloc_fail; + + return 0; + +alloc_fail: + kfree(input->dma_vaddr); + + return -ENOMEM; +} + +static int fsl_easrc_dmaconfig(struct fsl_easrc_m2m *m2m, + struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, int bits) +{ + struct dma_async_tx_descriptor *desc = m2m->desc[dir]; + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + unsigned int sg_nent = m2m->sg_nodes[dir]; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (bits) { + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + break; + default: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + } + + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + slave_config.dst_maxburst = + ctx->in_params.fifo_wtmk * ctx->channels; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + slave_config.src_maxburst = + ctx->out_params.fifo_wtmk * ctx->channels; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + dev_err(dev, "failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], + buf_addr + i * m2m->dma_block[dir].max_buf_size, + m2m->dma_block[dir].max_buf_size); + + sg_set_buf(&sg[i], + buf_addr + i * m2m->dma_block[dir].max_buf_size, + buf_len - i * m2m->dma_block[dir].max_buf_size); + break; + default: + dev_err(dev, "invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + dev_err(dev, "failed to map DMA sg for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, + DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dev, "failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + m2m->desc[dir] = desc; + m2m->desc[dir]->callback = EASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = EASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = m2m; + + return 0; +} + +static long fsl_easrc_calc_outbuf_len(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_easrc_context *ctx = m2m->ctx; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = ctx->channels; + unsigned int in_samples, out_samples; + + in_width = snd_pcm_format_physical_width(ctx->in_params.sample_format) / 8; + out_width = snd_pcm_format_physical_width(ctx->out_params.sample_format) / 8; + + in_samples = pbuf->input_buffer_length / (in_width * channels); + out_samples = ctx->out_params.sample_rate * in_samples / + ctx->in_params.sample_rate; + out_length = out_samples * out_width * channels; + + if (out_samples <= ctx->out_missed_sample) { + out_length = 0; + ctx->out_missed_sample -= out_samples; + } else { + out_length -= ctx->out_missed_sample * out_width * channels; + ctx->out_missed_sample = 0; + } + + return out_length; +} + +static long fsl_easrc_prepare_io_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf, + bool dir) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + struct dma_chan *dma_chan = ctx->dma_chan[dir]; + unsigned int *dma_len = &m2m->dma_block[dir].length; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + enum asrc_pair_index index = m2m->ctx->index; + unsigned int buf_len, bits; + u32 fifo_addr; + void __user *buf_vaddr; + + if (dir == IN) { + buf_vaddr = (void __user *)buf->input_buffer_vaddr; + buf_len = buf->input_buffer_length; + bits = snd_pcm_format_physical_width(ctx->in_params.sample_format); + fifo_addr = easrc->paddr + REG_EASRC_WRFIFO(index); + } else { + buf_vaddr = (void __user *)buf->output_buffer_vaddr; + buf_len = buf->output_buffer_length; + bits = snd_pcm_format_physical_width(ctx->out_params.sample_format); + fifo_addr = easrc->paddr + REG_EASRC_RDFIFO(index); + } + + if (buf_len > EASRC_DMA_BUFFER_SIZE || + (dir == IN && (buf_len % (bits / 8)))) { + dev_err(dev, "%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + + if (dir == OUT) + *dma_len = fsl_easrc_calc_outbuf_len(m2m, buf); + + if (*dma_len <= 0) + return 0; + + *sg_nodes = *dma_len / m2m->dma_block[dir].max_buf_size + 1; + + return fsl_easrc_dmaconfig(m2m, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, bits); +} + +static long fsl_easrc_prepare_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + int ret; + + ret = fsl_easrc_prepare_io_buffer(m2m, buf, IN); + if (ret) { + dev_err(dev, "failed to prepare input buffer %d\n", ret); + return ret; + } + + ret = fsl_easrc_prepare_io_buffer(m2m, buf, OUT); + if (ret) { + dev_err(dev, "failed to prepare output buffer %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_easrc_process_buffer_pre(struct fsl_easrc_m2m *m2m, bool dir) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + + if (!wait_for_completion_interruptible_timeout(&m2m->complete[dir], + 10 * HZ)) { + dev_err(dev, "%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + dev_err(dev, "%sput task forcibly aborted\n", DIR_STR(dir)); + return -ERESTARTSYS; + } + + return 0; +} + +static unsigned int fsl_easrc_get_output_FIFO_size(struct fsl_easrc_m2m *m2m) +{ + struct fsl_easrc *easrc = m2m->easrc; + enum asrc_pair_index index = m2m->ctx->index; + u32 val; + + regmap_read(easrc->regmap, REG_EASRC_SFS(index), &val); + + val &= EASRC_SFS_NSGO_MASK; + + return val >> EASRC_SFS_NSGO_SHIFT; +} + +static void fsl_easrc_read_last_FIFO(struct fsl_easrc_m2m *m2m) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct dma_block *output = &m2m->dma_block[OUT]; + struct fsl_easrc_context *ctx = m2m->ctx; + enum asrc_pair_index index = m2m->ctx->index; + u32 i, reg, size, t_size = 0, width; + u32 *reg32 = NULL; + u16 *reg16 = NULL; + u8 *reg24 = NULL; + + width = snd_pcm_format_physical_width(ctx->out_params.sample_format); + + if (width == 32) + reg32 = output->dma_vaddr + output->length; + else if (width == 16) + reg16 = output->dma_vaddr + output->length; + else + reg24 = output->dma_vaddr + output->length; +retry: + size = fsl_easrc_get_output_FIFO_size(m2m); + for (i = 0; i < size * ctx->channels; i++) { + regmap_read(easrc->regmap, REG_EASRC_RDFIFO(index), ®); + + if (reg32) { + *(reg32) = reg; + reg32++; + } else if (reg16) { + *(reg16) = (u16)reg; + reg16++; + } else { + *reg24++ = (u8)reg; + *reg24++ = (u8)(reg >> 8); + *reg24++ = (u8)(reg >> 16); + } + } + t_size += size; + + if (size) + goto retry; + + if (reg32) + output->length += t_size * ctx->channels * 4; + else if (reg16) + output->length += t_size * ctx->channels * 2; + else + output->length += t_size * ctx->channels * 3; +} + +static long fsl_easrc_process_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_easrc_process_buffer_pre(m2m, IN); + if (ret) { + mxc_easrc_dma_umap_in(dev, m2m); + if (m2m->dma_block[OUT].length > 0) + mxc_easrc_dma_umap_out(dev, m2m); + return ret; + } + + /* ...then output task*/ + if (m2m->dma_block[OUT].length > 0) { + ret = fsl_easrc_process_buffer_pre(m2m, OUT); + if (ret) { + mxc_easrc_dma_umap_in(dev, m2m); + mxc_easrc_dma_umap_out(dev, m2m); + return ret; + } + } + + mxc_easrc_dma_umap_in(dev, m2m); + if (m2m->dma_block[OUT].length > 0) + mxc_easrc_dma_umap_out(dev, m2m); + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->ctx_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + /* Fetch the remaining data */ + fsl_easrc_read_last_FIFO(m2m); + + /* Update final lengths after getting last FIFO */ + buf->input_buffer_length = m2m->dma_block[IN].length; + buf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)buf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +void fsl_easrc_submit_dma(struct fsl_easrc_m2m *m2m) +{ + /* Submit DMA request */ + dmaengine_submit(m2m->desc[IN]); + dma_async_issue_pending(m2m->desc[IN]->chan); + + if (m2m->dma_block[OUT].length > 0) { + dmaengine_submit(m2m->desc[OUT]); + dma_async_issue_pending(m2m->desc[OUT]->chan); + } +} + +static long fsl_easrc_ioctl_req_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space:%ld\n", ret); + return ret; + } + + ret = fsl_easrc_request_context(m2m->ctx, req.chn_num); + if (ret < 0) { + dev_err(dev, "failed to request context:%ld\n", ret); + return ret; + } + + /* request context returns the context id in case of success */ + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->ctx_hold = 1; + req.index = m2m->ctx->index; + req.supported_in_format = FSL_EASRC_FORMATS; + req.supported_out_format = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_config_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + enum asrc_pair_index index = m2m->ctx->index; + struct device *dev = &easrc->pdev->dev; + struct asrc_config config; + int ret; + int in_word_size, out_word_size; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %d\n", ret); + return ret; + } + + /* set context configuration parameters received from userspace */ + ctx->in_params.sample_rate = config.input_sample_rate; + ctx->out_params.sample_rate = config.output_sample_rate; + + ctx->in_params.fifo_wtmk = FSL_EASRC_INPUTFIFO_WML; + ctx->out_params.fifo_wtmk = FSL_EASRC_OUTPUTFIFO_WML; + + ctx->in_params.sample_format = config.input_format; + ctx->out_params.sample_format = config.output_format; + + ctx->channels = config.channel_num; + ctx->rs_init_mode = 0x2; + ctx->pf_init_mode = 0x2; + + ret = fsl_easrc_set_ctx_format(ctx, + &ctx->in_params.sample_format, + &ctx->out_params.sample_format); + if (ret) + return ret; + + ret = fsl_easrc_config_context(easrc, index); + if (ret) { + dev_err(dev, "failed to config context %d\n", ret); + return ret; + } + + ctx->in_params.iterations = 1; + ctx->in_params.group_len = ctx->channels; + ctx->in_params.access_len = ctx->channels; + ctx->out_params.iterations = 1; + ctx->out_params.group_len = ctx->channels; + ctx->out_params.access_len = ctx->channels; + + /* You can also call fsl_easrc_set_ctx_organziation for + * sample interleaving support + */ + ret = fsl_easrc_set_ctx_organziation(ctx); + if (ret) { + dev_err(dev, "failed to set fifo organization\n"); + return ret; + } + + in_word_size = snd_pcm_format_physical_width(config.input_format) / 8; + out_word_size = snd_pcm_format_physical_width(config.output_format) / 8; + + /* allocate dma buffers */ + m2m->dma_block[IN].length = EASRC_DMA_BUFFER_SIZE; + m2m->dma_block[IN].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE, + in_word_size * ctx->channels); + m2m->dma_block[OUT].length = EASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE, + out_word_size * ctx->channels); + + ret = fsl_allocate_dma_buf(m2m); + if (ret) { + dev_err(dev, "failed to allocate DMA buffers: %d\n", ret); + return ret; + } + + ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN); + if (!ctx->dma_chan[IN]) { + dev_err(dev, "[ctx%d] failed to get input DMA channel\n", + m2m->ctx->index); + return -EBUSY; + } + ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT); + if (!ctx->dma_chan[OUT]) { + dev_err(dev, "[ctx%d] failed to get output DMA channel\n", + m2m->ctx->index); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + dev_err(dev, "failed to send config to user: %d\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_release_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + enum asrc_pair_index index; + unsigned long lock_flags; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, + "[ctx%d] failed to get index from user space %d\n", + m2m->ctx->index, ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, + "[ctx%d] releasing wrong context - %d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + if (m2m->easrc_active) { + m2m->easrc_active = 0; + fsl_easrc_stop_context(ctx); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->ctx_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = NULL; + ctx->dma_chan[OUT] = NULL; + + /* free buffers allocated in config context*/ + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_easrc_release_context(ctx); + + return 0; +} + +static long fsl_easrc_ioctl_convert(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + struct asrc_convert_buffer buf; + int ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to get buf from user space: %d\n", ret); + return ret; + } + + /* fsl_easrc_calc_last_period_size(ctx, &buf); */ + ret = fsl_easrc_prepare_buffer(m2m, &buf); + if (ret) { + dev_err(dev, "failed to prepare buffer\n"); + return ret; + } + + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + + fsl_easrc_submit_dma(m2m); + + if (m2m->first_convert) { + fsl_easrc_start_context(ctx); + m2m->first_convert = 0; + } + + ret = fsl_easrc_process_buffer(m2m, &buf); + if (ret) { + dev_err(dev, "failed to process buffer %d\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to send buffer to user: %d\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_start_conv(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + enum asrc_pair_index index; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, "failed to get index from user space: %d\n", + ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, "[ctx%d] attempting to start wrong context%d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + m2m->easrc_active = 1; + m2m->first_convert = 1; + + return 0; +} + +static long fsl_easrc_ioctl_stop_conv(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + enum asrc_pair_index index; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, "failed to get index from user space: %d\n", + ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, "[ctx%d] attempting to start wrong context%d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + dmaengine_terminate_all(ctx->dma_chan[IN]); + dmaengine_terminate_all(ctx->dma_chan[OUT]); + + fsl_easrc_stop_context(ctx); + m2m->easrc_active = 0; + + return 0; +} + +static long fsl_easrc_ioctl_status(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + struct asrc_status_flags flags; + int ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + dev_err(dev, + "[ctx%d] failed to get flags from user space: %d\n", + m2m->ctx->index, ret); + return ret; + } + + if (m2m->ctx->index != flags.index) { + dev_err(dev, "[ctx%d] getting status for other context: %d\n", + m2m->ctx->index, flags.index); + return -EINVAL; + } + + fsl_easrc_get_status(ctx, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) + dev_err(dev, "[ctx%d] failed to send flags to user space\n", + m2m->ctx->index); + + return ret; +} + +static long fsl_easrc_ioctl_flush(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + + /* Release DMA and request again */ + dma_release_channel(ctx->dma_chan[IN]); + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN); + if (!ctx->dma_chan[IN]) { + dev_err(dev, "failed to request input task DMA channel\n"); + return -EBUSY; + } + + ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT); + if (!ctx->dma_chan[OUT]) { + dev_err(dev, "failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static int fsl_easrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *easrc_miscdev = file->private_data; + struct fsl_easrc *easrc = dev_get_drvdata(easrc_miscdev->parent); + struct fsl_easrc_m2m *m2m; + struct fsl_easrc_context *ctx; + struct device *dev = &easrc->pdev->dev; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + /* set the pointer to easrc private data */ + m2m = kzalloc(sizeof(*m2m), GFP_KERNEL); + if (!m2m) { + ret = -ENOMEM; + goto out; + } + /* just save the pointer to easrc private data */ + m2m->easrc = easrc; + m2m->ctx = ctx; + ctx->easrc = easrc; + ctx->private_data = m2m; + + spin_lock_init(&m2m->lock); + + /* context structs are already allocated in fsl_easrc->ctx[i] */ + file->private_data = m2m; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(ctx); + return ret; +} + +static int fsl_easrc_close(struct inode *inode, struct file *file) +{ + struct fsl_easrc_m2m *m2m = file->private_data; + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + unsigned long lock_flags; + + if (m2m->easrc_active) { + m2m->easrc_active = 0; + dmaengine_terminate_all(ctx->dma_chan[IN]); + dmaengine_terminate_all(ctx->dma_chan[OUT]); + + fsl_easrc_stop_context(ctx); + fsl_easrc_input_dma_callback((void *)m2m); + fsl_easrc_output_dma_callback((void *)m2m); + } + + if (!ctx) + goto null_ctx; + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->ctx_hold) { + m2m->ctx_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_easrc_release_context(ctx); + } else { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + } + +null_ctx: + spin_lock_irqsave(&easrc->lock, lock_flags); + kfree(m2m); + kfree(ctx); + file->private_data = NULL; + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + pm_runtime_put_sync(dev); + + return 0; +} + +static long fsl_easrc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct fsl_easrc_m2m *m2m = file->private_data; + struct fsl_easrc *easrc = m2m->easrc; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_easrc_ioctl_req_context(m2m, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_easrc_ioctl_config_context(m2m, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_easrc_ioctl_release_context(m2m, user); + break; + case ASRC_CONVERT: + ret = fsl_easrc_ioctl_convert(m2m, user); + break; + case ASRC_START_CONV: + ret = fsl_easrc_ioctl_start_conv(m2m, user); + break; + case ASRC_STOP_CONV: + ret = fsl_easrc_ioctl_stop_conv(m2m, user); + break; + case ASRC_STATUS: + ret = fsl_easrc_ioctl_status(m2m, user); + break; + case ASRC_FLUSH: + ret = fsl_easrc_ioctl_flush(m2m, user); + break; + default: + dev_err(&easrc->pdev->dev, "invalid ioctl command\n"); + } + + return ret; +} + +static const struct file_operations easrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_easrc_ioctl, + .open = fsl_easrc_open, + .release = fsl_easrc_close, +}; + +static int fsl_easrc_m2m_init(struct fsl_easrc *easrc) +{ + struct device *dev = &easrc->pdev->dev; + int ret; + + easrc->easrc_miscdev.fops = &easrc_fops; + easrc->easrc_miscdev.parent = dev; + easrc->easrc_miscdev.name = easrc->name; + easrc->easrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&easrc->easrc_miscdev); + if (ret) + dev_err(dev, "failed to register char device %d\n", ret); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static void fsl_easrc_m2m_suspend(struct fsl_easrc *easrc) +{ + struct fsl_easrc_context *ctx; + struct fsl_easrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + spin_lock_irqsave(&easrc->lock, lock_flags); + ctx = easrc->ctx[i]; + if (!ctx || !ctx->private_data) { + spin_unlock_irqrestore(&easrc->lock, lock_flags); + continue; + } + m2m = ctx->private_data; + + if (!completion_done(&m2m->complete[IN])) { + if (ctx->dma_chan[IN]) + dmaengine_terminate_all(ctx->dma_chan[IN]); + fsl_easrc_input_dma_callback((void *)m2m); + } + if (!completion_done(&m2m->complete[OUT])) { + if (ctx->dma_chan[OUT]) + dmaengine_terminate_all(ctx->dma_chan[OUT]); + fsl_easrc_output_dma_callback((void *)m2m); + } + + m2m->first_convert = 1; + fsl_easrc_stop_context(ctx); + spin_unlock_irqrestore(&easrc->lock, lock_flags); + } +} + +static void fsl_easrc_m2m_resume(struct fsl_easrc *easrc) +{ + /* null */ +} +#endif diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index a78e4ab478df..8ade4c4d0918 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -10,6 +10,7 @@ #include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/pm_runtime.h> +#include <linux/pm_domain.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -22,6 +23,19 @@ SNDRV_PCM_FMTBIT_S24_LE) /** + * fsl_esai_soc_data: soc specific data + * + * @imx: for imx platform + * @reset_at_xrun: flags for enable reset operaton + * @use_edma: edma is used. + */ +struct fsl_esai_soc_data { + bool imx; + bool reset_at_xrun; + bool use_edma; +}; + +/** * fsl_esai: ESAI private data * * @dma_params_rx: DMA parameters for receive channel @@ -32,7 +46,8 @@ * @extalclk: esai clock source to derive HCK, SCK and FS * @fsysclk: system clock source to derive HCK, SCK and FS * @spbaclk: SPBA clock (optional, depending on SoC design) - * @task: tasklet to handle the reset operation + * @soc: soc specific data + * @lock: spin lock between hw_reset() and trigger() * @fifo_depth: depth of tx/rx FIFO * @slot_width: width of each DAI slot * @slots: number of slots @@ -43,7 +58,6 @@ * @sck_div: if using PSR/PM dividers for SCKx clock * @slave_mode: if fully using DAI slave mode * @synchronous: if using tx/rx synchronous mode - * @reset_at_xrun: flags for enable reset operaton * @name: driver name */ struct fsl_esai { @@ -55,7 +69,8 @@ struct fsl_esai { struct clk *extalclk; struct clk *fsysclk; struct clk *spbaclk; - struct tasklet_struct task; + const struct fsl_esai_soc_data *soc; + spinlock_t lock; /* Protect hw_reset and trigger */ u32 fifo_depth; u32 slot_width; u32 slots; @@ -66,12 +81,37 @@ struct fsl_esai { u32 sck_rate[2]; bool hck_dir[2]; bool sck_div[2]; - bool slave_mode; + bool slave_mode[2]; bool synchronous; - bool reset_at_xrun; char name[32]; }; +static struct fsl_esai_soc_data fsl_esai_vf610 = { + .imx = false, + .reset_at_xrun = true, + .use_edma = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx35 = { + .imx = true, + .reset_at_xrun = true, + .use_edma = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx6ull = { + .imx = true, + .reset_at_xrun = false, + .use_edma = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx8qm = { + .imx = true, + .reset_at_xrun = false, + .use_edma = true, +}; + +static void fsl_esai_hw_reset(struct fsl_esai *esai_priv); + static irqreturn_t esai_isr(int irq, void *devid) { struct fsl_esai *esai_priv = (struct fsl_esai *)devid; @@ -83,19 +123,19 @@ static irqreturn_t esai_isr(int irq, void *devid) regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr); if ((saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE)) && - esai_priv->reset_at_xrun) { + esai_priv->soc->reset_at_xrun) { dev_dbg(&pdev->dev, "reset module for xrun\n"); - tasklet_schedule(&esai_priv->task); + fsl_esai_hw_reset(esai_priv); } if (esr & ESAI_ESR_TINIT_MASK) dev_dbg(&pdev->dev, "isr: Transmission Initialized\n"); if (esr & ESAI_ESR_RFF_MASK) - dev_warn(&pdev->dev, "isr: Receiving overrun\n"); + dev_dbg(&pdev->dev, "isr: Receiving overrun\n"); if (esr & ESAI_ESR_TFE_MASK) - dev_warn(&pdev->dev, "isr: Transmission underrun\n"); + dev_dbg(&pdev->dev, "isr: Transmission underrun\n"); if (esr & ESAI_ESR_TLS_MASK) dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n"); @@ -336,7 +376,7 @@ static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) int ret; /* Don't apply for fully slave mode or unchanged bclk */ - if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq) + if (esai_priv->slave_mode[tx] || esai_priv->sck_rate[tx] == freq) return 0; if (ratio * freq > hck_rate) @@ -445,35 +485,62 @@ static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - esai_priv->slave_mode = false; + if (esai_priv->slave_mode[0] == esai_priv->slave_mode[1]) { + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + esai_priv->slave_mode[0] = true; + esai_priv->slave_mode[1] = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + xccr |= ESAI_xCCR_xCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + xccr |= ESAI_xCCR_xFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + esai_priv->slave_mode[0] = false; + esai_priv->slave_mode[1] = false; + break; + default: + return -EINVAL; + } - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - esai_priv->slave_mode = true; - break; - case SND_SOC_DAIFMT_CBS_CFM: - xccr |= ESAI_xCCR_xCKD; - break; - case SND_SOC_DAIFMT_CBM_CFS: - xccr |= ESAI_xCCR_xFSD; - break; - case SND_SOC_DAIFMT_CBS_CFS: - xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; - break; - default: - return -EINVAL; + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + regmap_update_bits(esai_priv->regmap, + REG_ESAI_TCCR, mask, xccr); + regmap_update_bits(esai_priv->regmap, + REG_ESAI_RCCR, mask, xccr); + + } else { + + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + if (esai_priv->slave_mode[0]) + regmap_update_bits(esai_priv->regmap, + REG_ESAI_RCCR, mask, xccr); + else + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + mask, + xccr | ESAI_xCCR_xFSD | + ESAI_xCCR_xCKD); + + if (esai_priv->slave_mode[1]) + regmap_update_bits(esai_priv->regmap, + REG_ESAI_TCCR, mask, xccr); + else + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + mask, + xccr | ESAI_xCCR_xFSD | + ESAI_xCCR_xCKD); } mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR | ESAI_xCR_xWA; regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr); regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr); - mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | - ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; - regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr); - regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr); - return 0; } @@ -481,6 +548,7 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; if (!dai->active) { /* Set synchronous mode */ @@ -495,6 +563,12 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); } + if (esai_priv->soc->use_edma) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + tx ? esai_priv->dma_params_tx.maxburst : + esai_priv->dma_params_rx.maxburst); + return 0; } @@ -672,12 +746,13 @@ static void fsl_esai_trigger_stop(struct fsl_esai *esai_priv, bool tx) ESAI_xFCR_xFR, 0); } -static void fsl_esai_hw_reset(unsigned long arg) +static void fsl_esai_hw_reset(struct fsl_esai *esai_priv) { - struct fsl_esai *esai_priv = (struct fsl_esai *)arg; bool tx = true, rx = false, enabled[2]; + unsigned long lock_flags; u32 tfcr, rfcr; + spin_lock_irqsave(&esai_priv->lock, lock_flags); /* Save the registers */ regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); @@ -715,6 +790,8 @@ static void fsl_esai_hw_reset(unsigned long arg) fsl_esai_trigger_start(esai_priv, tx); if (enabled[rx]) fsl_esai_trigger_start(esai_priv, rx); + + spin_unlock_irqrestore(&esai_priv->lock, lock_flags); } static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, @@ -722,6 +799,7 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned long lock_flags; esai_priv->channels[tx] = substream->runtime->channels; @@ -729,12 +807,16 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&esai_priv->lock, lock_flags); fsl_esai_trigger_start(esai_priv, tx); + spin_unlock_irqrestore(&esai_priv->lock, lock_flags); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&esai_priv->lock, lock_flags); fsl_esai_trigger_stop(esai_priv, tx); + spin_unlock_irqrestore(&esai_priv->lock, lock_flags); break; default: return -EINVAL; @@ -913,6 +995,8 @@ static int fsl_esai_probe(struct platform_device *pdev) const __be32 *iprop; void __iomem *regs; int irq, ret; + unsigned long irqflag = 0; + int i, num_domains = 0; esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL); if (!esai_priv) @@ -921,9 +1005,11 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->pdev = pdev; snprintf(esai_priv->name, sizeof(esai_priv->name), "%pOFn", np); - if (of_device_is_compatible(np, "fsl,vf610-esai") || - of_device_is_compatible(np, "fsl,imx35-esai")) - esai_priv->reset_at_xrun = true; + esai_priv->soc = of_device_get_match_data(&pdev->dev); + if (!esai_priv->soc) { + dev_err(&pdev->dev, "failed to get soc data\n"); + return -ENODEV; + } /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -965,7 +1051,29 @@ static int fsl_esai_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, + num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + for (i = 0; i < num_domains; i++) { + struct device *pd_dev; + struct device_link *link; + + pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i); + if (IS_ERR(pd_dev)) + return PTR_ERR(pd_dev); + + link = device_link_add(&pdev->dev, pd_dev, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(link)) + return PTR_ERR(link); + } + + /* ESAI shared interrupt */ + if (of_property_read_bool(np, "shared-interrupt")) + irqflag = IRQF_SHARED; + + ret = devm_request_irq(&pdev->dev, irq, esai_isr, irqflag, esai_priv->name, esai_priv); if (ret) { dev_err(&pdev->dev, "failed to claim irq %u\n", irq); @@ -975,9 +1083,6 @@ static int fsl_esai_probe(struct platform_device *pdev) /* Set a default slot number */ esai_priv->slots = 2; - /* Set a default master/slave state */ - esai_priv->slave_mode = true; - /* Determine the FIFO depth */ iprop = of_get_property(np, "fsl,fifo-depth", NULL); if (iprop) @@ -993,6 +1098,20 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->synchronous = of_property_read_bool(np, "fsl,esai-synchronous"); + if (!esai_priv->synchronous) { + if (of_property_read_bool(pdev->dev.of_node, "fsl,txm-rxs")) { + /* 0 -- rx, 1 -- tx */ + esai_priv->slave_mode[0] = true; + esai_priv->slave_mode[1] = false; + } + + if (of_property_read_bool(pdev->dev.of_node, "fsl,txs-rxm")) { + /* 0 -- rx, 1 -- tx */ + esai_priv->slave_mode[0] = false; + esai_priv->slave_mode[1] = true; + } + } + /* Implement full symmetry for synchronous mode */ if (esai_priv->synchronous) { fsl_esai_dai.symmetric_rates = 1; @@ -1002,6 +1121,7 @@ static int fsl_esai_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, esai_priv); + spin_lock_init(&esai_priv->lock); ret = fsl_esai_hw_init(esai_priv); if (ret) return ret; @@ -1022,9 +1142,6 @@ static int fsl_esai_probe(struct platform_device *pdev) return ret; } - tasklet_init(&esai_priv->task, fsl_esai_hw_reset, - (unsigned long)esai_priv); - pm_runtime_enable(&pdev->dev); regcache_cache_only(esai_priv->regmap, true); @@ -1038,18 +1155,16 @@ static int fsl_esai_probe(struct platform_device *pdev) static int fsl_esai_remove(struct platform_device *pdev) { - struct fsl_esai *esai_priv = platform_get_drvdata(pdev); - pm_runtime_disable(&pdev->dev); - tasklet_kill(&esai_priv->task); return 0; } static const struct of_device_id fsl_esai_dt_ids[] = { - { .compatible = "fsl,imx35-esai", }, - { .compatible = "fsl,vf610-esai", }, - { .compatible = "fsl,imx6ull-esai", }, + { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 }, + { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 }, + { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull }, + { .compatible = "fsl,imx8qm-esai", .data = &fsl_esai_imx8qm }, {} }; MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); diff --git a/sound/soc/fsl/imx-ak4458.c b/sound/soc/fsl/imx-ak4458.c new file mode 100644 index 000000000000..08a437d5f89b --- /dev/null +++ b/sound/soc/fsl/imx-ak4458.c @@ -0,0 +1,417 @@ +/* i.MX AK4458 audio support + * + * 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/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" +#include "fsl_dsd.h" + +struct imx_ak4458_data { + struct snd_soc_card card; + int num_codec_conf; + struct snd_soc_codec_conf *codec_conf; + bool tdm_mode; + int pdn_gpio; + unsigned int slots; + unsigned int slot_width; + bool one2one_ratio; +}; + +static struct snd_soc_dapm_widget imx_ak4458_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +/** + * Tables 3 & 4 - mapping LRCK fs and frame width + */ +static const struct imx_ak4458_fs_map { + unsigned int rmin; + unsigned int rmax; + unsigned int wmin; + unsigned int wmax; +} fs_map[] = { + /* Normal, < 32kHz */ + { .rmin = 8000, .rmax = 24000, .wmin = 1024, .wmax = 1024, }, + /* Normal, 32kHz */ + { .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, }, + /* Normal */ + { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, }, + /* Double */ + { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, }, + /* Quad */ + { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, }, + /* Oct */ + { .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, }, + /* Hex */ + { .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, }, +}; + +static const struct imx_ak4458_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul_tdm[] = { + /* + * Table 13 - Audio Interface Format + * For TDM mode, MCLK should is set to + * obtained from 2 * slots * slot_width + */ + { .min = 128, .max = 128, .mul = 256 }, /* TDM128 */ + { .min = 256, .max = 256, .mul = 512 }, /* TDM256 */ + { .min = 512, .max = 512, .mul = 1024 }, /* TDM512 */ +}; + +static const u32 ak4458_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static const u32 ak4458_rates_tdm[] = { + 8000, 16000, 32000, + 48000, 96000, +}; + +static const u32 ak4458_channels[] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, +}; + +static const u32 ak4458_channels_tdm[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, +}; + +static unsigned long ak4458_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = params_rate(params); + unsigned int width = data->slots * data->slot_width; + int i, mode; + + if (data->tdm_mode) { + /* can be 128, 256 or 512 */ + mode = data->slots * data->slot_width; + + for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) { + /* min = max = slots * slots_width */ + if (mode != fs_mul_tdm[i].min) + continue; + return rate * fs_mul_tdm[i].mul; + } + } else { + for (i = 0; i < ARRAY_SIZE(fs_map); i++) { + if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) { + width = max(width, fs_map[i].wmin); + width = min(width, fs_map[i].wmax); + + /* Adjust SAI bclk:mclk ratio */ + width *= data->one2one_ratio ? 1 : 2; + + return rate * width; + } + } + } + + /* Let DAI manage clk frequency by default */ + return 0; +} + +static int imx_aif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + unsigned long mclk_freq; + bool is_dsd = fsl_is_dsd(params); + int ret, i; + + if (is_dsd) { + channels = 1; + data->slots = 1; + data->slot_width = params_width(params); + fmt |= SND_SOC_DAIFMT_PDM; + } else if (data->tdm_mode) { + data->slots = 8; + data->slot_width = 32; + fmt |= SND_SOC_DAIFMT_DSP_B; + } else { + data->slots = 2; + data->slot_width = params_physical_width(params); + fmt |= SND_SOC_DAIFMT_I2S; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai[%d] fmt: %d\n", + i, ret); + return ret; + } + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n", + i, ret); + return ret; + } + } + + /* set MCLK freq */ + mclk_freq = ak4458_get_mclk_rate(substream, params); + if (is_dsd) + mclk_freq = 22579200; + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n", + mclk_freq, ret); + return ret; +} + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + if (data->tdm_mode) { + constraint_channels.list = ak4458_channels_tdm; + constraint_channels.count = ARRAY_SIZE(ak4458_channels_tdm); + constraint_rates.list = ak4458_rates_tdm; + constraint_rates.count = ARRAY_SIZE(ak4458_rates_tdm); + } else { + constraint_channels.list = ak4458_channels; + constraint_channels.count = ARRAY_SIZE(ak4458_channels); + constraint_rates.list = ak4458_rates; + constraint_rates.count = ARRAY_SIZE(ak4458_rates); + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_aif_ops = { + .hw_params = imx_aif_hw_params, + .startup = imx_aif_startup, +}; + +static struct snd_soc_dai_link_component ak4458_codecs[] = { + { + /* Playback */ + .dai_name = "ak4458-aif", + }, + { + /* Capture */ + .dai_name = "ak4458-aif", + }, +}; + +static struct snd_soc_dai_link imx_ak4458_dai = { + .name = "ak4458", + .stream_name = "Audio", + .codecs = ak4458_codecs, + .num_codecs = 2, + .ignore_pmdown_time = 1, + .ops = &imx_aif_ops, + .playback_only = 1, +}; + +static int imx_ak4458_probe(struct platform_device *pdev) +{ + struct imx_ak4458_data *priv; + struct device_node *cpu_np, *codec_np_0 = NULL, *codec_np_1 = NULL; + struct platform_device *cpu_pdev; + struct snd_soc_dai_link_component *dlc; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dlc = devm_kzalloc(&pdev->dev, 2 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np_0 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np_0) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np_1 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!codec_np_1) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL)) + priv->tdm_mode = true; + + priv->num_codec_conf = 2; + priv->codec_conf = devm_kzalloc(&pdev->dev, + priv->num_codec_conf * sizeof(struct snd_soc_codec_conf), + GFP_KERNEL); + if (!priv->codec_conf) { + ret = -ENOMEM; + goto fail; + } + + priv->codec_conf[0].name_prefix = "0"; + priv->codec_conf[0].of_node = codec_np_0; + priv->codec_conf[1].name_prefix = "1"; + priv->codec_conf[1].of_node = codec_np_1; + + ak4458_codecs[0].of_node = codec_np_0; + ak4458_codecs[1].of_node = codec_np_1; + + imx_ak4458_dai.cpus = &dlc[0]; + imx_ak4458_dai.num_cpus = 1; + imx_ak4458_dai.platforms = &dlc[1]; + imx_ak4458_dai.num_platforms = 1; + + imx_ak4458_dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_ak4458_dai.platforms->of_node = cpu_np; + + priv->card.num_links = 1; + priv->card.dai_link = &imx_ak4458_dai; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak4458_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4458_dapm_widgets); + priv->card.codec_conf = priv->codec_conf; + priv->card.num_configs = priv->num_codec_conf; + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak4458-mq"); + + priv->pdn_gpio = of_get_named_gpio(pdev->dev.of_node, "ak4458,pdn-gpio", 0); + if (gpio_is_valid(priv->pdn_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, priv->pdn_gpio, + GPIOF_OUT_INIT_LOW, "ak4458,pdn"); + if (ret) { + dev_err(&pdev->dev, "unable to get pdn gpio\n"); + goto fail; + } + + gpio_set_value_cansleep(priv->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(priv->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np_0) + of_node_put(codec_np_0); + if (codec_np_1) + of_node_put(codec_np_1); + + return ret; +} + +static const struct of_device_id imx_ak4458_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak4458", }, + { .compatible = "fsl,imx-audio-ak4458-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak4458_dt_ids); + +static struct platform_driver imx_ak4458_driver = { + .driver = { + .name = "imx-ak4458", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak4458_dt_ids, + }, + .probe = imx_ak4458_probe, +}; +module_platform_driver(imx_ak4458_driver); + +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK4458 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak4458"); diff --git a/sound/soc/fsl/imx-ak4497.c b/sound/soc/fsl/imx-ak4497.c new file mode 100644 index 000000000000..a4d24333026c --- /dev/null +++ b/sound/soc/fsl/imx-ak4497.c @@ -0,0 +1,269 @@ +/* i.MX AK4458 audio support + * + * 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/clk.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" + +struct imx_ak4497_data { + struct snd_soc_card card; + bool one2one_ratio; +}; + +static struct snd_soc_dapm_widget imx_ak4497_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +static const struct imx_ak4497_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul[] = { + /** + * Table 7 - mapping multiplier and speed mode + * Tables 8 & 9 - mapping speed mode and LRCK fs + */ + { .min = 8000, .max = 32000, .mul = 1024 }, /* Normal, <= 32kHz */ + { .min = 44100, .max = 48000, .mul = 512 }, /* Normal */ + { .min = 88200, .max = 96000, .mul = 256 }, /* Double */ + { .min = 176400, .max = 192000, .mul = 128 }, /* Quad */ + { .min = 352800, .max = 384000, .mul = 2*64 }, /* Oct */ + { .min = 705600, .max = 768000, .mul = 2*32 }, /* Hex */ +}; + +static bool imx_ak4497_is_dsd(struct snd_pcm_hw_params *params) +{ + snd_pcm_format_t format = params_format(params); + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + return true; + default: + return false; + } +} + +static unsigned long imx_ak4497_compute_freq(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + int i; + + /* Find the appropriate MCLK freq */ + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate >= fs_mul[i].min && rate <= fs_mul[i].max) + return rate * fs_mul[i].mul; + } + + /* Let DAI manage MCLK frequency */ + return 0; +} + +static int imx_aif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak4497_data *priv = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + unsigned long freq = imx_ak4497_compute_freq(substream, params); + bool is_dsd = imx_ak4497_is_dsd(params); + int ret; + + fmt |= (is_dsd ? SND_SOC_DAIFMT_PDM : SND_SOC_DAIFMT_I2S); + + if (is_dsd && freq > 22579200 && priv->one2one_ratio) + freq = 22579200; + + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(dev, "failed to set cpu dai mclk1 rate(%lu): %d\n", + freq, ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (is_dsd) + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + 0x1, 0x1, + 1, params_width(params)); + else + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + 2, params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return ret; +} + +static const u32 support_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + static struct snd_pcm_hw_constraint_list constraint_rates; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return ret; +} + +static struct snd_soc_ops imx_aif_ops = { + .startup = imx_aif_startup, + .hw_params = imx_aif_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak4497-aif")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link imx_ak4497_dai = { + .name = "ak4497", + .stream_name = "Audio", + .ops = &imx_aif_ops, + .playback_only = 1, + SND_SOC_DAILINK_REG(hifi), +}; + +static int imx_ak4497_probe(struct platform_device *pdev) +{ + struct imx_ak4497_data *priv; + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + imx_ak4497_dai.codecs->of_node = codec_np; + imx_ak4497_dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_ak4497_dai.platforms->of_node = cpu_np; + imx_ak4497_dai.playback_only = 1; + + priv->card.dai_link = &imx_ak4497_dai; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak4497_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4497_dapm_widgets); + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak4497-mq"); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_ak4497_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak4497", }, + { .compatible = "fsl,imx-audio-ak4497-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak4497_dt_ids); + +static struct platform_driver imx_ak4497_driver = { + .driver = { + .name = "imx-ak4497", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak4497_dt_ids, + }, + .probe = imx_ak4497_probe, +}; +module_platform_driver(imx_ak4497_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK4497 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak4497"); diff --git a/sound/soc/fsl/imx-ak5558.c b/sound/soc/fsl/imx-ak5558.c new file mode 100644 index 000000000000..98a057dc91a4 --- /dev/null +++ b/sound/soc/fsl/imx-ak5558.c @@ -0,0 +1,485 @@ +/* i.MX AK5558 audio support + * + * 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/clk.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" +#include "../codecs/ak5558.h" + + +struct imx_ak5558_data { + struct snd_soc_card card; + bool tdm_mode; + unsigned long slots; + unsigned long slot_width; + bool one2one_ratio; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; + +/* + * imx_ack5558_fs_mul - sampling frequency multiplier + * + * min <= fs <= max, MCLK = mul * LRCK + */ +struct imx_ak5558_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +}; + +/* + * Auto MCLK selection based on LRCK for Normal Mode + * (Table 4 from datasheet) + */ +static const struct imx_ak5558_fs_mul fs_mul[] = { + { .min = 8000, .max = 32000, .mul = 1024 }, + { .min = 44100, .max = 48000, .mul = 512 }, + { .min = 88200, .max = 96000, .mul = 256 }, + { .min = 176400, .max = 192000, .mul = 128 }, + { .min = 352800, .max = 384000, .mul = 64 }, + { .min = 705600, .max = 768000, .mul = 32 }, +}; + +/* + * MCLK and BCLK selection based on TDM mode + * because of SAI we also add the restriction: MCLK >= 2 * BCLK + * (Table 9 from datasheet) + */ +static const struct imx_ak5558_fs_mul fs_mul_tdm[] = { + { .min = 128, .max = 128, .mul = 256 }, + { .min = 256, .max = 256, .mul = 512 }, + { .min = 512, .max = 512, .mul = 1024 }, +}; + +static struct snd_soc_dapm_widget imx_ak5558_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const u32 ak5558_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static const u32 ak5558_tdm_rates[] = { + 8000, 16000, 32000, + 48000, 96000 +}; + +static const u32 ak5558_channels[] = { + 1, 2, 4, 6, 8, +}; + +static unsigned long ak5558_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = params_rate(params); + unsigned int freq = 0; /* Let DAI manage clk frequency by default */ + int mode; + int i; + + if (data->tdm_mode) { + mode = data->slots * data->slot_width; + + for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) { + /* min = max = slots * slots_width */ + if (mode != fs_mul_tdm[i].min) + continue; + freq = rate * fs_mul_tdm[i].mul; + break; + } + } else { + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate < fs_mul[i].min || rate > fs_mul[i].max) + continue; + freq = rate * fs_mul[i].mul; + break; + } + } + + return freq; +} + +static int imx_aif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned long mclk_freq; + unsigned int fmt; + int ret; + + if (data->tdm_mode) + fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (data->tdm_mode) { + /* support TDM256 (8 slots * 32 bits/per slot) */ + data->slots = 8; + data->slot_width = 32; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + } else { + /* normal mode (I2S) */ + data->slots = 2; + data->slot_width = params_physical_width(params); + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + } + + mclk_freq = ak5558_get_mclk_rate(substream, params); + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n", + mclk_freq); + + return ret; +} + +static int imx_ak5558_hw_rule_rate(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_rule *r) +{ + struct imx_ak5558_data *data = r->private; + struct snd_interval t = { .min = 8000, .max = 8000, }; + unsigned int fs; + unsigned long mclk_freq; + int i; + + fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + fs *= data->tdm_mode ? 8 : 2; + + /* Identify maximum supported rate */ + for (i = 0; i < ARRAY_SIZE(ak5558_rates); i++) { + mclk_freq = fs * ak5558_rates[i]; + /* Adjust SAI bclk:mclk ratio */ + mclk_freq *= data->one2one_ratio ? 1 : 2; + + /* Skip rates for which MCLK is beyond supported value */ + if (mclk_freq > 36864000) + continue; + + if (t.max < ak5558_rates[i]) + t.max = ak5558_rates[i]; + } + + return snd_interval_refine(hw_param_interval(p, r->var), &t); +} + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + if (data->tdm_mode) { + constraint_rates.list = ak5558_tdm_rates; + constraint_rates.count = ARRAY_SIZE(ak5558_tdm_rates); + } else { + constraint_rates.list = ak5558_rates; + constraint_rates.count = ARRAY_SIZE(ak5558_rates); + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + constraint_channels.list = ak5558_channels; + constraint_channels.count = ARRAY_SIZE(ak5558_channels); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, imx_ak5558_hw_rule_rate, data, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); +} + +static struct snd_soc_ops imx_aif_ops = { + .hw_params = imx_aif_hw_params, + .startup = imx_aif_startup, +}; + +static struct snd_soc_ops imx_aif_ops_be = { + .hw_params = imx_aif_hw_params, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_ak5558_data *priv = snd_soc_card_get_drvdata(card); + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = priv->asrc_rate; + rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_ak5558_dai[] = { + { + .name = "ak5558", + .stream_name = "Audio", + .ops = &imx_aif_ops, + .capture_only = 1, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 0, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 0, + .dpcm_capture = 1, + .ops = &imx_aif_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, + +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"CPU-Capture", NULL, "Capture"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static int imx_ak5558_probe(struct platform_device *pdev) +{ + struct imx_ak5558_data *priv; + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct device_node *asrc_np = NULL; + struct platform_device *asrc_pdev = NULL; + int ret; + u32 width; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL)) + priv->tdm_mode = true; + + imx_ak5558_dai[0].codecs->of_node = codec_np; + imx_ak5558_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_ak5558_dai[0].platforms->of_node = cpu_np; + imx_ak5558_dai[0].capture_only = 1; + + priv->card.dai_link = &imx_ak5558_dai[0]; + priv->card.num_links = 1; + priv->card.dapm_routes = audio_map; + priv->card.num_dapm_routes = 1; + + /*if there is no asrc controller, we only enable one device*/ + if (asrc_pdev) { + imx_ak5558_dai[1].cpus->of_node = asrc_np; + imx_ak5558_dai[1].platforms->of_node = asrc_np; + + imx_ak5558_dai[2].codecs->of_node = codec_np; + imx_ak5558_dai[2].cpus->of_node = cpu_np; + priv->card.num_links = 3; + priv->card.num_dapm_routes += 1; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak5558_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak5558_dapm_widgets); + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak5558-mq"); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_ak5558_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak5558", }, + { .compatible = "fsl,imx-audio-ak5558-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak5558_dt_ids); + +static struct platform_driver imx_ak5558_driver = { + .driver = { + .name = "imx-ak5558", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak5558_dt_ids, + }, + .probe = imx_ak5558_probe, +}; +module_platform_driver(imx_ak5558_driver); + +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK5558 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak5558"); diff --git a/sound/soc/fsl/imx-cs42888.c b/sound/soc/fsl/imx-cs42888.c new file mode 100644 index 000000000000..2b01fa5f132a --- /dev/null +++ b/sound/soc/fsl/imx-cs42888.c @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2010-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> + +#include "fsl_esai.h" + +#define CODEC_CLK_EXTER_OSC 1 +#define CODEC_CLK_ESAI_HCKT 2 +#define SUPPORT_RATE_NUM 10 + +struct imx_priv { + struct clk *codec_clk; + struct clk *esai_clk; + unsigned int mclk_freq; + unsigned int esai_freq; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; + bool is_codec_master; + bool is_codec_rpmsg; + bool is_stream_in_use[2]; + bool is_stream_tdm[2]; +}; + +static struct imx_priv card_priv; + +static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 channels = params_channels(params); + u32 max_tdm_rate; + u32 dai_format; + int ret = 0; + + priv->is_stream_tdm[tx] = channels > 1 && channels % 2; + dai_format = SND_SOC_DAIFMT_NB_NF | + (priv->is_stream_tdm[tx] ? SND_SOC_DAIFMT_DSP_A : + SND_SOC_DAIFMT_LEFT_J); + + priv->is_stream_in_use[tx] = true; + + if (priv->is_stream_in_use[!tx] && + (priv->is_stream_tdm[tx] != priv->is_stream_tdm[!tx])) { + + dev_err(dev, "Don't support different fmt for tx & rx\n"); + return -EINVAL; + } + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + priv->esai_freq = clk_get_rate(priv->esai_clk); + + if (priv->is_codec_master) { + /* TDM is not supported by codec in master mode */ + if (priv->is_stream_tdm[tx]) { + dev_err(dev, "%d channels are not supported in codec master mode\n", + channels); + return -EINVAL; + } + dai_format |= SND_SOC_DAIFMT_CBM_CFM; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_IN); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + + } else { + dai_format |= SND_SOC_DAIFMT_CBS_CFS; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set i.MX active slot mask */ + if (priv->is_stream_tdm[tx]) { + /* 2 required by ESAI BCLK divisors, 8 slots, 32 width */ + if (priv->is_codec_master) + max_tdm_rate = priv->mclk_freq / (8*32); + else + max_tdm_rate = priv->esai_freq / (2*8*32); + if (params_rate(params) > max_tdm_rate) { + dev_err(dev, + "maximum supported sampling rate for %d channels is %dKHz\n", + channels, max_tdm_rate / 1000); + return -EINVAL; + } + + /* + * Per datasheet, the codec expects 8 slots and 32 bits + * for every slot in TDM mode. + */ + snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + } else + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32); + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + return 0; +} + +static int imx_cs42888_surround_hw_free(struct snd_pcm_substream *substream) +{ + struct imx_priv *priv = &card_priv; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + priv->is_stream_in_use[tx] = false; + + return 0; +} + +static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + static u32 support_rates[SUPPORT_RATE_NUM]; + int ret; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + + if (priv->mclk_freq % 12288000 == 0) { + support_rates[0] = 48000; + support_rates[1] = 96000; + support_rates[2] = 192000; + constraint_rates.list = support_rates; + constraint_rates.count = 3; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + } else + dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq); + + return 0; +} + +static struct snd_soc_ops imx_cs42888_surround_ops = { + .startup = imx_cs42888_surround_startup, + .hw_params = imx_cs42888_surround_hw_params, + .hw_free = imx_cs42888_surround_hw_free, +}; + +/** + * imx_cs42888_surround_startup() is to set constrain for hw parameter, but + * backend use same runtime as frontend, for p2p backend need to use different + * parameter, so backend can't use the startup. + */ +static struct snd_soc_ops imx_cs42888_surround_ops_be = { + .hw_params = imx_cs42888_surround_hw_params, + .hw_free = imx_cs42888_surround_hw_free, +}; + + +static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Line out jack */ + {"Line Out Jack", NULL, "AOUT1L"}, + {"Line Out Jack", NULL, "AOUT1R"}, + {"Line Out Jack", NULL, "AOUT2L"}, + {"Line Out Jack", NULL, "AOUT2R"}, + {"Line Out Jack", NULL, "AOUT3L"}, + {"Line Out Jack", NULL, "AOUT3R"}, + {"Line Out Jack", NULL, "AOUT4L"}, + {"Line Out Jack", NULL, "AOUT4R"}, + {"AIN1L", NULL, "Line In Jack"}, + {"AIN1R", NULL, "Line In Jack"}, + {"AIN2L", NULL, "Line In Jack"}, + {"AIN2R", NULL, "Line In Jack"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ + {"CPU-Capture", NULL, "Capture"}, + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_cs42888_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .ops = &imx_cs42888_surround_ops, + .ignore_pmdown_time = 1, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_cs42888_surround_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static struct snd_soc_card snd_soc_card_imx_cs42888 = { + .name = "cs42888-audio", + .dai_link = imx_cs42888_dai, + .dapm_widgets = imx_cs42888_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .owner = THIS_MODULE, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + */ +static int imx_cs42888_probe(struct platform_device *pdev) +{ + struct device_node *esai_np, *codec_np; + struct device_node *asrc_np = NULL; + struct platform_device *esai_pdev; + struct platform_device *asrc_pdev = NULL; + struct imx_priv *priv = &card_priv; + int ret; + u32 width; + + priv->pdev = pdev; + priv->asrc_pdev = NULL; + + if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg")) + priv->is_codec_rpmsg = true; + + esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!esai_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + esai_pdev = of_find_device_by_node(esai_np); + if (!esai_pdev) { + dev_err(&pdev->dev, "failed to find ESAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (priv->is_codec_rpmsg) { + struct platform_device *codec_dev; + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + } else { + struct i2c_client *codec_dev; + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } + + if (priv->is_codec_rpmsg) { + imx_cs42888_dai[0].codecs->name = "rpmsg-audio-codec-cs42888"; + imx_cs42888_dai[0].codecs->dai_name = "cs42888"; + } else { + imx_cs42888_dai[0].codecs->of_node = codec_np; + } + + /*if there is no asrc controller, we only enable one device*/ + if (!asrc_pdev) { + imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai[0].platforms->of_node = esai_np; + snd_soc_card_imx_cs42888.num_links = 1; + snd_soc_card_imx_cs42888.num_dapm_routes = + ARRAY_SIZE(audio_map) - 2; + } else { + imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai[0].platforms->of_node = esai_np; + imx_cs42888_dai[1].cpus->of_node = asrc_np; + imx_cs42888_dai[1].platforms->of_node = asrc_np; + imx_cs42888_dai[2].cpus->dai_name = dev_name(&esai_pdev->dev); + snd_soc_card_imx_cs42888.num_links = 3; + + if (priv->is_codec_rpmsg) { + imx_cs42888_dai[2].codecs->name = "rpmsg-audio-codec-cs42888"; + imx_cs42888_dai[2].codecs->dai_name = "cs42888"; + } else { + imx_cs42888_dai[2].codecs->of_node = codec_np; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + priv->esai_clk = devm_clk_get(&esai_pdev->dev, "extal"); + if (IS_ERR(priv->esai_clk)) { + ret = PTR_ERR(priv->esai_clk); + dev_err(&esai_pdev->dev, "failed to get cpu clk: %d\n", ret); + goto fail; + } + + priv->is_codec_master = false; + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + priv->is_codec_master = true; + + snd_soc_card_imx_cs42888.dev = &pdev->dev; + + platform_set_drvdata(pdev, &snd_soc_card_imx_cs42888); + + ret = snd_soc_register_card(&snd_soc_card_imx_cs42888); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); +fail: + if (asrc_np) + of_node_put(asrc_np); + if (esai_np) + of_node_put(esai_np); + if (codec_np) + of_node_put(codec_np); + return ret; +} + +static int imx_cs42888_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_soc_card_imx_cs42888); + return 0; +} + +static const struct of_device_id imx_cs42888_dt_ids[] = { + { .compatible = "fsl,imx-audio-cs42888", }, + { /* sentinel */ } +}; + +static struct platform_driver imx_cs42888_driver = { + .probe = imx_cs42888_probe, + .remove = imx_cs42888_remove, + .driver = { + .name = "imx-cs42888", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_cs42888_dt_ids, + }, +}; +module_platform_driver(imx_cs42888_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver"); +MODULE_ALIAS("platform:imx-cs42888"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-dsp.c b/sound/soc/fsl/imx-dsp.c new file mode 100644 index 000000000000..f60d55073d8e --- /dev/null +++ b/sound/soc/fsl/imx-dsp.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: (GPL-2.0+ +// +// DSP machine driver +// +// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. +// Copyright 2018 NXP + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> + +struct imx_dsp_audio_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; +}; + +static int imx_dsp_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; + int ret; + u32 dai_format = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + 24576000, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(rtd->dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops imx_dsp_ops_be = { + .hw_params = imx_dsp_hw_params, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct snd_interval *rate; + struct snd_interval *channels; + struct snd_mask *mask; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = 48000; + + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + channels->max = channels->min = 2; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static const struct snd_soc_dapm_route imx_dsp_audio_map[] = { + {"Playback", NULL, "Compress Playback"},/* dai route for be and fe */ + {"Playback", NULL, "Playback"}, +}; + +static int imx_dsp_audio_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np=NULL, *codec_np=NULL, *platform_np=NULL; + struct snd_soc_dai_link_component *comp; + struct platform_device *cpu_pdev; + struct imx_dsp_audio_data *data; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + goto fail; + } + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find rpmsg platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + platform_np = of_parse_phandle(pdev->dev.of_node, "audio-platform", 0); + if (!platform_np) { + dev_err(&pdev->dev, "platform missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + + data->dai[0].cpus = &comp[0]; + data->dai[0].codecs = &comp[1]; + data->dai[0].platforms = &comp[2]; + + data->dai[0].num_cpus = 1; + data->dai[0].num_codecs = 1; + data->dai[0].num_platforms = 1; + + data->dai[0].name = "dsp hifi fe"; + data->dai[0].stream_name = "dsp hifi fe"; + data->dai[0].codecs->dai_name = "snd-soc-dummy-dai"; + data->dai[0].codecs->name = "snd-soc-dummy"; + data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].cpus->of_node = cpu_np; + data->dai[0].platforms->of_node = platform_np; + data->dai[0].playback_only = true; + data->dai[0].capture_only = false; + data->dai[0].dpcm_playback = 1; + data->dai[0].dpcm_capture = 0; + data->dai[0].dynamic = 1, + data->dai[0].ignore_pmdown_time = 1, + data->dai[0].dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + data->dai[1].cpus = &comp[3]; + data->dai[1].codecs = &comp[4]; + data->dai[1].platforms = &comp[5]; + + data->dai[1].num_cpus = 1; + data->dai[1].num_codecs = 1; + data->dai[1].num_platforms = 1; + + data->dai[1].name = "dsp hifi be"; + data->dai[1].stream_name = "dsp hifi be"; + data->dai[1].codecs->dai_name = "cs42888"; + data->dai[1].codecs->of_node = codec_np; + data->dai[1].cpus->dai_name = "snd-soc-dummy-dai"; + data->dai[1].cpus->name = "snd-soc-dummy"; + data->dai[1].platforms->name = "snd-soc-dummy"; + data->dai[1].playback_only = true; + data->dai[1].capture_only = false; + data->dai[1].dpcm_playback = 1; + data->dai[1].dpcm_capture = 0; + data->dai[1].no_pcm = 1, + data->dai[1].ignore_pmdown_time = 1, + data->dai[1].dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + data->dai[1].ops = &imx_dsp_ops_be; + data->dai[1].be_hw_params_fixup = be_hw_params_fixup; + + data->card.dapm_routes = imx_dsp_audio_map; + data->card.num_dapm_routes = ARRAY_SIZE(imx_dsp_audio_map); + data->card.num_links = 2; + data->card.dai_link = data->dai; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + if (platform_np) + of_node_put(platform_np); + return ret; +} + +static const struct of_device_id imx_dsp_audio_dt_ids[] = { + { .compatible = "fsl,imx-dsp-audio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_dsp_audio_dt_ids); + +static struct platform_driver imx_dsp_audio_driver = { + .driver = { + .name = "imx-dsp-audio", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_dsp_audio_dt_ids, + }, + .probe = imx_dsp_audio_probe, +}; +module_platform_driver(imx_dsp_audio_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-micfil.c b/sound/soc/fsl/imx-micfil.c new file mode 100644 index 000000000000..13e5c992d195 --- /dev/null +++ b/sound/soc/fsl/imx-micfil.c @@ -0,0 +1,184 @@ +/* + * Copyright 2018 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/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_micfil.h" + +#define RX 0 +#define TX 1 + +struct imx_micfil_data { + char name[32]; + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_micfil_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + int ret; + static u32 support_rates[] = {11025, 16000, 22050, + 32000, 44100, 48000,}; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static int imx_micfil_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = rtd->card->dev; + unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + unsigned int rate = params_rate(params); + int ret, dir; + + /* For playback the XTOR 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 cpu 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; + } + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, + rate, dir); + if (ret) { + dev_err(dev, + "%s: failed to set cpu sysclk: %d\n", __func__, + ret); + return ret; + } + + return 0; +} + +struct snd_soc_ops imx_micfil_ops = { + .startup = imx_micfil_startup, + .hw_params = imx_micfil_hw_params, +}; + +static int imx_micfil_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct platform_device *cpu_pdev; + struct imx_micfil_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + strncpy(data->name, cpu_np->name, sizeof(data->name) - 1); + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find MICFIL platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + data->dai.name = "micfil hifi"; + data->dai.stream_name = "micfil hifi"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.playback_only = false; + data->dai.ops = &imx_micfil_ops; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_micfil_dt_ids[] = { + { .compatible = "fsl,imx-audio-micfil", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids); + +static struct platform_driver imx_micfil_driver = { + .driver = { + .name = "imx-micfil", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_micfil_dt_ids, + }, + .probe = imx_micfil_probe, +}; +module_platform_driver(imx_micfil_driver); + +MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>"); +MODULE_DESCRIPTION("NXP Micfil ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-micfil"); diff --git a/sound/soc/fsl/imx-mqs.c b/sound/soc/fsl/imx-mqs.c new file mode 100644 index 000000000000..6462003dc009 --- /dev/null +++ b/sound/soc/fsl/imx-mqs.c @@ -0,0 +1,291 @@ +/* + * Copyright 2012, 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * 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/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#define SUPPORT_RATE_NUM 10 + +struct imx_priv { + struct clk *codec_clk; + unsigned int mclk_freq; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; + +static struct imx_priv card_priv; + +static int imx_mqs_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + static u32 support_rates[SUPPORT_RATE_NUM]; + int ret; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + + if (priv->mclk_freq % 24576000 == 0) { + support_rates[0] = 48000; + constraint_rates.list = support_rates; + constraint_rates.count = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + } else + dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq); + + return 0; +} + +static int imx_mqs_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params)); + if (ret) { + dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + return 0; +} + +static struct snd_soc_ops imx_mqs_ops = { + .startup = imx_mqs_startup, + .hw_params = imx_mqs_hw_params, +}; + +static struct snd_soc_ops imx_mqs_ops_be = { + .hw_params = imx_mqs_hw_params, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "fsl-mqs-dai")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_mqs_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &imx_mqs_ops, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 0, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 0, + .ops = &imx_mqs_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static struct snd_soc_card snd_soc_card_imx_mqs = { + .name = "mqs-audio", + .dai_link = imx_mqs_dai, + .owner = THIS_MODULE, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int imx_mqs_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np; + struct imx_priv *priv = &card_priv; + struct platform_device *codec_dev; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct device_node *asrc_np; + int ret; + u32 width; + + priv->pdev = pdev; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find cpu dai device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&codec_dev->dev, "failed to find codec device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + imx_mqs_dai[0].codecs->dai_name = "fsl-mqs-dai"; + + if (!asrc_pdev) { + imx_mqs_dai[0].codecs->of_node = codec_np; + imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_mqs_dai[0].platforms->of_node = cpu_np; + snd_soc_card_imx_mqs.num_links = 1; + } else { + imx_mqs_dai[0].codecs->of_node = codec_np; + imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_mqs_dai[0].platforms->of_node = cpu_np; + imx_mqs_dai[1].cpus->of_node = asrc_np; + imx_mqs_dai[1].platforms->of_node = asrc_np; + imx_mqs_dai[2].codecs->of_node = codec_np; + imx_mqs_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + snd_soc_card_imx_mqs.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + snd_soc_card_imx_mqs.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_imx_mqs); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static const struct of_device_id imx_mqs_dt_ids[] = { + { .compatible = "fsl,imx-audio-mqs", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_mqs_dt_ids); + +static struct platform_driver imx_mqs_driver = { + .driver = { + .name = "imx-mqs", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_mqs_dt_ids, + }, + .probe = imx_mqs_probe, +}; +module_platform_driver(imx_mqs_driver); + +MODULE_AUTHOR("Nicolin Chen <Guangyu.Chen@freescale.com>"); +MODULE_DESCRIPTION("Freescale i.MX MQS ASoC machine driver"); +MODULE_ALIAS("platform:imx-mqs"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-pcm-dma-v2.c b/sound/soc/fsl/imx-pcm-dma-v2.c new file mode 100644 index 000000000000..54b08131199b --- /dev/null +++ b/sound/soc/fsl/imx-pcm-dma-v2.c @@ -0,0 +1,268 @@ +/* + * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "imx-pcm.h" + +static struct snd_pcm_hardware imx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool imx_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +/* this may get called several times by oss emulation */ +static int imx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_config config; + struct dma_chan *chan; + int err = 0; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME + */ + if (!dma_data) + return 0; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + chan = snd_dmaengine_pcm_get_chan(substream); + if (!chan) + return -EINVAL; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + dma_data, + &config); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + config.dst_fifo_num = dma_data->fifo_num; + else + config.src_fifo_num = dma_data->fifo_num; + + return dmaengine_slave_config(chan, &config); +} + +static int imx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_pcm_pointer(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} + +static void imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, + struct device *dev) +{ + size_t size = imx_pcm_hardware.buffer_bytes_max; + + snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV_IRAM, + dev, + size, + size); +} + +static void imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_preallocate_free(substream); +} + +static int imx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_caps dma_caps; + struct dma_chan *chan; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* DT boot: filter_data is the DMA name */ + if (rtd->cpu_dai->dev->of_node) { + struct dma_chan *chan; + + chan = dma_request_slave_channel(rtd->cpu_dai->dev, + dma_data->chan_name); + ret = snd_dmaengine_pcm_open(substream, chan); + if (ret) + return ret; + } else { + ret = snd_dmaengine_pcm_open_request_chan(substream, + imx_dma_filter_fn, + dma_data->filter_data); + if (ret) + return ret; + } + + chan = snd_dmaengine_pcm_get_chan(substream); + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + imx_pcm_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware); + + imx_pcm_preallocate_dma_buffer(substream, chan->device->dev); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; +} + +static int imx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static int imx_pcm_close(struct snd_pcm_substream *substream) +{ + imx_pcm_free_dma_buffers(substream); + + return snd_dmaengine_pcm_close_release_chan(substream); +} + +static struct snd_pcm_ops imx_pcm_ops = { + .open = imx_pcm_open, + .close = imx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = imx_pcm_hw_params, + .hw_free = imx_pcm_hw_free, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = imx_pcm_pointer, + .mmap = imx_pcm_mmap, +}; + +static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + return ret; +} + +static struct snd_soc_component_driver imx_soc_platform = { + .name = "imx-pcm-dma-v2", + .ops = &imx_pcm_ops, + .pcm_new = imx_pcm_new, +}; + +int imx_pcm_platform_register(struct device *dev) +{ + return devm_snd_soc_register_component(dev, &imx_soc_platform, NULL, 0); +} +EXPORT_SYMBOL_GPL(imx_pcm_platform_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h index 5dd406774d3e..edbb268332bd 100644 --- a/sound/soc/fsl/imx-pcm.h +++ b/sound/soc/fsl/imx-pcm.h @@ -39,13 +39,27 @@ struct imx_pcm_fiq_params { struct snd_dmaengine_dai_dma_data *dma_params_tx; }; +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_RPMSG) +int imx_rpmsg_platform_register(struct device *dev); +#else +static inline int imx_rpmsg_platform_register(struct device *dev) +{ + return -ENODEV; +} +#endif + #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA) int imx_pcm_dma_init(struct platform_device *pdev, size_t size); +int imx_pcm_platform_register(struct device *dev); #else static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size) { return -ENODEV; } +static inline int imx_pcm_platform_register(struct device *dev) +{ + return -ENODEV; +} #endif #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ) diff --git a/sound/soc/fsl/imx-pdm.c b/sound/soc/fsl/imx-pdm.c new file mode 100644 index 000000000000..9e61689d0278 --- /dev/null +++ b/sound/soc/fsl/imx-pdm.c @@ -0,0 +1,207 @@ +/* + * 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/slab.h> +#include <linux/clk.h> +#include <sound/soc.h> + +#include "fsl_sai.h" + +struct imx_pdm_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + unsigned int decimation; +}; + +static u32 imx_pdm_mic_rates[] = { 8000, 16000, 32000, 48000, 64000 }; +static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_rates), + .list = imx_pdm_mic_rates, +}; +static u32 imx_pdm_mic_channels[] = { 1 }; +static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_channels), + .list = imx_pdm_mic_channels, +}; + +static int imx_pdm_mic_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &imx_pdm_mic_rate_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw rate constrains: %d\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw channels constrains: %d", ret); + return ret; + } + + return 0; +} + +static int imx_pdm_mic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + int ret; + + /* set cpu dai format configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set tdm slots only one for now */ + snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 1, 32); + /* Set clock out */ + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation); + if (ret) { + dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops imx_pdm_mic_ops = { + .startup = imx_pdm_mic_startup, + .hw_params = imx_pdm_mic_hw_params, +}; + +static int imx_pdm_mic_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_pdm_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "fail to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_property_read_u32(np, "decimation", &data->decimation); + if (ret < 0) { + ret = -EINVAL; + goto fail; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "pdm hifi"; + data->dai.stream_name = "pdm hifi"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.capture_only = 1; + data->dai.ops = &imx_pdm_mic_ops; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(&pdev->dev, "fail to find card model name\n"); + goto fail; + } + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static int imx_pdm_mic_remove(struct platform_device *pdev) +{ + struct imx_pdm_data *data = platform_get_drvdata(pdev); + /* unregister card */ + snd_soc_unregister_card(&data->card); + return 0; +} + +static const struct of_device_id imx_pdm_mic_dt_ids[] = { + { .compatible = "fsl,imx-pdm-mic", }, + { /* sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids); + +static struct platform_driver imx_pdm_mic_driver = { + .driver = { + .name = "imx-pdm-mic", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_pdm_mic_dt_ids, + }, + .probe = imx_pdm_mic_probe, + .remove = imx_pdm_mic_remove, +}; +module_platform_driver(imx_pdm_mic_driver); + +MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-pdm-mic"); diff --git a/sound/soc/fsl/imx-si476x.c b/sound/soc/fsl/imx-si476x.c new file mode 100644 index 000000000000..a5112a7d8d5a --- /dev/null +++ b/sound/soc/fsl/imx-si476x.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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/i2c.h> +#include <sound/soc.h> + +#include "imx-audmux.h" + +static int imx_audmux_config(int slave, int master) +{ + unsigned int ptcr, pdcr; + slave = slave - 1; + master = master - 1; + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(slave) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(slave); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(slave); + imx_audmux_v2_configure_port(master, ptcr, pdcr); + + /* + * According to RM, RCLKDIR and SYN should not be changed at same time. + * So separate to two step for configuring this port. + */ + ptcr |= IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RFSEL(slave) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_RCSEL(slave); + imx_audmux_v2_configure_port(master, ptcr, pdcr); + + ptcr = IMX_AUDMUX_V2_PTCR_SYN; + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(master); + imx_audmux_v2_configure_port(slave, ptcr, pdcr); + + return 0; +} + +static int imx_si476x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(cpu_dai->dev, "failed to set dai fmt\n"); + return ret; + } + + return ret; +} + +static struct snd_soc_ops imx_si476x_ops = { + .hw_params = imx_si476x_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "si476x-codec")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link imx_dai = { + .name = "imx-si476x", + .stream_name = "imx-si476x", + .ops = &imx_si476x_ops, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-audio-si476x", + .dai_link = &imx_dai, + .num_links = 1, + .owner = THIS_MODULE, +}; + +static int imx_si476x_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_imx_3stack; + struct device_node *ssi_np, *np = pdev->dev.of_node; + struct platform_device *ssi_pdev; + struct i2c_client *fm_dev; + struct device_node *fm_np = NULL; + int int_port, ext_port, ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + imx_audmux_config(int_port, ext_port); + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + if (!ssi_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + return -EINVAL; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto end; + } + + fm_np = of_parse_phandle(pdev->dev.of_node, "fm-controller", 0); + if (!fm_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto end; + } + + fm_dev = of_find_i2c_device_by_node(fm_np->parent); + if (!fm_dev || !fm_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find FM platform device\n"); + ret = -EINVAL; + goto end; + } + + card->dev = &pdev->dev; + card->dai_link->cpus->dai_name = dev_name(&ssi_pdev->dev); + card->dai_link->platforms->of_node = ssi_np; + card->dai_link->codecs->of_node = fm_np; + + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&pdev->dev, "Failed to register card: %d\n", ret); + +end: + if (ssi_np) + of_node_put(ssi_np); + if (fm_np) + of_node_put(fm_np); + + return ret; +} + +static int imx_si476x_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_imx_3stack; + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id imx_si476x_dt_ids[] = { + { .compatible = "fsl,imx-audio-si476x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_si476x_dt_ids); + +static struct platform_driver imx_si476x_driver = { + .driver = { + .name = "imx-tuner-si476x", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_si476x_dt_ids, + }, + .probe = imx_si476x_probe, + .remove = imx_si476x_remove, +}; + +module_platform_driver(imx_si476x_driver); + +/* Module information */ +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC i.MX si476x"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-tuner-si476x"); diff --git a/sound/soc/fsl/imx-sii902x.c b/sound/soc/fsl/imx-sii902x.c new file mode 100644 index 000000000000..d4b18e0fdf06 --- /dev/null +++ b/sound/soc/fsl/imx-sii902x.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * 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/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_sai.h" + +#define SUPPORT_RATE_NUM 10 + +struct imx_sii902x_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + bool is_stream_opened[2]; +}; + +static int imx_sii902x_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + static u32 support_rates[SUPPORT_RATE_NUM]; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } + + support_rates[0] = 32000; + support_rates[1] = 48000; + support_rates[2] = 96000; + support_rates[3] = 192000; + constraint_rates.list = support_rates; + constraint_rates.count = 4; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 2); + if (ret) + return ret; + + return 0; +} + +static int imx_sii902x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + int ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 24); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return 0; +} + +static void imx_sii902x_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_sii902x_ops = { + .startup = imx_sii902x_startup, + .shutdown = imx_sii902x_shutdown, + .hw_params = imx_sii902x_hw_params, +}; + +static int imx_sii902x_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *sii902x_np = NULL; + struct platform_device *cpu_pdev; + struct imx_sii902x_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "sii902x hdmi"; + data->dai.stream_name = "sii902x hdmi"; + data->dai.codecs->dai_name = "i2s-hifi"; + data->dai.codecs->name = "hdmi-audio-codec.1"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.ops = &imx_sii902x_ops; + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (sii902x_np) + of_node_put(sii902x_np); + return ret; +} + +static const struct of_device_id imx_sii902x_dt_ids[] = { + { .compatible = "fsl,imx-audio-sii902x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sii902x_dt_ids); + +static struct platform_driver imx_sii902x_driver = { + .driver = { + .name = "imx-sii902x", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_sii902x_dt_ids, + }, + .probe = imx_sii902x_probe, +}; +module_platform_driver(imx_sii902x_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX SII902X hdmi audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-sii902x"); diff --git a/sound/soc/fsl/imx-wm8524.c b/sound/soc/fsl/imx-wm8524.c new file mode 100644 index 000000000000..6d2f1e840b8e --- /dev/null +++ b/sound/soc/fsl/imx-wm8524.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * 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/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/syscon.h> + +struct imx_priv { + struct snd_soc_dai_link dai[3]; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; + u32 asrc_rate; + u32 asrc_format; +}; + +static const struct snd_soc_dapm_widget imx_wm8524_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + unsigned int fmt; + int ret = 0; + + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, + params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return ret; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Playback", NULL, "CPU-Playback"}, + {"CPU-Playback", NULL, "ASRC-Playback"}, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = priv->asrc_rate; + rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static int imx_wm8524_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); + int ret; + + priv->clk_frequency = clk_get_rate(priv->codec_clk); + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, priv->clk_frequency, + SND_SOC_CLOCK_IN); + + return 0; +} + +static int imx_wm8524_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *asrc_np = NULL; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv; + struct platform_device *codec_pdev = NULL; + struct snd_soc_dai_link_component *dlc; + int ret; + u32 width; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_pdev = of_find_device_by_node(codec_np); + if (!codec_pdev || !codec_pdev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk"); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->dai[0].cpus = &dlc[0]; + priv->dai[0].num_cpus = 1; + priv->dai[0].platforms = &dlc[1]; + priv->dai[0].num_platforms = 1; + priv->dai[0].codecs = &dlc[2]; + priv->dai[0].num_codecs = 1; + + priv->dai[0].name = "HiFi"; + priv->dai[0].stream_name = "HiFi"; + priv->dai[0].codecs->dai_name = "wm8524-hifi", + priv->dai[0].ops = &imx_hifi_ops, + priv->dai[0].codecs->of_node = codec_np; + priv->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + priv->dai[0].platforms->of_node = cpu_np; + priv->dai[0].playback_only = 1; + + priv->card.late_probe = imx_wm8524_late_probe; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_wm8524_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets); + priv->card.dai_link = priv->dai; + priv->card.dapm_routes = audio_map; + priv->card.num_dapm_routes = 1; + + /*if there is no asrc controller, we only enable one device*/ + if (asrc_pdev) { + priv->dai[1].cpus = &dlc[3]; + priv->dai[1].num_cpus = 1; + priv->dai[1].platforms = &dlc[4]; + priv->dai[1].num_platforms = 1; + priv->dai[1].codecs = &dlc[5]; + priv->dai[1].num_codecs = 1; + + priv->dai[2].cpus = &dlc[6]; + priv->dai[2].num_cpus = 1; + priv->dai[2].platforms = &dlc[7]; + priv->dai[2].num_platforms = 1; + priv->dai[2].codecs = &dlc[8]; + priv->dai[2].num_codecs = 1; + + priv->dai[1].name = "HiFi-ASRC-FE"; + priv->dai[1].stream_name = "HiFi-ASRC-FE"; + priv->dai[1].codecs->dai_name = "snd-soc-dummy-dai"; + priv->dai[1].codecs->name = "snd-soc-dummy"; + priv->dai[1].cpus->of_node = asrc_np; + priv->dai[1].platforms->of_node = asrc_np; + priv->dai[1].dynamic = 1; + priv->dai[1].dpcm_playback = 1; + priv->dai[1].dpcm_capture = 0; + + priv->dai[2].name = "HiFi-ASRC-BE"; + priv->dai[2].stream_name = "HiFi-ASRC-BE"; + priv->dai[2].codecs->dai_name = "wm8524-hifi"; + priv->dai[2].codecs->of_node = codec_np; + priv->dai[2].cpus->of_node = cpu_np; + priv->dai[2].platforms->name = "snd-soc-dummy"; + priv->dai[2].no_pcm = 1; + priv->dai[2].dpcm_playback = 1; + priv->dai[2].dpcm_capture = 0; + priv->dai[2].ops = &imx_hifi_ops, + priv->dai[2].be_hw_params_fixup = be_hw_params_fixup, + priv->card.num_links = 3; + priv->card.dai_link = &priv->dai[0]; + priv->card.num_dapm_routes += 1; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_wm8524_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8524", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids); + +static struct platform_driver imx_wm8524_driver = { + .driver = { + .name = "imx-wm8524", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8524_dt_ids, + }, + .probe = imx_wm8524_probe, +}; +module_platform_driver(imx_wm8524_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8524 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8524"); diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c new file mode 100644 index 000000000000..2ffbd1f69e2b --- /dev/null +++ b/sound/soc/fsl/imx-wm8958.c @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * 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/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/syscon.h> +#include "../fsl/fsl_sai.h" +#include "../codecs/wm8994.h" + +#define DAI_NAME_SIZE 32 + +struct imx_wm8958_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *mclk; + unsigned int clk_frequency; + bool is_codec_master; + int sr_stream[2]; + struct regmap *gpr; +}; + +struct imx_priv { + int hp_gpio; + int hp_active_low; + struct snd_soc_component *component; + struct platform_device *pdev; +}; + +static struct imx_priv card_priv; + +static struct snd_soc_jack imx_hp_jack; + +static struct snd_soc_jack_pin imx_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 1, +}; + +static int hpjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int hp_status, ret; + + if (!gpio_is_valid(priv->hp_gpio)) + return 0; + + hp_status = gpio_get_value(priv->hp_gpio); + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (hp_status != priv->hp_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = imx_hp_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = 0; + } + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + +static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route imx_wm8958_dapm_route[] = { + {"Headphone Jack", NULL, "HPOUT1L"}, + {"Headphone Jack", NULL, "HPOUT1R"}, + {"Ext Spk", NULL, "SPKOUTLP"}, + {"Ext Spk", NULL, "SPKOUTLN"}, + {"Ext Spk", NULL, "SPKOUTRP"}, + {"Ext Spk", NULL, "SPKOUTRN"}, + {"IN1LN", NULL, "MICBIAS2"}, +}; + +static int imx_hifi_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 snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + int ret; + + if (tx && params_width(params) == 24) { + if (sample_rate == 88200 || sample_rate == 96000 || + sample_rate == 48000 || sample_rate == 44100) { + dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate); + return -EINVAL; + } + } else if (!tx && params_width(params) == 24) { + if (sample_rate == 44100 || sample_rate == 48000) { + dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate); + return -EINVAL; + } + } + + ret = snd_soc_dai_set_fmt(codec_dai, data->dai.dai_fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + data->clk_frequency = clk_get_rate(data->mclk); + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } else { + data->sr_stream[tx] = sample_rate; + + if (params_width(params) == 24) + pll_out = data->sr_stream[tx] * 384; + else + pll_out = data->sr_stream[tx] * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + data->clk_frequency, + pll_out); + if (ret) { + dev_err(dev, "failed to set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } + + /* + * Set GPIO1 pin function to reserve, so that DAC1 and ADC1 using shared + * LRCLK from DACLRCK1. + */ + snd_soc_component_update_bits(codec_dai->component, WM8994_GPIO_1, 0x1f, 0x2); + + /* + * Clear ADC_OSR128 bit to support slower SYSCLK, and support ADC sample + * rate 8K, 11.025K and 12K. + */ + snd_soc_component_update_bits(codec_dai->component, WM8994_OVERSAMPLING, 1<<1, 0); + return 0; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (data->is_codec_master && + data->sr_stream[!tx] == 0 && data->sr_stream[tx]) { + /* + * We should connect AIF1CLK source to FLL after enable FLL, and + * disconnet AIF1CLK source to FLL before disable FLL, otherwise + * FLL worked abnormal. + */ + snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1, + data->clk_frequency, SND_SOC_CLOCK_OUT); + + /* Disable FLL1 after all stream finished. */ + snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, 0, 0); + } + + data->sr_stream[tx] = 0; + + return 0; +} + +static u32 imx_wm8958_adc_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000 +}; + +static u32 imx_wm8958_dac_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 88200, 96000 +}; + +static struct snd_pcm_hw_constraint_list imx_wm8958_adc_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8958_adc_rates), + .list = imx_wm8958_adc_rates, +}; + +static struct snd_pcm_hw_constraint_list imx_wm8958_dac_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8958_dac_rates), + .list = imx_wm8958_dac_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + if (!tx) + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_adc_rate_constraints); + else + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_dac_rate_constraints); + return ret; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, +}; + +static int imx_wm8958_gpio_init(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + int ret; + + priv->component = codec_dai->component; + + if (gpio_is_valid(priv->hp_gpio)) { + imx_hp_jack_gpio.gpio = priv->hp_gpio; + imx_hp_jack_gpio.jack_status_check = hpjack_status_check; + + ret = snd_soc_card_jack_new(card, "Headphone Jack", + SND_JACK_HEADPHONE, &imx_hp_jack, + imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins)); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1, + &imx_hp_jack_gpio); + if (ret) + return ret; + } + + return 0; +} + +static ssize_t headphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + if (!gpio_is_valid(priv->hp_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(priv->hp_gpio); + + if (hp_status != priv->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR_RO(headphone); + +static int imx_wm8958_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (card->dapm.bias_level == SND_SOC_BIAS_OFF) { + if (!IS_ERR(data->mclk)) { + ret = clk_prepare_enable(data->mclk); + if (ret) { + dev_err(card->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + } + break; + default: + break; + } + + return 0; +} + +static int imx_wm8958_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_OFF: + if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) + if (!IS_ERR(data->mclk)) + clk_disable_unprepare(data->mclk); + break; + default: + break; + } + + card->dapm.bias_level = level; + + return 0; +} + +static int of_parse_gpr(struct platform_device *pdev, + struct imx_wm8958_data *data) +{ + int ret; + struct of_phandle_args args; + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7d-12x12-lpddr3-arm2-wm8958")) + return 0; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "gpr", 3, 0, &args); + if (ret) { + dev_warn(&pdev->dev, "failed to get gpr property\n"); + return ret; + } + + data->gpr = syscon_node_to_regmap(args.np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + return ret; + } + + regmap_update_bits(data->gpr, args.args[0], args.args[1], + args.args[2]); + + return 0; +} + +static int imx_wm8958_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8958_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + priv->pdev = pdev; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_parse_gpr(pdev, data); + if (ret) + goto fail; + + if (of_property_read_bool(np, "codec-master")) { + data->dai.dai_fmt = SND_SOC_DAIFMT_CBM_CFM; + data->is_codec_master = true; + } else + data->dai.dai_fmt = SND_SOC_DAIFMT_CBS_CFS; + + data->mclk = devm_clk_get(&codec_dev->dev, "mclk1"); + if (IS_ERR(data->mclk)) { + ret = PTR_ERR(data->mclk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0, + (enum of_gpio_flags *)&priv->hp_active_low); + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codecs->dai_name = "wm8994-aif1"; + data->dai.codecs->name = "wm8994-codec"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.ops = &imx_hifi_ops; + data->dai.dai_fmt |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + data->card.set_bias_level = imx_wm8958_set_bias_level; + data->card.set_bias_level_post = imx_wm8958_set_bias_level_post; + data->card.owner = THIS_MODULE; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_wm8958_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets); + data->card.dapm_routes = imx_wm8958_dapm_route; + data->card.num_dapm_routes = ARRAY_SIZE(imx_wm8958_dapm_route); + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = imx_wm8958_gpio_init(&data->card); + + if (gpio_is_valid(priv->hp_gpio)) { + ret = driver_create_file(pdev->dev.driver, + &driver_attr_headphone); + if (ret) { + dev_err(&pdev->dev, + "create hp attr failed (%d)\n", ret); + goto fail; + } + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8958_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + return 0; +} + +static const struct of_device_id imx_wm8958_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8958", }, + { .compatible = "fsl,imx7d-12x12-lpddr3-arm2-wm8958", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids); + +static struct platform_driver imx_wm8958_driver = { + .driver = { + .name = "imx-wm8958", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8958_dt_ids, + }, + .probe = imx_wm8958_probe, + .remove = imx_wm8958_remove, +}; +module_platform_driver(imx_wm8958_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8958"); diff --git a/sound/soc/fsl/imx-wm8960.c b/sound/soc/fsl/imx-wm8960.c new file mode 100644 index 000000000000..9005b5e36569 --- /dev/null +++ b/sound/soc/fsl/imx-wm8960.c @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * 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/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/syscon.h> +#include "../codecs/wm8960.h" +#include "fsl_sai.h" + +struct imx_wm8960_data { + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; + bool is_codec_master; + bool is_codec_rpmsg; + bool is_stream_in_use[2]; + bool is_stream_opened[2]; + struct regmap *gpr; + unsigned int hp_det[2]; + u32 asrc_rate; + u32 asrc_format; +}; + +struct imx_priv { + enum of_gpio_flags hp_active_low; + enum of_gpio_flags mic_active_low; + bool is_headset_jack; + struct platform_device *pdev; + struct platform_device *asrc_pdev; +}; + +static struct imx_priv card_priv; + +static struct snd_soc_jack imx_hp_jack; +static struct snd_soc_jack_pin imx_hp_jack_pin = { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, +}; +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static struct snd_soc_jack imx_mic_jack; +static struct snd_soc_jack_pin imx_mic_jack_pins = { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, +}; +static struct snd_soc_jack_gpio imx_mic_jack_gpio = { + .name = "mic detect", + .report = SND_JACK_MICROPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static int hp_jack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct snd_soc_jack *jack = data; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + int hp_status, ret; + + hp_status = gpio_get_value(imx_hp_jack_gpio.gpio); + + if (hp_status != priv->hp_active_low) { + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + if (priv->is_headset_jack) { + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin(dapm, "Main MIC"); + } + ret = imx_hp_jack_gpio.report; + } else { + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + if (priv->is_headset_jack) { + snd_soc_dapm_disable_pin(dapm, "Mic Jack"); + snd_soc_dapm_enable_pin(dapm, "Main MIC"); + } + ret = 0; + } + + return ret; +} + +static int mic_jack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct snd_soc_jack *jack = data; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + int mic_status, ret; + + mic_status = gpio_get_value(imx_mic_jack_gpio.gpio); + + if (mic_status != priv->mic_active_low) { + snd_soc_dapm_disable_pin(dapm, "Main MIC"); + ret = imx_mic_jack_gpio.report; + } else { + snd_soc_dapm_enable_pin(dapm, "Main MIC"); + ret = 0; + } + + return ret; +} + +static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Main MIC", NULL), +}; + +static int imx_wm8960_jack_init(struct snd_soc_card *card, + struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin, + struct snd_soc_jack_gpio *gpio) +{ + int ret; + + ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1); + if (ret) { + return ret; + } + + ret = snd_soc_jack_add_gpios(jack, 1, gpio); + if (ret) + return ret; + + return 0; +} + +static ssize_t headphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(imx_hp_jack_gpio.gpio); + + if (hp_status != priv->hp_active_low) + strcpy(buf, "Headphone\n"); + else + strcpy(buf, "Speaker\n"); + + return strlen(buf); +} + +static ssize_t micphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int mic_status; + + /* Check if headphone is plugged in */ + mic_status = gpio_get_value(imx_mic_jack_gpio.gpio); + + if (mic_status != priv->mic_active_low) + strcpy(buf, "Mic Jack\n"); + else + strcpy(buf, "Main MIC\n"); + + return strlen(buf); +} +static DRIVER_ATTR_RO(headphone); +static DRIVER_ATTR_RO(micphone); + +static int imx_hifi_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 snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + unsigned int fmt; + int ret = 0; + + data->is_stream_in_use[tx] = true; + + if (data->is_stream_in_use[!tx]) + return 0; + + if (data->is_codec_master) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + return 0; + } else { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + /* Set codec pll */ + if (params_width(params) == 24) + pll_out = sample_rate * 768; + else + pll_out = sample_rate * 512; + + ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out); + if (ret) + return ret; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0); + + return ret; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + int ret; + + data->is_stream_in_use[tx] = false; + + if (data->is_codec_master && !data->is_stream_in_use[!tx]) { + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF); + if (ret) + dev_warn(dev, "failed to set codec dai fmt: %d\n", ret); + } + + return 0; +} + +static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 }; +static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8960_rates), + .list = imx_wm8960_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints); + if (ret) + return ret; + } + + return ret; +} + +static void imx_hifi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, + .shutdown = imx_hifi_shutdown, +}; + +static int imx_wm8960_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + + /* + * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame + * clock for ADCs and DACs + */ + snd_soc_component_update_bits(codec_dai->component, WM8960_IFACE2, 1<<6, 1<<6); + + /* GPIO1 used as headphone detect output */ + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 7<<4, 3<<4); + + /* Enable headphone jack detect */ + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<6, 1<<6); + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5); + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2); + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL1, 3, 3); + + return 0; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = data->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, data->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_wm8960_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .ops = &imx_hifi_ops, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_hifi_ops, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static int of_parse_gpr(struct platform_device *pdev, + struct imx_wm8960_data *data) +{ + int ret; + struct of_phandle_args args; + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7d-evk-wm8960")) + return 0; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "gpr", 3, 0, &args); + if (ret) { + dev_warn(&pdev->dev, "failed to get gpr property\n"); + return ret; + } + + data->gpr = syscon_node_to_regmap(args.np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + return ret; + } + + regmap_update_bits(data->gpr, args.args[0], args.args[1], + args.args[2]); + + return 0; +} + +static int imx_wm8960_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct imx_wm8960_data *data; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + u32 width; + int ret; + + priv->pdev = pdev; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg")) + data->is_codec_rpmsg = true; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + if (data->is_codec_rpmsg) { + struct platform_device *codec_dev; + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } else { + struct i2c_client *codec_dev; + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + ret = of_parse_gpr(pdev, data); + if (ret) + goto fail; + + of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2); + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + data->card.dai_link = imx_wm8960_dai; + + if (data->is_codec_rpmsg) { + imx_wm8960_dai[0].codecs->name = "rpmsg-audio-codec-wm8960"; + imx_wm8960_dai[0].codecs->dai_name = "rpmsg-wm8960-hifi"; + } else + imx_wm8960_dai[0].codecs->of_node = codec_np; + + imx_wm8960_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_wm8960_dai[0].platforms->of_node = cpu_np; + + if (!asrc_pdev) { + data->card.num_links = 1; + } else { + imx_wm8960_dai[1].cpus->of_node = asrc_np; + imx_wm8960_dai[1].platforms->of_node = asrc_np; + if (data->is_codec_rpmsg) { + imx_wm8960_dai[2].codecs->name = "rpmsg-audio-codec-wm8960"; + imx_wm8960_dai[2].codecs->dai_name = "rpmsg-wm8960-hifi"; + } else + imx_wm8960_dai[2].codecs->of_node = codec_np; + imx_wm8960_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &data->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + data->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + data->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.dapm_widgets = imx_wm8960_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets); + + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + + data->card.late_probe = imx_wm8960_late_probe; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node, + "hp-det-gpios", 0, &priv->hp_active_low); + + imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node, + "mic-det-gpios", 0, &priv->mic_active_low); + + if (gpio_is_valid(imx_hp_jack_gpio.gpio) && + gpio_is_valid(imx_mic_jack_gpio.gpio) && + imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio) + priv->is_headset_jack = true; + + if (gpio_is_valid(imx_hp_jack_gpio.gpio)) { + if (priv->is_headset_jack) { + imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE; + imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE; + } + + imx_hp_jack_gpio.jack_status_check = hp_jack_status_check; + imx_hp_jack_gpio.data = &imx_hp_jack; + ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack, + &imx_hp_jack_pin, &imx_hp_jack_gpio); + if (ret) { + dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret); + goto out; + } + + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret) + dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret); + } + + if (gpio_is_valid(imx_mic_jack_gpio.gpio)) { + if (!priv->is_headset_jack) { + imx_mic_jack_gpio.jack_status_check = mic_jack_status_check; + imx_mic_jack_gpio.data = &imx_mic_jack; + ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack, + &imx_mic_jack_pins, &imx_mic_jack_gpio); + if (ret) { + dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret); + goto out; + } + } + ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone); + if (ret) + dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret); + } + +out: + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8960_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_micphone); + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + + return 0; +} + +static const struct of_device_id imx_wm8960_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8960", }, + { .compatible = "fsl,imx7d-evk-wm8960" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids); + +static struct platform_driver imx_wm8960_driver = { + .driver = { + .name = "imx-wm8960", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8960_dt_ids, + }, + .probe = imx_wm8960_probe, + .remove = imx_wm8960_remove, +}; +module_platform_driver(imx_wm8960_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8960"); diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c new file mode 100644 index 000000000000..cb50a6fa48a9 --- /dev/null +++ b/sound/soc/fsl/imx-wm8962.c @@ -0,0 +1,860 @@ +/* + * Copyright (C) 2013-2016 Freescale Semiconductor, Inc. + * + * Based on imx-sgtl5000.c + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Linaro Ltd. + * 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/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> + +#include "../codecs/wm8962.h" +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 + +struct imx_wm8962_data { + struct snd_soc_dai_link dai[3]; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; + bool is_codec_master; +}; + +struct imx_priv { + int hp_gpio; + int hp_active_low; + int mic_gpio; + int mic_active_low; + bool amic_mono; + bool dmic_mono; + struct snd_soc_component *component; + struct platform_device *pdev; + struct snd_pcm_substream *first_stream; + struct snd_pcm_substream *second_stream; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; +static struct imx_priv card_priv; + +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID +static int sample_rate = 44100; +static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; +#endif + +static struct snd_soc_jack imx_hp_jack; +static struct snd_soc_jack_pin imx_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static struct snd_soc_jack imx_mic_jack; +static struct snd_soc_jack_pin imx_mic_jack_pins[] = { + { + .pin = "AMIC", + .mask = SND_JACK_MICROPHONE, + }, +}; +static struct snd_soc_jack_gpio imx_mic_jack_gpio = { + .name = "microphone detect", + .report = SND_JACK_MICROPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static int hpjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int hp_status, ret; + + if (!gpio_is_valid(priv->hp_gpio)) + return 0; + + hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0; + + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (hp_status != priv->hp_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = imx_hp_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = 0; + } + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + +static int micjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int mic_status, ret; + + if (!gpio_is_valid(priv->mic_gpio)) + return 0; + + mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0; + + if ((mic_status != priv->mic_active_low && priv->amic_mono) + || (mic_status == priv->mic_active_low && priv->dmic_mono)) + snd_soc_component_update_bits(priv->component, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX); + + else + snd_soc_component_update_bits(priv->component, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, 0); + + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (mic_status != priv->mic_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "DMIC"); + ret = imx_mic_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "DMIC"); + ret = 0; + } + + envp[0] = "NAME=microphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + + +static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static u32 imx_wm8962_rates[] = {32000, 48000, 96000}; +static struct snd_pcm_hw_constraint_list imx_wm8962_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8962_rates), + .list = imx_wm8962_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &imx_wm8962_rate_constraints); + if (ret) + return ret; + } + + return 0; +} + +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID +static int imx_hifi_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 imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + u32 dai_format; + int ret = 0; + + sample_rate = params_rate(params); + sample_format = params_format(params); + + if (data->is_codec_master) + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .startup = imx_hifi_startup, + .hw_params = imx_hifi_hw_params, +}; + +static int imx_wm8962_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct imx_priv *priv = &card_priv; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + struct device *dev = &priv->pdev->dev; + unsigned int pll_out; + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + data->clk_frequency = clk_get_rate(data->codec_clk); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + if (sample_format == SNDRV_PCM_FORMAT_S24_LE + || sample_format == SNDRV_PCM_FORMAT_S20_3LE) + pll_out = sample_rate * 384; + else + pll_out = sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, data->clk_frequency, + pll_out); + if (ret < 0) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_FLL, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + } + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_PREPARE) { + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_MCLK, data->clk_frequency, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, + "failed to switch away from FLL: %d\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + 0, 0, 0); + if (ret < 0) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + } + break; + + default: + break; + } + + return 0; +} + +#else + +static int imx_hifi_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 imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + struct snd_soc_card *card = platform_get_drvdata(priv->pdev); + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + unsigned int sample_rate = params_rate(params); + snd_pcm_format_t sample_format = params_format(params); + u32 dai_format, pll_out; + int ret = 0; + + if (!priv->first_stream) { + priv->first_stream = substream; + } else { + priv->second_stream = substream; + + /* We suppose the two substream are using same params */ + return 0; + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + if (data->is_codec_master) + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (sample_format == SNDRV_PCM_FORMAT_S24_LE + || sample_format == SNDRV_PCM_FORMAT_S20_3LE) + pll_out = sample_rate * 384; + else + pll_out = sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK, + data->clk_frequency, pll_out); + if (ret) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL, + pll_out, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + int ret; + + /* We don't need to handle anything if there's no substream running */ + if (!priv->first_stream) + return 0; + + if (priv->first_stream == substream) + priv->first_stream = priv->second_stream; + priv->second_stream = NULL; + + if (!priv->first_stream) { + /* + * Continuously setting FLL would cause playback distortion. + * We can fix it just by mute codec after playback. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); + + /* + * WM8962 doesn't allow us to continuously setting FLL, + * So we set MCLK as sysclk once, which'd remove the limitation. + */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "failed to switch away from FLL: %d\n", ret); + return ret; + } + + /* Disable FLL and let codec do pm_runtime_put() */ + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, 0, 0); + if (ret < 0) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + } + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .startup = imx_hifi_startup, + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, +}; +#endif /* CONFIG_SND_SOC_IMX_WM8962_ANDROID */ + +static int imx_wm8962_gpio_init(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + + priv->component = codec_dai->component; + + if (gpio_is_valid(priv->hp_gpio)) { + imx_hp_jack_gpio.gpio = priv->hp_gpio; + imx_hp_jack_gpio.jack_status_check = hpjack_status_check; + + snd_soc_card_jack_new(card, "Headphone Jack", + SND_JACK_HEADPHONE, &imx_hp_jack, + imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins)); + + snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio); + } + + if (gpio_is_valid(priv->mic_gpio)) { + imx_mic_jack_gpio.gpio = priv->mic_gpio; + imx_mic_jack_gpio.jack_status_check = micjack_status_check; + + snd_soc_card_jack_new(card, "AMIC", + SND_JACK_MICROPHONE, &imx_mic_jack, + imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins)); + + snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio); + } else if (priv->amic_mono || priv->dmic_mono) { + /* + * Permanent set monomix bit if only one microphone + * is present on the board while it needs monomix. + */ + snd_soc_component_update_bits(priv->component, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX); + } + + return 0; +} + +static ssize_t headphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + if (!gpio_is_valid(priv->hp_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0; + + if (hp_status != priv->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR_RO(headphone); + +static ssize_t microphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int mic_status; + + if (!gpio_is_valid(priv->mic_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if analog microphone is plugged in */ + mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0; + + if (mic_status != priv->mic_active_low) + strcpy(buf, "amic\n"); + else + strcpy(buf, "dmic\n"); + + return strlen(buf); +} + +static DRIVER_ATTR_RO(microphone); + +static int imx_wm8962_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct imx_priv *priv = &card_priv; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + struct device *dev = &priv->pdev->dev; + int ret; + + data->clk_frequency = clk_get_rate(data->codec_clk); + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(dev, "failed to set sysclk in %s\n", __func__); + + return ret; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static int imx_wm8962_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *cpu_np = NULL, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8962_data *data; + int int_port, ext_port, tmp_port; + int ret; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + struct snd_soc_dai_link_component *dlc; + u32 width; + + priv->pdev = pdev; + priv->asrc_pdev = NULL; + + dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + if (!strstr(cpu_np->name, "ssi")) + goto audmux_bypass; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + goto fail; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + goto fail; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + if (data->is_codec_master) { + tmp_port = int_port; + int_port = ext_port; + ext_port = tmp_port; + } + + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(&pdev->dev, "audmux internal port setup failed\n"); + goto fail; + } + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(&pdev->dev, "audmux external port setup failed\n"); + goto fail; + } + +audmux_bypass: + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + priv->first_stream = NULL; + priv->second_stream = NULL; + + data->codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->amic_mono = of_property_read_bool(codec_np, "amic-mono"); + priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono"); + + priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0, + (enum of_gpio_flags *)&priv->hp_active_low); + priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0, + (enum of_gpio_flags *)&priv->mic_active_low); + + data->dai[0].cpus = &dlc[0]; + data->dai[0].num_cpus = 1; + data->dai[0].platforms = &dlc[1]; + data->dai[0].num_platforms = 1; + data->dai[0].codecs = &dlc[2]; + data->dai[0].num_codecs = 1; + + data->dai[0].name = "HiFi"; + data->dai[0].stream_name = "HiFi"; + data->dai[0].codecs->dai_name = "wm8962"; + data->dai[0].codecs->of_node = codec_np; + data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].platforms->of_node = cpu_np; + data->dai[0].ops = &imx_hifi_ops; + data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + if (data->is_codec_master) + data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + else + data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + + data->card.num_links = 1; + + if (asrc_pdev) { + data->dai[1].cpus = &dlc[3]; + data->dai[1].num_cpus = 1; + data->dai[1].platforms = &dlc[4]; + data->dai[1].num_platforms = 1; + data->dai[1].codecs = &dlc[5]; + data->dai[1].num_codecs = 1; + + data->dai[2].cpus = &dlc[6]; + data->dai[2].num_cpus = 1; + data->dai[2].platforms = &dlc[7]; + data->dai[2].num_platforms = 1; + data->dai[2].codecs = &dlc[8]; + data->dai[2].num_codecs = 1; + + data->dai[0].ignore_pmdown_time = 1; + data->dai[1].name = "HiFi-ASRC-FE"; + data->dai[1].stream_name = "HiFi-ASRC-FE"; + data->dai[1].codecs->name = "snd-soc-dummy"; + data->dai[1].codecs->dai_name = "snd-soc-dummy-dai"; + data->dai[1].cpus->of_node = asrc_np; + data->dai[1].platforms->of_node = asrc_np; + data->dai[1].dynamic = 1; + data->dai[1].ignore_pmdown_time = 1; + data->dai[1].dpcm_playback = 1; + data->dai[1].dpcm_capture = 1; + data->dai[1].dpcm_merged_chan = 1; + + data->dai[2].name = "HiFi-ASRC-BE"; + data->dai[2].stream_name = "HiFi-ASRC-BE"; + data->dai[2].codecs->dai_name = "wm8962"; + data->dai[2].codecs->of_node = codec_np; + data->dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[2].platforms->name = "snd-soc-dummy"; + data->dai[2].ops = &imx_hifi_ops; + data->dai[2].be_hw_params_fixup = be_hw_params_fixup; + data->dai[2].no_pcm = 1; + data->dai[2].ignore_pmdown_time = 1; + data->dai[2].dpcm_playback = 1; + data->dai[2].dpcm_capture = 1; + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + data->card.owner = THIS_MODULE; + data->card.dai_link = data->dai; + data->card.dapm_widgets = imx_wm8962_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets); + + data->card.late_probe = imx_wm8962_late_probe; + +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID + data->card.set_bias_level = imx_wm8962_set_bias_level; +#endif + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + imx_wm8962_gpio_init(&data->card); + + if (gpio_is_valid(priv->hp_gpio)) { + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret) { + dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret); + goto fail_hp; + } + } + + if (gpio_is_valid(priv->mic_gpio)) { + ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone); + if (ret) { + dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret); + goto fail_mic; + } + } + + goto fail; + +fail_mic: + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); +fail_hp: +fail: + of_node_put(cpu_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8962_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_microphone); + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + + return 0; +} + +static const struct of_device_id imx_wm8962_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8962", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids); + +static struct platform_driver imx_wm8962_driver = { + .driver = { + .name = "imx-wm8962", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8962_dt_ids, + }, + .probe = imx_wm8962_probe, + .remove = imx_wm8962_remove, +}; +module_platform_driver(imx_wm8962_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8962"); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index b600d3eaaf5c..0eed248a1c4b 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1295,7 +1295,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, } /* dai link name and stream name set correctly ? */ - dev_err(card->dev, "ASoC: can't get %s BE for %s\n", + dev_dbg(card->dev, "ASoC: can't get %s BE for %s\n", stream ? "capture" : "playback", widget->name); return NULL; } @@ -1445,7 +1445,7 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, /* is there a valid BE rtd for this widget */ be = dpcm_get_be(card, list->widgets[i], stream); if (!be) { - dev_err(fe->dev, "ASoC: no BE found for %s\n", + dev_dbg(fe->dev, "ASoC: no BE found for %s\n", list->widgets[i]->name); continue; } |