Merge branch 'audio/next' into next

* audio/next: (528 commits)
  LF-276: ASoC: fsl_easi: constrain period size for edma case
  LF-215: ASoC: fsl_rpmsg_i2s: Enable WQ_FREEZABLE for workqueue
  ASoC: SOF: Read tplg filename from board descriptor
  ASoC: SOF: Update fw_filename from board description
  ASoC: SOF: Allow probe to continue when we have an actual codec
  ...
This commit is contained in:
Dong Aisheng 2019-12-02 18:02:06 +08:00
commit 45efd194bf
147 changed files with 31542 additions and 1062 deletions

View File

@ -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.

View File

@ -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";
};

View File

@ -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";
};

View File

@ -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";
};

View File

@ -0,0 +1,22 @@
Freescale rpmsg i2s interface.
The rpmsg i2s is based on RPMSG that used communicating with M4 core,
which provides a synchronous audio interface that supports fullduplex
serial interfaces with frame synchronization such as I2S.
Required properties:
- compatible : Compatible list, contains "fsl,imx7ulp-rpmsg-i2s".
"fsl,imx8mq-rpmsg-i2s", "fsl,imx8qxp-rpmsg-i2s"
"fsl,imx8qm-rpmsg-i2s"
- fsl,audioindex : This is an index indicating the audio device index in
the M4 side.
Example:
rpmsg_i2s: rpmsg-i2s {
compatible = "fsl,imx7ulp-rpmsg-i2s";
/* the audio device index in m4 domain */
fsl,audioindex = <0> ;
status = "okay";
};

View File

@ -6,7 +6,9 @@ a fibre cable.
Required properties:
- compatible : Compatible list, must contain "fsl,imx35-spdif".
- compatible : Compatible list, must contain "fsl,imx35-spdif",
"fsl,vf610-spdif", "fsl,imx8qm-spdif",
"fsl,imx8mm-spdif"
- reg : Offset and length of the register set for the device.

View File

@ -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)

View File

@ -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";
};

View File

@ -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",
};

View File

@ -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";
};

View File

@ -0,0 +1,16 @@
Freescale i.MX audio complex with Cadence HDMI
Required properties:
- compatible : "fsl,imx-audio-cdnhdmi", "fsl,imx8mq-evk-cdnhdmi"
- model : The user-visible name of this sound complex
- audio-cpu : The phandle of the i.MX SAI controller
- protocol : 0 is hdmi, 1 is dp.
Example:
sound-hdmi {
compatible = "fsl,imx-audio-cdnhdmi";
model = "imx-audio-hdmi";
audio-cpu = <&sai4>;
protocol = <0>;
};

View File

@ -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>;
};

View File

@ -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>;
};

View File

@ -0,0 +1,13 @@
Freescale i.MX audio complex with rpmsg devices
Required properties:
- compatible : "fsl,imx-audio-rpmsg"
- model : The user-visible name of this sound complex
- cpu-dai : The phandle of the i.MX rpmsg i2s device.
Example:
sound-rpmsg {
compatible = "fsl,imx-audio-rpmsg";
model = "rpmsg-audio";
cpu-dai = <&rpmsg_i2s>;
};

View File

@ -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>;
};

View File

@ -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";
};

View File

@ -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>;
};

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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(

View File

@ -22,7 +22,6 @@ struct snd_sof_dsp_ops;
*/
struct snd_sof_pdata {
const struct firmware *fw;
const char *drv_name;
const char *name;
const char *platform;
@ -88,13 +87,13 @@ struct sof_dev_desc {
const char *default_fw_path;
const char *default_tplg_path;
/* default firmware name */
const char *default_fw_filename;
const struct snd_sof_dsp_ops *ops;
const struct sof_arch_ops *arch_ops;
};
int sof_nocodec_setup(struct device *dev,
struct snd_sof_pdata *sof_pdata,
struct snd_soc_acpi_mach *mach,
const struct sof_dev_desc *desc,
const struct snd_sof_dsp_ops *ops);
#endif

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/*
* Copyright 2019 NXP
*
* Author: Daniel Baluta <daniel.baluta@nxp.com>
*/
#ifndef __INCLUDE_SOUND_SOF_DAI_IMX_H__
#define __INCLUDE_SOUND_SOF_DAI_IMX_H__
#include <sound/sof/header.h>
/* ESAI Configuration Request - SOF_IPC_DAI_ESAI_CONFIG */
struct sof_ipc_dai_esai_params {
struct sof_ipc_hdr hdr;
/* MCLK */
uint16_t reserved1;
uint16_t mclk_id;
uint32_t mclk_direction;
uint32_t mclk_rate; /* MCLK frequency in Hz */
uint32_t fsync_rate; /* FSYNC frequency in Hz */
uint32_t bclk_rate; /* BCLK frequency in Hz */
/* TDM */
uint32_t tdm_slots;
uint32_t rx_slots;
uint32_t tx_slots;
uint16_t tdm_slot_width;
uint16_t reserved2; /* alignment */
} __packed;
/* SAI Configuration Request - SOF_IPC_DAI_SAI_CONFIG */
struct sof_ipc_dai_sai_params {
struct sof_ipc_hdr hdr;
/* MCLK */
uint16_t reserved1;
uint16_t mclk_id;
uint32_t mclk_direction;
uint32_t mclk_rate; /* MCLK frequency in Hz */
uint32_t fsync_rate; /* FSYNC frequency in Hz */
uint32_t bclk_rate; /* BCLK frequency in Hz */
/* TDM */
uint32_t tdm_slots;
uint32_t rx_slots;
uint32_t tx_slots;
uint16_t tdm_slot_width;
uint16_t reserved2; /* alignment */
} __packed;
#endif

View File

@ -11,6 +11,7 @@
#include <sound/sof/header.h>
#include <sound/sof/dai-intel.h>
#include <sound/sof/dai-imx.h>
/*
* DAI Configuration.
@ -73,6 +74,8 @@ struct sof_ipc_dai_config {
struct sof_ipc_dai_dmic_params dmic;
struct sof_ipc_dai_hda_params hda;
struct sof_ipc_dai_alh_params alh;
struct sof_ipc_dai_esai_params esai;
struct sof_ipc_dai_sai_params sai;
};
} __packed;

View File

@ -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__ */

View File

@ -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__ */

View File

@ -26,7 +26,7 @@
/* SOF ABI version major, minor and patch numbers */
#define SOF_ABI_MAJOR 3
#define SOF_ABI_MINOR 10
#define SOF_ABI_MINOR 11
#define SOF_ABI_PATCH 0
/* SOF ABI version number. Format within 32bit word is MMmmmppp */

View File

@ -107,11 +107,9 @@
#define SOF_TKN_EFFECT_TYPE SOF_TKN_PROCESS_TYPE
/* SAI */
#define SOF_TKN_IMX_SAI_FIRST_TOKEN 1000
/* TODO: Add SAI tokens */
#define SOF_TKN_IMX_SAI_MCLK_ID 1000
/* ESAI */
#define SOF_TKN_IMX_ESAI_FIRST_TOKEN 1100
/* TODO: Add ESAI tokens */
#define SOF_TKN_IMX_ESAI_MCLK_ID 1100
#endif

View File

@ -571,8 +571,11 @@ static int atmel_classd_probe(struct platform_device *pdev)
dd->pdata = pdata;
dd->irq = platform_get_irq(pdev, 0);
if (dd->irq < 0)
return dd->irq;
if (dd->irq < 0) {
ret = dd->irq;
dev_err(dev, "failed to could not get irq: %d\n", ret);
return ret;
}
dd->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dd->pclk)) {

View File

@ -612,8 +612,11 @@ static int atmel_pdmic_probe(struct platform_device *pdev)
dd->dev = dev;
dd->irq = platform_get_irq(pdev, 0);
if (dd->irq < 0)
return dd->irq;
if (dd->irq < 0) {
ret = dd->irq;
dev_err(dev, "failed to get irq: %d\n", ret);
return ret;
}
dd->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dd->pclk)) {

View File

@ -1342,8 +1342,11 @@ static int cygnus_ssp_probe(struct platform_device *pdev)
}
cygaud->irq_num = platform_get_irq(pdev, 0);
if (cygaud->irq_num <= 0)
return cygaud->irq_num;
if (cygaud->irq_num <= 0) {
dev_err(dev, "platform_get_irq failed\n");
err = cygaud->irq_num;
return err;
}
err = audio_clk_init(pdev, cygaud);
if (err) {

View File

@ -257,6 +257,9 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_WM9712 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_WM9713 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_RPMSG_WM8960
select SND_SOC_RPMSG_CS42XX8
select SND_SOC_RPMSG_AK4497
help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
@ -696,6 +699,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'
@ -1436,6 +1442,14 @@ config SND_SOC_ZX_AUD96P22
depends on I2C
select REGMAP_I2C
config SND_SOC_RPMSG_WM8960
tristate
config SND_SOC_RPMSG_CS42XX8
tristate
config SND_SOC_RPMSG_AK4497
tristate
# Amp
config SND_SOC_LM4857
tristate

View File

@ -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
@ -274,6 +275,10 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-zx-aud96p22-objs := zx_aud96p22.o
snd-soc-rpmsg-wm8960-objs := rpmsg_wm8960.o
snd-soc-rpmsg-cs42xx8-objs := rpmsg_cs42xx8.o
snd-soc-rpmsg-ak4497-objs := rpmsg_ak4497.o
# Amp
snd-soc-max9877-objs := max9877.o
snd-soc-max98504-objs := max98504.o
@ -370,6 +375,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
@ -557,6 +563,9 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o
obj-$(CONFIG_SND_SOC_RPMSG_WM8960) += snd-soc-rpmsg-wm8960.o
obj-$(CONFIG_SND_SOC_RPMSG_CS42XX8) += snd-soc-rpmsg-cs42xx8.o
obj-$(CONFIG_SND_SOC_RPMSG_AK4497) += snd-soc-rpmsg-ak4497.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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];

357
sound/soc/codecs/fsl_mqs.c Normal file
View File

@ -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");

View File

@ -281,6 +281,7 @@ struct hdmi_codec_priv {
static const struct snd_soc_dapm_widget hdmi_widgets[] = {
SND_SOC_DAPM_OUTPUT("TX"),
SND_SOC_DAPM_OUTPUT("RX"),
};
enum {
@ -388,6 +389,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret = 0;
ret = test_and_set_bit(0, &hcp->busy);
@ -402,7 +404,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
goto err;
}
if (hcp->hcd.ops->get_eld) {
if (tx && hcp->hcd.ops->get_eld) {
ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data,
hcp->eld, sizeof(hcp->eld));
@ -471,13 +473,11 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
/* Select a channel allocation that matches with ELD and pcm channels */
idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels);
if (idx < 0) {
dev_err(dai->dev, "Not able to map channels to speakers (%d)\n",
idx);
hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN;
return idx;
} else {
hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id;
hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id;
}
hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id;
hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id;
hp.sample_width = params_width(params);
hp.sample_rate = params_rate(params);
@ -647,14 +647,20 @@ static int hdmi_dai_probe(struct snd_soc_dai *dai)
{
struct snd_soc_dapm_context *dapm;
struct hdmi_codec_daifmt *daifmt;
struct snd_soc_dapm_route route = {
.sink = "TX",
.source = dai->driver->playback.stream_name,
struct snd_soc_dapm_route route[] = {
{
.sink = "TX",
.source = dai->driver->playback.stream_name,
},
{
.sink = dai->driver->playback.stream_name,
.source = "RX",
},
};
int ret;
dapm = snd_soc_component_get_dapm(dai->component);
ret = snd_soc_dapm_add_routes(dapm, &route, 1);
ret = snd_soc_dapm_add_routes(dapm, route, 2);
if (ret)
return ret;
@ -744,6 +750,14 @@ static const struct snd_soc_dai_driver hdmi_i2s_dai = {
.sig_bits = 24,
},
.ops = &hdmi_codec_i2s_dai_ops,
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 8,
.rates = HDMI_RATES,
.formats = I2S_FORMATS,
.sig_bits = 24,
},
.pcm_new = hdmi_codec_pcm_new,
};
@ -760,6 +774,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = {
.formats = SPDIF_FORMATS,
},
.ops = &hdmi_codec_spdif_dai_ops,
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = HDMI_RATES,
.formats = SPDIF_FORMATS,
},
.pcm_new = hdmi_codec_pcm_new,
};

View File

