Merge remote-tracking branch 'origin/audio/rpmsg' into audio/next

* origin/audio/rpmsg: (59 commits)
  LF-215: ASoC: fsl_rpmsg_i2s: Enable WQ_FREEZABLE for workqueue
  ASoC: imx-pcm-rpmsg: Remove the prtd
  ASoC: imx-pcm-rpmsg: Fix writecombine/wc build error
  ASoC: rpmsg_ak4497: ignore suspend with DAPM
  Revert "ASoC: soc-pcm: remove soc_rtdcom_ack()"
  ...
This commit is contained in:
Dong Aisheng 2019-12-02 18:00:43 +08:00
commit d7ba9dc826
16 changed files with 5779 additions and 0 deletions

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

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

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

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

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

@ -90,6 +90,13 @@ config SND_SOC_FSL_DSP
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.
@ -103,6 +110,11 @@ 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
@ -405,6 +417,20 @@ config SND_SOC_IMX_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)

View File

@ -29,6 +29,7 @@ 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
@ -43,6 +44,7 @@ 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
@ -63,6 +65,7 @@ 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
@ -90,6 +93,7 @@ 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
@ -103,6 +107,7 @@ 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

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

View File

@ -0,0 +1,893 @@
/*
* imx-rpmsg-platform.c -- ALSA Soc Audio Layer
*
* 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/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/imx_rpmsg.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>
#include "imx-pcm.h"
#include "fsl_rpmsg_i2s.h"
#include "../../core/pcm_local.h"
#define DRV_NAME "imx_pcm_rpmsg"
struct i2s_info *i2s_info_g;
static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.buffer_bytes_max = IMX_SAI_DMABUF_SIZE,
.period_bytes_min = 512,
.period_bytes_max = 65532, /* Limited by SDMA engine */
.periods_min = 2,
.periods_max = 6000,
.fifo_size = 0,
};
static int imx_rpmsg_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_HW_PARAM];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_HW_PARAM];
rpmsg->send_msg.param.rate = params_rate(params);
if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
rpmsg->send_msg.param.format = RPMSG_S16_LE;
else if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE)
rpmsg->send_msg.param.format = RPMSG_S24_LE;
else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U16_LE)
rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U16_LE;
else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U32_LE)
rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U32_LE;
else
rpmsg->send_msg.param.format = RPMSG_S32_LE;
if (params_channels(params) == 1)
rpmsg->send_msg.param.channels = RPMSG_CH_LEFT;
else
rpmsg->send_msg.param.channels = RPMSG_CH_STEREO;
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = params_buffer_bytes(params);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_HW_PARAM;
else
rpmsg->send_msg.header.cmd = I2S_RX_HW_PARAM;
i2s_info->send_message(rpmsg, i2s_info);
return 0;
}
static int imx_rpmsg_pcm_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
unsigned int pos = 0;
struct i2s_rpmsg *rpmsg;
int buffer_tail = 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
buffer_tail = rpmsg->recv_msg.param.buffer_tail;
pos = buffer_tail * snd_pcm_lib_period_bytes(substream);
return bytes_to_frames(substream->runtime, pos);
}
static void imx_rpmsg_timer_callback(struct timer_list *t)
{
struct stream_timer *stream_timer =
from_timer(stream_timer, t, timer);
struct snd_pcm_substream *substream = stream_timer->substream;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
unsigned long flags;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE;
else
rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE;
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
} else
i2s_info->msg_drop_count[substream->stream]++;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
}
static int imx_rpmsg_pcm_open(struct snd_pcm_substream *substream)
{
int ret = 0;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
int cmd;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_OPEN];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_OPEN];
imx_rpmsg_pcm_hardware.buffer_bytes_max =
i2s_info->prealloc_buffer_size;
imx_rpmsg_pcm_hardware.period_bytes_max =
imx_rpmsg_pcm_hardware.buffer_bytes_max / 2;
snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_OPEN;
else
rpmsg->send_msg.header.cmd = I2S_RX_OPEN;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM;
i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
} else {
cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM;
i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
}
i2s_info->send_message(rpmsg, i2s_info);
ret = snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
return ret;
i2s_info->msg_drop_count[substream->stream] = 0;
/*create thread*/
i2s_info->stream_timer[substream->stream].substream = substream;
timer_setup(&i2s_info->stream_timer[substream->stream].timer,
imx_rpmsg_timer_callback, 0);
return ret;
}
static int imx_rpmsg_pcm_close(struct snd_pcm_substream *substream)
{
int ret = 0;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_CLOSE];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_CLOSE];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_CLOSE;
else
rpmsg->send_msg.header.cmd = I2S_RX_CLOSE;
flush_workqueue(i2s_info->rpmsg_wq);
i2s_info->send_message(rpmsg, i2s_info);
del_timer(&i2s_info->stream_timer[substream->stream].timer);
rtd->dai_link->ignore_suspend = 0;
if (i2s_info->msg_drop_count[substream->stream])
dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
i2s_info->msg_drop_count[substream->stream]);
return ret;
}
static int imx_rpmsg_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
/* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
* four condition to determine the lpa is enabled.
*/
if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
rpmsg_i2s->version == 2 &&
rpmsg_i2s->enable_lpa) {
rtd->dai_link->ignore_suspend = 1;
rpmsg_i2s->force_lpa = 1;
} else
rpmsg_i2s->force_lpa = 0;
return 0;
}
static int imx_rpmsg_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
return dma_mmap_wc(substream->pcm->card->dev, vma,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
}
static void imx_rpmsg_pcm_dma_complete(void *arg)
{
struct snd_pcm_substream *substream = arg;
snd_pcm_period_elapsed(substream);
}
static int imx_rpmsg_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
unsigned long flags;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_BUFFER];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_BUFFER];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_BUFFER;
else
rpmsg->send_msg.header.cmd = I2S_RX_BUFFER;
rpmsg->send_msg.param.buffer_addr = substream->runtime->dma_addr;
rpmsg->send_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
rpmsg->send_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
rpmsg->send_msg.param.buffer_tail = 0;
i2s_info->num_period[substream->stream] =
rpmsg->send_msg.param.buffer_size /
rpmsg->send_msg.param.period_size;
i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
i2s_info->callback_param[substream->stream] = substream;
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
} else {
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
return -EPIPE;
}
return 0;
}
static int imx_rpmsg_async_issue_pending(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
unsigned long flags;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_START];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_START];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_START;
else
rpmsg->send_msg.header.cmd = I2S_RX_START;
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
} else {
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
return -EPIPE;
}
return 0;
}
static int imx_rpmsg_restart(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
unsigned long flags;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_RESTART];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_RESTART];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_RESTART;
else
rpmsg->send_msg.header.cmd = I2S_RX_RESTART;
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
} else {
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
return -EPIPE;
}
return 0;
}
static int imx_rpmsg_pause(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
unsigned long flags;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_PAUSE];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_PAUSE];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_PAUSE;
else
rpmsg->send_msg.header.cmd = I2S_RX_PAUSE;
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
} else {
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
return -EPIPE;
}
return 0;
}
static int imx_rpmsg_terminate_all(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
int cmd;
unsigned long flags;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_TERMINATE];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_TERMINATE];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_TERMINATE;
else
rpmsg->send_msg.header.cmd = I2S_RX_TERMINATE;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM;
i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
} else {
cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM;
i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
}
del_timer(&i2s_info->stream_timer[substream->stream].timer);
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
} else {
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
return -EPIPE;
}
return 0;
}
int imx_rpmsg_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
ret = imx_rpmsg_pcm_prepare_and_submit(substream);
if (ret)
return ret;
ret = imx_rpmsg_async_issue_pending(substream);
break;
case SNDRV_PCM_TRIGGER_RESUME:
if (rpmsg_i2s->force_lpa)
break;
/* fall through */
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = imx_rpmsg_restart(substream);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
if (!rpmsg_i2s->force_lpa) {
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
ret = imx_rpmsg_pause(substream);
else
ret = imx_rpmsg_terminate_all(substream);
}
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ret = imx_rpmsg_pause(substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
ret = imx_rpmsg_terminate_all(substream);
break;
default:
return -EINVAL;
}
if (ret)
return ret;
return 0;
}
int imx_rpmsg_pcm_ack(struct snd_pcm_substream *substream)
{
/*send the hw_avail size through rpmsg*/
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
struct i2s_rpmsg *rpmsg;
int buffer_tail = 0;
int writen_num = 0;
snd_pcm_sframes_t avail;
unsigned long flags;
if (!rpmsg_i2s->force_lpa)
return 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
else
rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE;
else
rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE;
rpmsg->send_msg.header.type = I2S_TYPE_C;
buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) %
snd_pcm_lib_buffer_bytes(substream));
buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream);
if (buffer_tail != rpmsg->send_msg.param.buffer_tail) {
writen_num = buffer_tail - rpmsg->send_msg.param.buffer_tail;
if (writen_num < 0)
writen_num += runtime->periods;
rpmsg->send_msg.param.buffer_tail = buffer_tail;
spin_lock_irqsave(&i2s_info->lock[substream->stream], flags);
memcpy(&i2s_info->period_done_msg[substream->stream], rpmsg,
sizeof(struct i2s_rpmsg_s));
i2s_info->period_done_msg_enabled[substream->stream] = true;
spin_unlock_irqrestore(&i2s_info->lock[substream->stream], flags);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
avail = snd_pcm_playback_hw_avail(runtime);
else
avail = snd_pcm_capture_hw_avail(runtime);
if ((avail - writen_num * runtime->period_size) <= runtime->period_size) {
spin_lock_irqsave(&i2s_info->wq_lock, flags);
if (i2s_info->work_write_index != i2s_info->work_read_index) {
int index = i2s_info->work_write_index;
memcpy(&i2s_info->work_list[index].msg, rpmsg,
sizeof(struct i2s_rpmsg_s));
queue_work(i2s_info->rpmsg_wq,
&i2s_info->work_list[index].work);
i2s_info->work_write_index++;
i2s_info->work_write_index %= WORK_MAX_NUM;
} else
i2s_info->msg_drop_count[substream->stream]++;
spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
} else {
if (rpmsg_i2s->force_lpa && !timer_pending(&i2s_info->stream_timer[substream->stream].timer)) {
int time_msec;
time_msec = (int)(runtime->period_size*1000/runtime->rate);
mod_timer(&i2s_info->stream_timer[substream->stream].timer,
jiffies + msecs_to_jiffies(time_msec));
}
}
}
return 0;
}
static struct snd_pcm_ops imx_rpmsg_pcm_ops = {
.open = imx_rpmsg_pcm_open,
.close = imx_rpmsg_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = imx_rpmsg_pcm_hw_params,
.hw_free = imx_rpmsg_pcm_hw_free,
.trigger = imx_rpmsg_pcm_trigger,
.pointer = imx_rpmsg_pcm_pointer,
.mmap = imx_rpmsg_pcm_mmap,
.ack = imx_rpmsg_pcm_ack,
.prepare = imx_rpmsg_pcm_prepare,
};
static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
int stream, int size)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_wc(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
static void imx_rpmsg_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
int stream;
for (stream = SNDRV_PCM_STREAM_PLAYBACK;
stream < SNDRV_PCM_STREAM_LAST; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
buf = &substream->dma_buffer;
if (!buf->area)
continue;
dma_free_wc(pcm->card->dev, buf->bytes,
buf->area, buf->addr);
buf->area = NULL;
}
}
static int imx_rpmsg_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK,
i2s_info->prealloc_buffer_size);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE,
i2s_info->prealloc_buffer_size);
if (ret)
goto out;
}
out:
/* free preallocated buffers in case of error */
if (ret)
imx_rpmsg_pcm_free_dma_buffers(pcm);
return ret;
}
static struct snd_soc_component_driver imx_rpmsg_soc_component = {
.name = DRV_NAME,
.ops = &imx_rpmsg_pcm_ops,
.pcm_new = imx_rpmsg_pcm_new,
.pcm_free = imx_rpmsg_pcm_free_dma_buffers,
};
int imx_rpmsg_platform_register(struct device *dev)
{
struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
struct snd_soc_component *component;
int ret;
i2s_info_g = &rpmsg_i2s->i2s_info;
ret = devm_snd_soc_register_component(dev, &imx_rpmsg_soc_component,
NULL, 0);
if (ret)
return ret;
component = snd_soc_lookup_component(dev, DRV_NAME);
if (!component)
return -EINVAL;
#ifdef CONFIG_DEBUG_FS
component->debugfs_prefix = "dma";
#endif
return 0;
}
EXPORT_SYMBOL_GPL(imx_rpmsg_platform_register);
static int i2s_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
struct i2s_rpmsg_r *msg = (struct i2s_rpmsg_r *)data;
struct i2s_rpmsg *rpmsg;
unsigned long flags;
dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n",
src, msg->header.cmd, msg->param.resp);
if (msg->header.type == I2S_TYPE_C) {
if (msg->header.cmd == I2S_TX_PERIOD_DONE) {
spin_lock_irqsave(&i2s_info_g->lock[0], flags);
rpmsg = &i2s_info_g->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
if (msg->header.major == 1 && msg->header.minor == 2)
rpmsg->recv_msg.param.buffer_tail =
msg->param.buffer_tail;
else
rpmsg->recv_msg.param.buffer_tail++;
rpmsg->recv_msg.param.buffer_tail %=
i2s_info_g->num_period[0];
spin_unlock_irqrestore(&i2s_info_g->lock[0], flags);
i2s_info_g->callback[0](i2s_info_g->callback_param[0]);
} else if (msg->header.cmd == I2S_RX_PERIOD_DONE) {
spin_lock_irqsave(&i2s_info_g->lock[1], flags);
rpmsg = &i2s_info_g->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
if (msg->header.major == 1 && msg->header.minor == 2)
rpmsg->recv_msg.param.buffer_tail =
msg->param.buffer_tail;
else
rpmsg->recv_msg.param.buffer_tail++;
rpmsg->recv_msg.param.buffer_tail %=
i2s_info_g->num_period[1];
spin_unlock_irqrestore(&i2s_info_g->lock[1], flags);
i2s_info_g->callback[1](i2s_info_g->callback_param[1]);
}
}
if (msg->header.type == I2S_TYPE_B) {
memcpy(&i2s_info_g->recv_msg, msg, sizeof(struct i2s_rpmsg_r));
complete(&i2s_info_g->cmd_complete);
}
return 0;
}
static int i2s_rpmsg_probe(struct rpmsg_device *rpdev)
{
struct platform_device *codec_pdev;
struct fsl_rpmsg_i2s *rpmsg_i2s = NULL;
struct fsl_rpmsg_codec rpmsg_codec[3];
int ret;
if (!i2s_info_g)
return 0;
i2s_info_g->rpdev = rpdev;
init_completion(&i2s_info_g->cmd_complete);
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
rpmsg_i2s = container_of(i2s_info_g, struct fsl_rpmsg_i2s, i2s_info);
if (rpmsg_i2s->codec_wm8960) {
rpmsg_codec[0].audioindex = rpmsg_i2s->codec_wm8960 >> 16;
rpmsg_codec[0].shared_lrclk = true;
rpmsg_codec[0].capless = false;
codec_pdev = platform_device_register_data(
&rpmsg_i2s->pdev->dev,
RPMSG_CODEC_DRV_NAME_WM8960,
PLATFORM_DEVID_NONE,
&rpmsg_codec[0], sizeof(struct fsl_rpmsg_codec));
if (IS_ERR(codec_pdev)) {
dev_err(&rpdev->dev,
"failed to register rpmsg audio codec\n");
ret = PTR_ERR(codec_pdev);
return ret;
}
}
if (rpmsg_i2s->codec_cs42888) {
rpmsg_codec[1].audioindex = rpmsg_i2s->codec_cs42888 >> 16;
strcpy(rpmsg_codec[1].name, "cs42888");
rpmsg_codec[1].num_adcs = 2;
codec_pdev = platform_device_register_data(
&rpmsg_i2s->pdev->dev,
RPMSG_CODEC_DRV_NAME_CS42888,
PLATFORM_DEVID_NONE,
&rpmsg_codec[1], sizeof(struct fsl_rpmsg_codec));
if (IS_ERR(codec_pdev)) {
dev_err(&rpdev->dev,
"failed to register rpmsg audio codec\n");
ret = PTR_ERR(codec_pdev);
return ret;
}
}
if (rpmsg_i2s->codec_ak4497) {
rpmsg_codec[2].audioindex = rpmsg_i2s->codec_ak4497 >> 16;
codec_pdev = platform_device_register_data(
&rpmsg_i2s->pdev->dev,
RPMSG_CODEC_DRV_NAME_AK4497,
PLATFORM_DEVID_NONE,
&rpmsg_codec[2], sizeof(struct fsl_rpmsg_codec));
if (IS_ERR(codec_pdev)) {
dev_err(&rpdev->dev,
"failed to register rpmsg audio codec\n");
ret = PTR_ERR(codec_pdev);
return ret;
}
}
return 0;
}
static void i2s_rpmsg_remove(struct rpmsg_device *rpdev)
{
dev_info(&rpdev->dev, "i2s rpmsg driver is removed\n");
}
static struct rpmsg_device_id i2s_rpmsg_id_table[] = {
{ .name = "rpmsg-audio-channel" },
{ },
};
static struct rpmsg_driver i2s_rpmsg_driver = {
.drv.name = "i2s_rpmsg",
.drv.owner = THIS_MODULE,
.id_table = i2s_rpmsg_id_table,
.probe = i2s_rpmsg_probe,
.callback = i2s_rpmsg_cb,
.remove = i2s_rpmsg_remove,
};
static int __init i2s_rpmsg_init(void)
{
return register_rpmsg_driver(&i2s_rpmsg_driver);
}
static void __exit i2s_rpmsg_exit(void)
{
unregister_rpmsg_driver(&i2s_rpmsg_driver);
}
module_init(i2s_rpmsg_init);
module_exit(i2s_rpmsg_exit);
MODULE_LICENSE("GPL");

