From d3d2af51f9c2f29a0bf5df278503820004d71e36 Mon Sep 17 00:00:00 2001 From: Vaibhav Agarwal Date: Mon, 23 Nov 2015 15:57:45 +0530 Subject: [PATCH] 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 Reviewed-by: Mark Greer Signed-off-by: Greg Kroah-Hartman --- drivers/staging/greybus/Makefile | 2 + drivers/staging/greybus/audio-codec.c | 315 ++++++++++++++++++++++++++ drivers/staging/greybus/audio.h | 86 +++++++ 3 files changed, 403 insertions(+) create mode 100644 drivers/staging/greybus/audio-codec.c create mode 100644 drivers/staging/greybus/audio.h diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index 2c1f9746bae1..40e22ec810ae 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -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 index 000000000000..2bc23095ffd0 --- /dev/null +++ b/drivers/staging/greybus/audio-codec.c @@ -0,0 +1,315 @@ +/* + * Greybus audio driver + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ +#include + +#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 "); +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 index 000000000000..5dec00d63e27 --- /dev/null +++ b/drivers/staging/greybus/audio.h @@ -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 + +#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 */