@ -1185,8 +1185,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
}
irq = platform_get_irq_byname(pdev, "mbhc_switch_int");
if (irq < 0)
if (irq < 0) {
dev_err(dev, "failed to get mbhc switch irq\n");
return irq;
}
ret = devm_request_threaded_irq(dev, irq, NULL,
pm8916_mbhc_switch_irq_handler,
@ -1198,8 +1200,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
if (priv->mbhc_btn_enabled) {
irq = platform_get_irq_byname(pdev, "mbhc_but_press_det");
if (irq < 0)
if (irq < 0) {
dev_err(dev, "failed to get button press irq\n");
return irq;
}
ret = devm_request_threaded_irq(dev, irq, NULL,
mbhc_btn_press_irq_handler,
@ -1210,8 +1214,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
dev_err(dev, "cannot request mbhc button press irq\n");
irq = platform_get_irq_byname(pdev, "mbhc_but_rel_det");
if (irq < 0)
if (irq < 0) {
dev_err(dev, "failed to get button release irq\n");
return irq;
}
ret = devm_request_threaded_irq(dev, irq, NULL,
mbhc_btn_release_irq_handler,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
/*
* ak4497.h -- audio driver for ak4497
*
* Copyright (C) 2016 Asahi Kasei Microdevices Corporation
* Copyright (C) 2017, NXP
*
* 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.
*
*/
#ifndef _RPMSG_AK4497_H
#define _RPMSG_AK4497_H
#define AK4497_00_CONTROL1 0x00
#define AK4497_01_CONTROL2 0x01
#define AK4497_02_CONTROL3 0x02
#define AK4497_03_LCHATT 0x03
#define AK4497_04_RCHATT 0x04
#define AK4497_05_CONTROL4 0x05
#define AK4497_06_DSD1 0x06
#define AK4497_07_CONTROL5 0x07
#define AK4497_08_SOUNDCONTROL 0x08
#define AK4497_09_DSD2 0x09
#define AK4497_0A_CONTROL7 0x0A
#define AK4497_0B_CONTROL8 0x0B
#define AK4497_0C_RESERVED 0x0C
#define AK4497_0D_RESERVED 0x0D
#define AK4497_0E_RESERVED 0x0E
#define AK4497_0F_RESERVED 0x0F
#define AK4497_10_RESERVED 0x10
#define AK4497_11_RESERVED 0x11
#define AK4497_12_RESERVED 0x12
#define AK4497_13_RESERVED 0x13
#define AK4497_14_RESERVED 0x14
#define AK4497_15_DFSREAD 0x15
#define AK4497_MAX_REGISTERS (AK4497_15_DFSREAD)
/* Bitfield Definitions */
/* AK4497_00_CONTROL1 (0x00) Fields */
#define AK4497_DIF 0x0E
#define AK4497_DIF_MSB_MODE (2 << 1)
#define AK4497_DIF_I2S_MODE (3 << 1)
#define AK4497_DIF_32BIT_MODE (4 << 1)
#define AK4497_DIF_16BIT_LSB (0 << 1)
#define AK4497_DIF_20BIT_LSB (1 << 1)
#define AK4497_DIF_24BIT_MSB (2 << 1)
#define AK4497_DIF_24BIT_I2S (3 << 1)
#define AK4497_DIF_24BIT_LSB (4 << 1)
#define AK4497_DIF_32BIT_LSB (5 << 1)
#define AK4497_DIF_32BIT_MSB (6 << 1)
#define AK4497_DIF_32BIT_I2S (7 << 1)
/* AK4497_02_CONTROL3 (0x02) Fields */
#define AK4497_DIF_DSD 0x80
#define AK4497_DIF_DSD_MODE (1 << 7)
/* AK4497_01_CONTROL2 (0x01) Fields */
/* AK4497_05_CONTROL4 (0x05) Fields */
#define AK4497_DFS 0x18
#define AK4497_DFS_48KHZ (0x0 << 3) // 30kHz to 54kHz
#define AK4497_DFS_96KHZ (0x1 << 3) // 54kHz to 108kHz
#define AK4497_DFS_192KHZ (0x2 << 3) // 120kHz to 216kHz
#define AK4497_DFS_384KHZ (0x0 << 3)
#define AK4497_DFS_768KHZ (0x1 << 3)
#define AK4497_DFS2 0x2
#define AK4497_DFS2_48KHZ (0x0 << 1) // 30kHz to 216kHz
#define AK4497_DFS2_384KHZ (0x1 << 1) // 384kHz, 768kHz to 108kHz
#define AK4497_DSDSEL0 0x1
#define AK4497_DSDSEL0_2MHZ 0x0
#define AK4497_DSDSEL0_5MHZ 0x1
#define AK4497_DSDSEL0_11MHZ 0x0
#define AK4497_DSDSEL0_22MHZ 0x1
#define AK4497_DSDSEL1 0x1
#define AK4497_DSDSEL1_2MHZ 0x0
#define AK4497_DSDSEL1_5MHZ 0x0
#define AK4497_DSDSEL1_11MHZ 0x1
#define AK4497_DSDSEL1_22MHZ 0x1
#endif

View File

@ -0,0 +1,752 @@
/*
* Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver
*
* Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
*
* Author: Nicolin Chen <Guangyu.Chen@freescale.com>
*
* 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.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "rpmsg_cs42xx8.h"
#include "../fsl/fsl_rpmsg_i2s.h"
#define CS42XX8_NUM_SUPPLIES 4
static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = {
"VA",
"VD",
"VLS",
"VLC",
};
#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE)
/* codec private data */
struct rpmsg_cs42xx8_priv {
struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES];
struct cs42xx8_driver_data *drvdata;
struct regmap *regmap;
struct clk *clk;
bool slave_mode[2];
unsigned long sysclk;
u32 tx_channels;
int rate[2];
int reset_gpio;
int audioindex;
struct fsl_rpmsg_i2s *rpmsg_i2s;
};
/* -127.5dB to 0dB with step of 0.5dB */
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
/* -64dB to 24dB with step of 0.5dB */
static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0);
static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" };
static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross",
"Soft Ramp", "Soft Ramp on Zero Cross" };
static const struct soc_enum adc1_single_enum =
SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single);
static const struct soc_enum adc2_single_enum =
SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single);
static const struct soc_enum adc3_single_enum =
SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single);
static const struct soc_enum dac_szc_enum =
SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc);
static const struct soc_enum adc_szc_enum =
SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc);
static const struct snd_kcontrol_new cs42xx8_snd_controls[] = {
SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1,
CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv),
SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3,
CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv),
SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5,
CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv),
SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7,
CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv),
SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1,
CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv),
SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3,
CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv),
SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0),
SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0),
SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0),
SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0),
SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0),
SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0),
SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1),
SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0),
SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum),
SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum),
SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0),
SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum),
SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0),
SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0),
SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0),
SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum),
};
static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = {
SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5,
CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv),
SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0),
SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum),
};
static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1),
SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1),
SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1),
SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1),
SND_SOC_DAPM_OUTPUT("AOUT1L"),
SND_SOC_DAPM_OUTPUT("AOUT1R"),
SND_SOC_DAPM_OUTPUT("AOUT2L"),
SND_SOC_DAPM_OUTPUT("AOUT2R"),
SND_SOC_DAPM_OUTPUT("AOUT3L"),
SND_SOC_DAPM_OUTPUT("AOUT3R"),
SND_SOC_DAPM_OUTPUT("AOUT4L"),
SND_SOC_DAPM_OUTPUT("AOUT4R"),
SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1),
SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1),
SND_SOC_DAPM_INPUT("AIN1L"),
SND_SOC_DAPM_INPUT("AIN1R"),
SND_SOC_DAPM_INPUT("AIN2L"),
SND_SOC_DAPM_INPUT("AIN2R"),
};
static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1),
SND_SOC_DAPM_INPUT("AIN3L"),
SND_SOC_DAPM_INPUT("AIN3R"),
};
static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
/* Playback */
{ "AOUT1L", NULL, "DAC1" },
{ "AOUT1R", NULL, "DAC1" },
{ "AOUT2L", NULL, "DAC2" },
{ "AOUT2R", NULL, "DAC2" },
{ "AOUT3L", NULL, "DAC3" },
{ "AOUT3R", NULL, "DAC3" },
{ "AOUT4L", NULL, "DAC4" },
{ "AOUT4R", NULL, "DAC4" },
/* Capture */
{ "ADC1", NULL, "AIN1L" },
{ "ADC1", NULL, "AIN1R" },
{ "ADC2", NULL, "AIN2L" },
{ "ADC2", NULL, "AIN2R" },
};
static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
/* Capture */
{ "ADC3", NULL, "AIN3L" },
{ "ADC3", NULL, "AIN3R" },
};
struct cs42xx8_ratios {
unsigned int mfreq;
unsigned int min_mclk;
unsigned int max_mclk;
unsigned int ratio[3];
};
static const struct cs42xx8_ratios cs42xx8_ratios[] = {
{ 0, 1029000, 12800000, {256, 128, 64} },
{ 2, 1536000, 19200000, {384, 192, 96} },
{ 4, 2048000, 25600000, {512, 256, 128} },
{ 6, 3072000, 38400000, {768, 384, 192} },
{ 8, 4096000, 51200000, {1024, 512, 256} },
};
static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_component *component = dai->component;
struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
cs42xx8->sysclk = freq;
return 0;
}
static int cs42xx8_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int format)
{
struct snd_soc_component *component = dai->component;
struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
u32 val;
/* Set DAI format */
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ;
break;
case SND_SOC_DAIFMT_I2S:
val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S;
break;
case SND_SOC_DAIFMT_RIGHT_J:
val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ;
break;
case SND_SOC_DAIFMT_DSP_A:
val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM;
break;
default:
dev_err(component->dev, "unsupported dai format\n");
return -EINVAL;
}
regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF,
CS42XX8_INTF_DAC_DIF_MASK |
CS42XX8_INTF_ADC_DIF_MASK, val);
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;
}
static int cs42xx8_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 rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
u32 ratio[2];
u32 rate[2];
u32 fm[2];
u32 i, val, mask;
bool condition1, condition2;
if (tx)
cs42xx8->tx_channels = params_channels(params);
rate[tx] = params_rate(params);
rate[!tx] = cs42xx8->rate[!tx];
ratio[tx] = rate[tx] > 0 ? cs42xx8->sysclk / rate[tx] : 0;
ratio[!tx] = rate[!tx] > 0 ? cs42xx8->sysclk / rate[!tx] : 0;
for (i = 0; i < 2; i++) {
if (cs42xx8->slave_mode[i]) {
fm[i] = CS42XX8_FM_AUTO;
} else {
if (rate[i] < 50000)
fm[i] = CS42XX8_FM_SINGLE;
else if (rate[i] > 50000 && rate[i] < 100000)
fm[i] = CS42XX8_FM_DOUBLE;
else if (rate[i] > 100000 && rate[i] < 200000)
fm[i] = CS42XX8_FM_QUAD;
else {
dev_err(component->dev,
"unsupported sample rate or rate combine\n");
return -EINVAL;
}
}
}
for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
condition1 = ((fm[tx] == CS42XX8_FM_AUTO) ?
(cs42xx8_ratios[i].ratio[0] == ratio[tx] ||
cs42xx8_ratios[i].ratio[1] == ratio[tx] ||
cs42xx8_ratios[i].ratio[2] == ratio[tx]) :
(cs42xx8_ratios[i].ratio[fm[tx]] == ratio[tx])) &&
cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk;
if (ratio[tx] <= 0)
condition1 = true;
condition2 = ((fm[!tx] == CS42XX8_FM_AUTO) ?
(cs42xx8_ratios[i].ratio[0] == ratio[!tx] ||
cs42xx8_ratios[i].ratio[1] == ratio[!tx] ||
cs42xx8_ratios[i].ratio[2] == ratio[!tx]) :
(cs42xx8_ratios[i].ratio[fm[!tx]] == ratio[!tx]));
if (ratio[!tx] <= 0)
condition2 = true;
if (condition1 && condition2)
break;
}
if (i == ARRAY_SIZE(cs42xx8_ratios)) {
dev_err(component->dev, "unsupported sysclk ratio\n");
return -EINVAL;
}
cs42xx8->rate[tx] = params_rate(params);
mask = CS42XX8_FUNCMOD_MFREQ_MASK;
val = cs42xx8_ratios[i].mfreq;
regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask,
CS42XX8_FUNCMOD_xC_FM(tx, fm[tx]) | val);
return 0;
}
static int cs42xx8_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
cs42xx8->rate[tx] = 0;
regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
CS42XX8_FUNCMOD_xC_FM_MASK(tx),
CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO));
return 0;
}
static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_component *component = dai->component;
struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
u8 dac_unmute = cs42xx8->tx_channels ?
~((0x1 << cs42xx8->tx_channels) - 1) : 0;
regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE,
mute ? CS42XX8_DACMUTE_ALL : dac_unmute);
return 0;
}
static const struct snd_soc_dai_ops cs42xx8_dai_ops = {
.set_fmt = cs42xx8_set_dai_fmt,
.set_sysclk = cs42xx8_set_dai_sysclk,
.hw_params = cs42xx8_hw_params,
.hw_free = cs42xx8_hw_free,
.digital_mute = cs42xx8_digital_mute,
};
static struct snd_soc_dai_driver cs42xx8_dai = {
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = CS42XX8_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = CS42XX8_FORMATS,
},
.ops = &cs42xx8_dai_ops,
};
static const struct reg_default cs42xx8_reg[] = {
{ 0x02, 0x00 }, /* Power Control */
{ 0x03, 0xF0 }, /* Functional Mode */
{ 0x04, 0x46 }, /* Interface Formats */
{ 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */
{ 0x06, 0x10 }, /* Transition Control */
{ 0x07, 0x00 }, /* DAC Channel Mute */
{ 0x08, 0x00 }, /* Volume Control AOUT1 */
{ 0x09, 0x00 }, /* Volume Control AOUT2 */
{ 0x0a, 0x00 }, /* Volume Control AOUT3 */
{ 0x0b, 0x00 }, /* Volume Control AOUT4 */
{ 0x0c, 0x00 }, /* Volume Control AOUT5 */
{ 0x0d, 0x00 }, /* Volume Control AOUT6 */
{ 0x0e, 0x00 }, /* Volume Control AOUT7 */
{ 0x0f, 0x00 }, /* Volume Control AOUT8 */
{ 0x10, 0x00 }, /* DAC Channel Invert */
{ 0x11, 0x00 }, /* Volume Control AIN1 */
{ 0x12, 0x00 }, /* Volume Control AIN2 */
{ 0x13, 0x00 }, /* Volume Control AIN3 */
{ 0x14, 0x00 }, /* Volume Control AIN4 */
{ 0x15, 0x00 }, /* Volume Control AIN5 */
{ 0x16, 0x00 }, /* Volume Control AIN6 */
{ 0x17, 0x00 }, /* ADC Channel Invert */
{ 0x18, 0x00 }, /* Status Control */
{ 0x1a, 0x00 }, /* Status Mask */
{ 0x1b, 0x00 }, /* MUTEC Pin Control */
};
static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case CS42XX8_STATUS:
return true;
default:
return false;
}
}
static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case CS42XX8_CHIPID:
case CS42XX8_STATUS:
return false;
default:
return true;
}
}
static int rpmsg_cs42xx8_read(void *context, unsigned int reg, unsigned int *val)
{
struct rpmsg_cs42xx8_priv *cs42xx8 = context;
struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s;
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg;
int err, reg_val;
mutex_lock(&i2s_info->i2c_lock);
rpmsg->param.audioindex = cs42xx8->audioindex;
rpmsg->param.buffer_addr = reg;
rpmsg->header.cmd = GET_CODEC_VALUE;
err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info);
reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data;
mutex_unlock(&i2s_info->i2c_lock);
if (err)
return -EIO;
*val = reg_val;
return 0;
}
static int rpmsg_cs42xx8_write(void *context, unsigned int reg, unsigned int val)
{
struct rpmsg_cs42xx8_priv *cs42xx8 = context;
struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s;
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg;
int err;
mutex_lock(&i2s_info->i2c_lock);
rpmsg->param.audioindex = cs42xx8->audioindex;
rpmsg->param.buffer_addr = reg;
rpmsg->param.buffer_size = val;
rpmsg->header.cmd = SET_CODEC_VALUE;
err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info);
mutex_unlock(&i2s_info->i2c_lock);
if (err)
return -EIO;
return 0;
}
static struct regmap_config rpmsg_cs42xx8_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = CS42XX8_LASTREG,
.reg_defaults = cs42xx8_reg,
.num_reg_defaults = ARRAY_SIZE(cs42xx8_reg),
.volatile_reg = cs42xx8_volatile_register,
.writeable_reg = cs42xx8_writeable_register,
.cache_type = REGCACHE_RBTREE,
.reg_read = rpmsg_cs42xx8_read,
.reg_write = rpmsg_cs42xx8_write,
};
static int cs42xx8_codec_probe(struct snd_soc_component *component)
{
struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
switch (cs42xx8->drvdata->num_adcs) {
case 3:
snd_soc_add_component_controls(component, cs42xx8_adc3_snd_controls,
ARRAY_SIZE(cs42xx8_adc3_snd_controls));
snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets,
ARRAY_SIZE(cs42xx8_adc3_dapm_widgets));
snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes,
ARRAY_SIZE(cs42xx8_adc3_dapm_routes));
break;
default:
break;
}
/* 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;
}
static const struct snd_soc_component_driver cs42xx8_driver = {
.probe = cs42xx8_codec_probe,
.controls = cs42xx8_snd_controls,
.num_controls = ARRAY_SIZE(cs42xx8_snd_controls),
.dapm_widgets = cs42xx8_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets),
.dapm_routes = cs42xx8_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes),
.suspend_bias_off = 1,
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int rpmsg_cs42xx8_codec_probe(struct platform_device *pdev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent);
struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data;
struct rpmsg_cs42xx8_priv *cs42xx8;
struct device *dev = &pdev->dev;
int ret, val, i;
cs42xx8 = devm_kzalloc(&pdev->dev, sizeof(*cs42xx8), GFP_KERNEL);
if (cs42xx8 == NULL)
return -ENOMEM;
cs42xx8->regmap = devm_regmap_init(&pdev->dev, NULL,
cs42xx8, &rpmsg_cs42xx8_regmap_config);
if (IS_ERR(cs42xx8->regmap))
return PTR_ERR(cs42xx8->regmap);
dev_set_drvdata(&pdev->dev, cs42xx8);
cs42xx8->drvdata = devm_kzalloc(&pdev->dev,
sizeof(struct cs42xx8_driver_data), GFP_KERNEL);
if (!cs42xx8->drvdata)
return -ENOMEM;
cs42xx8->rpmsg_i2s = rpmsg_i2s;
memcpy(cs42xx8->drvdata->name, pdata->name, 32);
cs42xx8->drvdata->num_adcs = pdata->num_adcs;
cs42xx8->audioindex = pdata->audioindex;
cs42xx8->reset_gpio = of_get_named_gpio(pdev->dev.parent->of_node,
"reset-gpio", 0);
if (gpio_is_valid(cs42xx8->reset_gpio)) {
ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio,
GPIOF_OUT_INIT_LOW, "cs42xx8 reset");
if (ret) {
dev_err(dev, "unable to get reset gpio\n");
return ret;
}
gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
}
cs42xx8->clk = devm_clk_get(pdev->dev.parent, "mclk");
if (IS_ERR(cs42xx8->clk)) {
dev_err(dev, "failed to get the clock: %ld\n",
PTR_ERR(cs42xx8->clk));
return -EINVAL;
}
cs42xx8->sysclk = clk_get_rate(cs42xx8->clk);
if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txm-rxs")) {
/* 0 -- rx, 1 -- tx */
cs42xx8->slave_mode[0] = true;
cs42xx8->slave_mode[1] = false;
}
if (of_property_read_bool(pdev->dev.parent->of_node, "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];
ret = devm_regulator_bulk_get(pdev->dev.parent,
ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies);
if (ret) {
dev_err(dev, "failed to request supplies: %d\n", ret);
return ret;
}
ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
cs42xx8->supplies);
if (ret) {
dev_err(dev, "failed to enable supplies: %d\n", ret);
return ret;
}
/* Make sure hardware reset done */
usleep_range(5000, 10000);
/*
* We haven't marked the chip revision as volatile due to
* sharing a register with the right input volume; explicitly
* bypass the cache to read it.
*/
regcache_cache_bypass(cs42xx8->regmap, true);
/* Validate the chip ID */
ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val);
if (ret < 0) {
dev_err(dev, "failed to get device ID, ret = %d", ret);
goto err_enable;
}
/* The top four bits of the chip ID should be 0000 */
if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) {
dev_err(dev, "unmatched chip ID: %d\n",
(val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4);
ret = -EINVAL;
goto err_enable;
}
dev_info(dev, "found device, revision %X\n",
val & CS42XX8_CHIPID_REV_ID_MASK);
regcache_cache_bypass(cs42xx8->regmap, false);
cs42xx8_dai.name = cs42xx8->drvdata->name;
/* Each adc supports stereo input */
cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2;
pm_runtime_enable(dev);
pm_request_idle(dev);
ret = devm_snd_soc_register_component(dev, &cs42xx8_driver, &cs42xx8_dai, 1);
if (ret) {
dev_err(dev, "failed to register codec:%d\n", ret);
goto err_enable;
}
regcache_cache_only(cs42xx8->regmap, true);
err_enable:
regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
cs42xx8->supplies);
return ret;
}
#ifdef CONFIG_PM
static int cs42xx8_runtime_resume(struct device *dev)
{
struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(cs42xx8->clk);
if (ret) {
dev_err(dev, "failed to enable mclk: %d\n", ret);
return ret;
}
if (gpio_is_valid(cs42xx8->reset_gpio)) {
gpio_set_value_cansleep(cs42xx8->reset_gpio, 0);
gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
}
ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
cs42xx8->supplies);
if (ret) {
dev_err(dev, "failed to enable supplies: %d\n", ret);
goto err_clk;
}
regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
CS42XX8_PWRCTL_PDN_MASK, 1);
/* Make sure hardware reset done */
usleep_range(5000, 10000);
regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
CS42XX8_PWRCTL_PDN_MASK, 0);
regcache_cache_only(cs42xx8->regmap, false);
ret = regcache_sync(cs42xx8->regmap);
if (ret) {
dev_err(dev, "failed to sync regmap: %d\n", ret);
goto err_bulk;
}
return 0;
err_bulk:
regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
cs42xx8->supplies);
err_clk:
clk_disable_unprepare(cs42xx8->clk);
return ret;
}
static int cs42xx8_runtime_suspend(struct device *dev)
{
struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
regcache_cache_only(cs42xx8->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
cs42xx8->supplies);
clk_disable_unprepare(cs42xx8->clk);
return 0;
}
#endif
const struct dev_pm_ops rpmsg_cs42xx8_pm = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL)
};
static int rpmsg_cs42xx8_codec_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver rpmsg_cs42xx8_codec_driver = {
.driver = {
.name = RPMSG_CODEC_DRV_NAME_CS42888,
.pm = &rpmsg_cs42xx8_pm,
},
.probe = rpmsg_cs42xx8_codec_probe,
.remove = rpmsg_cs42xx8_codec_remove,
};
module_platform_driver(rpmsg_cs42xx8_codec_driver);
MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver");
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,232 @@
/*
* cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file
*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
*
* Author: Nicolin Chen <Guangyu.Chen@freescale.com>
*
* 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.
*/
#ifndef _RPMSG_CS42XX8_H
#define _RPMSG_CS42XX8_H
struct cs42xx8_driver_data {
char name[32];
int num_adcs;
};
/* CS42888 register map */
#define CS42XX8_CHIPID 0x01 /* Chip ID */
#define CS42XX8_PWRCTL 0x02 /* Power Control */
#define CS42XX8_FUNCMOD 0x03 /* Functional Mode */
#define CS42XX8_INTF 0x04 /* Interface Formats */
#define CS42XX8_ADCCTL 0x05 /* ADC Control */
#define CS42XX8_TXCTL 0x06 /* Transition Control */
#define CS42XX8_DACMUTE 0x07 /* DAC Mute Control */
#define CS42XX8_VOLAOUT1 0x08 /* Volume Control AOUT1 */
#define CS42XX8_VOLAOUT2 0x09 /* Volume Control AOUT2 */
#define CS42XX8_VOLAOUT3 0x0A /* Volume Control AOUT3 */
#define CS42XX8_VOLAOUT4 0x0B /* Volume Control AOUT4 */
#define CS42XX8_VOLAOUT5 0x0C /* Volume Control AOUT5 */
#define CS42XX8_VOLAOUT6 0x0D /* Volume Control AOUT6 */
#define CS42XX8_VOLAOUT7 0x0E /* Volume Control AOUT7 */
#define CS42XX8_VOLAOUT8 0x0F /* Volume Control AOUT8 */
#define CS42XX8_DACINV 0x10 /* DAC Channel Invert */
#define CS42XX8_VOLAIN1 0x11 /* Volume Control AIN1 */
#define CS42XX8_VOLAIN2 0x12 /* Volume Control AIN2 */
#define CS42XX8_VOLAIN3 0x13 /* Volume Control AIN3 */
#define CS42XX8_VOLAIN4 0x14 /* Volume Control AIN4 */
#define CS42XX8_VOLAIN5 0x15 /* Volume Control AIN5 */
#define CS42XX8_VOLAIN6 0x16 /* Volume Control AIN6 */
#define CS42XX8_ADCINV 0x17 /* ADC Channel Invert */
#define CS42XX8_STATUSCTL 0x18 /* Status Control */
#define CS42XX8_STATUS 0x19 /* Status */
#define CS42XX8_STATUSM 0x1A /* Status Mask */
#define CS42XX8_MUTEC 0x1B /* MUTEC Pin Control */
#define CS42XX8_FIRSTREG CS42XX8_CHIPID
#define CS42XX8_LASTREG CS42XX8_MUTEC
#define CS42XX8_NUMREGS (CS42XX8_LASTREG - CS42XX8_FIRSTREG + 1)
#define CS42XX8_I2C_INCR 0x80
/* Chip I.D. and Revision Register (Address 01h) */
#define CS42XX8_CHIPID_CHIP_ID_MASK 0xF0
#define CS42XX8_CHIPID_REV_ID_MASK 0x0F
/* Power Control (Address 02h) */
#define CS42XX8_PWRCTL_PDN_ADC3_SHIFT 7
#define CS42XX8_PWRCTL_PDN_ADC3_MASK (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT)
#define CS42XX8_PWRCTL_PDN_ADC3 (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT)
#define CS42XX8_PWRCTL_PDN_ADC2_SHIFT 6
#define CS42XX8_PWRCTL_PDN_ADC2_MASK (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT)
#define CS42XX8_PWRCTL_PDN_ADC2 (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT)
#define CS42XX8_PWRCTL_PDN_ADC1_SHIFT 5
#define CS42XX8_PWRCTL_PDN_ADC1_MASK (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT)
#define CS42XX8_PWRCTL_PDN_ADC1 (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC4_SHIFT 4
#define CS42XX8_PWRCTL_PDN_DAC4_MASK (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC4 (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC3_SHIFT 3
#define CS42XX8_PWRCTL_PDN_DAC3_MASK (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC3 (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC2_SHIFT 2
#define CS42XX8_PWRCTL_PDN_DAC2_MASK (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC2 (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC1_SHIFT 1
#define CS42XX8_PWRCTL_PDN_DAC1_MASK (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT)
#define CS42XX8_PWRCTL_PDN_DAC1 (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT)
#define CS42XX8_PWRCTL_PDN_SHIFT 0
#define CS42XX8_PWRCTL_PDN_MASK (1 << CS42XX8_PWRCTL_PDN_SHIFT)
#define CS42XX8_PWRCTL_PDN (1 << CS42XX8_PWRCTL_PDN_SHIFT)
/* Functional Mode (Address 03h) */
#define CS42XX8_FUNCMOD_DAC_FM_SHIFT 6
#define CS42XX8_FUNCMOD_DAC_FM_WIDTH 2
#define CS42XX8_FUNCMOD_DAC_FM_MASK (((1 << CS42XX8_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_DAC_FM_SHIFT)
#define CS42XX8_FUNCMOD_DAC_FM(v) ((v) << CS42XX8_FUNCMOD_DAC_FM_SHIFT)
#define CS42XX8_FUNCMOD_ADC_FM_SHIFT 4
#define CS42XX8_FUNCMOD_ADC_FM_WIDTH 2
#define CS42XX8_FUNCMOD_ADC_FM_MASK (((1 << CS42XX8_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_ADC_FM_SHIFT)
#define CS42XX8_FUNCMOD_ADC_FM(v) ((v) << CS42XX8_FUNCMOD_ADC_FM_SHIFT)
#define CS42XX8_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42XX8_FUNCMOD_DAC_FM_MASK : CS42XX8_FUNCMOD_ADC_FM_MASK)
#define CS42XX8_FUNCMOD_xC_FM(x, v) ((x) ? CS42XX8_FUNCMOD_DAC_FM(v) : CS42XX8_FUNCMOD_ADC_FM(v))
#define CS42XX8_FUNCMOD_MFREQ_SHIFT 1
#define CS42XX8_FUNCMOD_MFREQ_WIDTH 3
#define CS42XX8_FUNCMOD_MFREQ_MASK (((1 << CS42XX8_FUNCMOD_MFREQ_WIDTH) - 1) << CS42XX8_FUNCMOD_MFREQ_SHIFT)
#define CS42XX8_FUNCMOD_MFREQ_256(s) ((0 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
#define CS42XX8_FUNCMOD_MFREQ_384(s) ((1 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
#define CS42XX8_FUNCMOD_MFREQ_512(s) ((2 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
#define CS42XX8_FUNCMOD_MFREQ_768(s) ((3 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
#define CS42XX8_FUNCMOD_MFREQ_1024(s) ((4 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
#define CS42XX8_FM_SINGLE 0
#define CS42XX8_FM_DOUBLE 1
#define CS42XX8_FM_QUAD 2
#define CS42XX8_FM_AUTO 3
/* Interface Formats (Address 04h) */
#define CS42XX8_INTF_FREEZE_SHIFT 7
#define CS42XX8_INTF_FREEZE_MASK (1 << CS42XX8_INTF_FREEZE_SHIFT)
#define CS42XX8_INTF_FREEZE (1 << CS42XX8_INTF_FREEZE_SHIFT)
#define CS42XX8_INTF_AUX_DIF_SHIFT 6
#define CS42XX8_INTF_AUX_DIF_MASK (1 << CS42XX8_INTF_AUX_DIF_SHIFT)
#define CS42XX8_INTF_AUX_DIF (1 << CS42XX8_INTF_AUX_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_SHIFT 3
#define CS42XX8_INTF_DAC_DIF_WIDTH 3
#define CS42XX8_INTF_DAC_DIF_MASK (((1 << CS42XX8_INTF_DAC_DIF_WIDTH) - 1) << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_LEFTJ (0 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_I2S (1 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_SHIFT 0
#define CS42XX8_INTF_ADC_DIF_WIDTH 3
#define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_LEFTJ (0 << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_I2S (1 << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT)
#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT)
/* ADC Control & DAC De-Emphasis (Address 05h) */
#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7
#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT)
#define CS42XX8_ADCCTL_ADC_HPF_FREEZE (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT)
#define CS42XX8_ADCCTL_DAC_DEM_SHIFT 5
#define CS42XX8_ADCCTL_DAC_DEM_MASK (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT)
#define CS42XX8_ADCCTL_DAC_DEM (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT)
#define CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT 4
#define CS42XX8_ADCCTL_ADC1_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT)
#define CS42XX8_ADCCTL_ADC1_SINGLE (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT)
#define CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT 3
#define CS42XX8_ADCCTL_ADC2_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT)
#define CS42XX8_ADCCTL_ADC2_SINGLE (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT)
#define CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT 2
#define CS42XX8_ADCCTL_ADC3_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT)
#define CS42XX8_ADCCTL_ADC3_SINGLE (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT)
#define CS42XX8_ADCCTL_AIN5_MUX_SHIFT 1
#define CS42XX8_ADCCTL_AIN5_MUX_MASK (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT)
#define CS42XX8_ADCCTL_AIN5_MUX (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT)
#define CS42XX8_ADCCTL_AIN6_MUX_SHIFT 0
#define CS42XX8_ADCCTL_AIN6_MUX_MASK (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT)
#define CS42XX8_ADCCTL_AIN6_MUX (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT)
/* Transition Control (Address 06h) */
#define CS42XX8_TXCTL_DAC_SNGVOL_SHIFT 7
#define CS42XX8_TXCTL_DAC_SNGVOL_MASK (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT)
#define CS42XX8_TXCTL_DAC_SNGVOL (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT)
#define CS42XX8_TXCTL_DAC_SZC_SHIFT 5
#define CS42XX8_TXCTL_DAC_SZC_WIDTH 2
#define CS42XX8_TXCTL_DAC_SZC_MASK (((1 << CS42XX8_TXCTL_DAC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_DAC_SZC_SHIFT)
#define CS42XX8_TXCTL_DAC_SZC_IC (0 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
#define CS42XX8_TXCTL_DAC_SZC_ZC (1 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
#define CS42XX8_TXCTL_DAC_SZC_SR (2 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
#define CS42XX8_TXCTL_DAC_SZC_SRZC (3 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
#define CS42XX8_TXCTL_AMUTE_SHIFT 4
#define CS42XX8_TXCTL_AMUTE_MASK (1 << CS42XX8_TXCTL_AMUTE_SHIFT)
#define CS42XX8_TXCTL_AMUTE (1 << CS42XX8_TXCTL_AMUTE_SHIFT)
#define CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT 3
#define CS42XX8_TXCTL_MUTE_ADC_SP_MASK (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT)
#define CS42XX8_TXCTL_MUTE_ADC_SP (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT)
#define CS42XX8_TXCTL_ADC_SNGVOL_SHIFT 2
#define CS42XX8_TXCTL_ADC_SNGVOL_MASK (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT)
#define CS42XX8_TXCTL_ADC_SNGVOL (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT)
#define CS42XX8_TXCTL_ADC_SZC_SHIFT 0
#define CS42XX8_TXCTL_ADC_SZC_MASK (((1 << CS42XX8_TXCTL_ADC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_ADC_SZC_SHIFT)
#define CS42XX8_TXCTL_ADC_SZC_IC (0 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
#define CS42XX8_TXCTL_ADC_SZC_ZC (1 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
#define CS42XX8_TXCTL_ADC_SZC_SR (2 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
#define CS42XX8_TXCTL_ADC_SZC_SRZC (3 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
/* DAC Channel Mute (Address 07h) */
#define CS42XX8_DACMUTE_AOUT(n) (0x1 << n)
#define CS42XX8_DACMUTE_ALL 0xff
/* Status Control (Address 18h)*/
#define CS42XX8_STATUSCTL_INI_SHIFT 2
#define CS42XX8_STATUSCTL_INI_WIDTH 2
#define CS42XX8_STATUSCTL_INI_MASK (((1 << CS42XX8_STATUSCTL_INI_WIDTH) - 1) << CS42XX8_STATUSCTL_INI_SHIFT)
#define CS42XX8_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42XX8_STATUSCTL_INI_SHIFT)
#define CS42XX8_STATUSCTL_INT_ACTIVE_LOW (1 << CS42XX8_STATUSCTL_INI_SHIFT)
#define CS42XX8_STATUSCTL_INT_OPEN_DRAIN (2 << CS42XX8_STATUSCTL_INI_SHIFT)
/* Status (Address 19h)*/
#define CS42XX8_STATUS_DAC_CLK_ERR_SHIFT 4
#define CS42XX8_STATUS_DAC_CLK_ERR_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_SHIFT)
#define CS42XX8_STATUS_ADC_CLK_ERR_SHIFT 3
#define CS42XX8_STATUS_ADC_CLK_ERR_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_SHIFT)
#define CS42XX8_STATUS_ADC3_OVFL_SHIFT 2
#define CS42XX8_STATUS_ADC3_OVFL_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_SHIFT)
#define CS42XX8_STATUS_ADC2_OVFL_SHIFT 1
#define CS42XX8_STATUS_ADC2_OVFL_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_SHIFT)
#define CS42XX8_STATUS_ADC1_OVFL_SHIFT 0
#define CS42XX8_STATUS_ADC1_OVFL_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_SHIFT)
/* Status Mask (Address 1Ah) */
#define CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT 4
#define CS42XX8_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT)
#define CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT 3
#define CS42XX8_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT)
#define CS42XX8_STATUS_ADC3_OVFL_M_SHIFT 2
#define CS42XX8_STATUS_ADC3_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_M_SHIFT)
#define CS42XX8_STATUS_ADC2_OVFL_M_SHIFT 1
#define CS42XX8_STATUS_ADC2_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_M_SHIFT)
#define CS42XX8_STATUS_ADC1_OVFL_M_SHIFT 0
#define CS42XX8_STATUS_ADC1_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_M_SHIFT)
/* MUTEC Pin Control (Address 1Bh) */
#define CS42XX8_MUTEC_MCPOLARITY_SHIFT 1
#define CS42XX8_MUTEC_MCPOLARITY_MASK (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
#define CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT 0
#define CS42XX8_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT)
#define CS42XX8_MUTEC_MUTEC_ACTIVE (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT)
#endif /* _CS42XX8_H */

