Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Atmel"
depends on HAS_IOMEM
config SND_ATMEL_SOC_PDC
bool
config SND_ATMEL_SOC_DMA
bool
select SND_SOC_GENERIC_DMAENGINE_PCM
config SND_ATMEL_SOC_SSC
tristate
select SND_ATMEL_SOC_DMA
select SND_ATMEL_SOC_PDC
config SND_ATMEL_SOC_SSC_PDC
tristate "SoC PCM DAI support for AT91 SSC controller using PDC"
depends on ATMEL_SSC
select SND_ATMEL_SOC_PDC
select SND_ATMEL_SOC_SSC
help
Say Y or M if you want to add support for Atmel SSC interface
in PDC mode configured using audio-graph-card in device-tree.
config SND_ATMEL_SOC_SSC_DMA
tristate "SoC PCM DAI support for AT91 SSC controller using DMA"
depends on ATMEL_SSC
select SND_ATMEL_SOC_DMA
select SND_ATMEL_SOC_SSC
help
Say Y or M if you want to add support for Atmel SSC interface
in DMA mode configured using audio-graph-card in device-tree.
config SND_AT91_SOC_SAM9G20_WM8731
tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
depends on ARCH_AT91 || COMPILE_TEST
depends on ATMEL_SSC && I2C
select SND_ATMEL_SOC_SSC_PDC
select SND_SOC_WM8731_I2C
help
Say Y if you want to add support for SoC audio on WM8731-based
AT91sam9g20 evaluation board.
config SND_ATMEL_SOC_WM8904
tristate "Atmel ASoC driver for boards using WM8904 codec"
depends on ARCH_AT91 || COMPILE_TEST
depends on ATMEL_SSC && I2C
select SND_ATMEL_SOC_SSC_DMA
select SND_SOC_WM8904
help
Say Y if you want to add support for Atmel ASoC driver for boards using
WM8904 codec.
config SND_AT91_SOC_SAM9X5_WM8731
tristate "SoC Audio support for WM8731-based at91sam9x5 board"
depends on ARCH_AT91 || COMPILE_TEST
depends on ATMEL_SSC && SND_SOC_I2C_AND_SPI
select SND_ATMEL_SOC_SSC_DMA
select SND_SOC_WM8731
help
Say Y if you want to add support for audio SoC on an
at91sam9x5 based board that is using WM8731 codec.
config SND_ATMEL_SOC_CLASSD
tristate "Atmel ASoC driver for boards using CLASSD"
depends on ARCH_AT91 || COMPILE_TEST
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y if you want to add support for Atmel ASoC driver for boards using
CLASSD.
config SND_ATMEL_SOC_PDMIC
tristate "Atmel ASoC driver for boards using PDMIC"
depends on OF && (ARCH_AT91 || COMPILE_TEST)
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y if you want to add support for Atmel ASoC driver for boards using
PDMIC.
config SND_ATMEL_SOC_TSE850_PCM5142
tristate "ASoC driver for the Axentia TSE-850"
depends on ARCH_AT91 && OF
depends on ATMEL_SSC && I2C
select SND_ATMEL_SOC_SSC_DMA
select SND_SOC_PCM512x_I2C
help
Say Y if you want to add support for the ASoC driver for the
Axentia TSE-850 with a PCM5142 codec.
config SND_ATMEL_SOC_I2S
tristate "Atmel ASoC driver for boards using I2S"
depends on OF && (ARCH_AT91 || COMPILE_TEST)
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for Atmel ASoc driver for boards
using I2S.
config SND_SOC_MIKROE_PROTO
tristate "Support for Mikroe-PROTO board"
depends on OF
depends on SND_SOC_I2C_AND_SPI
select SND_SOC_WM8731
help
Say Y or M if you want to add support for MikroElektronika PROTO Audio
Board. This board contains the WM8731 codec, which can be configured
using I2C over SDA (MPU Data Input) and SCL (MPU Clock Input) pins.
Both playback and capture are supported.
config SND_MCHP_SOC_I2S_MCC
tristate "Microchip ASoC driver for boards using I2S MCC"
depends on OF && (ARCH_AT91 || COMPILE_TEST)
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for I2S Multi-Channel ASoC
driver on the following Microchip platforms:
- sam9x60
- sama7g5
The I2SMCC complies with the Inter-IC Sound (I2S) bus specification
and supports a Time Division Multiplexed (TDM) interface with
external multi-channel audio codecs.
Starting with sama7g5, I2S and Left-Justified multi-channel is
supported by using multiple data pins, output and input, without TDM.
config SND_MCHP_SOC_SPDIFTX
tristate "Microchip ASoC driver for boards using S/PDIF TX"
depends on OF && (ARCH_AT91 || COMPILE_TEST)
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for Microchip S/PDIF TX ASoc
driver on the following Microchip platforms:
- sama7g5
This S/PDIF TX driver is compliant with IEC-60958 standard and
includes programmable User Data and Channel Status fields.
config SND_MCHP_SOC_SPDIFRX
tristate "Microchip ASoC driver for boards using S/PDIF RX"
depends on OF && (ARCH_AT91 || COMPILE_TEST)
depends on COMMON_CLK
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for Microchip S/PDIF RX ASoc
driver on the following Microchip platforms:
- sama7g5
This S/PDIF RX driver is compliant with IEC-60958 standard and
includes programmable User Data and Channel Status fields.
config SND_MCHP_SOC_PDMC
tristate "Microchip ASoC driver for boards using PDMC"
depends on OF && (ARCH_AT91 || COMPILE_TEST)
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for Microchip ASoC PDMC driver on the
following Microchip platforms:
- sama7g5
The Pulse Density Microphone Controller (PDMC) interfaces up to 4 digital
microphones PDM outputs. It generates a single clock line and samples 1 or
2 data lines. The signal path includes an audio grade programmable
decimation filter and outputs 24-bit audio words.
endmenu
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0
# AT91 Platform Support
snd-soc-atmel-pcm-pdc-y := atmel-pcm-pdc.o
snd-soc-atmel-pcm-dma-y := atmel-pcm-dma.o
snd-soc-atmel_ssc_dai-y := atmel_ssc_dai.o
snd-soc-atmel-i2s-y := atmel-i2s.o
snd-soc-mchp-i2s-mcc-y := mchp-i2s-mcc.o
snd-soc-mchp-spdiftx-y := mchp-spdiftx.o
snd-soc-mchp-spdifrx-y := mchp-spdifrx.o
snd-soc-mchp-pdmc-y := mchp-pdmc.o
# pdc and dma need to both be built-in if any user of
# ssc is built-in.
ifdef CONFIG_SND_ATMEL_SOC_PDC
obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel-pcm-pdc.o
endif
ifdef CONFIG_SND_ATMEL_SOC_DMA
obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel-pcm-dma.o
endif
obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o
obj-$(CONFIG_SND_MCHP_SOC_I2S_MCC) += snd-soc-mchp-i2s-mcc.o
obj-$(CONFIG_SND_MCHP_SOC_SPDIFTX) += snd-soc-mchp-spdiftx.o
obj-$(CONFIG_SND_MCHP_SOC_SPDIFRX) += snd-soc-mchp-spdifrx.o
obj-$(CONFIG_SND_MCHP_SOC_PDMC) += snd-soc-mchp-pdmc.o
# AT91 Machine Support
snd-soc-sam9g20-wm8731-y := sam9g20_wm8731.o
snd-atmel-soc-wm8904-y := atmel_wm8904.o
snd-soc-sam9x5-wm8731-y := sam9x5_wm8731.o
snd-atmel-soc-classd-y := atmel-classd.o
snd-atmel-soc-pdmic-y := atmel-pdmic.o
snd-atmel-soc-tse850-pcm5142-y := tse850-pcm5142.o
snd-soc-mikroe-proto-y := mikroe-proto.o
obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o
obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o
obj-$(CONFIG_SND_ATMEL_SOC_CLASSD) += snd-atmel-soc-classd.o
obj-$(CONFIG_SND_ATMEL_SOC_PDMIC) += snd-atmel-soc-pdmic.o
obj-$(CONFIG_SND_ATMEL_SOC_TSE850_PCM5142) += snd-atmel-soc-tse850-pcm5142.o
obj-$(CONFIG_SND_SOC_MIKROE_PROTO) += snd-soc-mikroe-proto.o
+633
View File
@@ -0,0 +1,633 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Atmel ALSA SoC Audio Class D Amplifier (CLASSD) driver
*
* Copyright (C) 2015 Atmel
*
* Author: Songjun Wu <songjun.wu@atmel.com>
*/
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
#include <linux/string_choices.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include "atmel-classd.h"
struct atmel_classd_pdata {
bool non_overlap_enable;
int non_overlap_time;
int pwm_type;
const char *card_name;
};
struct atmel_classd {
dma_addr_t phy_base;
struct regmap *regmap;
struct clk *pclk;
struct clk *gclk;
struct device *dev;
int irq;
const struct atmel_classd_pdata *pdata;
};
#ifdef CONFIG_OF
static const struct of_device_id atmel_classd_of_match[] = {
{
.compatible = "atmel,sama5d2-classd",
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, atmel_classd_of_match);
static struct atmel_classd_pdata *atmel_classd_dt_init(struct device *dev)
{
struct device_node *np = dev->of_node;
struct atmel_classd_pdata *pdata;
const char *pwm_type_s;
int ret;
if (!np) {
dev_err(dev, "device node not found\n");
return ERR_PTR(-EINVAL);
}
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
ret = of_property_read_string(np, "atmel,pwm-type", &pwm_type_s);
if ((ret == 0) && (strcmp(pwm_type_s, "diff") == 0))
pdata->pwm_type = CLASSD_MR_PWMTYP_DIFF;
else
pdata->pwm_type = CLASSD_MR_PWMTYP_SINGLE;
ret = of_property_read_u32(np,
"atmel,non-overlap-time", &pdata->non_overlap_time);
if (ret)
pdata->non_overlap_enable = false;
else
pdata->non_overlap_enable = true;
ret = of_property_read_string(np, "atmel,model", &pdata->card_name);
if (ret)
pdata->card_name = "CLASSD";
return pdata;
}
#else
static inline struct atmel_classd_pdata *
atmel_classd_dt_init(struct device *dev)
{
return ERR_PTR(-EINVAL);
}
#endif
#define ATMEL_CLASSD_RATES (SNDRV_PCM_RATE_8000 \
| SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 \
| SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 \
| SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 \
| SNDRV_PCM_RATE_96000)
static const struct snd_pcm_hardware atmel_classd_hw = {
.info = SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID
| SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_RESUME
| SNDRV_PCM_INFO_PAUSE,
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = ATMEL_CLASSD_RATES,
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 64 * 1024,
.period_bytes_min = 256,
.period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 256,
};
#define ATMEL_CLASSD_PREALLOC_BUF_SIZE (64 * 1024)
/* cpu dai component */
static int atmel_classd_cpu_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
int err;
regmap_write(dd->regmap, CLASSD_THR, 0x0);
err = clk_prepare_enable(dd->pclk);
if (err)
return err;
err = clk_prepare_enable(dd->gclk);
if (err) {
clk_disable_unprepare(dd->pclk);
return err;
}
return 0;
}
/* platform */
static int
atmel_classd_platform_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
if (params_physical_width(params) != 16) {
dev_err(dd->dev,
"only supports 16-bit audio data\n");
return -EINVAL;
}
if (params_channels(params) == 1)
slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
else
slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config->direction = DMA_MEM_TO_DEV;
slave_config->dst_addr = dd->phy_base + CLASSD_THR;
slave_config->dst_maxburst = 1;
slave_config->src_maxburst = 1;
slave_config->device_fc = false;
return 0;
}
static const struct snd_dmaengine_pcm_config
atmel_classd_dmaengine_pcm_config = {
.prepare_slave_config = atmel_classd_platform_configure_dma,
.pcm_hardware = &atmel_classd_hw,
.prealloc_buffer_size = ATMEL_CLASSD_PREALLOC_BUF_SIZE,
};
/* codec */
static const char * const mono_mode_text[] = {
"mix", "sat", "left", "right"
};
static SOC_ENUM_SINGLE_DECL(classd_mono_mode_enum,
CLASSD_INTPMR, CLASSD_INTPMR_MONO_MODE_SHIFT,
mono_mode_text);
static const char * const eqcfg_text[] = {
"Treble-12dB", "Treble-6dB",
"Medium-8dB", "Medium-3dB",
"Bass-12dB", "Bass-6dB",
"0 dB",
"Bass+6dB", "Bass+12dB",
"Medium+3dB", "Medium+8dB",
"Treble+6dB", "Treble+12dB",
};
static const unsigned int eqcfg_value[] = {
CLASSD_INTPMR_EQCFG_T_CUT_12, CLASSD_INTPMR_EQCFG_T_CUT_6,
CLASSD_INTPMR_EQCFG_M_CUT_8, CLASSD_INTPMR_EQCFG_M_CUT_3,
CLASSD_INTPMR_EQCFG_B_CUT_12, CLASSD_INTPMR_EQCFG_B_CUT_6,
CLASSD_INTPMR_EQCFG_FLAT,
CLASSD_INTPMR_EQCFG_B_BOOST_6, CLASSD_INTPMR_EQCFG_B_BOOST_12,
CLASSD_INTPMR_EQCFG_M_BOOST_3, CLASSD_INTPMR_EQCFG_M_BOOST_8,
CLASSD_INTPMR_EQCFG_T_BOOST_6, CLASSD_INTPMR_EQCFG_T_BOOST_12,
};
static SOC_VALUE_ENUM_SINGLE_DECL(classd_eqcfg_enum,
CLASSD_INTPMR, CLASSD_INTPMR_EQCFG_SHIFT, 0xf,
eqcfg_text, eqcfg_value);
static const DECLARE_TLV_DB_SCALE(classd_digital_tlv, -7800, 100, 1);
static const struct snd_kcontrol_new atmel_classd_snd_controls[] = {
SOC_DOUBLE_TLV("Playback Volume", CLASSD_INTPMR,
CLASSD_INTPMR_ATTL_SHIFT, CLASSD_INTPMR_ATTR_SHIFT,
78, 1, classd_digital_tlv),
SOC_SINGLE("Deemphasis Switch", CLASSD_INTPMR,
CLASSD_INTPMR_DEEMP_SHIFT, 1, 0),
SOC_SINGLE("Mono Switch", CLASSD_INTPMR, CLASSD_INTPMR_MONO_SHIFT, 1, 0),
SOC_SINGLE("Swap Switch", CLASSD_INTPMR, CLASSD_INTPMR_SWAP_SHIFT, 1, 0),
SOC_ENUM("Mono Mode", classd_mono_mode_enum),
SOC_ENUM("EQ", classd_eqcfg_enum),
};
static const char * const pwm_type[] = {
"Single ended", "Differential"
};
static int atmel_classd_component_probe(struct snd_soc_component *component)
{
struct snd_soc_card *card = snd_soc_component_get_drvdata(component);
struct atmel_classd *dd = snd_soc_card_get_drvdata(card);
const struct atmel_classd_pdata *pdata = dd->pdata;
u32 mask, val;
mask = CLASSD_MR_PWMTYP_MASK;
val = FIELD_PREP(CLASSD_MR_PWMTYP_MASK, pdata->pwm_type);
mask |= CLASSD_MR_NON_OVERLAP_MASK;
if (pdata->non_overlap_enable) {
val |= FIELD_PREP(CLASSD_MR_NON_OVERLAP_MASK,
CLASSD_MR_NON_OVERLAP_EN);
mask |= CLASSD_MR_NOVR_VAL_MASK;
switch (pdata->non_overlap_time) {
case 5:
val |= FIELD_PREP(CLASSD_MR_NOVR_VAL_MASK,
CLASSD_MR_NOVR_VAL_5NS);
break;
case 10:
val |= FIELD_PREP(CLASSD_MR_NOVR_VAL_MASK,
CLASSD_MR_NOVR_VAL_10NS);
break;
case 15:
val |= FIELD_PREP(CLASSD_MR_NOVR_VAL_MASK,
CLASSD_MR_NOVR_VAL_15NS);
break;
case 20:
val |= FIELD_PREP(CLASSD_MR_NOVR_VAL_MASK,
CLASSD_MR_NOVR_VAL_20NS);
break;
default:
val |= FIELD_PREP(CLASSD_MR_NOVR_VAL_MASK,
CLASSD_MR_NOVR_VAL_10NS);
dev_warn(component->dev,
"non-overlapping value %d is invalid, the default value 10 is specified\n",
pdata->non_overlap_time);
break;
}
}
snd_soc_component_update_bits(component, CLASSD_MR, mask, val);
dev_info(component->dev,
"PWM modulation type is %s, non-overlapping is %s\n",
pwm_type[pdata->pwm_type],
str_enabled_disabled(pdata->non_overlap_enable));
return 0;
}
static int atmel_classd_component_resume(struct snd_soc_component *component)
{
struct snd_soc_card *card = snd_soc_component_get_drvdata(component);
struct atmel_classd *dd = snd_soc_card_get_drvdata(card);
return regcache_sync(dd->regmap);
}
static int atmel_classd_cpu_dai_mute_stream(struct snd_soc_dai *cpu_dai,
int mute, int direction)
{
struct snd_soc_component *component = cpu_dai->component;
u32 mask, val;
mask = CLASSD_MR_LMUTE_MASK | CLASSD_MR_RMUTE_MASK;
if (mute)
val = mask;
else
val = 0;
snd_soc_component_update_bits(component, CLASSD_MR, mask, val);
return 0;
}
#define CLASSD_GCLK_RATE_11M2896_MPY_8 (112896 * 100 * 8)
#define CLASSD_GCLK_RATE_12M288_MPY_8 (12288 * 1000 * 8)
static struct {
int rate;
int sample_rate;
int dsp_clk;
unsigned long gclk_rate;
} const sample_rates[] = {
{ 8000, CLASSD_INTPMR_FRAME_8K,
CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 },
{ 16000, CLASSD_INTPMR_FRAME_16K,
CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 },
{ 32000, CLASSD_INTPMR_FRAME_32K,
CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 },
{ 48000, CLASSD_INTPMR_FRAME_48K,
CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 },
{ 96000, CLASSD_INTPMR_FRAME_96K,
CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 },
{ 22050, CLASSD_INTPMR_FRAME_22K,
CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 },
{ 44100, CLASSD_INTPMR_FRAME_44K,
CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 },
{ 88200, CLASSD_INTPMR_FRAME_88K,
CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 },
};
static int
atmel_classd_cpu_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_component *component = cpu_dai->component;
int fs;
int i, best, best_val, cur_val, ret;
u32 mask, val;
fs = params_rate(params);
best = 0;
best_val = abs(fs - sample_rates[0].rate);
for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
/* Closest match */
cur_val = abs(fs - sample_rates[i].rate);
if (cur_val < best_val) {
best = i;
best_val = cur_val;
}
}
dev_dbg(component->dev,
"Selected SAMPLE_RATE of %dHz, GCLK_RATE of %ldHz\n",
sample_rates[best].rate, sample_rates[best].gclk_rate);
clk_disable_unprepare(dd->gclk);
ret = clk_set_rate(dd->gclk, sample_rates[best].gclk_rate);
if (ret)
return ret;
mask = CLASSD_INTPMR_DSP_CLK_FREQ_MASK | CLASSD_INTPMR_FRAME_MASK;
val = FIELD_PREP(CLASSD_INTPMR_DSP_CLK_FREQ_MASK,
sample_rates[best].dsp_clk) |
FIELD_PREP(CLASSD_INTPMR_FRAME_MASK,
sample_rates[best].sample_rate);
snd_soc_component_update_bits(component, CLASSD_INTPMR, mask, val);
return clk_prepare_enable(dd->gclk);
}
static void
atmel_classd_cpu_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
clk_disable_unprepare(dd->gclk);
}
static int atmel_classd_cpu_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_component *component = cpu_dai->component;
snd_soc_component_update_bits(component, CLASSD_MR,
CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK,
FIELD_PREP(CLASSD_MR_LEN_MASK, CLASSD_MR_LEN_DIS) |
FIELD_PREP(CLASSD_MR_REN_MASK, CLASSD_MR_REN_DIS));
return 0;
}
static int atmel_classd_cpu_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *cpu_dai)
{
struct snd_soc_component *component = cpu_dai->component;
u32 mask, val;
mask = CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
val = mask;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
val = FIELD_PREP(CLASSD_MR_LEN_MASK, CLASSD_MR_LEN_DIS) |
FIELD_PREP(CLASSD_MR_REN_MASK, CLASSD_MR_REN_DIS);
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, CLASSD_MR, mask, val);
return 0;
}
static const struct snd_soc_dai_ops atmel_classd_cpu_dai_ops = {
.startup = atmel_classd_cpu_dai_startup,
.shutdown = atmel_classd_cpu_dai_shutdown,
.mute_stream = atmel_classd_cpu_dai_mute_stream,
.hw_params = atmel_classd_cpu_dai_hw_params,
.prepare = atmel_classd_cpu_dai_prepare,
.trigger = atmel_classd_cpu_dai_trigger,
.no_capture_mute = 1,
};
static struct snd_soc_dai_driver atmel_classd_cpu_dai = {
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = ATMEL_CLASSD_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &atmel_classd_cpu_dai_ops,
};
static const struct snd_soc_component_driver atmel_classd_cpu_dai_component = {
.name = "atmel-classd",
.probe = atmel_classd_component_probe,
.resume = atmel_classd_component_resume,
.controls = atmel_classd_snd_controls,
.num_controls = ARRAY_SIZE(atmel_classd_snd_controls),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.legacy_dai_naming = 1,
};
/* ASoC sound card */
static int atmel_classd_asoc_card_init(struct device *dev,
struct snd_soc_card *card)
{
struct snd_soc_dai_link *dai_link;
struct atmel_classd *dd = snd_soc_card_get_drvdata(card);
struct snd_soc_dai_link_component *comp;
dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL);
if (!dai_link)
return -ENOMEM;
comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
dai_link->cpus = &comp[0];
dai_link->codecs = &snd_soc_dummy_dlc;
dai_link->platforms = &comp[1];
dai_link->num_cpus = 1;
dai_link->num_codecs = 1;
dai_link->num_platforms = 1;
dai_link->name = "CLASSD";
dai_link->stream_name = "CLASSD PCM";
dai_link->cpus->dai_name = dev_name(dev);
dai_link->platforms->name = dev_name(dev);
card->dai_link = dai_link;
card->num_links = 1;
card->name = dd->pdata->card_name;
card->dev = dev;
return 0;
};
/* regmap configuration */
static const struct reg_default atmel_classd_reg_defaults[] = {
{ CLASSD_INTPMR, 0x00301212 },
};
#define ATMEL_CLASSD_REG_MAX 0xE4
static const struct regmap_config atmel_classd_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = ATMEL_CLASSD_REG_MAX,
.cache_type = REGCACHE_FLAT,
.reg_defaults = atmel_classd_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(atmel_classd_reg_defaults),
};
static int atmel_classd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct atmel_classd *dd;
struct resource *res;
void __iomem *io_base;
const struct atmel_classd_pdata *pdata;
struct snd_soc_card *card;
int ret;
pdata = dev_get_platdata(dev);
if (!pdata) {
pdata = atmel_classd_dt_init(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
if (!dd)
return -ENOMEM;
dd->pdata = pdata;
dd->irq = platform_get_irq(pdev, 0);
if (dd->irq < 0)
return dd->irq;
dd->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dd->pclk)) {
ret = PTR_ERR(dd->pclk);
dev_err(dev, "failed to get peripheral clock: %d\n", ret);
return ret;
}
dd->gclk = devm_clk_get(dev, "gclk");
if (IS_ERR(dd->gclk)) {
ret = PTR_ERR(dd->gclk);
dev_err(dev, "failed to get GCK clock: %d\n", ret);
return ret;
}
io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(io_base))
return PTR_ERR(io_base);
dd->phy_base = res->start;
dd->dev = dev;
dd->regmap = devm_regmap_init_mmio(dev, io_base,
&atmel_classd_regmap_config);
if (IS_ERR(dd->regmap)) {
ret = PTR_ERR(dd->regmap);
dev_err(dev, "failed to init register map: %d\n", ret);
return ret;
}
ret = devm_snd_soc_register_component(dev,
&atmel_classd_cpu_dai_component,
&atmel_classd_cpu_dai, 1);
if (ret) {
dev_err(dev, "could not register CPU DAI: %d\n", ret);
return ret;
}
ret = devm_snd_dmaengine_pcm_register(dev,
&atmel_classd_dmaengine_pcm_config,
0);
if (ret) {
dev_err(dev, "could not register platform: %d\n", ret);
return ret;
}
/* register sound card */
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card) {
ret = -ENOMEM;
goto unregister_codec;
}
snd_soc_card_set_drvdata(card, dd);
ret = atmel_classd_asoc_card_init(dev, card);
if (ret) {
dev_err(dev, "failed to init sound card\n");
goto unregister_codec;
}
ret = devm_snd_soc_register_card(dev, card);
if (ret) {
dev_err(dev, "failed to register sound card: %d\n", ret);
goto unregister_codec;
}
return 0;
unregister_codec:
return ret;
}
static struct platform_driver atmel_classd_driver = {
.driver = {
.name = "atmel-classd",
.of_match_table = of_match_ptr(atmel_classd_of_match),
.pm = &snd_soc_pm_ops,
},
.probe = atmel_classd_probe,
};
module_platform_driver(atmel_classd_driver);
MODULE_DESCRIPTION("Atmel ClassD driver under ALSA SoC architecture");
MODULE_AUTHOR("Songjun Wu <songjun.wu@atmel.com>");
MODULE_LICENSE("GPL");
+121
View File
@@ -0,0 +1,121 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ATMEL_CLASSD_H_
#define __ATMEL_CLASSD_H_
#define CLASSD_CR 0x00000000
#define CLASSD_CR_RESET 0x1
#define CLASSD_MR 0x00000004
#define CLASSD_MR_LEN_DIS 0x0
#define CLASSD_MR_LEN_EN 0x1
#define CLASSD_MR_LEN_MASK (0x1 << 0)
#define CLASSD_MR_LEN_SHIFT (0)
#define CLASSD_MR_LMUTE_DIS 0x0
#define CLASSD_MR_LMUTE_EN 0x1
#define CLASSD_MR_LMUTE_SHIFT (0x1)
#define CLASSD_MR_LMUTE_MASK (0x1 << 1)
#define CLASSD_MR_REN_DIS 0x0
#define CLASSD_MR_REN_EN 0x1
#define CLASSD_MR_REN_MASK (0x1 << 4)
#define CLASSD_MR_REN_SHIFT (4)
#define CLASSD_MR_RMUTE_DIS 0x0
#define CLASSD_MR_RMUTE_EN 0x1
#define CLASSD_MR_RMUTE_SHIFT (0x5)
#define CLASSD_MR_RMUTE_MASK (0x1 << 5)
#define CLASSD_MR_PWMTYP_SINGLE 0x0
#define CLASSD_MR_PWMTYP_DIFF 0x1
#define CLASSD_MR_PWMTYP_MASK (0x1 << 8)
#define CLASSD_MR_PWMTYP_SHIFT (8)
#define CLASSD_MR_NON_OVERLAP_DIS 0x0
#define CLASSD_MR_NON_OVERLAP_EN 0x1
#define CLASSD_MR_NON_OVERLAP_MASK (0x1 << 16)
#define CLASSD_MR_NON_OVERLAP_SHIFT (16)
#define CLASSD_MR_NOVR_VAL_5NS 0x0
#define CLASSD_MR_NOVR_VAL_10NS 0x1
#define CLASSD_MR_NOVR_VAL_15NS 0x2
#define CLASSD_MR_NOVR_VAL_20NS 0x3
#define CLASSD_MR_NOVR_VAL_MASK (0x3 << 20)
#define CLASSD_MR_NOVR_VAL_SHIFT (20)
#define CLASSD_INTPMR 0x00000008
#define CLASSD_INTPMR_ATTL_MASK (0x3f << 0)
#define CLASSD_INTPMR_ATTL_SHIFT (0)
#define CLASSD_INTPMR_ATTR_MASK (0x3f << 8)
#define CLASSD_INTPMR_ATTR_SHIFT (8)
#define CLASSD_INTPMR_DSP_CLK_FREQ_12M288 0x0
#define CLASSD_INTPMR_DSP_CLK_FREQ_11M2896 0x1
#define CLASSD_INTPMR_DSP_CLK_FREQ_MASK (0x1 << 16)
#define CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT (16)
#define CLASSD_INTPMR_DEEMP_DIS 0x0
#define CLASSD_INTPMR_DEEMP_EN 0x1
#define CLASSD_INTPMR_DEEMP_MASK (0x1 << 18)
#define CLASSD_INTPMR_DEEMP_SHIFT (18)
#define CLASSD_INTPMR_SWAP_LEFT_ON_LSB 0x0
#define CLASSD_INTPMR_SWAP_RIGHT_ON_LSB 0x1
#define CLASSD_INTPMR_SWAP_MASK (0x1 << 19)
#define CLASSD_INTPMR_SWAP_SHIFT (19)
#define CLASSD_INTPMR_FRAME_8K 0x0
#define CLASSD_INTPMR_FRAME_16K 0x1
#define CLASSD_INTPMR_FRAME_32K 0x2
#define CLASSD_INTPMR_FRAME_48K 0x3
#define CLASSD_INTPMR_FRAME_96K 0x4
#define CLASSD_INTPMR_FRAME_22K 0x5
#define CLASSD_INTPMR_FRAME_44K 0x6
#define CLASSD_INTPMR_FRAME_88K 0x7
#define CLASSD_INTPMR_FRAME_MASK (0x7 << 20)
#define CLASSD_INTPMR_FRAME_SHIFT (20)
#define CLASSD_INTPMR_EQCFG_FLAT 0x0
#define CLASSD_INTPMR_EQCFG_B_BOOST_12 0x1
#define CLASSD_INTPMR_EQCFG_B_BOOST_6 0x2
#define CLASSD_INTPMR_EQCFG_B_CUT_12 0x3
#define CLASSD_INTPMR_EQCFG_B_CUT_6 0x4
#define CLASSD_INTPMR_EQCFG_M_BOOST_3 0x5
#define CLASSD_INTPMR_EQCFG_M_BOOST_8 0x6
#define CLASSD_INTPMR_EQCFG_M_CUT_3 0x7
#define CLASSD_INTPMR_EQCFG_M_CUT_8 0x8
#define CLASSD_INTPMR_EQCFG_T_BOOST_12 0x9
#define CLASSD_INTPMR_EQCFG_T_BOOST_6 0xa
#define CLASSD_INTPMR_EQCFG_T_CUT_12 0xb
#define CLASSD_INTPMR_EQCFG_T_CUT_6 0xc
#define CLASSD_INTPMR_EQCFG_SHIFT (24)
#define CLASSD_INTPMR_MONO_DIS 0x0
#define CLASSD_INTPMR_MONO_EN 0x1
#define CLASSD_INTPMR_MONO_MASK (0x1 << 28)
#define CLASSD_INTPMR_MONO_SHIFT (28)
#define CLASSD_INTPMR_MONO_MODE_MIX 0x0
#define CLASSD_INTPMR_MONO_MODE_SAT 0x1
#define CLASSD_INTPMR_MONO_MODE_LEFT 0x2
#define CLASSD_INTPMR_MONO_MODE_RIGHT 0x3
#define CLASSD_INTPMR_MONO_MODE_MASK (0x3 << 29)
#define CLASSD_INTPMR_MONO_MODE_SHIFT (29)
#define CLASSD_INTSR 0x0000000c
#define CLASSD_THR 0x00000010
#define CLASSD_IER 0x00000014
#define CLASSD_IDR 0x00000018
#define CLASSD_IMR 0x0000001c
#define CLASSD_ISR 0x00000020
#define CLASSD_WPMR 0x000000e4
#endif
+742
View File
@@ -0,0 +1,742 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Atmel I2S controller
*
* Copyright (C) 2015 Atmel Corporation
*
* Author: Cyrille Pitchen <cyrille.pitchen@atmel.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#define ATMEL_I2SC_MAX_TDM_CHANNELS 8
/*
* ---- I2S Controller Register map ----
*/
#define ATMEL_I2SC_CR 0x0000 /* Control Register */
#define ATMEL_I2SC_MR 0x0004 /* Mode Register */
#define ATMEL_I2SC_SR 0x0008 /* Status Register */
#define ATMEL_I2SC_SCR 0x000c /* Status Clear Register */
#define ATMEL_I2SC_SSR 0x0010 /* Status Set Register */
#define ATMEL_I2SC_IER 0x0014 /* Interrupt Enable Register */
#define ATMEL_I2SC_IDR 0x0018 /* Interrupt Disable Register */
#define ATMEL_I2SC_IMR 0x001c /* Interrupt Mask Register */
#define ATMEL_I2SC_RHR 0x0020 /* Receiver Holding Register */
#define ATMEL_I2SC_THR 0x0024 /* Transmitter Holding Register */
#define ATMEL_I2SC_VERSION 0x0028 /* Version Register */
/*
* ---- Control Register (Write-only) ----
*/
#define ATMEL_I2SC_CR_RXEN BIT(0) /* Receiver Enable */
#define ATMEL_I2SC_CR_RXDIS BIT(1) /* Receiver Disable */
#define ATMEL_I2SC_CR_CKEN BIT(2) /* Clock Enable */
#define ATMEL_I2SC_CR_CKDIS BIT(3) /* Clock Disable */
#define ATMEL_I2SC_CR_TXEN BIT(4) /* Transmitter Enable */
#define ATMEL_I2SC_CR_TXDIS BIT(5) /* Transmitter Disable */
#define ATMEL_I2SC_CR_SWRST BIT(7) /* Software Reset */
/*
* ---- Mode Register (Read/Write) ----
*/
#define ATMEL_I2SC_MR_MODE_MASK GENMASK(0, 0)
#define ATMEL_I2SC_MR_MODE_SLAVE (0 << 0)
#define ATMEL_I2SC_MR_MODE_MASTER (1 << 0)
#define ATMEL_I2SC_MR_DATALENGTH_MASK GENMASK(4, 2)
#define ATMEL_I2SC_MR_DATALENGTH_32_BITS (0 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_24_BITS (1 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_20_BITS (2 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_18_BITS (3 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_16_BITS (4 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_16_BITS_COMPACT (5 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_8_BITS (6 << 2)
#define ATMEL_I2SC_MR_DATALENGTH_8_BITS_COMPACT (7 << 2)
#define ATMEL_I2SC_MR_FORMAT_MASK GENMASK(7, 6)
#define ATMEL_I2SC_MR_FORMAT_I2S (0 << 6)
#define ATMEL_I2SC_MR_FORMAT_LJ (1 << 6) /* Left Justified */
#define ATMEL_I2SC_MR_FORMAT_TDM (2 << 6)
#define ATMEL_I2SC_MR_FORMAT_TDMLJ (3 << 6)
/* Left audio samples duplicated to right audio channel */
#define ATMEL_I2SC_MR_RXMONO BIT(8)
/* Receiver uses one DMA channel ... */
#define ATMEL_I2SC_MR_RXDMA_MASK GENMASK(9, 9)
#define ATMEL_I2SC_MR_RXDMA_SINGLE (0 << 9) /* for all audio channels */
#define ATMEL_I2SC_MR_RXDMA_MULTIPLE (1 << 9) /* per audio channel */
/* I2SDO output of I2SC is internally connected to I2SDI input */
#define ATMEL_I2SC_MR_RXLOOP BIT(10)
/* Left audio samples duplicated to right audio channel */
#define ATMEL_I2SC_MR_TXMONO BIT(12)
/* Transmitter uses one DMA channel ... */
#define ATMEL_I2SC_MR_TXDMA_MASK GENMASK(13, 13)
#define ATMEL_I2SC_MR_TXDMA_SINGLE (0 << 13) /* for all audio channels */
#define ATMEL_I2SC_MR_TXDME_MULTIPLE (1 << 13) /* per audio channel */
/* x sample transmitted when underrun */
#define ATMEL_I2SC_MR_TXSAME_MASK GENMASK(14, 14)
#define ATMEL_I2SC_MR_TXSAME_ZERO (0 << 14) /* Zero sample */
#define ATMEL_I2SC_MR_TXSAME_PREVIOUS (1 << 14) /* Previous sample */
/* Audio Clock to I2SC Master Clock ratio */
#define ATMEL_I2SC_MR_IMCKDIV_MASK GENMASK(21, 16)
#define ATMEL_I2SC_MR_IMCKDIV(div) \
(((div) << 16) & ATMEL_I2SC_MR_IMCKDIV_MASK)
/* Master Clock to fs ratio */
#define ATMEL_I2SC_MR_IMCKFS_MASK GENMASK(29, 24)
#define ATMEL_I2SC_MR_IMCKFS(fs) \
(((fs) << 24) & ATMEL_I2SC_MR_IMCKFS_MASK)
/* Master Clock mode */
#define ATMEL_I2SC_MR_IMCKMODE_MASK GENMASK(30, 30)
/* 0: No master clock generated (selected clock drives I2SCK pin) */
#define ATMEL_I2SC_MR_IMCKMODE_I2SCK (0 << 30)
/* 1: master clock generated (internally generated clock drives I2SMCK pin) */
#define ATMEL_I2SC_MR_IMCKMODE_I2SMCK (1 << 30)
/* Slot Width */
/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */
/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */
#define ATMEL_I2SC_MR_IWS BIT(31)
/*
* ---- Status Registers ----
*/
#define ATMEL_I2SC_SR_RXEN BIT(0) /* Receiver Enabled */
#define ATMEL_I2SC_SR_RXRDY BIT(1) /* Receive Ready */
#define ATMEL_I2SC_SR_RXOR BIT(2) /* Receive Overrun */
#define ATMEL_I2SC_SR_TXEN BIT(4) /* Transmitter Enabled */
#define ATMEL_I2SC_SR_TXRDY BIT(5) /* Transmit Ready */
#define ATMEL_I2SC_SR_TXUR BIT(6) /* Transmit Underrun */
/* Receive Overrun Channel */
#define ATMEL_I2SC_SR_RXORCH_MASK GENMASK(15, 8)
#define ATMEL_I2SC_SR_RXORCH(ch) (1 << (((ch) & 0x7) + 8))
/* Transmit Underrun Channel */
#define ATMEL_I2SC_SR_TXURCH_MASK GENMASK(27, 20)
#define ATMEL_I2SC_SR_TXURCH(ch) (1 << (((ch) & 0x7) + 20))
/*
* ---- Interrupt Enable/Disable/Mask Registers ----
*/
#define ATMEL_I2SC_INT_RXRDY ATMEL_I2SC_SR_RXRDY
#define ATMEL_I2SC_INT_RXOR ATMEL_I2SC_SR_RXOR
#define ATMEL_I2SC_INT_TXRDY ATMEL_I2SC_SR_TXRDY
#define ATMEL_I2SC_INT_TXUR ATMEL_I2SC_SR_TXUR
static const struct regmap_config atmel_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = ATMEL_I2SC_VERSION,
};
struct atmel_i2s_gck_param {
int fs;
unsigned long mck;
int imckdiv;
int imckfs;
};
#define I2S_MCK_12M288 12288000UL
#define I2S_MCK_11M2896 11289600UL
#define I2S_MCK_6M144 6144000UL
/* mck = (32 * (imckfs+1) / (imckdiv+1)) * fs */
static const struct atmel_i2s_gck_param gck_params[] = {
/* mck = 6.144Mhz */
{ 8000, I2S_MCK_6M144, 1, 47}, /* mck = 768 fs */
/* mck = 12.288MHz */
{ 16000, I2S_MCK_12M288, 1, 47}, /* mck = 768 fs */
{ 24000, I2S_MCK_12M288, 3, 63}, /* mck = 512 fs */
{ 32000, I2S_MCK_12M288, 3, 47}, /* mck = 384 fs */
{ 48000, I2S_MCK_12M288, 7, 63}, /* mck = 256 fs */
{ 64000, I2S_MCK_12M288, 7, 47}, /* mck = 192 fs */
{ 96000, I2S_MCK_12M288, 7, 31}, /* mck = 128 fs */
{192000, I2S_MCK_12M288, 7, 15}, /* mck = 64 fs */
/* mck = 11.2896MHz */
{ 11025, I2S_MCK_11M2896, 1, 63}, /* mck = 1024 fs */
{ 22050, I2S_MCK_11M2896, 3, 63}, /* mck = 512 fs */
{ 44100, I2S_MCK_11M2896, 7, 63}, /* mck = 256 fs */
{ 88200, I2S_MCK_11M2896, 7, 31}, /* mck = 128 fs */
{176400, I2S_MCK_11M2896, 7, 15}, /* mck = 64 fs */
};
struct atmel_i2s_dev;
struct atmel_i2s_caps {
int (*mck_init)(struct atmel_i2s_dev *, struct device_node *np);
};
struct atmel_i2s_dev {
struct device *dev;
struct regmap *regmap;
struct clk *pclk;
struct clk *gclk;
struct snd_dmaengine_dai_dma_data playback;
struct snd_dmaengine_dai_dma_data capture;
unsigned int fmt;
const struct atmel_i2s_gck_param *gck_param;
const struct atmel_i2s_caps *caps;
int clk_use_no;
};
static irqreturn_t atmel_i2s_interrupt(int irq, void *dev_id)
{
struct atmel_i2s_dev *dev = dev_id;
unsigned int sr, imr, pending, ch, mask;
irqreturn_t ret = IRQ_NONE;
regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr);
regmap_read(dev->regmap, ATMEL_I2SC_IMR, &imr);
pending = sr & imr;
if (!pending)
return IRQ_NONE;
if (pending & ATMEL_I2SC_INT_RXOR) {
mask = ATMEL_I2SC_SR_RXOR;
for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) {
if (sr & ATMEL_I2SC_SR_RXORCH(ch)) {
mask |= ATMEL_I2SC_SR_RXORCH(ch);
dev_err(dev->dev,
"RX overrun on channel %d\n", ch);
}
}
regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask);
ret = IRQ_HANDLED;
}
if (pending & ATMEL_I2SC_INT_TXUR) {
mask = ATMEL_I2SC_SR_TXUR;
for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) {
if (sr & ATMEL_I2SC_SR_TXURCH(ch)) {
mask |= ATMEL_I2SC_SR_TXURCH(ch);
dev_err(dev->dev,
"TX underrun on channel %d\n", ch);
}
}
regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask);
ret = IRQ_HANDLED;
}
return ret;
}
#define ATMEL_I2S_RATES SNDRV_PCM_RATE_8000_192000
#define ATMEL_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static int atmel_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
dev->fmt = fmt;
return 0;
}
static int atmel_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
unsigned int rhr, sr = 0;
if (is_playback) {
regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr);
if (sr & ATMEL_I2SC_SR_RXRDY) {
/*
* The RX Ready flag should not be set. However if here,
* we flush (read) the Receive Holding Register to start
* from a clean state.
*/
dev_dbg(dev->dev, "RXRDY is set\n");
regmap_read(dev->regmap, ATMEL_I2SC_RHR, &rhr);
}
}
return 0;
}
static int atmel_i2s_get_gck_param(struct atmel_i2s_dev *dev, int fs)
{
int i, best;
if (!dev->gclk) {
dev_err(dev->dev, "cannot generate the I2S Master Clock\n");
return -EINVAL;
}
/*
* Find the best possible settings to generate the I2S Master Clock
* from the PLL Audio.
*/
dev->gck_param = NULL;
best = INT_MAX;
for (i = 0; i < ARRAY_SIZE(gck_params); ++i) {
const struct atmel_i2s_gck_param *gck_param = &gck_params[i];
int val = abs(fs - gck_param->fs);
if (val < best) {
best = val;
dev->gck_param = gck_param;
}
}
return 0;
}
static int atmel_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
unsigned int mr = 0, mr_mask;
int ret;
mr_mask = ATMEL_I2SC_MR_FORMAT_MASK | ATMEL_I2SC_MR_MODE_MASK |
ATMEL_I2SC_MR_DATALENGTH_MASK;
if (is_playback)
mr_mask |= ATMEL_I2SC_MR_TXMONO;
else
mr_mask |= ATMEL_I2SC_MR_RXMONO;
switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
mr |= ATMEL_I2SC_MR_FORMAT_I2S;
break;
default:
dev_err(dev->dev, "unsupported bus format\n");
return -EINVAL;
}
switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
/* codec is slave, so cpu is master */
mr |= ATMEL_I2SC_MR_MODE_MASTER;
ret = atmel_i2s_get_gck_param(dev, params_rate(params));
if (ret)
return ret;
break;
case SND_SOC_DAIFMT_BC_FC:
/* codec is master, so cpu is slave */
mr |= ATMEL_I2SC_MR_MODE_SLAVE;
dev->gck_param = NULL;
break;
default:
dev_err(dev->dev, "unsupported master/slave mode\n");
return -EINVAL;
}
switch (params_channels(params)) {
case 1:
if (is_playback)
mr |= ATMEL_I2SC_MR_TXMONO;
else
mr |= ATMEL_I2SC_MR_RXMONO;
break;
case 2:
break;
default:
dev_err(dev->dev, "unsupported number of audio channels\n");
return -EINVAL;
}
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
mr |= ATMEL_I2SC_MR_DATALENGTH_8_BITS;
break;
case SNDRV_PCM_FORMAT_S16_LE:
mr |= ATMEL_I2SC_MR_DATALENGTH_16_BITS;
break;
case SNDRV_PCM_FORMAT_S18_3LE:
mr |= ATMEL_I2SC_MR_DATALENGTH_18_BITS | ATMEL_I2SC_MR_IWS;
break;
case SNDRV_PCM_FORMAT_S20_3LE:
mr |= ATMEL_I2SC_MR_DATALENGTH_20_BITS | ATMEL_I2SC_MR_IWS;
break;
case SNDRV_PCM_FORMAT_S24_3LE:
mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS | ATMEL_I2SC_MR_IWS;
break;
case SNDRV_PCM_FORMAT_S24_LE:
mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS;
break;
case SNDRV_PCM_FORMAT_S32_LE:
mr |= ATMEL_I2SC_MR_DATALENGTH_32_BITS;
break;
default:
dev_err(dev->dev, "unsupported size/endianness for audio samples\n");
return -EINVAL;
}
return regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr);
}
static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev,
bool enabled)
{
unsigned int mr, mr_mask;
unsigned long gclk_rate;
int ret;
mr = 0;
mr_mask = (ATMEL_I2SC_MR_IMCKDIV_MASK |
ATMEL_I2SC_MR_IMCKFS_MASK |
ATMEL_I2SC_MR_IMCKMODE_MASK);
if (!enabled) {
/* Disable the I2S Master Clock generator. */
ret = regmap_write(dev->regmap, ATMEL_I2SC_CR,
ATMEL_I2SC_CR_CKDIS);
if (ret)
return ret;
/* Reset the I2S Master Clock generator settings. */
ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR,
mr_mask, mr);
if (ret)
return ret;
/* Disable/unprepare the PMC generated clock. */
clk_disable_unprepare(dev->gclk);
return 0;
}
if (!dev->gck_param)
return -EINVAL;
gclk_rate = dev->gck_param->mck * (dev->gck_param->imckdiv + 1);
ret = clk_set_rate(dev->gclk, gclk_rate);
if (ret)
return ret;
ret = clk_prepare_enable(dev->gclk);
if (ret)
return ret;
/* Update the Mode Register to generate the I2S Master Clock. */
mr |= ATMEL_I2SC_MR_IMCKDIV(dev->gck_param->imckdiv);
mr |= ATMEL_I2SC_MR_IMCKFS(dev->gck_param->imckfs);
mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK;
ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr);
if (ret)
return ret;
/* Finally enable the I2S Master Clock generator. */
return regmap_write(dev->regmap, ATMEL_I2SC_CR,
ATMEL_I2SC_CR_CKEN);
}
static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
bool is_master, mck_enabled;
unsigned int cr, mr;
int err;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
cr = is_playback ? ATMEL_I2SC_CR_TXEN : ATMEL_I2SC_CR_RXEN;
mck_enabled = true;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
cr = is_playback ? ATMEL_I2SC_CR_TXDIS : ATMEL_I2SC_CR_RXDIS;
mck_enabled = false;
break;
default:
return -EINVAL;
}
/* Read the Mode Register to retrieve the master/slave state. */
err = regmap_read(dev->regmap, ATMEL_I2SC_MR, &mr);
if (err)
return err;
is_master = (mr & ATMEL_I2SC_MR_MODE_MASK) == ATMEL_I2SC_MR_MODE_MASTER;
/* If master starts, enable the audio clock. */
if (is_master && mck_enabled) {
if (!dev->clk_use_no) {
err = atmel_i2s_switch_mck_generator(dev, true);
if (err)
return err;
}
dev->clk_use_no++;
}
err = regmap_write(dev->regmap, ATMEL_I2SC_CR, cr);
if (err)
return err;
/* If master stops, disable the audio clock. */
if (is_master && !mck_enabled) {
if (dev->clk_use_no == 1) {
err = atmel_i2s_switch_mck_generator(dev, false);
if (err)
return err;
}
dev->clk_use_no--;
}
return err;
}
static int atmel_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture);
return 0;
}
static const struct snd_soc_dai_ops atmel_i2s_dai_ops = {
.probe = atmel_i2s_dai_probe,
.prepare = atmel_i2s_prepare,
.trigger = atmel_i2s_trigger,
.hw_params = atmel_i2s_hw_params,
.set_fmt = atmel_i2s_set_dai_fmt,
};
static struct snd_soc_dai_driver atmel_i2s_dai = {
.playback = {
.channels_min = 1,
.channels_max = 2,
.rates = ATMEL_I2S_RATES,
.formats = ATMEL_I2S_FORMATS,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rates = ATMEL_I2S_RATES,
.formats = ATMEL_I2S_FORMATS,
},
.ops = &atmel_i2s_dai_ops,
.symmetric_rate = 1,
.symmetric_sample_bits = 1,
};
static const struct snd_soc_component_driver atmel_i2s_component = {
.name = "atmel-i2s",
.legacy_dai_naming = 1,
};
static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev,
struct device_node *np)
{
struct clk *muxclk;
int err;
if (!dev->gclk)
return 0;
/* muxclk is optional, so we return error for probe defer only */
muxclk = devm_clk_get(dev->dev, "muxclk");
if (IS_ERR(muxclk)) {
err = PTR_ERR(muxclk);
if (err == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_dbg(dev->dev,
"failed to get the I2S clock control: %d\n", err);
return 0;
}
return clk_set_parent(muxclk, dev->gclk);
}
static const struct atmel_i2s_caps atmel_i2s_sama5d2_caps = {
.mck_init = atmel_i2s_sama5d2_mck_init,
};
static const struct of_device_id atmel_i2s_dt_ids[] = {
{
.compatible = "atmel,sama5d2-i2s",
.data = (void *)&atmel_i2s_sama5d2_caps,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_i2s_dt_ids);
static int atmel_i2s_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
struct atmel_i2s_dev *dev;
struct resource *mem;
struct regmap *regmap;
void __iomem *base;
int irq;
int err;
unsigned int pcm_flags = 0;
unsigned int version;
/* Get memory for driver data. */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* Get hardware capabilities. */
match = of_match_node(atmel_i2s_dt_ids, np);
if (match)
dev->caps = match->data;
/* Map I/O registers. */
base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(base))
return PTR_ERR(base);
regmap = devm_regmap_init_mmio(&pdev->dev, base,
&atmel_i2s_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
/* Request IRQ. */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
err = devm_request_irq(&pdev->dev, irq, atmel_i2s_interrupt, 0,
dev_name(&pdev->dev), dev);
if (err)
return err;
/* Get the peripheral clock. */
dev->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(dev->pclk)) {
err = PTR_ERR(dev->pclk);
dev_err(&pdev->dev,
"failed to get the peripheral clock: %d\n", err);
return err;
}
/* Get audio clock to generate the I2S Master Clock (I2S_MCK) */
dev->gclk = devm_clk_get(&pdev->dev, "gclk");
if (IS_ERR(dev->gclk)) {
if (PTR_ERR(dev->gclk) == -EPROBE_DEFER)
return -EPROBE_DEFER;
/* Master Mode not supported */
dev->gclk = NULL;
}
dev->dev = &pdev->dev;
dev->regmap = regmap;
platform_set_drvdata(pdev, dev);
/* Do hardware specific settings to initialize I2S_MCK generator */
if (dev->caps && dev->caps->mck_init) {
err = dev->caps->mck_init(dev, np);
if (err)
return err;
}
/* Enable the peripheral clock. */
err = clk_prepare_enable(dev->pclk);
if (err)
return err;
/* Get IP version. */
regmap_read(dev->regmap, ATMEL_I2SC_VERSION, &version);
dev_info(&pdev->dev, "hw version: %#x\n", version);
/* Enable error interrupts. */
regmap_write(dev->regmap, ATMEL_I2SC_IER,
ATMEL_I2SC_INT_RXOR | ATMEL_I2SC_INT_TXUR);
err = devm_snd_soc_register_component(&pdev->dev,
&atmel_i2s_component,
&atmel_i2s_dai, 1);
if (err) {
dev_err(&pdev->dev, "failed to register DAI: %d\n", err);
clk_disable_unprepare(dev->pclk);
return err;
}
/* Prepare DMA config. */
dev->playback.addr = (dma_addr_t)mem->start + ATMEL_I2SC_THR;
dev->playback.maxburst = 1;
dev->capture.addr = (dma_addr_t)mem->start + ATMEL_I2SC_RHR;
dev->capture.maxburst = 1;
if (of_property_match_string(np, "dma-names", "rx-tx") == 0)
pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX;
err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags);
if (err) {
dev_err(&pdev->dev, "failed to register PCM: %d\n", err);
clk_disable_unprepare(dev->pclk);
return err;
}
return 0;
}
static void atmel_i2s_remove(struct platform_device *pdev)
{
struct atmel_i2s_dev *dev = platform_get_drvdata(pdev);
clk_disable_unprepare(dev->pclk);
}
static struct platform_driver atmel_i2s_driver = {
.driver = {
.name = "atmel_i2s",
.of_match_table = atmel_i2s_dt_ids,
},
.probe = atmel_i2s_probe,
.remove = atmel_i2s_remove,
};
module_platform_driver(atmel_i2s_driver);
MODULE_DESCRIPTION("Atmel I2S Controller driver");
MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>");
MODULE_LICENSE("GPL v2");
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC.
*
* Copyright (C) 2012 Atmel
*
* Author: Bo Shen <voice.shen@atmel.com>
*
* Based on atmel-pcm by:
* Sedji Gaouaou <sedji.gaouaou@atmel.com>
* Copyright 2008 Atmel
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "atmel-pcm.h"
/*--------------------------------------------------------------------------*\
* Hardware definition
\*--------------------------------------------------------------------------*/
static const struct snd_pcm_hardware atmel_pcm_dma_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_PAUSE,
.period_bytes_min = 256, /* lighting DMA overhead */
.period_bytes_max = 2 * 0xffff, /* if 2 bytes format */
.periods_min = 8,
.periods_max = 1024, /* no limit */
.buffer_bytes_max = 512 * 1024,
};
/*
* atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC
*
* We use DMAENGINE to send/receive data to/from SSC so this ISR is only to
* check if any overrun occured.
*/
static void atmel_pcm_dma_irq(u32 ssc_sr,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pcm_dma_params *prtd;
prtd = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
if (ssc_sr & prtd->mask->ssc_error) {
if (snd_pcm_running(substream))
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n",
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? "underrun" : "overrun", prtd->name,
ssc_sr);
/* stop RX and capture: will be enabled again at restart */
ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable);
snd_pcm_stop_xrun(substream);
/* now drain RHR and read status to remove xrun condition */
ssc_readx(prtd->ssc->regs, SSC_RHR);
ssc_readx(prtd->ssc->regs, SSC_SR);
}
}
static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pcm_dma_params *prtd;
struct ssc_device *ssc;
int ret;
prtd = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
ssc = prtd->ssc;
ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
if (ret) {
pr_err("atmel-pcm: hwparams to dma slave configure failed\n");
return ret;
}
slave_config->dst_addr = ssc->phybase + SSC_THR;
slave_config->dst_maxburst = 1;
slave_config->src_addr = ssc->phybase + SSC_RHR;
slave_config->src_maxburst = 1;
prtd->dma_intr_handler = atmel_pcm_dma_irq;
return 0;
}
static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = {
.prepare_slave_config = atmel_pcm_configure_dma,
.pcm_hardware = &atmel_pcm_dma_hardware,
.prealloc_buffer_size = 64 * 1024,
};
int atmel_pcm_dma_platform_register(struct device *dev)
{
return devm_snd_dmaengine_pcm_register(dev,
&atmel_dmaengine_pcm_config, 0);
}
EXPORT_SYMBOL(atmel_pcm_dma_platform_register);
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
MODULE_DESCRIPTION("Atmel DMA based PCM module");
MODULE_LICENSE("GPL");
+331
View File
@@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on at91-pcm. by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
*
* Based on pxa2xx-pcm.c by:
*
* Author: Nicolas Pitre
* Created: Nov 30, 2004
* Copyright: (C) 2004 MontaVista Software, Inc.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/atmel_pdc.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "atmel-pcm.h"
static int atmel_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
card->dev, ATMEL_SSC_DMABUF_SIZE,
ATMEL_SSC_DMABUF_SIZE);
return 0;
}
/*--------------------------------------------------------------------------*\
* Hardware definition
\*--------------------------------------------------------------------------*/
/* TODO: These values were taken from the AT91 platform driver, check
* them against real values for AT32
*/
static const struct snd_pcm_hardware atmel_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE,
.period_bytes_min = 32,
.period_bytes_max = 8192,
.periods_min = 2,
.periods_max = 1024,
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
};
/*--------------------------------------------------------------------------*\
* Data types
\*--------------------------------------------------------------------------*/
struct atmel_runtime_data {
struct atmel_pcm_dma_params *params;
dma_addr_t dma_buffer; /* physical address of dma buffer */
dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
size_t period_size;
dma_addr_t period_ptr; /* physical address of next period */
};
/*--------------------------------------------------------------------------*\
* ISR
\*--------------------------------------------------------------------------*/
static void atmel_pcm_dma_irq(u32 ssc_sr,
struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
static int count;
count++;
if (ssc_sr & params->mask->ssc_endbuf) {
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? "underrun" : "overrun",
params->name, ssc_sr, count);
/* re-start the PDC */
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_disable);
prtd->period_ptr += prtd->period_size;
if (prtd->period_ptr >= prtd->dma_buffer_end)
prtd->period_ptr = prtd->dma_buffer;
ssc_writex(params->ssc->regs, params->pdc->xpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xcr,
prtd->period_size / params->pdc_xfer_size);
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_enable);
}
if (ssc_sr & params->mask->ssc_endx) {
/* Load the PDC next pointer and counter registers */
prtd->period_ptr += prtd->period_size;
if (prtd->period_ptr >= prtd->dma_buffer_end)
prtd->period_ptr = prtd->dma_buffer;
ssc_writex(params->ssc->regs, params->pdc->xnpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xncr,
prtd->period_size / params->pdc_xfer_size);
}
snd_pcm_period_elapsed(substream);
}
/*--------------------------------------------------------------------------*\
* PCM operations
\*--------------------------------------------------------------------------*/
static int atmel_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
/* this may get called several times by oss emulation
* with different params */
prtd->params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
prtd->dma_buffer = runtime->dma_addr;
prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
prtd->period_size = params_period_bytes(params);
pr_debug("atmel-pcm: "
"hw_params: DMA for %s initialized "
"(dma_bytes=%zu, period_size=%zu)\n",
prtd->params->name,
runtime->dma_bytes,
prtd->period_size);
return 0;
}
static int atmel_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
if (params != NULL) {
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
params->mask->pdc_disable);
prtd->params->dma_intr_handler = NULL;
}
return 0;
}
static int atmel_pcm_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
ssc_writex(params->ssc->regs, SSC_IDR,
params->mask->ssc_endx | params->mask->ssc_endbuf);
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_disable);
return 0;
}
static int atmel_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcm_runtime *rtd = substream->runtime;
struct atmel_runtime_data *prtd = rtd->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
int ret = 0;
pr_debug("atmel-pcm:buffer_size = %ld,"
"dma_area = %p, dma_bytes = %zu\n",
rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
prtd->period_ptr = prtd->dma_buffer;
ssc_writex(params->ssc->regs, params->pdc->xpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xcr,
prtd->period_size / params->pdc_xfer_size);
prtd->period_ptr += prtd->period_size;
ssc_writex(params->ssc->regs, params->pdc->xnpr,
prtd->period_ptr);
ssc_writex(params->ssc->regs, params->pdc->xncr,
prtd->period_size / params->pdc_xfer_size);
pr_debug("atmel-pcm: trigger: "
"period_ptr=%lx, xpr=%u, "
"xcr=%u, xnpr=%u, xncr=%u\n",
(unsigned long)prtd->period_ptr,
ssc_readx(params->ssc->regs, params->pdc->xpr),
ssc_readx(params->ssc->regs, params->pdc->xcr),
ssc_readx(params->ssc->regs, params->pdc->xnpr),
ssc_readx(params->ssc->regs, params->pdc->xncr));
ssc_writex(params->ssc->regs, SSC_IER,
params->mask->ssc_endx | params->mask->ssc_endbuf);
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
params->mask->pdc_enable);
pr_debug("sr=%u imr=%u\n",
ssc_readx(params->ssc->regs, SSC_SR),
ssc_readx(params->ssc->regs, SSC_IER));
break; /* SNDRV_PCM_TRIGGER_START */
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_disable);
break;
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
params->mask->pdc_enable);
break;
default:
ret = -EINVAL;
}
return ret;
}
static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd = runtime->private_data;
struct atmel_pcm_dma_params *params = prtd->params;
dma_addr_t ptr;
snd_pcm_uframes_t x;
ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
if (x == runtime->buffer_size)
x = 0;
return x;
}
static int atmel_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd;
int ret = 0;
snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
/* ensure that buffer size is a multiple of period size */
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
goto out;
prtd = kzalloc_obj(struct atmel_runtime_data);
if (prtd == NULL) {
ret = -ENOMEM;
goto out;
}
runtime->private_data = prtd;
out:
return ret;
}
static int atmel_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct atmel_runtime_data *prtd = substream->runtime->private_data;
kfree(prtd);
return 0;
}
static const struct snd_soc_component_driver atmel_soc_platform = {
.open = atmel_pcm_open,
.close = atmel_pcm_close,
.hw_params = atmel_pcm_hw_params,
.hw_free = atmel_pcm_hw_free,
.prepare = atmel_pcm_prepare,
.trigger = atmel_pcm_trigger,
.pointer = atmel_pcm_pointer,
.pcm_new = atmel_pcm_new,
};
int atmel_pcm_pdc_platform_register(struct device *dev)
{
return devm_snd_soc_register_component(dev, &atmel_soc_platform,
NULL, 0);
}
EXPORT_SYMBOL(atmel_pcm_pdc_platform_register);
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
MODULE_DESCRIPTION("Atmel PCM module");
MODULE_LICENSE("GPL");
+91
View File
@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on at91-pcm. by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
*
* Based on pxa2xx-pcm.c by:
*
* Author: Nicolas Pitre
* Created: Nov 30, 2004
* Copyright: (C) 2004 MontaVista Software, Inc.
*/
#ifndef _ATMEL_PCM_H
#define _ATMEL_PCM_H
#include <linux/atmel-ssc.h>
#define ATMEL_SSC_DMABUF_SIZE (64 * 1024)
/*
* Registers and status bits that are required by the PCM driver.
*/
struct atmel_pdc_regs {
unsigned int xpr; /* PDC recv/trans pointer */
unsigned int xcr; /* PDC recv/trans counter */
unsigned int xnpr; /* PDC next recv/trans pointer */
unsigned int xncr; /* PDC next recv/trans counter */
unsigned int ptcr; /* PDC transfer control */
};
struct atmel_ssc_mask {
u32 ssc_enable; /* SSC recv/trans enable */
u32 ssc_disable; /* SSC recv/trans disable */
u32 ssc_error; /* SSC error conditions */
u32 ssc_endx; /* SSC ENDTX or ENDRX */
u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
u32 pdc_enable; /* PDC recv/trans enable */
u32 pdc_disable; /* PDC recv/trans disable */
};
/*
* This structure, shared between the PCM driver and the interface,
* contains all information required by the PCM driver to perform the
* PDC DMA operation. All fields except dma_intr_handler() are initialized
* by the interface. The dma_intr_handler() pointer is set by the PCM
* driver and called by the interface SSC interrupt handler if it is
* non-NULL.
*/
struct atmel_pcm_dma_params {
char *name; /* stream identifier */
int pdc_xfer_size; /* PDC counter increment in bytes */
struct ssc_device *ssc; /* SSC device for stream */
struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */
struct atmel_ssc_mask *mask; /* SSC & PDC status bits */
struct snd_pcm_substream *substream;
void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
};
/*
* SSC register access (since ssc_writel() / ssc_readl() require literal name)
*/
#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
#if IS_ENABLED(CONFIG_SND_ATMEL_SOC_PDC)
int atmel_pcm_pdc_platform_register(struct device *dev);
#else
static inline int atmel_pcm_pdc_platform_register(struct device *dev)
{
return 0;
}
#endif
#if IS_ENABLED(CONFIG_SND_ATMEL_SOC_DMA)
int atmel_pcm_dma_platform_register(struct device *dev);
#else
static inline int atmel_pcm_dma_platform_register(struct device *dev)
{
return 0;
}
#endif
#endif /* _ATMEL_PCM_H */
+702
View File
@@ -0,0 +1,702 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Atmel PDMIC driver
*
* Copyright (C) 2015 Atmel
*
* Author: Songjun Wu <songjun.wu@atmel.com>
*/
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include "atmel-pdmic.h"
struct atmel_pdmic_pdata {
u32 mic_min_freq;
u32 mic_max_freq;
s32 mic_offset;
const char *card_name;
};
struct atmel_pdmic {
dma_addr_t phy_base;
struct regmap *regmap;
struct clk *pclk;
struct clk *gclk;
struct device *dev;
int irq;
struct snd_pcm_substream *substream;
const struct atmel_pdmic_pdata *pdata;
};
static const struct of_device_id atmel_pdmic_of_match[] = {
{
.compatible = "atmel,sama5d2-pdmic",
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, atmel_pdmic_of_match);
#define PDMIC_OFFSET_MAX_VAL S16_MAX
#define PDMIC_OFFSET_MIN_VAL S16_MIN
static struct atmel_pdmic_pdata *atmel_pdmic_dt_init(struct device *dev)
{
struct device_node *np = dev->of_node;
struct atmel_pdmic_pdata *pdata;
if (!np) {
dev_err(dev, "device node not found\n");
return ERR_PTR(-EINVAL);
}
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
if (of_property_read_string(np, "atmel,model", &pdata->card_name))
pdata->card_name = "PDMIC";
if (of_property_read_u32(np, "atmel,mic-min-freq",
&pdata->mic_min_freq)) {
dev_err(dev, "failed to get mic-min-freq\n");
return ERR_PTR(-EINVAL);
}
if (of_property_read_u32(np, "atmel,mic-max-freq",
&pdata->mic_max_freq)) {
dev_err(dev, "failed to get mic-max-freq\n");
return ERR_PTR(-EINVAL);
}
if (pdata->mic_max_freq < pdata->mic_min_freq) {
dev_err(dev,
"mic-max-freq should not be less than mic-min-freq\n");
return ERR_PTR(-EINVAL);
}
if (of_property_read_s32(np, "atmel,mic-offset", &pdata->mic_offset))
pdata->mic_offset = 0;
if (pdata->mic_offset > PDMIC_OFFSET_MAX_VAL) {
dev_warn(dev,
"mic-offset value %d is larger than the max value %d, the max value is specified\n",
pdata->mic_offset, PDMIC_OFFSET_MAX_VAL);
pdata->mic_offset = PDMIC_OFFSET_MAX_VAL;
} else if (pdata->mic_offset < PDMIC_OFFSET_MIN_VAL) {
dev_warn(dev,
"mic-offset value %d is less than the min value %d, the min value is specified\n",
pdata->mic_offset, PDMIC_OFFSET_MIN_VAL);
pdata->mic_offset = PDMIC_OFFSET_MIN_VAL;
}
return pdata;
}
/* cpu dai component */
static int atmel_pdmic_cpu_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
int ret;
ret = clk_prepare_enable(dd->gclk);
if (ret)
return ret;
ret = clk_prepare_enable(dd->pclk);
if (ret) {
clk_disable_unprepare(dd->gclk);
return ret;
}
/* Clear all bits in the Control Register(PDMIC_CR) */
regmap_write(dd->regmap, PDMIC_CR, 0);
dd->substream = substream;
/* Enable the overrun error interrupt */
regmap_write(dd->regmap, PDMIC_IER, PDMIC_IER_OVRE);
return 0;
}
static void atmel_pdmic_cpu_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
/* Disable the overrun error interrupt */
regmap_write(dd->regmap, PDMIC_IDR, PDMIC_IDR_OVRE);
clk_disable_unprepare(dd->gclk);
clk_disable_unprepare(dd->pclk);
}
static int atmel_pdmic_cpu_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_component *component = cpu_dai->component;
u32 val;
int ret;
/* Clean the PDMIC Converted Data Register */
ret = regmap_read(dd->regmap, PDMIC_CDR, &val);
if (ret < 0)
return 0;
ret = snd_soc_component_update_bits(component, PDMIC_CR,
PDMIC_CR_ENPDM_MASK,
PDMIC_CR_ENPDM_DIS <<
PDMIC_CR_ENPDM_SHIFT);
if (ret < 0)
return ret;
return 0;
}
#define ATMEL_PDMIC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
/* platform */
#define ATMEL_PDMIC_MAX_BUF_SIZE (64 * 1024)
#define ATMEL_PDMIC_PREALLOC_BUF_SIZE ATMEL_PDMIC_MAX_BUF_SIZE
static const struct snd_pcm_hardware atmel_pdmic_hw = {
.info = SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID
| SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_RESUME
| SNDRV_PCM_INFO_PAUSE,
.formats = ATMEL_PDMIC_FORMATS,
.buffer_bytes_max = ATMEL_PDMIC_MAX_BUF_SIZE,
.period_bytes_min = 256,
.period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 256,
};
static int
atmel_pdmic_platform_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
int ret;
ret = snd_hwparams_to_dma_slave_config(substream, params,
slave_config);
if (ret) {
dev_err(dd->dev,
"hw params to dma slave configure failed\n");
return ret;
}
slave_config->src_addr = dd->phy_base + PDMIC_CDR;
slave_config->src_maxburst = 1;
slave_config->dst_maxburst = 1;
return 0;
}
static const struct snd_dmaengine_pcm_config
atmel_pdmic_dmaengine_pcm_config = {
.prepare_slave_config = atmel_pdmic_platform_configure_dma,
.pcm_hardware = &atmel_pdmic_hw,
.prealloc_buffer_size = ATMEL_PDMIC_PREALLOC_BUF_SIZE,
};
/* codec */
/* Mic Gain = dgain * 2^(-scale) */
struct mic_gain {
unsigned int dgain;
unsigned int scale;
};
/* range from -90 dB to 90 dB */
static const struct mic_gain mic_gain_table[] = {
{ 1, 15}, { 1, 14}, /* -90, -84 dB */
{ 3, 15}, { 1, 13}, { 3, 14}, { 1, 12}, /* -81, -78, -75, -72 dB */
{ 5, 14}, { 13, 15}, /* -70, -68 dB */
{ 9, 14}, { 21, 15}, { 23, 15}, { 13, 14}, /* -65 ~ -62 dB */
{ 29, 15}, { 33, 15}, { 37, 15}, { 41, 15}, /* -61 ~ -58 dB */
{ 23, 14}, { 13, 13}, { 58, 15}, { 65, 15}, /* -57 ~ -54 dB */
{ 73, 15}, { 41, 14}, { 23, 13}, { 13, 12}, /* -53 ~ -50 dB */
{ 29, 13}, { 65, 14}, { 73, 14}, { 41, 13}, /* -49 ~ -46 dB */
{ 23, 12}, { 207, 15}, { 29, 12}, { 65, 13}, /* -45 ~ -42 dB */
{ 73, 13}, { 41, 12}, { 23, 11}, { 413, 15}, /* -41 ~ -38 dB */
{ 463, 15}, { 519, 15}, { 583, 15}, { 327, 14}, /* -37 ~ -34 dB */
{ 367, 14}, { 823, 15}, { 231, 13}, { 1036, 15}, /* -33 ~ -30 dB */
{ 1163, 15}, { 1305, 15}, { 183, 12}, { 1642, 15}, /* -29 ~ -26 dB */
{ 1843, 15}, { 2068, 15}, { 145, 11}, { 2603, 15}, /* -25 ~ -22 dB */
{ 365, 12}, { 3277, 15}, { 3677, 15}, { 4125, 15}, /* -21 ~ -18 dB */
{ 4629, 15}, { 5193, 15}, { 5827, 15}, { 3269, 14}, /* -17 ~ -14 dB */
{ 917, 12}, { 8231, 15}, { 9235, 15}, { 5181, 14}, /* -13 ~ -10 dB */
{11627, 15}, {13045, 15}, {14637, 15}, {16423, 15}, /* -9 ~ -6 dB */
{18427, 15}, {20675, 15}, { 5799, 13}, {26029, 15}, /* -5 ~ -2 dB */
{ 7301, 13}, { 1, 0}, {18383, 14}, {10313, 13}, /* -1 ~ 2 dB */
{23143, 14}, {25967, 14}, {29135, 14}, {16345, 13}, /* 3 ~ 6 dB */
{ 4585, 11}, {20577, 13}, { 1443, 9}, {25905, 13}, /* 7 ~ 10 dB */
{14533, 12}, { 8153, 11}, { 2287, 9}, {20529, 12}, /* 11 ~ 14 dB */
{11517, 11}, { 6461, 10}, {28997, 12}, { 4067, 9}, /* 15 ~ 18 dB */
{18253, 11}, { 10, 0}, {22979, 11}, {25783, 11}, /* 19 ~ 22 dB */
{28929, 11}, {32459, 11}, { 9105, 9}, {20431, 10}, /* 23 ~ 26 dB */
{22925, 10}, {12861, 9}, { 7215, 8}, {16191, 9}, /* 27 ~ 30 dB */
{ 9083, 8}, {20383, 9}, {11435, 8}, { 6145, 7}, /* 31 ~ 34 dB */
{ 3599, 6}, {32305, 9}, {18123, 8}, {20335, 8}, /* 35 ~ 38 dB */
{ 713, 3}, { 100, 0}, { 7181, 6}, { 8057, 6}, /* 39 ~ 42 dB */
{ 565, 2}, {20287, 7}, {11381, 6}, {25539, 7}, /* 43 ~ 46 dB */
{ 1791, 3}, { 4019, 4}, { 9019, 5}, {20239, 6}, /* 47 ~ 50 dB */
{ 5677, 4}, {25479, 6}, { 7147, 4}, { 8019, 4}, /* 51 ~ 54 dB */
{17995, 5}, {20191, 5}, {11327, 4}, {12709, 4}, /* 55 ~ 58 dB */
{ 3565, 2}, { 1000, 0}, { 1122, 0}, { 1259, 0}, /* 59 ~ 62 dB */
{ 2825, 1}, {12679, 3}, { 7113, 2}, { 7981, 2}, /* 63 ~ 66 dB */
{ 8955, 2}, {20095, 3}, {22547, 3}, {12649, 2}, /* 67 ~ 70 dB */
{28385, 3}, { 3981, 0}, {17867, 2}, {20047, 2}, /* 71 ~ 74 dB */
{11247, 1}, {12619, 1}, {14159, 1}, {31773, 2}, /* 75 ~ 78 dB */
{17825, 1}, {10000, 0}, {11220, 0}, {12589, 0}, /* 79 ~ 82 dB */
{28251, 1}, {15849, 0}, {17783, 0}, {19953, 0}, /* 83 ~ 86 dB */
{22387, 0}, {25119, 0}, {28184, 0}, {31623, 0}, /* 87 ~ 90 dB */
};
static const DECLARE_TLV_DB_RANGE(mic_gain_tlv,
0, 1, TLV_DB_SCALE_ITEM(-9000, 600, 0),
2, 5, TLV_DB_SCALE_ITEM(-8100, 300, 0),
6, 7, TLV_DB_SCALE_ITEM(-7000, 200, 0),
8, ARRAY_SIZE(mic_gain_table)-1, TLV_DB_SCALE_ITEM(-6500, 100, 0),
);
static int pdmic_get_mic_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
unsigned int dgain_val, scale_val;
int i;
dgain_val = (snd_soc_component_read(component, PDMIC_DSPR1) & PDMIC_DSPR1_DGAIN_MASK)
>> PDMIC_DSPR1_DGAIN_SHIFT;
scale_val = (snd_soc_component_read(component, PDMIC_DSPR0) & PDMIC_DSPR0_SCALE_MASK)
>> PDMIC_DSPR0_SCALE_SHIFT;
for (i = 0; i < ARRAY_SIZE(mic_gain_table); i++) {
if ((mic_gain_table[i].dgain == dgain_val) &&
(mic_gain_table[i].scale == scale_val))
ucontrol->value.integer.value[0] = i;
}
return 0;
}
static int pdmic_put_mic_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
int max = mc->max;
unsigned int val;
int ret;
val = ucontrol->value.integer.value[0];
if (val > max)
return -EINVAL;
ret = snd_soc_component_update_bits(component, PDMIC_DSPR1, PDMIC_DSPR1_DGAIN_MASK,
mic_gain_table[val].dgain << PDMIC_DSPR1_DGAIN_SHIFT);
if (ret < 0)
return ret;
ret = snd_soc_component_update_bits(component, PDMIC_DSPR0, PDMIC_DSPR0_SCALE_MASK,
mic_gain_table[val].scale << PDMIC_DSPR0_SCALE_SHIFT);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_kcontrol_new atmel_pdmic_snd_controls[] = {
SOC_SINGLE_EXT_TLV("Mic Capture Volume", PDMIC_DSPR1, PDMIC_DSPR1_DGAIN_SHIFT,
ARRAY_SIZE(mic_gain_table)-1, 0,
pdmic_get_mic_volsw, pdmic_put_mic_volsw, mic_gain_tlv),
SOC_SINGLE("High Pass Filter Switch", PDMIC_DSPR0,
PDMIC_DSPR0_HPFBYP_SHIFT, 1, 1),
SOC_SINGLE("SINCC Filter Switch", PDMIC_DSPR0, PDMIC_DSPR0_SINBYP_SHIFT, 1, 1),
};
static int atmel_pdmic_component_probe(struct snd_soc_component *component)
{
struct snd_soc_card *card = snd_soc_component_get_drvdata(component);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(card);
snd_soc_component_update_bits(component, PDMIC_DSPR1, PDMIC_DSPR1_OFFSET_MASK,
(u32)(dd->pdata->mic_offset << PDMIC_DSPR1_OFFSET_SHIFT));
return 0;
}
#define PDMIC_MR_PRESCAL_MAX_VAL 127
static int
atmel_pdmic_cpu_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_component *component = cpu_dai->component;
unsigned int rate_min = substream->runtime->hw.rate_min;
unsigned int rate_max = substream->runtime->hw.rate_max;
int fs = params_rate(params);
int bits = params_width(params);
unsigned long pclk_rate, gclk_rate;
unsigned int f_pdmic;
u32 mr_val, dspr0_val, pclk_prescal, gclk_prescal;
if (params_channels(params) != 1) {
dev_err(component->dev,
"only supports one channel\n");
return -EINVAL;
}
if ((fs < rate_min) || (fs > rate_max)) {
dev_err(component->dev,
"sample rate is %dHz, min rate is %dHz, max rate is %dHz\n",
fs, rate_min, rate_max);
return -EINVAL;
}
switch (bits) {
case 16:
dspr0_val = (PDMIC_DSPR0_SIZE_16_BITS
<< PDMIC_DSPR0_SIZE_SHIFT);
break;
case 32:
dspr0_val = (PDMIC_DSPR0_SIZE_32_BITS
<< PDMIC_DSPR0_SIZE_SHIFT);
break;
default:
return -EINVAL;
}
if ((fs << 7) > (rate_max << 6)) {
f_pdmic = fs << 6;
dspr0_val |= PDMIC_DSPR0_OSR_64 << PDMIC_DSPR0_OSR_SHIFT;
} else {
f_pdmic = fs << 7;
dspr0_val |= PDMIC_DSPR0_OSR_128 << PDMIC_DSPR0_OSR_SHIFT;
}
pclk_rate = clk_get_rate(dd->pclk);
gclk_rate = clk_get_rate(dd->gclk);
/* PRESCAL = SELCK/(2*f_pdmic) - 1*/
pclk_prescal = (u32)(pclk_rate/(f_pdmic << 1)) - 1;
gclk_prescal = (u32)(gclk_rate/(f_pdmic << 1)) - 1;
if ((pclk_prescal > PDMIC_MR_PRESCAL_MAX_VAL) ||
(gclk_rate/((gclk_prescal + 1) << 1) <
pclk_rate/((pclk_prescal + 1) << 1))) {
mr_val = gclk_prescal << PDMIC_MR_PRESCAL_SHIFT;
mr_val |= PDMIC_MR_CLKS_GCK << PDMIC_MR_CLKS_SHIFT;
} else {
mr_val = pclk_prescal << PDMIC_MR_PRESCAL_SHIFT;
mr_val |= PDMIC_MR_CLKS_PCK << PDMIC_MR_CLKS_SHIFT;
}
snd_soc_component_update_bits(component, PDMIC_MR,
PDMIC_MR_PRESCAL_MASK | PDMIC_MR_CLKS_MASK, mr_val);
snd_soc_component_update_bits(component, PDMIC_DSPR0,
PDMIC_DSPR0_OSR_MASK | PDMIC_DSPR0_SIZE_MASK, dspr0_val);
return 0;
}
static int atmel_pdmic_cpu_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *cpu_dai)
{
struct snd_soc_component *component = cpu_dai->component;
u32 val;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
val = PDMIC_CR_ENPDM_EN << PDMIC_CR_ENPDM_SHIFT;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
val = PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT;
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, PDMIC_CR, PDMIC_CR_ENPDM_MASK, val);
return 0;
}
static const struct snd_soc_dai_ops atmel_pdmic_cpu_dai_ops = {
.startup = atmel_pdmic_cpu_dai_startup,
.shutdown = atmel_pdmic_cpu_dai_shutdown,
.prepare = atmel_pdmic_cpu_dai_prepare,
.hw_params = atmel_pdmic_cpu_dai_hw_params,
.trigger = atmel_pdmic_cpu_dai_trigger,
};
static struct snd_soc_dai_driver atmel_pdmic_cpu_dai = {
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = ATMEL_PDMIC_FORMATS,
},
.ops = &atmel_pdmic_cpu_dai_ops,
};
static const struct snd_soc_component_driver atmel_pdmic_cpu_dai_component = {
.name = "atmel-pdmic",
.probe = atmel_pdmic_component_probe,
.controls = atmel_pdmic_snd_controls,
.num_controls = ARRAY_SIZE(atmel_pdmic_snd_controls),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.legacy_dai_naming = 1,
};
/* ASoC sound card */
static int atmel_pdmic_asoc_card_init(struct device *dev,
struct snd_soc_card *card)
{
struct snd_soc_dai_link *dai_link;
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(card);
struct snd_soc_dai_link_component *comp;
dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL);
if (!dai_link)
return -ENOMEM;
comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
dai_link->cpus = comp;
dai_link->codecs = &snd_soc_dummy_dlc;
dai_link->num_cpus = 1;
dai_link->num_codecs = 1;
dai_link->name = "PDMIC";
dai_link->stream_name = "PDMIC PCM";
dai_link->cpus->dai_name = dev_name(dev);
card->dai_link = dai_link;
card->num_links = 1;
card->name = dd->pdata->card_name;
card->dev = dev;
return 0;
}
static void atmel_pdmic_get_sample_rate(struct atmel_pdmic *dd,
unsigned int *rate_min, unsigned int *rate_max)
{
u32 mic_min_freq = dd->pdata->mic_min_freq;
u32 mic_max_freq = dd->pdata->mic_max_freq;
u32 clk_max_rate = (u32)(clk_get_rate(dd->pclk) >> 1);
u32 clk_min_rate = (u32)(clk_get_rate(dd->gclk) >> 8);
if (mic_max_freq > clk_max_rate)
mic_max_freq = clk_max_rate;
if (mic_min_freq < clk_min_rate)
mic_min_freq = clk_min_rate;
*rate_min = DIV_ROUND_CLOSEST(mic_min_freq, 128);
*rate_max = mic_max_freq >> 6;
}
/* PDMIC interrupt handler */
static irqreturn_t atmel_pdmic_interrupt(int irq, void *dev_id)
{
struct atmel_pdmic *dd = (struct atmel_pdmic *)dev_id;
u32 pdmic_isr;
irqreturn_t ret = IRQ_NONE;
regmap_read(dd->regmap, PDMIC_ISR, &pdmic_isr);
if (pdmic_isr & PDMIC_ISR_OVRE) {
regmap_update_bits(dd->regmap, PDMIC_CR, PDMIC_CR_ENPDM_MASK,
PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT);
snd_pcm_stop_xrun(dd->substream);
ret = IRQ_HANDLED;
}
return ret;
}
/* regmap configuration */
#define ATMEL_PDMIC_REG_MAX 0x124
static const struct regmap_config atmel_pdmic_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = ATMEL_PDMIC_REG_MAX,
};
static int atmel_pdmic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct atmel_pdmic *dd;
struct resource *res;
void __iomem *io_base;
const struct atmel_pdmic_pdata *pdata;
struct snd_soc_card *card;
unsigned int rate_min, rate_max;
int ret;
pdata = atmel_pdmic_dt_init(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
if (!dd)
return -ENOMEM;
dd->pdata = pdata;
dd->dev = dev;
dd->irq = platform_get_irq(pdev, 0);
if (dd->irq < 0)
return dd->irq;
dd->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dd->pclk)) {
ret = PTR_ERR(dd->pclk);
dev_err(dev, "failed to get peripheral clock: %d\n", ret);
return ret;
}
dd->gclk = devm_clk_get(dev, "gclk");
if (IS_ERR(dd->gclk)) {
ret = PTR_ERR(dd->gclk);
dev_err(dev, "failed to get GCK: %d\n", ret);
return ret;
}
/* The gclk clock frequency must always be three times
* lower than the pclk clock frequency
*/
ret = clk_set_rate(dd->gclk, clk_get_rate(dd->pclk)/3);
if (ret) {
dev_err(dev, "failed to set GCK clock rate: %d\n", ret);
return ret;
}
io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(io_base))
return PTR_ERR(io_base);
dd->phy_base = res->start;
dd->regmap = devm_regmap_init_mmio(dev, io_base,
&atmel_pdmic_regmap_config);
if (IS_ERR(dd->regmap)) {
ret = PTR_ERR(dd->regmap);
dev_err(dev, "failed to init register map: %d\n", ret);
return ret;
}
ret = devm_request_irq(dev, dd->irq, atmel_pdmic_interrupt, 0,
"PDMIC", (void *)dd);
if (ret < 0) {
dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
dd->irq, ret);
return ret;
}
/* Get the minimal and maximal sample rate that the microphone supports */
atmel_pdmic_get_sample_rate(dd, &rate_min, &rate_max);
/* register cpu dai */
atmel_pdmic_cpu_dai.capture.rate_min = rate_min;
atmel_pdmic_cpu_dai.capture.rate_max = rate_max;
ret = devm_snd_soc_register_component(dev,
&atmel_pdmic_cpu_dai_component,
&atmel_pdmic_cpu_dai, 1);
if (ret) {
dev_err(dev, "could not register CPU DAI: %d\n", ret);
return ret;
}
/* register platform */
ret = devm_snd_dmaengine_pcm_register(dev,
&atmel_pdmic_dmaengine_pcm_config,
0);
if (ret) {
dev_err(dev, "could not register platform: %d\n", ret);
return ret;
}
/* register sound card */
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card) {
ret = -ENOMEM;
goto unregister_codec;
}
snd_soc_card_set_drvdata(card, dd);
ret = atmel_pdmic_asoc_card_init(dev, card);
if (ret) {
dev_err(dev, "failed to init sound card: %d\n", ret);
goto unregister_codec;
}
ret = devm_snd_soc_register_card(dev, card);
if (ret) {
dev_err(dev, "failed to register sound card: %d\n", ret);
goto unregister_codec;
}
return 0;
unregister_codec:
return ret;
}
static struct platform_driver atmel_pdmic_driver = {
.driver = {
.name = "atmel-pdmic",
.of_match_table = atmel_pdmic_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = atmel_pdmic_probe,
};
module_platform_driver(atmel_pdmic_driver);
MODULE_DESCRIPTION("Atmel PDMIC driver under ALSA SoC architecture");
MODULE_AUTHOR("Songjun Wu <songjun.wu@atmel.com>");
MODULE_LICENSE("GPL v2");
+81
View File
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ATMEL_PDMIC_H_
#define __ATMEL_PDMIC_H_
#include <linux/bitops.h>
#define PDMIC_CR 0x00000000
#define PDMIC_CR_SWRST 0x1
#define PDMIC_CR_SWRST_MASK BIT(0)
#define PDMIC_CR_SWRST_SHIFT (0)
#define PDMIC_CR_ENPDM_DIS 0x0
#define PDMIC_CR_ENPDM_EN 0x1
#define PDMIC_CR_ENPDM_MASK BIT(4)
#define PDMIC_CR_ENPDM_SHIFT (4)
#define PDMIC_MR 0x00000004
#define PDMIC_MR_CLKS_PCK 0x0
#define PDMIC_MR_CLKS_GCK 0x1
#define PDMIC_MR_CLKS_MASK BIT(4)
#define PDMIC_MR_CLKS_SHIFT (4)
#define PDMIC_MR_PRESCAL_MASK GENMASK(14, 8)
#define PDMIC_MR_PRESCAL_SHIFT (8)
#define PDMIC_CDR 0x00000014
#define PDMIC_IER 0x00000018
#define PDMIC_IER_OVRE BIT(25)
#define PDMIC_IDR 0x0000001c
#define PDMIC_IDR_OVRE BIT(25)
#define PDMIC_IMR 0x00000020
#define PDMIC_ISR 0x00000024
#define PDMIC_ISR_OVRE BIT(25)
#define PDMIC_DSPR0 0x00000058
#define PDMIC_DSPR0_HPFBYP_DIS 0x1
#define PDMIC_DSPR0_HPFBYP_EN 0x0
#define PDMIC_DSPR0_HPFBYP_MASK BIT(1)
#define PDMIC_DSPR0_HPFBYP_SHIFT (1)
#define PDMIC_DSPR0_SINBYP_DIS 0x1
#define PDMIC_DSPR0_SINBYP_EN 0x0
#define PDMIC_DSPR0_SINBYP_MASK BIT(2)
#define PDMIC_DSPR0_SINBYP_SHIFT (2)
#define PDMIC_DSPR0_SIZE_16_BITS 0x0
#define PDMIC_DSPR0_SIZE_32_BITS 0x1
#define PDMIC_DSPR0_SIZE_MASK BIT(3)
#define PDMIC_DSPR0_SIZE_SHIFT (3)
#define PDMIC_DSPR0_OSR_128 0x0
#define PDMIC_DSPR0_OSR_64 0x1
#define PDMIC_DSPR0_OSR_MASK GENMASK(6, 4)
#define PDMIC_DSPR0_OSR_SHIFT (4)
#define PDMIC_DSPR0_SCALE_MASK GENMASK(11, 8)
#define PDMIC_DSPR0_SCALE_SHIFT (8)
#define PDMIC_DSPR0_SHIFT_MASK GENMASK(15, 12)
#define PDMIC_DSPR0_SHIFT_SHIFT (12)
#define PDMIC_DSPR1 0x0000005c
#define PDMIC_DSPR1_DGAIN_MASK GENMASK(14, 0)
#define PDMIC_DSPR1_DGAIN_SHIFT (0)
#define PDMIC_DSPR1_OFFSET_MASK GENMASK(31, 16)
#define PDMIC_DSPR1_OFFSET_SHIFT (16)
#define PDMIC_WPMR 0x000000e4
#define PDMIC_WPSR 0x000000e8
#endif
+923
View File
@@ -0,0 +1,923 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
* ATMEL CORP.
*
* Based on at91-ssc.c by
* Frank Mandarino <fmandarino@endrelia.com>
* Based on pxa2xx Platform drivers by
* Liam Girdwood <lrg@slimlogic.co.uk>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/atmel_pdc.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include "atmel-pcm.h"
#include "atmel_ssc_dai.h"
#define NUM_SSC_DEVICES 3
/*
* SSC PDC registers required by the PCM DMA engine.
*/
static struct atmel_pdc_regs pdc_tx_reg = {
.xpr = ATMEL_PDC_TPR,
.xcr = ATMEL_PDC_TCR,
.xnpr = ATMEL_PDC_TNPR,
.xncr = ATMEL_PDC_TNCR,
};
static struct atmel_pdc_regs pdc_rx_reg = {
.xpr = ATMEL_PDC_RPR,
.xcr = ATMEL_PDC_RCR,
.xnpr = ATMEL_PDC_RNPR,
.xncr = ATMEL_PDC_RNCR,
};
/*
* SSC & PDC status bits for transmit and receive.
*/
static struct atmel_ssc_mask ssc_tx_mask = {
.ssc_enable = SSC_BIT(CR_TXEN),
.ssc_disable = SSC_BIT(CR_TXDIS),
.ssc_endx = SSC_BIT(SR_ENDTX),
.ssc_endbuf = SSC_BIT(SR_TXBUFE),
.ssc_error = SSC_BIT(SR_OVRUN),
.pdc_enable = ATMEL_PDC_TXTEN,
.pdc_disable = ATMEL_PDC_TXTDIS,
};
static struct atmel_ssc_mask ssc_rx_mask = {
.ssc_enable = SSC_BIT(CR_RXEN),
.ssc_disable = SSC_BIT(CR_RXDIS),
.ssc_endx = SSC_BIT(SR_ENDRX),
.ssc_endbuf = SSC_BIT(SR_RXBUFF),
.ssc_error = SSC_BIT(SR_OVRUN),
.pdc_enable = ATMEL_PDC_RXTEN,
.pdc_disable = ATMEL_PDC_RXTDIS,
};
/*
* DMA parameters.
*/
static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
{{
.name = "SSC0 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
.name = "SSC0 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
} },
{{
.name = "SSC1 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
.name = "SSC1 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
} },
{{
.name = "SSC2 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
.name = "SSC2 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
} },
};
static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
{
.name = "ssc0",
.dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0,
},
{
.name = "ssc1",
.dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0,
},
{
.name = "ssc2",
.dir_mask = SSC_DIR_MASK_UNUSED,
.initialized = 0,
},
};
/*
* SSC interrupt handler. Passes PDC interrupts to the DMA
* interrupt handler in the PCM driver.
*/
static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id)
{
struct atmel_ssc_info *ssc_p = dev_id;
struct atmel_pcm_dma_params *dma_params;
u32 ssc_sr;
u32 ssc_substream_mask;
int i;
ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR)
& (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR);
/*
* Loop through the substreams attached to this SSC. If
* a DMA-related interrupt occurred on that substream, call
* the DMA interrupt handler function, if one has been
* registered in the dma_params structure by the PCM driver.
*/
for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
dma_params = ssc_p->dma_params[i];
if ((dma_params != NULL) &&
(dma_params->dma_intr_handler != NULL)) {
ssc_substream_mask = (dma_params->mask->ssc_endx |
dma_params->mask->ssc_endbuf);
if (ssc_sr & ssc_substream_mask) {
dma_params->dma_intr_handler(ssc_sr,
dma_params->
substream);
}
}
}
return IRQ_HANDLED;
}
/*
* When the bit clock is input, limit the maximum rate according to the
* Serial Clock Ratio Considerations section from the SSC documentation:
*
* The Transmitter and the Receiver can be programmed to operate
* with the clock signals provided on either the TK or RK pins.
* This allows the SSC to support many slave-mode data transfers.
* In this case, the maximum clock speed allowed on the RK pin is:
* - Peripheral clock divided by 2 if Receiver Frame Synchro is input
* - Peripheral clock divided by 3 if Receiver Frame Synchro is output
* In addition, the maximum clock speed allowed on the TK pin is:
* - Peripheral clock divided by 6 if Transmit Frame Synchro is input
* - Peripheral clock divided by 2 if Transmit Frame Synchro is output
*
* When the bit clock is output, limit the rate according to the
* SSC divider restrictions.
*/
static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct atmel_ssc_info *ssc_p = rule->private;
struct ssc_device *ssc = ssc_p->ssc;
struct snd_interval *i = hw_param_interval(params, rule->var);
struct snd_interval t;
struct snd_ratnum r = {
.den_min = 1,
.den_max = 4095,
.den_step = 1,
};
unsigned int num = 0, den = 0;
int frame_size;
int mck_div = 2;
int ret;
frame_size = snd_soc_params_to_frame_size(params);
if (frame_size < 0)
return frame_size;
switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FP:
if ((ssc_p->dir_mask & SSC_DIR_MASK_CAPTURE)
&& ssc->clk_from_rk_pin)
/* Receiver Frame Synchro (i.e. capture)
* is output (format is _CFS) and the RK pin
* is used for input (format is _CBM_).
*/
mck_div = 3;
break;
case SND_SOC_DAIFMT_BC_FC:
if ((ssc_p->dir_mask & SSC_DIR_MASK_PLAYBACK)
&& !ssc->clk_from_rk_pin)
/* Transmit Frame Synchro (i.e. playback)
* is input (format is _CFM) and the TK pin
* is used for input (format _CBM_ but not
* using the RK pin).
*/
mck_div = 6;
break;
}
switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
r.num = ssc_p->mck_rate / mck_div / frame_size;
ret = snd_interval_ratnum(i, 1, &r, &num, &den);
if (ret >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
params->rate_num = num;
params->rate_den = den;
}
break;
case SND_SOC_DAIFMT_BC_FP:
case SND_SOC_DAIFMT_BC_FC:
t.min = 8000;
t.max = ssc_p->mck_rate / mck_div / frame_size;
t.openmin = t.openmax = 0;
t.integer = 0;
ret = snd_interval_refine(i, &t);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/*-------------------------------------------------------------------------*\
* DAI functions
\*-------------------------------------------------------------------------*/
/*
* Startup. Only that one substream allowed in each direction.
*/
static int atmel_ssc_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct platform_device *pdev = to_platform_device(dai->dev);
struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
struct atmel_pcm_dma_params *dma_params;
int dir, dir_mask;
int ret;
pr_debug("atmel_ssc_startup: SSC_SR=0x%x\n",
ssc_readl(ssc_p->ssc->regs, SR));
/* Enable PMC peripheral clock for this SSC */
pr_debug("atmel_ssc_dai: Starting clock\n");
ret = clk_enable(ssc_p->ssc->clk);
if (ret)
return ret;
ssc_p->mck_rate = clk_get_rate(ssc_p->ssc->clk);
/* Reset the SSC unless initialized to keep it in a clean state */
if (!ssc_p->initialized)
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
dir = 0;
dir_mask = SSC_DIR_MASK_PLAYBACK;
} else {
dir = 1;
dir_mask = SSC_DIR_MASK_CAPTURE;
}
ret = snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
atmel_ssc_hw_rule_rate,
ssc_p,
SNDRV_PCM_HW_PARAM_FRAME_BITS,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (ret < 0) {
dev_err(dai->dev, "Failed to specify rate rule: %d\n", ret);
return ret;
}
dma_params = &ssc_dma_params[pdev->id][dir];
dma_params->ssc = ssc_p->ssc;
dma_params->substream = substream;
ssc_p->dma_params[dir] = dma_params;
snd_soc_dai_set_dma_data(dai, substream, dma_params);
if (ssc_p->dir_mask & dir_mask)
return -EBUSY;
ssc_p->dir_mask |= dir_mask;
return 0;
}
/*
* Shutdown. Clear DMA parameters and shutdown the SSC if there
* are no other substreams open.
*/
static void atmel_ssc_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct platform_device *pdev = to_platform_device(dai->dev);
struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
struct atmel_pcm_dma_params *dma_params;
int dir, dir_mask;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
if (dma_params != NULL) {
dma_params->ssc = NULL;
dma_params->substream = NULL;
ssc_p->dma_params[dir] = NULL;
}
dir_mask = 1 << dir;
ssc_p->dir_mask &= ~dir_mask;
if (!ssc_p->dir_mask) {
if (ssc_p->initialized) {
free_irq(ssc_p->ssc->irq, ssc_p);
ssc_p->initialized = 0;
}
/* Reset the SSC */
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
/* Clear the SSC dividers */
ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
ssc_p->forced_divider = 0;
}
/* Shutdown the SSC clock. */
pr_debug("atmel_ssc_dai: Stopping clock\n");
clk_disable(ssc_p->ssc->clk);
}
/*
* Record the DAI format for use in hw_params().
*/
static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct platform_device *pdev = to_platform_device(cpu_dai->dev);
struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
ssc_p->daifmt = fmt;
return 0;
}
/*
* Record SSC clock dividers for use in hw_params().
*/
static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct platform_device *pdev = to_platform_device(cpu_dai->dev);
struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
switch (div_id) {
case ATMEL_SSC_CMR_DIV:
/*
* The same master clock divider is used for both
* transmit and receive, so if a value has already
* been set, it must match this value.
*/
if (ssc_p->dir_mask !=
(SSC_DIR_MASK_PLAYBACK | SSC_DIR_MASK_CAPTURE))
ssc_p->cmr_div = div;
else if (ssc_p->cmr_div == 0)
ssc_p->cmr_div = div;
else
if (div != ssc_p->cmr_div)
return -EBUSY;
ssc_p->forced_divider |= BIT(ATMEL_SSC_CMR_DIV);
break;
case ATMEL_SSC_TCMR_PERIOD:
ssc_p->tcmr_period = div;
ssc_p->forced_divider |= BIT(ATMEL_SSC_TCMR_PERIOD);
break;
case ATMEL_SSC_RCMR_PERIOD:
ssc_p->rcmr_period = div;
ssc_p->forced_divider |= BIT(ATMEL_SSC_RCMR_PERIOD);
break;
default:
return -EINVAL;
}
return 0;
}
/* Is the cpu-dai master of the frame clock? */
static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p)
{
switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FP:
case SND_SOC_DAIFMT_BP_FP:
return 1;
}
return 0;
}
/* Is the cpu-dai master of the bit clock? */
static int atmel_ssc_cbs(struct atmel_ssc_info *ssc_p)
{
switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FC:
case SND_SOC_DAIFMT_BP_FP:
return 1;
}
return 0;
}
/*
* Configure the SSC.
*/
static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct platform_device *pdev = to_platform_device(dai->dev);
int id = pdev->id;
struct atmel_ssc_info *ssc_p = &ssc_info[id];
struct ssc_device *ssc = ssc_p->ssc;
struct atmel_pcm_dma_params *dma_params;
int dir, channels, bits;
u32 tfmr, rfmr, tcmr, rcmr;
int ret;
int fslen, fslen_ext, fs_osync, fs_edge;
u32 cmr_div;
u32 tcmr_period;
u32 rcmr_period;
/*
* Currently, there is only one set of dma params for
* each direction. If more are added, this code will
* have to be changed to select the proper set.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
/*
* If the cpu dai should provide BCLK, but noone has provided the
* divider needed for that to work, fall back to something sensible.
*/
cmr_div = ssc_p->cmr_div;
if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_CMR_DIV)) &&
atmel_ssc_cbs(ssc_p)) {
int bclk_rate = snd_soc_params_to_bclk(params);
if (bclk_rate < 0) {
dev_err(dai->dev, "unable to calculate cmr_div: %d\n",
bclk_rate);
return bclk_rate;
}
cmr_div = DIV_ROUND_CLOSEST(ssc_p->mck_rate, 2 * bclk_rate);
}
/*
* If the cpu dai should provide LRCLK, but noone has provided the
* dividers needed for that to work, fall back to something sensible.
*/
tcmr_period = ssc_p->tcmr_period;
rcmr_period = ssc_p->rcmr_period;
if (atmel_ssc_cfs(ssc_p)) {
int frame_size = snd_soc_params_to_frame_size(params);
if (frame_size < 0) {
dev_err(dai->dev,
"unable to calculate tx/rx cmr_period: %d\n",
frame_size);
return frame_size;
}
if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_TCMR_PERIOD)))
tcmr_period = frame_size / 2 - 1;
if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_RCMR_PERIOD)))
rcmr_period = frame_size / 2 - 1;
}
dma_params = ssc_p->dma_params[dir];
channels = params_channels(params);
/*
* Determine sample size in bits and the PDC increment.
*/
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
bits = 8;
dma_params->pdc_xfer_size = 1;
break;
case SNDRV_PCM_FORMAT_S16_LE:
bits = 16;
dma_params->pdc_xfer_size = 2;
break;
case SNDRV_PCM_FORMAT_S24_LE:
bits = 24;
dma_params->pdc_xfer_size = 4;
break;
case SNDRV_PCM_FORMAT_S32_LE:
bits = 32;
dma_params->pdc_xfer_size = 4;
break;
default:
printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format");
return -EINVAL;
}
/*
* Compute SSC register settings.
*/
fslen_ext = (bits - 1) / 16;
fslen = (bits - 1) % 16;
switch (ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
fs_osync = SSC_FSOS_POSITIVE;
fs_edge = SSC_START_RISING_RF;
rcmr = SSC_BF(RCMR_STTDLY, 0);
tcmr = SSC_BF(TCMR_STTDLY, 0);
break;
case SND_SOC_DAIFMT_I2S:
fs_osync = SSC_FSOS_NEGATIVE;
fs_edge = SSC_START_FALLING_RF;
rcmr = SSC_BF(RCMR_STTDLY, 1);
tcmr = SSC_BF(TCMR_STTDLY, 1);
break;
case SND_SOC_DAIFMT_DSP_A:
/*
* DSP/PCM Mode A format
*
* Data is transferred on first BCLK after LRC pulse rising
* edge.If stereo, the right channel data is contiguous with
* the left channel data.
*/
fs_osync = SSC_FSOS_POSITIVE;
fs_edge = SSC_START_RISING_RF;
fslen = fslen_ext = 0;
rcmr = SSC_BF(RCMR_STTDLY, 1);
tcmr = SSC_BF(TCMR_STTDLY, 1);
break;
default:
printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
ssc_p->daifmt);
return -EINVAL;
}
if (!atmel_ssc_cfs(ssc_p)) {
fslen = fslen_ext = 0;
rcmr_period = tcmr_period = 0;
fs_osync = SSC_FSOS_NONE;
}
rcmr |= SSC_BF(RCMR_START, fs_edge);
tcmr |= SSC_BF(TCMR_START, fs_edge);
if (atmel_ssc_cbs(ssc_p)) {
/*
* SSC provides BCLK
*
* The SSC transmit and receive clocks are generated from the
* MCK divider, and the BCLK signal is output
* on the SSC TK line.
*/
rcmr |= SSC_BF(RCMR_CKS, SSC_CKS_DIV)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE);
tcmr |= SSC_BF(TCMR_CKS, SSC_CKS_DIV)
| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS);
} else {
rcmr |= SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ?
SSC_CKS_PIN : SSC_CKS_CLOCK)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE);
tcmr |= SSC_BF(TCMR_CKS, ssc->clk_from_rk_pin ?
SSC_CKS_CLOCK : SSC_CKS_PIN)
| SSC_BF(TCMR_CKO, SSC_CKO_NONE);
}
rcmr |= SSC_BF(RCMR_PERIOD, rcmr_period)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING);
tcmr |= SSC_BF(TCMR_PERIOD, tcmr_period)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING);
rfmr = SSC_BF(RFMR_FSLEN_EXT, fslen_ext)
| SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(RFMR_FSOS, fs_osync)
| SSC_BF(RFMR_FSLEN, fslen)
| SSC_BF(RFMR_DATNB, (channels - 1))
| SSC_BIT(RFMR_MSBF)
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tfmr = SSC_BF(TFMR_FSLEN_EXT, fslen_ext)
| SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
| SSC_BF(TFMR_FSDEN, 0)
| SSC_BF(TFMR_FSOS, fs_osync)
| SSC_BF(TFMR_FSLEN, fslen)
| SSC_BF(TFMR_DATNB, (channels - 1))
| SSC_BIT(TFMR_MSBF)
| SSC_BF(TFMR_DATDEF, 0)
| SSC_BF(TFMR_DATLEN, (bits - 1));
if (fslen_ext && !ssc->pdata->has_fslen_ext) {
dev_err(dai->dev, "sample size %d is too large for SSC device\n",
bits);
return -EINVAL;
}
pr_debug("atmel_ssc_hw_params: "
"RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
rcmr, rfmr, tcmr, tfmr);
if (!ssc_p->initialized) {
if (!ssc_p->ssc->pdata->use_dma) {
ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
}
ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0,
ssc_p->name, ssc_p);
if (ret < 0) {
printk(KERN_WARNING
"atmel_ssc_dai: request_irq failure\n");
pr_debug("Atmel_ssc_dai: Stopping clock\n");
clk_disable(ssc_p->ssc->clk);
return ret;
}
ssc_p->initialized = 1;
}
/* set SSC clock mode register */
ssc_writel(ssc_p->ssc->regs, CMR, cmr_div);
/* set receive clock mode and format */
ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
/* set transmit clock mode and format */
ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n");
return 0;
}
static int atmel_ssc_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct platform_device *pdev = to_platform_device(dai->dev);
struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
struct atmel_pcm_dma_params *dma_params;
int dir;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
ssc_writel(ssc_p->ssc->regs, IDR, dma_params->mask->ssc_error);
pr_debug("%s enabled SSC_SR=0x%08x\n",
dir ? "receive" : "transmit",
ssc_readl(ssc_p->ssc->regs, SR));
return 0;
}
static int atmel_ssc_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct platform_device *pdev = to_platform_device(dai->dev);
struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
struct atmel_pcm_dma_params *dma_params;
int dir;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0;
else
dir = 1;
dma_params = ssc_p->dma_params[dir];
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
break;
default:
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
break;
}
return 0;
}
static int atmel_ssc_suspend(struct snd_soc_component *component)
{
struct atmel_ssc_info *ssc_p;
struct platform_device *pdev = to_platform_device(component->dev);
if (!snd_soc_component_active(component))
return 0;
ssc_p = &ssc_info[pdev->id];
/* Save the status register before disabling transmit and receive */
ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
/* Save the current interrupt mask, then disable unmasked interrupts */
ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
return 0;
}
static int atmel_ssc_resume(struct snd_soc_component *component)
{
struct atmel_ssc_info *ssc_p;
struct platform_device *pdev = to_platform_device(component->dev);
u32 cr;
if (!snd_soc_component_active(component))
return 0;
ssc_p = &ssc_info[pdev->id];
/* restore SSC register settings */
ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
/* re-enable interrupts */
ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
/* Re-enable receive and transmit as appropriate */
cr = 0;
cr |=
(ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
cr |=
(ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
ssc_writel(ssc_p->ssc->regs, CR, cr);
return 0;
}
/* S24_LE is not supported if more than 2 channels (of TDM slots) are used. */
#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
.startup = atmel_ssc_startup,
.shutdown = atmel_ssc_shutdown,
.prepare = atmel_ssc_prepare,
.trigger = atmel_ssc_trigger,
.hw_params = atmel_ssc_hw_params,
.set_fmt = atmel_ssc_set_dai_fmt,
.set_clkdiv = atmel_ssc_set_dai_clkdiv,
};
static struct snd_soc_dai_driver atmel_ssc_dai = {
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 8000,
.rate_max = 384000,
.formats = ATMEL_SSC_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 8000,
.rate_max = 384000,
.formats = ATMEL_SSC_FORMATS,},
.ops = &atmel_ssc_dai_ops,
};
static const struct snd_soc_component_driver atmel_ssc_component = {
.name = "atmel-ssc",
.suspend = pm_ptr(atmel_ssc_suspend),
.resume = pm_ptr(atmel_ssc_resume),
.legacy_dai_naming = 1,
};
static int asoc_ssc_init(struct device *dev)
{
struct ssc_device *ssc = dev_get_drvdata(dev);
int ret;
ret = devm_snd_soc_register_component(dev, &atmel_ssc_component,
&atmel_ssc_dai, 1);
if (ret) {
dev_err(dev, "Could not register DAI: %d\n", ret);
return ret;
}
if (ssc->pdata->use_dma)
ret = atmel_pcm_dma_platform_register(dev);
else
ret = atmel_pcm_pdc_platform_register(dev);
if (ret) {
dev_err(dev, "Could not register PCM: %d\n", ret);
return ret;
}
return 0;
}
/**
* atmel_ssc_set_audio - Allocate the specified SSC for audio use.
* @ssc_id: SSD ID in [0, NUM_SSC_DEVICES[
*/
int atmel_ssc_set_audio(int ssc_id)
{
struct ssc_device *ssc;
/* If we can grab the SSC briefly to parent the DAI device off it */
ssc = ssc_request(ssc_id);
if (IS_ERR(ssc)) {
pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n",
PTR_ERR(ssc));
return PTR_ERR(ssc);
} else {
ssc_info[ssc_id].ssc = ssc;
}
return asoc_ssc_init(&ssc->pdev->dev);
}
EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
void atmel_ssc_put_audio(int ssc_id)
{
struct ssc_device *ssc = ssc_info[ssc_id].ssc;
ssc_free(ssc);
}
EXPORT_SYMBOL_GPL(atmel_ssc_put_audio);
/* Module information */
MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
MODULE_LICENSE("GPL");
+111
View File
@@ -0,0 +1,111 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
* ATMEL CORP.
*
* Based on at91-ssc.c by
* Frank Mandarino <fmandarino@endrelia.com>
* Based on pxa2xx Platform drivers by
* Liam Girdwood <lrg@slimlogic.co.uk>
*/
#ifndef _ATMEL_SSC_DAI_H
#define _ATMEL_SSC_DAI_H
#include <linux/types.h>
#include <linux/atmel-ssc.h>
#include "atmel-pcm.h"
/* SSC system clock ids */
#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
/* SSC divider ids */
#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */
#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
/*
* SSC direction masks
*/
#define SSC_DIR_MASK_UNUSED 0
#define SSC_DIR_MASK_PLAYBACK 1
#define SSC_DIR_MASK_CAPTURE 2
/*
* SSC register values that Atmel left out of <linux/atmel-ssc.h>. These
* are expected to be used with SSC_BF
*/
/* START bit field values */
#define SSC_START_CONTINUOUS 0
#define SSC_START_TX_RX 1
#define SSC_START_LOW_RF 2
#define SSC_START_HIGH_RF 3
#define SSC_START_FALLING_RF 4
#define SSC_START_RISING_RF 5
#define SSC_START_LEVEL_RF 6
#define SSC_START_EDGE_RF 7
#define SSS_START_COMPARE_0 8
/* CKI bit field values */
#define SSC_CKI_FALLING 0
#define SSC_CKI_RISING 1
/* CKO bit field values */
#define SSC_CKO_NONE 0
#define SSC_CKO_CONTINUOUS 1
#define SSC_CKO_TRANSFER 2
/* CKS bit field values */
#define SSC_CKS_DIV 0
#define SSC_CKS_CLOCK 1
#define SSC_CKS_PIN 2
/* FSEDGE bit field values */
#define SSC_FSEDGE_POSITIVE 0
#define SSC_FSEDGE_NEGATIVE 1
/* FSOS bit field values */
#define SSC_FSOS_NONE 0
#define SSC_FSOS_NEGATIVE 1
#define SSC_FSOS_POSITIVE 2
#define SSC_FSOS_LOW 3
#define SSC_FSOS_HIGH 4
#define SSC_FSOS_TOGGLE 5
#define START_DELAY 1
struct atmel_ssc_state {
u32 ssc_cmr;
u32 ssc_rcmr;
u32 ssc_rfmr;
u32 ssc_tcmr;
u32 ssc_tfmr;
u32 ssc_sr;
u32 ssc_imr;
};
struct atmel_ssc_info {
char *name;
struct ssc_device *ssc;
unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
unsigned short initialized; /* true if SSC has been initialized */
unsigned short daifmt;
unsigned short cmr_div;
unsigned short tcmr_period;
unsigned short rcmr_period;
unsigned int forced_divider;
struct atmel_pcm_dma_params *dma_params[2];
struct atmel_ssc_state ssc_state;
unsigned long mck_rate;
};
int atmel_ssc_set_audio(int ssc_id);
void atmel_ssc_put_audio(int ssc_id);
#endif /* _AT91_SSC_DAI_H */
+198
View File
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec.
*
* Copyright (C) 2012 Atmel
*
* Author: Bo Shen <voice.shen@atmel.com>
*/
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of.h>
#include <sound/soc.h>
#include "../codecs/wm8904.h"
#include "atmel_ssc_dai.h"
static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
};
static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
int ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK,
32768, params_rate(params) * 256);
if (ret < 0) {
pr_err("%s - failed to set wm8904 codec PLL.", __func__);
return ret;
}
/*
* As here wm8904 use FLL output as its system clock
* so calling set_sysclk won't care freq parameter
* then we pass 0
*/
ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL,
0, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("%s -failed to set wm8904 SYSCLK\n", __func__);
return ret;
}
return 0;
}
static const struct snd_soc_ops atmel_asoc_wm8904_ops = {
.hw_params = atmel_asoc_wm8904_hw_params,
};
SND_SOC_DAILINK_DEFS(pcm,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8904-hifi")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {
.name = "WM8904",
.stream_name = "WM8904 PCM",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ops = &atmel_asoc_wm8904_ops,
SND_SOC_DAILINK_REG(pcm),
};
static struct snd_soc_card atmel_asoc_wm8904_card = {
.name = "atmel_asoc_wm8904",
.owner = THIS_MODULE,
.dai_link = &atmel_asoc_wm8904_dailink,
.num_links = 1,
.dapm_widgets = atmel_asoc_wm8904_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets),
.fully_routed = true,
};
static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card = &atmel_asoc_wm8904_card;
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
int ret;
if (!np) {
dev_err(&pdev->dev, "only device tree supported\n");
return -EINVAL;
}
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret) {
dev_err(&pdev->dev, "failed to parse card name\n");
return ret;
}
ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
if (ret) {
dev_err(&pdev->dev, "failed to parse audio routing\n");
return ret;
}
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "failed to get dai and pcm info\n");
ret = -EINVAL;
return ret;
}
dailink->cpus->of_node = cpu_np;
dailink->platforms->of_node = cpu_np;
of_node_put(cpu_np);
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "failed to get codec info\n");
ret = -EINVAL;
return ret;
}
dailink->codecs->of_node = codec_np;
of_node_put(codec_np);
return 0;
}
static int atmel_asoc_wm8904_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &atmel_asoc_wm8904_card;
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
int id, ret;
card->dev = &pdev->dev;
ret = atmel_asoc_wm8904_dt_init(pdev);
if (ret) {
dev_err(&pdev->dev, "failed to init dt info\n");
return ret;
}
id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc");
ret = atmel_ssc_set_audio(id);
if (ret != 0) {
dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id);
return ret;
}
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed\n");
goto err_set_audio;
}
return 0;
err_set_audio:
atmel_ssc_put_audio(id);
return ret;
}
static void atmel_asoc_wm8904_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
int id;
id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc");
snd_soc_unregister_card(card);
atmel_ssc_put_audio(id);
}
#ifdef CONFIG_OF
static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = {
{ .compatible = "atmel,asoc-wm8904", },
{ }
};
MODULE_DEVICE_TABLE(of, atmel_asoc_wm8904_dt_ids);
#endif
static struct platform_driver atmel_asoc_wm8904_driver = {
.driver = {
.name = "atmel-wm8904-audio",
.of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids),
.pm = &snd_soc_pm_ops,
},
.probe = atmel_asoc_wm8904_probe,
.remove = atmel_asoc_wm8904_remove,
};
module_platform_driver(atmel_asoc_wm8904_driver);
/* Module information */
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+903
View File
@@ -0,0 +1,903 @@
// SPDX-License-Identifier: GPL-2.0
//
// Driver for Microchip S/PDIF TX Controller
//
// Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
//
// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/spinlock.h>
#include <sound/asoundef.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
/*
* ---- S/PDIF Transmitter Controller Register map ----
*/
#define SPDIFTX_CR 0x00 /* Control Register */
#define SPDIFTX_MR 0x04 /* Mode Register */
#define SPDIFTX_CDR 0x0C /* Common Data Register */
#define SPDIFTX_IER 0x14 /* Interrupt Enable Register */
#define SPDIFTX_IDR 0x18 /* Interrupt Disable Register */
#define SPDIFTX_IMR 0x1C /* Interrupt Mask Register */
#define SPDIFTX_ISR 0x20 /* Interrupt Status Register */
#define SPDIFTX_CH1UD(reg) (0x50 + (reg) * 4) /* User Data 1 Register x */
#define SPDIFTX_CH1S(reg) (0x80 + (reg) * 4) /* Channel Status 1 Register x */
#define SPDIFTX_VERSION 0xF0
/*
* ---- Control Register (Write-only) ----
*/
#define SPDIFTX_CR_SWRST BIT(0) /* Software Reset */
#define SPDIFTX_CR_FCLR BIT(1) /* FIFO clear */
/*
* ---- Mode Register (Read/Write) ----
*/
/* Transmit Enable */
#define SPDIFTX_MR_TXEN_MASK GENMASK(0, 0)
#define SPDIFTX_MR_TXEN_DISABLE (0 << 0)
#define SPDIFTX_MR_TXEN_ENABLE (1 << 0)
/* Multichannel Transfer */
#define SPDIFTX_MR_MULTICH_MASK GENAMSK(1, 1)
#define SPDIFTX_MR_MULTICH_MONO (0 << 1)
#define SPDIFTX_MR_MULTICH_DUAL (1 << 1)
/* Data Word Endian Mode */
#define SPDIFTX_MR_ENDIAN_MASK GENMASK(2, 2)
#define SPDIFTX_MR_ENDIAN_LITTLE (0 << 2)
#define SPDIFTX_MR_ENDIAN_BIG (1 << 2)
/* Data Justification */
#define SPDIFTX_MR_JUSTIFY_MASK GENMASK(3, 3)
#define SPDIFTX_MR_JUSTIFY_LSB (0 << 3)
#define SPDIFTX_MR_JUSTIFY_MSB (1 << 3)
/* Common Audio Register Transfer Mode */
#define SPDIFTX_MR_CMODE_MASK GENMASK(5, 4)
#define SPDIFTX_MR_CMODE_INDEX_ACCESS (0 << 4)
#define SPDIFTX_MR_CMODE_TOGGLE_ACCESS (1 << 4)
#define SPDIFTX_MR_CMODE_INTERLVD_ACCESS (2 << 4)
/* Valid Bits per Sample */
#define SPDIFTX_MR_VBPS_MASK GENMASK(13, 8)
/* Chunk Size */
#define SPDIFTX_MR_CHUNK_MASK GENMASK(19, 16)
/* Validity Bits for Channels 1 and 2 */
#define SPDIFTX_MR_VALID1 BIT(24)
#define SPDIFTX_MR_VALID2 BIT(25)
/* Disable Null Frame on underrun */
#define SPDIFTX_MR_DNFR_MASK GENMASK(27, 27)
#define SPDIFTX_MR_DNFR_INVALID (0 << 27)
#define SPDIFTX_MR_DNFR_VALID (1 << 27)
/* Bytes per Sample */
#define SPDIFTX_MR_BPS_MASK GENMASK(29, 28)
/*
* ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ----
*/
#define SPDIFTX_IR_TXRDY BIT(0)
#define SPDIFTX_IR_TXEMPTY BIT(1)
#define SPDIFTX_IR_TXFULL BIT(2)
#define SPDIFTX_IR_TXCHUNK BIT(3)
#define SPDIFTX_IR_TXUDR BIT(4)
#define SPDIFTX_IR_TXOVR BIT(5)
#define SPDIFTX_IR_CSRDY BIT(6)
#define SPDIFTX_IR_UDRDY BIT(7)
#define SPDIFTX_IR_TXRDYCH(ch) BIT((ch) + 8)
#define SPDIFTX_IR_SECE BIT(10)
#define SPDIFTX_IR_TXUDRCH(ch) BIT((ch) + 11)
#define SPDIFTX_IR_BEND BIT(13)
static bool mchp_spdiftx_readable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SPDIFTX_MR:
case SPDIFTX_IMR:
case SPDIFTX_ISR:
case SPDIFTX_CH1UD(0):
case SPDIFTX_CH1UD(1):
case SPDIFTX_CH1UD(2):
case SPDIFTX_CH1UD(3):
case SPDIFTX_CH1UD(4):
case SPDIFTX_CH1UD(5):
case SPDIFTX_CH1S(0):
case SPDIFTX_CH1S(1):
case SPDIFTX_CH1S(2):
case SPDIFTX_CH1S(3):
case SPDIFTX_CH1S(4):
case SPDIFTX_CH1S(5):
return true;
default:
return false;
}
}
static bool mchp_spdiftx_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SPDIFTX_CR:
case SPDIFTX_MR:
case SPDIFTX_CDR:
case SPDIFTX_IER:
case SPDIFTX_IDR:
case SPDIFTX_CH1UD(0):
case SPDIFTX_CH1UD(1):
case SPDIFTX_CH1UD(2):
case SPDIFTX_CH1UD(3):
case SPDIFTX_CH1UD(4):
case SPDIFTX_CH1UD(5):
case SPDIFTX_CH1S(0):
case SPDIFTX_CH1S(1):
case SPDIFTX_CH1S(2):
case SPDIFTX_CH1S(3):
case SPDIFTX_CH1S(4):
case SPDIFTX_CH1S(5):
return true;
default:
return false;
}
}
static bool mchp_spdiftx_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SPDIFTX_CDR:
case SPDIFTX_ISR:
return true;
default:
return false;
}
}
static const struct regmap_config mchp_spdiftx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SPDIFTX_VERSION,
.readable_reg = mchp_spdiftx_readable_reg,
.writeable_reg = mchp_spdiftx_writeable_reg,
.precious_reg = mchp_spdiftx_precious_reg,
.cache_type = REGCACHE_FLAT,
};
#define SPDIFTX_GCLK_RATIO 128
#define SPDIFTX_CS_BITS 192
#define SPDIFTX_UD_BITS 192
struct mchp_spdiftx_mixer_control {
unsigned char ch_stat[SPDIFTX_CS_BITS / 8];
unsigned char user_data[SPDIFTX_UD_BITS / 8];
spinlock_t lock; /* exclusive access to control data */
};
struct mchp_spdiftx_dev {
struct mchp_spdiftx_mixer_control control;
struct snd_dmaengine_dai_dma_data playback;
struct device *dev;
struct regmap *regmap;
struct clk *pclk;
struct clk *gclk;
unsigned int fmt;
unsigned int suspend_irq;
};
static inline int mchp_spdiftx_is_running(struct mchp_spdiftx_dev *dev)
{
u32 mr;
regmap_read(dev->regmap, SPDIFTX_MR, &mr);
return !!(mr & SPDIFTX_MR_TXEN_ENABLE);
}
static void mchp_spdiftx_channel_status_write(struct mchp_spdiftx_dev *dev)
{
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
u32 val;
int i;
for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat) / 4; i++) {
val = (ctrl->ch_stat[(i * 4) + 0] << 0) |
(ctrl->ch_stat[(i * 4) + 1] << 8) |
(ctrl->ch_stat[(i * 4) + 2] << 16) |
(ctrl->ch_stat[(i * 4) + 3] << 24);
regmap_write(dev->regmap, SPDIFTX_CH1S(i), val);
}
}
static void mchp_spdiftx_user_data_write(struct mchp_spdiftx_dev *dev)
{
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
u32 val;
int i;
for (i = 0; i < ARRAY_SIZE(ctrl->user_data) / 4; i++) {
val = (ctrl->user_data[(i * 4) + 0] << 0) |
(ctrl->user_data[(i * 4) + 1] << 8) |
(ctrl->user_data[(i * 4) + 2] << 16) |
(ctrl->user_data[(i * 4) + 3] << 24);
regmap_write(dev->regmap, SPDIFTX_CH1UD(i), val);
}
}
static irqreturn_t mchp_spdiftx_interrupt(int irq, void *dev_id)
{
struct mchp_spdiftx_dev *dev = dev_id;
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
u32 sr, imr, pending, idr = 0;
regmap_read(dev->regmap, SPDIFTX_ISR, &sr);
regmap_read(dev->regmap, SPDIFTX_IMR, &imr);
pending = sr & imr;
if (!pending)
return IRQ_NONE;
if (pending & SPDIFTX_IR_TXUDR) {
dev_warn(dev->dev, "underflow detected\n");
idr |= SPDIFTX_IR_TXUDR;
}
if (pending & SPDIFTX_IR_TXOVR) {
dev_warn(dev->dev, "overflow detected\n");
idr |= SPDIFTX_IR_TXOVR;
}
if (pending & SPDIFTX_IR_UDRDY) {
spin_lock(&ctrl->lock);
mchp_spdiftx_user_data_write(dev);
spin_unlock(&ctrl->lock);
idr |= SPDIFTX_IR_UDRDY;
}
if (pending & SPDIFTX_IR_CSRDY) {
spin_lock(&ctrl->lock);
mchp_spdiftx_channel_status_write(dev);
spin_unlock(&ctrl->lock);
idr |= SPDIFTX_IR_CSRDY;
}
regmap_write(dev->regmap, SPDIFTX_IDR, idr);
return IRQ_HANDLED;
}
static int mchp_spdiftx_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
/* Software reset the IP */
regmap_write(dev->regmap, SPDIFTX_CR,
SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR);
return 0;
}
static void mchp_spdiftx_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
/* Disable interrupts */
regmap_write(dev->regmap, SPDIFTX_IDR, 0xffffffff);
}
static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
int ret;
/* do not start/stop while channel status or user data is updated */
spin_lock(&ctrl->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
regmap_write(dev->regmap, SPDIFTX_IER, dev->suspend_irq |
SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
dev->suspend_irq = 0;
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
SPDIFTX_MR_TXEN_ENABLE);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
regmap_read(dev->regmap, SPDIFTX_IMR, &dev->suspend_irq);
fallthrough;
case SNDRV_PCM_TRIGGER_STOP:
regmap_write(dev->regmap, SPDIFTX_IDR, dev->suspend_irq |
SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
SPDIFTX_MR_TXEN_DISABLE);
break;
default:
ret = -EINVAL;
}
spin_unlock(&ctrl->lock);
if (ret)
dev_err(dev->dev, "unable to start/stop TX: %d\n", ret);
return ret;
}
static int mchp_spdiftx_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
unsigned long flags;
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
u32 mr;
unsigned int bps = params_physical_width(params) / 8;
unsigned char aes3;
int ret;
dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
__func__, params_rate(params), params_format(params),
params_width(params), params_channels(params));
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
dev_err(dev->dev, "Capture is not supported\n");
return -EINVAL;
}
regmap_read(dev->regmap, SPDIFTX_MR, &mr);
if (mr & SPDIFTX_MR_TXEN_ENABLE) {
dev_err(dev->dev, "PCM already running\n");
return -EBUSY;
}
/* Defaults: Toggle mode, justify to LSB, chunksize 1 */
mr = SPDIFTX_MR_CMODE_TOGGLE_ACCESS | SPDIFTX_MR_JUSTIFY_LSB;
dev->playback.maxburst = 1;
switch (params_channels(params)) {
case 1:
mr |= SPDIFTX_MR_MULTICH_MONO;
break;
case 2:
mr |= SPDIFTX_MR_MULTICH_DUAL;
if (bps > 2)
dev->playback.maxburst = 2;
break;
default:
dev_err(dev->dev, "unsupported number of channels: %d\n",
params_channels(params));
return -EINVAL;
}
mr |= FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, dev->playback.maxburst);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 8);
break;
case SNDRV_PCM_FORMAT_S16_BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S16_LE:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 16);
break;
case SNDRV_PCM_FORMAT_S18_3BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S18_3LE:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 18);
break;
case SNDRV_PCM_FORMAT_S20_3BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S20_3LE:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 20);
break;
case SNDRV_PCM_FORMAT_S24_3BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S24_3LE:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
break;
case SNDRV_PCM_FORMAT_S24_BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S24_LE:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
break;
case SNDRV_PCM_FORMAT_S32_BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S32_LE:
mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 32);
break;
default:
dev_err(dev->dev, "unsupported PCM format: %d\n",
params_format(params));
return -EINVAL;
}
mr |= FIELD_PREP(SPDIFTX_MR_BPS_MASK, bps - 1);
switch (params_rate(params)) {
case 22050:
aes3 = IEC958_AES3_CON_FS_22050;
break;
case 24000:
aes3 = IEC958_AES3_CON_FS_24000;
break;
case 32000:
aes3 = IEC958_AES3_CON_FS_32000;
break;
case 44100:
aes3 = IEC958_AES3_CON_FS_44100;
break;
case 48000:
aes3 = IEC958_AES3_CON_FS_48000;
break;
case 88200:
aes3 = IEC958_AES3_CON_FS_88200;
break;
case 96000:
aes3 = IEC958_AES3_CON_FS_96000;
break;
case 176400:
aes3 = IEC958_AES3_CON_FS_176400;
break;
case 192000:
aes3 = IEC958_AES3_CON_FS_192000;
break;
case 8000:
case 11025:
case 16000:
case 64000:
aes3 = IEC958_AES3_CON_FS_NOTID;
break;
default:
dev_err(dev->dev, "unsupported sample frequency: %u\n",
params_rate(params));
return -EINVAL;
}
spin_lock_irqsave(&ctrl->lock, flags);
ctrl->ch_stat[3] &= ~IEC958_AES3_CON_FS;
ctrl->ch_stat[3] |= aes3;
mchp_spdiftx_channel_status_write(dev);
spin_unlock_irqrestore(&ctrl->lock, flags);
/* GCLK is enabled by runtime PM. */
clk_disable_unprepare(dev->gclk);
ret = clk_set_rate(dev->gclk, params_rate(params) *
SPDIFTX_GCLK_RATIO);
if (ret) {
dev_err(dev->dev,
"unable to change gclk rate to: rate %u * ratio %u\n",
params_rate(params), SPDIFTX_GCLK_RATIO);
return ret;
}
ret = clk_prepare_enable(dev->gclk);
if (ret) {
dev_err(dev->dev, "unable to enable gclk: %d\n", ret);
return ret;
}
dev_dbg(dev->dev, "%s(): GCLK set to %d\n", __func__,
params_rate(params) * SPDIFTX_GCLK_RATIO);
regmap_write(dev->regmap, SPDIFTX_MR, mr);
return 0;
}
static int mchp_spdiftx_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
return regmap_write(dev->regmap, SPDIFTX_CR,
SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR);
}
#define MCHP_SPDIFTX_RATES SNDRV_PCM_RATE_8000_192000
#define MCHP_SPDIFTX_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_U16_BE | \
SNDRV_PCM_FMTBIT_S18_3LE | \
SNDRV_PCM_FMTBIT_S18_3BE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S20_3BE | \
SNDRV_PCM_FMTBIT_S24_3LE | \
SNDRV_PCM_FMTBIT_S24_3BE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S24_BE | \
SNDRV_PCM_FMTBIT_S32_LE | \
SNDRV_PCM_FMTBIT_S32_BE \
)
static int mchp_spdiftx_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int mchp_spdiftx_cs_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
unsigned long flags;
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
spin_lock_irqsave(&ctrl->lock, flags);
memcpy(uvalue->value.iec958.status, ctrl->ch_stat,
sizeof(ctrl->ch_stat));
spin_unlock_irqrestore(&ctrl->lock, flags);
return 0;
}
static int mchp_spdiftx_cs_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
unsigned long flags;
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
int changed = 0;
int i;
spin_lock_irqsave(&ctrl->lock, flags);
for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat); i++) {
if (ctrl->ch_stat[i] != uvalue->value.iec958.status[i])
changed = 1;
ctrl->ch_stat[i] = uvalue->value.iec958.status[i];
}
if (changed) {
/* don't enable IP while we copy the channel status */
if (mchp_spdiftx_is_running(dev)) {
/*
* if SPDIF is running, wait for interrupt to write
* channel status
*/
regmap_write(dev->regmap, SPDIFTX_IER,
SPDIFTX_IR_CSRDY);
} else {
mchp_spdiftx_channel_status_write(dev);
}
}
spin_unlock_irqrestore(&ctrl->lock, flags);
return changed;
}
static int mchp_spdiftx_cs_mask(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
memset(uvalue->value.iec958.status, 0xff,
sizeof(uvalue->value.iec958.status));
return 0;
}
static int mchp_spdiftx_subcode_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
unsigned long flags;
spin_lock_irqsave(&ctrl->lock, flags);
memcpy(uvalue->value.iec958.subcode, ctrl->user_data,
sizeof(ctrl->user_data));
spin_unlock_irqrestore(&ctrl->lock, flags);
return 0;
}
static int mchp_spdiftx_subcode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uvalue)
{
unsigned long flags;
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
int changed = 0;
int i;
spin_lock_irqsave(&ctrl->lock, flags);
for (i = 0; i < ARRAY_SIZE(ctrl->user_data); i++) {
if (ctrl->user_data[i] != uvalue->value.iec958.subcode[i])
changed = 1;
ctrl->user_data[i] = uvalue->value.iec958.subcode[i];
}
if (changed) {
if (mchp_spdiftx_is_running(dev)) {
/*
* if SPDIF is running, wait for interrupt to write
* user data
*/
regmap_write(dev->regmap, SPDIFTX_IER,
SPDIFTX_IR_UDRDY);
} else {
mchp_spdiftx_user_data_write(dev);
}
}
spin_unlock_irqrestore(&ctrl->lock, flags);
return changed;
}
static struct snd_kcontrol_new mchp_spdiftx_ctrls[] = {
/* Channel status controller */
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = mchp_spdiftx_info,
.get = mchp_spdiftx_cs_get,
.put = mchp_spdiftx_cs_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
.access = SNDRV_CTL_ELEM_ACCESS_READ,
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = mchp_spdiftx_info,
.get = mchp_spdiftx_cs_mask,
},
/* User bits controller */
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "IEC958 Subcode Playback Default",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = mchp_spdiftx_info,
.get = mchp_spdiftx_subcode_get,
.put = mchp_spdiftx_subcode_put,
},
};
static int mchp_spdiftx_dai_probe(struct snd_soc_dai *dai)
{
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &dev->playback, NULL);
/* Add controls */
snd_soc_add_dai_controls(dai, mchp_spdiftx_ctrls,
ARRAY_SIZE(mchp_spdiftx_ctrls));
return 0;
}
static const struct snd_soc_dai_ops mchp_spdiftx_dai_ops = {
.probe = mchp_spdiftx_dai_probe,
.startup = mchp_spdiftx_dai_startup,
.shutdown = mchp_spdiftx_dai_shutdown,
.trigger = mchp_spdiftx_trigger,
.hw_params = mchp_spdiftx_hw_params,
.hw_free = mchp_spdiftx_hw_free,
};
static struct snd_soc_dai_driver mchp_spdiftx_dai = {
.name = "mchp-spdiftx",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = MCHP_SPDIFTX_RATES,
.formats = MCHP_SPDIFTX_FORMATS,
},
.ops = &mchp_spdiftx_dai_ops,
};
static const struct snd_soc_component_driver mchp_spdiftx_component = {
.name = "mchp-spdiftx",
.legacy_dai_naming = 1,
};
static const struct of_device_id mchp_spdiftx_dt_ids[] = {
{
.compatible = "microchip,sama7g5-spdiftx",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mchp_spdiftx_dt_ids);
static int mchp_spdiftx_runtime_suspend(struct device *dev)
{
struct mchp_spdiftx_dev *spdiftx = dev_get_drvdata(dev);
regcache_cache_only(spdiftx->regmap, true);
clk_disable_unprepare(spdiftx->gclk);
clk_disable_unprepare(spdiftx->pclk);
return 0;
}
static int mchp_spdiftx_runtime_resume(struct device *dev)
{
struct mchp_spdiftx_dev *spdiftx = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(spdiftx->pclk);
if (ret) {
dev_err(spdiftx->dev,
"failed to enable the peripheral clock: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(spdiftx->gclk);
if (ret) {
dev_err(spdiftx->dev,
"failed to enable generic clock: %d\n", ret);
goto disable_pclk;
}
regcache_cache_only(spdiftx->regmap, false);
regcache_mark_dirty(spdiftx->regmap);
ret = regcache_sync(spdiftx->regmap);
if (ret) {
regcache_cache_only(spdiftx->regmap, true);
clk_disable_unprepare(spdiftx->gclk);
disable_pclk:
clk_disable_unprepare(spdiftx->pclk);
}
return ret;
}
static const struct dev_pm_ops mchp_spdiftx_pm_ops = {
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
RUNTIME_PM_OPS(mchp_spdiftx_runtime_suspend, mchp_spdiftx_runtime_resume,
NULL)
};
static int mchp_spdiftx_probe(struct platform_device *pdev)
{
struct mchp_spdiftx_dev *dev;
struct resource *mem;
struct regmap *regmap;
void __iomem *base;
struct mchp_spdiftx_mixer_control *ctrl;
int irq;
int err;
/* Get memory for driver data. */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* Map I/O registers. */
base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(base))
return PTR_ERR(base);
regmap = devm_regmap_init_mmio(&pdev->dev, base,
&mchp_spdiftx_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
/* Request IRQ */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
err = devm_request_irq(&pdev->dev, irq, mchp_spdiftx_interrupt, 0,
dev_name(&pdev->dev), dev);
if (err)
return err;
/* Get the peripheral clock */
dev->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(dev->pclk)) {
err = PTR_ERR(dev->pclk);
dev_err(&pdev->dev,
"failed to get the peripheral clock: %d\n", err);
return err;
}
/* Get the generic clock */
dev->gclk = devm_clk_get(&pdev->dev, "gclk");
if (IS_ERR(dev->gclk)) {
err = PTR_ERR(dev->gclk);
dev_err(&pdev->dev,
"failed to get the PMC generic clock: %d\n", err);
return err;
}
ctrl = &dev->control;
spin_lock_init(&ctrl->lock);
/* Init channel status */
ctrl->ch_stat[0] = IEC958_AES0_CON_NOT_COPYRIGHT |
IEC958_AES0_CON_EMPHASIS_NONE;
dev->dev = &pdev->dev;
dev->regmap = regmap;
platform_set_drvdata(pdev, dev);
pm_runtime_enable(dev->dev);
if (!pm_runtime_enabled(dev->dev)) {
err = mchp_spdiftx_runtime_resume(dev->dev);
if (err)
return err;
}
dev->playback.addr = (dma_addr_t)mem->start + SPDIFTX_CDR;
dev->playback.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (err) {
dev_err(&pdev->dev, "failed to register PMC: %d\n", err);
goto pm_runtime_suspend;
}
err = devm_snd_soc_register_component(&pdev->dev,
&mchp_spdiftx_component,
&mchp_spdiftx_dai, 1);
if (err) {
dev_err(&pdev->dev, "failed to register component: %d\n", err);
goto pm_runtime_suspend;
}
return 0;
pm_runtime_suspend:
if (!pm_runtime_status_suspended(dev->dev))
mchp_spdiftx_runtime_suspend(dev->dev);
pm_runtime_disable(dev->dev);
return err;
}
static void mchp_spdiftx_remove(struct platform_device *pdev)
{
struct mchp_spdiftx_dev *dev = platform_get_drvdata(pdev);
if (!pm_runtime_status_suspended(dev->dev))
mchp_spdiftx_runtime_suspend(dev->dev);
pm_runtime_disable(dev->dev);
}
static struct platform_driver mchp_spdiftx_driver = {
.probe = mchp_spdiftx_probe,
.remove = mchp_spdiftx_remove,
.driver = {
.name = "mchp_spdiftx",
.of_match_table = mchp_spdiftx_dt_ids,
.pm = pm_ptr(&mchp_spdiftx_pm_ops)
},
};
module_platform_driver(mchp_spdiftx_driver);
MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>");
MODULE_DESCRIPTION("Microchip S/PDIF TX Controller Driver");
MODULE_LICENSE("GPL v2");
+176
View File
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ASoC driver for PROTO AudioCODEC (with a WM8731)
*
* Author: Florian Meier, <koalo@koalo.de>
* Copyright 2013
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "../codecs/wm8731.h"
#define XTAL_RATE 12288000 /* This is fixed on this board */
static int snd_proto_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
/* Set proto sysclk */
int ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
XTAL_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "Failed to set WM8731 SYSCLK: %d\n",
ret);
return ret;
}
return 0;
}
static const struct snd_soc_dapm_widget snd_proto_widget[] = {
SND_SOC_DAPM_MIC("Microphone Jack", NULL),
SND_SOC_DAPM_HP("Headphone Jack", NULL),
};
static const struct snd_soc_dapm_route snd_proto_route[] = {
/* speaker connected to LHPOUT/RHPOUT */
{"Headphone Jack", NULL, "LHPOUT"},
{"Headphone Jack", NULL, "RHPOUT"},
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
{"MICIN", NULL, "Mic Bias"},
{"Mic Bias", NULL, "Microphone Jack"},
};
/* audio machine driver */
static struct snd_soc_card snd_proto = {
.name = "snd_mikroe_proto",
.owner = THIS_MODULE,
.dapm_widgets = snd_proto_widget,
.num_dapm_widgets = ARRAY_SIZE(snd_proto_widget),
.dapm_routes = snd_proto_route,
.num_dapm_routes = ARRAY_SIZE(snd_proto_route),
};
static int snd_proto_probe(struct platform_device *pdev)
{
struct snd_soc_dai_link *dai;
struct snd_soc_dai_link_component *comp;
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct device_node *bitclkmaster = NULL;
struct device_node *framemaster = NULL;
unsigned int dai_fmt;
int ret = 0;
if (!np) {
dev_err(&pdev->dev, "No device node supplied\n");
return -EINVAL;
}
snd_proto.dev = &pdev->dev;
ret = snd_soc_of_parse_card_name(&snd_proto, "model");
if (ret)
return ret;
dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL);
if (!dai)
return -ENOMEM;
/* for cpus/codecs/platforms */
comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
snd_proto.dai_link = dai;
snd_proto.num_links = 1;
dai->cpus = &comp[0];
dai->num_cpus = 1;
dai->codecs = &comp[1];
dai->num_codecs = 1;
dai->platforms = &comp[2];
dai->num_platforms = 1;
dai->name = "WM8731";
dai->stream_name = "WM8731 HiFi";
dai->codecs->dai_name = "wm8731-hifi";
dai->init = &snd_proto_init;
codec_np = of_parse_phandle(np, "audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "audio-codec node missing\n");
return -EINVAL;
}
dai->codecs->of_node = codec_np;
cpu_np = of_parse_phandle(np, "i2s-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "i2s-controller missing\n");
ret = -EINVAL;
goto put_codec_node;
}
dai->cpus->of_node = cpu_np;
dai->platforms->of_node = cpu_np;
dai_fmt = snd_soc_daifmt_parse_format(np, NULL);
snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL,
&bitclkmaster, &framemaster);
if (bitclkmaster != framemaster) {
dev_err(&pdev->dev, "Must be the same bitclock and frame master\n");
ret = -EINVAL;
goto put_cpu_node;
}
if (bitclkmaster) {
if (codec_np == bitclkmaster)
dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
else
dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
} else {
dai_fmt |= snd_soc_daifmt_parse_clock_provider_as_flag(np, NULL);
}
dai->dai_fmt = dai_fmt;
ret = devm_snd_soc_register_card(&pdev->dev, &snd_proto);
if (ret)
dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card() failed\n");
put_cpu_node:
of_node_put(bitclkmaster);
of_node_put(framemaster);
of_node_put(cpu_np);
put_codec_node:
of_node_put(codec_np);
return ret;
}
static const struct of_device_id snd_proto_of_match[] = {
{ .compatible = "mikroe,mikroe-proto", },
{},
};
MODULE_DEVICE_TABLE(of, snd_proto_of_match);
static struct platform_driver snd_proto_driver = {
.driver = {
.name = "snd-mikroe-proto",
.of_match_table = snd_proto_of_match,
},
.probe = snd_proto_probe,
};
module_platform_driver(snd_proto_driver);
MODULE_AUTHOR("Florian Meier");
MODULE_DESCRIPTION("ASoC Driver for PROTO board (WM8731)");
MODULE_LICENSE("GPL");
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based
* ATMEL AT91SAM9G20ek board.
*
* Copyright (C) 2005 SAN People
* Copyright (C) 2008 Atmel
*
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
*
* Based on ati_b1_wm8731.c by:
* Frank Mandarino <fmandarino@endrelia.com>
* Copyright 2006 Endrelia Technologies Inc.
* Based on corgi.c by:
* Copyright 2005 Wolfson Microelectronics PLC.
* Copyright 2005 Openedhand Ltd.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/atmel-ssc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "../codecs/wm8731.h"
#include "atmel-pcm.h"
#include "atmel_ssc_dai.h"
#define MCLK_RATE 12000000
/*
* As shipped the board does not have inputs. However, it is relatively
* straightforward to modify the board to hook them up so support is left
* in the driver.
*/
#undef ENABLE_MIC_INPUT
static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
};
static const struct snd_soc_dapm_route intercon[] = {
/* speaker connected to LHPOUT/RHPOUT */
{"Ext Spk", NULL, "LHPOUT"},
{"Ext Spk", NULL, "RHPOUT"},
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
{"MICIN", NULL, "Mic Bias"},
{"Mic Bias", NULL, "Int Mic"},
};
/*
* Logic for a wm8731 as connected on a at91sam9g20ek board.
*/
static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct device *dev = rtd->dev;
int ret;
dev_dbg(dev, "%s called\n", __func__);
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK,
MCLK_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret);
return ret;
}
#ifndef ENABLE_MIC_INPUT
snd_soc_dapm_disable_pin(snd_soc_card_to_dapm(rtd->card), "Int Mic");
#endif
return 0;
}
SND_SOC_DAILINK_DEFS(pcm,
DAILINK_COMP_ARRAY(COMP_CPU("at91rm9200_ssc.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.0-001b", "wm8731-hifi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("at91rm9200_ssc.0")));
static struct snd_soc_dai_link at91sam9g20ek_dai = {
.name = "WM8731",
.stream_name = "WM8731 PCM",
.init = at91sam9g20ek_wm8731_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
#ifndef ENABLE_MIC_INPUT
.playback_only = true,
#endif
SND_SOC_DAILINK_REG(pcm),
};
static struct snd_soc_card snd_soc_at91sam9g20ek = {
.name = "AT91SAMG20-EK",
.owner = THIS_MODULE,
.dai_link = &at91sam9g20ek_dai,
.num_links = 1,
.dapm_widgets = at91sam9g20ek_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets),
.dapm_routes = intercon,
.num_dapm_routes = ARRAY_SIZE(intercon),
.fully_routed = true,
};
static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
int ret;
if (!np) {
return -ENODEV;
}
ret = atmel_ssc_set_audio(0);
if (ret) {
dev_err(&pdev->dev, "ssc channel is not valid: %d\n", ret);
return ret;
}
card->dev = &pdev->dev;
/* Parse device node info */
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret)
goto err;
ret = snd_soc_of_parse_audio_routing(card,
"atmel,audio-routing");
if (ret)
goto err;
/* Parse codec info */
at91sam9g20ek_dai.codecs->name = NULL;
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "codec info missing\n");
ret = -EINVAL;
goto err;
}
at91sam9g20ek_dai.codecs->of_node = codec_np;
/* Parse dai and platform info */
at91sam9g20ek_dai.cpus->dai_name = NULL;
at91sam9g20ek_dai.platforms->name = NULL;
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "dai and pcm info missing\n");
of_node_put(codec_np);
ret = -EINVAL;
goto err;
}
at91sam9g20ek_dai.cpus->of_node = cpu_np;
at91sam9g20ek_dai.platforms->of_node = cpu_np;
of_node_put(codec_np);
of_node_put(cpu_np);
ret = snd_soc_register_card(card);
if (ret) {
dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card() failed\n");
goto err;
}
return 0;
err:
atmel_ssc_put_audio(0);
return ret;
}
static void at91sam9g20ek_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
atmel_ssc_put_audio(0);
}
#ifdef CONFIG_OF
static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = {
{ .compatible = "atmel,at91sam9g20ek-wm8731-audio", },
{ }
};
MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids);
#endif
static struct platform_driver at91sam9g20ek_audio_driver = {
.driver = {
.name = "at91sam9g20ek-audio",
.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
},
.probe = at91sam9g20ek_audio_probe,
.remove = at91sam9g20ek_audio_remove,
};
module_platform_driver(at91sam9g20ek_audio_driver);
/* Module information */
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
MODULE_ALIAS("platform:at91sam9g20ek-audio");
MODULE_LICENSE("GPL");
+207
View File
@@ -0,0 +1,207 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* sam9x5_wm8731 -- SoC audio for AT91SAM9X5-based boards
* that are using WM8731 as codec.
*
* Copyright (C) 2011 Atmel,
* Nicolas Ferre <nicolas.ferre@atmel.com>
*
* Copyright (C) 2013 Paratronic,
* Richard Genoud <richard.genoud@gmail.com>
*
* Based on sam9g20_wm8731.c by:
* Sedji Gaouaou <sedji.gaouaou@atmel.com>
*/
#include <linux/of.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/soc-dapm.h>
#include "../codecs/wm8731.h"
#include "atmel_ssc_dai.h"
#define MCLK_RATE 12288000
#define DRV_NAME "sam9x5-snd-wm8731"
struct sam9x5_drvdata {
int ssc_id;
};
/*
* Logic for a wm8731 as connected on a at91sam9x5ek based board.
*/
static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct device *dev = rtd->dev;
int ret;
dev_dbg(dev, "%s called\n", __func__);
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
MCLK_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret);
return ret;
}
return 0;
}
/*
* Audio paths on at91sam9x5ek board:
*
* |A| ------------> | | ---R----> Headphone Jack
* |T| <----\ | WM | ---L--/
* |9| ---> CLK <--> | 8731 | <--R----- Line In Jack
* |1| <------------ | | <--L--/
*/
static const struct snd_soc_dapm_widget sam9x5_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
};
static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card;
struct snd_soc_dai_link *dai;
struct sam9x5_drvdata *priv;
struct snd_soc_dai_link_component *comp;
int ret;
if (!np) {
dev_err(&pdev->dev, "No device node supplied\n");
return -EINVAL;
}
card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL);
comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
if (!dai || !card || !priv || !comp) {
ret = -ENOMEM;
goto out;
}
snd_soc_card_set_drvdata(card, priv);
card->dev = &pdev->dev;
card->owner = THIS_MODULE;
card->dai_link = dai;
card->num_links = 1;
card->dapm_widgets = sam9x5_dapm_widgets;
card->num_dapm_widgets = ARRAY_SIZE(sam9x5_dapm_widgets);
dai->cpus = &comp[0];
dai->num_cpus = 1;
dai->codecs = &comp[1];
dai->num_codecs = 1;
dai->platforms = &comp[2];
dai->num_platforms = 1;
dai->name = "WM8731";
dai->stream_name = "WM8731 PCM";
dai->codecs->dai_name = "wm8731-hifi";
dai->init = sam9x5_wm8731_init;
dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP;
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret) {
dev_err(&pdev->dev, "atmel,model node missing\n");
goto out;
}
ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
if (ret) {
dev_err(&pdev->dev, "atmel,audio-routing node missing\n");
goto out;
}
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "atmel,audio-codec node missing\n");
ret = -EINVAL;
goto out;
}
dai->codecs->of_node = codec_np;
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "atmel,ssc-controller node missing\n");
ret = -EINVAL;
goto out_put_codec_np;
}
dai->cpus->of_node = cpu_np;
dai->platforms->of_node = cpu_np;
priv->ssc_id = of_alias_get_id(cpu_np, "ssc");
ret = atmel_ssc_set_audio(priv->ssc_id);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to set SSC %d for audio: %d\n",
ret, priv->ssc_id);
goto out_put_cpu_np;
}
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev, "Platform device allocation failed\n");
goto out_put_audio;
}
dev_dbg(&pdev->dev, "%s ok\n", __func__);
goto out_put_cpu_np;
out_put_audio:
atmel_ssc_put_audio(priv->ssc_id);
out_put_cpu_np:
of_node_put(cpu_np);
out_put_codec_np:
of_node_put(codec_np);
out:
return ret;
}
static void sam9x5_wm8731_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sam9x5_drvdata *priv = card->drvdata;
atmel_ssc_put_audio(priv->ssc_id);
}
static const struct of_device_id sam9x5_wm8731_of_match[] = {
{ .compatible = "atmel,sam9x5-wm8731-audio", },
{},
};
MODULE_DEVICE_TABLE(of, sam9x5_wm8731_of_match);
static struct platform_driver sam9x5_wm8731_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(sam9x5_wm8731_of_match),
},
.probe = sam9x5_wm8731_driver_probe,
.remove = sam9x5_wm8731_driver_remove,
};
module_platform_driver(sam9x5_wm8731_driver);
/* Module information */
MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
MODULE_AUTHOR("Richard Genoud <richard.genoud@gmail.com>");
MODULE_DESCRIPTION("ALSA SoC machine driver for AT91SAM9x5 - WM8731");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
@@ -0,0 +1,435 @@
// SPDX-License-Identifier: GPL-2.0
//
// TSE-850 audio - ASoC driver for the Axentia TSE-850 with a PCM5142 codec
//
// Copyright (C) 2016 Axentia Technologies AB
//
// Author: Peter Rosin <peda@axentia.se>
//
// loop1 relays
// IN1 +---o +------------+ o---+ OUT1
// \ /
// + +
// | / |
// +--o +--. |
// | add | |
// | V |
// | .---. |
// DAC +----------->|Sum|---+
// | '---' |
// | |
// + +
//
// IN2 +---o--+------------+--o---+ OUT2
// loop2 relays
//
// The 'loop1' gpio pin controls two relays, which are either in loop
// position, meaning that input and output are directly connected, or
// they are in mixer position, meaning that the signal is passed through
// the 'Sum' mixer. Similarly for 'loop2'.
//
// In the above, the 'loop1' relays are inactive, thus feeding IN1 to the
// mixer (if 'add' is active) and feeding the mixer output to OUT1. The
// 'loop2' relays are active, short-cutting the TSE-850 from channel 2.
// IN1, IN2, OUT1 and OUT2 are TSE-850 connectors and DAC is the PCB name
// of the (filtered) output from the PCM5142 codec.
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
struct tse850_priv {
struct gpio_desc *add;
struct gpio_desc *loop1;
struct gpio_desc *loop2;
struct regulator *ana;
int add_cache;
int loop1_cache;
int loop2_cache;
};
static int tse850_get_mux1(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = tse850->loop1_cache;
return 0;
}
static int tse850_put_mux1(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
struct soc_enum *e = (struct soc_enum *)kctrl->private_value;
unsigned int val = ucontrol->value.enumerated.item[0];
if (val >= e->items)
return -EINVAL;
gpiod_set_value_cansleep(tse850->loop1, val);
tse850->loop1_cache = val;
return snd_soc_dapm_put_enum_double(kctrl, ucontrol);
}
static int tse850_get_mux2(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = tse850->loop2_cache;
return 0;
}
static int tse850_put_mux2(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
struct soc_enum *e = (struct soc_enum *)kctrl->private_value;
unsigned int val = ucontrol->value.enumerated.item[0];
if (val >= e->items)
return -EINVAL;
gpiod_set_value_cansleep(tse850->loop2, val);
tse850->loop2_cache = val;
return snd_soc_dapm_put_enum_double(kctrl, ucontrol);
}
static int tse850_get_mix(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = tse850->add_cache;
return 0;
}
static int tse850_put_mix(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
int connect = !!ucontrol->value.integer.value[0];
if (tse850->add_cache == connect)
return 0;
/*
* Hmmm, this gpiod_set_value_cansleep call should probably happen
* inside snd_soc_dapm_mixer_update_power in the loop.
*/
gpiod_set_value_cansleep(tse850->add, connect);
tse850->add_cache = connect;
snd_soc_dapm_mixer_update_power(dapm, kctrl, connect, NULL);
return 1;
}
static int tse850_get_ana(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
int ret;
ret = regulator_get_voltage(tse850->ana);
if (ret < 0)
return ret;
/*
* Map regulator output values like so:
* -11.5V to "Low" (enum 0)
* 11.5V-12.5V to "12V" (enum 1)
* 12.5V-13.5V to "13V" (enum 2)
* ...
* 18.5V-19.5V to "19V" (enum 8)
* 19.5V- to "20V" (enum 9)
*/
if (ret < 11000000)
ret = 11000000;
else if (ret > 20000000)
ret = 20000000;
ret -= 11000000;
ret = (ret + 500000) / 1000000;
ucontrol->value.enumerated.item[0] = ret;
return 0;
}
static int tse850_put_ana(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctrl);
struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
struct soc_enum *e = (struct soc_enum *)kctrl->private_value;
unsigned int uV = ucontrol->value.enumerated.item[0];
int ret;
if (uV >= e->items)
return -EINVAL;
/*
* Map enum zero (Low) to 2 volts on the regulator, do this since
* the ana regulator is supplied by the system 12V voltage and
* requesting anything below the system voltage causes the system
* voltage to be passed through the regulator. Also, the ana
* regulator induces noise when requesting voltages near the
* system voltage. So, by mapping Low to 2V, that noise is
* eliminated when all that is needed is 12V (the system voltage).
*/
if (uV)
uV = 11000000 + (1000000 * uV);
else
uV = 2000000;
ret = regulator_set_voltage(tse850->ana, uV, uV);
if (ret < 0)
return ret;
return snd_soc_dapm_put_enum_double(kctrl, ucontrol);
}
static const char * const mux_text[] = { "Mixer", "Loop" };
static const struct soc_enum mux_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(mux_text), mux_text);
static const struct snd_kcontrol_new mux1 =
SOC_DAPM_ENUM_EXT("MUX1", mux_enum, tse850_get_mux1, tse850_put_mux1);
static const struct snd_kcontrol_new mux2 =
SOC_DAPM_ENUM_EXT("MUX2", mux_enum, tse850_get_mux2, tse850_put_mux2);
static const struct snd_kcontrol_new mix[] = {
SOC_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0,
tse850_get_mix, tse850_put_mix),
};
static const char * const ana_text[] = {
"Low", "12V", "13V", "14V", "15V", "16V", "17V", "18V", "19V", "20V"
};
static const struct soc_enum ana_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(ana_text), ana_text);
static const struct snd_kcontrol_new out =
SOC_DAPM_ENUM_EXT("ANA", ana_enum, tse850_get_ana, tse850_put_ana);
static const struct snd_soc_dapm_widget tse850_dapm_widgets[] = {
SND_SOC_DAPM_LINE("OUT1", NULL),
SND_SOC_DAPM_LINE("OUT2", NULL),
SND_SOC_DAPM_LINE("IN1", NULL),
SND_SOC_DAPM_LINE("IN2", NULL),
SND_SOC_DAPM_INPUT("DAC"),
SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
SOC_MIXER_ARRAY("MIX", SND_SOC_NOPM, 0, 0, mix),
SND_SOC_DAPM_MUX("MUX1", SND_SOC_NOPM, 0, 0, &mux1),
SND_SOC_DAPM_MUX("MUX2", SND_SOC_NOPM, 0, 0, &mux2),
SND_SOC_DAPM_OUT_DRV("OUT", SND_SOC_NOPM, 0, 0, &out, 1),
};
/*
* These connections are not entirely correct, since both IN1 and IN2
* are always fed to MIX (if the "IN switch" is set so), i.e. without
* regard to the loop1 and loop2 relays that according to this only
* control MUX1 and MUX2 but in fact also control how the input signals
* are routed.
* But, 1) I don't know how to do it right, and 2) it doesn't seem to
* matter in practice since nothing is powered in those sections anyway.
*/
static const struct snd_soc_dapm_route tse850_intercon[] = {
{ "OUT1", NULL, "MUX1" },
{ "OUT2", NULL, "MUX2" },
{ "MUX1", "Loop", "IN1" },
{ "MUX1", "Mixer", "OUT" },
{ "MUX2", "Loop", "IN2" },
{ "MUX2", "Mixer", "OUT" },
{ "OUT", NULL, "MIX" },
{ "MIX", NULL, "DAC" },
{ "MIX", "IN Switch", "IN1" },
{ "MIX", "IN Switch", "IN2" },
/* connect board input to the codec left channel output pin */
{ "DAC", NULL, "OUTL" },
};
SND_SOC_DAILINK_DEFS(pcm,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "pcm512x-hifi")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link tse850_dailink = {
.name = "TSE-850",
.stream_name = "TSE-850-PCM",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFC,
SND_SOC_DAILINK_REG(pcm),
};
static struct snd_soc_card tse850_card = {
.name = "TSE-850-ASoC",
.owner = THIS_MODULE,
.dai_link = &tse850_dailink,
.num_links = 1,
.dapm_widgets = tse850_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tse850_dapm_widgets),
.dapm_routes = tse850_intercon,
.num_dapm_routes = ARRAY_SIZE(tse850_intercon),
.fully_routed = true,
};
static int tse850_dt_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_dai_link *dailink = &tse850_dailink;
if (!np) {
dev_err(&pdev->dev, "only device tree supported\n");
return -EINVAL;
}
cpu_np = of_parse_phandle(np, "axentia,cpu-dai", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "failed to get cpu dai\n");
return -EINVAL;
}
dailink->cpus->of_node = cpu_np;
dailink->platforms->of_node = cpu_np;
of_node_put(cpu_np);
codec_np = of_parse_phandle(np, "axentia,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "failed to get codec info\n");
return -EINVAL;
}
dailink->codecs->of_node = codec_np;
of_node_put(codec_np);
return 0;
}
static int tse850_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &tse850_card;
struct device *dev = card->dev = &pdev->dev;
struct tse850_priv *tse850;
int ret;
tse850 = devm_kzalloc(dev, sizeof(*tse850), GFP_KERNEL);
if (!tse850)
return -ENOMEM;
snd_soc_card_set_drvdata(card, tse850);
ret = tse850_dt_init(pdev);
if (ret) {
dev_err(dev, "failed to init dt info\n");
return ret;
}
tse850->add = devm_gpiod_get(dev, "axentia,add", GPIOD_OUT_HIGH);
if (IS_ERR(tse850->add))
return dev_err_probe(dev, PTR_ERR(tse850->add),
"failed to get 'add' gpio\n");
tse850->add_cache = 1;
tse850->loop1 = devm_gpiod_get(dev, "axentia,loop1", GPIOD_OUT_HIGH);
if (IS_ERR(tse850->loop1))
return dev_err_probe(dev, PTR_ERR(tse850->loop1),
"failed to get 'loop1' gpio\n");
tse850->loop1_cache = 1;
tse850->loop2 = devm_gpiod_get(dev, "axentia,loop2", GPIOD_OUT_HIGH);
if (IS_ERR(tse850->loop2))
return dev_err_probe(dev, PTR_ERR(tse850->loop2),
"failed to get 'loop2' gpio\n");
tse850->loop2_cache = 1;
tse850->ana = devm_regulator_get(dev, "axentia,ana");
if (IS_ERR(tse850->ana))
return dev_err_probe(dev, PTR_ERR(tse850->ana),
"failed to get 'ana' regulator\n");
ret = regulator_enable(tse850->ana);
if (ret < 0) {
dev_err(dev, "failed to enable the 'ana' regulator\n");
return ret;
}
ret = snd_soc_register_card(card);
if (ret) {
dev_err(dev, "snd_soc_register_card failed\n");
goto err_disable_ana;
}
return 0;
err_disable_ana:
regulator_disable(tse850->ana);
return ret;
}
static void tse850_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
regulator_disable(tse850->ana);
}
static const struct of_device_id tse850_dt_ids[] = {
{ .compatible = "axentia,tse850-pcm5142", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tse850_dt_ids);
static struct platform_driver tse850_driver = {
.driver = {
.name = "axentia-tse850-pcm5142",
.of_match_table = tse850_dt_ids,
},
.probe = tse850_probe,
.remove = tse850_remove,
};
module_platform_driver(tse850_driver);
/* Module information */
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_DESCRIPTION("ALSA SoC driver for TSE-850 with PCM5142 codec");
MODULE_LICENSE("GPL v2");