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
+151
View File
@@ -0,0 +1,151 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig SND_SOC_SAMSUNG
tristate "Samsung"
depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
depends on COMMON_CLK
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for codecs attached to
the Samsung SoCs' Audio interfaces. You will also need to
select the audio interfaces to support below.
if SND_SOC_SAMSUNG
config SND_SAMSUNG_PCM
tristate "Samsung PCM interface support"
config SND_SAMSUNG_SPDIF
tristate "Samsung SPDIF transmitter support"
select SND_SOC_SPDIF
config SND_SAMSUNG_I2S
tristate "Samsung I2S interface support"
config SND_SOC_SAMSUNG_SMDK_WM8994
tristate "SoC I2S Audio support for WM8994 on SMDK"
depends on I2C=y
select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_I2S
help
Say Y if you want to add support for SoC audio on the SMDKs.
config SND_SOC_SAMSUNG_SMDK_SPDIF
tristate "SoC S/PDIF Audio support for SMDK"
select SND_SAMSUNG_SPDIF
help
Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
config SND_SOC_SMDK_WM8994_PCM
tristate "SoC PCM Audio support for WM8994 on SMDK"
depends on I2C=y
select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_PCM
help
Say Y if you want to add support for SoC audio on the SMDK
config SND_SOC_SPEYSIDE
tristate "Audio support for Wolfson Speyside"
depends on I2C && SPI_MASTER
depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
select SND_SAMSUNG_I2S
select SND_SOC_WM8996
select SND_SOC_WM9081
select SND_SOC_WM0010
select SND_SOC_WM1250_EV1
config SND_SOC_TOBERMORY
tristate "Audio support for Wolfson Tobermory"
depends on INPUT && I2C
depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
select SND_SAMSUNG_I2S
select SND_SOC_WM8962
config SND_SOC_BELLS
tristate "Audio support for Wolfson Bells"
depends on MFD_ARIZONA && MFD_WM5102 && MFD_WM5110 && I2C && SPI_MASTER
depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
select SND_SAMSUNG_I2S
select SND_SOC_WM5102
select SND_SOC_WM5110
select SND_SOC_WM9081
select SND_SOC_WM0010
select SND_SOC_WM1250_EV1
config SND_SOC_LOWLAND
tristate "Audio support for Wolfson Lowland"
depends on I2C
depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
select SND_SAMSUNG_I2S
select SND_SOC_WM5100
select SND_SOC_WM9081
config SND_SOC_LITTLEMILL
tristate "Audio support for Wolfson Littlemill"
depends on I2C
depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
select SND_SAMSUNG_I2S
select MFD_WM8994
select SND_SOC_WM8994
config SND_SOC_SNOW
tristate "Audio support for Google Snow boards"
depends on I2C
select SND_SOC_MAX98090
select SND_SOC_MAX98095
select SND_SAMSUNG_I2S
help
Say Y if you want to add audio support for various Snow
boards based on Exynos5 series of SoCs.
config SND_SOC_ODROID
tristate "Audio support for Odroid XU3/XU4"
depends on SND_SOC_SAMSUNG && I2C
select SND_SOC_MAX98090
select SND_SAMSUNG_I2S
help
Say Y here to enable audio support for the Odroid XU3/XU4.
config SND_SOC_ARNDALE
tristate "Audio support for Arndale Board"
depends on I2C
select SND_SAMSUNG_I2S
select SND_SOC_RT5631
select MFD_WM8994
select SND_SOC_WM8994
config SND_SOC_SAMSUNG_TM2_WM5110
tristate "SoC I2S Audio support for WM5110 on TM2 board"
depends on SND_SOC_SAMSUNG && MFD_ARIZONA && MFD_WM5110 && I2C && SPI_MASTER
depends on GPIOLIB || COMPILE_TEST
select SND_SOC_MAX98504
select SND_SOC_WM5110
select SND_SAMSUNG_I2S
help
Say Y if you want to add support for SoC audio on the TM2 board.
config SND_SOC_SAMSUNG_ARIES_WM8994
tristate "SoC I2S Audio support for WM8994 on Aries"
depends on SND_SOC_SAMSUNG && I2C && IIO && EXTCON
select SND_SOC_BT_SCO
select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_I2S
help
Say Y if you want to add support for SoC audio on Aries boards,
which has a WM8994 codec connected to a BT codec, a cellular
modem, and the Samsung I2S controller. Jack detection is done
via ADC, GPIOs, and an extcon device. Switching between the Mic
and TV-Out path is also handled.
config SND_SOC_SAMSUNG_MIDAS_WM1811
tristate "SoC I2S Audio support for Midas boards"
depends on SND_SOC_SAMSUNG && I2C && IIO
select SND_SAMSUNG_I2S
select MFD_WM8994
select SND_SOC_WM8994
help
Say Y if you want to add support for SoC audio on the Midas boards.
endif #SND_SOC_SAMSUNG
+44
View File
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-2.0
# S3c24XX Platform Support
snd-soc-s3c-dma-y := dmaengine.o
snd-soc-idma-y := idma.o
snd-soc-samsung-spdif-y := spdif.o
snd-soc-pcm-y := pcm.o
snd-soc-i2s-y := i2s.o
obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c-dma.o
obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o
obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o
# S3C24XX Machine Support
snd-soc-smdk-wm8994-y := smdk_wm8994.o
snd-soc-snow-y := snow.o
snd-soc-smdk-spdif-y := smdk_spdif.o
snd-soc-smdk-wm8994pcm-y := smdk_wm8994pcm.o
snd-soc-speyside-y := speyside.o
snd-soc-tobermory-y := tobermory.o
snd-soc-lowland-y := lowland.o
snd-soc-littlemill-y := littlemill.o
snd-soc-bells-y := bells.o
snd-soc-odroid-y := odroid.o
snd-soc-arndale-y := arndale.o
snd-soc-tm2-wm5110-y := tm2_wm5110.o
snd-soc-aries-wm8994-y := aries_wm8994.o
snd-soc-midas-wm1811-y := midas_wm1811.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o
obj-$(CONFIG_SND_SOC_SNOW) += snd-soc-snow.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o
obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o
obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o
obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o
obj-$(CONFIG_SND_SOC_ODROID) += snd-soc-odroid.o
obj-$(CONFIG_SND_SOC_ARNDALE) += snd-soc-arndale.o
obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o
obj-$(CONFIG_SND_SOC_SAMSUNG_ARIES_WM8994) += snd-soc-aries-wm8994.o
obj-$(CONFIG_SND_SOC_SAMSUNG_MIDAS_WM1811) += snd-soc-midas-wm1811.o
@@ -0,0 +1,702 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/extcon.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/input-event-codes.h>
#include <linux/mfd/wm8994/registers.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <sound/jack.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "i2s.h"
#include "../codecs/wm8994.h"
#define ARIES_MCLK1_FREQ 24000000
struct aries_wm8994_variant {
unsigned int modem_dai_fmt;
bool has_fm_radio;
};
struct aries_wm8994_data {
struct extcon_dev *usb_extcon;
struct regulator *reg_main_micbias;
struct regulator *reg_headset_micbias;
struct gpio_desc *gpio_headset_detect;
struct gpio_desc *gpio_headset_key;
struct gpio_desc *gpio_earpath_sel;
struct iio_channel *adc;
const struct aries_wm8994_variant *variant;
};
/* USB dock */
static struct snd_soc_jack aries_dock;
static struct snd_soc_jack_pin dock_pins[] = {
{
.pin = "LINE",
.mask = SND_JACK_LINEOUT,
},
};
static int aries_extcon_notifier(struct notifier_block *this,
unsigned long connected, void *_cmd)
{
if (connected)
snd_soc_jack_report(&aries_dock, SND_JACK_LINEOUT,
SND_JACK_LINEOUT);
else
snd_soc_jack_report(&aries_dock, 0, SND_JACK_LINEOUT);
return NOTIFY_DONE;
}
static struct notifier_block aries_extcon_notifier_block = {
.notifier_call = aries_extcon_notifier,
};
/* Headset jack */
static struct snd_soc_jack aries_headset;
static struct snd_soc_jack_pin jack_pins[] = {
{
.pin = "HP",
.mask = SND_JACK_HEADPHONE,
}, {
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
static struct snd_soc_jack_zone headset_zones[] = {
{
.min_mv = 0,
.max_mv = 241,
.jack_type = SND_JACK_HEADPHONE,
}, {
.min_mv = 242,
.max_mv = 2980,
.jack_type = SND_JACK_HEADSET,
}, {
.min_mv = 2981,
.max_mv = UINT_MAX,
.jack_type = SND_JACK_HEADPHONE,
},
};
static irqreturn_t headset_det_irq_thread(int irq, void *data)
{
struct aries_wm8994_data *priv = (struct aries_wm8994_data *) data;
int ret = 0;
int time_left_ms = 300;
int adc;
while (time_left_ms > 0) {
if (!gpiod_get_value(priv->gpio_headset_detect)) {
snd_soc_jack_report(&aries_headset, 0,
SND_JACK_HEADSET);
gpiod_set_value(priv->gpio_earpath_sel, 0);
return IRQ_HANDLED;
}
msleep(20);
time_left_ms -= 20;
}
/* Temporarily enable micbias and earpath selector */
ret = regulator_enable(priv->reg_headset_micbias);
if (ret)
pr_err("%s failed to enable micbias: %d", __func__, ret);
gpiod_set_value(priv->gpio_earpath_sel, 1);
ret = iio_read_channel_processed(priv->adc, &adc);
if (ret < 0) {
/* failed to read ADC, so assume headphone */
pr_err("%s failed to read ADC, assuming headphones", __func__);
snd_soc_jack_report(&aries_headset, SND_JACK_HEADPHONE,
SND_JACK_HEADSET);
} else {
snd_soc_jack_report(&aries_headset,
snd_soc_jack_get_type(&aries_headset, adc),
SND_JACK_HEADSET);
}
ret = regulator_disable(priv->reg_headset_micbias);
if (ret)
pr_err("%s failed disable micbias: %d", __func__, ret);
/* Disable earpath selector when no mic connected */
if (!(aries_headset.status & SND_JACK_MICROPHONE))
gpiod_set_value(priv->gpio_earpath_sel, 0);
return IRQ_HANDLED;
}
static int headset_button_check(void *data)
{
struct aries_wm8994_data *priv = (struct aries_wm8994_data *) data;
/* Filter out keypresses when 4 pole jack not detected */
if (gpiod_get_value_cansleep(priv->gpio_headset_key) &&
aries_headset.status & SND_JACK_MICROPHONE)
return SND_JACK_BTN_0;
return 0;
}
static struct snd_soc_jack_gpio headset_button_gpio[] = {
{
.name = "Media Button",
.report = SND_JACK_BTN_0,
.debounce_time = 30,
.jack_status_check = headset_button_check,
},
};
static int aries_spk_cfg(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_component *component;
int ret = 0;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
component = snd_soc_rtd_to_codec(rtd, 0)->component;
/**
* We have an odd setup - the SPKMODE pin is pulled up so
* we only have access to the left side SPK configs,
* but SPKOUTR isn't bridged so when playing back in
* stereo, we only get the left hand channel. The only
* option we're left with is to force the AIF into mono
* mode.
*/
switch (event) {
case SND_SOC_DAPM_POST_PMU:
ret = snd_soc_component_update_bits(component,
WM8994_AIF1_DAC1_FILTERS_1,
WM8994_AIF1DAC1_MONO, WM8994_AIF1DAC1_MONO);
break;
case SND_SOC_DAPM_PRE_PMD:
ret = snd_soc_component_update_bits(component,
WM8994_AIF1_DAC1_FILTERS_1,
WM8994_AIF1DAC1_MONO, 0);
break;
}
return ret;
}
static int aries_main_bias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card);
int ret = 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = regulator_enable(priv->reg_main_micbias);
break;
case SND_SOC_DAPM_POST_PMD:
ret = regulator_disable(priv->reg_main_micbias);
break;
}
return ret;
}
static int aries_headset_bias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card);
int ret = 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = regulator_enable(priv->reg_headset_micbias);
break;
case SND_SOC_DAPM_POST_PMD:
ret = regulator_disable(priv->reg_headset_micbias);
break;
}
return ret;
}
static const struct snd_kcontrol_new aries_controls[] = {
SOC_DAPM_PIN_SWITCH("Modem In"),
SOC_DAPM_PIN_SWITCH("Modem Out"),
};
static const struct snd_soc_dapm_widget aries_dapm_widgets[] = {
SND_SOC_DAPM_HP("HP", NULL),
SND_SOC_DAPM_SPK("SPK", aries_spk_cfg),
SND_SOC_DAPM_SPK("RCV", NULL),
SND_SOC_DAPM_LINE("LINE", NULL),
SND_SOC_DAPM_MIC("Main Mic", aries_main_bias),
SND_SOC_DAPM_MIC("Headset Mic", aries_headset_bias),
SND_SOC_DAPM_MIC("Bluetooth Mic", NULL),
SND_SOC_DAPM_SPK("Bluetooth SPK", NULL),
SND_SOC_DAPM_LINE("Modem In", NULL),
SND_SOC_DAPM_LINE("Modem Out", NULL),
/* This must be last as it is conditionally not used */
SND_SOC_DAPM_LINE("FM In", NULL),
};
static int aries_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);
unsigned int pll_out;
int ret;
/* AIF1CLK should be >=3MHz for optimal performance */
if (params_width(params) == 24)
pll_out = params_rate(params) * 384;
else if (params_rate(params) == 8000 || params_rate(params) == 11025)
pll_out = params_rate(params) * 512;
else
pll_out = params_rate(params) * 256;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
ARIES_MCLK1_FREQ, pll_out);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
pll_out, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
static int aries_hw_free(struct snd_pcm_substream *substream)
{
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;
/* Switch sysclk to MCLK1 */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1,
ARIES_MCLK1_FREQ, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Stop PLL */
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
ARIES_MCLK1_FREQ, 0);
if (ret < 0)
return ret;
return 0;
}
/*
* Main DAI operations
*/
static const struct snd_soc_ops aries_ops = {
.hw_params = aries_hw_params,
.hw_free = aries_hw_free,
};
static int aries_baseband_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
unsigned int pll_out;
int ret;
pll_out = 8000 * 512;
/* Set the codec FLL */
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, WM8994_FLL_SRC_MCLK1,
ARIES_MCLK1_FREQ, pll_out);
if (ret < 0)
return ret;
/* Set the codec system clock */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
pll_out, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
static int aries_late_probe(struct snd_soc_card *card)
{
struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card);
int ret, irq;
ret = snd_soc_card_jack_new_pins(card, "Dock", SND_JACK_LINEOUT,
&aries_dock, dock_pins, ARRAY_SIZE(dock_pins));
if (ret)
return ret;
ret = devm_extcon_register_notifier(card->dev,
priv->usb_extcon, EXTCON_JACK_LINE_OUT,
&aries_extcon_notifier_block);
if (ret)
return ret;
if (extcon_get_state(priv->usb_extcon,
EXTCON_JACK_LINE_OUT) > 0)
snd_soc_jack_report(&aries_dock, SND_JACK_LINEOUT,
SND_JACK_LINEOUT);
else
snd_soc_jack_report(&aries_dock, 0, SND_JACK_LINEOUT);
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_BTN_0,
&aries_headset,
jack_pins, ARRAY_SIZE(jack_pins));
if (ret)
return ret;
ret = snd_soc_jack_add_zones(&aries_headset, ARRAY_SIZE(headset_zones),
headset_zones);
if (ret)
return ret;
irq = gpiod_to_irq(priv->gpio_headset_detect);
if (irq < 0) {
dev_err(card->dev, "Failed to map headset detect gpio to irq");
return -EINVAL;
}
ret = devm_request_threaded_irq(card->dev, irq, NULL,
headset_det_irq_thread,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT, "headset_detect", priv);
if (ret) {
dev_err(card->dev, "Failed to request headset detect irq");
return ret;
}
headset_button_gpio[0].data = priv;
headset_button_gpio[0].desc = priv->gpio_headset_key;
snd_jack_set_key(aries_headset.jack, SND_JACK_BTN_0, KEY_MEDIA);
return snd_soc_jack_add_gpios(&aries_headset,
ARRAY_SIZE(headset_button_gpio), headset_button_gpio);
}
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 1,
.channels_max = 1,
};
static const struct snd_soc_pcm_stream bluetooth_params = {
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 1,
.channels_max = 2,
};
static const struct snd_soc_dapm_widget aries_modem_widgets[] = {
SND_SOC_DAPM_INPUT("Modem RX"),
SND_SOC_DAPM_OUTPUT("Modem TX"),
};
static const struct snd_soc_dapm_route aries_modem_routes[] = {
{ "Modem Capture", NULL, "Modem RX" },
{ "Modem TX", NULL, "Modem Playback" },
};
static const struct snd_soc_component_driver aries_component = {
.name = "aries-audio",
.dapm_widgets = aries_modem_widgets,
.num_dapm_widgets = ARRAY_SIZE(aries_modem_widgets),
.dapm_routes = aries_modem_routes,
.num_dapm_routes = ARRAY_SIZE(aries_modem_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
};
static struct snd_soc_dai_driver aries_ext_dai[] = {
{
.name = "Voice call",
.playback = {
.stream_name = "Modem Playback",
.channels_min = 1,
.channels_max = 1,
.rate_min = 8000,
.rate_max = 8000,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "Modem Capture",
.channels_min = 1,
.channels_max = 1,
.rate_min = 8000,
.rate_max = 8000,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
};
SND_SOC_DAILINK_DEFS(aif1,
DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(baseband,
DAILINK_COMP_ARRAY(COMP_CPU("Voice call")),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif2")));
SND_SOC_DAILINK_DEFS(bluetooth,
DAILINK_COMP_ARRAY(COMP_CPU("bt-sco-pcm")),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif3")));
static struct snd_soc_dai_link aries_dai[] = {
{
.name = "WM8994 AIF1",
.stream_name = "HiFi",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ops = &aries_ops,
SND_SOC_DAILINK_REG(aif1),
},
{
.name = "WM8994 AIF2",
.stream_name = "Baseband",
.init = &aries_baseband_init,
.c2c_params = &baseband_params,
.num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(baseband),
},
{
.name = "WM8994 AIF3",
.stream_name = "Bluetooth",
.c2c_params = &bluetooth_params,
.num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(bluetooth),
},
};
static struct snd_soc_card aries_card = {
.name = "ARIES",
.owner = THIS_MODULE,
.dai_link = aries_dai,
.num_links = ARRAY_SIZE(aries_dai),
.controls = aries_controls,
.num_controls = ARRAY_SIZE(aries_controls),
.dapm_widgets = aries_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(aries_dapm_widgets),
.late_probe = aries_late_probe,
};
static const struct aries_wm8994_variant fascinate4g_variant = {
.modem_dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBC_CFC
| SND_SOC_DAIFMT_IB_NF,
.has_fm_radio = false,
};
static const struct aries_wm8994_variant aries_variant = {
.modem_dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBP_CFP
| SND_SOC_DAIFMT_IB_NF,
.has_fm_radio = true,
};
static const struct of_device_id samsung_wm8994_of_match[] = {
{
.compatible = "samsung,fascinate4g-wm8994",
.data = &fascinate4g_variant,
},
{
.compatible = "samsung,aries-wm8994",
.data = &aries_variant,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
static int aries_audio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu, *codec, *extcon_np;
struct device *dev = &pdev->dev;
struct snd_soc_card *card = &aries_card;
struct aries_wm8994_data *priv;
struct snd_soc_dai_link *dai_link;
const struct of_device_id *match;
enum iio_chan_type channel_type;
int ret, i;
if (!np)
return -EINVAL;
card->dev = dev;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
snd_soc_card_set_drvdata(card, priv);
match = of_match_node(samsung_wm8994_of_match, np);
priv->variant = match->data;
/* Remove FM widget if not present */
if (!priv->variant->has_fm_radio)
card->num_dapm_widgets--;
priv->reg_main_micbias = devm_regulator_get(dev, "main-micbias");
if (IS_ERR(priv->reg_main_micbias)) {
dev_err(dev, "Failed to get main micbias regulator\n");
return PTR_ERR(priv->reg_main_micbias);
}
priv->reg_headset_micbias = devm_regulator_get(dev, "headset-micbias");
if (IS_ERR(priv->reg_headset_micbias)) {
dev_err(dev, "Failed to get headset micbias regulator\n");
return PTR_ERR(priv->reg_headset_micbias);
}
priv->gpio_earpath_sel = devm_gpiod_get(dev, "earpath-sel",
GPIOD_OUT_LOW);
if (IS_ERR(priv->gpio_earpath_sel)) {
dev_err(dev, "Failed to get earpath selector gpio");
return PTR_ERR(priv->gpio_earpath_sel);
}
extcon_np = of_parse_phandle(np, "extcon", 0);
priv->usb_extcon = extcon_find_edev_by_node(extcon_np);
of_node_put(extcon_np);
if (IS_ERR(priv->usb_extcon))
return dev_err_probe(dev, PTR_ERR(priv->usb_extcon),
"Failed to get extcon device");
priv->adc = devm_iio_channel_get(dev, "headset-detect");
if (IS_ERR(priv->adc))
return dev_err_probe(dev, PTR_ERR(priv->adc),
"Failed to get ADC channel");
ret = iio_get_channel_type(priv->adc, &channel_type);
if (ret)
return dev_err_probe(dev, ret,
"Failed to get ADC channel type");
if (channel_type != IIO_VOLTAGE)
return -EINVAL;
priv->gpio_headset_key = devm_gpiod_get(dev, "headset-key",
GPIOD_IN);
if (IS_ERR(priv->gpio_headset_key)) {
dev_err(dev, "Failed to get headset key gpio");
return PTR_ERR(priv->gpio_headset_key);
}
priv->gpio_headset_detect = devm_gpiod_get(dev,
"headset-detect", GPIOD_IN);
if (IS_ERR(priv->gpio_headset_detect)) {
dev_err(dev, "Failed to get headset detect gpio");
return PTR_ERR(priv->gpio_headset_detect);
}
/* Update card-name if provided through DT, else use default name */
snd_soc_of_parse_card_name(card, "model");
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
if (ret < 0) {
/* Backwards compatible way */
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
if (ret < 0) {
dev_err(dev, "Audio routing invalid/unspecified\n");
return ret;
}
}
aries_dai[1].dai_fmt = priv->variant->modem_dai_fmt;
cpu = of_get_child_by_name(dev->of_node, "cpu");
if (!cpu)
return -EINVAL;
codec = of_get_child_by_name(dev->of_node, "codec");
if (!codec) {
ret = -EINVAL;
goto out;
}
for_each_card_prelinks(card, i, dai_link) {
dai_link->codecs->of_node = of_parse_phandle(codec,
"sound-dai", 0);
if (!dai_link->codecs->of_node) {
ret = -EINVAL;
goto out;
}
}
/* Set CPU and platform of_node for main DAI */
aries_dai[0].cpus->of_node = of_parse_phandle(cpu,
"sound-dai", 0);
if (!aries_dai[0].cpus->of_node) {
ret = -EINVAL;
goto out;
}
aries_dai[0].platforms->of_node = aries_dai[0].cpus->of_node;
/* Set CPU of_node for BT DAI */
aries_dai[2].cpus->of_node = of_parse_phandle(cpu,
"sound-dai", 1);
if (!aries_dai[2].cpus->of_node) {
ret = -EINVAL;
goto out;
}
ret = devm_snd_soc_register_component(dev, &aries_component,
aries_ext_dai, ARRAY_SIZE(aries_ext_dai));
if (ret < 0) {
dev_err(dev, "Failed to register component: %d\n", ret);
goto out;
}
ret = devm_snd_soc_register_card(dev, card);
if (ret)
dev_err(dev, "snd_soc_register_card() failed:%d\n", ret);
out:
of_node_put(cpu);
of_node_put(codec);
return ret;
}
static struct platform_driver aries_audio_driver = {
.driver = {
.name = "aries-audio-wm8994",
.of_match_table = of_match_ptr(samsung_wm8994_of_match),
.pm = &snd_soc_pm_ops,
},
.probe = aries_audio_probe,
};
module_platform_driver(aries_audio_driver);
MODULE_DESCRIPTION("ALSA SoC ARIES WM8994");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:aries-audio-wm8994");
+217
View File
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright (c) 2014, Insignal Co., Ltd.
//
// Author: Claude <claude@insginal.co.kr>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "../codecs/wm8994.h"
#include "i2s.h"
static int arndale_rt5631_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 *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
int rfs, ret;
unsigned long rclk;
rfs = 256;
rclk = params_rate(params) * rfs;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
0, SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk, SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_soc_ops arndale_rt5631_ops = {
.hw_params = arndale_rt5631_hw_params,
};
static int arndale_wm1811_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);
unsigned int rfs, rclk;
/* Ensure AIF1CLK is >= 3 MHz for optimal performance */
if (params_width(params) == 24)
rfs = 384;
else if (params_rate(params) == 8000 || params_rate(params) == 11025)
rfs = 512;
else
rfs = 256;
rclk = params_rate(params) * rfs;
/*
* We add 1 to the frequency value to ensure proper EPLL setting
* for each audio sampling rate (see epll_24mhz_tbl in drivers/clk/
* samsung/clk-exynos5250.c for list of available EPLL rates).
* The CODEC uses clk API and the value will be rounded hence the MCLK1
* clock's frequency will still be exact multiple of the sample rate.
*/
return snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1,
rclk + 1, SND_SOC_CLOCK_IN);
}
static const struct snd_soc_ops arndale_wm1811_ops = {
.hw_params = arndale_wm1811_hw_params,
};
SND_SOC_DAILINK_DEFS(rt5631_hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5631-aif1")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link arndale_rt5631_dai[] = {
{
.name = "RT5631 HiFi",
.stream_name = "Primary",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBC_CFC,
.ops = &arndale_rt5631_ops,
SND_SOC_DAILINK_REG(rt5631_hifi),
},
};
SND_SOC_DAILINK_DEFS(wm1811_hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link arndale_wm1811_dai[] = {
{
.name = "WM1811 HiFi",
.stream_name = "Primary",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ops = &arndale_wm1811_ops,
SND_SOC_DAILINK_REG(wm1811_hifi),
},
};
static struct snd_soc_card arndale_rt5631 = {
.name = "Arndale RT5631",
.owner = THIS_MODULE,
.dai_link = arndale_rt5631_dai,
.num_links = ARRAY_SIZE(arndale_rt5631_dai),
};
static struct snd_soc_card arndale_wm1811 = {
.name = "Arndale WM1811",
.owner = THIS_MODULE,
.dai_link = arndale_wm1811_dai,
.num_links = ARRAY_SIZE(arndale_wm1811_dai),
};
static void arndale_put_of_nodes(struct snd_soc_card *card)
{
struct snd_soc_dai_link *dai_link;
int i;
for_each_card_prelinks(card, i, dai_link) {
of_node_put(dai_link->cpus->of_node);
of_node_put(dai_link->codecs->of_node);
}
}
static int arndale_audio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card;
struct snd_soc_dai_link *dai_link;
int ret;
card = (struct snd_soc_card *)of_device_get_match_data(&pdev->dev);
card->dev = &pdev->dev;
dai_link = card->dai_link;
dai_link->cpus->of_node = of_parse_phandle(np, "samsung,audio-cpu", 0);
if (!dai_link->cpus->of_node) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu' missing or invalid\n");
return -EINVAL;
}
if (!dai_link->platforms->name)
dai_link->platforms->of_node = dai_link->cpus->of_node;
dai_link->codecs->of_node = of_parse_phandle(np, "samsung,audio-codec", 0);
if (!dai_link->codecs->of_node) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec' missing or invalid\n");
ret = -EINVAL;
goto err_put_of_nodes;
}
ret = devm_snd_soc_register_card(card->dev, card);
if (ret) {
dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card() failed\n");
goto err_put_of_nodes;
}
return 0;
err_put_of_nodes:
arndale_put_of_nodes(card);
return ret;
}
static void arndale_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
arndale_put_of_nodes(card);
}
static const struct of_device_id arndale_audio_of_match[] = {
{ .compatible = "samsung,arndale-rt5631", .data = &arndale_rt5631 },
{ .compatible = "samsung,arndale-alc5631", .data = &arndale_rt5631 },
{ .compatible = "samsung,arndale-wm1811", .data = &arndale_wm1811 },
{},
};
MODULE_DEVICE_TABLE(of, arndale_audio_of_match);
static struct platform_driver arndale_audio_driver = {
.driver = {
.name = "arndale-audio",
.pm = &snd_soc_pm_ops,
.of_match_table = arndale_audio_of_match,
},
.probe = arndale_audio_probe,
.remove = arndale_audio_remove,
};
module_platform_driver(arndale_audio_driver);
MODULE_AUTHOR("Claude <claude@insignal.co.kr>");
MODULE_DESCRIPTION("ALSA SoC Driver for Arndale Board");
MODULE_LICENSE("GPL");
+497
View File
@@ -0,0 +1,497 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Bells audio support
//
// Copyright 2012 Wolfson Microelectronics
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/module.h>
#include "../codecs/wm5102.h"
#include "../codecs/wm9081.h"
/* BCLK2 is fixed at this currently */
#define BCLK2_RATE (64 * 8000)
/*
* Expect a 24.576MHz crystal if one is fitted (the driver will function
* if this is not fitted).
*/
#define MCLK_RATE 24576000
#define SYS_AUDIO_RATE 44100
#define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512)
#define DAI_AP_DSP 0
#define DAI_DSP_CODEC 1
#define DAI_CODEC_CP 2
#define DAI_CODEC_SUB 3
struct bells_drvdata {
int sysclk_rate;
int asyncclk_rate;
};
static struct bells_drvdata wm2200_drvdata = {
.sysclk_rate = 22579200,
};
static struct bells_drvdata wm5102_drvdata = {
.sysclk_rate = 45158400,
.asyncclk_rate = 49152000,
};
static struct bells_drvdata wm5110_drvdata = {
.sysclk_rate = 135475200,
.asyncclk_rate = 147456000,
};
static int bells_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct snd_soc_component *component;
struct bells_drvdata *bells = card->drvdata;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_DSP_CODEC]);
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
component = codec_dai->component;
if (snd_soc_dapm_to_dev(dapm) != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_STANDBY)
break;
ret = snd_soc_component_set_pll(component, WM5102_FLL1,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
bells->sysclk_rate);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
if (bells->asyncclk_rate) {
ret = snd_soc_component_set_pll(component, WM5102_FLL2,
ARIZONA_FLL_SRC_AIF2BCLK,
BCLK2_RATE,
bells->asyncclk_rate);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
}
break;
default:
break;
}
return 0;
}
static int bells_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct snd_soc_component *component;
struct bells_drvdata *bells = card->drvdata;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_DSP_CODEC]);
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
component = codec_dai->component;
if (snd_soc_dapm_to_dev(dapm) != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_component_set_pll(component, WM5102_FLL1, 0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
if (bells->asyncclk_rate) {
ret = snd_soc_component_set_pll(component, WM5102_FLL2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int bells_late_probe(struct snd_soc_card *card)
{
struct bells_drvdata *bells = card->drvdata;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_component *wm0010;
struct snd_soc_component *component;
struct snd_soc_dai *aif1_dai;
struct snd_soc_dai *aif2_dai;
struct snd_soc_dai *aif3_dai;
struct snd_soc_dai *wm9081_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_AP_DSP]);
wm0010 = snd_soc_rtd_to_codec(rtd, 0)->component;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_DSP_CODEC]);
component = snd_soc_rtd_to_codec(rtd, 0)->component;
aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
bells->sysclk_rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(component->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0);
if (ret != 0) {
dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0)
dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_OPCLK, 0,
SYS_MCLK_RATE, SND_SOC_CLOCK_OUT);
if (ret != 0)
dev_err(component->dev, "Failed to set OPCLK: %d\n", ret);
if (card->num_rtd == DAI_CODEC_CP)
return 0;
ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
bells->asyncclk_rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(component->dev, "Failed to set ASYNCCLK: %d\n", ret);
return ret;
}
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_CODEC_CP]);
aif2_dai = snd_soc_rtd_to_cpu(rtd, 0);
ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
if (ret != 0) {
dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret);
return ret;
}
if (card->num_rtd == DAI_CODEC_SUB)
return 0;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_CODEC_SUB]);
aif3_dai = snd_soc_rtd_to_cpu(rtd, 0);
wm9081_dai = snd_soc_rtd_to_codec(rtd, 0);
ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0) {
dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_sysclk(wm9081_dai->component, WM9081_SYSCLK_MCLK,
0, SYS_MCLK_RATE, 0);
if (ret != 0) {
dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret);
return ret;
}
return 0;
}
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = SYS_AUDIO_RATE,
.rate_max = SYS_AUDIO_RATE,
.channels_min = 2,
.channels_max = 2,
};
SND_SOC_DAILINK_DEFS(wm2200_cpu_dsp,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(wm2200_dsp_codec,
DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm2200.1-003a", "wm2200")));
static struct snd_soc_dai_link bells_dai_wm2200[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
SND_SOC_DAILINK_REG(wm2200_cpu_dsp),
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.c2c_params = &sub_params,
.num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm2200_dsp_codec),
},
};
SND_SOC_DAILINK_DEFS(wm5102_cpu_dsp,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(wm5102_dsp_codec,
DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")));
SND_SOC_DAILINK_DEFS(wm5102_baseband,
DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
SND_SOC_DAILINK_DEFS(wm5102_sub,
DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif3")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
static struct snd_soc_dai_link bells_dai_wm5102[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
SND_SOC_DAILINK_REG(wm5102_cpu_dsp),
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.c2c_params = &sub_params,
.num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm5102_dsp_codec),
},
{
.name = "Baseband",
.stream_name = "Baseband",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
.c2c_params = &baseband_params,
.num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5102_baseband),
},
{
.name = "Sub",
.stream_name = "Sub",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBC_CFC,
.ignore_suspend = 1,
.c2c_params = &sub_params,
.num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5102_sub),
},
};
SND_SOC_DAILINK_DEFS(wm5110_cpu_dsp,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(wm5110_dsp_codec,
DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm5110-codec", "wm5110-aif1")));
SND_SOC_DAILINK_DEFS(wm5110_baseband,
DAILINK_COMP_ARRAY(COMP_CPU("wm5110-aif2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
SND_SOC_DAILINK_DEFS(wm5110_sub,
DAILINK_COMP_ARRAY(COMP_CPU("wm5110-aif3")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
static struct snd_soc_dai_link bells_dai_wm5110[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
SND_SOC_DAILINK_REG(wm5110_cpu_dsp),
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.c2c_params = &sub_params,
.num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm5110_dsp_codec),
},
{
.name = "Baseband",
.stream_name = "Baseband",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
.c2c_params = &baseband_params,
.num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5110_baseband),
},
{
.name = "Sub",
.stream_name = "Sub",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBC_CFC,
.ignore_suspend = 1,
.c2c_params = &sub_params,
.num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5110_sub),
},
};
static struct snd_soc_codec_conf bells_codec_conf[] = {
{
.dlc = COMP_CODEC_CONF("wm9081.1-006c"),
.name_prefix = "Sub",
},
};
static const struct snd_soc_dapm_widget bells_widgets[] = {
SND_SOC_DAPM_MIC("DMIC", NULL),
};
static const struct snd_soc_dapm_route bells_routes[] = {
{ "Sub CLK_SYS", NULL, "OPCLK" },
{ "CLKIN", NULL, "OPCLK" },
{ "DMIC", NULL, "MICBIAS2" },
{ "IN2L", NULL, "DMIC" },
{ "IN2R", NULL, "DMIC" },
};
static struct snd_soc_card bells_cards[] = {
{
.name = "Bells WM2200",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm2200,
.num_links = ARRAY_SIZE(bells_dai_wm2200),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_widgets = bells_widgets,
.num_dapm_widgets = ARRAY_SIZE(bells_widgets),
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm2200_drvdata,
},
{
.name = "Bells WM5102",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm5102,
.num_links = ARRAY_SIZE(bells_dai_wm5102),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_widgets = bells_widgets,
.num_dapm_widgets = ARRAY_SIZE(bells_widgets),
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm5102_drvdata,
},
{
.name = "Bells WM5110",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm5110,
.num_links = ARRAY_SIZE(bells_dai_wm5110),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_widgets = bells_widgets,
.num_dapm_widgets = ARRAY_SIZE(bells_widgets),
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm5110_drvdata,
},
};
static int bells_probe(struct platform_device *pdev)
{
int ret;
bells_cards[pdev->id].dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, &bells_cards[pdev->id]);
if (ret)
dev_err(&pdev->dev,
"snd_soc_register_card(%s) failed: %d\n",
bells_cards[pdev->id].name, ret);
return ret;
}
static struct platform_driver bells_driver = {
.driver = {
.name = "bells",
.pm = &snd_soc_pm_ops,
},
.probe = bells_probe,
};
module_platform_driver(bells_driver);
MODULE_DESCRIPTION("Bells audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bells");
+18
View File
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* ALSA PCM interface for the Samsung SoC
*/
#ifndef _SAMSUNG_DMA_H
#define _SAMSUNG_DMA_H
#include <sound/dmaengine_pcm.h>
/*
* @tx, @rx arguments can be NULL if the DMA channel names are "tx", "rx",
* otherwise actual DMA channel names must be passed to this function.
*/
int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter,
const char *tx, const char *rx,
struct device *dma_dev);
#endif /* _SAMSUNG_DMA_H */
+41
View File
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0
//
// dmaengine.c - Samsung dmaengine wrapper
//
// Author: Mark Brown <broonie@linaro.org>
// Copyright 2013 Linaro
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>
#include "dma.h"
int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter,
const char *tx, const char *rx,
struct device *dma_dev)
{
struct snd_dmaengine_pcm_config *pcm_conf;
pcm_conf = devm_kzalloc(dev, sizeof(*pcm_conf), GFP_KERNEL);
if (!pcm_conf)
return -ENOMEM;
pcm_conf->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config;
pcm_conf->compat_filter_fn = filter;
pcm_conf->dma_dev = dma_dev;
pcm_conf->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = tx;
pcm_conf->chan_names[SNDRV_PCM_STREAM_CAPTURE] = rx;
return devm_snd_dmaengine_pcm_register(dev, pcm_conf,
SND_DMAENGINE_PCM_FLAG_COMPAT);
}
EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register);
MODULE_AUTHOR("Mark Brown <broonie@linaro.org>");
MODULE_DESCRIPTION("Samsung dmaengine ASoC driver");
MODULE_LICENSE("GPL");
+158
View File
@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung I2S driver's register header
*/
#ifndef __SND_SOC_SAMSUNG_I2S_REGS_H
#define __SND_SOC_SAMSUNG_I2S_REGS_H
#define I2SCON 0x0
#define I2SMOD 0x4
#define I2SFIC 0x8
#define I2SPSR 0xc
#define I2STXD 0x10
#define I2SRXD 0x14
#define I2SFICS 0x18
#define I2STXDS 0x1c
#define I2SAHB 0x20
#define I2SSTR0 0x24
#define I2SSIZE 0x28
#define I2STRNCNT 0x2c
#define I2SLVL0ADDR 0x30
#define I2SLVL1ADDR 0x34
#define I2SLVL2ADDR 0x38
#define I2SLVL3ADDR 0x3c
#define I2SSTR1 0x40
#define I2SVER 0x44
#define I2SFIC1 0x48
#define I2STDM 0x4c
#define I2SFSTA 0x50
#define CON_RSTCLR (1 << 31)
#define CON_FRXOFSTATUS (1 << 26)
#define CON_FRXORINTEN (1 << 25)
#define CON_FTXSURSTAT (1 << 24)
#define CON_FTXSURINTEN (1 << 23)
#define CON_TXSDMA_PAUSE (1 << 20)
#define CON_TXSDMA_ACTIVE (1 << 18)
#define CON_FTXURSTATUS (1 << 17)
#define CON_FTXURINTEN (1 << 16)
#define CON_TXFIFO2_EMPTY (1 << 15)
#define CON_TXFIFO1_EMPTY (1 << 14)
#define CON_TXFIFO2_FULL (1 << 13)
#define CON_TXFIFO1_FULL (1 << 12)
#define CON_LRINDEX (1 << 11)
#define CON_TXFIFO_EMPTY (1 << 10)
#define CON_RXFIFO_EMPTY (1 << 9)
#define CON_TXFIFO_FULL (1 << 8)
#define CON_RXFIFO_FULL (1 << 7)
#define CON_TXDMA_PAUSE (1 << 6)
#define CON_RXDMA_PAUSE (1 << 5)
#define CON_TXCH_PAUSE (1 << 4)
#define CON_RXCH_PAUSE (1 << 3)
#define CON_TXDMA_ACTIVE (1 << 2)
#define CON_RXDMA_ACTIVE (1 << 1)
#define CON_ACTIVE (1 << 0)
#define MOD_OPCLK_SHIFT 30
#define MOD_OPCLK_CDCLK_OUT (0 << MOD_OPCLK_SHIFT)
#define MOD_OPCLK_CDCLK_IN (1 << MOD_OPCLK_SHIFT)
#define MOD_OPCLK_BCLK_OUT (2 << MOD_OPCLK_SHIFT)
#define MOD_OPCLK_PCLK (3 << MOD_OPCLK_SHIFT)
#define MOD_OPCLK_MASK (3 << MOD_OPCLK_SHIFT)
#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
#define MOD_BLCS_SHIFT 26
#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT)
#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT)
#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT)
#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT)
#define MOD_BLCP_SHIFT 24
#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT)
#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT)
#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT)
#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT)
#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
#define MOD_C1DD_HHALF (1 << 19)
#define MOD_C1DD_LHALF (1 << 18)
#define MOD_DC2_EN (1 << 17)
#define MOD_DC1_EN (1 << 16)
#define MOD_BLC_16BIT (0 << 13)
#define MOD_BLC_8BIT (1 << 13)
#define MOD_BLC_24BIT (2 << 13)
#define MOD_BLC_MASK (3 << 13)
#define MOD_TXONLY (0 << 8)
#define MOD_RXONLY (1 << 8)
#define MOD_TXRX (2 << 8)
#define MOD_MASK (3 << 8)
#define MOD_LRP_SHIFT 7
#define MOD_LR_LLOW 0
#define MOD_LR_RLOW 1
#define MOD_SDF_SHIFT 5
#define MOD_SDF_IIS 0
#define MOD_SDF_MSB 1
#define MOD_SDF_LSB 2
#define MOD_SDF_MASK 3
#define MOD_RCLK_SHIFT 3
#define MOD_RCLK_256FS 0
#define MOD_RCLK_512FS 1
#define MOD_RCLK_384FS 2
#define MOD_RCLK_768FS 3
#define MOD_RCLK_MASK 3
#define MOD_BCLK_SHIFT 1
#define MOD_BCLK_32FS 0
#define MOD_BCLK_48FS 1
#define MOD_BCLK_16FS 2
#define MOD_BCLK_24FS 3
#define MOD_BCLK_MASK 3
#define MOD_8BIT (1 << 0)
#define EXYNOS5420_MOD_LRP_SHIFT 15
#define EXYNOS5420_MOD_SDF_SHIFT 6
#define EXYNOS5420_MOD_RCLK_SHIFT 4
#define EXYNOS5420_MOD_BCLK_SHIFT 0
#define EXYNOS5420_MOD_BCLK_64FS 4
#define EXYNOS5420_MOD_BCLK_96FS 5
#define EXYNOS5420_MOD_BCLK_128FS 6
#define EXYNOS5420_MOD_BCLK_192FS 7
#define EXYNOS5420_MOD_BCLK_256FS 8
#define EXYNOS5420_MOD_BCLK_MASK 0xf
#define EXYNOS7_MOD_RCLK_64FS 4
#define EXYNOS7_MOD_RCLK_128FS 5
#define EXYNOS7_MOD_RCLK_96FS 6
#define EXYNOS7_MOD_RCLK_192FS 7
#define PSR_PSREN (1 << 15)
#define PSR_PSVAL(x) ((((x) - 1) << 8) & 0x3f00)
#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
#define FIC_TXFLUSH (1 << 15)
#define FIC_RXFLUSH (1 << 7)
#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
#define AHB_INTENLVL0 (1 << 24)
#define AHB_LVL0INT (1 << 20)
#define AHB_CLRLVL0INT (1 << 16)
#define AHB_DMARLD (1 << 5)
#define AHB_INTMASK (1 << 3)
#define AHB_DMAEN (1 << 0)
#define AHB_LVLINTMASK (0xf << 20)
#define I2SSIZE_TRNMSK (0xffff)
#define I2SSIZE_SHIFT (16)
#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ALSA SoC Audio Layer - Samsung I2S Controller driver
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassisinghbrar@gmail.com>
*/
#ifndef __SND_SOC_SAMSUNG_I2S_H
#define __SND_SOC_SAMSUNG_I2S_H
#define SAMSUNG_I2S_DAI "samsung-i2s"
#define SAMSUNG_I2S_DAI_SEC "samsung-i2s-sec"
#define SAMSUNG_I2S_DIV_BCLK 1
#define SAMSUNG_I2S_RCLKSRC_0 0
#define SAMSUNG_I2S_RCLKSRC_1 1
#define SAMSUNG_I2S_CDCLK 2
/* Operation clock for IIS logic */
#define SAMSUNG_I2S_OPCLK 3
#define SAMSUNG_I2S_OPCLK_CDCLK_OUT 0 /* CODEC clock out */
#define SAMSUNG_I2S_OPCLK_CDCLK_IN 1 /* CODEC clock in */
#define SAMSUNG_I2S_OPCLK_BCLK_OUT 2 /* Bit clock out */
#define SAMSUNG_I2S_OPCLK_PCLK 3 /* Audio bus clock */
#endif /* __SND_SOC_SAMSUNG_I2S_H */
+420
View File
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0+
//
// idma.c - I2S0 internal DMA driver
//
// Copyright (c) 2011 Samsung Electronics Co., Ltd.
// http://www.samsung.com
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "i2s.h"
#include "idma.h"
#include "i2s-regs.h"
#define ST_RUNNING (1<<0)
#define ST_OPENED (1<<1)
static const struct snd_pcm_hardware idma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.buffer_bytes_max = MAX_IDMA_BUFFER,
.period_bytes_min = 128,
.period_bytes_max = MAX_IDMA_PERIOD,
.periods_min = 1,
.periods_max = 2,
};
struct idma_ctrl {
spinlock_t lock;
int state;
dma_addr_t start;
dma_addr_t pos;
dma_addr_t end;
dma_addr_t period;
dma_addr_t periodsz;
void *token;
void (*cb)(void *dt, int bytes_xfer);
};
static struct idma_info {
spinlock_t lock;
void __iomem *regs;
dma_addr_t lp_tx_addr;
} idma;
static int idma_irq;
static void idma_getpos(dma_addr_t *src)
{
*src = idma.lp_tx_addr +
(readl(idma.regs + I2STRNCNT) & 0xffffff) * 4;
}
static int idma_enqueue(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = substream->runtime->private_data;
u32 val;
scoped_guard(spinlock, &prtd->lock)
prtd->token = (void *) substream;
/* Internal DMA Level0 Interrupt Address */
val = idma.lp_tx_addr + prtd->periodsz;
writel(val, idma.regs + I2SLVL0ADDR);
/* Start address0 of I2S internal DMA operation. */
val = idma.lp_tx_addr;
writel(val, idma.regs + I2SSTR0);
/*
* Transfer block size for I2S internal DMA.
* Should decide transfer size before start dma operation
*/
val = readl(idma.regs + I2SSIZE);
val &= ~(I2SSIZE_TRNMSK << I2SSIZE_SHIFT);
val |= (((runtime->dma_bytes >> 2) &
I2SSIZE_TRNMSK) << I2SSIZE_SHIFT);
writel(val, idma.regs + I2SSIZE);
val = readl(idma.regs + I2SAHB);
val |= AHB_INTENLVL0;
writel(val, idma.regs + I2SAHB);
return 0;
}
static void idma_setcallbk(struct snd_pcm_substream *substream,
void (*cb)(void *, int))
{
struct idma_ctrl *prtd = substream->runtime->private_data;
guard(spinlock)(&prtd->lock);
prtd->cb = cb;
}
static void idma_control(int op)
{
u32 val = readl(idma.regs + I2SAHB);
guard(spinlock)(&idma.lock);
switch (op) {
case LPAM_DMA_START:
val |= (AHB_INTENLVL0 | AHB_DMAEN);
break;
case LPAM_DMA_STOP:
val &= ~(AHB_INTENLVL0 | AHB_DMAEN);
break;
default:
return;
}
writel(val, idma.regs + I2SAHB);
}
static void idma_done(void *id, int bytes_xfer)
{
struct snd_pcm_substream *substream = id;
struct idma_ctrl *prtd = substream->runtime->private_data;
if (prtd && (prtd->state & ST_RUNNING))
snd_pcm_period_elapsed(substream);
}
static int idma_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 idma_ctrl *prtd = substream->runtime->private_data;
u32 mod = readl(idma.regs + I2SMOD);
u32 ahb = readl(idma.regs + I2SAHB);
ahb |= (AHB_DMARLD | AHB_INTMASK);
mod |= MOD_TXS_IDMA;
writel(ahb, idma.regs + I2SAHB);
writel(mod, idma.regs + I2SMOD);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = params_buffer_bytes(params);
prtd->start = prtd->pos = runtime->dma_addr;
prtd->period = params_periods(params);
prtd->periodsz = params_period_bytes(params);
prtd->end = runtime->dma_addr + runtime->dma_bytes;
idma_setcallbk(substream, idma_done);
return 0;
}
static int idma_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
static int idma_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct idma_ctrl *prtd = substream->runtime->private_data;
prtd->pos = prtd->start;
/* flush the DMA channel */
idma_control(LPAM_DMA_STOP);
idma_enqueue(substream);
return 0;
}
static int idma_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
struct idma_ctrl *prtd = substream->runtime->private_data;
int ret = 0;
guard(spinlock)(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
prtd->state |= ST_RUNNING;
idma_control(LPAM_DMA_START);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
prtd->state &= ~ST_RUNNING;
idma_control(LPAM_DMA_STOP);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static snd_pcm_uframes_t
idma_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = runtime->private_data;
dma_addr_t src;
unsigned long res;
scoped_guard(spinlock, &prtd->lock) {
idma_getpos(&src);
res = src - prtd->start;
}
return bytes_to_frames(substream->runtime, res);
}
static int idma_mmap(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long size, offset;
/* From snd_pcm_lib_mmap_iomem */
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
size = vma->vm_end - vma->vm_start;
offset = vma->vm_pgoff << PAGE_SHIFT;
return io_remap_pfn_range(vma, vma->vm_start,
(runtime->dma_addr + offset) >> PAGE_SHIFT,
size, vma->vm_page_prot);
}
static irqreturn_t iis_irq(int irqno, void *dev_id)
{
struct idma_ctrl *prtd = (struct idma_ctrl *)dev_id;
u32 iisahb, val, addr;
iisahb = readl(idma.regs + I2SAHB);
val = (iisahb & AHB_LVL0INT) ? AHB_CLRLVL0INT : 0;
if (val) {
iisahb |= val;
writel(iisahb, idma.regs + I2SAHB);
addr = readl(idma.regs + I2SLVL0ADDR) - idma.lp_tx_addr;
addr += prtd->periodsz;
addr %= (u32)(prtd->end - prtd->start);
addr += idma.lp_tx_addr;
writel(addr, idma.regs + I2SLVL0ADDR);
if (prtd->cb)
prtd->cb(prtd->token, prtd->period);
}
return IRQ_HANDLED;
}
static int idma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd;
int ret;
snd_soc_set_runtime_hwparams(substream, &idma_hardware);
prtd = kzalloc_obj(struct idma_ctrl);
if (prtd == NULL)
return -ENOMEM;
ret = request_irq(idma_irq, iis_irq, 0, "i2s", prtd);
if (ret < 0) {
pr_err("fail to claim i2s irq , ret = %d\n", ret);
kfree(prtd);
return ret;
}
spin_lock_init(&prtd->lock);
runtime->private_data = prtd;
return 0;
}
static int idma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = runtime->private_data;
free_irq(idma_irq, prtd);
if (!prtd)
pr_err("idma_close called with prtd == NULL\n");
kfree(prtd);
return 0;
}
static void idma_free(struct snd_soc_component *component,
struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (!substream)
return;
buf = &substream->dma_buffer;
if (!buf->area)
return;
iounmap((void __iomem *)buf->area);
buf->area = NULL;
buf->addr = 0;
}
static int preallocate_idma_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
/* Assign PCM buffer pointers */
buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS;
buf->addr = idma.lp_tx_addr;
buf->bytes = idma_hardware.buffer_bytes_max;
buf->area = (unsigned char * __force)ioremap(buf->addr, buf->bytes);
if (!buf->area)
return -ENOMEM;
return 0;
}
static int idma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = preallocate_idma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
}
return ret;
}
void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr)
{
spin_lock_init(&idma.lock);
idma.regs = regs;
idma.lp_tx_addr = addr;
}
EXPORT_SYMBOL_GPL(idma_reg_addr_init);
static const struct snd_soc_component_driver asoc_idma_platform = {
.open = idma_open,
.close = idma_close,
.trigger = idma_trigger,
.pointer = idma_pointer,
.mmap = idma_mmap,
.hw_params = idma_hw_params,
.hw_free = idma_hw_free,
.prepare = idma_prepare,
.pcm_new = idma_new,
.pcm_free = idma_free,
};
static int asoc_idma_platform_probe(struct platform_device *pdev)
{
idma_irq = platform_get_irq(pdev, 0);
if (idma_irq < 0)
return idma_irq;
return devm_snd_soc_register_component(&pdev->dev, &asoc_idma_platform,
NULL, 0);
}
static struct platform_driver asoc_idma_driver = {
.driver = {
.name = "samsung-idma",
},
.probe = asoc_idma_platform_probe,
};
module_platform_driver(asoc_idma_driver);
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("Samsung ASoC IDMA Driver");
MODULE_LICENSE("GPL");
+19
View File
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2011 Samsung Electronics Co., Ltd
* http://www.samsung.com
*/
#ifndef __SND_SOC_SAMSUNG_IDMA_H_
#define __SND_SOC_SAMSUNG_IDMA_H_
extern void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr);
/* dma_state */
#define LPAM_DMA_STOP 0
#define LPAM_DMA_START 1
#define MAX_IDMA_PERIOD (128 * 1024)
#define MAX_IDMA_BUFFER (160 * 1024)
#endif /* __SND_SOC_SAMSUNG_IDMA_H_ */
+360
View File
@@ -0,0 +1,360 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Littlemill audio support
//
// Copyright 2011 Wolfson Microelectronics
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/module.h>
#include "../codecs/wm8994.h"
static int sample_rate = 44100;
static int littlemill_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *aif1_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
/*
* If we've not already clocked things via hw_params()
* then do so now, otherwise these are noops.
*/
if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
WM8994_FLL_SRC_MCLK2, 32768,
sample_rate * 512);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai,
WM8994_SYSCLK_FLL1,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int littlemill_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *aif1_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL1: %d\n", ret);
return ret;
}
break;
default:
break;
}
return 0;
}
static int littlemill_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;
sample_rate = params_rate(params);
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
WM8994_FLL_SRC_MCLK2, 32768,
sample_rate * 512);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8994_SYSCLK_FLL1,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
return 0;
}
static const struct snd_soc_ops littlemill_ops = {
.hw_params = littlemill_hw_params,
};
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
SND_SOC_DAILINK_DEFS(cpu,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(baseband,
DAILINK_COMP_ARRAY(COMP_CPU("wm8994-aif2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027",
"wm1250-ev1")));
static struct snd_soc_dai_link littlemill_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ops = &littlemill_ops,
SND_SOC_DAILINK_REG(cpu),
},
{
.name = "Baseband",
.stream_name = "Baseband",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
.c2c_params = &baseband_params,
.num_c2c_params = 1,
SND_SOC_DAILINK_REG(baseband),
},
};
static int bbclk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *aif2_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
aif2_dai = snd_soc_rtd_to_cpu(rtd, 0);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
WM8994_FLL_SRC_BCLK, 64 * 8000,
8000 * 256);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2,
8000 * 256,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
break;
case SND_SOC_DAPM_POST_PMD:
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL2: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL2: %d\n", ret);
return ret;
}
break;
default:
return -EINVAL;
}
return 0;
}
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
};
static const struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_HP("Headset Mic", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0,
bbclk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route audio_paths[] = {
{ "Headphone", NULL, "HPOUT1L" },
{ "Headphone", NULL, "HPOUT1R" },
{ "AMIC", NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */
{ "IN1LN", NULL, "AMIC" },
{ "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */
{ "DMIC1DAT", NULL, "DMIC" },
{ "DMIC2DAT", NULL, "DMIC" },
{ "AIF2CLK", NULL, "Baseband Clock" },
};
static struct snd_soc_jack littlemill_headset;
static struct snd_soc_jack_pin littlemill_headset_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
static int littlemill_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_component *component;
struct snd_soc_dai *aif1_dai;
struct snd_soc_dai *aif2_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
component = snd_soc_rtd_to_codec(rtd, 0)->component;
aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
aif2_dai = snd_soc_rtd_to_cpu(rtd, 0);
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5,
&littlemill_headset,
littlemill_headset_pins,
ARRAY_SIZE(littlemill_headset_pins));
if (ret)
return ret;
/* This will check device compatibility itself */
wm8958_mic_detect(component, &littlemill_headset, NULL, NULL, NULL, NULL);
/* As will this */
wm8994_mic_detect(component, &littlemill_headset, 1);
return 0;
}
static struct snd_soc_card littlemill = {
.name = "Littlemill",
.owner = THIS_MODULE,
.dai_link = littlemill_dai,
.num_links = ARRAY_SIZE(littlemill_dai),
.set_bias_level = littlemill_set_bias_level,
.set_bias_level_post = littlemill_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.late_probe = littlemill_late_probe,
};
static int littlemill_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &littlemill;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
return ret;
}
static struct platform_driver littlemill_driver = {
.driver = {
.name = "littlemill",
.pm = &snd_soc_pm_ops,
},
.probe = littlemill_probe,
};
module_platform_driver(littlemill_driver);
MODULE_DESCRIPTION("Littlemill audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:littlemill");
+213
View File
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Lowland audio support
//
// Copyright 2011 Wolfson Microelectronics
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/module.h>
#include "../codecs/wm5100.h"
#include "../codecs/wm9081.h"
#define MCLK1_RATE (44100 * 512)
#define CLKOUT_RATE (44100 * 256)
static struct snd_soc_jack lowland_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin lowland_headset_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
{
.pin = "Line Out",
.mask = SND_JACK_LINEOUT,
},
};
static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
int ret;
ret = snd_soc_component_set_sysclk(component, WM5100_CLK_SYSCLK,
WM5100_CLKSRC_MCLK1, MCLK1_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK clock source: %d\n", ret);
return ret;
}
/* Clock OPCLK, used by the other audio components. */
ret = snd_soc_component_set_sysclk(component, WM5100_CLK_OPCLK, 0,
CLKOUT_RATE, 0);
if (ret < 0) {
pr_err("Failed to set OPCLK rate: %d\n", ret);
return ret;
}
ret = snd_soc_card_jack_new_pins(rtd->card, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&lowland_headset, lowland_headset_pins,
ARRAY_SIZE(lowland_headset_pins));
if (ret)
return ret;
wm5100_detect(component, &lowland_headset);
return 0;
}
static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(rtd->card);
snd_soc_dapm_disable_pin(dapm, "LINEOUT");
/* At any time the WM9081 is active it will have this clock */
return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0,
CLKOUT_RATE, 0);
}
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 44100,
.rate_max = 44100,
.channels_min = 2,
.channels_max = 2,
};
SND_SOC_DAILINK_DEFS(cpu,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm5100.1-001a", "wm5100-aif1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(baseband,
DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
SND_SOC_DAILINK_DEFS(speaker,
DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif3")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
static struct snd_soc_dai_link lowland_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.init = lowland_wm5100_init,
SND_SOC_DAILINK_REG(cpu),
},
{
.name = "Baseband",
.stream_name = "Baseband",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(baseband),
},
{
.name = "Sub Speaker",
.stream_name = "Sub Speaker",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
.c2c_params = &sub_params,
.num_c2c_params = 1,
.init = lowland_wm9081_init,
SND_SOC_DAILINK_REG(speaker),
},
};
static struct snd_soc_codec_conf lowland_codec_conf[] = {
{
.dlc = COMP_CODEC_CONF("wm9081.1-006c"),
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Line Out"),
};
static const struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_LINE("Line Out", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static const struct snd_soc_dapm_route audio_paths[] = {
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT1" },
};
static struct snd_soc_card lowland = {
.name = "Lowland",
.owner = THIS_MODULE,
.dai_link = lowland_dai,
.num_links = ARRAY_SIZE(lowland_dai),
.codec_conf = lowland_codec_conf,
.num_configs = ARRAY_SIZE(lowland_codec_conf),
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
};
static int lowland_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &lowland;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
return ret;
}
static struct platform_driver lowland_driver = {
.driver = {
.name = "lowland",
.pm = &snd_soc_pm_ops,
},
.probe = lowland_probe,
};
module_platform_driver(lowland_driver);
MODULE_DESCRIPTION("Lowland audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lowland");
@@ -0,0 +1,775 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Midas audio support
//
// Copyright (C) 2018 Simon Shields <simon@lineageos.org>
// Copyright (C) 2020 Samsung Electronics Co., Ltd.
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/mfd/wm8994/registers.h>
#include <linux/input-event-codes.h>
#include <linux/module.h>
#include <linux/of.h>
#include <sound/jack.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include "i2s.h"
#include "../codecs/wm8994.h"
/*
* The MCLK1 clock source is XCLKOUT with its mux set to the external fixed rate
* oscillator (XXTI).
*/
#define MCLK1_RATE 24000000U
#define MCLK2_RATE 32768U
#define DEFAULT_FLL1_RATE 11289600U
struct midas_priv {
struct gpio_desc *gpio_fm_sel;
struct gpio_desc *gpio_lineout_sel;
struct gpio_desc *gpio_headset_detect;
struct gpio_desc *gpio_headset_key;
struct iio_channel *adc_headset_detect;
unsigned int fll1_rate;
struct snd_soc_jack headset_jack;
};
static struct snd_soc_jack_pin headset_jack_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
/*
* min_mv/max_mv values in this struct are set up based on DT values.
*/
static struct snd_soc_jack_zone headset_jack_zones[] = {
{ .jack_type = SND_JACK_HEADPHONE, },
{ .jack_type = SND_JACK_HEADSET, },
{ .jack_type = SND_JACK_HEADPHONE, },
};
/*
* This is used for manual detection in headset_key_check, we reuse the
* structure since it's convenient.
*
* min_mv/max_mv values in this struct are set up based on DT values.
*/
static struct snd_soc_jack_zone headset_key_zones[] = {
{ .jack_type = SND_JACK_BTN_0, }, /* Media */
{ .jack_type = SND_JACK_BTN_1, }, /* Volume Up */
{ .jack_type = SND_JACK_BTN_2, }, /* Volume Down */
};
static int headset_jack_check(void *data)
{
struct snd_soc_component *codec = data;
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(codec);
struct midas_priv *priv = snd_soc_card_get_drvdata(codec->card);
int adc, ret;
int jack_type = 0;
if (!gpiod_get_value_cansleep(priv->gpio_headset_detect))
return 0;
/* Enable headset mic bias regulator so that the ADC reading works */
ret = snd_soc_dapm_force_enable_pin(dapm, "headset-mic-bias");
if (ret < 0) {
pr_err("%s: Failed to enable headset mic bias regulator (%d), assuming headphones\n",
__func__, ret);
return SND_JACK_HEADPHONE;
}
snd_soc_dapm_sync(dapm);
/* Sleep for a small amount of time to get the value to stabilize */
msleep(20);
ret = iio_read_channel_processed(priv->adc_headset_detect, &adc);
if (ret) {
pr_err("%s: Failed to read ADC (%d), assuming headphones\n",
__func__, ret);
jack_type = SND_JACK_HEADPHONE;
goto out;
}
pr_debug("%s: ADC value is %d\n", __func__, adc);
jack_type = snd_soc_jack_get_type(&priv->headset_jack, adc);
out:
ret = snd_soc_dapm_disable_pin(dapm, "headset-mic-bias");
if (ret < 0)
pr_err("%s: Failed to disable headset mic bias regulator (%d)\n",
__func__, ret);
snd_soc_dapm_sync(dapm);
return jack_type;
}
static int headset_key_check(void *data)
{
struct snd_soc_component *codec = data;
struct midas_priv *priv = snd_soc_card_get_drvdata(codec->card);
int adc, i, ret;
if (!gpiod_get_value_cansleep(priv->gpio_headset_key))
return 0;
/* Filter out keypresses when 4 pole jack not detected */
if (!(priv->headset_jack.status & SND_JACK_MICROPHONE))
return 0;
ret = iio_read_channel_processed(priv->adc_headset_detect, &adc);
if (ret) {
pr_err("%s: Failed to read ADC (%d), can't detect key type\n",
__func__, ret);
return 0;
}
pr_debug("%s: ADC value is %d\n", __func__, adc);
for (i = 0; i < ARRAY_SIZE(headset_key_zones); i++) {
if (adc >= headset_key_zones[i].min_mv &&
adc <= headset_key_zones[i].max_mv) {
return headset_key_zones[i].jack_type;
}
}
return 0;
}
static struct snd_soc_jack_gpio headset_gpio[] = {
{
.name = "Headset Jack",
.report = SND_JACK_HEADSET,
.debounce_time = 150,
.jack_status_check = headset_jack_check,
},
{
.name = "Headset Key",
.report = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
.debounce_time = 30,
.jack_status_check = headset_key_check,
},
};
static int midas_start_fll1(struct snd_soc_pcm_runtime *rtd, unsigned int rate)
{
struct snd_soc_card *card = rtd->card;
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
int ret;
if (!rate)
rate = priv->fll1_rate;
/*
* If no new rate is requested, set FLL1 to a sane default for jack
* detection.
*/
if (!rate)
rate = DEFAULT_FLL1_RATE;
if (rate != priv->fll1_rate && priv->fll1_rate) {
/* while reconfiguring, switch to MCLK2 for SYSCLK */
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
MCLK2_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "Unable to switch to MCLK2: %d\n", ret);
return ret;
}
}
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
MCLK1_RATE, rate);
if (ret < 0) {
dev_err(card->dev, "Failed to set FLL1 rate: %d\n", ret);
return ret;
}
priv->fll1_rate = rate;
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1,
priv->fll1_rate, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "Failed to set SYSCLK source: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, 0,
SAMSUNG_I2S_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev, "Failed to set OPCLK source: %d\n", ret);
return ret;
}
return 0;
}
static int midas_stop_fll1(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
int ret;
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
MCLK2_RATE, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "Unable to switch to MCLK2: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
if (ret < 0) {
dev_err(card->dev, "Unable to stop FLL1: %d\n", ret);
return ret;
}
priv->fll1_rate = 0;
return 0;
}
static int midas_aif1_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);
unsigned int pll_out;
/* AIF1CLK should be at least 3MHz for "optimal performance" */
if (params_rate(params) == 8000 || params_rate(params) == 11025)
pll_out = params_rate(params) * 512;
else
pll_out = params_rate(params) * 256;
return midas_start_fll1(rtd, pll_out);
}
static const struct snd_soc_ops midas_aif1_ops = {
.hw_params = midas_aif1_hw_params,
};
/*
* We only have a single external speaker, so mix stereo data
* to a single mono stream.
*/
static int midas_ext_spkmode(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm);
int ret = 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = snd_soc_component_update_bits(codec, WM8994_SPKOUT_MIXERS,
WM8994_SPKMIXR_TO_SPKOUTL_MASK,
WM8994_SPKMIXR_TO_SPKOUTL);
break;
case SND_SOC_DAPM_POST_PMD:
ret = snd_soc_component_update_bits(codec, WM8994_SPKOUT_MIXERS,
WM8994_SPKMIXR_TO_SPKOUTL_MASK,
0);
break;
}
return ret;
}
static int midas_fm_set(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
if (!priv->gpio_fm_sel)
return 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
gpiod_set_value_cansleep(priv->gpio_fm_sel, 1);
break;
case SND_SOC_DAPM_POST_PMD:
gpiod_set_value_cansleep(priv->gpio_fm_sel, 0);
break;
}
return 0;
}
static int midas_line_set(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
if (!priv->gpio_lineout_sel)
return 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
gpiod_set_value_cansleep(priv->gpio_lineout_sel, 1);
break;
case SND_SOC_DAPM_POST_PMD:
gpiod_set_value_cansleep(priv->gpio_lineout_sel, 0);
break;
}
return 0;
}
static const struct snd_kcontrol_new midas_controls[] = {
SOC_DAPM_PIN_SWITCH("HP"),
SOC_DAPM_PIN_SWITCH("SPK"),
SOC_DAPM_PIN_SWITCH("RCV"),
SOC_DAPM_PIN_SWITCH("LINE"),
SOC_DAPM_PIN_SWITCH("HDMI"),
SOC_DAPM_PIN_SWITCH("Main Mic"),
SOC_DAPM_PIN_SWITCH("Sub Mic"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("FM In"),
};
static const struct snd_soc_dapm_widget midas_dapm_widgets[] = {
SND_SOC_DAPM_HP("HP", NULL),
SND_SOC_DAPM_SPK("SPK", midas_ext_spkmode),
SND_SOC_DAPM_SPK("RCV", NULL),
/* FIXME: toggle MAX77693 on i9300/i9305 */
SND_SOC_DAPM_LINE("LINE", midas_line_set),
SND_SOC_DAPM_LINE("HDMI", NULL),
SND_SOC_DAPM_LINE("FM In", midas_fm_set),
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_REGULATOR_SUPPLY("headset-mic-bias", 0, 0),
SND_SOC_DAPM_MIC("Main Mic", NULL),
SND_SOC_DAPM_REGULATOR_SUPPLY("mic-bias", 0, 0),
SND_SOC_DAPM_MIC("Sub Mic", NULL),
SND_SOC_DAPM_REGULATOR_SUPPLY("submic-bias", 0, 0),
};
/* Default routing; supplemented by audio-routing DT property */
static const struct snd_soc_dapm_route midas_dapm_routes[] = {
/* Bind microphones with their respective regulator supplies */
{"Main Mic", NULL, "mic-bias"},
{"Sub Mic", NULL, "submic-bias"},
{"Headset Mic", NULL, "headset-mic-bias"},
};
static int midas_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card,
&card->dai_link[0]);
struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
return midas_stop_fll1(rtd);
case SND_SOC_BIAS_PREPARE:
return midas_start_fll1(rtd, 0);
default:
break;
}
return 0;
}
static int midas_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card,
&card->dai_link[0]);
struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
/* Use MCLK2 as SYSCLK for boot */
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, MCLK2_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(aif1_dai->dev, "Failed to switch to MCLK2: %d\n", ret);
return ret;
}
if (!priv->gpio_headset_detect) {
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5,
&priv->headset_jack,
headset_jack_pins,
ARRAY_SIZE(headset_jack_pins));
if (ret)
return ret;
wm8958_mic_detect(aif1_dai->component, &priv->headset_jack,
NULL, NULL, NULL, NULL);
} else {
/* Some devices (n8000, t310) use a GPIO to detect the jack. */
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_BTN_0 |
SND_JACK_BTN_1 | SND_JACK_BTN_2,
&priv->headset_jack,
headset_jack_pins,
ARRAY_SIZE(headset_jack_pins));
if (ret) {
dev_err(card->dev,
"Failed to set up headset pins: %d\n", ret);
return ret;
}
ret = snd_soc_jack_add_zones(&priv->headset_jack,
ARRAY_SIZE(headset_jack_zones),
headset_jack_zones);
if (ret) {
dev_err(card->dev,
"Failed to set up headset zones: %d\n", ret);
return ret;
}
headset_gpio[0].data = aif1_dai->component;
headset_gpio[0].desc = priv->gpio_headset_detect;
headset_gpio[1].data = aif1_dai->component;
headset_gpio[1].desc = priv->gpio_headset_key;
snd_jack_set_key(priv->headset_jack.jack,
SND_JACK_BTN_0, KEY_MEDIA);
snd_jack_set_key(priv->headset_jack.jack,
SND_JACK_BTN_1, KEY_VOLUMEUP);
snd_jack_set_key(priv->headset_jack.jack,
SND_JACK_BTN_2, KEY_VOLUMEDOWN);
ret = snd_soc_jack_add_gpios(&priv->headset_jack,
ARRAY_SIZE(headset_gpio),
headset_gpio);
if (ret)
dev_err(card->dev,
"Failed to set up headset jack GPIOs: %d\n",
ret);
return ret;
}
return 0;
}
static struct snd_soc_dai_driver midas_ext_dai[] = {
{
.name = "Voice call",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "Bluetooth",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
};
static const struct snd_soc_component_driver midas_component = {
.name = "midas-audio",
};
SND_SOC_DAILINK_DEFS(wm1811_hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(wm1811_voice,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif2")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(wm1811_bt,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif3")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link midas_dai[] = {
{
.name = "WM8994 AIF1",
.stream_name = "HiFi Primary",
.ops = &midas_aif1_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
SND_SOC_DAILINK_REG(wm1811_hifi),
}, {
.name = "WM1811 Voice",
.stream_name = "Voice call",
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm1811_voice),
}, {
.name = "WM1811 BT",
.stream_name = "Bluetooth",
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm1811_bt),
},
};
static struct snd_soc_card midas_card = {
.name = "Midas WM1811",
.owner = THIS_MODULE,
.dai_link = midas_dai,
.num_links = ARRAY_SIZE(midas_dai),
.controls = midas_controls,
.num_controls = ARRAY_SIZE(midas_controls),
.dapm_widgets = midas_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(midas_dapm_widgets),
.dapm_routes = midas_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(midas_dapm_routes),
.set_bias_level = midas_set_bias_level,
.late_probe = midas_late_probe,
};
static int midas_probe(struct platform_device *pdev)
{
struct device_node *cpu_dai_node = NULL, *codec_dai_node = NULL;
struct device_node *cpu = NULL, *codec = NULL;
struct snd_soc_card *card = &midas_card;
struct device *dev = &pdev->dev;
static struct snd_soc_dai_link *dai_link;
enum iio_chan_type channel_type;
u32 fourpole_threshold[2];
u32 button_threshold[3];
struct midas_priv *priv;
int ret, i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
snd_soc_card_set_drvdata(card, priv);
card->dev = dev;
priv->gpio_fm_sel = devm_gpiod_get_optional(dev, "fm-sel", GPIOD_OUT_HIGH);
if (IS_ERR(priv->gpio_fm_sel))
return dev_err_probe(dev, PTR_ERR(priv->gpio_fm_sel),
"Failed to get FM selection GPIO\n");
priv->gpio_lineout_sel = devm_gpiod_get_optional(dev, "lineout-sel",
GPIOD_OUT_HIGH);
if (IS_ERR(priv->gpio_lineout_sel))
return dev_err_probe(dev, PTR_ERR(priv->gpio_lineout_sel),
"Failed to get line out selection GPIO\n");
priv->gpio_headset_detect = devm_gpiod_get_optional(dev,
"headset-detect", GPIOD_IN);
if (IS_ERR(priv->gpio_headset_detect))
return dev_err_probe(dev, PTR_ERR(priv->gpio_headset_detect),
"Failed to get headset jack detect GPIO\n");
if (priv->gpio_headset_detect) {
priv->adc_headset_detect = devm_iio_channel_get(dev,
"headset-detect");
if (IS_ERR(priv->adc_headset_detect))
return dev_err_probe(dev,
PTR_ERR(priv->adc_headset_detect),
"Failed to get ADC channel\n");
ret = iio_get_channel_type(priv->adc_headset_detect,
&channel_type);
if (ret) {
dev_err(dev, "Failed to get ADC channel type\n");
return ret;
}
if (channel_type != IIO_VOLTAGE) {
dev_err(dev, "ADC channel is not voltage\n");
return -EINVAL;
}
priv->gpio_headset_key = devm_gpiod_get(dev, "headset-key",
GPIOD_IN);
if (IS_ERR(priv->gpio_headset_key))
return dev_err_probe(dev,
PTR_ERR(priv->gpio_headset_key),
"Failed to get headset key GPIO\n");
ret = of_property_read_u32_array(dev->of_node,
"samsung,headset-4pole-threshold-microvolt",
fourpole_threshold,
ARRAY_SIZE(fourpole_threshold));
if (ret) {
dev_err(dev, "Failed to get 4-pole jack detection threshold\n");
return ret;
}
if (fourpole_threshold[0] > fourpole_threshold[1]) {
dev_err(dev, "Invalid 4-pole jack detection threshold value\n");
return -EINVAL;
}
headset_jack_zones[0].max_mv = (fourpole_threshold[0]);
headset_jack_zones[1].min_mv = (fourpole_threshold[0] + 1);
headset_jack_zones[1].max_mv = (fourpole_threshold[1]);
headset_jack_zones[2].min_mv = (fourpole_threshold[1] + 1);
ret = of_property_read_u32_array(dev->of_node,
"samsung,headset-button-threshold-microvolt",
button_threshold,
ARRAY_SIZE(button_threshold));
if (ret) {
dev_err(dev, "Failed to get headset button detection threshold\n");
return ret;
}
if (button_threshold[0] > button_threshold[1] ||
button_threshold[1] > button_threshold[2]) {
dev_err(dev, "Invalid headset button detection threshold value\n");
return -EINVAL;
}
for (i = 0; i < 3; i++) {
if (i != 0 && button_threshold[i] <= 0) {
dev_err(dev, "Invalid headset button detection threshold value\n");
return -EINVAL;
}
headset_key_zones[i].min_mv = button_threshold[i];
if (i == 2)
headset_key_zones[i].max_mv = UINT_MAX;
else
headset_key_zones[i].max_mv = \
(button_threshold[i+1] - 1);
}
}
ret = snd_soc_of_parse_card_name(card, "model");
if (ret < 0) {
dev_err(dev, "Card name is not specified\n");
return ret;
}
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
if (ret < 0) {
/* Backwards compatible way */
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
if (ret < 0) {
dev_err(dev, "Audio routing invalid/unspecified\n");
return ret;
}
}
cpu = of_get_child_by_name(dev->of_node, "cpu");
if (!cpu)
return -EINVAL;
codec = of_get_child_by_name(dev->of_node, "codec");
if (!codec) {
of_node_put(cpu);
return -EINVAL;
}
cpu_dai_node = of_parse_phandle(cpu, "sound-dai", 0);
of_node_put(cpu);
if (!cpu_dai_node) {
dev_err(dev, "parsing cpu/sound-dai failed\n");
of_node_put(codec);
return -EINVAL;
}
codec_dai_node = of_parse_phandle(codec, "sound-dai", 0);
of_node_put(codec);
if (!codec_dai_node) {
dev_err(dev, "audio-codec property invalid/missing\n");
ret = -EINVAL;
goto put_cpu_dai_node;
}
for_each_card_prelinks(card, i, dai_link) {
dai_link->codecs->of_node = codec_dai_node;
dai_link->cpus->of_node = cpu_dai_node;
dai_link->platforms->of_node = cpu_dai_node;
}
ret = devm_snd_soc_register_component(dev, &midas_component,
midas_ext_dai, ARRAY_SIZE(midas_ext_dai));
if (ret < 0) {
dev_err(dev, "Failed to register component: %d\n", ret);
goto put_codec_dai_node;
}
ret = devm_snd_soc_register_card(dev, card);
if (ret < 0) {
dev_err(dev, "Failed to register card: %d\n", ret);
goto put_codec_dai_node;
}
return 0;
put_codec_dai_node:
of_node_put(codec_dai_node);
put_cpu_dai_node:
of_node_put(cpu_dai_node);
return ret;
}
static const struct of_device_id midas_of_match[] = {
{ .compatible = "samsung,midas-audio" },
{ },
};
MODULE_DEVICE_TABLE(of, midas_of_match);
static struct platform_driver midas_driver = {
.driver = {
.name = "midas-audio",
.of_match_table = midas_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = midas_probe,
};
module_platform_driver(midas_driver);
MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
MODULE_DESCRIPTION("ASoC support for Midas");
MODULE_LICENSE("GPL v2");
+340
View File
@@ -0,0 +1,340 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2017 Samsung Electronics Co., Ltd.
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "i2s.h"
#include "i2s-regs.h"
struct odroid_priv {
struct snd_soc_card card;
struct clk *clk_i2s_bus;
struct clk *sclk_i2s;
/* Spinlock protecting fields below */
spinlock_t lock;
unsigned int be_sample_rate;
bool be_active;
};
static int odroid_card_fe_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2);
return 0;
}
static int odroid_card_fe_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 odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
guard(spinlock_irqsave)(&priv->lock);
if (priv->be_active && priv->be_sample_rate != params_rate(params))
return -EINVAL;
return 0;
}
static const struct snd_soc_ops odroid_card_fe_ops = {
.startup = odroid_card_fe_startup,
.hw_params = odroid_card_fe_hw_params,
};
static int odroid_card_be_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 odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
unsigned int pll_freq, rclk_freq, rfs;
int ret;
switch (params_rate(params)) {
case 64000:
pll_freq = 196608001U;
rfs = 384;
break;
case 44100:
case 88200:
pll_freq = 180633609U;
rfs = 512;
break;
case 32000:
case 48000:
case 96000:
pll_freq = 196608001U;
rfs = 512;
break;
default:
return -EINVAL;
}
ret = clk_set_rate(priv->clk_i2s_bus, pll_freq / 2 + 1);
if (ret < 0)
return ret;
/*
* We add 2 to the rclk_freq value in order to avoid too low clock
* frequency values due to the EPLL output frequency not being exact
* multiple of the audio sampling rate.
*/
rclk_freq = params_rate(params) * rfs + 2;
ret = clk_set_rate(priv->sclk_i2s, rclk_freq);
if (ret < 0)
return ret;
if (rtd->dai_link->num_codecs > 1) {
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 1);
ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
}
scoped_guard(spinlock_irqsave, &priv->lock)
priv->be_sample_rate = params_rate(params);
return 0;
}
static int odroid_card_be_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
guard(spinlock_irqsave)(&priv->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
priv->be_active = true;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
priv->be_active = false;
break;
}
return 0;
}
static const struct snd_soc_ops odroid_card_be_ops = {
.hw_params = odroid_card_be_hw_params,
.trigger = odroid_card_be_trigger,
};
/* DAPM routes for backward compatibility with old DTS */
static const struct snd_soc_dapm_route odroid_dapm_routes[] = {
{ "I2S Playback", NULL, "Mixer DAI TX" },
{ "HiFi Playback", NULL, "Mixer DAI TX" },
};
SND_SOC_DAILINK_DEFS(primary,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_DUMMY()),
DAILINK_COMP_ARRAY(COMP_PLATFORM("3830000.i2s")));
SND_SOC_DAILINK_DEFS(mixer,
DAILINK_COMP_ARRAY(COMP_DUMMY()),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(secondary,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_DUMMY()),
DAILINK_COMP_ARRAY(COMP_PLATFORM("3830000.i2s-sec")));
static struct snd_soc_dai_link odroid_card_dais[] = {
{
/* Primary FE <-> BE link */
.ops = &odroid_card_fe_ops,
.name = "Primary",
.stream_name = "Primary",
.dynamic = 1,
.playback_only = 1,
SND_SOC_DAILINK_REG(primary),
}, {
/* BE <-> CODECs link */
.name = "I2S Mixer",
.ops = &odroid_card_be_ops,
.no_pcm = 1,
.playback_only = 1,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
SND_SOC_DAILINK_REG(mixer),
}, {
/* Secondary FE <-> BE link */
.ops = &odroid_card_fe_ops,
.name = "Secondary",
.stream_name = "Secondary",
.dynamic = 1,
.playback_only = 1,
SND_SOC_DAILINK_REG(secondary),
}
};
static int odroid_audio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *cpu_dai = NULL;
struct device_node *cpu, *codec;
struct odroid_priv *priv;
struct snd_soc_card *card;
struct snd_soc_dai_link *link, *codec_link;
int num_pcms, ret, i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card = &priv->card;
card->dev = dev;
card->owner = THIS_MODULE;
card->fully_routed = true;
spin_lock_init(&priv->lock);
snd_soc_card_set_drvdata(card, priv);
ret = snd_soc_of_parse_card_name(card, "model");
if (ret < 0)
return ret;
if (of_property_present(dev->of_node, "samsung,audio-widgets")) {
ret = snd_soc_of_parse_audio_simple_widgets(card,
"samsung,audio-widgets");
if (ret < 0)
return ret;
}
ret = 0;
if (of_property_present(dev->of_node, "audio-routing"))
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
else if (of_property_present(dev->of_node, "samsung,audio-routing"))
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
if (ret < 0)
return ret;
card->dai_link = odroid_card_dais;
card->num_links = ARRAY_SIZE(odroid_card_dais);
cpu = of_get_child_by_name(dev->of_node, "cpu");
codec = of_get_child_by_name(dev->of_node, "codec");
link = card->dai_link;
codec_link = &card->dai_link[1];
/*
* For backwards compatibility create the secondary CPU DAI link only
* if there are 2 CPU DAI entries in the cpu sound-dai property in DT.
* Also add required DAPM routes not available in old DTS.
*/
num_pcms = of_count_phandle_with_args(cpu, "sound-dai",
"#sound-dai-cells");
if (num_pcms == 1) {
card->dapm_routes = odroid_dapm_routes;
card->num_dapm_routes = ARRAY_SIZE(odroid_dapm_routes);
card->num_links--;
}
for (i = 0; i < num_pcms; i++, link += 2) {
ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, i);
if (ret < 0)
break;
}
if (ret == 0) {
cpu_dai = of_parse_phandle(cpu, "sound-dai", 0);
if (!cpu_dai)
ret = -EINVAL;
}
of_node_put(cpu);
if (ret < 0)
goto err_put_node;
ret = snd_soc_of_get_dai_link_codecs(dev, codec, codec_link);
if (ret < 0)
goto err_put_cpu_dai;
/* Set capture capability only for boards with the MAX98090 CODEC */
if (codec_link->num_codecs > 1) {
card->dai_link[0].playback_only = 0;
card->dai_link[1].playback_only = 0;
}
priv->sclk_i2s = of_clk_get_by_name(cpu_dai, "i2s_opclk1");
if (IS_ERR(priv->sclk_i2s)) {
ret = PTR_ERR(priv->sclk_i2s);
goto err_put_cpu_dai;
}
priv->clk_i2s_bus = of_clk_get_by_name(cpu_dai, "iis");
if (IS_ERR(priv->clk_i2s_bus)) {
ret = PTR_ERR(priv->clk_i2s_bus);
goto err_put_sclk;
}
ret = devm_snd_soc_register_card(dev, card);
if (ret < 0) {
dev_err_probe(dev, ret, "snd_soc_register_card() failed\n");
goto err_put_clk_i2s;
}
of_node_put(cpu_dai);
of_node_put(codec);
return 0;
err_put_clk_i2s:
clk_put(priv->clk_i2s_bus);
err_put_sclk:
clk_put(priv->sclk_i2s);
err_put_cpu_dai:
of_node_put(cpu_dai);
snd_soc_of_put_dai_link_codecs(codec_link);
err_put_node:
of_node_put(codec);
return ret;
}
static void odroid_audio_remove(struct platform_device *pdev)
{
struct odroid_priv *priv = platform_get_drvdata(pdev);
snd_soc_of_put_dai_link_codecs(&priv->card.dai_link[1]);
clk_put(priv->sclk_i2s);
clk_put(priv->clk_i2s_bus);
}
static const struct of_device_id odroid_audio_of_match[] = {
{ .compatible = "hardkernel,odroid-xu3-audio" },
{ .compatible = "hardkernel,odroid-xu4-audio" },
{ .compatible = "samsung,odroid-xu3-audio" },
{ .compatible = "samsung,odroid-xu4-audio" },
{ },
};
MODULE_DEVICE_TABLE(of, odroid_audio_of_match);
static struct platform_driver odroid_audio_driver = {
.driver = {
.name = "odroid-audio",
.of_match_table = odroid_audio_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = odroid_audio_probe,
.remove = odroid_audio_remove,
};
module_platform_driver(odroid_audio_driver);
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
MODULE_DESCRIPTION("Odroid XU3/XU4 audio support");
MODULE_LICENSE("GPL v2");
+586
View File
@@ -0,0 +1,586 @@
// SPDX-License-Identifier: GPL-2.0
//
// ALSA SoC Audio Layer - S3C PCM-Controller driver
//
// Copyright (c) 2009 Samsung Electronics Co. Ltd
// Author: Jaswinder Singh <jassisinghbrar@gmail.com>
// based upon I2S drivers by Ben Dooks.
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/platform_data/asoc-s3c.h>
#include "dma.h"
#include "pcm.h"
/*Register Offsets */
#define S3C_PCM_CTL 0x00
#define S3C_PCM_CLKCTL 0x04
#define S3C_PCM_TXFIFO 0x08
#define S3C_PCM_RXFIFO 0x0C
#define S3C_PCM_IRQCTL 0x10
#define S3C_PCM_IRQSTAT 0x14
#define S3C_PCM_FIFOSTAT 0x18
#define S3C_PCM_CLRINT 0x20
/* PCM_CTL Bit-Fields */
#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f
#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13
#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f
#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7
#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6)
#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5)
#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4)
#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3)
#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2)
#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1)
#define S3C_PCM_CTL_ENABLE (0x1 << 0)
/* PCM_CLKCTL Bit-Fields */
#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19)
#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18)
#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff
#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff
#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9
#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0
/* PCM_TXFIFO Bit-Fields */
#define S3C_PCM_TXFIFO_DVALID (0x1 << 16)
#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0)
/* PCM_RXFIFO Bit-Fields */
#define S3C_PCM_RXFIFO_DVALID (0x1 << 16)
#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0)
/* PCM_IRQCTL Bit-Fields */
#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14)
#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12)
#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11)
#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10)
#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9)
#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8)
#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7)
#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6)
#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5)
#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4)
#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3)
#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2)
#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1)
#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0)
/* PCM_IRQSTAT Bit-Fields */
#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13)
#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12)
#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11)
#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10)
#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9)
#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8)
#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7)
#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6)
#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5)
#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4)
#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3)
#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2)
#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1)
#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0)
/* PCM_FIFOSTAT Bit-Fields */
#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14)
#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13)
#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12)
#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11)
#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10)
#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4)
#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3)
#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2)
#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1)
#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0)
/**
* struct s3c_pcm_info - S3C PCM Controller information
* @lock: Spin lock
* @dev: The parent device passed to use from the probe.
* @regs: The pointer to the device register block.
* @sclk_per_fs: number of sclk per frame sync
* @idleclk: Whether to keep PCMSCLK enabled even when idle (no active xfer)
* @pclk: the PCLK_PCM (pcm) clock pointer
* @cclk: the SCLK_AUDIO (audio-bus) clock pointer
* @dma_playback: DMA information for playback channel.
* @dma_capture: DMA information for capture channel.
*/
struct s3c_pcm_info {
spinlock_t lock;
struct device *dev;
void __iomem *regs;
unsigned int sclk_per_fs;
/* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
unsigned int idleclk;
struct clk *pclk;
struct clk *cclk;
struct snd_dmaengine_dai_dma_data *dma_playback;
struct snd_dmaengine_dai_dma_data *dma_capture;
};
static struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_out[] = {
[0] = {
.addr_width = 4,
},
[1] = {
.addr_width = 4,
},
};
static struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_in[] = {
[0] = {
.addr_width = 4,
},
[1] = {
.addr_width = 4,
},
};
static struct s3c_pcm_info s3c_pcm[2];
static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on)
{
void __iomem *regs = pcm->regs;
u32 ctl, clkctl;
clkctl = readl(regs + S3C_PCM_CLKCTL);
ctl = readl(regs + S3C_PCM_CTL);
ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK
<< S3C_PCM_CTL_TXDIPSTICK_SHIFT);
if (on) {
ctl |= S3C_PCM_CTL_TXDMA_EN;
ctl |= S3C_PCM_CTL_TXFIFO_EN;
ctl |= S3C_PCM_CTL_ENABLE;
ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT);
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
} else {
ctl &= ~S3C_PCM_CTL_TXDMA_EN;
ctl &= ~S3C_PCM_CTL_TXFIFO_EN;
if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) {
ctl &= ~S3C_PCM_CTL_ENABLE;
if (!pcm->idleclk)
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
}
}
writel(clkctl, regs + S3C_PCM_CLKCTL);
writel(ctl, regs + S3C_PCM_CTL);
}
static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on)
{
void __iomem *regs = pcm->regs;
u32 ctl, clkctl;
ctl = readl(regs + S3C_PCM_CTL);
clkctl = readl(regs + S3C_PCM_CLKCTL);
ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK
<< S3C_PCM_CTL_RXDIPSTICK_SHIFT);
if (on) {
ctl |= S3C_PCM_CTL_RXDMA_EN;
ctl |= S3C_PCM_CTL_RXFIFO_EN;
ctl |= S3C_PCM_CTL_ENABLE;
ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT);
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
} else {
ctl &= ~S3C_PCM_CTL_RXDMA_EN;
ctl &= ~S3C_PCM_CTL_RXFIFO_EN;
if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) {
ctl &= ~S3C_PCM_CTL_ENABLE;
if (!pcm->idleclk)
clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
}
}
writel(clkctl, regs + S3C_PCM_CLKCTL);
writel(ctl, regs + S3C_PCM_CTL);
}
static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
dev_dbg(pcm->dev, "Entered %s\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
scoped_guard(spinlock_irqsave, &pcm->lock) {
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c_pcm_snd_rxctrl(pcm, 1);
else
s3c_pcm_snd_txctrl(pcm, 1);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
scoped_guard(spinlock_irqsave, &pcm->lock) {
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c_pcm_snd_rxctrl(pcm, 0);
else
s3c_pcm_snd_txctrl(pcm, 0);
}
break;
default:
return -EINVAL;
}
return 0;
}
static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *socdai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
void __iomem *regs = pcm->regs;
struct clk *clk;
int sclk_div, sync_div;
u32 clkctl;
dev_dbg(pcm->dev, "Entered %s\n", __func__);
/* Strictly check for sample size */
switch (params_width(params)) {
case 16:
break;
default:
return -EINVAL;
}
scoped_guard(spinlock_irqsave, &pcm->lock) {
/* Get hold of the PCMSOURCE_CLK */
clkctl = readl(regs + S3C_PCM_CLKCTL);
if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK)
clk = pcm->pclk;
else
clk = pcm->cclk;
/* Set the SCLK divider */
sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs /
params_rate(params) / 2 - 1;
clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK
<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK)
<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
/* Set the SYNC divider */
sync_div = pcm->sclk_per_fs - 1;
clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK
<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK)
<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
writel(clkctl, regs + S3C_PCM_CLKCTL);
}
dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
clk_get_rate(clk), pcm->sclk_per_fs,
sclk_div, sync_div);
return 0;
}
static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
void __iomem *regs = pcm->regs;
u32 ctl;
dev_dbg(pcm->dev, "Entered %s\n", __func__);
guard(spinlock_irqsave)(&pcm->lock);
ctl = readl(regs + S3C_PCM_CTL);
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_NF:
/* Nothing to do, IB_NF by default */
break;
default:
dev_err(pcm->dev, "Unsupported clock inversion!\n");
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
/* Nothing to do, Master by default */
break;
default:
dev_err(pcm->dev, "Unsupported master/slave format!\n");
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
case SND_SOC_DAIFMT_CONT:
pcm->idleclk = 1;
break;
case SND_SOC_DAIFMT_GATED:
pcm->idleclk = 0;
break;
default:
dev_err(pcm->dev, "Invalid Clock gating request!\n");
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
break;
case SND_SOC_DAIFMT_DSP_B:
ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
break;
default:
dev_err(pcm->dev, "Unsupported data format!\n");
return -EINVAL;
}
writel(ctl, regs + S3C_PCM_CTL);
return 0;
}
static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
switch (div_id) {
case S3C_PCM_SCLK_PER_FS:
pcm->sclk_per_fs = div;
break;
default:
return -EINVAL;
}
return 0;
}
static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
void __iomem *regs = pcm->regs;
u32 clkctl = readl(regs + S3C_PCM_CLKCTL);
switch (clk_id) {
case S3C_PCM_CLKSRC_PCLK:
clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
break;
case S3C_PCM_CLKSRC_MUX:
clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
if (clk_get_rate(pcm->cclk) != freq)
clk_set_rate(pcm->cclk, freq);
break;
default:
return -EINVAL;
}
writel(clkctl, regs + S3C_PCM_CLKCTL);
return 0;
}
static int s3c_pcm_dai_probe(struct snd_soc_dai *dai)
{
struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, pcm->dma_playback, pcm->dma_capture);
return 0;
}
static const struct snd_soc_dai_ops s3c_pcm_dai_ops = {
.probe = s3c_pcm_dai_probe,
.set_sysclk = s3c_pcm_set_sysclk,
.set_clkdiv = s3c_pcm_set_clkdiv,
.trigger = s3c_pcm_trigger,
.hw_params = s3c_pcm_hw_params,
.set_fmt = s3c_pcm_set_fmt,
};
#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000
#define S3C_PCM_DAI_DECLARE \
.symmetric_rate = 1, \
.ops = &s3c_pcm_dai_ops, \
.playback = { \
.channels_min = 2, \
.channels_max = 2, \
.rates = S3C_PCM_RATES, \
.formats = SNDRV_PCM_FMTBIT_S16_LE, \
}, \
.capture = { \
.channels_min = 2, \
.channels_max = 2, \
.rates = S3C_PCM_RATES, \
.formats = SNDRV_PCM_FMTBIT_S16_LE, \
}
static struct snd_soc_dai_driver s3c_pcm_dai[] = {
[0] = {
.name = "samsung-pcm.0",
S3C_PCM_DAI_DECLARE,
},
[1] = {
.name = "samsung-pcm.1",
S3C_PCM_DAI_DECLARE,
},
};
static const struct snd_soc_component_driver s3c_pcm_component = {
.name = "s3c-pcm",
.legacy_dai_naming = 1,
};
static int s3c_pcm_dev_probe(struct platform_device *pdev)
{
struct s3c_pcm_info *pcm;
struct resource *mem_res;
struct s3c_audio_pdata *pcm_pdata;
dma_filter_fn filter;
int ret;
/* Check for valid device index */
if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) {
dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
return -EINVAL;
}
pcm_pdata = pdev->dev.platform_data;
if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure gpio\n");
return -EINVAL;
}
pcm = &s3c_pcm[pdev->id];
pcm->dev = &pdev->dev;
spin_lock_init(&pcm->lock);
/* Default is 128fs */
pcm->sclk_per_fs = 128;
pcm->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem_res);
if (IS_ERR(pcm->regs))
return PTR_ERR(pcm->regs);
pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus");
if (IS_ERR(pcm->cclk)) {
dev_err(&pdev->dev, "failed to get audio-bus clock\n");
return PTR_ERR(pcm->cclk);
}
ret = clk_prepare_enable(pcm->cclk);
if (ret)
return ret;
/* record our pcm structure for later use in the callbacks */
dev_set_drvdata(&pdev->dev, pcm);
pcm->pclk = devm_clk_get(&pdev->dev, "pcm");
if (IS_ERR(pcm->pclk)) {
dev_err(&pdev->dev, "failed to get pcm clock\n");
ret = PTR_ERR(pcm->pclk);
goto err_dis_cclk;
}
ret = clk_prepare_enable(pcm->pclk);
if (ret)
goto err_dis_cclk;
s3c_pcm_stereo_in[pdev->id].addr = mem_res->start + S3C_PCM_RXFIFO;
s3c_pcm_stereo_out[pdev->id].addr = mem_res->start + S3C_PCM_TXFIFO;
filter = NULL;
if (pcm_pdata) {
s3c_pcm_stereo_in[pdev->id].filter_data = pcm_pdata->dma_capture;
s3c_pcm_stereo_out[pdev->id].filter_data = pcm_pdata->dma_playback;
filter = pcm_pdata->dma_filter;
}
pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
NULL, NULL, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err_dis_pclk;
}
pm_runtime_enable(&pdev->dev);
ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component,
&s3c_pcm_dai[pdev->id], 1);
if (ret != 0) {
dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret);
goto err_dis_pm;
}
return 0;
err_dis_pm:
pm_runtime_disable(&pdev->dev);
err_dis_pclk:
clk_disable_unprepare(pcm->pclk);
err_dis_cclk:
clk_disable_unprepare(pcm->cclk);
return ret;
}
static void s3c_pcm_dev_remove(struct platform_device *pdev)
{
struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(pcm->cclk);
clk_disable_unprepare(pcm->pclk);
}
static struct platform_driver s3c_pcm_driver = {
.probe = s3c_pcm_dev_probe,
.remove = s3c_pcm_dev_remove,
.driver = {
.name = "samsung-pcm",
},
};
module_platform_driver(s3c_pcm_driver);
/* Module information */
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("S3C PCM Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-pcm");
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __S3C_PCM_H
#define __S3C_PCM_H __FILE__
#define S3C_PCM_CLKSRC_PCLK 0
#define S3C_PCM_CLKSRC_MUX 1
#define S3C_PCM_SCLK_PER_FS 0
#endif /* __S3C_PCM_H */
+219
View File
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-2.0+
//
// smdk_spdif.c - S/PDIF audio for SMDK
//
// Copyright (C) 2010 Samsung Electronics Co., Ltd.
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/soc.h>
#include "spdif.h"
/* Audio clock settings are belonged to board specific part. Every
* board can set audio source clock setting which is matched with H/W
* like this function-'set_audio_clock_heirachy'.
*/
static int set_audio_clock_heirachy(struct platform_device *pdev)
{
struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
int ret = 0;
fout_epll = clk_get(NULL, "fout_epll");
if (IS_ERR(fout_epll)) {
printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
__func__);
return -EINVAL;
}
mout_epll = clk_get(NULL, "mout_epll");
if (IS_ERR(mout_epll)) {
printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
__func__);
ret = -EINVAL;
goto out1;
}
sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
if (IS_ERR(sclk_audio0)) {
printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
__func__);
ret = -EINVAL;
goto out2;
}
sclk_spdif = clk_get(NULL, "sclk_spdif");
if (IS_ERR(sclk_spdif)) {
printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
__func__);
ret = -EINVAL;
goto out3;
}
/* Set audio clock hierarchy for S/PDIF */
clk_set_parent(mout_epll, fout_epll);
clk_set_parent(sclk_audio0, mout_epll);
clk_set_parent(sclk_spdif, sclk_audio0);
clk_put(sclk_spdif);
out3:
clk_put(sclk_audio0);
out2:
clk_put(mout_epll);
out1:
clk_put(fout_epll);
return ret;
}
/* We should haved to set clock directly on this part because of clock
* scheme of Samsudng SoCs did not support to set rates from abstrct
* clock of it's hierarchy.
*/
static int set_audio_clock_rate(unsigned long epll_rate,
unsigned long audio_rate)
{
struct clk *fout_epll, *sclk_spdif;
fout_epll = clk_get(NULL, "fout_epll");
if (IS_ERR(fout_epll)) {
printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
return -ENOENT;
}
clk_set_rate(fout_epll, epll_rate);
clk_put(fout_epll);
sclk_spdif = clk_get(NULL, "sclk_spdif");
if (IS_ERR(sclk_spdif)) {
printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
return -ENOENT;
}
clk_set_rate(sclk_spdif, audio_rate);
clk_put(sclk_spdif);
return 0;
}
static int smdk_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 *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
unsigned long pll_out, rclk_rate;
int ret, ratio;
switch (params_rate(params)) {
case 44100:
pll_out = 45158400;
break;
case 32000:
case 48000:
case 96000:
pll_out = 49152000;
break;
default:
return -EINVAL;
}
/* Setting ratio to 512fs helps to use S/PDIF with HDMI without
* modify S/PDIF ASoC machine driver.
*/
ratio = 512;
rclk_rate = params_rate(params) * ratio;
/* Set audio source clock rates */
ret = set_audio_clock_rate(pll_out, rclk_rate);
if (ret < 0)
return ret;
/* Set S/PDIF uses internal source clock */
ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
rclk_rate, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return ret;
}
static const struct snd_soc_ops smdk_spdif_ops = {
.hw_params = smdk_hw_params,
};
SND_SOC_DAILINK_DEFS(spdif,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-spdif")),
DAILINK_COMP_ARRAY(COMP_CODEC("spdif-dit", "dit-hifi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-spdif")));
static struct snd_soc_dai_link smdk_dai = {
.name = "S/PDIF",
.stream_name = "S/PDIF PCM Playback",
.ops = &smdk_spdif_ops,
SND_SOC_DAILINK_REG(spdif),
};
static struct snd_soc_card smdk = {
.name = "SMDK-S/PDIF",
.owner = THIS_MODULE,
.dai_link = &smdk_dai,
.num_links = 1,
};
static struct platform_device *smdk_snd_spdif_dit_device;
static struct platform_device *smdk_snd_spdif_device;
static int __init smdk_init(void)
{
int ret;
smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
if (!smdk_snd_spdif_dit_device)
return -ENOMEM;
ret = platform_device_add(smdk_snd_spdif_dit_device);
if (ret)
goto err1;
smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
if (!smdk_snd_spdif_device) {
ret = -ENOMEM;
goto err2;
}
platform_set_drvdata(smdk_snd_spdif_device, &smdk);
ret = platform_device_add(smdk_snd_spdif_device);
if (ret)
goto err3;
/* Set audio clock hierarchy manually */
ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
if (ret)
goto err4;
return 0;
err4:
platform_device_del(smdk_snd_spdif_device);
err3:
platform_device_put(smdk_snd_spdif_device);
err2:
platform_device_del(smdk_snd_spdif_dit_device);
err1:
platform_device_put(smdk_snd_spdif_dit_device);
return ret;
}
static void __exit smdk_exit(void)
{
platform_device_unregister(smdk_snd_spdif_device);
platform_device_unregister(smdk_snd_spdif_dit_device);
}
module_init(smdk_init);
module_exit(smdk_exit);
MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
MODULE_LICENSE("GPL");
+179
View File
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: GPL-2.0+
#include "../codecs/wm8994.h"
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/module.h>
#include <linux/of.h>
/*
* Default CFG switch settings to use this driver:
* SMDKV310: CFG5-1000, CFG7-111111
*/
/*
* Configure audio route as :-
* $ amixer sset 'DAC1' on,on
* $ amixer sset 'Right Headphone Mux' 'DAC'
* $ amixer sset 'Left Headphone Mux' 'DAC'
* $ amixer sset 'DAC1R Mixer AIF1.1' on
* $ amixer sset 'DAC1L Mixer AIF1.1' on
* $ amixer sset 'IN2L' on
* $ amixer sset 'IN2L PGA IN2LN' on
* $ amixer sset 'MIXINL IN2L' on
* $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
* $ amixer sset 'IN2R' on
* $ amixer sset 'IN2R PGA IN2RN' on
* $ amixer sset 'MIXINR IN2R' on
* $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
*/
/* SMDK has a 16.934MHZ crystal attached to WM8994 */
#define SMDK_WM8994_FREQ 16934000
static int smdk_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);
unsigned int pll_out;
int ret;
/* AIF1CLK should be >=3MHz for optimal performance */
if (params_width(params) == 24)
pll_out = params_rate(params) * 384;
else if (params_rate(params) == 8000 || params_rate(params) == 11025)
pll_out = params_rate(params) * 512;
else
pll_out = params_rate(params) * 256;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
SMDK_WM8994_FREQ, pll_out);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
pll_out, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
/*
* SMDK WM8994 DAI operations.
*/
static const struct snd_soc_ops smdk_ops = {
.hw_params = smdk_hw_params,
};
static int smdk_wm8994_init_paiftx(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(rtd->card);
/* Other pins NC */
snd_soc_dapm_disable_pin(dapm, "HPOUT2P");
snd_soc_dapm_disable_pin(dapm, "HPOUT2N");
snd_soc_dapm_disable_pin(dapm, "SPKOUTLN");
snd_soc_dapm_disable_pin(dapm, "SPKOUTLP");
snd_soc_dapm_disable_pin(dapm, "SPKOUTRP");
snd_soc_dapm_disable_pin(dapm, "SPKOUTRN");
snd_soc_dapm_disable_pin(dapm, "LINEOUT1N");
snd_soc_dapm_disable_pin(dapm, "LINEOUT1P");
snd_soc_dapm_disable_pin(dapm, "LINEOUT2N");
snd_soc_dapm_disable_pin(dapm, "LINEOUT2P");
snd_soc_dapm_disable_pin(dapm, "IN1LP");
snd_soc_dapm_disable_pin(dapm, "IN2LP:VXRN");
snd_soc_dapm_disable_pin(dapm, "IN1RP");
snd_soc_dapm_disable_pin(dapm, "IN2RP:VXRP");
return 0;
}
SND_SOC_DAILINK_DEFS(aif1,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(fifo_tx,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s-sec")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s-sec")));
static struct snd_soc_dai_link smdk_dai[] = {
{ /* Primary DAI i/f */
.name = "WM8994 AIF1",
.stream_name = "Pri_Dai",
.init = smdk_wm8994_init_paiftx,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ops = &smdk_ops,
SND_SOC_DAILINK_REG(aif1),
}, { /* Sec_Fifo Playback i/f */
.name = "Sec_FIFO TX",
.stream_name = "Sec_Dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ops = &smdk_ops,
SND_SOC_DAILINK_REG(fifo_tx),
},
};
static struct snd_soc_card smdk = {
.name = "SMDK-I2S",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = ARRAY_SIZE(smdk_dai),
};
static const struct of_device_id samsung_wm8994_of_match[] = {
{ .compatible = "samsung,smdk-wm8994" },
{},
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
static int smdk_audio_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &smdk;
card->dev = &pdev->dev;
if (np) {
smdk_dai[0].cpus->dai_name = NULL;
smdk_dai[0].cpus->of_node = of_parse_phandle(np,
"samsung,i2s-controller", 0);
if (!smdk_dai[0].cpus->of_node) {
dev_err(&pdev->dev,
"Property 'samsung,i2s-controller' missing or invalid\n");
ret = -EINVAL;
return ret;
}
smdk_dai[0].platforms->name = NULL;
smdk_dai[0].platforms->of_node = smdk_dai[0].cpus->of_node;
}
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
return ret;
}
static struct platform_driver smdk_audio_driver = {
.driver = {
.name = "smdk-audio-wm8994",
.of_match_table = samsung_wm8994_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = smdk_audio_probe,
};
module_platform_driver(smdk_audio_driver);
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:smdk-audio-wm8994");
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright (c) 2011 Samsung Electronics Co., Ltd
// http://www.samsung.com
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "../codecs/wm8994.h"
#include "pcm.h"
/*
* Board Settings:
* o '1' means 'ON'
* o '0' means 'OFF'
* o 'X' means 'Don't care'
*
* SMDKC210, SMDKV310: CFG3- 1001, CFG5-1000, CFG7-111111
*/
/*
* Configure audio route as :-
* $ amixer sset 'DAC1' on,on
* $ amixer sset 'Right Headphone Mux' 'DAC'
* $ amixer sset 'Left Headphone Mux' 'DAC'
* $ amixer sset 'DAC1R Mixer AIF1.1' on
* $ amixer sset 'DAC1L Mixer AIF1.1' on
* $ amixer sset 'IN2L' on
* $ amixer sset 'IN2L PGA IN2LN' on
* $ amixer sset 'MIXINL IN2L' on
* $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
* $ amixer sset 'IN2R' on
* $ amixer sset 'IN2R PGA IN2RN' on
* $ amixer sset 'MIXINR IN2R' on
* $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
*/
/* SMDK has a 16.9344MHZ crystal attached to WM8994 */
#define SMDK_WM8994_FREQ 16934400
static int smdk_wm8994_pcm_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);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
unsigned long mclk_freq;
int rfs, ret;
switch(params_rate(params)) {
case 8000:
rfs = 512;
break;
default:
dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n",
__func__, __LINE__, params_rate(params));
return -EINVAL;
}
mclk_freq = params_rate(params) * rfs;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
SMDK_WM8994_FREQ, mclk_freq);
if (ret < 0)
return ret;
/* Set PCM source clock on CPU */
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Set SCLK_DIV for making bclk */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_soc_ops smdk_wm8994_pcm_ops = {
.hw_params = smdk_wm8994_pcm_hw_params,
};
SND_SOC_DAILINK_DEFS(paif_pcm,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-pcm.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-pcm.0")));
static struct snd_soc_dai_link smdk_dai[] = {
{
.name = "WM8994 PAIF PCM",
.stream_name = "Primary PCM",
.dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBC_CFC,
.ops = &smdk_wm8994_pcm_ops,
SND_SOC_DAILINK_REG(paif_pcm),
},
};
static struct snd_soc_card smdk_pcm = {
.name = "SMDK-PCM",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 1,
};
static int snd_smdk_probe(struct platform_device *pdev)
{
int ret = 0;
smdk_pcm.dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, &smdk_pcm);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
return ret;
}
static struct platform_driver snd_smdk_driver = {
.driver = {
.name = "samsung-smdk-pcm",
},
.probe = snd_smdk_probe,
};
module_platform_driver(snd_smdk_driver);
MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM");
MODULE_LICENSE("GPL");
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0
//
// ASoC machine driver for Snow boards
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "i2s.h"
#define FIN_PLL_RATE 24000000
SND_SOC_DAILINK_DEFS(links,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
struct snow_priv {
struct snd_soc_dai_link dai_link;
struct clk *clk_i2s_bus;
};
static int snow_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
static const unsigned int pll_rate[] = {
73728000U, 67737602U, 49152000U, 45158401U, 32768001U
};
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snow_priv *priv = snd_soc_card_get_drvdata(rtd->card);
int bfs, psr, rfs, bitwidth;
unsigned long int rclk;
long int freq = -EINVAL;
int ret, i;
bitwidth = snd_pcm_format_width(params_format(params));
if (bitwidth < 0) {
dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth);
return bitwidth;
}
if (bitwidth != 16 && bitwidth != 24) {
dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth);
return -EINVAL;
}
bfs = 2 * bitwidth;
switch (params_rate(params)) {
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
case 88200:
case 96000:
rfs = 8 * bfs;
break;
case 64000:
rfs = 384;
break;
case 8000:
case 11025:
case 12000:
rfs = 16 * bfs;
break;
default:
return -EINVAL;
}
rclk = params_rate(params) * rfs;
for (psr = 8; psr > 0; psr /= 2) {
for (i = 0; i < ARRAY_SIZE(pll_rate); i++) {
if ((pll_rate[i] - rclk * psr) <= 2) {
freq = pll_rate[i];
break;
}
}
}
if (freq < 0) {
dev_err(rtd->card->dev, "Unsupported RCLK rate: %lu\n", rclk);
return -EINVAL;
}
ret = clk_set_rate(priv->clk_i2s_bus, freq);
if (ret < 0) {
dev_err(rtd->card->dev, "I2S bus clock rate set failed\n");
return ret;
}
return 0;
}
static const struct snd_soc_ops snow_card_ops = {
.hw_params = snow_card_hw_params,
};
static int snow_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
/* In the multi-codec case codec_dais 0 is MAX98095 and 1 is HDMI. */
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
/* Set the MCLK rate for the codec */
return snd_soc_dai_set_sysclk(codec_dai, 0,
FIN_PLL_RATE, SND_SOC_CLOCK_IN);
}
static struct snd_soc_card snow_snd = {
.name = "Snow-I2S",
.owner = THIS_MODULE,
.late_probe = snow_late_probe,
};
static int snow_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct snd_soc_card *card = &snow_snd;
struct device_node *cpu, *codec;
struct snd_soc_dai_link *link;
struct snow_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
link = &priv->dai_link;
link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC;
link->name = "Primary";
link->stream_name = link->name;
link->cpus = links_cpus;
link->num_cpus = ARRAY_SIZE(links_cpus);
link->codecs = links_codecs;
link->num_codecs = ARRAY_SIZE(links_codecs);
link->platforms = links_platforms;
link->num_platforms = ARRAY_SIZE(links_platforms);
card->dai_link = link;
card->num_links = 1;
card->dev = dev;
/* Try new DT bindings with HDMI support first. */
cpu = of_get_child_by_name(dev->of_node, "cpu");
if (cpu) {
link->ops = &snow_card_ops;
link->cpus->of_node = of_parse_phandle(cpu, "sound-dai", 0);
of_node_put(cpu);
if (!link->cpus->of_node) {
dev_err(dev, "Failed parsing cpu/sound-dai property\n");
return -EINVAL;
}
codec = of_get_child_by_name(dev->of_node, "codec");
ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
of_node_put(codec);
if (ret < 0) {
of_node_put(link->cpus->of_node);
dev_err(dev, "Failed parsing codec node\n");
return ret;
}
priv->clk_i2s_bus = of_clk_get_by_name(link->cpus->of_node,
"i2s_opclk0");
if (IS_ERR(priv->clk_i2s_bus)) {
snd_soc_of_put_dai_link_codecs(link);
of_node_put(link->cpus->of_node);
return PTR_ERR(priv->clk_i2s_bus);
}
} else {
link->codecs->dai_name = "HiFi";
link->cpus->of_node = of_parse_phandle(dev->of_node,
"samsung,i2s-controller", 0);
if (!link->cpus->of_node) {
dev_err(dev, "i2s-controller property parse error\n");
return -EINVAL;
}
link->codecs->of_node = of_parse_phandle(dev->of_node,
"samsung,audio-codec", 0);
if (!link->codecs->of_node) {
of_node_put(link->cpus->of_node);
dev_err(dev, "audio-codec property parse error\n");
return -EINVAL;
}
}
link->platforms->of_node = link->cpus->of_node;
/* Update card-name if provided through DT, else use default name */
snd_soc_of_parse_card_name(card, "samsung,model");
snd_soc_card_set_drvdata(card, priv);
ret = devm_snd_soc_register_card(dev, card);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card failed\n");
return 0;
}
static void snow_remove(struct platform_device *pdev)
{
struct snow_priv *priv = platform_get_drvdata(pdev);
struct snd_soc_dai_link *link = &priv->dai_link;
of_node_put(link->cpus->of_node);
of_node_put(link->codecs->of_node);
snd_soc_of_put_dai_link_codecs(link);
clk_put(priv->clk_i2s_bus);
}
static const struct of_device_id snow_of_match[] = {
{ .compatible = "google,snow-audio-max98090", },
{ .compatible = "google,snow-audio-max98091", },
{ .compatible = "google,snow-audio-max98095", },
{},
};
MODULE_DEVICE_TABLE(of, snow_of_match);
static struct platform_driver snow_driver = {
.driver = {
.name = "snow-audio",
.pm = &snd_soc_pm_ops,
.of_match_table = snow_of_match,
},
.probe = snow_probe,
.remove = snow_remove,
};
module_platform_driver(snow_driver);
MODULE_DESCRIPTION("ALSA SoC Audio machine driver for Snow");
MODULE_LICENSE("GPL");
+462
View File
@@ -0,0 +1,462 @@
// SPDX-License-Identifier: GPL-2.0
//
// ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
//
// Copyright (c) 2010 Samsung Electronics Co. Ltd
// http://www.samsung.com/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/platform_data/asoc-s3c.h>
#include "dma.h"
#include "spdif.h"
/* Registers */
#define CLKCON 0x00
#define CON 0x04
#define BSTAS 0x08
#define CSTAS 0x0C
#define DATA_OUTBUF 0x10
#define DCNT 0x14
#define BSTAS_S 0x18
#define DCNT_S 0x1C
#define CLKCTL_MASK 0x7
#define CLKCTL_MCLK_EXT (0x1 << 2)
#define CLKCTL_PWR_ON (0x1 << 0)
#define CON_MASK 0x3ffffff
#define CON_FIFO_TH_SHIFT 19
#define CON_FIFO_TH_MASK (0x7 << 19)
#define CON_USERDATA_23RDBIT (0x1 << 12)
#define CON_SW_RESET (0x1 << 5)
#define CON_MCLKDIV_MASK (0x3 << 3)
#define CON_MCLKDIV_256FS (0x0 << 3)
#define CON_MCLKDIV_384FS (0x1 << 3)
#define CON_MCLKDIV_512FS (0x2 << 3)
#define CON_PCM_MASK (0x3 << 1)
#define CON_PCM_16BIT (0x0 << 1)
#define CON_PCM_20BIT (0x1 << 1)
#define CON_PCM_24BIT (0x2 << 1)
#define CON_PCM_DATA (0x1 << 0)
#define CSTAS_MASK 0x3fffffff
#define CSTAS_SAMP_FREQ_MASK (0xF << 24)
#define CSTAS_SAMP_FREQ_44 (0x0 << 24)
#define CSTAS_SAMP_FREQ_48 (0x2 << 24)
#define CSTAS_SAMP_FREQ_32 (0x3 << 24)
#define CSTAS_SAMP_FREQ_96 (0xA << 24)
#define CSTAS_CATEGORY_MASK (0xFF << 8)
#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8)
#define CSTAS_NO_COPYRIGHT (0x1 << 2)
/**
* struct samsung_spdif_info - Samsung S/PDIF Controller information
* @lock: Spin lock for S/PDIF.
* @dev: The parent device passed to use from the probe.
* @regs: The pointer to the device register block.
* @clk_rate: Current clock rate for calcurate ratio.
* @pclk: The peri-clock pointer for spdif master operation.
* @sclk: The source clock pointer for making sync signals.
* @saved_clkcon: Backup clkcon reg. in suspend.
* @saved_con: Backup con reg. in suspend.
* @saved_cstas: Backup cstas reg. in suspend.
* @dma_playback: DMA information for playback channel.
*/
struct samsung_spdif_info {
spinlock_t lock;
struct device *dev;
void __iomem *regs;
unsigned long clk_rate;
struct clk *pclk;
struct clk *sclk;
u32 saved_clkcon;
u32 saved_con;
u32 saved_cstas;
struct snd_dmaengine_dai_dma_data *dma_playback;
};
static struct snd_dmaengine_dai_dma_data spdif_stereo_out;
static struct samsung_spdif_info spdif_info;
static inline struct samsung_spdif_info
*component_to_info(struct snd_soc_component *component)
{
return snd_soc_component_get_drvdata(component);
}
static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
{
return snd_soc_dai_get_drvdata(cpu_dai);
}
static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
{
void __iomem *regs = spdif->regs;
u32 clkcon;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
if (on)
writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
else
writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
}
static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct samsung_spdif_info *spdif = to_info(cpu_dai);
u32 clkcon;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
clkcon = readl(spdif->regs + CLKCON);
if (clk_id == SND_SOC_SPDIF_INT_MCLK)
clkcon &= ~CLKCTL_MCLK_EXT;
else
clkcon |= CLKCTL_MCLK_EXT;
writel(clkcon, spdif->regs + CLKCON);
spdif->clk_rate = freq;
return 0;
}
static int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct samsung_spdif_info *spdif = to_info(snd_soc_rtd_to_cpu(rtd, 0));
dev_dbg(spdif->dev, "Entered %s\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
scoped_guard(spinlock_irqsave, &spdif->lock)
spdif_snd_txctrl(spdif, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
scoped_guard(spinlock_irqsave, &spdif->lock)
spdif_snd_txctrl(spdif, 0);
break;
default:
return -EINVAL;
}
return 0;
}
static int spdif_sysclk_ratios[] = {
512, 384, 256,
};
static int spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *socdai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct samsung_spdif_info *spdif = to_info(snd_soc_rtd_to_cpu(rtd, 0));
void __iomem *regs = spdif->regs;
struct snd_dmaengine_dai_dma_data *dma_data;
u32 con, clkcon, cstas;
int i, ratio;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = spdif->dma_playback;
else {
dev_err(spdif->dev, "Capture is not supported\n");
return -EINVAL;
}
snd_soc_dai_set_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream, dma_data);
guard(spinlock_irqsave)(&spdif->lock);
con = readl(regs + CON) & CON_MASK;
cstas = readl(regs + CSTAS) & CSTAS_MASK;
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
con &= ~CON_FIFO_TH_MASK;
con |= (0x7 << CON_FIFO_TH_SHIFT);
con |= CON_USERDATA_23RDBIT;
con |= CON_PCM_DATA;
con &= ~CON_PCM_MASK;
switch (params_width(params)) {
case 16:
con |= CON_PCM_16BIT;
break;
default:
dev_err(spdif->dev, "Unsupported data size.\n");
return -EINVAL;
}
ratio = spdif->clk_rate / params_rate(params);
for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
if (ratio == spdif_sysclk_ratios[i])
break;
if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
spdif->clk_rate, params_rate(params));
return -EINVAL;
}
con &= ~CON_MCLKDIV_MASK;
switch (ratio) {
case 256:
con |= CON_MCLKDIV_256FS;
break;
case 384:
con |= CON_MCLKDIV_384FS;
break;
case 512:
con |= CON_MCLKDIV_512FS;
break;
}
cstas &= ~CSTAS_SAMP_FREQ_MASK;
switch (params_rate(params)) {
case 44100:
cstas |= CSTAS_SAMP_FREQ_44;
break;
case 48000:
cstas |= CSTAS_SAMP_FREQ_48;
break;
case 32000:
cstas |= CSTAS_SAMP_FREQ_32;
break;
case 96000:
cstas |= CSTAS_SAMP_FREQ_96;
break;
default:
dev_err(spdif->dev, "Invalid sampling rate %d\n",
params_rate(params));
return -EINVAL;
}
cstas &= ~CSTAS_CATEGORY_MASK;
cstas |= CSTAS_CATEGORY_CODE_CDP;
cstas |= CSTAS_NO_COPYRIGHT;
writel(con, regs + CON);
writel(cstas, regs + CSTAS);
writel(clkcon, regs + CLKCON);
return 0;
}
static void spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct samsung_spdif_info *spdif = to_info(snd_soc_rtd_to_cpu(rtd, 0));
void __iomem *regs = spdif->regs;
u32 con, clkcon;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
con = readl(regs + CON) & CON_MASK;
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
writel(con | CON_SW_RESET, regs + CON);
cpu_relax();
writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
}
#ifdef CONFIG_PM
static int spdif_suspend(struct snd_soc_component *component)
{
struct samsung_spdif_info *spdif = component_to_info(component);
u32 con = spdif->saved_con;
dev_dbg(spdif->dev, "Entered %s\n", __func__);
spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK;
spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
writel(con | CON_SW_RESET, spdif->regs + CON);
cpu_relax();
return 0;
}
static int spdif_resume(struct snd_soc_component *component)
{
struct samsung_spdif_info *spdif = component_to_info(component);
dev_dbg(spdif->dev, "Entered %s\n", __func__);
writel(spdif->saved_clkcon, spdif->regs + CLKCON);
writel(spdif->saved_con, spdif->regs + CON);
writel(spdif->saved_cstas, spdif->regs + CSTAS);
return 0;
}
#else
#define spdif_suspend NULL
#define spdif_resume NULL
#endif
static const struct snd_soc_dai_ops spdif_dai_ops = {
.set_sysclk = spdif_set_sysclk,
.trigger = spdif_trigger,
.hw_params = spdif_hw_params,
.shutdown = spdif_shutdown,
};
static struct snd_soc_dai_driver samsung_spdif_dai = {
.name = "samsung-spdif",
.playback = {
.stream_name = "S/PDIF Playback",
.channels_min = 2,
.channels_max = 2,
.rates = (SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000),
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
.ops = &spdif_dai_ops,
};
static const struct snd_soc_component_driver samsung_spdif_component = {
.name = "samsung-spdif",
.suspend = spdif_suspend,
.resume = spdif_resume,
.legacy_dai_naming = 1,
};
static int spdif_probe(struct platform_device *pdev)
{
struct s3c_audio_pdata *spdif_pdata;
struct resource *mem_res;
struct samsung_spdif_info *spdif;
dma_filter_fn filter;
int ret;
spdif_pdata = pdev->dev.platform_data;
dev_dbg(&pdev->dev, "Entered %s\n", __func__);
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {
dev_err(&pdev->dev, "Unable to get register resource.\n");
return -ENXIO;
}
if (spdif_pdata && spdif_pdata->cfg_gpio
&& spdif_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
return -EINVAL;
}
spdif = &spdif_info;
spdif->dev = &pdev->dev;
spin_lock_init(&spdif->lock);
spdif->pclk = devm_clk_get(&pdev->dev, "spdif");
if (IS_ERR(spdif->pclk)) {
dev_err(&pdev->dev, "failed to get peri-clock\n");
ret = -ENOENT;
goto err0;
}
ret = clk_prepare_enable(spdif->pclk);
if (ret)
goto err0;
spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif");
if (IS_ERR(spdif->sclk)) {
dev_err(&pdev->dev, "failed to get internal source clock\n");
ret = -ENOENT;
goto err1;
}
ret = clk_prepare_enable(spdif->sclk);
if (ret)
goto err1;
spdif->regs = devm_ioremap_resource(&pdev->dev, mem_res);
if (IS_ERR(spdif->regs)) {
ret = PTR_ERR(spdif->regs);
goto err2;
}
spdif_stereo_out.addr_width = 2;
spdif_stereo_out.addr = mem_res->start + DATA_OUTBUF;
filter = NULL;
if (spdif_pdata) {
spdif_stereo_out.filter_data = spdif_pdata->dma_playback;
filter = spdif_pdata->dma_filter;
}
spdif->dma_playback = &spdif_stereo_out;
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
NULL, NULL, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
goto err2;
}
dev_set_drvdata(&pdev->dev, spdif);
ret = devm_snd_soc_register_component(&pdev->dev,
&samsung_spdif_component, &samsung_spdif_dai, 1);
if (ret != 0) {
dev_err(&pdev->dev, "fail to register dai\n");
goto err2;
}
return 0;
err2:
clk_disable_unprepare(spdif->sclk);
err1:
clk_disable_unprepare(spdif->pclk);
err0:
return ret;
}
static void spdif_remove(struct platform_device *pdev)
{
struct samsung_spdif_info *spdif = &spdif_info;
clk_disable_unprepare(spdif->sclk);
clk_disable_unprepare(spdif->pclk);
}
static struct platform_driver samsung_spdif_driver = {
.probe = spdif_probe,
.remove = spdif_remove,
.driver = {
.name = "samsung-spdif",
},
};
module_platform_driver(samsung_spdif_driver);
MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-spdif");
+15
View File
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd
* http://www.samsung.com/
*/
#ifndef __SND_SOC_SAMSUNG_SPDIF_H
#define __SND_SOC_SAMSUNG_SPDIF_H __FILE__
#define SND_SOC_SPDIF_INT_MCLK 0
#define SND_SOC_SPDIF_EXT_MCLK 1
#endif /* __SND_SOC_SAMSUNG_SPDIF_H */
+388
View File
@@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Speyside audio support
//
// Copyright 2011 Wolfson Microelectronics
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include "../codecs/wm8996.h"
#include "../codecs/wm9081.h"
#define MCLK_AUDIO_RATE (512 * 48000)
static int speyside_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(codec_dai, WM8996_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8996_FLL_MCLK2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL\n");
return ret;
}
break;
default:
break;
}
return 0;
}
static int speyside_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(codec_dai, 0,
WM8996_FLL_MCLK2,
32768, MCLK_AUDIO_RATE);
if (ret < 0) {
pr_err("Failed to start FLL\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8996_SYSCLK_FLL,
MCLK_AUDIO_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
}
break;
default:
break;
}
return 0;
}
static struct snd_soc_jack speyside_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin speyside_headset_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
static struct gpio_desc *speyside_hpsel_gpio;
/* Default the headphone selection to active high */
static int speyside_jack_polarity;
static int speyside_get_micbias(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
if (speyside_jack_polarity && (snd_soc_dapm_widget_name_cmp(source, "MICB1") == 0))
return 1;
if (!speyside_jack_polarity && (snd_soc_dapm_widget_name_cmp(source, "MICB2") == 0))
return 1;
return 0;
}
static void speyside_set_polarity(struct snd_soc_component *component,
int polarity)
{
speyside_jack_polarity = !polarity;
gpiod_direction_output(speyside_hpsel_gpio, speyside_jack_polarity);
/* Re-run DAPM to make sure we're using the correct mic bias */
snd_soc_dapm_sync(snd_soc_component_to_dapm(component));
}
static int speyside_wm0010_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0);
int ret;
ret = snd_soc_dai_set_sysclk(dai, 0, MCLK_AUDIO_RATE, 0);
if (ret < 0)
return ret;
return 0;
}
static int speyside_wm8996_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = dai->component;
enum gpiod_flags flags;
int ret;
ret = snd_soc_dai_set_sysclk(dai, WM8996_SYSCLK_MCLK2, 32768, 0);
if (ret < 0)
return ret;
if (speyside_jack_polarity)
flags = GPIOD_OUT_HIGH;
else
flags = GPIOD_OUT_LOW;
speyside_hpsel_gpio = devm_gpiod_get(rtd->card->dev,
"hp-sel",
flags);
if (IS_ERR(speyside_hpsel_gpio))
return PTR_ERR(speyside_hpsel_gpio);
ret = snd_soc_card_jack_new_pins(rtd->card, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&speyside_headset,
speyside_headset_pins,
ARRAY_SIZE(speyside_headset_pins));
if (ret)
return ret;
wm8996_detect(component, &speyside_headset, speyside_set_polarity);
return 0;
}
static int speyside_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
snd_soc_dapm_ignore_suspend(dapm, "Headphone");
snd_soc_dapm_ignore_suspend(dapm, "Headset Mic");
snd_soc_dapm_ignore_suspend(dapm, "Main AMIC");
snd_soc_dapm_ignore_suspend(dapm, "Main DMIC");
snd_soc_dapm_ignore_suspend(dapm, "Main Speaker");
snd_soc_dapm_ignore_suspend(dapm, "WM1250 Output");
snd_soc_dapm_ignore_suspend(dapm, "WM1250 Input");
return 0;
}
static const struct snd_soc_pcm_stream dsp_codec_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
};
SND_SOC_DAILINK_DEFS(cpu_dsp,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(dsp_codec,
DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8996.1-001a", "wm8996-aif1")));
SND_SOC_DAILINK_DEFS(baseband,
DAILINK_COMP_ARRAY(COMP_CPU("wm8996-aif2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
static struct snd_soc_dai_link speyside_dai[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.init = speyside_wm0010_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
SND_SOC_DAILINK_REG(cpu_dsp),
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.init = speyside_wm8996_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.c2c_params = &dsp_codec_params,
.num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(dsp_codec),
},
{
.name = "Baseband",
.stream_name = "Baseband",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(baseband),
},
};
static int speyside_wm9081_init(struct snd_soc_component *component)
{
/* At any time the WM9081 is active it will have this clock */
return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0,
MCLK_AUDIO_RATE, 0);
}
static struct snd_soc_aux_dev speyside_aux_dev[] = {
{
.dlc = COMP_AUX("wm9081.1-006c"),
.init = speyside_wm9081_init,
},
};
static struct snd_soc_codec_conf speyside_codec_conf[] = {
{
.dlc = COMP_CODEC_CONF("wm9081.1-006c"),
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
};
static const struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static const struct snd_soc_dapm_route audio_paths[] = {
{ "IN1RN", NULL, "MICB1" },
{ "IN1RP", NULL, "MICB1" },
{ "IN1RN", NULL, "MICB2" },
{ "IN1RP", NULL, "MICB2" },
{ "MICB1", NULL, "Headset Mic", speyside_get_micbias },
{ "MICB2", NULL, "Headset Mic", speyside_get_micbias },
{ "IN1LP", NULL, "MICB2" },
{ "IN1RN", NULL, "MICB1" },
{ "MICB2", NULL, "Main AMIC" },
{ "DMIC1DAT", NULL, "MICB1" },
{ "DMIC2DAT", NULL, "MICB1" },
{ "MICB1", NULL, "Main DMIC" },
{ "Headphone", NULL, "HPOUT1L" },
{ "Headphone", NULL, "HPOUT1R" },
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT" },
};
static struct snd_soc_card speyside = {
.name = "Speyside",
.owner = THIS_MODULE,
.dai_link = speyside_dai,
.num_links = ARRAY_SIZE(speyside_dai),
.aux_dev = speyside_aux_dev,
.num_aux_devs = ARRAY_SIZE(speyside_aux_dev),
.codec_conf = speyside_codec_conf,
.num_configs = ARRAY_SIZE(speyside_codec_conf),
.set_bias_level = speyside_set_bias_level,
.set_bias_level_post = speyside_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.fully_routed = true,
.late_probe = speyside_late_probe,
};
static struct gpiod_lookup_table wm8996_gpiod_table = {
/* Hardcoded device name in board file mach-crag6410.c */
.dev_id = "speyside",
.table = {
/*
* This line was hardcoded to 214 in the global GPIO
* number space, S3C GPIO macros seems top set the
* wm8996 codec GPIO start offset to 212, so this will
* be GPIO 214 - 212 = 2 on the wm8996.
*/
GPIO_LOOKUP("wm8996", 2, "hp-sel", GPIO_ACTIVE_HIGH),
{ },
},
};
static void speyside_gpiod_table_action(void *data)
{
gpiod_remove_lookup_table(&wm8996_gpiod_table);
}
static int speyside_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &speyside;
int ret;
card->dev = &pdev->dev;
gpiod_add_lookup_table(&wm8996_gpiod_table);
ret = devm_add_action_or_reset(&pdev->dev, speyside_gpiod_table_action,
NULL);
if (ret)
return ret;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
return ret;
}
static struct platform_driver speyside_driver = {
.driver = {
.name = "speyside",
.pm = &snd_soc_pm_ops,
},
.probe = speyside_probe,
};
module_platform_driver(speyside_driver);
MODULE_DESCRIPTION("Speyside audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:speyside");
+677
View File
@@ -0,0 +1,677 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright (C) 2015 - 2016 Samsung Electronics Co., Ltd.
//
// Authors: Inha Song <ideal.song@samsung.com>
// Sylwester Nawrocki <s.nawrocki@samsung.com>
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "i2s.h"
#include "../codecs/wm5110.h"
/*
* The source clock is XCLKOUT with its mux set to the external fixed rate
* oscillator (XXTI).
*/
#define MCLK_RATE 24000000U
#define TM2_DAI_AIF1 0
#define TM2_DAI_AIF2 1
struct tm2_machine_priv {
struct snd_soc_component *component;
unsigned int sysclk_rate;
struct gpio_desc *gpio_mic_bias;
};
static int tm2_start_sysclk(struct snd_soc_card *card)
{
struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_component *component = priv->component;
int ret;
ret = snd_soc_component_set_pll(component, WM5110_FLL1_REFCLK,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
priv->sysclk_rate);
if (ret < 0) {
dev_err(component->dev, "Failed to set FLL1 source: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_pll(component, WM5110_FLL1,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
priv->sysclk_rate);
if (ret < 0) {
dev_err(component->dev, "Failed to start FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
priv->sysclk_rate,
SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(component->dev, "Failed to set SYSCLK source: %d\n", ret);
return ret;
}
return 0;
}
static int tm2_stop_sysclk(struct snd_soc_card *card)
{
struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_component *component = priv->component;
int ret;
ret = snd_soc_component_set_pll(component, WM5110_FLL1, 0, 0, 0);
if (ret < 0) {
dev_err(component->dev, "Failed to stop FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1, 0, 0);
if (ret < 0) {
dev_err(component->dev, "Failed to stop SYSCLK: %d\n", ret);
return ret;
}
return 0;
}
static int tm2_aif1_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_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(rtd->card);
switch (params_rate(params)) {
case 4000:
case 8000:
case 12000:
case 16000:
case 24000:
case 32000:
case 48000:
case 96000:
case 192000:
/* Highest possible SYSCLK frequency: 147.456MHz */
priv->sysclk_rate = 147456000U;
break;
case 11025:
case 22050:
case 44100:
case 88200:
case 176400:
/* Highest possible SYSCLK frequency: 135.4752 MHz */
priv->sysclk_rate = 135475200U;
break;
default:
dev_err(component->dev, "Not supported sample rate: %d\n",
params_rate(params));
return -EINVAL;
}
return tm2_start_sysclk(rtd->card);
}
static const struct snd_soc_ops tm2_aif1_ops = {
.hw_params = tm2_aif1_hw_params,
};
static int tm2_aif2_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_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
unsigned int asyncclk_rate;
int ret;
switch (params_rate(params)) {
case 8000:
case 12000:
case 16000:
/* Highest possible ASYNCCLK frequency: 49.152MHz */
asyncclk_rate = 49152000U;
break;
case 11025:
/* Highest possible ASYNCCLK frequency: 45.1584 MHz */
asyncclk_rate = 45158400U;
break;
default:
dev_err(component->dev, "Not supported sample rate: %d\n",
params_rate(params));
return -EINVAL;
}
ret = snd_soc_component_set_pll(component, WM5110_FLL2_REFCLK,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
asyncclk_rate);
if (ret < 0) {
dev_err(component->dev, "Failed to set FLL2 source: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_pll(component, WM5110_FLL2,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
asyncclk_rate);
if (ret < 0) {
dev_err(component->dev, "Failed to start FLL2: %d\n", ret);
return ret;
}
ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
asyncclk_rate,
SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(component->dev, "Failed to set ASYNCCLK source: %d\n", ret);
return ret;
}
return 0;
}
static int tm2_aif2_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
int ret;
/* disable FLL2 */
ret = snd_soc_component_set_pll(component, WM5110_FLL2, ARIZONA_FLL_SRC_MCLK1,
0, 0);
if (ret < 0)
dev_err(component->dev, "Failed to stop FLL2: %d\n", ret);
return ret;
}
static const struct snd_soc_ops tm2_aif2_ops = {
.hw_params = tm2_aif2_hw_params,
.hw_free = tm2_aif2_hw_free,
};
static int tm2_hdmi_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 *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
unsigned int bfs;
int bitwidth, ret;
bitwidth = snd_pcm_format_width(params_format(params));
if (bitwidth < 0) {
dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth);
return bitwidth;
}
switch (bitwidth) {
case 48:
bfs = 64;
break;
case 16:
bfs = 32;
break;
default:
dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth);
return -EINVAL;
}
switch (params_rate(params)) {
case 48000:
case 96000:
case 192000:
break;
default:
dev_err(rtd->card->dev, "Unsupported sample rate: %d\n",
params_rate(params));
return -EINVAL;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, SAMSUNG_I2S_OPCLK_PCLK);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_soc_ops tm2_hdmi_ops = {
.hw_params = tm2_hdmi_hw_params,
};
static int tm2_mic_bias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
gpiod_set_value_cansleep(priv->gpio_mic_bias, 1);
break;
case SND_SOC_DAPM_POST_PMD:
gpiod_set_value_cansleep(priv->gpio_mic_bias, 0);
break;
}
return 0;
}
static int tm2_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dapm_context *card_dapm = snd_soc_card_to_dapm(card);
struct snd_soc_pcm_runtime *rtd;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
if (snd_soc_dapm_to_dev(dapm) != snd_soc_rtd_to_codec(rtd, 0)->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
if (snd_soc_dapm_get_bias_level(card_dapm) == SND_SOC_BIAS_OFF)
tm2_start_sysclk(card);
break;
case SND_SOC_BIAS_OFF:
tm2_stop_sysclk(card);
break;
default:
break;
}
return 0;
}
static struct snd_soc_aux_dev tm2_speaker_amp_dev;
static int tm2_late_probe(struct snd_soc_card *card)
{
struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
unsigned int ch_map[] = { 0, 1 };
struct snd_soc_dai *amp_pdm_dai;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *aif1_dai;
struct snd_soc_dai *aif2_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[TM2_DAI_AIF1]);
aif1_dai = snd_soc_rtd_to_codec(rtd, 0);
priv->component = snd_soc_rtd_to_codec(rtd, 0)->component;
ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret < 0) {
dev_err(aif1_dai->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[TM2_DAI_AIF2]);
aif2_dai = snd_soc_rtd_to_codec(rtd, 0);
ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
if (ret < 0) {
dev_err(aif2_dai->dev, "Failed to set ASYNCCLK: %d\n", ret);
return ret;
}
amp_pdm_dai = snd_soc_find_dai(&tm2_speaker_amp_dev.dlc);
if (!amp_pdm_dai)
return -ENODEV;
/* Set the MAX98504 V/I sense PDM Tx DAI channel mapping */
ret = snd_soc_dai_set_channel_map(amp_pdm_dai, ARRAY_SIZE(ch_map),
ch_map, 0, NULL);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_tdm_slot(amp_pdm_dai, 0x3, 0x0, 2, 16);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_kcontrol_new tm2_controls[] = {
SOC_DAPM_PIN_SWITCH("HP"),
SOC_DAPM_PIN_SWITCH("SPK"),
SOC_DAPM_PIN_SWITCH("RCV"),
SOC_DAPM_PIN_SWITCH("VPS"),
SOC_DAPM_PIN_SWITCH("HDMI"),
SOC_DAPM_PIN_SWITCH("Main Mic"),
SOC_DAPM_PIN_SWITCH("Sub Mic"),
SOC_DAPM_PIN_SWITCH("Third Mic"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
};
static const struct snd_soc_dapm_widget tm2_dapm_widgets[] = {
SND_SOC_DAPM_HP("HP", NULL),
SND_SOC_DAPM_SPK("SPK", NULL),
SND_SOC_DAPM_SPK("RCV", NULL),
SND_SOC_DAPM_LINE("VPS", NULL),
SND_SOC_DAPM_LINE("HDMI", NULL),
SND_SOC_DAPM_MIC("Main Mic", tm2_mic_bias),
SND_SOC_DAPM_MIC("Sub Mic", NULL),
SND_SOC_DAPM_MIC("Third Mic", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
};
static const struct snd_soc_component_driver tm2_component = {
.name = "tm2-audio",
};
static struct snd_soc_dai_driver tm2_ext_dai[] = {
{
.name = "Voice call",
.playback = {
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "Bluetooth",
.playback = {
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
};
SND_SOC_DAILINK_DEFS(aif1,
DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm5110-aif1")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(voice,
DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm5110-aif2")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(bt,
DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm5110-aif3")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
SND_SOC_DAILINK_DEFS(hdmi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link tm2_dai_links[] = {
{
.name = "WM5110 AIF1",
.stream_name = "HiFi Primary",
.ops = &tm2_aif1_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
SND_SOC_DAILINK_REG(aif1),
}, {
.name = "WM5110 Voice",
.stream_name = "Voice call",
.ops = &tm2_aif2_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(voice),
}, {
.name = "WM5110 BT",
.stream_name = "Bluetooth",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(bt),
}, {
.name = "HDMI",
.stream_name = "i2s1",
.ops = &tm2_hdmi_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
SND_SOC_DAILINK_REG(hdmi),
}
};
static struct snd_soc_card tm2_card = {
.owner = THIS_MODULE,
.dai_link = tm2_dai_links,
.controls = tm2_controls,
.num_controls = ARRAY_SIZE(tm2_controls),
.dapm_widgets = tm2_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tm2_dapm_widgets),
.aux_dev = &tm2_speaker_amp_dev,
.num_aux_devs = 1,
.late_probe = tm2_late_probe,
.set_bias_level = tm2_set_bias_level,
};
static int tm2_probe(struct platform_device *pdev)
{
struct device_node *cpu_dai_node[2] = {};
struct device_node *codec_dai_node[2] = {};
const char *cells_name = NULL;
struct device *dev = &pdev->dev;
struct snd_soc_card *card = &tm2_card;
struct tm2_machine_priv *priv;
struct snd_soc_dai_link *dai_link;
int num_codecs, ret, i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
snd_soc_card_set_drvdata(card, priv);
card->dev = dev;
priv->gpio_mic_bias = devm_gpiod_get(dev, "mic-bias", GPIOD_OUT_HIGH);
if (IS_ERR(priv->gpio_mic_bias)) {
dev_err(dev, "Failed to get mic bias gpio\n");
return PTR_ERR(priv->gpio_mic_bias);
}
ret = snd_soc_of_parse_card_name(card, "model");
if (ret < 0) {
dev_err(dev, "Card name is not specified\n");
return ret;
}
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
if (ret < 0) {
/* Backwards compatible way */
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
if (ret < 0) {
dev_err(dev, "Audio routing is not specified or invalid\n");
return ret;
}
}
card->aux_dev[0].dlc.of_node = of_parse_phandle(dev->of_node,
"audio-amplifier", 0);
if (!card->aux_dev[0].dlc.of_node) {
dev_err(dev, "audio-amplifier property invalid or missing\n");
return -EINVAL;
}
num_codecs = of_count_phandle_with_args(dev->of_node, "audio-codec",
NULL);
/* Skip the HDMI link if not specified in DT */
if (num_codecs > 1) {
card->num_links = ARRAY_SIZE(tm2_dai_links);
cells_name = "#sound-dai-cells";
} else {
card->num_links = ARRAY_SIZE(tm2_dai_links) - 1;
}
for (i = 0; i < num_codecs; i++) {
struct of_phandle_args args;
ret = of_parse_phandle_with_args(dev->of_node, "i2s-controller",
cells_name, i, &args);
if (ret) {
dev_err(dev, "i2s-controller property parse error: %d\n", i);
ret = -EINVAL;
goto dai_node_put;
}
cpu_dai_node[i] = args.np;
codec_dai_node[i] = of_parse_phandle(dev->of_node,
"audio-codec", i);
if (!codec_dai_node[i]) {
dev_err(dev, "audio-codec property parse error\n");
ret = -EINVAL;
goto dai_node_put;
}
}
/* Initialize WM5110 - I2S and HDMI - I2S1 DAI links */
for_each_card_prelinks(card, i, dai_link) {
unsigned int dai_index = 0; /* WM5110 */
dai_link->cpus->name = NULL;
dai_link->platforms->name = NULL;
if (num_codecs > 1 && i == card->num_links - 1)
dai_index = 1; /* HDMI */
dai_link->codecs->of_node = codec_dai_node[dai_index];
dai_link->cpus->of_node = cpu_dai_node[dai_index];
dai_link->platforms->of_node = cpu_dai_node[dai_index];
}
if (num_codecs > 1) {
struct of_phandle_args args;
/* HDMI DAI link (I2S1) */
i = card->num_links - 1;
ret = of_parse_phandle_with_fixed_args(dev->of_node,
"audio-codec", 0, 1, &args);
if (ret) {
dev_err(dev, "audio-codec property parse error\n");
goto dai_node_put;
}
ret = snd_soc_get_dai_name(&args, &card->dai_link[i].codecs->dai_name);
if (ret) {
dev_err(dev, "Unable to get codec_dai_name\n");
goto dai_node_put;
}
}
ret = devm_snd_soc_register_component(dev, &tm2_component,
tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai));
if (ret < 0) {
dev_err(dev, "Failed to register component: %d\n", ret);
goto dai_node_put;
}
ret = devm_snd_soc_register_card(dev, card);
if (ret < 0) {
dev_err_probe(dev, ret, "Failed to register card\n");
goto dai_node_put;
}
dai_node_put:
for (i = 0; i < num_codecs; i++) {
of_node_put(codec_dai_node[i]);
of_node_put(cpu_dai_node[i]);
}
of_node_put(card->aux_dev[0].dlc.of_node);
return ret;
}
static int tm2_pm_prepare(struct device *dev)
{
struct snd_soc_card *card = dev_get_drvdata(dev);
return tm2_stop_sysclk(card);
}
static void tm2_pm_complete(struct device *dev)
{
struct snd_soc_card *card = dev_get_drvdata(dev);
tm2_start_sysclk(card);
}
static const struct dev_pm_ops tm2_pm_ops = {
.prepare = tm2_pm_prepare,
.suspend = snd_soc_suspend,
.resume = snd_soc_resume,
.complete = tm2_pm_complete,
.freeze = snd_soc_suspend,
.thaw = snd_soc_resume,
.poweroff = snd_soc_poweroff,
.restore = snd_soc_resume,
};
static const struct of_device_id tm2_of_match[] = {
{ .compatible = "samsung,tm2-audio" },
{ },
};
MODULE_DEVICE_TABLE(of, tm2_of_match);
static struct platform_driver tm2_driver = {
.driver = {
.name = "tm2-audio",
.pm = &tm2_pm_ops,
.of_match_table = tm2_of_match,
},
.probe = tm2_probe,
};
module_platform_driver(tm2_driver);
MODULE_AUTHOR("Inha Song <ideal.song@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC Exynos TM2 Audio Support");
MODULE_LICENSE("GPL v2");
+248
View File
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Tobermory audio support
//
// Copyright 2011 Wolfson Microelectronics
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/module.h>
#include "../codecs/wm8962.h"
static int sample_rate = 44100;
static int tobermory_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
WM8962_FLL_MCLK, 32768,
sample_rate * 512);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8962_SYSCLK_FLL,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0, 0, 0);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int tobermory_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
if (snd_soc_dapm_to_dev(dapm) != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
break;
default:
break;
}
return 0;
}
static int tobermory_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
sample_rate = params_rate(params);
return 0;
}
static const struct snd_soc_ops tobermory_ops = {
.hw_params = tobermory_hw_params,
};
SND_SOC_DAILINK_DEFS(cpu,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8962.1-001a", "wm8962")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
static struct snd_soc_dai_link tobermory_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ops = &tobermory_ops,
SND_SOC_DAILINK_REG(cpu),
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("DMIC"),
};
static const struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
};
static const struct snd_soc_dapm_route audio_paths[] = {
{ "Headphone", NULL, "HPOUTL" },
{ "Headphone", NULL, "HPOUTR" },
{ "Main Speaker", NULL, "SPKOUTL" },
{ "Main Speaker", NULL, "SPKOUTR" },
{ "Headset Mic", NULL, "MICBIAS" },
{ "IN4L", NULL, "Headset Mic" },
{ "IN4R", NULL, "Headset Mic" },
{ "AMIC", NULL, "MICBIAS" },
{ "IN1L", NULL, "AMIC" },
{ "IN1R", NULL, "AMIC" },
{ "DMIC", NULL, "MICBIAS" },
{ "DMICDAT", NULL, "DMIC" },
};
static struct snd_soc_jack tobermory_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin tobermory_headset_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
{
.pin = "Headphone",
.mask = SND_JACK_MICROPHONE,
},
};
static int tobermory_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_component *component;
struct snd_soc_dai *codec_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
component = snd_soc_rtd_to_codec(rtd, 0)->component;
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET |
SND_JACK_BTN_0, &tobermory_headset,
tobermory_headset_pins,
ARRAY_SIZE(tobermory_headset_pins));
if (ret)
return ret;
wm8962_mic_detect(component, &tobermory_headset);
return 0;
}
static struct snd_soc_card tobermory = {
.name = "Tobermory",
.owner = THIS_MODULE,
.dai_link = tobermory_dai,
.num_links = ARRAY_SIZE(tobermory_dai),
.set_bias_level = tobermory_set_bias_level,
.set_bias_level_post = tobermory_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.fully_routed = true,
.late_probe = tobermory_late_probe,
};
static int tobermory_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &tobermory;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
return ret;
}
static struct platform_driver tobermory_driver = {
.driver = {
.name = "tobermory",
.pm = &snd_soc_pm_ops,
},
.probe = tobermory_probe,
};
module_platform_driver(tobermory_driver);
MODULE_DESCRIPTION("Tobermory audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:tobermory");