File diff suppressed because it is too large Load Diff

View File

@ -199,9 +199,28 @@ static int si476x_codec_hw_params(struct snd_pcm_substream *substream,
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 = {

View File

@ -1108,8 +1108,10 @@ static int twl6040_probe(struct snd_soc_component *component)
priv->component = component;
priv->plug_irq = platform_get_irq(pdev, 0);
if (priv->plug_irq < 0)
if (priv->plug_irq < 0) {
dev_err(component->dev, "invalid irq: %d\n", priv->plug_irq);
return priv->plug_irq;
}
INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work);

View File

@ -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,

View File

@ -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;

View File

@ -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)
};

View File

@ -65,6 +65,14 @@ config SND_SOC_FSL_ESAI
This option is only useful for out-of-tree drivers since
in-tree drivers select it automatically.
config SND_SOC_FSL_DAI
tristate "Generic FSL DAI support for Sound Open Firmware"
help
Say Y if you want to enable generic FSL DAI support to be used
with Sound Open Firmware. This module takes care of enabling
clocks, power domain, pinctrl for FSL DAIs. The rest of DAI
control is taken care of by SOF firmware.
config SND_SOC_FSL_MICFIL
tristate "Pulse Density Modulation Microphone Interface (MICFIL) module support"
select REGMAP_MMIO
@ -74,13 +82,47 @@ 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.
config SND_SOC_FSL_RPMSG_I2S
tristate "I2S base on the RPMSG support"
depends on HAVE_IMX_RPMSG
help
Say Y if you want to add rpmsg i2s support for the Freescale CPUs.
which is depends on the rpmsg.
This option is only useful for out-of-tree drivers since
in-tree drivers select it automatically.
config SND_SOC_FSL_UTILS
tristate
config SND_SOC_FSL_HDMI
tristate
config SND_SOC_IMX_PCM_DMA
tristate
select SND_SOC_GENERIC_DMAENGINE_PCM
config SND_SOC_IMX_PCM_RPMSG
tristate
depends on HAVE_IMX_RPMSG
select SND_SOC_GENERIC_DMAENGINE_PCM
config SND_SOC_IMX_AUDMUX
tristate "Digital Audio Mux module support"
help
@ -98,7 +140,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.
@ -201,6 +243,11 @@ config SND_SOC_IMX_SSI
tristate
select SND_SOC_FSL_UTILS
config SND_SOC_IMX_HDMI_DMA
bool
select SND_SOC_GENERIC_DMAENGINE_PCM
select SND_SOC_IMX_PCM_DMA
comment "SoC Audio support for Freescale i.MX boards:"
config SND_MXC_SOC_WM1133_EV1
@ -249,6 +296,149 @@ 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_RPMSG
tristate "SoC Audio support for i.MX boards with rpmsg"
depends on HAVE_IMX_RPMSG
select SND_SOC_IMX_PCM_RPMSG
select SND_SOC_FSL_RPMSG_I2S
select SND_SOC_RPMSG_WM8960
select SND_SOC_RPMSG_AK4497
select SND_SOC_RPMSG_CS42XX8
help
SoC Audio support for i.MX boards with rpmsg.
There should be rpmsg devices defined in other core
Say Y if you want to add support for SoC audio on an i.MX board with
a rpmsg devices.
config SND_SOC_IMX_ES8328
tristate "SoC Audio support for i.MX boards with the ES8328 codec"
depends on OF && (I2C || SPI)
@ -272,6 +462,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
@ -302,7 +500,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
@ -314,6 +512,59 @@ 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
select SND_SOC_IMX_HDMI_DMA
select SND_SOC_FSL_HDMI
select SND_SOC_HDMI_CODEC
help
SoC Audio support for i.MX boards with HDMI audio
Say Y if you want to add support for SoC audio on an i.MX board with
IMX HDMI.
config SND_SOC_IMX_CDNHDMI
tristate "SoC Audio support for i.MX boards with CDN HDMI port"
depends on DRM_IMX_CDNS_MHDP
select SND_SOC_IMX_PCM_DMA
select SND_SOC_FSL_SAI
select SND_SOC_HDMI_CODEC
help
SoC Audio support for i.MX boards with CDN HDMI audio
Say Y if you want to add support for SoC audio on an i.MX board with
IMX CDN HDMI.
endif # SND_IMX_SOC
endmenu

View File

@ -13,27 +13,40 @@ 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
snd-soc-fsl-spdif-objs := fsl_spdif.o
snd-soc-fsl-esai-objs := fsl_esai.o
snd-soc-fsl-dai-objs := fsl_dai.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
snd-soc-fsl-rpmsg-i2s-objs := fsl_rpmsg_i2s.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
obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o
obj-$(CONFIG_SND_SOC_FSL_DAI) += snd-soc-fsl-dai.o
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
obj-$(CONFIG_SND_SOC_FSL_RPMSG_I2S) += snd-soc-fsl-rpmsg-i2s.o
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
@ -51,7 +64,10 @@ 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
obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
@ -59,17 +75,53 @@ 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
snd-soc-imx-rpmsg-objs := imx-rpmsg.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
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_RPMSG) += snd-soc-imx-rpmsg.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
AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp

View File

@ -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, &reg);
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)
@ -885,8 +1069,10 @@ static int fsl_asrc_probe(struct platform_device *pdev)
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
if (irq < 0) {
dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
}
ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0,
dev_name(&pdev->dev), asrc_priv);
@ -920,14 +1106,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;
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_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;
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 +1185,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 +1194,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 +1208,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 +1229,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 = (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 +1276,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 +1296,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);
ret = pm_runtime_force_resume(dev);
/* Restore all registers */
regcache_cache_only(asrc_priv->regmap, false);
regcache_sync(asrc_priv->regmap);
fsl_asrc_m2m_resume(asrc_priv);
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);
return 0;
return ret;
}
#endif /* CONFIG_PM_SLEEP */
@ -1072,12 +1326,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,

View File

@ -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 */

View File

@ -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;

1046
sound/soc/fsl/fsl_asrc_m2m.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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;
};

276
sound/soc/fsl/fsl_dai.c Normal file
View File

@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0
//
// Freescale Generic DAI driver for DSP
//
// Copyright 2019 NXP
// Author: Daniel Baluta <daniel.baluta@nxp.com>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
enum fsl_dai_type {
FSL_DAI_TYPE_NONE,
FSL_DAI_TYPE_SAI,
FSL_DAI_TYPE_ESAI,
};
#define FSL_DAI_ESAI_CLK_NUM 4
static const char *esai_clks[FSL_DAI_ESAI_CLK_NUM] = {
"core",
"extal",
"fsys",
"spba",
};
#define FSL_DAI_SAI_CLK_NUM 5
static const char *sai_clks[FSL_DAI_SAI_CLK_NUM] = {
"bus",
"mclk0",
"mclk1",
"mclk2",
"mclk3",
};
struct fsl_dai {
struct platform_device *pdev;
/* DAI clocks */
struct clk **clks;
const char **clk_names;
int num_clks;
/* Power Domain handling */
int num_domains;
struct device **pd_dev;
struct device_link **link;
/* DAIS */
struct snd_soc_dai_driver *dai_drv;
int num_drv;
};
static struct snd_soc_dai_driver fsl_esai_dai = {
.name = "esai0",
};
static struct snd_soc_dai_driver fsl_sai_dai = {
.name = "sai1",
};
static const struct snd_soc_component_driver fsl_dai_component = {
.name = "fsl-dai",
};
static int fsl_dai_init_clocks(struct fsl_dai *dai_priv)
{
struct device *dev = &dai_priv->pdev->dev;
int i;
dai_priv->clks = devm_kcalloc(dev, dai_priv->num_clks,
sizeof(*dai_priv->clks), GFP_KERNEL);
if (!dai_priv->clks)
return -ENOMEM;
for (i = 0; i < dai_priv->num_clks; i++) {
dai_priv->clks[i] = devm_clk_get(dev, dai_priv->clk_names[i]);
if (IS_ERR(dai_priv->clks[i])) {
dev_dbg(dev, "Failed to get clk %s\n",
dai_priv->clk_names[i]);
dai_priv->clks[i] = NULL;
}
}
return 0;
}
int fsl_get_dai_type(struct fsl_dai *dai_priv)
{
struct device_node *np = dai_priv->pdev->dev.of_node;
if (of_device_is_compatible(np, "fsl,esai-dai"))
return FSL_DAI_TYPE_ESAI;
if (of_device_is_compatible(np, "fsl,sai-dai"))
return FSL_DAI_TYPE_SAI;
return FSL_DAI_TYPE_NONE;
}
static int fsl_dai_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct fsl_dai *priv;
int dai_type;
int ret;
int i;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pdev = pdev;
dev_set_drvdata(&pdev->dev, priv);
dai_type = fsl_get_dai_type(priv);
switch (dai_type) {
case FSL_DAI_TYPE_ESAI:
priv->clk_names = esai_clks;
priv->num_clks = FSL_DAI_ESAI_CLK_NUM;
priv->dai_drv = &fsl_esai_dai;
priv->num_drv = 1;
break;
case FSL_DAI_TYPE_SAI:
priv->clk_names = sai_clks;
priv->num_clks = FSL_DAI_SAI_CLK_NUM;
priv->dai_drv = &fsl_sai_dai;
priv->num_drv = 1;
break;
default:
dev_err(&pdev->dev, "Invalid DAI type %d\n", dai_type);
return -EINVAL;
}
ret = fsl_dai_init_clocks(priv);
if (ret < 0) {
dev_err(&pdev->dev, "Error at init clocks\n");
return ret;
}
priv->num_domains = of_count_phandle_with_args(np, "power-domains",
"#power-domain-cells");
if (priv->num_domains < 0) {
dev_err(&pdev->dev, "no power-domains property in %pOF\n", np);
return priv->num_domains;
}
priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains,
sizeof(*priv->pd_dev), GFP_KERNEL);
if (!priv->pd_dev)
return -ENOMEM;
priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains,
sizeof(*priv->link), GFP_KERNEL);
if (!priv->link)
return -ENOMEM;
for (i = 0; i < priv->num_domains; i++) {
priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i);
if (IS_ERR(priv->pd_dev[i])) {
ret = PTR_ERR(priv->pd_dev[i]);
goto unroll_pm;
}
priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i],
DL_FLAG_STATELESS |
DL_FLAG_PM_RUNTIME |
DL_FLAG_RPM_ACTIVE);
if (!priv->link[i]) {
ret = -EINVAL;
dev_pm_domain_detach(priv->pd_dev[i], false);
goto unroll_pm;
}
}
pm_runtime_enable(&pdev->dev);
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_dai_component,
priv->dai_drv, priv->num_drv);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register DAI ret = %d\n", ret);
return ret;
}
return 0;
unroll_pm:
while (--i >= 0) {
device_link_del(priv->link[i]);
dev_pm_domain_detach(priv->pd_dev[i], false);
}
return ret;
}
static int fsl_dai_remove(struct platform_device *pdev)
{
struct fsl_dai *priv = platform_get_drvdata(pdev);
int i;
pm_runtime_disable(&priv->pdev->dev);
for (i = 0; i < priv->num_domains; i++) {
device_link_del(priv->link[i]);
dev_pm_domain_detach(priv->pd_dev[i], false);
}
return 0;
}
static const struct of_device_id fsl_dai_dt_ids[] = {
{ .compatible = "fsl,esai-dai", },
{ .compatible = "fsl,sai-dai", },
{}
};
MODULE_DEVICE_TABLE(of, fsl_dai_dt_ids);
#ifdef CONFIG_PM
static int fsl_dai_runtime_resume(struct device *dev)
{
struct fsl_dai *priv = dev_get_drvdata(dev);
int i, ret;
for (i = 0; i < priv->num_clks; i++) {
ret = clk_prepare_enable(priv->clks[i]);
if (ret < 0) {
dev_err(dev, "Failed to enable clk %s\n",
priv->clk_names[i]);
goto out;
}
}
return 0;
out:
while (--i >= 0)
clk_disable_unprepare(priv->clks[i]);
return ret;
}
static int fsl_dai_runtime_suspend(struct device *dev)
{
struct fsl_dai *priv = dev_get_drvdata(dev);
int i;
for (i = 0; i < priv->num_clks; i++)
clk_disable_unprepare(priv->clks[i]);
return 0;
}
#endif /* CONFIG_PM */
static const struct dev_pm_ops fsl_dai_pm_ops = {
SET_RUNTIME_PM_OPS(fsl_dai_runtime_suspend,
fsl_dai_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
static struct platform_driver fsl_dai_driver = {
.probe = fsl_dai_probe,
.remove = fsl_dai_remove,
.driver = {
.name = "fsl-dai",
.pm = &fsl_dai_pm_ops,
.of_match_table = fsl_dai_dt_ids,
},
};
module_platform_driver(fsl_dai_driver);
MODULE_ALIAS("platform:fsl-dai");
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
MODULE_DESCRIPTION("FSL Generic DAI driver for DSP");
MODULE_LICENSE("GPL v2");

58
sound/soc/fsl/fsl_dsd.h Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright 2018 NXP
*
* 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 __FSL_DSD_H
#define __FSL_DSD_H
#include <linux/pinctrl/consumer.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
static bool fsl_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 inline struct pinctrl_state *fsl_get_pins_state(struct pinctrl *pinctrl,
struct snd_pcm_hw_params *params, u32 bclk)
{
struct pinctrl_state *state = 0;
if (fsl_is_dsd(params)) {
/* DSD512@44.1kHz, DSD512@48kHz */
if (bclk >= 22579200)
state = pinctrl_lookup_state(pinctrl, "dsd512");
/* Get default DSD state */
if (IS_ERR_OR_NULL(state))
state = pinctrl_lookup_state(pinctrl, "dsd");
} else {
/* 706k32b2c, 768k32b2c, etc */
if (bclk >= 45158400)
state = pinctrl_lookup_state(pinctrl, "pcm_b2m");
}
/* Get default state */
if (IS_ERR_OR_NULL(state))
state = pinctrl_lookup_state(pinctrl, "default");
return state;
}
#endif /* __FSL_DSD_H */

1176
sound/soc/fsl/fsl_dsp.c Normal file

File diff suppressed because it is too large Load Diff

143
sound/soc/fsl/fsl_dsp.h Normal file
View File

@ -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

View File

@ -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);

View File

@ -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*/

View File

@ -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);
}

View File

@ -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

View File

@ -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*/

View File

@ -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,
};

View File

@ -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);
}

View File

@ -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 */

View File

@ -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, &reg);
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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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 */

2533
sound/soc/fsl/fsl_easrc.c Normal file

File diff suppressed because it is too large Load Diff

698
sound/soc/fsl/fsl_easrc.h Normal file
View File

@ -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 */

View File

@ -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);

View File

@ -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), &reg);
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

View File

@ -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>
@ -21,6 +22,19 @@
SNDRV_PCM_FMTBIT_S20_3LE | \
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
*
@ -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);
@ -962,10 +1048,34 @@ static int fsl_esai_probe(struct platform_device *pdev)
PTR_ERR(esai_priv->spbaclk));
irq = platform_get_irq(pdev, 0);
if (irq < 0)
if (irq < 0) {
dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
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 +1085,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 +1100,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 +1123,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 +1144,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 +1157,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);

750
sound/soc/fsl/fsl_hdmi.c Normal file
View File

