greybus: Audio: Add skeleton code for GB virtual codec driver
authorVaibhav Agarwal <vaibhav.agarwal@linaro.org>
Mon, 23 Nov 2015 10:27:45 +0000 (15:57 +0530)
committerGreg Kroah-Hartman <gregkh@google.com>
Tue, 24 Nov 2015 00:38:22 +0000 (16:38 -0800)
This patch adds gb-codec driver with static information for
DAPM widgets, controls & dapm_routes.

Including some changes in kernel code(machine driver):
- Able to register codec and glue it with existing sound card successfully.
- Able to view & modify mixer controls:
        (volume/mute[left/right][input/output])
- Able to view DAPM widgets registered via /debug interface.
- Able to establish DAPM path for playback.

Since, FE<->BE path not yet verified with default jetson build,
registering GB DAI as normal DAI link to verify GB virtual codec
specific DAPM path.

Signed-off-by: Vaibhav Agarwal <vaibhav.agarwal@linaro.org>
Reviewed-by: Mark Greer <mgreer@animalcreek.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
drivers/staging/greybus/Makefile
drivers/staging/greybus/audio-codec.c [new file with mode: 0644]
drivers/staging/greybus/audio.h [new file with mode: 0644]

index 2c1f9746bae17b3fb407112a6faa6abc68920060..40e22ec810ae7a4de02e79a7fa43888e71d99360 100644 (file)
@@ -31,6 +31,7 @@ gb-raw-y := raw.o
 gb-hid-y := hid.o
 gb-es2-y := es2.o
 gb-db3-y := db3-platform.o
+gb-audio-codec-y := audio-codec.o
 
 obj-m += greybus.o
 obj-m += gb-phy.o
@@ -42,6 +43,7 @@ obj-m += gb-hid.o
 obj-m += gb-raw.o
 obj-m += gb-es2.o
 obj-m += gb-db3.o
+obj-m += gb-audio-codec.o
 
 KERNELVER              ?= $(shell uname -r)
 KERNELDIR              ?= /lib/modules/$(KERNELVER)/build
diff --git a/drivers/staging/greybus/audio-codec.c b/drivers/staging/greybus/audio-codec.c
new file mode 100644 (file)
index 0000000..2bc2309
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * Greybus audio driver
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+#include <linux/module.h>
+
+#include "audio.h"
+
+static int gbcodec_event_spk(struct snd_soc_dapm_widget *w,
+                                       struct snd_kcontrol *k, int event)
+{
+       /* Ensure GB speaker is connected */
+
+       return 0;
+}
+
+static int gbcodec_event_hp(struct snd_soc_dapm_widget *w,
+                                       struct snd_kcontrol *k, int event)
+{
+       /* Ensure GB module supports jack slot */
+
+       return 0;
+}
+
+static int gbcodec_event_int_mic(struct snd_soc_dapm_widget *w,
+                                       struct snd_kcontrol *k, int event)
+{
+       /* Ensure GB module supports jack slot */
+
+       return 0;
+}
+
+static const struct snd_kcontrol_new gbcodec_snd_controls[] = {
+       SOC_DOUBLE("Playback Mute", GBCODEC_MUTE_REG, 0, 1, 1, 1),
+       SOC_DOUBLE("Capture Mute", GBCODEC_MUTE_REG, 4, 5, 1, 1),
+       SOC_DOUBLE_R("Playback Volume", GBCODEC_PB_LVOL_REG,
+                    GBCODEC_PB_RVOL_REG, 0, 127, 0),
+       SOC_DOUBLE_R("Capture Volume", GBCODEC_CAP_LVOL_REG,
+                    GBCODEC_CAP_RVOL_REG, 0, 127, 0),
+};
+
+static const struct snd_kcontrol_new spk_amp_ctl =
+       SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 0, 1, 0);
+
+static const struct snd_kcontrol_new hp_amp_ctl =
+       SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 1, 1, 0);
+
+static const struct snd_kcontrol_new mic_adc_ctl =
+       SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 4, 1, 0);
+
+/* APB1-GBSPK source */
+static const char * const gbcodec_apb1_src[] = {"Stereo", "Left", "Right"};
+
+static const SOC_ENUM_SINGLE_DECL(
+       gbcodec_apb1_rx_enum, GBCODEC_APB1_MUX_REG, 0, gbcodec_apb1_src);
+
+static const struct snd_kcontrol_new gbcodec_apb1_rx_mux =
+       SOC_DAPM_ENUM("APB1 source", gbcodec_apb1_rx_enum);
+
+static const SOC_ENUM_SINGLE_DECL(
+       gbcodec_mic_enum, GBCODEC_APB1_MUX_REG, 4, gbcodec_apb1_src);
+
+static const struct snd_kcontrol_new gbcodec_mic_mux =
+       SOC_DAPM_ENUM("MIC source", gbcodec_mic_enum);
+
+static const struct snd_soc_dapm_widget gbcodec_dapm_widgets[] = {
+       SND_SOC_DAPM_SPK("Spk", gbcodec_event_spk),
+       SND_SOC_DAPM_SPK("HP", gbcodec_event_hp),
+       SND_SOC_DAPM_MIC("Int Mic", gbcodec_event_int_mic),
+
+       SND_SOC_DAPM_OUTPUT("SPKOUT"),
+       SND_SOC_DAPM_OUTPUT("HPOUT"),
+
+       SND_SOC_DAPM_INPUT("MIC"),
+       SND_SOC_DAPM_INPUT("HSMIC"),
+
+       SND_SOC_DAPM_SWITCH("SPK Amp", SND_SOC_NOPM, 0, 0, &spk_amp_ctl),
+       SND_SOC_DAPM_SWITCH("HP Amp", SND_SOC_NOPM, 0, 0, &hp_amp_ctl),
+       SND_SOC_DAPM_SWITCH("MIC ADC", SND_SOC_NOPM, 0, 0, &mic_adc_ctl),
+
+       SND_SOC_DAPM_PGA("SPK DAC", SND_SOC_NOPM, 0, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("HP DAC", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("SPK Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("APB1_TX Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+       SND_SOC_DAPM_MUX("APB1_RX Mux", SND_SOC_NOPM, 0, 0,
+                        &gbcodec_apb1_rx_mux),
+       SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &gbcodec_mic_mux),
+
+       SND_SOC_DAPM_AIF_IN("APB1RX", "APBridgeA1 Playback", 0, SND_SOC_NOPM, 0,
+                           0),
+       SND_SOC_DAPM_AIF_OUT("APB1TX", "APBridgeA1 Capture", 0, SND_SOC_NOPM, 0,
+                            0),
+};
+
+static const struct snd_soc_dapm_route gbcodec_dapm_routes[] = {
+       /* Playback path */
+       {"Spk", NULL, "SPKOUT"},
+       {"SPKOUT", NULL, "SPK Amp"},
+       {"SPK Amp", "Switch", "SPK DAC"},
+       {"SPK DAC", NULL, "SPK Mixer"},
+
+       {"HP", NULL, "HPOUT"},
+       {"HPOUT", NULL, "HP Amp"},
+       {"HP Amp", "Switch", "HP DAC"},
+       {"HP DAC", NULL, "HP Mixer"},
+
+       {"SPK Mixer", NULL, "APB1_RX Mux"},
+       {"HP Mixer", NULL, "APB1_RX Mux"},
+
+       {"APB1_RX Mux", "Left", "APB1RX"},
+       {"APB1_RX Mux", "Right", "APB1RX"},
+       {"APB1_RX Mux", "Stereo", "APB1RX"},
+
+       /* Capture path */
+       {"MIC", NULL, "Int Mic"},
+       {"MIC", NULL, "MIC Mux"},
+       {"MIC Mux", "Left", "MIC ADC"},
+       {"MIC Mux", "Right", "MIC ADC"},
+       {"MIC Mux", "Stereo", "MIC ADC"},
+       {"MIC ADC", "Switch", "APB1_TX Mixer"},
+       {"APB1_TX Mixer", NULL, "APB1TX"}
+};
+
+static int gbcodec_startup(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *dai)
+{
+       return 0;
+}
+
+static void gbcodec_shutdown(struct snd_pcm_substream *substream,
+                            struct snd_soc_dai *dai)
+{
+}
+
+static int gbcodec_hw_params(struct snd_pcm_substream *substream,
+                            struct snd_pcm_hw_params *hwparams,
+                            struct snd_soc_dai *dai)
+{
+       return 0;
+}
+
+static int gbcodec_prepare(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *dai)
+{
+       return 0;
+}
+
+static int gbcodec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+       return 0;
+}
+
+static int gbcodec_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+       return 0;
+}
+
+static struct snd_soc_dai_ops gbcodec_dai_ops = {
+       .startup = gbcodec_startup,
+       .shutdown = gbcodec_shutdown,
+       .hw_params = gbcodec_hw_params,
+       .prepare = gbcodec_prepare,
+       .set_fmt = gbcodec_set_dai_fmt,
+       .digital_mute = gbcodec_digital_mute,
+};
+
+static struct snd_soc_dai_driver gbcodec_dai = {
+               .playback = {
+                       .stream_name = "APBridgeA1 Playback",
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
+                       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+               },
+               .capture = {
+                       .stream_name = "APBridgeA1 Capture",
+                       .channels_min = 2,
+                       .channels_max = 2,
+                       .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
+                       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+               },
+               .ops = &gbcodec_dai_ops,
+};
+
+static int gbcodec_probe(struct snd_soc_codec *codec)
+{
+       struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+
+       gbcodec->codec = codec;
+
+       return 0;
+}
+
+static int gbcodec_remove(struct snd_soc_codec *codec)
+{
+       /* Empty function for now */
+       return 0;
+}
+
+static int gbcodec_write(struct snd_soc_codec *codec, unsigned int reg,
+                        unsigned int value)
+{
+       int ret = 0;
+       struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+       u8 *gbcodec_reg = gbcodec->reg;
+
+       if (reg == SND_SOC_NOPM)
+               return 0;
+
+       if (reg >= GBCODEC_REG_COUNT)
+               return 0;
+
+       gbcodec_reg[reg] = value;
+       dev_dbg(codec->dev, "reg[%d] = 0x%x\n", reg, value);
+
+       return ret;
+}
+
+static unsigned int gbcodec_read(struct snd_soc_codec *codec,
+                                unsigned int reg)
+{
+       unsigned int val = 0;
+
+       struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+       u8 *gbcodec_reg = gbcodec->reg;
+
+       if (reg == SND_SOC_NOPM)
+               return 0;
+
+       if (reg >= GBCODEC_REG_COUNT)
+               return 0;
+
+       val = gbcodec_reg[reg];
+       dev_dbg(codec->dev, "reg[%d] = 0x%x\n", reg, val);
+
+       return val;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_gbcodec = {
+       .probe = gbcodec_probe,
+       .remove = gbcodec_remove,
+
+       .read = gbcodec_read,
+       .write = gbcodec_write,
+
+       .reg_cache_size = GBCODEC_REG_COUNT,
+       .reg_cache_default = gbcodec_reg_defaults,
+       .reg_word_size = 1,
+
+       .idle_bias_off = true,
+
+       .controls = gbcodec_snd_controls,
+       .num_controls = ARRAY_SIZE(gbcodec_snd_controls),
+       .dapm_widgets = gbcodec_dapm_widgets,
+       .num_dapm_widgets = ARRAY_SIZE(gbcodec_dapm_widgets),
+       .dapm_routes = gbcodec_dapm_routes,
+       .num_dapm_routes = ARRAY_SIZE(gbcodec_dapm_routes),
+};
+
+static int gbaudio_codec_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct gbaudio_codec_info *gbcodec;
+       char dai_name[NAME_SIZE];
+
+       gbcodec = devm_kzalloc(&pdev->dev, sizeof(struct gbaudio_codec_info),
+                              GFP_KERNEL);
+       if (!gbcodec)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, gbcodec);
+
+       snprintf(dai_name, NAME_SIZE, "%s.%d", "gbcodec_pcm", pdev->id);
+       gbcodec_dai.name = dai_name;
+
+       ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_gbcodec,
+                                    &gbcodec_dai, 1);
+       if (!ret)
+               gbcodec->registered = 1;
+
+       return ret;
+}
+
+static const struct of_device_id gbcodec_of_match[] = {
+       { .compatible = "greybus,codec", },
+       {},
+};
+
+static int gbaudio_codec_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_codec(&pdev->dev);
+
+       return 0;
+}
+
+static struct platform_driver gbaudio_codec_driver = {
+       .driver = {
+               .name = "gbaudio-codec",
+               .owner = THIS_MODULE,
+               .of_match_table = gbcodec_of_match,
+       },
+       .probe = gbaudio_codec_probe,
+       .remove   = gbaudio_codec_remove,
+};
+module_platform_driver(gbaudio_codec_driver);
+
+MODULE_DESCRIPTION("Greybus Audio virtual codec driver");
+MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:gbaudio-codec");
diff --git a/drivers/staging/greybus/audio.h b/drivers/staging/greybus/audio.h
new file mode 100644 (file)
index 0000000..5dec00d
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Greybus audio driver
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __LINUX_GBAUDIO_H
+#define __LINUX_GBAUDIO_H
+
+#ifdef __KERNEL__
+
+#include <sound/soc.h>
+
+#define NAME_SIZE      32
+
+enum {
+       APB1_PCM = 0,
+       APB2_PCM,
+       NUM_CODEC_DAIS,
+};
+
+enum gbcodec_reg_index {
+       GBCODEC_CTL_REG,
+       GBCODEC_MUTE_REG,
+       GBCODEC_PB_LVOL_REG,
+       GBCODEC_PB_RVOL_REG,
+       GBCODEC_CAP_LVOL_REG,
+       GBCODEC_CAP_RVOL_REG,
+       GBCODEC_APB1_MUX_REG,
+       GBCODEC_APB2_MUX_REG,
+       GBCODEC_REG_COUNT
+};
+
+/* bit 0-SPK, 1-HP, 2-DAC,
+ * 4-MIC, 5-HSMIC, 6-MIC2
+ */
+#define GBCODEC_CTL_REG_DEFAULT                0x00
+
+/* bit 0,1 - APB1-PB-L/R
+ * bit 2,3 - APB2-PB-L/R
+ * bit 4,5 - APB1-Cap-L/R
+ * bit 6,7 - APB2-Cap-L/R
+ */
+#define        GBCODEC_MUTE_REG_DEFAULT        0x00
+
+/* 0-127 steps */
+#define        GBCODEC_PB_VOL_REG_DEFAULT      0x00
+#define        GBCODEC_CAP_VOL_REG_DEFAULT     0x00
+
+/* bit 0,1,2 - PB stereo, left, right
+ * bit 8,9,10 - Cap stereo, left, right
+ */
+#define GBCODEC_APB1_MUX_REG_DEFAULT   0x00
+#define GBCODEC_APB2_MUX_REG_DEFAULT   0x00
+
+static const u8 gbcodec_reg_defaults[GBCODEC_REG_COUNT] = {
+       GBCODEC_CTL_REG_DEFAULT,
+       GBCODEC_MUTE_REG_DEFAULT,
+       GBCODEC_PB_VOL_REG_DEFAULT,
+       GBCODEC_PB_VOL_REG_DEFAULT,
+       GBCODEC_CAP_VOL_REG_DEFAULT,
+       GBCODEC_CAP_VOL_REG_DEFAULT,
+       GBCODEC_APB1_MUX_REG_DEFAULT,
+       GBCODEC_APB2_MUX_REG_DEFAULT,
+};
+
+struct gbaudio_codec_info {
+       struct snd_soc_codec *codec;
+
+       bool usable;
+       u8 reg[GBCODEC_REG_COUNT];
+       int registered;
+
+       int num_kcontrols;
+       int num_dapm_widgets;
+       int num_dapm_routes;
+       struct snd_kcontrol_new *kctls;
+       struct snd_soc_dapm_widget *widgets;
+       struct snd_soc_dapm_route *routes;
+       struct mutex lock;
+};
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_GBAUDIO_H */