[alsa-devel] [PATCH 0/2] ASoC driver for the TSE-850
Hi!
The TSE-850 is an FM Transmitter Station Equipment, designed to generate baseband signals for FM, mainly the DARC subcarrier, but other signals are also possible.
This adds a driver for the "sound" bits of the device (quoted since it is normally not used for normal sound output, but that works too of course).
I have not provided a patch to add axentia as a devicetree vendor prefix, since such a patch is already pending in an IIO series [1] that seems close to being accepted.
However, there are a couple of points that I'm not 100% satisfied with for this driver.
First, I do not know how to describe the relays that control if the IN1/IN2 signals are directly routed towards OUT1/OUT2 or if they are routed to the "add" switch. The dapm routing treats this as if the IN1/IN2 signals are always routed to both the "add" switch and to the muxes feeding OUT1/OUT2. This is fine with me since nothing is powered in those sections anyway, so what dapm thinks does not really matter. But it is a wart all the same.
Second, there's my comment in tse850_put_mix() when the "add" switch is updated. I believe this update should really happen as a side effect of the call to snd_soc_dapm_mixer_update_power(), so that it happens at the right point compared to other stuff that is powered. But I do not know how to hook that up and instead I flip the switch before the call since it doesn't really matter. I.e., any noise resulting from this badness is negligeble in practice.
Cheers, Peter
[1] http://www.spinics.net/lists/devicetree/thrd3.html#147258
Peter Rosin (2): dt-bindings: sound: document axentia,tse850-pcm5142 bindings ASoC: axentia: tse850: add ASoC driver for the Axentia TSE-850
.../bindings/sound/axentia,tse850-pcm5142.txt | 88 ++++ MAINTAINERS | 7 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/axentia/Kconfig | 10 + sound/soc/axentia/Makefile | 3 + sound/soc/axentia/tse850-pcm5142.c | 504 +++++++++++++++++++++ 7 files changed, 614 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt create mode 100644 sound/soc/axentia/Kconfig create mode 100644 sound/soc/axentia/Makefile create mode 100644 sound/soc/axentia/tse850-pcm5142.c
The TSE-850 is an FM Transmitter Station Equipment, designed to generate baseband signals for FM, mainly the DARC subcarrier, but other signals are also possible.
Signed-off-by: Peter Rosin peda@axentia.se --- .../bindings/sound/axentia,tse850-pcm5142.txt | 88 ++++++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt
diff --git a/Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt b/Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt new file mode 100644 index 000000000000..0c2d44fda17e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt @@ -0,0 +1,88 @@ +ASoC driver for the Axentia TSE-850 with a PCM5142 codec + +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 = <®_3v3>; + DVDD-supply = <®_3v3>; + CPVDD-supply = <®_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>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 539b20baf791..4f2ebf3ab51a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2318,6 +2318,12 @@ 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,* + AZ6007 DVB DRIVER M: Mauro Carvalho Chehab mchehab@s-opensource.com M: Mauro Carvalho Chehab mchehab@kernel.org
On Tue, Nov 08, 2016 at 05:20:56PM +0100, Peter Rosin wrote:
The TSE-850 is an FM Transmitter Station Equipment, designed to generate baseband signals for FM, mainly the DARC subcarrier, but other signals are also possible.
Please use subject lines matching the style for the subsystem. This makes it easier for people to identify relevant patches.
The TSE-850 is an FM Transmitter Station Equipment, designed to generate baseband signals for FM, mainly the DARC subcarrier, but other signals are also possible.
Signed-off-by: Peter Rosin peda@axentia.se --- MAINTAINERS | 1 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/axentia/Kconfig | 10 + sound/soc/axentia/Makefile | 3 + sound/soc/axentia/tse850-pcm5142.c | 504 +++++++++++++++++++++++++++++++++++++ 6 files changed, 520 insertions(+) create mode 100644 sound/soc/axentia/Kconfig create mode 100644 sound/soc/axentia/Makefile create mode 100644 sound/soc/axentia/tse850-pcm5142.c
diff --git a/MAINTAINERS b/MAINTAINERS index 4f2ebf3ab51a..e966dfa79680 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2323,6 +2323,7 @@ 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/axentia/
AZ6007 DVB DRIVER M: Mauro Carvalho Chehab mchehab@s-opensource.com diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 182d92efc7c8..5e2f9e58710b 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -41,6 +41,7 @@ source "sound/soc/adi/Kconfig" source "sound/soc/amd/Kconfig" source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" +source "sound/soc/axentia/Kconfig" source "sound/soc/bcm/Kconfig" source "sound/soc/blackfin/Kconfig" source "sound/soc/cirrus/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9a30f21d16ee..dcdd41358e2a 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_SND_SOC) += adi/ obj-$(CONFIG_SND_SOC) += amd/ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ +obj-$(CONFIG_SND_SOC) += axentia/ obj-$(CONFIG_SND_SOC) += bcm/ obj-$(CONFIG_SND_SOC) += blackfin/ obj-$(CONFIG_SND_SOC) += cirrus/ diff --git a/sound/soc/axentia/Kconfig b/sound/soc/axentia/Kconfig new file mode 100644 index 000000000000..7c760edd4bed --- /dev/null +++ b/sound/soc/axentia/Kconfig @@ -0,0 +1,10 @@ +config SND_SOC_AXENTIA_TSE850_PCM5142 + tristate "ASoC driver for the Axentia TSE-850" + depends on ARCH_AT91 && OF + select ATMEL_SSC + select SND_ATMEL_SOC + 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. diff --git a/sound/soc/axentia/Makefile b/sound/soc/axentia/Makefile new file mode 100644 index 000000000000..d4ef52915809 --- /dev/null +++ b/sound/soc/axentia/Makefile @@ -0,0 +1,3 @@ +snd-soc-tse850-pcm5142-objs := tse850-pcm5142.o + +obj-$(CONFIG_SND_SOC_AXENTIA_TSE850_PCM5142) += snd-soc-tse850-pcm5142.o diff --git a/sound/soc/axentia/tse850-pcm5142.c b/sound/soc/axentia/tse850-pcm5142.c new file mode 100644 index 000000000000..1865f1eea554 --- /dev/null +++ b/sound/soc/axentia/tse850-pcm5142.c @@ -0,0 +1,504 @@ +/* + * 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/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; +}; + +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); + int ret; + + ret = gpiod_get_value(tse850->loop1); + if (ret < 0) + return ret; + + ucontrol->value.enumerated.item[0] = ret; + + 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(tse850->loop1, 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); + int ret; + + ret = gpiod_get_value(tse850->loop2); + if (ret < 0) + return ret; + + ucontrol->value.enumerated.item[0] = ret; + + 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(tse850->loop2, 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); + int ret; + + ret = gpiod_get_value(tse850->add); + if (ret < 0) + return ret; + + ucontrol->value.enumerated.item[0] = ret; + + 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 old; + int connect; + + connect = !!ucontrol->value.integer.value[0]; + old = gpiod_get_value(tse850->add); + + if (old == connect) + return 0; + + /* + * Hmmm, this gpiod_set_value call should probably happen + * inside snd_soc_dapm_mixer_update_power in the loop. + */ + gpiod_set_value(tse850->add, 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; + + 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 int tse850_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->dev; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int dir = substream->stream != SNDRV_PCM_STREAM_PLAYBACK; + int div_id = dir ? ATMEL_SSC_RCMR_PERIOD : ATMEL_SSC_TCMR_PERIOD; + int period = snd_soc_params_to_frame_size(params) / 2 - 1; + int ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, div_id, period); + if (ret < 0) { + dev_err(dev, "failed to set cpu dai lrclk %d divider\n", dir); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops tse850_ops = { + .hw_params = tse850_hw_params, +}; + +static int tse850_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + + return snd_soc_dapm_add_routes(dapm, tse850_intercon, + ARRAY_SIZE(tse850_intercon)); +} + +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, + .init = tse850_init, + .ops = &tse850_ops, +}; + +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), + .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->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->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->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");
On Tue, Nov 08, 2016 at 05:20:57PM +0100, Peter Rosin wrote:
+++ b/sound/soc/axentia/Kconfig @@ -0,0 +1,10 @@ +config SND_SOC_AXENTIA_TSE850_PCM5142
- tristate "ASoC driver for the Axentia TSE-850"
- depends on ARCH_AT91 && OF
- select ATMEL_SSC
- select SND_ATMEL_SOC
- 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.
This just looks like a normal machine driver for an Atmel system which would usually go in the atemel directory - why is a new directory being created?
+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);
- int ret;
- ret = gpiod_get_value(tse850->loop2);
- if (ret < 0)
return ret;
We can't reliably read the value of output GPIOs (though in practice the majority do support it) so it'd be better practice to use a state variable to remember what we set. I'd also expect this to use the _cansleep() GPIO calls as it's not in a context where sleeping would be a problem.
+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;
- if (ret < 11000000)
ret = 11000000;
- else if (ret > 20000000)
ret = 20000000;
This needs some comments...
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct device *dev = rtd->dev;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- int dir = substream->stream != SNDRV_PCM_STREAM_PLAYBACK;
- int div_id = dir ? ATMEL_SSC_RCMR_PERIOD : ATMEL_SSC_TCMR_PERIOD;
- int period = snd_soc_params_to_frame_size(params) / 2 - 1;
Please write the logic out as normal if statements for legibility. It's a bit concerning that we even need this function, it looks like pretty basic stuff that I'd expect the CPU DAI to just be doing - why can't this be the default behaviour of the CPU DAI?
+static int tse850_init(struct snd_soc_pcm_runtime *rtd) +{
- struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
- return snd_soc_dapm_add_routes(dapm, tse850_intercon,
ARRAY_SIZE(tse850_intercon));
Set this up in the card data structure rather than open coding the call, you can register DAPM routes there too.
On 2016-11-09 14:38, Mark Brown wrote:
On Tue, Nov 08, 2016 at 05:20:57PM +0100, Peter Rosin wrote:
+++ b/sound/soc/axentia/Kconfig @@ -0,0 +1,10 @@ +config SND_SOC_AXENTIA_TSE850_PCM5142
- tristate "ASoC driver for the Axentia TSE-850"
- depends on ARCH_AT91 && OF
- select ATMEL_SSC
- select SND_ATMEL_SOC
- 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.
This just looks like a normal machine driver for an Atmel system which would usually go in the atemel directory - why is a new directory being created?
I thought atmel in this context meant that Atmel made the board, not that the board was based on an Atmel cpu.
I'll move it for v2.
+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);
- int ret;
- ret = gpiod_get_value(tse850->loop2);
- if (ret < 0)
return ret;
We can't reliably read the value of output GPIOs (though in practice the majority do support it) so it'd be better practice to use a state variable to remember what we set. I'd also expect this to use the _cansleep() GPIO calls as it's not in a context where sleeping would be a problem.
Ok, I'll add _cansleep and cached values for v2.
+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;
- if (ret < 11000000)
ret = 11000000;
- else if (ret > 20000000)
ret = 20000000;
This needs some comments...
Ok, I'll add some words...
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct device *dev = rtd->dev;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- int dir = substream->stream != SNDRV_PCM_STREAM_PLAYBACK;
- int div_id = dir ? ATMEL_SSC_RCMR_PERIOD : ATMEL_SSC_TCMR_PERIOD;
- int period = snd_soc_params_to_frame_size(params) / 2 - 1;
Please write the logic out as normal if statements for legibility. It's a bit concerning that we even need this function, it looks like pretty basic stuff that I'd expect the CPU DAI to just be doing - why can't this be the default behaviour of the CPU DAI?
I don't know and obviously don't have all the relevant HW to test changes. Do you want me to attempt such a change anyway? Adding Cc: Nicolas Ferre
+static int tse850_init(struct snd_soc_pcm_runtime *rtd) +{
- struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
- return snd_soc_dapm_add_routes(dapm, tse850_intercon,
ARRAY_SIZE(tse850_intercon));
Set this up in the card data structure rather than open coding the call, you can register DAPM routes there too.
Right.
Thanks for looking!
Cheers, Peter
On 2016-11-09 17:27, Peter Rosin wrote:
On 2016-11-09 14:38, Mark Brown wrote:
On Tue, Nov 08, 2016 at 05:20:57PM +0100, Peter Rosin wrote:
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct device *dev = rtd->dev;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- int dir = substream->stream != SNDRV_PCM_STREAM_PLAYBACK;
- int div_id = dir ? ATMEL_SSC_RCMR_PERIOD : ATMEL_SSC_TCMR_PERIOD;
- int period = snd_soc_params_to_frame_size(params) / 2 - 1;
Please write the logic out as normal if statements for legibility. It's a bit concerning that we even need this function, it looks like pretty basic stuff that I'd expect the CPU DAI to just be doing - why can't this be the default behaviour of the CPU DAI?
I don't know and obviously don't have all the relevant HW to test changes. Do you want me to attempt such a change anyway? Adding Cc: Nicolas Ferre
Something like this, perhaps?
Cheers, Peter
diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 16e459aedffe..059b0b63bd51 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -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);
@@ -429,10 +430,12 @@ static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, break;
case ATMEL_SSC_TCMR_PERIOD: + ssc_p->forced_divider |= BIT(ATMEL_SSC_TCMR_PERIOD); ssc_p->tcmr_period = div; break;
case ATMEL_SSC_RCMR_PERIOD: + ssc_p->forced_divider |= BIT(ATMEL_SSC_RCMR_PERIOD); ssc_p->rcmr_period = div; break;
@@ -459,6 +462,8 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, u32 tfmr, rfmr, tcmr, rcmr; int ret; int fslen, fslen_ext; + u32 tcmr_period; + u32 rcmr_period;
/* * Currently, there is only one set of dma params for @@ -470,6 +475,13 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, else dir = 1;
+ tcmr_period = ssc_p->tcmr_period; + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_TCMR_PERIOD))) + tcmr_period = snd_soc_params_to_frame_size(params) / 2 - 1; + rcmr_period = ssc_p->rcmr_period; + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_RCMR_PERIOD))) + rcmr_period = snd_soc_params_to_frame_size(params) / 2 - 1; + dma_params = ssc_p->dma_params[dir];
channels = params_channels(params); @@ -524,7 +536,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 +552,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 +618,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 +635,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 +662,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 +677,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) diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h index 80b153857a88..75194f582131 100644 --- a/sound/soc/atmel/atmel_ssc_dai.h +++ b/sound/soc/atmel/atmel_ssc_dai.h @@ -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;
participants (2)
-
Mark Brown
-
Peter Rosin