@ -0,0 +1,750 @@
/*
* ALSA SoC HDMI Audio Layer for Freescale i.MX
*
* Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
*
* Some code from patch_hdmi.c
* Copyright (c) 2008-2010 Intel Corporation. All rights reserved.
* Copyright (c) 2006 ATI Technologies Inc.
* Copyright (c) 2008 NVIDIA Corp. All rights reserved.
* Copyright (c) 2008 Wei Ni <wni@nvidia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/mfd/mxc-hdmi-core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/asoundef.h>
#include <sound/hdmi-codec.h>
#include <video/mxc_hdmi.h>
#include "imx-hdmi.h"
static struct mxc_edid_cfg edid_cfg;
static u32 playback_rates[HDMI_MAX_RATES];
static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE];
static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS];
static struct snd_pcm_hw_constraint_list playback_constraint_rates;
static struct snd_pcm_hw_constraint_list playback_constraint_bits;
static struct snd_pcm_hw_constraint_list playback_constraint_channels;
#ifdef DEBUG
static void dumpregs(struct snd_soc_dai *dai)
{
u32 n, cts;
cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) |
(hdmi_readb(HDMI_AUD_CTS2) << 8) |
hdmi_readb(HDMI_AUD_CTS1);
n = (hdmi_readb(HDMI_AUD_N3) << 16) |
(hdmi_readb(HDMI_AUD_N2) << 8) |
hdmi_readb(HDMI_AUD_N1);
dev_dbg(dai->dev, "HDMI_PHY_CONF0 0x%02x\n",
hdmi_readb(HDMI_PHY_CONF0));
dev_dbg(dai->dev, "HDMI_MC_CLKDIS 0x%02x\n",
hdmi_readb(HDMI_MC_CLKDIS));
dev_dbg(dai->dev, "HDMI_AUD_N[1-3] 0x%06x (%d)\n",
n, n);
dev_dbg(dai->dev, "HDMI_AUD_CTS[1-3] 0x%06x (%d)\n",
cts, cts);
dev_dbg(dai->dev, "HDMI_FC_AUDSCONF 0x%02x\n",
hdmi_readb(HDMI_FC_AUDSCONF));
}
#else
static void dumpregs(struct snd_soc_dai *dai) {}
#endif
enum cea_speaker_placement {
FL = (1 << 0), /* Front Left */
FC = (1 << 1), /* Front Center */
FR = (1 << 2), /* Front Right */
FLC = (1 << 3), /* Front Left Center */
FRC = (1 << 4), /* Front Right Center */
RL = (1 << 5), /* Rear Left */
RC = (1 << 6), /* Rear Center */
RR = (1 << 7), /* Rear Right */
RLC = (1 << 8), /* Rear Left Center */
RRC = (1 << 9), /* Rear Right Center */
LFE = (1 << 10), /* Low Frequency Effect */
FLW = (1 << 11), /* Front Left Wide */
FRW = (1 << 12), /* Front Right Wide */
FLH = (1 << 13), /* Front Left High */
FCH = (1 << 14), /* Front Center High */
FRH = (1 << 15), /* Front Right High */
TC = (1 << 16), /* Top Center */
};
/*
* EDID SA bits in the CEA Speaker Allocation data block
*/
static int edid_speaker_allocation_bits[] = {
[0] = FL | FR,
[1] = LFE,
[2] = FC,
[3] = RL | RR,
[4] = RC,
[5] = FLC | FRC,
[6] = RLC | RRC,
[7] = FLW | FRW,
[8] = FLH | FRH,
[9] = TC,
[10] = FCH,
};
struct cea_channel_speaker_allocation {
int ca_index;
int speakers[8];
/* Derived values, just for convenience */
int channels;
int spk_mask;
};
/*
* This is an ordered list!
*
* The preceding ones have better chances to be selected by
* hdmi_channel_allocation().
*/
static struct cea_channel_speaker_allocation channel_allocations[] = {
/* channel: 7 6 5 4 3 2 1 0 */
{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL },},
/* 2.1 */
{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL },},
/* Dolby Surround */
{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL },},
{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL },},
{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL },},
{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL },},
{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL },},
{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL },},
{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL },},
{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL },},
{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL },},
/* surround51 */
{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL },},
{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL },},
{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL },},
/* 6.1 */
{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL },},
{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL },},
{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL },},
/* surround71 */
{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL },},
{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL },},
{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL },},
{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL },},
{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL },},
{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL },},
{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL },},
{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL },},
{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL },},
{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL },},
{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL },},
{ .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL },},
{ .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL },},
{ .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL },},
{ .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL },},
{ .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL },},
{ .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL },},
};
/* Compute derived values in channel_allocations[] */
static void init_channel_allocations(void)
{
struct cea_channel_speaker_allocation *p;
int i, j;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
p = channel_allocations + i;
p->channels = 0;
p->spk_mask = 0;
for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
if (p->speakers[j]) {
p->channels++;
p->spk_mask |= p->speakers[j];
}
}
}
/*
* The transformation takes two steps:
*
* speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask
* spk_mask => (channel_allocations[]) => CA
*
* TODO: it could select the wrong CA from multiple candidates.
*/
static int hdmi_channel_allocation(int channels)
{
int spk_mask = 0, ca = 0, i, tmpchn, tmpspk;
/* CA defaults to 0 for basic stereo audio */
if (channels <= 2)
return 0;
/*
* Expand EDID's speaker allocation mask
*
* EDID tells the speaker mask in a compact(paired) form,
* expand EDID's notions to match the ones used by Audio InfoFrame.
*/
for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) {
if (edid_cfg.speaker_alloc & (1 << i))
spk_mask |= edid_speaker_allocation_bits[i];
}
/* Search for the first working match in the CA table */
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
tmpchn = channel_allocations[i].channels;
tmpspk = channel_allocations[i].spk_mask;
if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) {
ca = channel_allocations[i].ca_index;
break;
}
}
return ca;
}
static void hdmi_set_audio_infoframe(unsigned int channels)
{
u8 audiconf0, audiconf2;
/*
* From CEA-861-D spec:
* HDMI requires the CT, SS and SF fields to be set to 0 ("Refer
* to Stream Header") as these items are carried in the audio stream.
*
* So we only set the CC and CA fields.
*/
audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) &
HDMI_FC_AUDICONF0_CC_MASK;
audiconf2 = hdmi_channel_allocation(channels);
hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0);
hdmi_writeb(0, HDMI_FC_AUDICONF1);
hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2);
hdmi_writeb(0, HDMI_FC_AUDICONF3);
}
static int cea_audio_rates[HDMI_MAX_RATES] = {
32000, 44100, 48000, 88200, 96000, 176400, 192000,
};
static void fsl_hdmi_get_playback_rates(void)
{
int i, count = 0;
u8 rates;
/* Always assume basic audio support */
rates = edid_cfg.sample_rates | 0x7;
for (i = 0 ; i < HDMI_MAX_RATES ; i++)
if ((rates & (1 << i)) != 0)
playback_rates[count++] = cea_audio_rates[i];
playback_constraint_rates.list = playback_rates;
playback_constraint_rates.count = count;
for (i = 0 ; i < playback_constraint_rates.count ; i++)
pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]);
}
static void fsl_hdmi_get_playback_sample_size(void)
{
int i = 0;
/* Always assume basic audio support */
playback_sample_size[i++] = 16;
if (edid_cfg.sample_sizes & 0x4)
playback_sample_size[i++] = 24;
playback_constraint_bits.list = playback_sample_size;
playback_constraint_bits.count = i;
for (i = 0 ; i < playback_constraint_bits.count ; i++)
pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]);
}
static void fsl_hdmi_get_playback_channels(void)
{
int channels = 2, i = 0;
/* Always assume basic audio support */
playback_channels[i++] = channels;
channels += 2;
while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) &&
(channels <= edid_cfg.max_channels)) {
playback_channels[i++] = channels;
channels += 2;
}
playback_constraint_channels.list = playback_channels;
playback_constraint_channels.count = i;
for (i = 0 ; i < playback_constraint_channels.count ; i++)
pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]);
}
static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_rates();
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&playback_constraint_rates);
if (ret)
return ret;
fsl_hdmi_get_playback_sample_size();
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
&playback_constraint_bits);
if (ret)
return ret;
fsl_hdmi_get_playback_channels();
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&playback_constraint_channels);
if (ret)
return ret;
ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (ret)
return ret;
return 0;
}
static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
int ret;
clk_prepare_enable(hdmi_data->mipi_core_clk);
clk_prepare_enable(hdmi_data->isfr_clk);
clk_prepare_enable(hdmi_data->iahb_clk);
dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__,
(int)clk_get_rate(hdmi_data->mipi_core_clk),
(int)clk_get_rate(hdmi_data->isfr_clk),
(int)clk_get_rate(hdmi_data->iahb_clk));
ret = fsl_hdmi_update_constraints(substream);
if (ret < 0)
return ret;
/* Indicates the subpacket represents a flatline sample */
hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0);
return 0;
}
static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(hdmi_data->iahb_clk);
clk_disable_unprepare(hdmi_data->isfr_clk);
clk_disable_unprepare(hdmi_data->mipi_core_clk);
}
static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_pcm_runtime *runtime = substream->runtime;
hdmi_set_audio_infoframe(runtime->channels);
hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT,
(runtime->channels > 2) ? 0x1 : 0x0);
hdmi_set_sample_rate(runtime->rate);
dumpregs(dai);
return 0;
}
static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = {
.startup = fsl_hdmi_soc_startup,
.shutdown = fsl_hdmi_soc_shutdown,
.prepare = fsl_hdmi_soc_prepare,
};
/* IEC60958 status functions */
static int fsl_hdmi_iec_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int fsl_hdmi_iec_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
int i;
for (i = 0 ; i < 4 ; i++)
uvalue->value.iec958.status[i] = iec_header.status[i];
return 0;
}
static int fsl_hdmi_iec_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
int i;
/* Do not allow professional mode */
if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL)
return -EPERM;
for (i = 0 ; i < 4 ; i++) {
iec_header.status[i] = uvalue->value.iec958.status[i];
pr_debug("%s status[%d]=0x%02x\n", __func__, i, iec_header.status[i]);
}
return 0;
}
static int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_channels();
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = playback_constraint_channels.count;
return 0;
}
static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
int i;
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_channels();
for (i = 0 ; i < playback_constraint_channels.count ; i++)
uvalue->value.integer.value[i] = playback_channels[i];
return 0;
}
static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_rates();
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = playback_constraint_rates.count;
return 0;
}
static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
int i;
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_rates();
for (i = 0 ; i < playback_constraint_rates.count ; i++)
uvalue->value.integer.value[i] = playback_rates[i];
return 0;
}
static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_sample_size();
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = playback_constraint_bits.count;
return 0;
}
static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
int i;
hdmi_get_edid_cfg(&edid_cfg);
fsl_hdmi_get_playback_sample_size();
for (i = 0 ; i < playback_constraint_bits.count ; i++)
uvalue->value.integer.value[i] = playback_sample_size[i];
return 0;
}
static struct snd_kcontrol_new fsl_hdmi_ctrls[] = {
/* Status cchanel controller */
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_WRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = fsl_hdmi_iec_info,
.get = fsl_hdmi_iec_get,
.put = fsl_hdmi_iec_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Support Channels",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = fsl_hdmi_channels_info,
.get = fsl_hdmi_channels_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Support Rates",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = fsl_hdmi_rates_info,
.get = fsl_hdmi_rates_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Support Formats",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = fsl_hdmi_formats_info,
.get = fsl_hdmi_formats_get,
},
};
static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai)
{
int ret;
init_channel_allocations();
ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls,
ARRAY_SIZE(fsl_hdmi_ctrls));
if (ret)
dev_warn(dai->dev, "failed to add dai controls\n");
return 0;
}
static struct snd_soc_dai_driver fsl_hdmi_dai = {
.probe = &fsl_hdmi_soc_dai_probe,
.playback = {
.channels_min = 2,
.channels_max = 8,
.rates = MXC_HDMI_RATES_PLAYBACK,
.formats = MXC_HDMI_FORMATS_PLAYBACK,
},
.ops = &fsl_hdmi_soc_dai_ops,
};
static const struct snd_soc_component_driver fsl_hdmi_component = {
.name = "fsl-hdmi",
};
/* HDMI audio codec callbacks */
static int fsl_hdmi_audio_hw_params(struct device *dev, void *data,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
dev_dbg(dev, "[%s]: %u Hz, %d bit, %d channels\n", __func__,
hparms->sample_rate, hparms->sample_width, hparms->cea.channels);
return 0;
}
static void fsl_hdmi_audio_shutdown(struct device *dev, void *data)
{
dev_dbg(dev, "[%s]\n", __func__);
}
static const struct hdmi_codec_ops fsl_hdmi_audio_codec_ops = {
.hw_params = fsl_hdmi_audio_hw_params,
.audio_shutdown = fsl_hdmi_audio_shutdown,
};
static struct hdmi_codec_pdata codec_data = {
.ops = &fsl_hdmi_audio_codec_ops,
.i2s = 1,
.max_i2s_channels = 8,
};
static int fsl_hdmi_dai_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct imx_hdmi *hdmi_data;
int ret = 0;
if (!np)
return -ENODEV;
if (!hdmi_get_registered()) {
dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n");
return -ENOMEM;
}
hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL);
if (!hdmi_data) {
dev_err(&pdev->dev, "failed to alloc hdmi_data\n");
return -ENOMEM;
}
hdmi_data->pdev = pdev;
memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai));
hdmi_data->cpu_dai_drv.name = np->name;
hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core");
if (IS_ERR(hdmi_data->mipi_core_clk)) {
ret = PTR_ERR(hdmi_data->mipi_core_clk);
dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret);
return -EINVAL;
}
hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr");
if (IS_ERR(hdmi_data->isfr_clk)) {
ret = PTR_ERR(hdmi_data->isfr_clk);
dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret);
return -EINVAL;
}
hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb");
if (IS_ERR(hdmi_data->iahb_clk)) {
ret = PTR_ERR(hdmi_data->iahb_clk);
dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret);
return -EINVAL;
}
dev_set_drvdata(&pdev->dev, hdmi_data);
ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component,
&hdmi_data->cpu_dai_drv, 1);
if (ret) {
dev_err(&pdev->dev, "register DAI failed\n");
return ret;
}
hdmi_data->codec_dev = platform_device_register_data(&pdev->dev,
HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_NONE,
&codec_data, sizeof(codec_data));
if (IS_ERR(hdmi_data->codec_dev)) {
dev_err(&pdev->dev, "failed to register HDMI audio codec\n");
ret = PTR_ERR(hdmi_data->codec_dev);
goto fail;
}
hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1);
if (!hdmi_data->dma_dev) {
ret = -ENOMEM;
goto fail_dma;
}
platform_set_drvdata(hdmi_data->dma_dev, hdmi_data);
ret = platform_device_add(hdmi_data->dma_dev);
if (ret) {
platform_device_put(hdmi_data->dma_dev);
goto fail_dma;
}
return 0;
fail_dma:
platform_device_unregister(hdmi_data->codec_dev);
fail:
snd_soc_unregister_component(&pdev->dev);
return ret;
}
static int fsl_hdmi_dai_remove(struct platform_device *pdev)
{
struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev);
platform_device_unregister(hdmi_data->dma_dev);
platform_device_unregister(hdmi_data->codec_dev);
snd_soc_unregister_component(&pdev->dev);
return 0;
}
static const struct of_device_id fsl_hdmi_dai_dt_ids[] = {
{ .compatible = "fsl,imx6dl-hdmi-audio", },
{ .compatible = "fsl,imx6q-hdmi-audio", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids);
static struct platform_driver fsl_hdmi_driver = {
.probe = fsl_hdmi_dai_probe,
.remove = fsl_hdmi_dai_remove,
.driver = {
.name = "fsl-hdmi-dai",
.owner = THIS_MODULE,
.of_match_table = fsl_hdmi_dai_dt_ids,
},
};
module_platform_driver(fsl_hdmi_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("IMX HDMI TX DAI");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:fsl-hdmi-dai");

File diff suppressed because it is too large Load Diff

View File

@ -258,6 +258,40 @@
#define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT)
#define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT)
/* HWVAD Constants */
#define MICFIL_HWVAD_ENVELOPE_MODE 0
#define MICFIL_HWVAD_ENERGY_MODE 1
#define MICFIL_HWVAD_INIT_FRAMES 10
#define MICFIL_HWVAD_INPGAIN 0
#define MICFIL_HWVAD_SGAIN 6
#define MICFIL_HWVAD_NGAIN 3
#define MICFIL_HWVAD_NFILADJ 0
#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2))
#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */
#define MICFIL_HWVAD_ZCDOR 0
#define MICFIL_HWVAD_ZCDAND 1
#define MICFIL_HWVAD_ZCD_MANUAL 0
#define MICFIL_HWVAD_ZCD_AUTO 1
#define MICFIL_HWVAD_HPF_BYPASS 0
#define MICFIL_HWVAD_HPF_1750HZ 1
#define MICFIL_HWVAD_HPF_215HZ 2
#define MICFIL_HWVAD_HPF_102HZ 3
#define MICFIL_HWVAD_FRAMET_DEFAULT 10
/* MICFIL DC Remover Control Register -- REG_MICFIL_DC_CTRL */
#define MICFIL_DC_CTRL_SHIFT 0
#define MICFIL_DC_CTRL_MASK 0xFFFF
#define MICFIL_DC_CTRL_WIDTH 2
#define MICFIL_DC_CHX_SHIFT(v) (2 * (v))
#define MICFIL_DC_CHX_MASK(v) ((BIT(MICFIL_DC_CTRL_WIDTH) - 1) \
<< MICFIL_DC_CHX_SHIFT(v))
#define MICFIL_DC_MODE(v1, v2) (((v1) << MICFIL_DC_CHX_SHIFT(v2)) \
& MICFIL_DC_CHX_MASK(v2))
#define MICFIL_DC_CUTOFF_21HZ 0
#define MICFIL_DC_CUTOFF_83HZ 1
#define MICFIL_DC_CUTOFF_152Hz 2
#define MICFIL_DC_BYPASS 3
/* MICFIL Output Control Register */
#define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v))
@ -273,11 +307,24 @@
#define FIFO_PTRWID 3
#define FIFO_LEN BIT(FIFO_PTRWID)
#define MICFIL_IRQ_LINES 2
#define MICFIL_IRQ_LINES 4
#define MICFIL_MAX_RETRY 25
#define MICFIL_SLEEP_MIN 90000 /* in us */
#define MICFIL_SLEEP_MAX 100000 /* in us */
#define MICFIL_SLEEP 100 /* in ms */
#define MICFIL_DMA_MAXBURST_RX 6
#define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT)
#define MICFIL_DEFAULT_RATE 48000
#define MICFIL_CLK_SRC_NUM 3
#define MICFIL_CLK_AUTO 0
/* clock source ids */
#define MICFIL_AUDIO_PLL1 0
#define MICFIL_AUDIO_PLL2 1
#define MICFIL_CLK_EXT3 2
/* States of micfil */
#define MICFIL_HWVAD_OFF 0
#define MICFIL_HWVAD_ON 1
#define MICFIL_RECORDING_OFF 0
#define MICFIL_RECORDING_ON 1
#endif /* _FSL_MICFIL_H */

View File