175
sound/soc/fsl/imx-rpmsg.c Normal file
View File

@ -0,0 +1,175 @@
/*
* Copyright (C) 2017 NXP
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/control.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/pinctrl/consumer.h>
#include "fsl_rpmsg_i2s.h"
struct imx_rpmsg_data {
struct snd_soc_dai_link dai[1];
struct snd_soc_card card;
};
static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("Main MIC", NULL),
};
static int imx_rpmsg_probe(struct platform_device *pdev)
{
struct device_node *cpu_np;
struct platform_device *cpu_pdev;
struct imx_rpmsg_data *data;
struct fsl_rpmsg_i2s *rpmsg_i2s;
struct snd_soc_dai_link_component *dlc;
int ret;
dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
if (!dlc)
return -ENOMEM;
cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto fail;
}
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;
}
rpmsg_i2s = platform_get_drvdata(cpu_pdev);
data->dai[0].cpus = &dlc[0];
data->dai[0].num_cpus = 1;
data->dai[0].platforms = &dlc[1];
data->dai[0].num_platforms = 1;
data->dai[0].codecs = &dlc[2];
data->dai[0].num_codecs = 1;
data->dai[0].name = "rpmsg hifi";
data->dai[0].stream_name = "rpmsg hifi";
data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM;
if (rpmsg_i2s->codec_wm8960) {
data->dai[0].codecs->dai_name = "rpmsg-wm8960-hifi";
data->dai[0].codecs->name = "rpmsg-audio-codec-wm8960";
}
if (rpmsg_i2s->codec_dummy) {
data->dai[0].codecs->dai_name = "snd-soc-dummy-dai";
data->dai[0].codecs->name = "snd-soc-dummy";
}
if (rpmsg_i2s->codec_ak4497) {
data->dai[0].codecs->dai_name = "rpmsg-ak4497-aif";
data->dai[0].codecs->name = "rpmsg-audio-codec-ak4497";
data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;
}
data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
data->dai[0].platforms->of_node = cpu_np;
data->dai[0].playback_only = true;
data->dai[0].capture_only = true;
data->card.num_links = 1;
data->card.dai_link = data->dai;
if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out"))
data->dai[0].capture_only = false;
if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in"))
data->dai[0].playback_only = false;
if (data->dai[0].playback_only && data->dai[0].capture_only) {
dev_err(&pdev->dev, "no enabled rpmsg DAI link\n");
ret = -EINVAL;
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;
if (rpmsg_i2s->codec_wm8960) {
ret = snd_soc_of_parse_audio_routing(&data->card,
"audio-routing");
if (ret)
goto fail;
data->card.dapm_widgets = imx_wm8960_dapm_widgets;
data->card.num_dapm_widgets =
ARRAY_SIZE(imx_wm8960_dapm_widgets);
}
platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto fail;
}
fail:
if (cpu_np)
of_node_put(cpu_np);
return ret;
}
static const struct of_device_id imx_rpmsg_dt_ids[] = {
{ .compatible = "fsl,imx-audio-rpmsg", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids);
static struct platform_driver imx_rpmsg_driver = {
.driver = {
.name = "imx-audio-rpmsg",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = imx_rpmsg_dt_ids,
},
.probe = imx_rpmsg_probe,
};
module_platform_driver(imx_rpmsg_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Freescale i.MX rpmsg audio ASoC machine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:imx-rpmsg");

View File

@ -2818,6 +2818,26 @@ static void soc_pcm_private_free(struct snd_pcm *pcm)
snd_soc_pcm_component_free(pcm);
}
static int soc_rtdcom_ack(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_rtdcom_list *rtdcom;
struct snd_soc_component *component;
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
if (!component->driver->ops ||
!component->driver->ops->ack)
continue;
/* FIXME. it returns 1st ask now */
return component->driver->ops->ack(substream);
}
return -EINVAL;
}
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
@ -2941,6 +2961,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
if (!ops)
continue;
if (ops->ack)
rtd->ops.ack = soc_rtdcom_ack;
if (ops->copy_user)
rtd->ops.copy_user = snd_soc_pcm_component_copy_user;
if (ops->page)