[alsa-devel] [PATCH 1/2 v2] ASoC: Add HA (HEAD acoustics) DSP codec driver template
This codec driver template represents an I2C controlled multichannel audio codec that has many typical ASoC codec driver features like volume controls, mixer stages, mux selection, output power control, in-codec audio routings, codec bias management and DAI link configuration.
This driver is based on an early version provided by Jarkko Nikula.
Signed-off-by: Jarkko Nikula jarkko.nikula@bitmer.com Signed-off-by: Stefan Roese sr@denx.de Cc: Thorsten Eisbein thorsten.eisbein@head-acoustics.de Cc: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@kernel.org --- v2: - Added/changed copyright line - Changed authorship (as suggested by Jarkko the original author) - Added Thorsten as maintainer - Remove ha_dsp_hw_params() and ha_dsp_set_dai_fmt() as its not used (only needed for CODEC as clock master which is currently not suported). - Removed some unneeded include files - "const char *const foo" used instead of "const char *foo" - SOC_MIXER_ARRAY() helper macro used - Removed ha_dsp_set_bias_level() and use default implementation - Use codec->dev instead of codec->dev->parent in dev_get_regmap() - Added CODEC reset to probe - Remove "ret" in ha_dsp_i2c_probe()
sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ha-dsp.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ha-dsp.h | 50 +++++++ 4 files changed, 389 insertions(+) create mode 100644 sound/soc/codecs/ha-dsp.c create mode 100644 sound/soc/codecs/ha-dsp.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f0e8401..f357988 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -51,6 +51,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C select SND_SOC_BT_SCO + select SND_SOC_HA_DSP if I2C select SND_SOC_ISABELLE if I2C select SND_SOC_JZ4740_CODEC select SND_SOC_LM4857 if I2C @@ -343,6 +344,9 @@ config SND_SOC_BT_SCO config SND_SOC_DMIC tristate
+config SND_SOC_HA_DSP + tristate + config SND_SOC_HDMI_CODEC tristate "HDMI stub CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3c4d275..f296bec 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -39,6 +39,7 @@ snd-soc-da732x-objs := da732x.o snd-soc-da9055-objs := da9055.o snd-soc-bt-sco-objs := bt-sco.o snd-soc-dmic-objs := dmic.o +snd-soc-ha-dsp-objs := ha-dsp.o snd-soc-isabelle-objs := isabelle.o snd-soc-jz4740-codec-objs := jz4740.o snd-soc-l3-objs := l3.o @@ -190,6 +191,7 @@ obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o +obj-$(CONFIG_SND_SOC_HA_DSP) += snd-soc-ha-dsp.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o diff --git a/sound/soc/codecs/ha-dsp.c b/sound/soc/codecs/ha-dsp.c new file mode 100644 index 0000000..5a3c7ef --- /dev/null +++ b/sound/soc/codecs/ha-dsp.c @@ -0,0 +1,333 @@ +/* + * ha-dsp.c -- HA DSP ALSA SoC Audio driver + * + * Copyright 2011-2014 HEAD acoustics GmbH + * + * Authors: + * Jarkko Nikula jarkko.nikula@bitmer.com + * Stefan Roese sr@denx.de + * Thorsten Eisbein thorsten.eisbein@head-acoustics.de + * + * 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. + * + * Maintainer: Thorsten Eisbein thorsten.eisbein@head-acoustics.de + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "ha-dsp.h" + +/* Reset default register values for soc-cache */ +static const struct reg_default ha_dsp_reg_defaults[] = { + { 0x00, 0x00 }, + { 0x01, 0x55 }, + { 0x02, 0x55 }, + { 0x03, 0x00 }, + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x00 }, + { 0x07, 0x00 }, + { 0x08, 0x02 }, + { 0x09, 0x02 }, + { 0x0a, 0x02 }, + { 0x0b, 0x02 }, + { 0x0c, 0x02 }, + { 0x0d, 0x02 }, + { 0x0e, 0x02 }, + { 0x0f, 0x02 }, +}; + +/* DSP mode selection */ +static const char *const ha_dsp_mode_texts[] = {"Mode 1", "Mode 2"}; +static SOC_ENUM_SINGLE_DECL(ha_dsp_mode_enum, HA_DSP_CTRL, 0, + ha_dsp_mode_texts); + +/* Monitor output mux selection */ +static const char *const ha_dsp_monitor_texts[] = {"Off", "ADC", "DAC"}; +static SOC_ENUM_SINGLE_DECL(ha_dsp_monitor_enum, HA_DSP_CTRL, 1, + ha_dsp_monitor_texts); + +static const struct snd_kcontrol_new ha_dsp_monitor_control = + SOC_DAPM_ENUM("Route", ha_dsp_monitor_enum); + +/* Output mixers */ +static const struct snd_kcontrol_new ha_dsp_out1_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT1_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT1_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out2_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT2_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT2_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out3_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT3_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT3_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out4_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT4_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT4_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out5_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT5_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT5_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out6_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT6_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT6_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out7_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT7_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT1_CTRL, 2, 1, 0), +}; +static const struct snd_kcontrol_new ha_dsp_out8_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", HA_DSP_OUT8_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("IN Bypass Switch", HA_DSP_OUT8_CTRL, 2, 1, 0), +}; + +static const struct snd_kcontrol_new ha_dsp_snd_controls[] = { + SOC_SINGLE("ADC Capture Volume", + HA_DSP_ADC_VOL, 0, 0x7f, 0), + SOC_SINGLE("ADC Capture Switch", + HA_DSP_ADC_VOL, 7, 0x01, 1), + + SOC_SINGLE("PCM Playback Volume", + HA_DSP_DAC_VOL, 0, 0x7f, 0), + SOC_SINGLE("PCM Playback Switch", + HA_DSP_DAC_VOL, 7, 0x01, 1), + + SOC_ENUM("DSP Mode", ha_dsp_mode_enum), +}; + +static const struct snd_soc_dapm_widget ha_dsp_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + + SOC_MIXER_ARRAY("OUT1 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out1_mixer_controls), + SOC_MIXER_ARRAY("OUT2 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out2_mixer_controls), + SOC_MIXER_ARRAY("OUT3 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out3_mixer_controls), + SOC_MIXER_ARRAY("OUT4 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out4_mixer_controls), + SOC_MIXER_ARRAY("OUT5 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out5_mixer_controls), + SOC_MIXER_ARRAY("OUT6 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out6_mixer_controls), + SOC_MIXER_ARRAY("OUT7 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out7_mixer_controls), + SOC_MIXER_ARRAY("OUT8 Mixer", SND_SOC_NOPM, 0, 0, + ha_dsp_out8_mixer_controls), + + SND_SOC_DAPM_PGA("OUT1 PGA", HA_DSP_OUT1_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT2 PGA", HA_DSP_OUT2_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT3 PGA", HA_DSP_OUT3_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT4 PGA", HA_DSP_OUT4_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT5 PGA", HA_DSP_OUT5_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT6 PGA", HA_DSP_OUT6_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT7 PGA", HA_DSP_OUT7_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUT8 PGA", HA_DSP_OUT8_CTRL, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Monitor Out Mux", SND_SOC_NOPM, 0, 0, + &ha_dsp_monitor_control), + + /* Input pins */ + SND_SOC_DAPM_INPUT("IN1"), + SND_SOC_DAPM_INPUT("IN2"), + SND_SOC_DAPM_INPUT("IN3"), + SND_SOC_DAPM_INPUT("IN4"), + SND_SOC_DAPM_INPUT("IN5"), + SND_SOC_DAPM_INPUT("IN6"), + SND_SOC_DAPM_INPUT("IN7"), + SND_SOC_DAPM_INPUT("IN8"), + + /* Output pins */ + SND_SOC_DAPM_OUTPUT("OUT1"), + SND_SOC_DAPM_OUTPUT("OUT2"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("OUT4"), + SND_SOC_DAPM_OUTPUT("OUT5"), + SND_SOC_DAPM_OUTPUT("OUT6"), + SND_SOC_DAPM_OUTPUT("OUT7"), + SND_SOC_DAPM_OUTPUT("OUT8"), + SND_SOC_DAPM_OUTPUT("MONITOR"), +}; + +static const struct snd_soc_dapm_route ha_dsp_routes[] = { + /* Inputs to ADC */ + {"ADC", NULL, "IN1"}, + {"ADC", NULL, "IN2"}, + {"ADC", NULL, "IN3"}, + {"ADC", NULL, "IN4"}, + {"ADC", NULL, "IN5"}, + {"ADC", NULL, "IN6"}, + {"ADC", NULL, "IN7"}, + {"ADC", NULL, "IN8"}, + + /* DAC and input bypass paths to outputs */ + {"OUT1 Mixer", "DAC Switch", "DAC"}, + {"OUT1 Mixer", "IN Bypass Switch", "IN1"}, + {"OUT1 PGA", NULL, "OUT1 Mixer"}, + {"OUT1", NULL, "OUT1 PGA"}, + + {"OUT2 Mixer", "DAC Switch", "DAC"}, + {"OUT2 Mixer", "IN Bypass Switch", "IN2"}, + {"OUT2 PGA", NULL, "OUT2 Mixer"}, + {"OUT2", NULL, "OUT2 PGA"}, + + {"OUT3 Mixer", "DAC Switch", "DAC"}, + {"OUT3 Mixer", "IN Bypass Switch", "IN3"}, + {"OUT3 PGA", NULL, "OUT3 Mixer"}, + {"OUT3", NULL, "OUT3 PGA"}, + + {"OUT4 Mixer", "DAC Switch", "DAC"}, + {"OUT4 Mixer", "IN Bypass Switch", "IN4"}, + {"OUT4 PGA", NULL, "OUT4 Mixer"}, + {"OUT4", NULL, "OUT4 PGA"}, + + {"OUT5 Mixer", "DAC Switch", "DAC"}, + {"OUT5 Mixer", "IN Bypass Switch", "IN5"}, + {"OUT5 PGA", NULL, "OUT5 Mixer"}, + {"OUT5", NULL, "OUT5 PGA"}, + + {"OUT6 Mixer", "DAC Switch", "DAC"}, + {"OUT6 Mixer", "IN Bypass Switch", "IN6"}, + {"OUT6 PGA", NULL, "OUT6 Mixer"}, + {"OUT6", NULL, "OUT6 PGA"}, + + {"OUT7 Mixer", "DAC Switch", "DAC"}, + {"OUT7 Mixer", "IN Bypass Switch", "IN7"}, + {"OUT7 PGA", NULL, "OUT7 Mixer"}, + {"OUT7", NULL, "OUT7 PGA"}, + + {"OUT8 Mixer", "DAC Switch", "DAC"}, + {"OUT8 Mixer", "IN Bypass Switch", "IN8"}, + {"OUT8 PGA", NULL, "OUT8 Mixer"}, + {"OUT8", NULL, "OUT8 PGA"}, + + /* Monitor output */ + {"Monitor Out Mux", "ADC", "ADC"}, + {"Monitor Out Mux", "DAC", "DAC"}, + {"MONITOR", NULL, "Monitor Out Mux"}, +}; + +static struct snd_soc_dai_driver ha_dsp_dai = { + .name = "ha-dsp-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + /* We use only 32 Bits for Audio */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + /* We use only 32 Bits for Audio */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, +}; + +static int ha_dsp_probe(struct snd_soc_codec *codec) +{ + int ret; + + codec->control_data = dev_get_regmap(codec->dev, NULL); + ret = snd_soc_codec_set_cache_io(codec, codec->control_data); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + snd_soc_write(codec, HA_DSP_CTRL, HA_DSP_SW_RESET); + + return 0; +} + +static int ha_dsp_remove(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, HA_DSP_CTRL, HA_DSP_SW_RESET); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_ha_dsp = { + .probe = ha_dsp_probe, + .remove = ha_dsp_remove, + + .controls = ha_dsp_snd_controls, + .num_controls = ARRAY_SIZE(ha_dsp_snd_controls), + .dapm_widgets = ha_dsp_widgets, + .num_dapm_widgets = ARRAY_SIZE(ha_dsp_widgets), + .dapm_routes = ha_dsp_routes, + .num_dapm_routes = ARRAY_SIZE(ha_dsp_routes), +}; + +static const struct regmap_config ha_dsp_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x0f, + .reg_defaults = ha_dsp_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ha_dsp_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int ha_dsp_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &ha_dsp_regmap); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to create regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return snd_soc_register_codec(&client->dev, &soc_codec_dev_ha_dsp, + &ha_dsp_dai, 1); +} + +static int ha_dsp_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + + return 0; +} + +/* + * This name/ID is neded to match the DT node for the codec + */ +static const struct i2c_device_id ha_dsp_i2c_id[] = { + { "ha-dsp-audio", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ha_dsp_i2c_id); + +static struct i2c_driver ha_dsp_i2c_driver = { + .driver = { + .name = "ha-dsp-codec", + .owner = THIS_MODULE, + }, + .probe = ha_dsp_i2c_probe, + .remove = ha_dsp_i2c_remove, + .id_table = ha_dsp_i2c_id, +}; + +module_i2c_driver(ha_dsp_i2c_driver); + +MODULE_DESCRIPTION("ASoC HA DSP driver"); +MODULE_AUTHOR("Jarkko Nikula jarkko.nikula@bitmer.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ha-dsp.h b/sound/soc/codecs/ha-dsp.h new file mode 100644 index 0000000..6622f8a --- /dev/null +++ b/sound/soc/codecs/ha-dsp.h @@ -0,0 +1,50 @@ +/* + * ha-dsp.h -- HA DSP ALSA SoC Audio driver + * + * Copyright 2011-2014 HEAD acoustics GmbH + * + * Author: Jarkko Nikula jhnikula@gmail.com + * + * 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. + */ + +#ifndef __HA_DSP_H__ +#define __HA_DSP_H__ + +/* Registers */ + +/* + * Bit 2-1: Monitor output selection: Off, ADC, DAC + * Bit 0: DSP Mode + */ +#define HA_DSP_CTRL 0x00 + +/* + * Bit 7: Mute + * Bit 6-0: Volume + */ +#define HA_DSP_DAC_VOL 0x01 +#define HA_DSP_ADC_VOL 0x02 + +/* + * Bit 2: INx Bypass to OUTx Switch + * Bit 1: DAC to OUTx switch + * Bit 0: Output power + */ +#define HA_DSP_OUT1_CTRL 0x08 +#define HA_DSP_OUT2_CTRL 0x09 +#define HA_DSP_OUT3_CTRL 0x0a +#define HA_DSP_OUT4_CTRL 0x0b +#define HA_DSP_OUT5_CTRL 0x0c +#define HA_DSP_OUT6_CTRL 0x0d +#define HA_DSP_OUT7_CTRL 0x0e +#define HA_DSP_OUT8_CTRL 0x0f + +/* Register bits and values */ + +/* HA_DSP_CTRL */ +#define HA_DSP_SW_RESET 0xff + +#endif
HA DSP card which features a HA DSP audio codec is intended to be connected to TAO-3530 (or BeagleBoard) using McBSP3 for digital audio and I2C bus for codec control. A GPIO signal from CPU to codec is used to request clock signals active.
This machine driver has a special feature to support linked streams where playback and capture can be triggered HW simultaneously. This is implemented for linked streams by activating/deactivating the clock request signal to codec only after both streams are ready to start/stop. For non-linked streams the request signal is active whenever there is a stream active.
The GPIO used to synchronize the clock for playback and record is now configured via the DT as well (no #define any more). When this GPIO is not configured in the DT, the sync clock feature is disabled.
Slave mode is not supported any more (was never really tested). So this version has the slave mode removed.
Also the now recommended card registration via devm_snd_soc_register_card() is used instead of platform_device_add().
This driver is based on an early version provided by Jarkko Nikula.
Signed-off-by: Jarkko Nikula jarkko.nikula@bitmer.com Signed-off-by: Stefan Roese sr@denx.de Cc: Thorsten Eisbein thorsten.eisbein@head-acoustics.de Cc: Lars-Peter Clausen lars@metafoo.de Cc: Mark Brown broonie@kernel.org --- v2: - Added/changed copyright line - Changed authorship (as suggested by Jarkko the original author) - Added Thorsten as maintainer - Moved DT bindings documention into this patch - ha_dsp_codec_slave removed as its currently not used - devm_snd_soc_register_card() used
.../devicetree/bindings/sound/omap3-ha.txt | 27 +++ sound/soc/omap/Kconfig | 11 + sound/soc/omap/Makefile | 2 + sound/soc/omap/ha-dsp-card.c | 250 +++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/omap3-ha.txt create mode 100644 sound/soc/omap/ha-dsp-card.c
diff --git a/Documentation/devicetree/bindings/sound/omap3-ha.txt b/Documentation/devicetree/bindings/sound/omap3-ha.txt new file mode 100644 index 0000000..09eccda --- /dev/null +++ b/Documentation/devicetree/bindings/sound/omap3-ha.txt @@ -0,0 +1,27 @@ +OMAP3 HA (HEAD acoustics) audio controller + +Required properties: +- compatible: "ha,ha-dsp-card" for OMAP3-HA board (TAO3530 based) +- ti,model: Name of the sound card (for example "omap3-ha") +- ti,mcbsp: phandle for the McBSP node +- ti,codec: phandle for the twl4030 audio node +- gpios: GPIO used to sync the clock for playback and record + +Example: +sound2 { + compatible = "ha,ha-dsp-card"; + ti,model = "omap3-ha"; + + /* McBSP3 is used for HA-DSP */ + ti,mcbsp = <&mcbsp3>; + ti,codec = <&soc_audio>; + + /* GPIO to sync the clock for playback and record */ + gpios = <&gpio2 12 GPIO_ACTIVE_LOW>; /* GPIO 44 */ +}; diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index e006593..86cc869 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -117,3 +117,14 @@ config SND_OMAP_SOC_OMAP3_PANDORA select SND_SOC_TWL4030 help Say Y if you want to add support for SoC audio on the OMAP3 Pandora. + +config SND_OMAP_SOC_HA_DSP_CARD + tristate "SoC Audio support for HA DSP add-on card" + depends on TWL4030_CORE && SND_OMAP_SOC + depends on SND_OMAP_SOC_OMAP_TWL4030 + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + select SND_SOC_HA_DSP + help + Say Y if you want to add support for Soc audio on HA DSP add-on card + for TAO-3530/Thunder or BeagleBoard diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index a725905..02c0094 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -21,6 +21,7 @@ snd-soc-omap-abe-twl6040-objs := omap-abe-twl6040.o snd-soc-omap-twl4030-objs := omap-twl4030.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap-hdmi-card-objs := omap-hdmi-card.o +snd-soc-ha-dsp-card-objs := ha-dsp-card.o
obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o @@ -31,3 +32,4 @@ obj-$(CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040) += snd-soc-omap-abe-twl6040.o obj-$(CONFIG_SND_OMAP_SOC_OMAP_TWL4030) += snd-soc-omap-twl4030.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP_HDMI) += snd-soc-omap-hdmi-card.o +obj-$(CONFIG_SND_OMAP_SOC_HA_DSP_CARD) += snd-soc-ha-dsp-card.o diff --git a/sound/soc/omap/ha-dsp-card.c b/sound/soc/omap/ha-dsp-card.c new file mode 100644 index 0000000..36821ca --- /dev/null +++ b/sound/soc/omap/ha-dsp-card.c @@ -0,0 +1,250 @@ +/* + * ha-dsp-card.c -- SoC audio HA-DSP add-on card for TAO-3530 + * + * Copyright 2011-2014 HEAD acoustics GmbH + * + * Authors: + * Jarkko Nikula jarkko.nikula@bitmer.com + * Stefan Roese sr@denx.de + * Thorsten Eisbein thorsten.eisbein@head-acoustics.de + * + * 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. + * + * Maintainer: Thorsten Eisbein thorsten.eisbein@head-acoustics.de + */ + +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#include "omap-mcbsp.h" + +struct ha_dsp_card { + int gpio; + int play_act; + int rec_act; +}; + +static int ha_dsp_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int fmt; + int ret; + + /* Check for allowed channel count */ + switch (params_channels(params)) { + case 2: + case 4: + case 8: + case 16: + fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF; + break; + default: + return -EINVAL; + } + + /* Set CODEC as master */ + fmt |= SND_SOC_DAIFMT_CBM_CFM; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + pr_err("Can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) { + pr_err("Can't set cpu DAI configuration\n"); + return ret; + } + + return 0; +} + +static int ha_dsp_card_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct ha_dsp_card *priv = snd_soc_card_get_drvdata(card); + int start; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + start = 1; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + start = 0; + break; + default: + return -EINVAL; + } + + if (priv->gpio != -1) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + priv->play_act = start; + else + priv->rec_act = start; + + if (snd_pcm_stream_linked(substream)) { + /* + * Set/clear the clock request gpio only after both + * streams have started/stopped in streams are linked + * case + */ + if (priv->play_act == priv->rec_act) + gpio_set_value(priv->gpio, start); + } else { + /* + * Keep clock request gpio active as long as there is + * either playback or capture active in non-linked + * stream case + */ + gpio_set_value(priv->gpio, + (priv->play_act || priv->rec_act)); + } + } + + return 0; +} + +static struct snd_soc_ops ha_dsp_card_ops = { + .hw_params = ha_dsp_card_hw_params, + .trigger = ha_dsp_card_trigger, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link ha_dsp_card_dai = { + .name = "HA-DSP", + .stream_name = "HA-DSP", + .cpu_dai_name = "omap-mcbsp-dai.2", + .platform_name = "omap-pcm-audio", + .codec_dai_name = "ha-dsp-hifi", + .codec_name = "ha-dsp-codec.2-0020", + .ops = &ha_dsp_card_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_ha_dsp_card = { + .name = "tao3530-ha-dsp", + .owner = THIS_MODULE, + .dai_link = &ha_dsp_card_dai, + .num_links = 1, +}; + +static int ha_dsp_card_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_ha_dsp_card; + struct device_node *node = pdev->dev.of_node; + struct device_node *dai_node; + struct ha_dsp_card *priv; + int ret; + + card->dev = &pdev->dev; + + if (!node) { + dev_err(&pdev->dev, "No DT node provided\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + ret = of_get_gpio(node, 0); + if (ret < 0) { + if (ret == -EPROBE_DEFER) + return ret; + + /* No GPIO provided, don't enable GPIO sync */ + priv->gpio = -1; + goto cont_no_gpio; + } + + /* GPIO setup */ + priv->gpio = ret; + ret = devm_gpio_request(&pdev->dev, priv->gpio, "HA-DSP clock request"); + if (ret) { + dev_err(&pdev->dev, "Failed to request GPIO %u\n", priv->gpio); + return ret; + } + + ret = gpio_direction_output(priv->gpio, 0); + if (ret) { + dev_err(&pdev->dev, "failed to set pin direction\n"); + return -EINVAL; + } + dev_info(&pdev->dev, "Using GPIO %d as sync clock\n", priv->gpio); + +cont_no_gpio: + if (snd_soc_of_parse_card_name(card, "ti,model")) { + dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + + dai_node = of_parse_phandle(node, "ti,mcbsp", 0); + if (!dai_node) { + dev_err(&pdev->dev, "McBSP node is not provided\n"); + return -EINVAL; + } + ha_dsp_card_dai.cpu_dai_name = NULL; + ha_dsp_card_dai.cpu_of_node = dai_node; + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ha_dsp_card_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id ha_dsp_card_of_match[] = { + { .compatible = "ha,ha-dsp-card", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ha_dsp_card_of_match); + +static struct platform_driver ha_dsp_card_driver = { + .driver = { + .name = "ha-dsp-card", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = ha_dsp_card_of_match, + }, + .probe = ha_dsp_card_probe, + .remove = ha_dsp_card_remove, +}; + +module_platform_driver(ha_dsp_card_driver); + +MODULE_AUTHOR("Jarkko Nikula jarkko.nikula@bitmer.com"); +MODULE_DESCRIPTION("ALSA SoC TAO3530-HA-DSP"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ha-dsp-card");
On Tue, May 06, 2014 at 12:36:36PM +0200, Stefan Roese wrote:
HA DSP card which features a HA DSP audio codec is intended to be connected to TAO-3530 (or BeagleBoard) using McBSP3 for digital audio and I2C bus for codec control. A GPIO signal from CPU to codec is used to request clock signals active.
So, it's been a while. In part this is because I'm still really not happy with the exceptionally generic name here - there must be some kind of name that's more specific than the entire company here? It seems awfully generic.
- /* Set CODEC as master */
- fmt |= SND_SOC_DAIFMT_CBM_CFM;
- /* Set codec DAI configuration */
- ret = snd_soc_dai_set_fmt(codec_dai, fmt);
- if (ret < 0) {
pr_err("Can't set codec DAI configuration\n");
return ret;
- }
- /* Set cpu DAI configuration */
- ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
- if (ret < 0) {
pr_err("Can't set cpu DAI configuration\n");
return ret;
- }
Just initialise the format in the DAI link and leave the error checking for the number of channels as the only code here.
- ret = devm_gpio_request(&pdev->dev, priv->gpio, "HA-DSP clock request");
- if (ret) {
dev_err(&pdev->dev, "Failed to request GPIO %u\n", priv->gpio);
return ret;
- }
- ret = gpio_direction_output(priv->gpio, 0);
- if (ret) {
dev_err(&pdev->dev, "failed to set pin direction\n");
return -EINVAL;
- }
devm_gpio_request_one()
On Tue, May 06, 2014 at 12:36:35PM +0200, Stefan Roese wrote:
This codec driver template represents an I2C controlled multichannel audio
It's not really a template, it's a driver for a specific device.
Again, naming is the biggest issue here - it's going to get confusing with a second product. While the code is pretty clean the template comment and the really generic name make this seem like it's more of a placeholder for something that people will hack on to deploy than an actual driver that someone can pick up and use.
I'm also missing any real management of the actual DSP, this appears to only control some analogue around the DSP and a single mode switch which is a bit odd.
codec that has many typical ASoC codec driver features like volume controls, mixer stages, mux selection, output power control, in-codec audio routings, codec bias management and DAI link configuration.
+static int ha_dsp_probe(struct snd_soc_codec *codec) +{
- int ret;
- codec->control_data = dev_get_regmap(codec->dev, NULL);
- ret = snd_soc_codec_set_cache_io(codec, codec->control_data);
- if (ret != 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
- }
This will be done automatically by the core.
- snd_soc_write(codec, HA_DSP_CTRL, HA_DSP_SW_RESET);
I'd expect this to be done in the I2C level probe.
+/*
- This name/ID is neded to match the DT node for the codec
- */
+static const struct i2c_device_id ha_dsp_i2c_id[] = {
- { "ha-dsp-audio", 0 },
- { }
+}; +MODULE_DEVICE_TABLE(i2c, ha_dsp_i2c_id);
For use with DT you should have an explicit compatible string. There's also no binding document here that I can see.
participants (2)
-
Mark Brown
-
Stefan Roese