Commit ffe9c4f3 authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branches 'asoc/topic/ab8500', 'asoc/topic/arizona',...

Merge remote-tracking branches 'asoc/topic/ab8500', 'asoc/topic/arizona', 'asoc/topic/atmel', 'asoc/topic/bcm' and 'asoc/topic/bitfield' into asoc-next
Devicetree bindings for the Axentia TSE-850 audio complex
Required properties:
- compatible: "axentia,tse850-pcm5142"
- axentia,ssc-controller: The phandle of the atmel SSC controller used as
cpu dai.
- axentia,audio-codec: The phandle of the PCM5142 codec.
- axentia,add-gpios: gpio specifier that controls the mixer.
- axentia,loop1-gpios: gpio specifier that controls loop relays on channel 1.
- axentia,loop2-gpios: gpio specifier that controls loop relays on channel 2.
- axentia,ana-supply: Regulator that supplies the output amplifier. Must
support voltages in the 2V - 20V range, in 1V steps.
The schematics explaining the gpios are as follows:
loop1 relays
IN1 +---o +------------+ o---+ OUT1
\ /
+ +
| / |
+--o +--. |
| add | |
| V |
| .---. |
DAC +----------->|Sum|---+
| '---' |
| |
+ +
IN2 +---o--+------------+--o---+ OUT2
loop2 relays
The 'loop1' gpio pin controlls two relays, which are either in loop position,
meaning that input and output are directly connected, or they are in mixer
position, meaning that the signal is passed through the 'Sum' mixer. Similarly
for 'loop2'.
In the above, the 'loop1' relays are inactive, thus feeding IN1 to the mixer
(if 'add' is active) and feeding the mixer output to OUT1. The 'loop2' relays
are active, short-cutting the TSE-850 from channel 2. IN1, IN2, OUT1 and OUT2
are TSE-850 connectors and DAC is the PCB name of the (filtered) output from
the PCM5142 codec.
Example:
&i2c {
codec: pcm5142@4c {
compatible = "ti,pcm5142";
reg = <0x4c>;
AVDD-supply = <&reg_3v3>;
DVDD-supply = <&reg_3v3>;
CPVDD-supply = <&reg_3v3>;
clocks = <&sck>;
pll-in = <3>;
pll-out = <6>;
};
};
ana: ana-reg {
compatible = "pwm-regulator";
regulator-name = "ANA";
pwms = <&pwm0 2 1000 PWM_POLARITY_INVERTED>;
pwm-dutycycle-unit = <1000>;
pwm-dutycycle-range = <100 1000>;
regulator-min-microvolt = <2000000>;
regulator-max-microvolt = <20000000>;
regulator-ramp-delay = <1000>;
};
sound {
compatible = "axentia,tse850-pcm5142";
axentia,ssc-controller = <&ssc0>;
axentia,audio-codec = <&codec>;
axentia,add-gpios = <&pioA 8 GPIO_ACTIVE_LOW>;
axentia,loop1-gpios = <&pioA 10 GPIO_ACTIVE_LOW>;
axentia,loop2-gpios = <&pioA 11 GPIO_ACTIVE_LOW>;
axentia,ana-supply = <&ana>;
};
......@@ -2325,6 +2325,13 @@ F: include/uapi/linux/ax25.h
F: include/net/ax25.h
F: net/ax25/
AXENTIA ASOC DRIVERS
M: Peter Rosin <peda@axentia.se>
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/sound/axentia,*
F: sound/soc/atmel/tse850-pcm5142.c
AZ6007 DVB DRIVER
M: Mauro Carvalho Chehab <mchehab@s-opensource.com>
M: Mauro Carvalho Chehab <mchehab@kernel.org>
......
......@@ -268,8 +268,9 @@ struct snd_soc_dai {
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits:1;
unsigned int probed:1;
unsigned int active;
unsigned char probed:1;
struct snd_soc_dapm_widget *playback_widget;
struct snd_soc_dapm_widget *capture_widget;
......
......@@ -1029,13 +1029,13 @@ struct snd_soc_dai_link {
const struct snd_soc_ops *ops;
const struct snd_soc_compr_ops *compr_ops;
/* For unidirectional dai links */
bool playback_only;
bool capture_only;
/* Mark this pcm with non atomic ops */
bool nonatomic;
/* For unidirectional dai links */
unsigned int playback_only:1;
unsigned int capture_only:1;
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
......@@ -1206,14 +1206,11 @@ struct snd_soc_pcm_runtime {
enum snd_soc_pcm_subclass pcm_subclass;
struct snd_pcm_ops ops;
unsigned int dev_registered:1;
/* Dynamic PCM BE runtime data */
struct snd_soc_dpcm_runtime dpcm[2];
int fe_compr;
long pmdown_time;
unsigned char pop_wait:1;
/* runtime devices */
struct snd_pcm *pcm;
......@@ -1234,6 +1231,10 @@ struct snd_soc_pcm_runtime {
unsigned int num; /* 0-based and monotonic increasing */
struct list_head list; /* rtd list of the soc card */
/* bit field */
unsigned int dev_registered:1;
unsigned int pop_wait:1;
};
/* mixer control */
......
......@@ -78,4 +78,14 @@ config SND_ATMEL_SOC_PDMIC
help
Say Y if you want to add support for Atmel ASoC driver for boards using
PDMIC.
config SND_ATMEL_SOC_TSE850_PCM5142
tristate "ASoC driver for the Axentia TSE-850"
depends on ARCH_AT91 && OF
depends on ATMEL_SSC && I2C
select SND_ATMEL_SOC_SSC_DMA
select SND_SOC_PCM512x_I2C
help
Say Y if you want to add support for the ASoC driver for the
Axentia TSE-850 with a PCM5142 codec.
endif
......@@ -13,9 +13,11 @@ snd-atmel-soc-wm8904-objs := atmel_wm8904.o
snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o
snd-atmel-soc-classd-objs := atmel-classd.o
snd-atmel-soc-pdmic-objs := atmel-pdmic.o
snd-atmel-soc-tse850-pcm5142-objs := tse850-pcm5142.o
obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o
obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o
obj-$(CONFIG_SND_ATMEL_SOC_CLASSD) += snd-atmel-soc-classd.o
obj-$(CONFIG_SND_ATMEL_SOC_PDMIC) += snd-atmel-soc-pdmic.o
obj-$(CONFIG_SND_ATMEL_SOC_TSE850_PCM5142) += snd-atmel-soc-tse850-pcm5142.o
......@@ -380,6 +380,7 @@ static void atmel_ssc_shutdown(struct snd_pcm_substream *substream,
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
/* Clear the SSC dividers */
ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
ssc_p->forced_divider = 0;
}
spin_unlock_irq(&ssc_p->lock);
......@@ -426,14 +427,17 @@ static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
else
if (div != ssc_p->cmr_div)
return -EBUSY;
ssc_p->forced_divider |= BIT(ATMEL_SSC_CMR_DIV);
break;
case ATMEL_SSC_TCMR_PERIOD:
ssc_p->tcmr_period = div;
ssc_p->forced_divider |= BIT(ATMEL_SSC_TCMR_PERIOD);
break;
case ATMEL_SSC_RCMR_PERIOD:
ssc_p->rcmr_period = div;
ssc_p->forced_divider |= BIT(ATMEL_SSC_RCMR_PERIOD);
break;
default:
......@@ -443,6 +447,28 @@ static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
return 0;
}
/* Is the cpu-dai master of the frame clock? */
static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p)
{
switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFS:
case SND_SOC_DAIFMT_CBS_CFS:
return 1;
}
return 0;
}
/* Is the cpu-dai master of the bit clock? */
static int atmel_ssc_cbs(struct atmel_ssc_info *ssc_p)
{
switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFM:
case SND_SOC_DAIFMT_CBS_CFS:
return 1;
}
return 0;
}
/*
* Configure the SSC.
*/
......@@ -459,6 +485,9 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
u32 tfmr, rfmr, tcmr, rcmr;
int ret;
int fslen, fslen_ext;
u32 cmr_div;
u32 tcmr_period;
u32 rcmr_period;
/*
* Currently, there is only one set of dma params for
......@@ -470,6 +499,46 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
else
dir = 1;
/*
* If the cpu dai should provide BCLK, but noone has provided the
* divider needed for that to work, fall back to something sensible.
*/
cmr_div = ssc_p->cmr_div;
if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_CMR_DIV)) &&
atmel_ssc_cbs(ssc_p)) {
int bclk_rate = snd_soc_params_to_bclk(params);
if (bclk_rate < 0) {
dev_err(dai->dev, "unable to calculate cmr_div: %d\n",
bclk_rate);
return bclk_rate;
}
cmr_div = DIV_ROUND_CLOSEST(ssc_p->mck_rate, 2 * bclk_rate);
}
/*
* If the cpu dai should provide LRCLK, but noone has provided the
* dividers needed for that to work, fall back to something sensible.
*/
tcmr_period = ssc_p->tcmr_period;
rcmr_period = ssc_p->rcmr_period;
if (atmel_ssc_cfs(ssc_p)) {
int frame_size = snd_soc_params_to_frame_size(params);
if (frame_size < 0) {
dev_err(dai->dev,
"unable to calculate tx/rx cmr_period: %d\n",
frame_size);
return frame_size;
}
if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_TCMR_PERIOD)))
tcmr_period = frame_size / 2 - 1;
if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_RCMR_PERIOD)))
rcmr_period = frame_size / 2 - 1;
}
dma_params = ssc_p->dma_params[dir];
channels = params_channels(params);
......@@ -524,7 +593,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
fslen_ext = (bits - 1) / 16;
fslen = (bits - 1) % 16;
rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
rcmr = SSC_BF(RCMR_PERIOD, rcmr_period)
| SSC_BF(RCMR_STTDLY, START_DELAY)
| SSC_BF(RCMR_START, SSC_START_FALLING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
......@@ -540,7 +609,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
tcmr = SSC_BF(TCMR_PERIOD, tcmr_period)
| SSC_BF(TCMR_STTDLY, START_DELAY)
| SSC_BF(TCMR_START, SSC_START_FALLING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
......@@ -606,7 +675,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
fslen_ext = (bits - 1) / 16;
fslen = (bits - 1) % 16;
rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
rcmr = SSC_BF(RCMR_PERIOD, rcmr_period)
| SSC_BF(RCMR_STTDLY, START_DELAY)
| SSC_BF(RCMR_START, SSC_START_FALLING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
......@@ -623,7 +692,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
tcmr = SSC_BF(TCMR_PERIOD, tcmr_period)
| SSC_BF(TCMR_STTDLY, START_DELAY)
| SSC_BF(TCMR_START, SSC_START_FALLING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
......@@ -650,7 +719,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
* MCK divider, and the BCLK signal is output
* on the SSC TK line.
*/
rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
rcmr = SSC_BF(RCMR_PERIOD, rcmr_period)
| SSC_BF(RCMR_STTDLY, 1)
| SSC_BF(RCMR_START, SSC_START_RISING_RF)
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
......@@ -665,7 +734,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
| SSC_BF(RFMR_LOOP, 0)
| SSC_BF(RFMR_DATLEN, (bits - 1));
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
tcmr = SSC_BF(TCMR_PERIOD, tcmr_period)
| SSC_BF(TCMR_STTDLY, 1)
| SSC_BF(TCMR_START, SSC_START_RISING_RF)
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
......@@ -760,7 +829,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
}
/* set SSC clock mode register */
ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
ssc_writel(ssc_p->ssc->regs, CMR, cmr_div);
/* set receive clock mode and format */
ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
......
......@@ -113,6 +113,7 @@ struct atmel_ssc_info {
unsigned short cmr_div;
unsigned short tcmr_period;
unsigned short rcmr_period;
unsigned int forced_divider;
struct atmel_pcm_dma_params *dma_params[2];
struct atmel_ssc_state ssc_state;
unsigned long mck_rate;
......
......@@ -53,7 +53,7 @@ static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream,
return 0;
}
static struct snd_soc_ops atmel_asoc_wm8904_ops = {
static const struct snd_soc_ops atmel_asoc_wm8904_ops = {
.hw_params = atmel_asoc_wm8904_hw_params,
};
......
/*
* TSE-850 audio - ASoC driver for the Axentia TSE-850 with a PCM5142 codec
*
* Copyright (C) 2016 Axentia Technologies AB
*
* Author: Peter Rosin <peda@axentia.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/*
* loop1 relays
* IN1 +---o +------------+ o---+ OUT1
* \ /
* + +
* | / |
* +--o +--. |
* | add | |
* | V |
* | .---. |
* DAC +----------->|Sum|---+
* | '---' |
* | |
* + +
*
* IN2 +---o--+------------+--o---+ OUT2
* loop2 relays
*
* The 'loop1' gpio pin controlls two relays, which are either in loop
* position, meaning that input and output are directly connected, or
* they are in mixer position, meaning that the signal is passed through
* the 'Sum' mixer. Similarly for 'loop2'.
*
* In the above, the 'loop1' relays are inactive, thus feeding IN1 to the
* mixer (if 'add' is active) and feeding the mixer output to OUT1. The
* 'loop2' relays are active, short-cutting the TSE-850 from channel 2.
* IN1, IN2, OUT1 and OUT2 are TSE-850 connectors and DAC is the PCB name
* of the (filtered) output from the PCM5142 codec.
*/
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "atmel_ssc_dai.h"
struct tse850_priv {
int ssc_id;
struct gpio_desc *add;
struct gpio_desc *loop1;
struct gpio_desc *loop2;
struct regulator *ana;
int add_cache;
int loop1_cache;
int loop2_cache;
};
static int tse850_get_mux1(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = tse850->loop1_cache;
return 0;
}
static int tse850_put_mux1(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
struct soc_enum *e = (struct soc_enum *)kctrl->private_value;
unsigned int val = ucontrol->value.enumerated.item[0];
if (val >= e->items)
return -EINVAL;
gpiod_set_value_cansleep(tse850->loop1, val);
tse850->loop1_cache = val;
return snd_soc_dapm_put_enum_double(kctrl, ucontrol);
}
static int tse850_get_mux2(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = tse850->loop2_cache;
return 0;
}
static int tse850_put_mux2(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
struct soc_enum *e = (struct soc_enum *)kctrl->private_value;
unsigned int val = ucontrol->value.enumerated.item[0];
if (val >= e->items)
return -EINVAL;
gpiod_set_value_cansleep(tse850->loop2, val);
tse850->loop2_cache = val;
return snd_soc_dapm_put_enum_double(kctrl, ucontrol);
}
int tse850_get_mix(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = tse850->add_cache;
return 0;
}
int tse850_put_mix(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
int connect = !!ucontrol->value.integer.value[0];
if (tse850->add_cache == connect)
return 0;
/*
* Hmmm, this gpiod_set_value_cansleep call should probably happen
* inside snd_soc_dapm_mixer_update_power in the loop.
*/
gpiod_set_value_cansleep(tse850->add, connect);
tse850->add_cache = connect;
snd_soc_dapm_mixer_update_power(dapm, kctrl, connect, NULL);
return 1;
}
int tse850_get_ana(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
int ret;
ret = regulator_get_voltage(tse850->ana);
if (ret < 0)
return ret;
/*
* Map regulator output values like so:
* -11.5V to "Low" (enum 0)
* 11.5V-12.5V to "12V" (enum 1)
* 12.5V-13.5V to "13V" (enum 2)
* ...
* 18.5V-19.5V to "19V" (enum 8)
* 19.5V- to "20V" (enum 9)
*/
if (ret < 11000000)
ret = 11000000;
else if (ret > 20000000)
ret = 20000000;
ret -= 11000000;
ret = (ret + 500000) / 1000000;
ucontrol->value.enumerated.item[0] = ret;
return 0;
}
int tse850_put_ana(struct snd_kcontrol *kctrl,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl);
struct snd_soc_card *card = dapm->card;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
struct soc_enum *e = (struct soc_enum *)kctrl->private_value;
unsigned int uV = ucontrol->value.enumerated.item[0];
int ret;
if (uV >= e->items)
return -EINVAL;
/*
* Map enum zero (Low) to 2 volts on the regulator, do this since
* the ana regulator is supplied by the system 12V voltage and
* requesting anything below the system voltage causes the system
* voltage to be passed through the regulator. Also, the ana
* regulator induces noise when requesting voltages near the
* system voltage. So, by mapping Low to 2V, that noise is
* eliminated when all that is needed is 12V (the system voltage).
*/
if (uV)
uV = 11000000 + (1000000 * uV);
else
uV = 2000000;
ret = regulator_set_voltage(tse850->ana, uV, uV);
if (ret < 0)
return ret;
return snd_soc_dapm_put_enum_double(kctrl, ucontrol);
}
static const char * const mux_text[] = { "Mixer", "Loop" };
static const struct soc_enum mux_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 2, mux_text);
static const struct snd_kcontrol_new mux1 =
SOC_DAPM_ENUM_EXT("MUX1", mux_enum, tse850_get_mux1, tse850_put_mux1);
static const struct snd_kcontrol_new mux2 =
SOC_DAPM_ENUM_EXT("MUX2", mux_enum, tse850_get_mux2, tse850_put_mux2);
#define TSE850_DAPM_SINGLE_EXT(xname, reg, shift, max, invert, xget, xput) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, \
.get = xget, \
.put = xput, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
static const struct snd_kcontrol_new mix[] = {
TSE850_DAPM_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0,
tse850_get_mix, tse850_put_mix),
};
static const char * const ana_text[] = {
"Low", "12V", "13V", "14V", "15V", "16V", "17V", "18V", "19V", "20V"
};
static const struct soc_enum ana_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 9, ana_text);
static const struct snd_kcontrol_new out =
SOC_DAPM_ENUM_EXT("ANA", ana_enum, tse850_get_ana, tse850_put_ana);
static const struct snd_soc_dapm_widget tse850_dapm_widgets[] = {
SND_SOC_DAPM_LINE("OUT1", NULL),
SND_SOC_DAPM_LINE("OUT2", NULL),
SND_SOC_DAPM_LINE("IN1", NULL),
SND_SOC_DAPM_LINE("IN2", NULL),
SND_SOC_DAPM_INPUT("DAC"),
SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
SOC_MIXER_ARRAY("MIX", SND_SOC_NOPM, 0, 0, mix),
SND_SOC_DAPM_MUX("MUX1", SND_SOC_NOPM, 0, 0, &mux1),
SND_SOC_DAPM_MUX("MUX2", SND_SOC_NOPM, 0, 0, &mux2),
SND_SOC_DAPM_OUT_DRV("OUT", SND_SOC_NOPM, 0, 0, &out, 1),
};
/*
* These connections are not entirely correct, since both IN1 and IN2
* are always fed to MIX (if the "IN switch" is set so), i.e. without
* regard to the loop1 and loop2 relays that according to this only
* control MUX1 and MUX2 but in fact also control how the input signals
* are routed.
* But, 1) I don't know how to do it right, and 2) it doesn't seem to
* matter in practice since nothing is powered in those sections anyway.
*/
static const struct snd_soc_dapm_route tse850_intercon[] = {
{ "OUT1", NULL, "MUX1" },
{ "OUT2", NULL, "MUX2" },
{ "MUX1", "Loop", "IN1" },
{ "MUX1", "Mixer", "OUT" },
{ "MUX2", "Loop", "IN2" },
{ "MUX2", "Mixer", "OUT" },
{ "OUT", NULL, "MIX" },
{ "MIX", NULL, "DAC" },
{ "MIX", "IN Switch", "IN1" },
{ "MIX", "IN Switch", "IN2" },
/* connect board input to the codec left channel output pin */
{ "DAC", NULL, "OUTL" },
};
static struct snd_soc_dai_link tse850_dailink = {
.name = "TSE-850",
.stream_name = "TSE-850-PCM",
.codec_dai_name = "pcm512x-hifi",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFS,
};
static struct snd_soc_card tse850_card = {
.name = "TSE-850-ASoC",
.owner = THIS_MODULE,
.dai_link = &tse850_dailink,
.num_links = 1,
.dapm_widgets = tse850_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tse850_dapm_widgets),
.dapm_routes = tse850_intercon,
.num_dapm_routes = ARRAY_SIZE(tse850_intercon),
.fully_routed = true,
};
static int tse850_dt_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card = &tse850_card;
struct snd_soc_dai_link *dailink = &tse850_dailink;
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
if (!np) {
dev_err(&pdev->dev, "only device tree supported\n");
return -EINVAL;
}
cpu_np = of_parse_phandle(np, "axentia,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "failed to get dai and pcm info\n");
return -EINVAL;
}
dailink->cpu_of_node = cpu_np;
dailink->platform_of_node = cpu_np;
tse850->ssc_id = of_alias_get_id(cpu_np, "ssc");
of_node_put(cpu_np);
codec_np = of_parse_phandle(np, "axentia,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "failed to get codec info\n");
return -EINVAL;
}
dailink->codec_of_node = codec_np;
of_node_put(codec_np);
return 0;
}
static int tse850_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &tse850_card;
struct device *dev = card->dev = &pdev->dev;
struct tse850_priv *tse850;
int ret;
tse850 = devm_kzalloc(dev, sizeof(*tse850), GFP_KERNEL);
if (!tse850)
return -ENOMEM;
snd_soc_card_set_drvdata(card, tse850);
ret = tse850_dt_init(pdev);
if (ret) {
dev_err(dev, "failed to init dt info\n");
return ret;
}
tse850->add = devm_gpiod_get(dev, "axentia,add", GPIOD_OUT_HIGH);
if (IS_ERR(tse850->add)) {
if (PTR_ERR(tse850->add) != -EPROBE_DEFER)
dev_err(dev, "failed to get 'add' gpio\n");
return PTR_ERR(tse850->add);
}
tse850->add_cache = 1;
tse850->loop1 = devm_gpiod_get(dev, "axentia,loop1", GPIOD_OUT_HIGH);
if (IS_ERR(tse850->loop1)) {
if (PTR_ERR(tse850->loop1) != -EPROBE_DEFER)
dev_err(dev, "failed to get 'loop1' gpio\n");
return PTR_ERR(tse850->loop1);
}
tse850->loop1_cache = 1;
tse850->loop2 = devm_gpiod_get(dev, "axentia,loop2", GPIOD_OUT_HIGH);
if (IS_ERR(tse850->loop2)) {
if (PTR_ERR(tse850->loop2) != -EPROBE_DEFER)
dev_err(dev, "failed to get 'loop2' gpio\n");
return PTR_ERR(tse850->loop2);
}
tse850->loop2_cache = 1;
tse850->ana = devm_regulator_get(dev, "axentia,ana");
if (IS_ERR(tse850->ana)) {
if (PTR_ERR(tse850->ana) != -EPROBE_DEFER)
dev_err(dev, "failed to get 'ana' regulator\n");
return PTR_ERR(tse850->ana);
}
ret = regulator_enable(tse850->ana);
if (ret < 0) {
dev_err(dev, "failed to enable the 'ana' regulator\n");
return ret;
}
ret = atmel_ssc_set_audio(tse850->ssc_id);
if (ret != 0) {
dev_err(dev,
"failed to set SSC %d for audio\n", tse850->ssc_id);
goto err_disable_ana;
}
ret = snd_soc_register_card(card);
if (ret) {
dev_err(dev, "snd_soc_register_card failed\n");
goto err_put_audio;
}
return 0;
err_put_audio:
atmel_ssc_put_audio(tse850->ssc_id);
err_disable_ana:
regulator_disable(tse850->ana);
return ret;
}
static int tse850_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
atmel_ssc_put_audio(tse850->ssc_id);
regulator_disable(tse850->ana);
return 0;
}
static const struct of_device_id tse850_dt_ids[] = {
{ .compatible = "axentia,tse850-pcm5142", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tse850_dt_ids);
static struct platform_driver tse850_driver = {
.driver = {
.name = "axentia-tse850-pcm5142",
.of_match_table = of_match_ptr(tse850_dt_ids),
},
.probe = tse850_probe,
.remove = tse850_remove,
};
module_platform_driver(tse850_driver);
/* Module information */
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_DESCRIPTION("ALSA SoC driver for TSE-850 with PCM5142 codec");
MODULE_LICENSE("GPL");
......@@ -11,6 +11,7 @@ config SND_BCM2835_SOC_I2S
config SND_SOC_CYGNUS
tristate "SoC platform audio for Broadcom Cygnus chips"
depends on ARCH_BCM_CYGNUS || COMPILE_TEST
depends on HAS_DMA
help
Say Y if you want to add support for ASoC audio on Broadcom
Cygnus chips (bcm958300, bcm958305, bcm911360)
......
......@@ -2587,8 +2587,6 @@ static struct platform_driver ab8500_codec_platform_driver = {
},
.probe = ab8500_codec_driver_probe,
.remove = ab8500_codec_driver_remove,
.suspend = NULL,
.resume = NULL,
};
module_platform_driver(ab8500_codec_platform_driver);
......
......@@ -191,6 +191,14 @@ int arizona_init_spk(struct snd_soc_codec *codec)
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(arizona_init_spk);
int arizona_init_spk_irqs(struct arizona *arizona)
{
int ret;
ret = arizona_request_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT_WARN,
"Thermal warning", arizona_thermal_warn,
arizona);
......@@ -209,19 +217,16 @@ int arizona_init_spk(struct snd_soc_codec *codec)
return 0;
}
EXPORT_SYMBOL_GPL(arizona_init_spk);
EXPORT_SYMBOL_GPL(arizona_init_spk_irqs);
int arizona_free_spk(struct snd_soc_codec *codec)
int arizona_free_spk_irqs(struct arizona *arizona)
{
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->arizona;
arizona_free_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT_WARN, arizona);
arizona_free_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT, arizona);
return 0;
}
EXPORT_SYMBOL_GPL(arizona_free_spk);
EXPORT_SYMBOL_GPL(arizona_free_spk_irqs);
static const struct snd_soc_dapm_route arizona_mono_routes[] = {
{ "OUT1R", NULL, "OUT1L" },
......@@ -252,6 +257,7 @@ EXPORT_SYMBOL_GPL(arizona_init_mono);
int arizona_init_gpio(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->arizona;
int i;
......@@ -259,21 +265,24 @@ int arizona_init_gpio(struct snd_soc_codec *codec)
switch (arizona->type) {
case WM5110:
case WM8280:
snd_soc_dapm_disable_pin(dapm, "DRC2 Signal Activity");
snd_soc_component_disable_pin(component,
"DRC2 Signal Activity");
break;
default:
break;
}
snd_soc_dapm_disable_pin(dapm, "DRC1 Signal Activity");
snd_soc_component_disable_pin(component, "DRC1 Signal Activity");
for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) {
switch (arizona->pdata.gpio_defaults[i] & ARIZONA_GPN_FN_MASK) {
case ARIZONA_GP_FN_DRC1_SIGNAL_DETECT:
snd_soc_dapm_enable_pin(dapm, "DRC1 Signal Activity");
snd_soc_component_enable_pin(component,
"DRC1 Signal Activity");
break;
case ARIZONA_GP_FN_DRC2_SIGNAL_DETECT:
snd_soc_dapm_enable_pin(dapm, "DRC2 Signal Activity");
snd_soc_component_enable_pin(component,
"DRC2 Signal Activity");
break;
default:
break;
......@@ -1233,6 +1242,46 @@ static int arizona_set_opclk(struct snd_soc_codec *codec, unsigned int clk,
return -EINVAL;
}
int arizona_clk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
struct arizona *arizona = dev_get_drvdata(codec->dev->parent);
unsigned int val;
int clk_idx;
int ret;
ret = regmap_read(arizona->regmap, w->reg, &val);
if (ret) {
dev_err(codec->dev, "Failed to check clock source: %d\n", ret);
return ret;
}
val = (val & ARIZONA_SYSCLK_SRC_MASK) >> ARIZONA_SYSCLK_SRC_SHIFT;
switch (val) {
case ARIZONA_CLK_SRC_MCLK1:
clk_idx = ARIZONA_MCLK1;
break;
case ARIZONA_CLK_SRC_MCLK2:
clk_idx = ARIZONA_MCLK2;
break;
default:
return 0;
}
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
return clk_prepare_enable(arizona->mclk[clk_idx]);
case SND_SOC_DAPM_POST_PMD:
clk_disable_unprepare(arizona->mclk[clk_idx]);
return 0;
default:
return 0;
}
}
EXPORT_SYMBOL_GPL(arizona_clk_ev);
int arizona_set_sysclk(struct snd_soc_codec *codec, int clk_id,
int source, unsigned int freq, int dir)
{
......@@ -2242,6 +2291,42 @@ static int arizona_is_enabled_fll(struct arizona_fll *fll, int base)
return reg & ARIZONA_FLL1_ENA;
}
static int arizona_set_fll_clks(struct arizona_fll *fll, int base, bool ena)
{
struct arizona *arizona = fll->arizona;
unsigned int val;
struct clk *clk;
int ret;
ret = regmap_read(arizona->regmap, base + 6, &val);
if (ret != 0) {
arizona_fll_err(fll, "Failed to read current source: %d\n",
ret);
return ret;
}
val &= ARIZONA_FLL1_CLK_REF_SRC_MASK;
val >>= ARIZONA_FLL1_CLK_REF_SRC_SHIFT;
switch (val) {
case ARIZONA_FLL_SRC_MCLK1:
clk = arizona->mclk[ARIZONA_MCLK1];
break;
case ARIZONA_FLL_SRC_MCLK2:
clk = arizona->mclk[ARIZONA_MCLK2];
break;
default:
return 0;
}
if (ena) {
return clk_prepare_enable(clk);
} else {
clk_disable_unprepare(clk);
return 0;
}
}
static int arizona_enable_fll(struct arizona_fll *fll)
{
struct arizona *arizona = fll->arizona;
......@@ -2264,6 +2349,10 @@ static int arizona_enable_fll(struct arizona_fll *fll)
udelay(32);
regmap_update_bits_async(fll->arizona->regmap, fll->base + 0x9,
ARIZONA_FLL1_GAIN_MASK, 0);
if (arizona_is_enabled_fll(fll, fll->base + 0x10) > 0)
arizona_set_fll_clks(fll, fll->base + 0x10, false);
arizona_set_fll_clks(fll, fll->base, false);
}
/*
......@@ -2318,10 +2407,13 @@ static int arizona_enable_fll(struct arizona_fll *fll)
if (!already_enabled)
pm_runtime_get_sync(arizona->dev);
if (use_sync)
if (use_sync) {
arizona_set_fll_clks(fll, fll->base + 0x10, true);
regmap_update_bits_async(arizona->regmap, fll->base + 0x11,
ARIZONA_FLL1_SYNC_ENA,
ARIZONA_FLL1_SYNC_ENA);
}
arizona_set_fll_clks(fll, fll->base, true);
regmap_update_bits_async(arizona->regmap, fll->base + 1,
ARIZONA_FLL1_ENA, ARIZONA_FLL1_ENA);
......@@ -2354,19 +2446,24 @@ static int arizona_enable_fll(struct arizona_fll *fll)
static void arizona_disable_fll(struct arizona_fll *fll)
{
struct arizona *arizona = fll->arizona;
bool change;
bool ref_change, sync_change;
regmap_update_bits_async(arizona->regmap, fll->base + 1,
ARIZONA_FLL1_FREERUN, ARIZONA_FLL1_FREERUN);
regmap_update_bits_check(arizona->regmap, fll->base + 1,
ARIZONA_FLL1_ENA, 0, &change);
regmap_update_bits(arizona->regmap, fll->base + 0x11,
ARIZONA_FLL1_SYNC_ENA, 0);
ARIZONA_FLL1_ENA, 0, &ref_change);
regmap_update_bits_check(arizona->regmap, fll->base + 0x11,
ARIZONA_FLL1_SYNC_ENA, 0, &sync_change);
regmap_update_bits_async(arizona->regmap, fll->base + 1,
ARIZONA_FLL1_FREERUN, 0);
if (change)
if (sync_change)
arizona_set_fll_clks(fll, fll->base + 0x10, false);
if (ref_change) {
arizona_set_fll_clks(fll, fll->base, false);
pm_runtime_put_autosuspend(arizona->dev);
}
}
int arizona_set_fll_refclk(struct arizona_fll *fll, int source,
......@@ -2598,30 +2695,6 @@ int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol,
}
EXPORT_SYMBOL_GPL(arizona_lhpf_coeff_put);
int arizona_register_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb,
int (*notify)(struct notifier_block *nb,
unsigned long action, void *data))
{
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->arizona;
nb->notifier_call = notify;
return blocking_notifier_chain_register(&arizona->notifier, nb);
}
EXPORT_SYMBOL_GPL(arizona_register_notifier);
int arizona_unregister_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb)
{
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->arizona;
return blocking_notifier_chain_unregister(&arizona->notifier, nb);
}
EXPORT_SYMBOL_GPL(arizona_unregister_notifier);
MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
......@@ -14,6 +14,8 @@
#define _ASOC_ARIZONA_H
#include <linux/completion.h>
#include <linux/notifier.h>
#include <linux/mfd/arizona/core.h>
#include <sound/soc.h>
......@@ -66,7 +68,6 @@
/* Notifier events */
#define ARIZONA_NOTIFY_VOICE_TRIGGER 0x1
struct arizona;
struct wm_adsp;
struct arizona_dai_priv {
......@@ -255,26 +256,24 @@ extern const struct soc_enum arizona_output_anc_src[];
extern const struct snd_kcontrol_new arizona_voice_trigger_switch[];
extern int arizona_in_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int arizona_in_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol,
int event);
extern int arizona_out_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int arizona_out_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol,
int event);
extern int arizona_hp_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int arizona_hp_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol,
int event);
extern int arizona_anc_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int arizona_anc_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol,
int event);
extern int arizona_eq_coeff_put(struct snd_kcontrol *kcontrol,
int arizona_eq_coeff_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
extern int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol,
int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
extern int arizona_set_sysclk(struct snd_soc_codec *codec, int clk_id,
int source, unsigned int freq, int dir);
int arizona_clk_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol,
int event);
int arizona_set_sysclk(struct snd_soc_codec *codec, int clk_id, int source,
unsigned int freq, int dir);
extern const struct snd_soc_dai_ops arizona_dai_ops;
extern const struct snd_soc_dai_ops arizona_simple_dai_ops;
......@@ -297,41 +296,57 @@ struct arizona_fll {
char clock_ok_name[ARIZONA_FLL_NAME_LEN];
};
extern int arizona_dvfs_up(struct snd_soc_codec *codec, unsigned int flags);
extern int arizona_dvfs_down(struct snd_soc_codec *codec, unsigned int flags);
extern int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w,
int arizona_dvfs_up(struct snd_soc_codec *codec, unsigned int flags);
int arizona_dvfs_down(struct snd_soc_codec *codec, unsigned int flags);
int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
extern void arizona_init_dvfs(struct arizona_priv *priv);
void arizona_init_dvfs(struct arizona_priv *priv);
extern int arizona_init_fll(struct arizona *arizona, int id, int base,
int arizona_init_fll(struct arizona *arizona, int id, int base,
int lock_irq, int ok_irq, struct arizona_fll *fll);
extern int arizona_set_fll_refclk(struct arizona_fll *fll, int source,
int arizona_set_fll_refclk(struct arizona_fll *fll, int source,
unsigned int Fref, unsigned int Fout);
extern int arizona_set_fll(struct arizona_fll *fll, int source,
int arizona_set_fll(struct arizona_fll *fll, int source,
unsigned int Fref, unsigned int Fout);
extern int arizona_init_spk(struct snd_soc_codec *codec);
extern int arizona_init_gpio(struct snd_soc_codec *codec);
extern int arizona_init_mono(struct snd_soc_codec *codec);
extern int arizona_init_notifiers(struct snd_soc_codec *codec);
int arizona_init_spk(struct snd_soc_codec *codec);
int arizona_init_gpio(struct snd_soc_codec *codec);
int arizona_init_mono(struct snd_soc_codec *codec);
int arizona_init_notifiers(struct snd_soc_codec *codec);
extern int arizona_free_spk(struct snd_soc_codec *codec);
int arizona_init_spk_irqs(struct arizona *arizona);
int arizona_free_spk_irqs(struct arizona *arizona);
extern int arizona_init_dai(struct arizona_priv *priv, int dai);
int arizona_init_dai(struct arizona_priv *priv, int dai);
int arizona_set_output_mode(struct snd_soc_codec *codec, int output,
bool diff);
extern bool arizona_input_analog(struct snd_soc_codec *codec, int shift);
bool arizona_input_analog(struct snd_soc_codec *codec, int shift);
extern const char *arizona_sample_rate_val_to_name(unsigned int rate_val);
const char *arizona_sample_rate_val_to_name(unsigned int rate_val);
extern int arizona_register_notifier(struct snd_soc_codec *codec,
static inline int arizona_register_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb,
int (*notify)(struct notifier_block *nb,
unsigned long action,
void *data));
extern int arizona_unregister_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb);
int (*notify)
(struct notifier_block *nb,
unsigned long action, void *data))
{
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->arizona;
nb->notifier_call = notify;
return blocking_notifier_chain_register(&arizona->notifier, nb);
}
static inline int arizona_unregister_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb)
{
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->arizona;
return blocking_notifier_chain_unregister(&arizona->notifier, nb);
}
#endif
......@@ -335,9 +335,11 @@ static const struct snd_kcontrol_new cs47l24_aec_loopback_mux =
static const struct snd_soc_dapm_widget cs47l24_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1,
ARIZONA_SYSCLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_SYSCLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1,
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK,
ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK,
......@@ -1064,7 +1066,7 @@ static struct snd_soc_dai_driver cs47l24_dai[] = {
static int cs47l24_open(struct snd_compr_stream *stream)
{
struct snd_soc_pcm_runtime *rtd = stream->private_data;
struct cs47l24_priv *priv = snd_soc_codec_get_drvdata(rtd->codec);
struct cs47l24_priv *priv = snd_soc_platform_get_drvdata(rtd->platform);
struct arizona *arizona = priv->core.arizona;
int n_adsp;
......@@ -1113,8 +1115,8 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data)
static int cs47l24_codec_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
struct cs47l24_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->core.arizona;
int ret;
priv->core.arizona->dapm = dapm;
......@@ -1124,14 +1126,6 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec)
arizona_init_mono(codec);
arizona_init_notifiers(codec);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", cs47l24_adsp2_irq,
priv);
if (ret != 0) {
dev_err(codec->dev, "Failed to request DSP IRQ: %d\n", ret);
return ret;
}
ret = wm_adsp2_codec_probe(&priv->core.adsp[1], codec);
if (ret)
goto err_adsp2_codec_probe;
......@@ -1145,7 +1139,7 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec)
if (ret)
goto err_adsp2_codec_probe;
snd_soc_dapm_disable_pin(dapm, "HAPTICS");
snd_soc_component_disable_pin(component, "HAPTICS");
return 0;
......@@ -1159,17 +1153,12 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec)
static int cs47l24_codec_remove(struct snd_soc_codec *codec)
{
struct cs47l24_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->core.arizona;
wm_adsp2_codec_remove(&priv->core.adsp[1], codec);
wm_adsp2_codec_remove(&priv->core.adsp[2], codec);
priv->core.arizona->dapm = NULL;
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, priv);
arizona_free_spk(codec);
return 0;
}
......@@ -1285,25 +1274,47 @@ static int cs47l24_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_idle(&pdev->dev);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", cs47l24_adsp2_irq,
cs47l24);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to request DSP IRQ: %d\n", ret);
return ret;
}
ret = arizona_init_spk_irqs(arizona);
if (ret < 0)
goto err_dsp_irq;
ret = snd_soc_register_platform(&pdev->dev, &cs47l24_compr_platform);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register platform: %d\n", ret);
return ret;
goto err_spk_irqs;
}
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_cs47l24,
cs47l24_dai, ARRAY_SIZE(cs47l24_dai));
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
snd_soc_unregister_platform(&pdev->dev);
goto err_platform;
}
return ret;
err_platform:
snd_soc_unregister_platform(&pdev->dev);
err_spk_irqs:
arizona_free_spk_irqs(arizona);
err_dsp_irq:
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, cs47l24);
return ret;
}
static int cs47l24_remove(struct platform_device *pdev)
{
struct cs47l24_priv *cs47l24 = platform_get_drvdata(pdev);
struct arizona *arizona = cs47l24->core.arizona;
snd_soc_unregister_platform(&pdev->dev);
snd_soc_unregister_codec(&pdev->dev);
......@@ -1312,6 +1323,10 @@ static int cs47l24_remove(struct platform_device *pdev)
wm_adsp2_remove(&cs47l24->core.adsp[1]);
wm_adsp2_remove(&cs47l24->core.adsp[2]);
arizona_free_spk_irqs(arizona);
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, cs47l24);
return 0;
}
......
......@@ -607,6 +607,9 @@ static int wm5102_sysclk_ev(struct snd_soc_dapm_widget *w,
break;
case SND_SOC_DAPM_PRE_PMD:
break;
case SND_SOC_DAPM_PRE_PMU:
case SND_SOC_DAPM_POST_PMD:
return arizona_clk_ev(w, kcontrol, event);
default:
return 0;
}
......@@ -1077,9 +1080,11 @@ static const struct snd_kcontrol_new wm5102_aec_loopback_mux =
static const struct snd_soc_dapm_widget wm5102_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT,
0, wm5102_sysclk_ev,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD |
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1,
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK,
ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK,
......@@ -1903,7 +1908,7 @@ static struct snd_soc_dai_driver wm5102_dai[] = {
static int wm5102_open(struct snd_compr_stream *stream)
{
struct snd_soc_pcm_runtime *rtd = stream->private_data;
struct wm5102_priv *priv = snd_soc_codec_get_drvdata(rtd->codec);
struct wm5102_priv *priv = snd_soc_platform_get_drvdata(rtd->platform);
return wm_adsp_compr_open(&priv->core.adsp[0], stream);
}
......@@ -1926,18 +1931,10 @@ static irqreturn_t wm5102_adsp2_irq(int irq, void *data)
static int wm5102_codec_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
struct wm5102_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->core.arizona;
int ret;
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", wm5102_adsp2_irq,
priv);
if (ret != 0) {
dev_err(codec->dev, "Failed to request DSP IRQ: %d\n", ret);
return ret;
}
ret = wm_adsp2_codec_probe(&priv->core.adsp[0], codec);
if (ret)
return ret;
......@@ -1949,8 +1946,9 @@ static int wm5102_codec_probe(struct snd_soc_codec *codec)
arizona_init_spk(codec);
arizona_init_gpio(codec);
arizona_init_notifiers(codec);
snd_soc_dapm_disable_pin(dapm, "HAPTICS");
snd_soc_component_disable_pin(component, "HAPTICS");
priv->core.arizona->dapm = dapm;
......@@ -1965,16 +1963,11 @@ static int wm5102_codec_probe(struct snd_soc_codec *codec)
static int wm5102_codec_remove(struct snd_soc_codec *codec)
{
struct wm5102_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->core.arizona;
wm_adsp2_codec_remove(&priv->core.adsp[0], codec);
priv->core.arizona->dapm = NULL;
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, priv);
arizona_free_spk(codec);
return 0;
}
......@@ -2092,25 +2085,47 @@ static int wm5102_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_idle(&pdev->dev);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", wm5102_adsp2_irq,
wm5102);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to request DSP IRQ: %d\n", ret);
return ret;
}
ret = arizona_init_spk_irqs(arizona);
if (ret < 0)
goto err_dsp_irq;
ret = snd_soc_register_platform(&pdev->dev, &wm5102_compr_platform);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register platform: %d\n", ret);
return ret;
goto err_spk_irqs;
}
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm5102,
wm5102_dai, ARRAY_SIZE(wm5102_dai));
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
snd_soc_unregister_platform(&pdev->dev);
goto err_platform;
}
return ret;
err_platform:
snd_soc_unregister_platform(&pdev->dev);
err_spk_irqs:
arizona_free_spk_irqs(arizona);
err_dsp_irq:
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5102);
return ret;
}
static int wm5102_remove(struct platform_device *pdev)
{
struct wm5102_priv *wm5102 = platform_get_drvdata(pdev);
struct arizona *arizona = wm5102->core.arizona;
snd_soc_unregister_platform(&pdev->dev);
snd_soc_unregister_codec(&pdev->dev);
......@@ -2118,6 +2133,10 @@ static int wm5102_remove(struct platform_device *pdev)
wm_adsp2_remove(&wm5102->core.adsp[0]);
arizona_free_spk_irqs(arizona);
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5102);
return 0;
}
......
......@@ -183,7 +183,9 @@ static int wm5110_sysclk_ev(struct snd_soc_dapm_widget *w,
regmap_write_async(regmap, patch[i].reg,
patch[i].def);
break;
case SND_SOC_DAPM_PRE_PMU:
case SND_SOC_DAPM_POST_PMD:
return arizona_clk_ev(w, kcontrol, event);
default:
break;
}
......@@ -1073,9 +1075,11 @@ static const struct snd_kcontrol_new wm5110_output_anc_src[] = {
static const struct snd_soc_dapm_widget wm5110_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT,
0, wm5110_sysclk_ev, SND_SOC_DAPM_POST_PMU),
0, wm5110_sysclk_ev, SND_SOC_DAPM_POST_PMU |
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1,
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK,
ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK,
......@@ -2220,7 +2224,7 @@ static struct snd_soc_dai_driver wm5110_dai[] = {
static int wm5110_open(struct snd_compr_stream *stream)
{
struct snd_soc_pcm_runtime *rtd = stream->private_data;
struct wm5110_priv *priv = snd_soc_codec_get_drvdata(rtd->codec);
struct wm5110_priv *priv = snd_soc_platform_get_drvdata(rtd->platform);
struct arizona *arizona = priv->core.arizona;
int n_adsp;
......@@ -2269,8 +2273,8 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data)
static int wm5110_codec_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
struct wm5110_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->core.arizona;
int i, ret;
priv->core.arizona->dapm = dapm;
......@@ -2280,14 +2284,6 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec)
arizona_init_mono(codec);
arizona_init_notifiers(codec);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", wm5110_adsp2_irq,
priv);
if (ret != 0) {
dev_err(codec->dev, "Failed to request DSP IRQ: %d\n", ret);
return ret;
}
for (i = 0; i < WM5110_NUM_ADSP; ++i) {
ret = wm_adsp2_codec_probe(&priv->core.adsp[i], codec);
if (ret)
......@@ -2300,7 +2296,7 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec)
if (ret)
goto err_adsp2_codec_probe;
snd_soc_dapm_disable_pin(dapm, "HAPTICS");
snd_soc_component_disable_pin(component, "HAPTICS");
return 0;
......@@ -2308,15 +2304,12 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec)
for (--i; i >= 0; --i)
wm_adsp2_codec_remove(&priv->core.adsp[i], codec);
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, priv);
return ret;
}
static int wm5110_codec_remove(struct snd_soc_codec *codec)
{
struct wm5110_priv *priv = snd_soc_codec_get_drvdata(codec);
struct arizona *arizona = priv->core.arizona;
int i;
for (i = 0; i < WM5110_NUM_ADSP; ++i)
......@@ -2324,10 +2317,6 @@ static int wm5110_codec_remove(struct snd_soc_codec *codec)
priv->core.arizona->dapm = NULL;
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, priv);
arizona_free_spk(codec);
return 0;
}
......@@ -2449,25 +2438,47 @@ static int wm5110_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_idle(&pdev->dev);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", wm5110_adsp2_irq,
wm5110);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to request DSP IRQ: %d\n", ret);
return ret;
}
ret = arizona_init_spk_irqs(arizona);
if (ret < 0)
goto err_dsp_irq;
ret = snd_soc_register_platform(&pdev->dev, &wm5110_compr_platform);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register platform: %d\n", ret);
return ret;
goto err_spk_irqs;
}
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm5110,
wm5110_dai, ARRAY_SIZE(wm5110_dai));
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
snd_soc_unregister_platform(&pdev->dev);
goto err_platform;
}
return ret;
err_platform:
snd_soc_unregister_platform(&pdev->dev);
err_spk_irqs:
arizona_free_spk_irqs(arizona);
err_dsp_irq:
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5110);
return ret;
}
static int wm5110_remove(struct platform_device *pdev)
{
struct wm5110_priv *wm5110 = platform_get_drvdata(pdev);
struct arizona *arizona = wm5110->core.arizona;
int i;
snd_soc_unregister_platform(&pdev->dev);
......@@ -2477,6 +2488,10 @@ static int wm5110_remove(struct platform_device *pdev)
for (i = 0; i < WM5110_NUM_ADSP; i++)
wm_adsp2_remove(&wm5110->core.adsp[i]);
arizona_free_spk_irqs(arizona);
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5110);
return 0;
}
......
......@@ -108,6 +108,9 @@ static int wm8997_sysclk_ev(struct snd_soc_dapm_widget *w,
break;
case SND_SOC_DAPM_PRE_PMD:
break;
case SND_SOC_DAPM_PRE_PMU:
case SND_SOC_DAPM_POST_PMD:
return arizona_clk_ev(w, kcontrol, event);
default:
return 0;
}
......@@ -408,9 +411,11 @@ static const struct snd_kcontrol_new wm8997_aec_loopback_mux =
static const struct snd_soc_dapm_widget wm8997_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT,
0, wm8997_sysclk_ev,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD |
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1,
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK,
ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK,
......@@ -1055,11 +1060,13 @@ static struct snd_soc_dai_driver wm8997_dai[] = {
static int wm8997_codec_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
struct wm8997_priv *priv = snd_soc_codec_get_drvdata(codec);
arizona_init_spk(codec);
arizona_init_notifiers(codec);
snd_soc_dapm_disable_pin(dapm, "HAPTICS");
snd_soc_component_disable_pin(component, "HAPTICS");
priv->core.arizona->dapm = dapm;
......@@ -1072,8 +1079,6 @@ static int wm8997_codec_remove(struct snd_soc_codec *codec)
priv->core.arizona->dapm = NULL;
arizona_free_spk(codec);
return 0;
}
......@@ -1119,7 +1124,7 @@ static int wm8997_probe(struct platform_device *pdev)
{
struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
struct wm8997_priv *wm8997;
int i;
int i, ret;
wm8997 = devm_kzalloc(&pdev->dev, sizeof(struct wm8997_priv),
GFP_KERNEL);
......@@ -1159,15 +1164,33 @@ static int wm8997_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_idle(&pdev->dev);
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8997,
ret = arizona_init_spk_irqs(arizona);
if (ret < 0)
return ret;
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8997,
wm8997_dai, ARRAY_SIZE(wm8997_dai));
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
goto err_spk_irqs;
}
err_spk_irqs:
arizona_free_spk_irqs(arizona);
return ret;
}
static int wm8997_remove(struct platform_device *pdev)
{
struct wm8997_priv *wm8997 = platform_get_drvdata(pdev);
struct arizona *arizona = wm8997->core.arizona;
snd_soc_unregister_codec(&pdev->dev);
pm_runtime_disable(&pdev->dev);
arizona_free_spk_irqs(arizona);
return 0;
}
......
......@@ -541,9 +541,11 @@ static const struct snd_kcontrol_new wm8998_aec_loopback_mux[] = {
static const struct snd_soc_dapm_widget wm8998_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1,
ARIZONA_SYSCLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_SYSCLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1,
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, NULL, 0),
ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK,
ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK,
......@@ -1318,13 +1320,15 @@ static int wm8998_codec_probe(struct snd_soc_codec *codec)
{
struct wm8998_priv *priv = snd_soc_codec_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
priv->core.arizona->dapm = dapm;
arizona_init_spk(codec);
arizona_init_gpio(codec);
arizona_init_notifiers(codec);
snd_soc_dapm_disable_pin(dapm, "HAPTICS");
snd_soc_component_disable_pin(component, "HAPTICS");
return 0;
}
......@@ -1335,8 +1339,6 @@ static int wm8998_codec_remove(struct snd_soc_codec *codec)
priv->core.arizona->dapm = NULL;
arizona_free_spk(codec);
return 0;
}
......@@ -1385,7 +1387,7 @@ static int wm8998_probe(struct platform_device *pdev)
{
struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
struct wm8998_priv *wm8998;
int i;
int i, ret;
wm8998 = devm_kzalloc(&pdev->dev, sizeof(struct wm8998_priv),
GFP_KERNEL);
......@@ -1417,15 +1419,35 @@ static int wm8998_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_idle(&pdev->dev);
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8998,
ret = arizona_init_spk_irqs(arizona);
if (ret < 0)
return ret;
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8998,
wm8998_dai, ARRAY_SIZE(wm8998_dai));
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
goto err_spk_irqs;
}
return ret;
err_spk_irqs:
arizona_free_spk_irqs(arizona);
return ret;
}
static int wm8998_remove(struct platform_device *pdev)
{
struct wm8998_priv *wm8998 = platform_get_drvdata(pdev);
struct arizona *arizona = wm8998->core.arizona;
snd_soc_unregister_codec(&pdev->dev);
pm_runtime_disable(&pdev->dev);
arizona_free_spk_irqs(arizona);
return 0;
}
......
......@@ -162,6 +162,16 @@
#define ADSP_MAX_STD_CTRL_SIZE 512
#define WM_ADSP_ACKED_CTL_TIMEOUT_MS 100
#define WM_ADSP_ACKED_CTL_N_QUICKPOLLS 10
#define WM_ADSP_ACKED_CTL_MIN_VALUE 0
#define WM_ADSP_ACKED_CTL_MAX_VALUE 0xFFFFFF
/*
* Event control messages
*/
#define WM_ADSP_FW_EVENT_SHUTDOWN 0x000001
struct wm_adsp_buf {
struct list_head list;
void *buf;
......@@ -177,7 +187,7 @@ static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len,
buf->buf = vmalloc(len);
if (!buf->buf) {
vfree(buf);
kfree(buf);
return NULL;
}
memcpy(buf->buf, src, len);
......@@ -441,11 +451,29 @@ struct wm_coeff_ctl {
unsigned int offset;
size_t len;
unsigned int set:1;
struct snd_kcontrol *kcontrol;
struct soc_bytes_ext bytes_ext;
unsigned int flags;
unsigned int type;
};
static const char *wm_adsp_mem_region_name(unsigned int type)
{
switch (type) {
case WMFW_ADSP1_PM:
return "PM";
case WMFW_ADSP1_DM:
return "DM";
case WMFW_ADSP2_XM:
return "XM";
case WMFW_ADSP2_YM:
return "YM";
case WMFW_ADSP1_ZM:
return "ZM";
default:
return NULL;
}
}
#ifdef CONFIG_DEBUG_FS
static void wm_adsp_debugfs_save_wmfwname(struct wm_adsp *dsp, const char *s)
{
......@@ -727,6 +755,24 @@ static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext)
return container_of(ext, struct wm_coeff_ctl, bytes_ext);
}
static int wm_coeff_base_reg(struct wm_coeff_ctl *ctl, unsigned int *reg)
{
const struct wm_adsp_alg_region *alg_region = &ctl->alg_region;
struct wm_adsp *dsp = ctl->dsp;
const struct wm_adsp_region *mem;
mem = wm_adsp_find_region(dsp, alg_region->type);
if (!mem) {
adsp_err(dsp, "No base for region %x\n",
alg_region->type);
return -EINVAL;
}
*reg = wm_adsp_region_to_reg(mem, ctl->alg_region.base + ctl->offset);
return 0;
}
static int wm_coeff_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *uinfo)
{
......@@ -734,30 +780,94 @@ static int wm_coeff_info(struct snd_kcontrol *kctl,
(struct soc_bytes_ext *)kctl->private_value;
struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext);
switch (ctl->type) {
case WMFW_CTL_TYPE_ACKED:
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->value.integer.min = WM_ADSP_ACKED_CTL_MIN_VALUE;
uinfo->value.integer.max = WM_ADSP_ACKED_CTL_MAX_VALUE;
uinfo->value.integer.step = 1;
uinfo->count = 1;
break;
default:
uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
uinfo->count = ctl->len;
break;
}
return 0;
}
static int wm_coeff_write_acked_control(struct wm_coeff_ctl *ctl,
unsigned int event_id)
{
struct wm_adsp *dsp = ctl->dsp;
u32 val = cpu_to_be32(event_id);
unsigned int reg;
int i, ret;
ret = wm_coeff_base_reg(ctl, &reg);
if (ret)
return ret;
adsp_dbg(dsp, "Sending 0x%x to acked control alg 0x%x %s:0x%x\n",
event_id, ctl->alg_region.alg,
wm_adsp_mem_region_name(ctl->alg_region.type), ctl->offset);
ret = regmap_raw_write(dsp->regmap, reg, &val, sizeof(val));
if (ret) {
adsp_err(dsp, "Failed to write %x: %d\n", reg, ret);
return ret;
}
/*
* Poll for ack, we initially poll at ~1ms intervals for firmwares
* that respond quickly, then go to ~10ms polls. A firmware is unlikely
* to ack instantly so we do the first 1ms delay before reading the
* control to avoid a pointless bus transaction
*/
for (i = 0; i < WM_ADSP_ACKED_CTL_TIMEOUT_MS;) {
switch (i) {
case 0 ... WM_ADSP_ACKED_CTL_N_QUICKPOLLS - 1:
usleep_range(1000, 2000);
i++;
break;
default:
usleep_range(10000, 20000);
i += 10;
break;
}
ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val));
if (ret) {
adsp_err(dsp, "Failed to read %x: %d\n", reg, ret);
return ret;
}
if (val == 0) {
adsp_dbg(dsp, "Acked control ACKED at poll %u\n", i);
return 0;
}
}
adsp_warn(dsp, "Acked control @0x%x alg:0x%x %s:0x%x timed out\n",
reg, ctl->alg_region.alg,
wm_adsp_mem_region_name(ctl->alg_region.type),
ctl->offset);
return -ETIMEDOUT;
}
static int wm_coeff_write_control(struct wm_coeff_ctl *ctl,
const void *buf, size_t len)
{
struct wm_adsp_alg_region *alg_region = &ctl->alg_region;
const struct wm_adsp_region *mem;
struct wm_adsp *dsp = ctl->dsp;
void *scratch;
int ret;
unsigned int reg;
mem = wm_adsp_find_region(dsp, alg_region->type);
if (!mem) {
adsp_err(dsp, "No base for region %x\n",
alg_region->type);
return -EINVAL;
}
reg = ctl->alg_region.base + ctl->offset;
reg = wm_adsp_region_to_reg(mem, reg);
ret = wm_coeff_base_reg(ctl, &reg);
if (ret)
return ret;
scratch = kmemdup(buf, len, GFP_KERNEL | GFP_DMA);
if (!scratch)
......@@ -823,25 +933,41 @@ static int wm_coeff_tlv_put(struct snd_kcontrol *kctl,
return ret;
}
static int wm_coeff_put_acked(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_bytes_ext *bytes_ext =
(struct soc_bytes_ext *)kctl->private_value;
struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext);
unsigned int val = ucontrol->value.integer.value[0];
int ret;
if (val == 0)
return 0; /* 0 means no event */
mutex_lock(&ctl->dsp->pwr_lock);
if (ctl->enabled)
ret = wm_coeff_write_acked_control(ctl, val);
else
ret = -EPERM;
mutex_unlock(&ctl->dsp->pwr_lock);
return ret;
}
static int wm_coeff_read_control(struct wm_coeff_ctl *ctl,
void *buf, size_t len)
{
struct wm_adsp_alg_region *alg_region = &ctl->alg_region;
const struct wm_adsp_region *mem;
struct wm_adsp *dsp = ctl->dsp;
void *scratch;
int ret;
unsigned int reg;
mem = wm_adsp_find_region(dsp, alg_region->type);
if (!mem) {
adsp_err(dsp, "No base for region %x\n",
alg_region->type);
return -EINVAL;
}
reg = ctl->alg_region.base + ctl->offset;
reg = wm_adsp_region_to_reg(mem, reg);
ret = wm_coeff_base_reg(ctl, &reg);
if (ret)
return ret;
scratch = kmalloc(len, GFP_KERNEL | GFP_DMA);
if (!scratch)
......@@ -918,6 +1044,21 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl,
return ret;
}
static int wm_coeff_get_acked(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/*
* Although it's not useful to read an acked control, we must satisfy
* user-side assumptions that all controls are readable and that a
* write of the same value should be filtered out (it's valid to send
* the same event number again to the firmware). We therefore return 0,
* meaning "no event" so valid event numbers will always be a change
*/
ucontrol->value.integer.value[0] = 0;
return 0;
}
struct wmfw_ctl_work {
struct wm_adsp *dsp;
struct wm_coeff_ctl *ctl;
......@@ -967,30 +1108,35 @@ static int wmfw_add_ctl(struct wm_adsp *dsp, struct wm_coeff_ctl *ctl)
kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL);
if (!kcontrol)
return -ENOMEM;
kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
kcontrol->name = ctl->name;
kcontrol->info = wm_coeff_info;
kcontrol->get = wm_coeff_get;
kcontrol->put = wm_coeff_put;
kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
kcontrol->tlv.c = snd_soc_bytes_tlv_callback;
kcontrol->private_value = (unsigned long)&ctl->bytes_ext;
kcontrol->access = wmfw_convert_flags(ctl->flags, ctl->len);
switch (ctl->type) {
case WMFW_CTL_TYPE_ACKED:
kcontrol->get = wm_coeff_get_acked;
kcontrol->put = wm_coeff_put_acked;
break;
default:
kcontrol->get = wm_coeff_get;
kcontrol->put = wm_coeff_put;
ctl->bytes_ext.max = ctl->len;
ctl->bytes_ext.get = wm_coeff_tlv_get;
ctl->bytes_ext.put = wm_coeff_tlv_put;
break;
}
kcontrol->access = wmfw_convert_flags(ctl->flags, ctl->len);
ret = snd_soc_add_card_controls(dsp->card, kcontrol, 1);
ret = snd_soc_add_codec_controls(dsp->codec, kcontrol, 1);
if (ret < 0)
goto err_kcontrol;
kfree(kcontrol);
ctl->kcontrol = snd_soc_card_get_kcontrol(dsp->card, ctl->name);
return 0;
err_kcontrol:
......@@ -1035,6 +1181,27 @@ static int wm_coeff_sync_controls(struct wm_adsp *dsp)
return 0;
}
static void wm_adsp_signal_event_controls(struct wm_adsp *dsp,
unsigned int event)
{
struct wm_coeff_ctl *ctl;
int ret;
list_for_each_entry(ctl, &dsp->ctl_list, list) {
if (ctl->type != WMFW_CTL_TYPE_HOSTEVENT)
continue;
if (!ctl->enabled)
continue;
ret = wm_coeff_write_acked_control(ctl, event);
if (ret)
adsp_warn(dsp,
"Failed to send 0x%x event to alg 0x%x (%d)\n",
event, ctl->alg_region.alg, ret);
}
}
static void wm_adsp_ctl_work(struct work_struct *work)
{
struct wmfw_ctl_work *ctl_work = container_of(work,
......@@ -1056,34 +1223,16 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,
const struct wm_adsp_alg_region *alg_region,
unsigned int offset, unsigned int len,
const char *subname, unsigned int subname_len,
unsigned int flags)
unsigned int flags, unsigned int type)
{
struct wm_coeff_ctl *ctl;
struct wmfw_ctl_work *ctl_work;
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
char *region_name;
const char *region_name;
int ret;
if (flags & WMFW_CTL_FLAG_SYS)
return 0;
switch (alg_region->type) {
case WMFW_ADSP1_PM:
region_name = "PM";
break;
case WMFW_ADSP1_DM:
region_name = "DM";
break;
case WMFW_ADSP2_XM:
region_name = "XM";
break;
case WMFW_ADSP2_YM:
region_name = "YM";
break;
case WMFW_ADSP1_ZM:
region_name = "ZM";
break;
default:
region_name = wm_adsp_mem_region_name(alg_region->type);
if (!region_name) {
adsp_err(dsp, "Unknown region type: %d\n", alg_region->type);
return -EINVAL;
}
......@@ -1139,6 +1288,7 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,
ctl->dsp = dsp;
ctl->flags = flags;
ctl->type = type;
ctl->offset = offset;
ctl->len = len;
ctl->cache = kzalloc(ctl->len, GFP_KERNEL);
......@@ -1149,6 +1299,9 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,
list_add(&ctl->list, &dsp->ctl_list);
if (flags & WMFW_CTL_FLAG_SYS)
return 0;
ctl_work = kzalloc(sizeof(*ctl_work), GFP_KERNEL);
if (!ctl_work) {
ret = -ENOMEM;
......@@ -1308,6 +1461,21 @@ static inline void wm_coeff_parse_coeff(struct wm_adsp *dsp, const u8 **data,
adsp_dbg(dsp, "\tALSA control len: %#x\n", blk->len);
}
static int wm_adsp_check_coeff_flags(struct wm_adsp *dsp,
const struct wm_coeff_parsed_coeff *coeff_blk,
unsigned int f_required,
unsigned int f_illegal)
{
if ((coeff_blk->flags & f_illegal) ||
((coeff_blk->flags & f_required) != f_required)) {
adsp_err(dsp, "Illegal flags 0x%x for control type 0x%x\n",
coeff_blk->flags, coeff_blk->ctl_type);
return -EINVAL;
}
return 0;
}
static int wm_adsp_parse_coeff(struct wm_adsp *dsp,
const struct wmfw_region *region)
{
......@@ -1324,6 +1492,28 @@ static int wm_adsp_parse_coeff(struct wm_adsp *dsp,
switch (coeff_blk.ctl_type) {
case SNDRV_CTL_ELEM_TYPE_BYTES:
break;
case WMFW_CTL_TYPE_ACKED:
if (coeff_blk.flags & WMFW_CTL_FLAG_SYS)
continue; /* ignore */
ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk,
WMFW_CTL_FLAG_VOLATILE |
WMFW_CTL_FLAG_WRITEABLE |
WMFW_CTL_FLAG_READABLE,
0);
if (ret)
return -EINVAL;
break;
case WMFW_CTL_TYPE_HOSTEVENT:
ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk,
WMFW_CTL_FLAG_SYS |
WMFW_CTL_FLAG_VOLATILE |
WMFW_CTL_FLAG_WRITEABLE |
WMFW_CTL_FLAG_READABLE,
0);
if (ret)
return -EINVAL;
break;
default:
adsp_err(dsp, "Unknown control type: %d\n",
coeff_blk.ctl_type);
......@@ -1338,7 +1528,8 @@ static int wm_adsp_parse_coeff(struct wm_adsp *dsp,
coeff_blk.len,
coeff_blk.name,
coeff_blk.name_len,
coeff_blk.flags);
coeff_blk.flags,
coeff_blk.ctl_type);
if (ret < 0)
adsp_err(dsp, "Failed to create control: %.*s, %d\n",
coeff_blk.name_len, coeff_blk.name, ret);
......@@ -1491,23 +1682,11 @@ static int wm_adsp_load(struct wm_adsp *dsp)
reg = offset;
break;
case WMFW_ADSP1_PM:
region_name = "PM";
reg = wm_adsp_region_to_reg(mem, offset);
break;
case WMFW_ADSP1_DM:
region_name = "DM";
reg = wm_adsp_region_to_reg(mem, offset);
break;
case WMFW_ADSP2_XM:
region_name = "XM";
reg = wm_adsp_region_to_reg(mem, offset);
break;
case WMFW_ADSP2_YM:
region_name = "YM";
reg = wm_adsp_region_to_reg(mem, offset);
break;
case WMFW_ADSP1_ZM:
region_name = "ZM";
region_name = wm_adsp_mem_region_name(type);
reg = wm_adsp_region_to_reg(mem, offset);
break;
default:
......@@ -1750,7 +1929,8 @@ static int wm_adsp1_setup_algs(struct wm_adsp *dsp)
len -= be32_to_cpu(adsp1_alg[i].dm);
len *= 4;
wm_adsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0);
len, NULL, 0, 0,
SNDRV_CTL_ELEM_TYPE_BYTES);
} else {
adsp_warn(dsp, "Missing length info for region DM with ID %x\n",
be32_to_cpu(adsp1_alg[i].alg.id));
......@@ -1770,7 +1950,8 @@ static int wm_adsp1_setup_algs(struct wm_adsp *dsp)
len -= be32_to_cpu(adsp1_alg[i].zm);
len *= 4;
wm_adsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0);
len, NULL, 0, 0,
SNDRV_CTL_ELEM_TYPE_BYTES);
} else {
adsp_warn(dsp, "Missing length info for region ZM with ID %x\n",
be32_to_cpu(adsp1_alg[i].alg.id));
......@@ -1861,7 +2042,8 @@ static int wm_adsp2_setup_algs(struct wm_adsp *dsp)
len -= be32_to_cpu(adsp2_alg[i].xm);
len *= 4;
wm_adsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0);
len, NULL, 0, 0,
SNDRV_CTL_ELEM_TYPE_BYTES);
} else {
adsp_warn(dsp, "Missing length info for region XM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
......@@ -1881,7 +2063,8 @@ static int wm_adsp2_setup_algs(struct wm_adsp *dsp)
len -= be32_to_cpu(adsp2_alg[i].ym);
len *= 4;
wm_adsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0);
len, NULL, 0, 0,
SNDRV_CTL_ELEM_TYPE_BYTES);
} else {
adsp_warn(dsp, "Missing length info for region YM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
......@@ -1901,7 +2084,8 @@ static int wm_adsp2_setup_algs(struct wm_adsp *dsp)
len -= be32_to_cpu(adsp2_alg[i].zm);
len *= 4;
wm_adsp_create_control(dsp, alg_region, 0,
len, NULL, 0, 0);
len, NULL, 0, 0,
SNDRV_CTL_ELEM_TYPE_BYTES);
} else {
adsp_warn(dsp, "Missing length info for region ZM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
......@@ -2114,7 +2298,7 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
int ret;
unsigned int val;
dsp->card = codec->component.card;
dsp->codec = codec;
mutex_lock(&dsp->pwr_lock);
......@@ -2325,8 +2509,6 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w,
struct wm_adsp *dsp = &dsps[w->shift];
struct wm_coeff_ctl *ctl;
dsp->card = codec->component.card;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
wm_adsp2_set_dspclk(dsp, freq);
......@@ -2393,14 +2575,22 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
mutex_lock(&dsp->pwr_lock);
if (wm_adsp_fw[dsp->fw].num_caps != 0)
if (wm_adsp_fw[dsp->fw].num_caps != 0) {
ret = wm_adsp_buffer_init(dsp);
if (ret < 0) {
mutex_unlock(&dsp->pwr_lock);
goto err;
}
}
mutex_unlock(&dsp->pwr_lock);
break;
case SND_SOC_DAPM_PRE_PMD:
/* Tell the firmware to cleanup */
wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN);
/* Log firmware state, it can be useful for analysis */
wm_adsp2_show_fw_status(dsp);
......@@ -2441,6 +2631,8 @@ EXPORT_SYMBOL_GPL(wm_adsp2_event);
int wm_adsp2_codec_probe(struct wm_adsp *dsp, struct snd_soc_codec *codec)
{
dsp->codec = codec;
wm_adsp2_init_debugfs(dsp, codec);
return snd_soc_add_codec_controls(codec,
......
......@@ -44,7 +44,7 @@ struct wm_adsp {
int type;
struct device *dev;
struct regmap *regmap;
struct snd_soc_card *card;
struct snd_soc_codec *codec;
int base;
int sysclk_reg;
......@@ -110,18 +110,17 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w,
int wm_adsp2_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
extern int wm_adsp_compr_open(struct wm_adsp *dsp,
struct snd_compr_stream *stream);
extern int wm_adsp_compr_free(struct snd_compr_stream *stream);
extern int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream);
int wm_adsp_compr_free(struct snd_compr_stream *stream);
int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
struct snd_compr_params *params);
extern int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
struct snd_compr_caps *caps);
extern int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
extern int wm_adsp_compr_handle_irq(struct wm_adsp *dsp);
extern int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
int wm_adsp_compr_handle_irq(struct wm_adsp *dsp);
int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
struct snd_compr_tstamp *tstamp);
extern int wm_adsp_compr_copy(struct snd_compr_stream *stream,
int wm_adsp_compr_copy(struct snd_compr_stream *stream,
char __user *buf, size_t count);
#endif
......@@ -26,6 +26,10 @@
#define WMFW_CTL_FLAG_WRITEABLE 0x0002
#define WMFW_CTL_FLAG_READABLE 0x0001
/* Non-ALSA coefficient types start at 0x1000 */
#define WMFW_CTL_TYPE_ACKED 0x1000 /* acked control */
#define WMFW_CTL_TYPE_HOSTEVENT 0x1001 /* event control */
struct wmfw_header {
char magic[4];
__le32 len;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment