greybus: Audio: Add skeleton code for GB virtual codec driver
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>
This commit is contained in:
parent
3823c61479
commit
d3d2af51f9
|
@ -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
|
||||
|
|
|
@ -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");
|
|
@ -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 */
|
Loading…
Reference in New Issue