@ -0,0 +1,425 @@
/*
* Freescale ALSA SoC rpmsg i2s driver.
*
* Copyright 2017 NXP
*
* 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/clk.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pm_runtime.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include "fsl_rpmsg_i2s.h"
#include "imx-pcm.h"
#define FSL_RPMSG_I2S_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
SNDRV_PCM_RATE_48000)
#define FSL_RPMSG_I2S_FORMATS SNDRV_PCM_FMTBIT_S16_LE
static int i2s_send_message(struct i2s_rpmsg *msg,
struct i2s_info *info)
{
int err = 0;
mutex_lock(&info->tx_lock);
if (!info->rpdev) {
dev_err(info->dev, "rpmsg channel not ready, m4 image ready?\n");
mutex_unlock(&info->tx_lock);
return -EINVAL;
}
dev_dbg(&info->rpdev->dev, "send cmd %d\n", msg->send_msg.header.cmd);
if (!(msg->send_msg.header.type == I2S_TYPE_C))
reinit_completion(&info->cmd_complete);
err = rpmsg_send(info->rpdev->ept, (void *)&msg->send_msg,
sizeof(struct i2s_rpmsg_s));
if (err) {
dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
mutex_unlock(&info->tx_lock);
return err;
}
if (!(msg->send_msg.header.type == I2S_TYPE_C)) {
/* wait response from rpmsg */
err = wait_for_completion_timeout(&info->cmd_complete,
msecs_to_jiffies(RPMSG_TIMEOUT));
if (!err) {
dev_err(&info->rpdev->dev,
"rpmsg_send cmd %d timeout!\n",
msg->send_msg.header.cmd);
mutex_unlock(&info->tx_lock);
return -ETIMEDOUT;
}
memcpy(&msg->recv_msg, &info->recv_msg,
sizeof(struct i2s_rpmsg_r));
memcpy(&info->rpmsg[msg->recv_msg.header.cmd].recv_msg,
&msg->recv_msg, sizeof(struct i2s_rpmsg_r));
/*
* Reset the buffer pointer to be zero, actully we have
* set the buffer pointer to be zero in imx_rpmsg_terminate_all
* But if there is timer task queued in queue, after it is
* executed the buffer pointer will be changed, so need to
* reset it again with TERMINATE command.
*/
switch (msg->send_msg.header.cmd) {
case I2S_TX_TERMINATE:
info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
break;
case I2S_RX_TERMINATE:
info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
break;
default:
break;
}
}
dev_dbg(&info->rpdev->dev, "cmd:%d, resp %d\n",
msg->send_msg.header.cmd,
info->recv_msg.param.resp);
mutex_unlock(&info->tx_lock);
return 0;
}
static const unsigned int fsl_rpmsg_rates[] = {
8000, 11025, 16000, 22050, 44100,
32000, 48000, 96000, 88200, 176400, 192000,
352800, 384000, 705600, 768000, 1411200, 2822400,
};
static const struct snd_pcm_hw_constraint_list fsl_rpmsg_rate_constraints = {
.count = ARRAY_SIZE(fsl_rpmsg_rates),
.list = fsl_rpmsg_rates,
};
static int fsl_rpmsg_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
int ret;
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &fsl_rpmsg_rate_constraints);
return ret;
}
static const struct snd_soc_dai_ops fsl_rpmsg_dai_ops = {
.startup = fsl_rpmsg_startup,
};
static struct snd_soc_dai_driver fsl_rpmsg_i2s_dai = {
.playback = {
.stream_name = "CPU-Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_RPMSG_I2S_FORMATS,
},
.capture = {
.stream_name = "CPU-Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_RPMSG_I2S_FORMATS,
},
.symmetric_rates = 1,
.symmetric_channels = 1,
.symmetric_samplebits = 1,
.ops = &fsl_rpmsg_dai_ops,
};
static const struct snd_soc_component_driver fsl_component = {
.name = "fsl-rpmsg-i2s",
};
static const struct of_device_id fsl_rpmsg_i2s_ids[] = {
{ .compatible = "fsl,imx7ulp-rpmsg-i2s"},
{ .compatible = "fsl,imx8mq-rpmsg-i2s"},
{ .compatible = "fsl,imx8qxp-rpmsg-i2s"},
{ .compatible = "fsl,imx8qm-rpmsg-i2s"},
{ .compatible = "fsl,imx8mn-rpmsg-i2s"},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_rpmsg_i2s_ids);
static void rpmsg_i2s_work(struct work_struct *work)
{
struct work_of_rpmsg *work_of_rpmsg;
struct i2s_info *i2s_info;
bool is_period_done = false;
unsigned long flags;
struct i2s_rpmsg msg;
work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
i2s_info = work_of_rpmsg->i2s_info;
spin_lock_irqsave(&i2s_info->lock[0], flags);
if (i2s_info->period_done_msg_enabled[0]) {
memcpy(&msg, &i2s_info->period_done_msg[0], sizeof(struct i2s_rpmsg_s));
i2s_info->period_done_msg_enabled[0] = false;
spin_unlock_irqrestore(&i2s_info->lock[0], flags);
i2s_send_message(&msg, i2s_info);
} else
spin_unlock_irqrestore(&i2s_info->lock[0], flags);
if (i2s_info->period_done_msg_enabled[1]) {
i2s_send_message(&i2s_info->period_done_msg[1], i2s_info);
i2s_info->period_done_msg_enabled[1] = false;
}
if (work_of_rpmsg->msg.send_msg.header.type == I2S_TYPE_C &&
(work_of_rpmsg->msg.send_msg.header.cmd == I2S_TX_PERIOD_DONE ||
work_of_rpmsg->msg.send_msg.header.cmd == I2S_RX_PERIOD_DONE))
is_period_done = true;
if (!is_period_done)
i2s_send_message(&work_of_rpmsg->msg, i2s_info);
spin_lock_irqsave(&i2s_info->wq_lock, flags);
i2s_info->work_read_index++;
i2s_info->work_read_index %= WORK_MAX_NUM;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
}
static int fsl_rpmsg_i2s_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct fsl_rpmsg_i2s *rpmsg_i2s;
struct i2s_info *i2s_info;
int audioindex = 0;
int ret;
int i;
rpmsg_i2s = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg_i2s),
GFP_KERNEL);
if (!rpmsg_i2s)
return -ENOMEM;
rpmsg_i2s->pdev = pdev;
i2s_info = &rpmsg_i2s->i2s_info;
ret = of_property_read_u32(np, "fsl,audioindex", &audioindex);
if (ret)
audioindex = 0;
/* Setup work queue */
i2s_info->rpmsg_wq = alloc_ordered_workqueue("rpmsg_i2s", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE);
if (i2s_info->rpmsg_wq == NULL) {
dev_err(&pdev->dev, "workqueue create failed\n");
return -ENOMEM;
}
i2s_info->work_write_index = 1;
i2s_info->send_message = i2s_send_message;
for (i = 0; i < WORK_MAX_NUM; i++) {
INIT_WORK(&i2s_info->work_list[i].work, rpmsg_i2s_work);
i2s_info->work_list[i].i2s_info = i2s_info;
}
for (i = 0; i < I2S_CMD_MAX_NUM ; i++) {
i2s_info->rpmsg[i].send_msg.header.cate = IMX_RPMSG_AUDIO;
i2s_info->rpmsg[i].send_msg.header.major = IMX_RMPSG_MAJOR;
i2s_info->rpmsg[i].send_msg.header.minor = IMX_RMPSG_MINOR;
i2s_info->rpmsg[i].send_msg.header.type = I2S_TYPE_A;
i2s_info->rpmsg[i].send_msg.param.audioindex = audioindex;
}
mutex_init(&i2s_info->tx_lock);
mutex_init(&i2s_info->i2c_lock);
spin_lock_init(&i2s_info->lock[0]);
spin_lock_init(&i2s_info->lock[1]);
spin_lock_init(&i2s_info->wq_lock);
if (of_device_is_compatible(pdev->dev.of_node,
"fsl,imx7ulp-rpmsg-i2s")) {
rpmsg_i2s->codec_wm8960 = 1;
rpmsg_i2s->version = 1;
rpmsg_i2s->rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000;
rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE;
fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
}
if (of_device_is_compatible(pdev->dev.of_node,
"fsl,imx8qxp-rpmsg-i2s")) {
rpmsg_i2s->codec_wm8960 = 1 + (1 << 16);
rpmsg_i2s->version = 1;
rpmsg_i2s->codec_cs42888 = 1 + (2 << 16);
}
if (of_device_is_compatible(pdev->dev.of_node,
"fsl,imx8qm-rpmsg-i2s")) {
rpmsg_i2s->codec_wm8960 = 0;
rpmsg_i2s->version = 1;
rpmsg_i2s->codec_cs42888 = 1 + (0 << 16);
}
if (of_device_is_compatible(pdev->dev.of_node,
"fsl,imx8mq-rpmsg-i2s")) {
rpmsg_i2s->codec_dummy = 0;
rpmsg_i2s->codec_ak4497 = 1;
rpmsg_i2s->version = 2;
rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT;
rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_DSD_U8 |
SNDRV_PCM_FMTBIT_DSD_U16_LE |
SNDRV_PCM_FMTBIT_DSD_U32_LE;
fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
}
if (of_device_is_compatible(pdev->dev.of_node,
"fsl,imx8mn-rpmsg-i2s")) {
rpmsg_i2s->codec_dummy = 1;
rpmsg_i2s->version = 2;
rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT;
rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE;
fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
}
if (of_property_read_bool(pdev->dev.of_node, "fsl,enable-lpa"))
rpmsg_i2s->enable_lpa = 1;
if (of_property_read_u32(np, "fsl,dma-buffer-size",
&i2s_info->prealloc_buffer_size))
i2s_info->prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE;
platform_set_drvdata(pdev, rpmsg_i2s);
pm_runtime_enable(&pdev->dev);
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
&fsl_rpmsg_i2s_dai, 1);
if (ret)
return ret;
return imx_rpmsg_platform_register(&pdev->dev);
}
static int fsl_rpmsg_i2s_remove(struct platform_device *pdev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = platform_get_drvdata(pdev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
if (i2s_info->rpmsg_wq)
destroy_workqueue(i2s_info->rpmsg_wq);
return 0;
}
#ifdef CONFIG_PM
static int fsl_rpmsg_i2s_runtime_resume(struct device *dev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
pm_qos_add_request(&rpmsg_i2s->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0);
return 0;
}
static int fsl_rpmsg_i2s_runtime_suspend(struct device *dev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
pm_qos_remove_request(&rpmsg_i2s->pm_qos_req);
return 0;
}
#endif
#ifdef CONFIG_PM_SLEEP
static int fsl_rpmsg_i2s_suspend(struct device *dev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg_tx;
struct i2s_rpmsg *rpmsg_rx;
flush_workqueue(i2s_info->rpmsg_wq);
rpmsg_tx = &i2s_info->rpmsg[I2S_TX_SUSPEND];
rpmsg_rx = &i2s_info->rpmsg[I2S_RX_SUSPEND];
rpmsg_tx->send_msg.header.cmd = I2S_TX_SUSPEND;
i2s_send_message(rpmsg_tx, i2s_info);
rpmsg_rx->send_msg.header.cmd = I2S_RX_SUSPEND;
i2s_send_message(rpmsg_rx, i2s_info);
return 0;
}
static int fsl_rpmsg_i2s_resume(struct device *dev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg_tx;
struct i2s_rpmsg *rpmsg_rx;
rpmsg_tx = &i2s_info->rpmsg[I2S_TX_RESUME];
rpmsg_rx = &i2s_info->rpmsg[I2S_RX_RESUME];
rpmsg_tx->send_msg.header.cmd = I2S_TX_RESUME;
i2s_send_message(rpmsg_tx, i2s_info);
rpmsg_rx->send_msg.header.cmd = I2S_RX_RESUME;
i2s_send_message(rpmsg_rx, i2s_info);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops fsl_rpmsg_i2s_pm_ops = {
SET_RUNTIME_PM_OPS(fsl_rpmsg_i2s_runtime_suspend,
fsl_rpmsg_i2s_runtime_resume,
NULL)
SET_SYSTEM_SLEEP_PM_OPS(fsl_rpmsg_i2s_suspend, fsl_rpmsg_i2s_resume)
};
static struct platform_driver fsl_rpmsg_i2s_driver = {
.probe = fsl_rpmsg_i2s_probe,
.remove = fsl_rpmsg_i2s_remove,
.driver = {
.name = "fsl-rpmsg-i2s",
.pm = &fsl_rpmsg_i2s_pm_ops,
.of_match_table = fsl_rpmsg_i2s_ids,
},
};
module_platform_driver(fsl_rpmsg_i2s_driver);
MODULE_DESCRIPTION("Freescale Soc rpmsg_i2s Interface");
MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@freescale.com>");
MODULE_ALIAS("platform:fsl-rpmsg_i2s");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,454 @@
/*
* Copyright 2017 NXP
*
* 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.
*
******************************************************************************
* communication stack of audio with rpmsg
******************************************************************************
* Packet structure:
* A SRTM message consists of a 10 bytes header followed by 0~N bytes of data
*
* +---------------+-------------------------------+
* | | Content |
* +---------------+-------------------------------+
* | Byte Offset | 7 6 5 4 3 2 1 0 |
* +---------------+---+---+---+---+---+---+---+---+
* | 0 | Category |
* +---------------+---+---+---+---+---+---+---+---+
* | 1 ~ 2 | Version |
* +---------------+---+---+---+---+---+---+---+---+
* | 3 | Type |
* +---------------+---+---+---+---+---+---+---+---+
* | 4 | Command |
* +---------------+---+---+---+---+---+---+---+---+
* | 5 | Reserved0 |
* +---------------+---+---+---+---+---+---+---+---+
* | 6 | Reserved1 |
* +---------------+---+---+---+---+---+---+---+---+
* | 7 | Reserved2 |
* +---------------+---+---+---+---+---+---+---+---+
* | 8 | Reserved3 |
* +---------------+---+---+---+---+---+---+---+---+
* | 9 | Reserved4 |
* +---------------+---+---+---+---+---+---+---+---+
* | 10 | DATA 0 |
* +---------------+---+---+---+---+---+---+---+---+
* : : : : : : : : : : : : :
* +---------------+---+---+---+---+---+---+---+---+
* | N + 10 - 1 | DATA N-1 |
* +---------------+---+---+---+---+---+---+---+---+
*
* +----------+------------+------------------------------------------------+
* | Field | Byte | |
* +----------+------------+------------------------------------------------+
* | Category | 0 | The destination category. |
* +----------+------------+------------------------------------------------+
* | Version | 1 ~ 2 | The category version of the sender of the |
* | | | packet. |
* | | | The first byte represent the major version of |
* | | | the packet.The second byte represent the minor |
* | | | version of the packet. |
* +----------+------------+------------------------------------------------+
* | Type | 3 | The message type of current message packet. |
* +----------+------------+------------------------------------------------+
* | Command | 4 | The command byte sent to remote processor/SoC. |
* +----------+------------+------------------------------------------------+
* | Reserved | 5 ~ 9 | Reserved field for future extension. |
* +----------+------------+------------------------------------------------+
* | Data | N | The data payload of the message packet. |
* +----------+------------+------------------------------------------------+
*
* Audio control:
* SRTM Audio Control Category Request Command Table:
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | Category | Version | Type | Command | Data | Function |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open an Audio TX Instance. |
* | | | | | Data[1]: format | |
* | | | | | Data[2]: channels | |
* | | | | | Data[3-6]: samplerate | |
* | | | | | Data[7-10]: buffer_addr | |
* | | | | | Data[11-14]: buffer_size | |
* | | | | | Data[15-18]: period_size | |
* | | | | | Data[19-22]: buffer_tail | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start an Audio TX Instance. |
* | | | | | Same as above command | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause an Audio TX Instance. |
* | | | | | Same as above command | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume an Audio TX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Terminate an Audio TX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close an Audio TX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for an Audio TX Instance. |
* | | | | | Data[1]: format | |
* | | | | | Data[2]: channels | |
* | | | | | Data[3-6]: samplerate | |
* | | | | | Data[7-22]: reserved | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set Audio TX Buffer. |
* | | | | | Data[1-6]: reserved | |
* | | | | | Data[7-10]: buffer_addr | |
* | | | | | Data[11-14]: buffer_size | |
* | | | | | Data[15-18]: period_size | |
* | | | | | Data[19-22]: buffer_tail | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend an Audio TX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume an Audio TX Instance. |
* | | | | | Data[1]: format | |
* | | | | | Data[2]: channels | |
* | | | | | Data[3-6]: samplerate | |
* | | | | | Data[7-10]: buffer_addr | |
* | | | | | Data[11-14]: buffer_size | |
* | | | | | Data[15-18]: period_size | |
* | | | | | Data[19-22]: buffer_tail | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Terminate an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for an Audio RX Instance. |
* | | | | | Data[1]: format | |
* | | | | | Data[2]: channels | |
* | | | | | Data[3-6]: samplerate | |
* | | | | | Data[7-22]: reserved | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set Audio RX Buffer. |
* | | | | | Data[1-6]: reserved | |
* | | | | | Data[7-10]: buffer_addr | |
* | | | | | Data[11-14]: buffer_size | |
* | | | | | Data[15-18]: period_size | |
* | | | | | Data[19-22]: buffer_tail | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend an Audio RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume an Audio RX Instance. |
* | | | | | Data[1]: format | |
* | | | | | Data[2]: channels | |
* | | | | | Data[3-6]: samplerate | |
* | | | | | Data[7-10]: buffer_addr | |
* | | | | | Data[11-14]: buffer_size | |
* | | | | | Data[15-18]: period_size | |
* | | | | | Data[19-22]: buffer_tail | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value to codec. |
* | | | | | Data[1-6]: reserved | |
* | | | | | Data[7-10]: register | |
* | | | | | Data[11-14]: value | |
* | | | | | Data[15-22]: reserved | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value from codec. |
* | | | | | Data[1-6]: reserved | |
* | | | | | Data[7-10]: register | |
* | | | | | Data[11-22]: reserved | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* Note 1: See <List of Sample Format> for available value of
* Sample Format;
* Note 2: See <List of Audio Channels> for available value of Channels;
* Note 3: Sample Rate of Set Parameters for an Audio TX Instance
* Command and Set Parameters for an Audio RX Instance Command is
* in little-endian format.
*
* SRTM Audio Control Category Response Command Table:
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | Category | Version | Type | Command | Data | Function |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Terminate an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio |
* | | | | | Data[1]: Return code | TX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set Audio TX Buffer. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Terminate an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio |
* | | | | | Data[1]: Return code | RX Instance. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set Audio RX Buffer. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Supend an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec register value. |
* | | | | | Data[1]: Return code | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec register value. |
* | | | | | Data[1]: Return code | |
* | | | | | Data[2-6]: reserved | |
* | | | | | Data[7-10]: register | |
* | | | | | Data[11-14]: value | |
* | | | | | Data[15-22]: reserved | |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
*
* SRTM Audio Control Category Notification Command Table:
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | Category | Version | Type | Command | Data | Function |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period is finished. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
* | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period is finished. |
* +------------+---------+------+---------+-------------------------------+------------------------------------------------+
*
* List of Sample Format:
* +--------------------+----------------------------------------------+
* | Sample Format | Description |
* +--------------------+----------------------------------------------+
* | 0x0 | S16_LE |
* +--------------------+----------------------------------------------+
* | 0x1 | S24_LE |
* +--------------------+----------------------------------------------+
*
* List of Audio Channels
* +--------------------+----------------------------------------------+
* | Audio Channel | Description |
* +--------------------+----------------------------------------------+
* | 0x0 | Left Channel |
* +--------------------+----------------------------------------------+
* | 0x1 | Right Channel |
* +--------------------+----------------------------------------------+
* | 0x2 | Left & Right Channel |
* +--------------------+----------------------------------------------+
*
*/
#ifndef __FSL_RPMSG_I2S_H
#define __FSL_RPMSG_I2S_H
#include <linux/pm_qos.h>
#include <linux/imx_rpmsg.h>
#include <linux/interrupt.h>
#include <sound/dmaengine_pcm.h>
#define RPMSG_TIMEOUT 1000
#define I2S_TX_OPEN 0x0
#define I2S_TX_START 0x1
#define I2S_TX_PAUSE 0x2
#define I2S_TX_RESTART 0x3
#define I2S_TX_TERMINATE 0x4
#define I2S_TX_CLOSE 0x5
#define I2S_TX_HW_PARAM 0x6
#define I2S_TX_BUFFER 0x7
#define I2S_TX_SUSPEND 0x8
#define I2S_TX_RESUME 0x9
#define I2S_RX_OPEN 0xA
#define I2S_RX_START 0xB
#define I2S_RX_PAUSE 0xC
#define I2S_RX_RESTART 0xD
#define I2S_RX_TERMINATE 0xE
#define I2S_RX_CLOSE 0xF
#define I2S_RX_HW_PARAM 0x10
#define I2S_RX_BUFFER 0x11
#define I2S_RX_SUSPEND 0x12
#define I2S_RX_RESUME 0x13
#define SET_CODEC_VALUE 0x14
#define GET_CODEC_VALUE 0x15
#define I2S_TX_POINTER 0x16
#define I2S_RX_POINTER 0x17
#define I2S_TYPE_A_NUM 0x18
#define WORK_MAX_NUM 0x30
#define I2S_TX_PERIOD_DONE 0x0
#define I2S_RX_PERIOD_DONE 0x1
#define I2S_TYPE_C_NUM 0x2
#define I2S_CMD_MAX_NUM (I2S_TYPE_A_NUM + I2S_TYPE_C_NUM)
#define I2S_TYPE_A 0x0
#define I2S_TYPE_B 0x1
#define I2S_TYPE_C 0x2
#define I2S_RESP_NONE 0x0
#define I2S_RESP_NOT_ALLOWED 0x1
#define I2S_RESP_SUCCESS 0x2
#define I2S_RESP_FAILED 0x3
#define RPMSG_S16_LE 0x0
#define RPMSG_S24_LE 0x1
#define RPMSG_S32_LE 0x2
#define RPMSG_DSD_U16_LE 0x3
#define RPMSG_DSD_U24_LE 0x4
#define RPMSG_DSD_U32_LE 0x5
#define RPMSG_CH_LEFT 0x0
#define RPMSG_CH_RIGHT 0x1
#define RPMSG_CH_STEREO 0x2
struct i2s_param_s {
unsigned char audioindex;
unsigned char format;
unsigned char channels;
unsigned int rate;
unsigned int buffer_addr; /* Register for SET_CODEC_VALUE*/
unsigned int buffer_size; /* register value for SET_CODEC_VALUE*/
unsigned int period_size;
unsigned int buffer_tail;
} __packed;
struct i2s_param_r {
unsigned char audioindex;
unsigned char resp;
unsigned char reserved1[1];
unsigned int buffer_offset; /* the consumed offset of buffer*/
unsigned int reg_addr;
unsigned int reg_data;
unsigned char reserved2[4];
unsigned int buffer_tail;
} __packed;
/* struct of send message */
struct i2s_rpmsg_s {
struct imx_rpmsg_head header;
struct i2s_param_s param;
};
/* struct of received message */
struct i2s_rpmsg_r {
struct imx_rpmsg_head header;
struct i2s_param_r param;
};
struct i2s_rpmsg {
struct i2s_rpmsg_s send_msg;
struct i2s_rpmsg_r recv_msg;
};
struct work_of_rpmsg {
struct i2s_info *i2s_info;
/* sent msg for each work */
struct i2s_rpmsg msg;
struct work_struct work;
};
struct stream_timer {
struct timer_list timer;
struct snd_pcm_substream *substream;
};
typedef void (*dma_callback)(void *arg);
struct i2s_info {
struct rpmsg_device *rpdev;
struct device *dev;
struct completion cmd_complete;
/* received msg (global) */
struct i2s_rpmsg_r recv_msg;
struct i2s_rpmsg rpmsg[I2S_CMD_MAX_NUM];
struct i2s_rpmsg period_done_msg[2];
bool period_done_msg_enabled[2];
struct workqueue_struct *rpmsg_wq;
struct work_of_rpmsg work_list[WORK_MAX_NUM];
int work_write_index;
int work_read_index;
int msg_drop_count[2];
int num_period[2];
void *callback_param[2];
int (*send_message)(struct i2s_rpmsg *msg, struct i2s_info *info);
dma_callback callback[2];
spinlock_t lock[2];
spinlock_t wq_lock;
struct mutex tx_lock;
struct mutex i2c_lock;
struct stream_timer stream_timer[2];
int prealloc_buffer_size;
};
struct fsl_rpmsg_i2s {
struct platform_device *pdev;
struct i2s_info i2s_info;
struct pm_qos_request pm_qos_req;
int codec_dummy;
int codec_wm8960;
int codec_cs42888;
int codec_ak4497;
int force_lpa;
int version;
int rates;
u64 formats;
int enable_lpa;
};
#define RPMSG_CODEC_DRV_NAME_WM8960 "rpmsg-audio-codec-wm8960"
#define RPMSG_CODEC_DRV_NAME_CS42888 "rpmsg-audio-codec-cs42888"
#define RPMSG_CODEC_DRV_NAME_AK4497 "rpmsg-audio-codec-ak4497"
struct fsl_rpmsg_codec {
int audioindex;
/*property for wm8960*/
bool capless;
bool shared_lrclk;
/*property for cs42xx8*/
char name[32];
int num_adcs;
};
#endif /* __FSL_RPMSG_I2S_H */

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2012-2013 Freescale Semiconductor, Inc.
* Copyright 2012-2016 Freescale Semiconductor, Inc.
*/
#ifndef __FSL_SAI_H
#define __FSL_SAI_H
#include <linux/pm_qos.h>
#include <sound/dmaengine_pcm.h>
#define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S20_3LE |\
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)
/* SAI Register Map Register */
#define FSL_SAI_TCSR(ofs) (0x00 + ofs) /* SAI Transmit Control */
#define FSL_SAI_TCR1(ofs) (0x04 + ofs) /* SAI Transmit Configuration 1 */
#define FSL_SAI_TCR2(ofs) (0x08 + ofs) /* SAI Transmit Configuration 2 */
#define FSL_SAI_TCR3(ofs) (0x0c + ofs) /* SAI Transmit Configuration 3 */
#define FSL_SAI_TCR4(ofs) (0x10 + ofs) /* SAI Transmit Configuration 4 */
#define FSL_SAI_TCR5(ofs) (0x14 + ofs) /* SAI Transmit Configuration 5 */
#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data 0 */
#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data 1 */
#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data 2 */
#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data 3 */
#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data 4 */
#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data 5 */
#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data 6 */
#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data 7 */
#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO 0 */
#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO 1 */
#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO 2 */
#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO 3 */
#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO 4 */
#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO 5 */
#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO 6 */
#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO 7 */
#define FSL_SAI_VERID 0x00 /* SAI Version ID Register */
#define FSL_SAI_PARAM 0x04 /* SAI Parameter Register */
#define FSL_SAI_TCSR(offset) (0x00 + offset) /* SAI Transmit Control */
#define FSL_SAI_TCR1(offset) (0x04 + offset) /* SAI Transmit Configuration 1 */
#define FSL_SAI_TCR2(offset) (0x08 + offset) /* SAI Transmit Configuration 2 */
#define FSL_SAI_TCR3(offset) (0x0c + offset) /* SAI Transmit Configuration 3 */
#define FSL_SAI_TCR4(offset) (0x10 + offset) /* SAI Transmit Configuration 4 */
#define FSL_SAI_TCR5(offset) (0x14 + offset) /* SAI Transmit Configuration 5 */
#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data */
#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data */
#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data */
#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data */
#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data */
#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data */
#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data */
#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data */
#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO */
#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO */
#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO */
#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO */
#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO */
#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO */
#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO */
#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO */
#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */
#define FSL_SAI_RCSR(ofs) (0x80 + ofs) /* SAI Receive Control */
#define FSL_SAI_RCR1(ofs) (0x84 + ofs)/* SAI Receive Configuration 1 */
#define FSL_SAI_RCR2(ofs) (0x88 + ofs) /* SAI Receive Configuration 2 */
#define FSL_SAI_RCR3(ofs) (0x8c + ofs) /* SAI Receive Configuration 3 */
#define FSL_SAI_RCR4(ofs) (0x90 + ofs) /* SAI Receive Configuration 4 */
#define FSL_SAI_RCR5(ofs) (0x94 + ofs) /* SAI Receive Configuration 5 */
#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data 0 */
#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data 1 */
#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data 2 */
#define FSL_SAI_RDR3 0xac /* SAI Receive Data 3 */
#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data 4 */
#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data 5 */
#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data 6 */
#define FSL_SAI_RDR7 0xbc /* SAI Receive Data 7 */
#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO 0 */
#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO 1 */
#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO 2 */
#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO 3 */
#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO 4 */
#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO 5 */
#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO 6 */
#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO 7 */
#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */
#define FSL_SAI_TTCTL 0x70 /* SAI Transmit Timestamp Control Register */
#define FSL_SAI_TTCTN 0x74 /* SAI Transmit Timestamp Counter Register */
#define FSL_SAI_TBCTN 0x78 /* SAI Transmit Bit Counter Register */
#define FSL_SAI_TTCAP 0x7C /* SAI Transmit Timestamp Capture */
#define FSL_SAI_xCSR(tx, ofs) (tx ? FSL_SAI_TCSR(ofs) : FSL_SAI_RCSR(ofs))
#define FSL_SAI_xCR1(tx, ofs) (tx ? FSL_SAI_TCR1(ofs) : FSL_SAI_RCR1(ofs))
#define FSL_SAI_xCR2(tx, ofs) (tx ? FSL_SAI_TCR2(ofs) : FSL_SAI_RCR2(ofs))
#define FSL_SAI_xCR3(tx, ofs) (tx ? FSL_SAI_TCR3(ofs) : FSL_SAI_RCR3(ofs))
#define FSL_SAI_xCR4(tx, ofs) (tx ? FSL_SAI_TCR4(ofs) : FSL_SAI_RCR4(ofs))
#define FSL_SAI_xCR5(tx, ofs) (tx ? FSL_SAI_TCR5(ofs) : FSL_SAI_RCR5(ofs))
#define FSL_SAI_xDR(tx, ofs) (tx ? FSL_SAI_TDR(ofs) : FSL_SAI_RDR(ofs))
#define FSL_SAI_xFR(tx, ofs) (tx ? FSL_SAI_TFR(ofs) : FSL_SAI_RFR(ofs))
#define FSL_SAI_RCSR(offset) (0x80 + offset) /* SAI Receive Control */
#define FSL_SAI_RCR1(offset) (0x84 + offset) /* SAI Receive Configuration 1 */
#define FSL_SAI_RCR2(offset) (0x88 + offset) /* SAI Receive Configuration 2 */
#define FSL_SAI_RCR3(offset) (0x8c + offset) /* SAI Receive Configuration 3 */
#define FSL_SAI_RCR4(offset) (0x90 + offset) /* SAI Receive Configuration 4 */
#define FSL_SAI_RCR5(offset) (0x94 + offset) /* SAI Receive Configuration 5 */
#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data */
#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data */
#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data */
#define FSL_SAI_RDR3 0xac /* SAI Receive Data */
#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data */
#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data */
#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data */
#define FSL_SAI_RDR7 0xbc /* SAI Receive Data */
#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO */
#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO */
#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO */
#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO */
#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO */
#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO */
#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO */
#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO */
#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */
#define FSL_SAI_RTCTL 0xf0 /* SAI Receive Timestamp Control Register */
#define FSL_SAI_RTCTN 0xf4 /* SAI Receive Timestamp Counter Register */
#define FSL_SAI_RBCTN 0xf8 /* SAI Receive Bit Counter Register */
#define FSL_SAI_RTCAP 0xfc /* SAI Receive Timestamp Capture */
#define FSL_SAI_MCTL 0x100 /* SAI MCLK Control Register */
#define FSL_SAI_MDIV 0x104 /* SAI MCLK Divide Register */
#define FSL_SAI_xCSR(tx, off) (tx ? FSL_SAI_TCSR(off) : FSL_SAI_RCSR(off))
#define FSL_SAI_xCR1(tx, off) (tx ? FSL_SAI_TCR1(off) : FSL_SAI_RCR1(off))
#define FSL_SAI_xCR2(tx, off) (tx ? FSL_SAI_TCR2(off) : FSL_SAI_RCR2(off))
#define FSL_SAI_xCR3(tx, off) (tx ? FSL_SAI_TCR3(off) : FSL_SAI_RCR3(off))
#define FSL_SAI_xCR4(tx, off) (tx ? FSL_SAI_TCR4(off) : FSL_SAI_RCR4(off))
#define FSL_SAI_xCR5(tx, off) (tx ? FSL_SAI_TCR5(off) : FSL_SAI_RCR5(off))
#define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR)
/* SAI Transmit/Receive Control Register */
#define FSL_SAI_CSR_TERE BIT(31)
#define FSL_SAI_CSR_SE BIT(30)
#define FSL_SAI_CSR_FR BIT(25)
#define FSL_SAI_CSR_SR BIT(24)
#define FSL_SAI_CSR_xF_SHIFT 16
@ -106,19 +122,29 @@
#define FSL_SAI_CR2_MSEL(ID) ((ID) << 26)
#define FSL_SAI_CR2_BCP BIT(25)
#define FSL_SAI_CR2_BCD_MSTR BIT(24)
#define FSL_SAI_CR2_BYP BIT(23) /* BCLK bypass */
#define FSL_SAI_CR2_DIV_MASK 0xff
/* SAI Transmit and Receive Configuration 3 Register */
#define FSL_SAI_CR3_TRCE BIT(16)
#define FSL_SAI_CR3_TRCE_MASK GENMASK(23, 16)
#define FSL_SAI_CR3_TRCE_MASK (0xff << 16)
#define FSL_SAI_CR3_TRCE(x) (x << 16)
#define FSL_SAI_CR3_WDFL(x) (x)
#define FSL_SAI_CR3_WDFL_MASK 0x1f
/* SAI Transmit and Receive Configuration 4 Register */
#define FSL_SAI_CR4_FCONT BIT(28)
#define FSL_SAI_CR4_FCOMB_SHIFT BIT(26)
#define FSL_SAI_CR4_FCOMB_SOFT BIT(27)
#define FSL_SAI_CR4_FCOMB_MASK (0x3 << 26)
#define FSL_SAI_CR4_FPACK_8 (0x2 << 24)
#define FSL_SAI_CR4_FPACK_16 (0x3 << 24)
#define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16)
#define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16)
#define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8)
#define FSL_SAI_CR4_SYWD_MASK (0x1f << 8)
#define FSL_SAI_CR4_CHMOD (1 << 5)
#define FSL_SAI_CR4_CHMOD_MASK (1 << 5)
#define FSL_SAI_CR4_MF BIT(4)
#define FSL_SAI_CR4_FSE BIT(3)
#define FSL_SAI_CR4_FSP BIT(1)
@ -132,6 +158,33 @@
#define FSL_SAI_CR5_FBT(x) ((x) << 8)
#define FSL_SAI_CR5_FBT_MASK (0x1f << 8)
/* SAI MCLK Control Register */
#define FSL_SAI_MCTL_MCLK_EN BIT(30) /* MCLK Enable */
#define FSL_SAI_MCTL_MSEL_MASK (0x3 << 24)
#define FSL_SAI_MCTL_MSEL(ID) ((ID) << 24)
#define FSL_SAI_MCTL_MSEL_BUS 0
#define FSL_SAI_MCTL_MSEL_MCLK1 BIT(24)
#define FSL_SAI_MCTL_MSEL_MCLK2 BIT(25)
#define FSL_SAI_MCTL_MSEL_MCLK3 (BIT(24) | BIT(25))
#define FSL_SAI_MCTL_DIV_EN BIT(23)
#define FSL_SAI_MCTL_DIV_MASK 0xFF
/* SAI VERID Register */
#define FSL_SAI_VER_ID_SHIFT 16
#define FSL_SAI_VER_ID_MASK (0xFFFF << FSL_SAI_VER_ID_SHIFT)
#define FSL_SAI_VER_EFIFO_EN BIT(0)
#define FSL_SAI_VER_TSTMP_EN BIT(1)
/* SAI PARAM Register */
#define FSL_SAI_PAR_SPF_SHIFT 16
#define FSL_SAI_PAR_SPF_MASK (0x0F << FSL_SAI_PAR_SPF_SHIFT)
#define FSL_SAI_PAR_WPF_SHIFT 8
#define FSL_SAI_PAR_WPF_MASK (0x0F << FSL_SAI_PAR_WPF_SHIFT)
#define FSL_SAI_PAR_DLN_MASK (0x0F)
/* SAI MCLK Divide Register */
#define FSL_SAI_MDIV_MASK 0xFFFFF
/* SAI type */
#define FSL_SAI_DMA BIT(0)
#define FSL_SAI_USE_AC97 BIT(1)
@ -155,11 +208,36 @@
#define FSL_SAI_MAXBURST_TX 6
#define FSL_SAI_MAXBURST_RX 6
#define SAI_FLAG_PMQOS BIT(0)
struct fsl_sai_soc_data {
bool use_imx_pcm;
bool use_edma;
unsigned int fifo_depth;
unsigned int reg_offset;
unsigned int fifos;
unsigned int dataline;
unsigned int flags;
unsigned char reg_offset;
bool imx;
/* True for EDMA because it needs period size multiple of maxburst */
bool constrain_period_size;
};
struct fsl_sai_verid {
u32 id;
bool timestamp_en;
bool extfifo_en;
bool loaded;
};
struct fsl_sai_param {
u32 spf; /* max slots per frame */
u32 wpf; /* words in fifo */
u32 dln; /* number of datalines implemented */
};
struct fsl_sai_dl_cfg {
unsigned int pins;
unsigned int mask[2];
unsigned int offset[2];
};
struct fsl_sai {
@ -167,21 +245,39 @@ struct fsl_sai {
struct regmap *regmap;
struct clk *bus_clk;
struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
struct clk *pll8k_clk;
struct clk *pll11k_clk;
bool is_slave_mode;
bool slave_mode[2];
bool is_lsb_first;
bool is_dsp_mode;
bool is_multi_lane;
bool synchronous[2];
bool is_stream_opened[2];
bool is_dsd;
int pcm_dl_cfg_cnt;
int dsd_dl_cfg_cnt;
struct fsl_sai_dl_cfg *pcm_dl_cfg;
struct fsl_sai_dl_cfg *dsd_dl_cfg;
unsigned int masterflag[2];
unsigned int mclk_id[2];
unsigned int mclk_streams;
unsigned int slots;
unsigned int slot_width;
unsigned int bclk_ratio;
unsigned int bitclk_ratio;
const struct fsl_sai_soc_data *soc_data;
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct snd_dmaengine_dai_dma_data dma_params_tx;
const struct fsl_sai_soc_data *soc;
struct pm_qos_request pm_qos_req;
struct pinctrl *pinctrl;
struct pinctrl_state *pins_state;
struct fsl_sai_verid verid;
struct fsl_sai_param param;
};
#define TX 1

View File

@ -2,7 +2,7 @@
//
// Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver
//
// Copyright (C) 2013 Freescale Semiconductor, Inc.
// Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
//
// Based on stmp3xxx_spdif_dai.c
// Vladimir Barinov <vbarinov@embeddedalley.com>
@ -11,11 +11,15 @@
#include <linux/bitrev.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <linux/busfreq-imx.h>
#include <sound/asoundef.h>
#include <sound/dmaengine_pcm.h>
@ -42,6 +46,16 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb };
#define DEFAULT_RXCLK_SRC 1
struct fsl_spdif_soc_data {
bool imx;
bool constrain_period_size;
u32 tx_burst;
u32 rx_burst;
u32 interrupts;
u64 tx_formats;
u64 rx_rates;
};
/*
* SPDIF control structure
* Defines channel status, subcode and Q sub
@ -99,15 +113,58 @@ struct fsl_spdif_priv {
u16 sysclk_df[SPDIF_TXRATE_MAX];
u8 txclk_src[SPDIF_TXRATE_MAX];
u8 rxclk_src;
struct clk *txclk[SPDIF_TXRATE_MAX];
struct clk *txclk[STC_TXCLK_SRC_MAX];
struct clk *rxclk;
struct clk *coreclk;
struct clk *sysclk;
struct clk *spbaclk;
const struct fsl_spdif_soc_data *soc;
struct snd_dmaengine_dai_dma_data dma_params_tx;
struct snd_dmaengine_dai_dma_data dma_params_rx;
/* regcache for SRPC */
u32 regcache_srpc;
struct clk *pll8k_clk;
struct clk *pll11k_clk;
};
static struct fsl_spdif_soc_data fsl_spdif_vf610 = {
.imx = false,
.tx_burst = FSL_SPDIF_TXFIFO_WML,
.rx_burst = FSL_SPDIF_RXFIFO_WML,
.interrupts = 1,
.tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
.rx_rates = FSL_SPDIF_RATES_CAPTURE,
.constrain_period_size = false,
};
static struct fsl_spdif_soc_data fsl_spdif_imx35 = {
.imx = true,
.tx_burst = FSL_SPDIF_TXFIFO_WML,
.rx_burst = FSL_SPDIF_RXFIFO_WML,
.interrupts = 1,
.tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
.rx_rates = FSL_SPDIF_RATES_CAPTURE,
.constrain_period_size = false,
};
static struct fsl_spdif_soc_data fsl_spdif_imx8qm = {
.imx = true,
.tx_burst = 2,
.rx_burst = 2,
.interrupts = 2,
.tx_formats = SNDRV_PCM_FMTBIT_S24_LE,
.rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000),
.constrain_period_size = true,
};
static struct fsl_spdif_soc_data fsl_spdif_imx8mm = {
.imx = true,
.tx_burst = FSL_SPDIF_TXFIFO_WML,
.rx_burst = FSL_SPDIF_RXFIFO_WML,
.interrupts = 1,
.tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
.rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000),
.constrain_period_size = false,
};
/* DPLL locked and lock loss interrupt handler */
@ -378,7 +435,6 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
u32 stc, mask, rate;
u16 sysclk_df;
u8 clk, txclk_df;
int ret;
switch (sample_rate) {
case 32000:
@ -420,23 +476,10 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
sysclk_df = spdif_priv->sysclk_df[rate];
/* Don't mess up the clocks from other modules */
if (clk != STC_TXCLK_SPDIF_ROOT)
goto clk_set_bypass;
/* The S/PDIF block needs a clock of 64 * fs * txclk_df */
ret = clk_set_rate(spdif_priv->txclk[rate],
64 * sample_rate * txclk_df);
if (ret) {
dev_err(&pdev->dev, "failed to set tx clock rate\n");
return ret;
}
clk_set_bypass:
dev_dbg(&pdev->dev, "expected clock rate = %d\n",
(64 * sample_rate * txclk_df * sysclk_df));
dev_dbg(&pdev->dev, "actual clock rate = %ld\n",
clk_get_rate(spdif_priv->txclk[rate]));
clk_get_rate(spdif_priv->txclk[clk]));
/* set fs field in consumer channel status */
spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs);
@ -462,25 +505,10 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
struct platform_device *pdev = spdif_priv->pdev;
struct regmap *regmap = spdif_priv->regmap;
u32 scr, mask;
int i;
int ret;
/* Reset module and interrupts only for first initialization */
if (!cpu_dai->active) {
ret = clk_prepare_enable(spdif_priv->coreclk);
if (ret) {
dev_err(&pdev->dev, "failed to enable core clock\n");
return ret;
}
if (!IS_ERR(spdif_priv->spbaclk)) {
ret = clk_prepare_enable(spdif_priv->spbaclk);
if (ret) {
dev_err(&pdev->dev, "failed to enable spba clock\n");
goto err_spbaclk;
}
}
ret = spdif_softreset(spdif_priv);
if (ret) {
dev_err(&pdev->dev, "failed to soft reset\n");
@ -498,35 +526,30 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK |
SCR_TXSEL_MASK | SCR_USRC_SEL_MASK |
SCR_TXFIFO_FSEL_MASK;
for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
ret = clk_prepare_enable(spdif_priv->txclk[i]);
if (ret)
goto disable_txclk;
}
} else {
scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC;
mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK|
SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK;
ret = clk_prepare_enable(spdif_priv->rxclk);
if (ret)
goto err;
}
regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
/* Power up SPDIF module */
regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0);
if (spdif_priv->soc->constrain_period_size) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
spdif_priv->dma_params_tx.maxburst);
else
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
spdif_priv->dma_params_rx.maxburst);
}
return 0;
disable_txclk:
for (i--; i >= 0; i--)
clk_disable_unprepare(spdif_priv->txclk[i]);
err:
if (!IS_ERR(spdif_priv->spbaclk))
clk_disable_unprepare(spdif_priv->spbaclk);
err_spbaclk:
clk_disable_unprepare(spdif_priv->coreclk);
return ret;
}
@ -536,20 +559,17 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
u32 scr, mask, i;
u32 scr, mask;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
scr = 0;
mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK |
SCR_TXSEL_MASK | SCR_USRC_SEL_MASK |
SCR_TXFIFO_FSEL_MASK;
for (i = 0; i < SPDIF_TXRATE_MAX; i++)
clk_disable_unprepare(spdif_priv->txclk[i]);
} else {
scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO;
mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK|
SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK;
clk_disable_unprepare(spdif_priv->rxclk);
}
regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
@ -558,9 +578,6 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
spdif_intr_status_clear(spdif_priv);
regmap_update_bits(regmap, REG_SPDIF_SCR,
SCR_LOW_POWER, SCR_LOW_POWER);
if (!IS_ERR(spdif_priv->spbaclk))
clk_disable_unprepare(spdif_priv->spbaclk);
clk_disable_unprepare(spdif_priv->coreclk);
}
}
@ -623,14 +640,178 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream,
return 0;
}
static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
struct clk *clk, u64 savesub,
enum spdif_txrate index, bool round)
{
static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
u64 rate_actual, sub;
u32 arate;
u16 sysclk_dfmin, sysclk_dfmax, sysclk_df;
u8 txclk_df;
/* The sysclk has an extra divisor [2, 512] */
sysclk_dfmin = is_sysclk ? 2 : 1;
sysclk_dfmax = is_sysclk ? 512 : 1;
for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
rate_actual = clk_get_rate(clk);
arate = rate_actual / 64;
arate /= txclk_df * sysclk_df;
if (arate == rate[index]) {
/* We are lucky */
savesub = 0;
spdif_priv->txclk_df[index] = txclk_df;
spdif_priv->sysclk_df[index] = sysclk_df;
spdif_priv->txrate[index] = arate;
goto out;
} else if (arate / rate[index] == 1) {
/* A little bigger than expect */
sub = (u64)(arate - rate[index]) * 100000;
do_div(sub, rate[index]);
if (sub >= savesub)
continue;
savesub = sub;
spdif_priv->txclk_df[index] = txclk_df;
spdif_priv->sysclk_df[index] = sysclk_df;
spdif_priv->txrate[index] = arate;
} else if (rate[index] / arate == 1) {
/* A little smaller than expect */
sub = (u64)(rate[index] - arate) * 100000;
do_div(sub, rate[index]);
if (sub >= savesub)
continue;
savesub = sub;
spdif_priv->txclk_df[index] = txclk_df;
spdif_priv->sysclk_df[index] = sysclk_df;
spdif_priv->txrate[index] = arate;
}
}
}
out:
return savesub;
}
static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
enum spdif_txrate index)
{
static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
struct platform_device *pdev = spdif_priv->pdev;
struct device *dev = &pdev->dev;
u64 savesub = 100000, ret;
struct clk *clk;
int i;
for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
clk = spdif_priv->txclk[i];
if (IS_ERR(clk)) {
dev_err(dev, "no rxtx%d clock in devicetree\n", i);
return PTR_ERR(clk);
}
if (!clk_get_rate(clk))
continue;
ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
i == STC_TXCLK_SPDIF_ROOT);
if (savesub == ret)
continue;
savesub = ret;
spdif_priv->txclk_src[index] = i;
/* To quick catch a divisor, we allow a 0.1% deviation */
if (savesub < 100)
break;
}
dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
spdif_priv->txclk_src[index], rate[index]);
dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
spdif_priv->txclk_df[index], rate[index]);
if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk))
dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
spdif_priv->sysclk_df[index], rate[index]);
dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
rate[index], spdif_priv->txrate[index]);
return 0;
}
static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai);
struct platform_device *pdev = data->pdev;
struct device *dev = &pdev->dev;
struct clk *clk, *p, *pll = 0, *npll = 0;
u64 ratio = freq;
int ret, i;
bool reparent = false;
if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT)
return 0;
if (data->pll8k_clk == NULL || data->pll11k_clk == NULL)
return 0;
clk = data->txclk[clk_id];
if (IS_ERR_OR_NULL(clk)) {
dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id);
return PTR_ERR(clk);
}
p = clk;
while (p && data->pll8k_clk && data->pll11k_clk) {
struct clk *pp = clk_get_parent(p);
if (clk_is_match(pp, data->pll8k_clk) ||
clk_is_match(pp, data->pll11k_clk)) {
pll = pp;
break;
}
p = pp;
}
npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk);
reparent = (pll && !clk_is_match(pll, npll));
clk_disable_unprepare(clk);
if (reparent) {
ret = clk_set_parent(p, npll);
if (ret < 0)
dev_warn(cpu_dai->dev, "failed to set parent %s: %d\n",
__clk_get_name(npll), ret);
}
ret = clk_set_rate(clk, freq);
if (ret < 0)
dev_warn(cpu_dai->dev, "failed to set clock rate (%u): %d\n",
freq, ret);
clk_prepare_enable(clk);
for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
ret = fsl_spdif_probe_txclk(data, i);
if (ret)
return ret;
}
return 0;
}
static const struct snd_soc_dai_ops fsl_spdif_dai_ops = {
.startup = fsl_spdif_startup,
.set_sysclk = fsl_spdif_set_dai_sysclk,
.hw_params = fsl_spdif_hw_params,
.trigger = fsl_spdif_trigger,
.shutdown = fsl_spdif_shutdown,
};
/*
* FSL SPDIF IEC958 controller(mixer) functions
*
@ -769,19 +950,8 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol,
}
/* Valid bit information */
static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/* Get valid good bit from interrupt status register */
static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
static int fsl_spdif_rx_vbit_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
@ -796,6 +966,68 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
return 0;
}
static int fsl_spdif_tx_vbit_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
u32 val;
regmap_read(regmap, REG_SPDIF_SCR, &val);
val = (val & SCR_VAL_MASK) >> SCR_VAL_OFFSET;
val = 1 - val;
ucontrol->value.integer.value[0] = val;
return 0;
}
static int fsl_spdif_tx_vbit_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
u32 val = (1 - ucontrol->value.integer.value[0]) << SCR_VAL_OFFSET;
regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_VAL_MASK, val);
return 0;
}
static int fsl_spdif_rx_rcm_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
u32 val;
regmap_read(regmap, REG_SPDIF_SCR, &val);
val = (val & SCR_RAW_CAPTURE_MODE) ? 1 : 0;
ucontrol->value.integer.value[0] = val;
return 0;
}
static int fsl_spdif_rx_rcm_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
u32 val = (ucontrol->value.integer.value[0] ? SCR_RAW_CAPTURE_MODE : 0);
if (val)
cpu_dai->driver->capture.formats |= SNDRV_PCM_FMTBIT_S32_LE;
else
cpu_dai->driver->capture.formats &= ~SNDRV_PCM_FMTBIT_S32_LE;
regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_RAW_CAPTURE_MODE, val);
return 0;
}
/* DPLL lock information */
static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
@ -863,18 +1095,6 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol,
return 0;
}
/* User bit sync mode info */
static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/*
* User bit sync mode:
* 1 CD User channel subcode
@ -953,11 +1173,21 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
/* Valid bit error controller */
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "IEC958 V-Bit Errors",
.name = "IEC958 Rx V-Bit Errors",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = fsl_spdif_vbit_info,
.get = fsl_spdif_vbit_get,
.info = snd_ctl_boolean_mono_info,
.get = fsl_spdif_rx_vbit_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "IEC958 Tx V-Bit",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_WRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = snd_ctl_boolean_mono_info,
.get = fsl_spdif_tx_vbit_get,
.put = fsl_spdif_tx_vbit_put,
},
/* DPLL lock info get controller */
{
@ -975,10 +1205,20 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_WRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = fsl_spdif_usync_info,
.info = snd_ctl_boolean_mono_info,
.get = fsl_spdif_usync_get,
.put = fsl_spdif_usync_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "IEC958 Rx Raw Capture Mode Bit",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_WRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = snd_ctl_boolean_mono_info,
.get = fsl_spdif_rx_rcm_get,
.put = fsl_spdif_rx_rcm_put,
},
};
static int fsl_spdif_dai_probe(struct snd_soc_dai *dai)
@ -1103,114 +1343,14 @@ static const struct regmap_config fsl_spdif_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
struct clk *clk, u64 savesub,
enum spdif_txrate index, bool round)
{
static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
u64 rate_ideal, rate_actual, sub;
u32 arate;
u16 sysclk_dfmin, sysclk_dfmax, sysclk_df;
u8 txclk_df;
/* The sysclk has an extra divisor [2, 512] */
sysclk_dfmin = is_sysclk ? 2 : 1;
sysclk_dfmax = is_sysclk ? 512 : 1;
for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
rate_ideal = rate[index] * txclk_df * 64ULL;
if (round)
rate_actual = clk_round_rate(clk, rate_ideal);
else
rate_actual = clk_get_rate(clk);
arate = rate_actual / 64;
arate /= txclk_df * sysclk_df;
if (arate == rate[index]) {
/* We are lucky */
savesub = 0;
spdif_priv->txclk_df[index] = txclk_df;
spdif_priv->sysclk_df[index] = sysclk_df;
spdif_priv->txrate[index] = arate;
goto out;
} else if (arate / rate[index] == 1) {
/* A little bigger than expect */
sub = (u64)(arate - rate[index]) * 100000;
do_div(sub, rate[index]);
if (sub >= savesub)
continue;
savesub = sub;
spdif_priv->txclk_df[index] = txclk_df;
spdif_priv->sysclk_df[index] = sysclk_df;
spdif_priv->txrate[index] = arate;
} else if (rate[index] / arate == 1) {
/* A little smaller than expect */
sub = (u64)(rate[index] - arate) * 100000;
do_div(sub, rate[index]);
if (sub >= savesub)
continue;
savesub = sub;
spdif_priv->txclk_df[index] = txclk_df;
spdif_priv->sysclk_df[index] = sysclk_df;
spdif_priv->txrate[index] = arate;
}
}
}
out:
return savesub;
}
static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
enum spdif_txrate index)
{
static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
struct platform_device *pdev = spdif_priv->pdev;
struct device *dev = &pdev->dev;
u64 savesub = 100000, ret;
struct clk *clk;
char tmp[16];
int i;
for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
sprintf(tmp, "rxtx%d", i);
clk = devm_clk_get(&pdev->dev, tmp);
if (IS_ERR(clk)) {
dev_err(dev, "no rxtx%d clock in devicetree\n", i);
return PTR_ERR(clk);
}
if (!clk_get_rate(clk))
continue;
ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
i == STC_TXCLK_SPDIF_ROOT);
if (savesub == ret)
continue;
savesub = ret;
spdif_priv->txclk[index] = clk;
spdif_priv->txclk_src[index] = i;
/* To quick catch a divisor, we allow a 0.1% deviation */
if (savesub < 100)
break;
}
dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
spdif_priv->txclk_src[index], rate[index]);
dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
spdif_priv->txclk_df[index], rate[index]);
if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk))
dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
spdif_priv->sysclk_df[index], rate[index]);
dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
rate[index], spdif_priv->txrate[index]);
return 0;
}
static const struct of_device_id fsl_spdif_dt_ids[] = {
{ .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, },
{ .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, },
{ .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, },
{ .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, },
{}
};
MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
static int fsl_spdif_probe(struct platform_device *pdev)
{
@ -1218,8 +1358,11 @@ static int fsl_spdif_probe(struct platform_device *pdev)
struct fsl_spdif_priv *spdif_priv;
struct spdif_mixer_control *ctrl;
struct resource *res;
const struct of_device_id *of_id;
void __iomem *regs;
int irq, ret, i;
char tmp[16];
int num_domains = 0;
if (!np)
return -ENODEV;
@ -1230,9 +1373,19 @@ static int fsl_spdif_probe(struct platform_device *pdev)
spdif_priv->pdev = pdev;
of_id = of_match_device(fsl_spdif_dt_ids, &pdev->dev);
if (!of_id || !of_id->data)
return -EINVAL;
spdif_priv->soc = of_id->data;
/* Initialize this copy of the CPU DAI driver structure */
memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai));
spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev);
spdif_priv->cpu_dai_drv.playback.formats =
spdif_priv->soc->tx_formats;
spdif_priv->cpu_dai_drv.capture.rates =
spdif_priv->soc->rx_rates;
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@ -1248,8 +1401,10 @@ static int fsl_spdif_probe(struct platform_device *pdev)
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
if (irq < 0) {
dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
}
ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
dev_name(&pdev->dev), spdif_priv);
@ -1258,8 +1413,50 @@ static int fsl_spdif_probe(struct platform_device *pdev)
return ret;
}
if (spdif_priv->soc->interrupts > 1) {
irq = platform_get_irq(pdev, 1);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
}
ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
dev_name(&pdev->dev), spdif_priv);
if (ret) {
dev_err(&pdev->dev, "could not claim irq %u\n", irq);
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);
}
for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
sprintf(tmp, "rxtx%d", i);
spdif_priv->txclk[i] = devm_clk_get(&pdev->dev, tmp);
if (IS_ERR(spdif_priv->txclk[i])) {
dev_err(&pdev->dev, "no rxtx%d clock in devicetree\n", i);
return PTR_ERR(spdif_priv->txclk[i]);
}
}
/* Get system clock for rx clock rate calculation */
spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5");
spdif_priv->sysclk = spdif_priv->txclk[5];
if (IS_ERR(spdif_priv->sysclk)) {
dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n");
return PTR_ERR(spdif_priv->sysclk);
@ -1277,13 +1474,21 @@ static int fsl_spdif_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "no spba clock in devicetree\n");
/* Select clock source for rx/tx clock */
spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1");
spdif_priv->rxclk = spdif_priv->txclk[1];
if (IS_ERR(spdif_priv->rxclk)) {
dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n");
return PTR_ERR(spdif_priv->rxclk);
}
spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC;
spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k");
if (IS_ERR(spdif_priv->pll8k_clk))
spdif_priv->pll8k_clk = NULL;
spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k");
if (IS_ERR(spdif_priv->pll11k_clk))
spdif_priv->pll11k_clk = NULL;
for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
ret = fsl_spdif_probe_txclk(spdif_priv, i);
if (ret)
@ -1304,11 +1509,17 @@ static int fsl_spdif_probe(struct platform_device *pdev)
spdif_priv->dpll_locked = false;
spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML;
spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML;
spdif_priv->dma_params_tx.maxburst = spdif_priv->soc->tx_burst;
spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst;
spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL;
spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL;
/*Clear the val bit for Tx*/
regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SCR,
SCR_VAL_MASK, 1 << SCR_VAL_OFFSET);
pm_runtime_enable(&pdev->dev);
/* Register with ASoC */
dev_set_drvdata(&pdev->dev, spdif_priv);
@ -1326,45 +1537,88 @@ static int fsl_spdif_probe(struct platform_device *pdev)
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int fsl_spdif_suspend(struct device *dev)
#ifdef CONFIG_PM
static int fsl_spdif_runtime_resume(struct device *dev)
{
struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
int ret;
int i;
regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
&spdif_priv->regcache_srpc);
ret = clk_prepare_enable(spdif_priv->coreclk);
if (ret) {
dev_err(dev, "failed to enable core clock\n");
return ret;
}
regcache_cache_only(spdif_priv->regmap, true);
regcache_mark_dirty(spdif_priv->regmap);
if (!IS_ERR(spdif_priv->spbaclk)) {
ret = clk_prepare_enable(spdif_priv->spbaclk);
if (ret) {
dev_err(dev, "failed to enable spba clock\n");
goto disable_core_clk;
}
}
return 0;
}
for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
ret = clk_prepare_enable(spdif_priv->txclk[i]);
if (ret)
goto disable_spba_clk;
}
static int fsl_spdif_resume(struct device *dev)
{
struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
request_bus_freq(BUS_FREQ_HIGH);
regcache_cache_only(spdif_priv->regmap, false);
regcache_mark_dirty(spdif_priv->regmap);
regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC,
SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK,
spdif_priv->regcache_srpc);
return regcache_sync(spdif_priv->regmap);
ret = regcache_sync(spdif_priv->regmap);
if (ret)
goto disable_tx_clk;
return 0;
disable_tx_clk:
disable_spba_clk:
for (i--; i >= 0; i--)
clk_disable_unprepare(spdif_priv->txclk[i]);
if (!IS_ERR(spdif_priv->spbaclk))
clk_disable_unprepare(spdif_priv->spbaclk);
disable_core_clk:
clk_disable_unprepare(spdif_priv->coreclk);
return ret;
}
#endif /* CONFIG_PM_SLEEP */
static int fsl_spdif_runtime_suspend(struct device *dev)
{
struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
int i;
regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
&spdif_priv->regcache_srpc);
regcache_cache_only(spdif_priv->regmap, true);
release_bus_freq(BUS_FREQ_HIGH);
for (i = 0; i < STC_TXCLK_SRC_MAX; i++)
clk_disable_unprepare(spdif_priv->txclk[i]);
if (!IS_ERR(spdif_priv->spbaclk))
clk_disable_unprepare(spdif_priv->spbaclk);
clk_disable_unprepare(spdif_priv->coreclk);
return 0;
}
#endif
static const struct dev_pm_ops fsl_spdif_pm = {
SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume)
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(fsl_spdif_runtime_suspend, fsl_spdif_runtime_resume,
NULL)
};
static const struct of_device_id fsl_spdif_dt_ids[] = {
{ .compatible = "fsl,imx35-spdif", },
{ .compatible = "fsl,vf610-spdif", },
{}
};
MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
static struct platform_driver fsl_spdif_driver = {
.driver = {
.name = "fsl-spdif-dai",

View File

@ -63,6 +63,7 @@
#define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET)
#define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET)
#define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET)
#define SCR_RAW_CAPTURE_MODE (1 << 14)
#define SCR_LOW_POWER (1 << 13)
#define SCR_SOFT_RESET (1 << 12)
#define SCR_TXFIFO_CTRL_OFFSET 10

View File

@ -40,6 +40,8 @@
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/busfreq-imx.h>
#include <sound/core.h>
#include <sound/pcm.h>
@ -266,6 +268,7 @@ struct fsl_ssi {
bool synchronous;
bool use_dma;
bool use_dual_fifo;
bool use_dyna_fifo;
bool has_ipg_clk_name;
unsigned int fifo_depth;
unsigned int slot_width;
@ -644,7 +647,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
* task from fifo0, fifo1 would be neglected at the end of each
* period. But SSI would still access fifo1 with an invalid data.
*/
if (ssi->use_dual_fifo)
if (ssi->use_dual_fifo || ssi->use_dyna_fifo)
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
@ -798,6 +801,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
unsigned int sample_size = params_width(hw_params);
u32 wl = SSI_SxCCR_WL(sample_size);
int ret;
struct fsl_ssi_regvals *vals = ssi->regvals;
if (fsl_ssi_is_i2s_master(ssi)) {
ret = fsl_ssi_set_bclk(substream, dai, hw_params);
@ -847,6 +851,24 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
tx2 = tx || ssi->synchronous;
regmap_update_bits(regs, REG_SSI_SxCCR(tx2), SSI_SxCCR_WL_MASK, wl);
if (ssi->use_dyna_fifo) {
if (channels == 1) {
ssi->dma_params_tx.fifo_num = 1;
ssi->dma_params_rx.fifo_num = 1;
vals[RX].srcr &= ~SSI_SRCR_RFEN1;
vals[TX].stcr &= ~SSI_STCR_TFEN1;
vals[RX].scr &= ~SSI_SCR_TCH_EN;
vals[TX].scr &= ~SSI_SCR_TCH_EN;
} else {
ssi->dma_params_tx.fifo_num = 2;
ssi->dma_params_rx.fifo_num = 2;
vals[RX].srcr |= SSI_SRCR_RFEN1;
vals[TX].stcr |= SSI_STCR_TFEN1;
vals[RX].scr |= SSI_SCR_TCH_EN;
vals[TX].scr |= SSI_SCR_TCH_EN;
}
}
return 0;
}
@ -1324,6 +1346,8 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev,
dev_dbg(dev, "failed to get baud clock: %ld\n",
PTR_ERR(ssi->baudclk));
ssi->dma_params_rx.chan_name = "rx";
ssi->dma_params_tx.chan_name = "tx";
ssi->dma_params_tx.maxburst = ssi->dma_maxburst;
ssi->dma_params_rx.maxburst = ssi->dma_maxburst;
ssi->dma_params_tx.addr = ssi->ssi_phys + REG_SSI_STX0;
@ -1349,7 +1373,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev,
if (ret)
goto error_pcm;
} else {
ret = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE);
ret = imx_pcm_platform_register(&pdev->dev);
if (ret)
goto error_pcm;
}
@ -1430,6 +1454,8 @@ static int fsl_ssi_probe_from_dt(struct fsl_ssi *ssi)
if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_SSI_DUAL)
ssi->use_dual_fifo = true;
if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_MULTI_SAI)
ssi->use_dyna_fifo = true;
/*
* Backward compatible for older bindings by manually triggering the
* machine driver's probe(). Use /compatible property, including the
@ -1510,8 +1536,10 @@ static int fsl_ssi_probe(struct platform_device *pdev)
}
ssi->irq = platform_get_irq(pdev, 0);
if (ssi->irq < 0)
if (ssi->irq < 0) {
dev_err(dev, "no irq for node %s\n", pdev->name);
return ssi->irq;
}
/* Set software limitations for synchronous mode except AC97 */
if (ssi->synchronous && !fsl_ssi_is_ac97(ssi)) {
@ -1548,6 +1576,7 @@ static int fsl_ssi_probe(struct platform_device *pdev)
}
dev_set_drvdata(dev, ssi);
pm_runtime_enable(&pdev->dev);
if (ssi->soc->imx) {
ret = fsl_ssi_imx_probe(pdev, ssi, iomem);
@ -1647,6 +1676,20 @@ static int fsl_ssi_remove(struct platform_device *pdev)
return 0;
}
#ifdef CONFIG_PM
static int fsl_ssi_runtime_resume(struct device *dev)
{
request_bus_freq(BUS_FREQ_AUDIO);
return 0;
}
static int fsl_ssi_runtime_suspend(struct device *dev)
{
release_bus_freq(BUS_FREQ_AUDIO);
return 0;
}
#endif
#ifdef CONFIG_PM_SLEEP
static int fsl_ssi_suspend(struct device *dev)
{
@ -1681,6 +1724,8 @@ static int fsl_ssi_resume(struct device *dev)
static const struct dev_pm_ops fsl_ssi_pm = {
SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume)
SET_RUNTIME_PM_OPS(fsl_ssi_runtime_suspend, fsl_ssi_runtime_resume,
NULL)
};
static struct platform_driver fsl_ssi_driver = {

246
sound/soc/fsl/hdmi_pcm.S Normal file
View File

@ -0,0 +1,246 @@
/**
* Copyright (C) 2010-2014 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
.section .text
.global hdmi_dma_copy_16_neon_lut
.global hdmi_dma_copy_16_neon_fast
.global hdmi_dma_copy_24_neon_lut
.global hdmi_dma_copy_24_neon_fast
/**
* hdmi_dma_copy_16_neon_lut
* Convert pcm sample to iec sample. Pcm sample is 16 bits.
* Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
* Frame count should be multipliable by 4, and Sample count by 8.
*
* C Prototype
* void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
* int samples, unsigned char *lookup_table);
* Return value
* None
* Parameters
* src Source PCM16 samples
* dst Dest buffer to store pcm with header
* samples Contains sample count (=frame_count * channel_count)
* lookup_table Preconstructed header table. Channels interleaved.
*/
hdmi_dma_copy_16_neon_lut:
mov r12, #1 /* construct vector(1) */
vdup.8 d6, r12
hdmi_dma_copy_16_neon_lut_start:
/* get 8 samples to q0 */
vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
/* pld [r1, #(64*4)] */
/* xor every bit */
vcnt.8 q1, q0 /* count of 1s */
vpadd.i8 d2, d2, d3 /* only care about the LST in every element */
vand d2, d2, d6 /* clear other bits while keep the least bit */
vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */
/* get packet header */
vld1.8 {d5}, [r3]!
veor d4, d5, d2 /* xor bit c */
/* store: (d4 << 16 | q0) << 8 */
vmovl.u8 q2, d4 /* expand from char to short */
vzip.16 q0, q2
vshl.u32 q0, q0, #8
vshl.u32 q1, q2, #8
vst1.32 {d0, d1, d2, d3}, [r1]!
/* decrease sample count */
subs r2, r2, #8
bne hdmi_dma_copy_16_neon_lut_start
mov pc, lr
/**
* hdmi_dma_copy_16_neon_fast
* Convert pcm sample to iec sample. Pcm sample is 16 bits.
* Frame index's between 48 and 191 inclusively.
* Channel count can be 1, 2, 4 or 8.
* Frame count should be multipliable by 4, and Sample count by 8.
*
* C Prototype
* void hdmi_dma_copy_16_neon_fast(unsigned short *src,
* unsigned int *dst, int samples);
* Return value
* None
* Parameters
* src Source PCM16 samples
* dst Dest buffer to store pcm with header
* samples Contains sample count (=frame_count * channel_count)
*/
hdmi_dma_copy_16_neon_fast:
mov r12, #1 /* construct vector(1) */
vdup.8 d6, r12
hdmi_dma_copy_16_neon_fast_start:
/* get 8 samples to q0 */
vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
/* pld [r1, #(64*4)] */
/* xor every bit */
vcnt.8 q1, q0 /* count of 1s */
vpadd.i8 d2, d2, d3
vand d2, d2, d6 /* clear other bits while keep the LST */
/* finally we construct packet header */
vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */
/* get packet header: always 0 */
/* store: (d4 << 16 | q0) << 8 */
vmovl.u8 q2, d4 /* expand from char to short */
vzip.16 q0, q2
vshl.u32 q0, q0, #8
vshl.u32 q1, q2, #8
vst1.32 {d0, d1, d2, d3}, [r1]!
/* decrease sample count */
subs r2, r2, #8
bne hdmi_dma_copy_16_neon_fast_start
mov pc, lr
/**
* hdmi_dma_copy_24_neon_lut
* Convert pcm sample to iec sample. Pcm sample is 24 bits.
* Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
* Frame count should be multipliable by 4, and Sample count by 8.
*
* C Prototype
* void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
* int samples, unsigned char *lookup_table);
* Return value
* None
* Parameters
* src Source PCM24 samples
* dst Dest buffer to store pcm with header
* samples Contains sample count (=frame_count * channel_count)
* lookup_table Preconstructed header table. Channels interleaved.
*/
hdmi_dma_copy_24_neon_lut:
vpush {d8}
mov r12, #1 /* construct vector(1) */
vdup.8 d8, r12
hdmi_dma_copy_24_neon_lut_start:
/* get 8 samples to q0 and q1 */
vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
/* pld [r1, #(64*4)] */
/* xor every bit */
vcnt.8 q2, q0 /* count of 1s */
vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */
vcnt.8 q3, q1
vpadd.i8 d6, d6, d7
vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
vand d4, d4, d8 /* clear other bits while keep the least bit */
vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
/* get packet header */
vld1.8 {d5}, [r3]!/* d5: original header */
veor d5, d5, d4 /* fix bit p */
/* store: (d5 << 24 | q0) */
vmovl.u8 q3, d5 /* expand from char to short */
vmovl.u16 q2, d6 /* expand from short to int */
vmovl.u16 q3, d7
vshl.u32 q2, q2, #24
vshl.u32 q3, q3, #24
vorr q0, q0, q2
vorr q1, q1, q3
vst1.32 {d0, d1, d2, d3}, [r1]!
/* decrease sample count */
subs r2, r2, #8
bne hdmi_dma_copy_24_neon_lut_start
vpop {d8}
mov pc, lr
/**
* hdmi_dma_copy_24_neon_fast
* Convert pcm sample to iec sample. Pcm sample is 24 bits.
* Frame index's between 48 and 191 inclusively.
* Channel count can be 1, 2, 4 or 8.
* Frame count should be multipliable by 4, and Sample count by 8.
*
* C Prototype
* void hdmi_dma_copy_24_neon_fast(unsigned int *src,
* unsigned int *dst, int samples);
* Return value
* None
* Parameters
* src Source PCM24 samples
* dst Dest buffer to store pcm with header
* samples Contains sample count (=frame_count * channel_count)
*/
hdmi_dma_copy_24_neon_fast:
vpush {d8}
mov r12, #1 /* construct vector(1) */
vdup.8 d8, r12
hdmi_dma_copy_24_neon_fast_start:
/* get 8 samples to q0 and q1 */
vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
/* pld [r1, #(64*4)] */
/* xor every bit */
vcnt.8 q2, q0 /* count of 1s */
vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */
vcnt.8 q3, q1
vpadd.i8 d6, d6, d7
vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
vand d4, d4, d8 /* clear other bits while keep the least bit */
vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
/* store: (d4 << 24 | q0) */
vmovl.u8 q3, d4 /* expand from char to short */
vmovl.u16 q2, d6 /* expand from short to int */
vmovl.u16 q3, d7
vshl.u32 q2, q2, #24
vshl.u32 q3, q3, #24
vorr q0, q0, q2
vorr q1, q1, q3
vst1.32 {d0, d1, d2, d3}, [r1]!
/* decrease sample count */
subs r2, r2, #8
bne hdmi_dma_copy_24_neon_fast_start
vpop {d8}
mov pc, lr

417
sound/soc/fsl/imx-ak4458.c Normal file
View File

@ -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");

269
sound/soc/fsl/imx-ak4497.c Normal file
View File

@ -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");

485
sound/soc/fsl/imx-ak5558.c Normal file
View File

@ -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");

561
sound/soc/fsl/imx-cdnhdmi.c Normal file
View File

@ -0,0 +1,561 @@
/*
* Copyright 2017-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 <sound/hdmi-codec.h>
#include <drm/drm_connector.h>
#include "fsl_sai.h"
#define SUPPORT_RATE_NUM 10
#define SUPPORT_CHANNEL_NUM 10
struct imx_cdnhdmi_data {
struct snd_soc_dai_link dai;
struct snd_soc_card card;
int protocol;
u32 support_rates[SUPPORT_RATE_NUM];
u32 support_rates_num;
u32 support_channels[SUPPORT_CHANNEL_NUM];
u32 support_channels_num;
u32 edid_rates[SUPPORT_RATE_NUM];
u32 edid_rates_count;
u32 edid_channels[SUPPORT_CHANNEL_NUM];
u32 edid_channels_count;
uint8_t eld[MAX_ELD_BYTES];
};
static int imx_cdnhdmi_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_cdnhdmi_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;
constraint_rates.list = data->support_rates;
constraint_rates.count = data->support_rates_num;
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraint_rates);
if (ret)
return ret;
constraint_channels.list = data->support_channels;
constraint_channels.count = data->support_channels_num;
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraint_channels);
if (ret)
return ret;
return 0;
}
static int imx_cdnhdmi_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;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret;
/* set cpu DAI configuration */
if (tx)
ret = snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
else
ret = snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret) {
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
return ret;
}
if (of_device_is_compatible(dev->of_node,
"fsl,imx8mq-evk-cdnhdmi"))
ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1,
256 * params_rate(params),
SND_SOC_CLOCK_OUT);
else
ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
0,
tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN);
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, 32);
if (ret) {
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
return ret;
}
return 0;
}
static struct snd_soc_ops imx_cdnhdmi_ops = {
.startup = imx_cdnhdmi_startup,
.hw_params = imx_cdnhdmi_hw_params,
};
static const unsigned int eld_rates[] = {
32000,
44100,
48000,
88200,
96000,
176400,
192000,
};
static unsigned int sad_max_channels(const u8 *sad)
{
return 1 + (sad[0] & 7);
}
static int get_edid_info(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 snd_soc_component *component = codec_dai->component;
struct hdmi_codec_pdata *hcd = component->dev->platform_data;
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
int i, j, ret;
const u8 *sad;
unsigned int channel_max = 0;
unsigned int rate_mask = 0;
unsigned int rate_mask_eld = 0;
ret = hcd->ops->get_eld(component->dev->parent, hcd->data,
data->eld, sizeof(data->eld));
sad = drm_eld_sad(data->eld);
if (sad) {
for (j = 0; j < data->support_rates_num; j++) {
for (i = 0; i < ARRAY_SIZE(eld_rates); i++)
if (eld_rates[i] == data->support_rates[j])
rate_mask |= BIT(i);
}
for (i = drm_eld_sad_count(data->eld); i > 0; i--, sad += 3) {
if (rate_mask & sad[1])
channel_max = max(channel_max, sad_max_channels(sad));
if (sad_max_channels(sad) >= 2)
rate_mask_eld |= sad[1];
}
}
rate_mask = rate_mask & rate_mask_eld;
data->edid_rates_count = 0;
data->edid_channels_count = 0;
for (i = 0; i < ARRAY_SIZE(eld_rates); i++) {
if (rate_mask & BIT(i)) {
data->edid_rates[data->edid_rates_count] = eld_rates[i];
data->edid_rates_count++;
}
}
for (i = 0; i < data->support_channels_num; i++) {
if (data->support_channels[i] <= channel_max) {
data->edid_channels[data->edid_channels_count]
= data->support_channels[i];
data->edid_channels_count++;
}
}
return 0;
}
static int imx_cdnhdmi_channels_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
get_edid_info(card);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = data->edid_channels_count;
return 0;
}
static int imx_cdnhdmi_channels_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
int i;
get_edid_info(card);
for (i = 0 ; i < data->edid_channels_count ; i++)
uvalue->value.integer.value[i] = data->edid_channels[i];
return 0;
}
static int imx_cdnhdmi_rates_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
get_edid_info(card);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = data->edid_rates_count;
return 0;
}
static int imx_cdnhdmi_rates_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
int i;
get_edid_info(card);
for (i = 0 ; i < data->edid_rates_count; i++)
uvalue->value.integer.value[i] = data->edid_rates[i];
return 0;
}
static int imx_cdnhdmi_formats_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 3;
return 0;
}
static int imx_cdnhdmi_formats_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
uvalue->value.integer.value[0] = 16;
uvalue->value.integer.value[1] = 24;
uvalue->value.integer.value[2] = 32;
return 0;
}
static int get_edid_rx_info(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 snd_soc_component *component = codec_dai->component;
struct hdmi_codec_pdata *hcd = component->dev->platform_data;
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
int ret;
ret = hcd->ops->get_eld(component->dev->parent, hcd->data,
data->eld, sizeof(data->eld));
if (ret)
return -EINVAL;
data->edid_rates[0] = data->eld[0] +
(data->eld[1] << 8) +
(data->eld[2] << 16) +
(data->eld[3] << 24);
data->edid_channels[0] = data->eld[4] +
(data->eld[5] << 8) +
(data->eld[6] << 16) +
(data->eld[7] << 24);
return 0;
}
static int imx_cdnhdmi_rx_channels_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 2;
uinfo->value.integer.max = 8;
return 0;
}
static int imx_cdnhdmi_rx_channels_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
int ret;
ret = get_edid_rx_info(card);
if (ret)
return ret;
uvalue->value.integer.value[0] = data->edid_channels[0];
return 0;
}
static int imx_cdnhdmi_rx_rates_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 16000;
uinfo->value.integer.max = 192000;
return 0;
}
static int imx_cdnhdmi_rx_rates_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
int ret;
ret = get_edid_rx_info(card);
if (ret)
return ret;
uvalue->value.integer.value[0] = data->edid_rates[0];
return 0;
}
static struct snd_kcontrol_new imx_cdnhdmi_ctrls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Support Channels",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = imx_cdnhdmi_channels_info,
.get = imx_cdnhdmi_channels_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Support Rates",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = imx_cdnhdmi_rates_info,
.get = imx_cdnhdmi_rates_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Support Formats",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = imx_cdnhdmi_formats_info,
.get = imx_cdnhdmi_formats_get,
},
};
static struct snd_kcontrol_new imx_cdnhdmi_rx_ctrls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Rx Channels",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = imx_cdnhdmi_rx_channels_info,
.get = imx_cdnhdmi_rx_channels_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Rx Rates",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = imx_cdnhdmi_rx_rates_info,
.get = imx_cdnhdmi_rx_rates_get,
},
};
static int imx_cdnhdmi_probe(struct platform_device *pdev)
{
struct device_node *cpu_np, *cdnhdmi_np = NULL;
struct platform_device *cpu_pdev;
struct imx_cdnhdmi_data *data;
struct snd_soc_dai_link_component *dlc;
int ret;
int i;
dlc = devm_kzalloc(&pdev->dev, 3 * 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;
}
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;
}
for (i = 0; i < SUPPORT_RATE_NUM; i++) {
ret = of_property_read_u32_index(pdev->dev.of_node,
"constraint-rate",
i, &data->support_rates[i]);
if (!ret)
data->support_rates_num = i + 1;
else
break;
}
if (data->support_rates_num == 0) {
data->support_rates[0] = 48000;
data->support_rates[1] = 96000;
data->support_rates[2] = 32000;
data->support_rates[3] = 192000;
data->support_rates_num = 4;
}
data->support_channels[0] = 2;
data->support_channels[1] = 4;
data->support_channels[2] = 8;
data->support_channels_num = 3;
of_property_read_u32(pdev->dev.of_node, "protocol",
&data->protocol);
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 = "imx8 hdmi";
data->dai.stream_name = "imx8 hdmi";
data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
data->dai.platforms->of_node = cpu_np;
data->dai.ops = &imx_cdnhdmi_ops;
data->dai.playback_only = true;
data->dai.capture_only = false;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;
if (of_property_read_bool(pdev->dev.of_node, "hdmi-out")) {
data->dai.playback_only = true;
data->dai.capture_only = false;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;
data->dai.codecs->dai_name = "i2s-hifi";
data->dai.codecs->name = "hdmi-audio-codec.1";
data->card.controls = imx_cdnhdmi_ctrls;
data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_ctrls);
}
if (of_property_read_bool(pdev->dev.of_node, "hdmi-in")) {
data->dai.playback_only = false;
data->dai.capture_only = true;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM;
data->dai.codecs->dai_name = "i2s-hifi";
data->dai.codecs->name = "hdmi-audio-codec.2";
data->card.controls = imx_cdnhdmi_rx_ctrls;
data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_rx_ctrls);
}
if ((data->dai.playback_only && data->dai.capture_only)
|| (!data->dai.playback_only && !data->dai.capture_only)) {
dev_err(&pdev->dev, "Wrongly enable HDMI DAI link\n");
goto fail;
}
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 (cdnhdmi_np)
of_node_put(cdnhdmi_np);
return ret;
}
static const struct of_device_id imx_cdnhdmi_dt_ids[] = {
{ .compatible = "fsl,imx8mq-evk-cdnhdmi", },
{ .compatible = "fsl,imx-audio-cdnhdmi", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_cdnhdmi_dt_ids);
static struct platform_driver imx_cdnhdmi_driver = {
.driver = {
.name = "imx-cdnhdmi",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = imx_cdnhdmi_dt_ids,
},
.probe = imx_cdnhdmi_probe,
};
module_platform_driver(imx_cdnhdmi_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:imx-cdnhdmi");

502
sound/soc/fsl/imx-cs42888.c Normal file
View File

@ -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");

227
sound/soc/fsl/imx-dsp.c Normal file
View File

@ -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");

1193
sound/soc/fsl/imx-hdmi-dma.c Normal file

File diff suppressed because it is too large Load Diff

117
sound/soc/fsl/imx-hdmi.c Normal file
View File

@ -0,0 +1,117 @@
/*
* ASoC HDMI Transmitter driver for IMX development boards
*
* Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
*
* based on stmp3780_devb_hdmi.c
*
* Vladimir Barinov <vbarinov@embeddedalley.com>
*
* Copyright 2008 SigmaTel, Inc
* Copyright 2008 Embedded Alley Solutions, 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.
*/
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/mfd/mxc-hdmi-core.h>
#include <sound/soc.h>
#include "imx-hdmi.h"
SND_SOC_DAILINK_DEFS(hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC("hdmi-audio-codec", "i2s-hifi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("imx-hdmi-audio")));
/* imx digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link imx_hdmi_dai_link = {
.name = "i.MX HDMI Audio Tx",
.stream_name = "i.MX HDMI Audio Tx",
SND_SOC_DAILINK_REG(hifi),
};
static struct snd_soc_card snd_soc_card_imx_hdmi = {
.name = "imx-hdmi-soc",
.dai_link = &imx_hdmi_dai_link,
.num_links = 1,
.owner = THIS_MODULE,
};
static int imx_hdmi_audio_probe(struct platform_device *pdev)
{
struct device_node *hdmi_np, *np = pdev->dev.of_node;
struct snd_soc_card *card = &snd_soc_card_imx_hdmi;
struct platform_device *hdmi_pdev;
int ret = 0;
if (!hdmi_get_registered()) {
dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n");
return -ENODEV;
}
hdmi_np = of_parse_phandle(np, "hdmi-controller", 0);
if (!hdmi_np) {
dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n");
ret = -EINVAL;
goto end;
}
hdmi_pdev = of_find_device_by_node(hdmi_np);
if (!hdmi_pdev) {
dev_err(&pdev->dev, "failed to find SSI platform device\n");
ret = -EINVAL;
goto end;
}
card->dev = &pdev->dev;
card->dai_link->cpus->dai_name = dev_name(&hdmi_pdev->dev);
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 (hdmi_np)
of_node_put(hdmi_np);
return ret;
}
static int imx_hdmi_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id imx_hdmi_dt_ids[] = {
{ .compatible = "fsl,imx-audio-hdmi", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
static struct platform_driver imx_hdmi_audio_driver = {
.probe = imx_hdmi_audio_probe,
.remove = imx_hdmi_audio_remove,
.driver = {
.of_match_table = imx_hdmi_dt_ids,
.name = "imx-audio-hdmi",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
};
module_platform_driver(imx_hdmi_audio_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("IMX HDMI TX ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx-audio-hdmi");

106
sound/soc/fsl/imx-hdmi.h Normal file
View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2011-2014 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __IMX_HDMI_H
#define __IMX_HDMI_H
struct imx_hdmi_sdma_params {
dma_addr_t phyaddr;
u32 buffer_num;
int dma;
};
struct imx_hdmi {
struct snd_soc_dai_driver cpu_dai_drv;
struct platform_device *codec_dev;
struct platform_device *dma_dev;
struct platform_device *pdev;
struct clk *isfr_clk;
struct clk *iahb_clk;
struct clk *mipi_core_clk;
};
#define HDMI_MAX_RATES 7
#define HDMI_MAX_SAMPLE_SIZE 3
#define HDMI_MAX_CHANNEL_CONSTRAINTS 4
#define MXC_HDMI_RATES_PLAYBACK \
(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
#define MXC_HDMI_FORMATS_PLAYBACK \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
union hdmi_audio_header_t {
uint64_t U;
struct {
unsigned consumer:1;
unsigned linear_pcm:1;
unsigned copyright:1;
unsigned pre_emphasis:3;
unsigned mode:2;
unsigned category_code:8;
unsigned source:4;
unsigned channel:4;
unsigned sample_freq:4;
unsigned clock_acc:2;
unsigned reserved0:2;
unsigned word_length:4;
unsigned org_sample_freq:4;
unsigned cgms_a:2;
unsigned reserved1:6;
unsigned reserved2:8;
unsigned reserved3:8;
} B;
unsigned char status[8];
};
union hdmi_audio_dma_data_t {
uint32_t U;
struct {
unsigned data:24;
unsigned v:1;
unsigned u:1;
unsigned c:1;
unsigned p:1;
unsigned b:1;
unsigned reserved:3;
} B;
};
extern union hdmi_audio_header_t iec_header;
#define hdmi_audio_writeb(reg, bit, val) \
do { \
hdmi_mask_writeb(val, HDMI_ ## reg, \
HDMI_ ## reg ## _ ## bit ## _OFFSET, \
HDMI_ ## reg ## _ ## bit ## _MASK); \
pr_debug("Set reg: HDMI_" #reg " (0x%x) "\
"bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \
HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \
} while (0)
#endif /* __IMX_HDMI_H */

Some files were not shown because too many files have changed in this diff Show More