From patchwork Fri Sep 2 11:04:52 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Subhransu S. Prusty" X-Patchwork-Id: 9311341 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id A39EF60756 for ; Fri, 2 Sep 2016 16:03:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 92AC6297DA for ; Fri, 2 Sep 2016 16:03:36 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 87499297DE; Fri, 2 Sep 2016 16:03:36 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 15725297DA for ; Fri, 2 Sep 2016 16:03:35 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 341D02677FC; Fri, 2 Sep 2016 18:03:34 +0200 (CEST) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 66F79267643; Fri, 2 Sep 2016 16:15:07 +0200 (CEST) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id E86B926762E; Fri, 2 Sep 2016 16:15:04 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) by alsa0.perex.cz (Postfix) with ESMTP id 59D85267799 for ; Fri, 2 Sep 2016 13:10:45 +0200 (CEST) Received: from fmsmga003.fm.intel.com ([10.253.24.29]) by orsmga105.jf.intel.com with ESMTP; 02 Sep 2016 04:10:43 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.30,270,1470726000"; d="scan'208";a="756489612" Received: from subhransu-desktop.iind.intel.com ([10.223.96.24]) by FMSMGA003.fm.intel.com with ESMTP; 02 Sep 2016 04:10:42 -0700 From: "Subhransu S. Prusty" To: alsa-devel@alsa-project.org Date: Fri, 2 Sep 2016 16:34:52 +0530 Message-Id: <1472814300-5629-4-git-send-email-subhransu.s.prusty@intel.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1472814300-5629-1-git-send-email-subhransu.s.prusty@intel.com> References: <1472814300-5629-1-git-send-email-subhransu.s.prusty@intel.com> Cc: tiwai@suse.de, lgirdwood@gmail.com, patches.audio@intel.com, broonie@kernel.org, Vinod Koul , "Subhransu S. Prusty" Subject: [alsa-devel] [PATCH v2 03/11] ASoC: hdac: Add a generic hdac driver framework X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds support to register the hdac driver with the HDA bus and enumerate the driver if a device is found. It uses the hdac core helper APIs to parse the HDA widgets and identifies the number of mapping dapm widgets to be created. Based on the ADCs and DACs queries the codec dais are allocated and registers with asoc. The AFG node is power managed through set_bias_level callback. Format is programmed though hw_params dai ops. Stream tag is programmed thorugh the set_tdm_slot callback. Signed-off-by: Subhransu S. Prusty Signed-off-by: Vinod Koul --- sound/hda/ext/hdac_ext_bus.c | 7 +- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdac_generic.c | 421 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/hdac_generic.h | 25 +++ 5 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 sound/soc/codecs/hdac_generic.c create mode 100644 sound/soc/codecs/hdac_generic.h diff --git a/sound/hda/ext/hdac_ext_bus.c b/sound/hda/ext/hdac_ext_bus.c index fe25771..85ccc18 100644 --- a/sound/hda/ext/hdac_ext_bus.c +++ b/sound/hda/ext/hdac_ext_bus.c @@ -162,7 +162,12 @@ int snd_hdac_ext_bus_device_init(struct hdac_ext_bus *ebus, int addr) hdev->dev.release = default_release; INIT_LIST_HEAD(&edev->widget_list); - snd_hdac_ext_parse_widgets(edev); + ret = snd_hdac_ext_parse_widgets(edev); + if (ret < 0) { + dev_err(bus->dev, "Failed to parse widgets with err: %d\n", + ret); + return ret; + } ret = snd_hdac_device_register(hdev); if (ret) { diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1cd6ab3..5e688ee 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -71,6 +71,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C select SND_SOC_GTM601 + select SND_SOC_HDAC_GENERIC select SND_SOC_HDAC_HDMI select SND_SOC_ICS43432 select SND_SOC_INNO_RK3036 @@ -523,6 +524,10 @@ config SND_SOC_ES8328_SPI config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' +config SND_SOC_HDAC_GENERIC + tristate + select SND_HDA_EXT_CORE + config SND_SOC_HDAC_HDMI tristate select SND_HDA_EXT_CORE diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 58036af..1d1001c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -65,6 +65,7 @@ snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o snd-soc-gtm601-objs := gtm601.o +snd-soc-hdac-generic-objs := hdac_generic.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-ics43432-objs := ics43432.o snd-soc-inno-rk3036-objs := inno_rk3036.o @@ -287,6 +288,7 @@ obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o +obj-$(CONFIG_SND_SOC_HDAC_GENERIC) += snd-soc-hdac-generic.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c new file mode 100644 index 0000000..af2db0b --- /dev/null +++ b/sound/soc/codecs/hdac_generic.c @@ -0,0 +1,421 @@ +/* + * hdac_generic.c - ASoc HDA generic codec driver + * + * Copyright (C) 2016 Intel Corp + * Author: Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../hda/local.h" +#include "hdac_generic.h" + +#define HDA_MAX_CVTS 10 + +struct hdac_generic_dai_map { + struct hdac_ext_codec_widget *cvt; +}; + +struct hdac_generic_priv { + struct hdac_generic_dai_map dai_map[HDA_MAX_CVTS]; + unsigned int num_pins; + unsigned int num_adcs; + unsigned int num_dacs; + unsigned int num_dapm_widgets; +}; + +static void hdac_generic_set_power_state(struct hdac_ext_device *edev, + hda_nid_t nid, unsigned int pwr_state) +{ + /* TODO: check D0sup bit before setting this */ + if (!snd_hdac_check_power_state(&edev->hdac, nid, pwr_state)) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_POWER_STATE, pwr_state); +} + +static void hdac_generic_calc_dapm_widgets(struct hdac_ext_device *edev) +{ + struct hdac_generic_priv *hdac_priv = edev->private_data; + struct hdac_ext_codec_widget *wid; + + if (list_empty(&edev->widget_list)) + return; + + /* + * PIN widget with output capable are represented with an additional + * virtual mux widgets. + */ + list_for_each_entry(wid, &edev->widget_list, head) { + switch (wid->type) { + case AC_WID_AUD_IN: + hdac_priv->num_dapm_widgets++; + hdac_priv->num_adcs++; + break; + + case AC_WID_AUD_OUT: + hdac_priv->num_dapm_widgets++; + hdac_priv->num_dacs++; + break; + + case AC_WID_PIN: + hdac_priv->num_pins++; + /* + * PIN widgets are represented with dapm_pga and + * dapm_output. + */ + hdac_priv->num_dapm_widgets += 2; + + if (is_input_pin(&edev->hdac, wid->nid)) + continue; + + /* + * PIN widget with output capable are represented + * with an additional virtual mux widgets. + */ + if (wid->num_inputs > 1) + hdac_priv->num_dapm_widgets++; + + break; + + case AC_WID_AUD_MIX: + hdac_priv->num_dapm_widgets++; + break; + + case AC_WID_AUD_SEL: + hdac_priv->num_dapm_widgets++; + break; + + case AC_WID_POWER: + hdac_priv->num_dapm_widgets++; + break; + + case AC_WID_BEEP: + /* + * Beep widgets are represented with a siggen and + * pga dapm widgets + */ + hdac_priv->num_dapm_widgets += 2; + break; + + default: + dev_warn(&edev->hdac.dev, "no dapm widget for type: %d\n", + wid->type); + break; + } + } +} + +static int hdac_generic_set_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hparams, struct snd_soc_dai *dai) +{ + struct hdac_ext_device *edev = snd_soc_dai_get_drvdata(dai); + struct hdac_generic_priv *hdac_priv = edev->private_data; + struct hdac_generic_dai_map *dai_map = &hdac_priv->dai_map[dai->id]; + u32 format; + + format = snd_hdac_calc_stream_format(params_rate(hparams), + params_channels(hparams), params_format(hparams), + 32, 0); + + snd_hdac_codec_write(&edev->hdac, dai_map->cvt->nid, 0, + AC_VERB_SET_STREAM_FORMAT, format); + + return 0; +} + +static int hdac_generic_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct hdac_ext_device *edev = snd_soc_dai_get_drvdata(dai); + struct hdac_generic_priv *hdac_priv = edev->private_data; + struct hdac_generic_dai_map *dai_map = &hdac_priv->dai_map[dai->id]; + int val; + + dev_dbg(&edev->hdac.dev, "%s: strm_tag: %d\n", __func__, tx_mask); + + val = snd_hdac_codec_read(&edev->hdac, dai_map->cvt->nid, 0, + AC_VERB_GET_CONV, 0); + snd_hdac_codec_write(&edev->hdac, dai_map->cvt->nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, + (val & 0xf0) | (tx_mask << 4)); + + return 0; +} + +static int hdac_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec); + struct hdac_device *hdac = &edev->hdac; + + dev_dbg(&edev->hdac.dev, "%s: level: %d\n", __func__, level); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + hdac_generic_set_power_state(edev, hdac->afg, AC_PWRST_D0); + break; + + case SND_SOC_BIAS_OFF: + hdac_generic_set_power_state(edev, hdac->afg, AC_PWRST_D3); + break; + + default: + dev_info(&edev->hdac.dev, "Bias level %d not handled\n", level); + break; + } + + return 0; +} + +static int hdac_codec_probe(struct snd_soc_codec *codec) +{ + struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(&codec->component); + + edev->scodec = codec; + + /* TODO: create widget, route and controls */ + /* TODO: jack sense */ + + /* Imp: Store the card pointer in hda_codec */ + edev->card = dapm->card->snd_card; + + /* TODO: runtime PM */ + return 0; +} + +static struct snd_soc_codec_driver hdac_generic_codec = { + .probe = hdac_codec_probe, + .set_bias_level = hdac_codec_set_bias_level, +}; + +static struct snd_soc_dai_ops hdac_generic_ops = { + .hw_params = hdac_generic_set_hw_params, + .set_tdm_slot = hdac_generic_set_tdm_slot, +}; + +static int fill_codec_dai_stream_info(struct hdac_device *hdac, + struct snd_soc_pcm_stream *strm, + struct hdac_ext_codec_widget *widget) +{ + u32 rates, bps; + unsigned int rate_max = 192000, rate_min = 8000; + u64 formats; + int ret; + + /* Set caps based on capability queried from the converter */ + ret = snd_hdac_query_supported_pcm(hdac, widget->nid, + &rates, &formats, &bps); + if (ret) + return ret; + + strm->formats = formats; + strm->rates = rates; + strm->rate_max = rate_max; + strm->rate_min = rate_min; + + /* Only stereo for now */ + strm->channels_min = 2; + strm->channels_max = 2; + + return 0; +} + +static int fill_codec_dai_desc(struct hdac_device *hdac, + struct snd_soc_dai_driver *codec_dai, + struct hdac_ext_codec_widget *widget) +{ + char dai_name[HDAC_GENERIC_NAME_SIZE]; + + sprintf(dai_name, "%x-aif%d", hdac->vendor_id, widget->nid); + + codec_dai->name = devm_kstrdup(&hdac->dev, dai_name, + GFP_KERNEL); + if (!codec_dai->name) + return -ENOMEM; + + codec_dai->ops = &hdac_generic_ops; + codec_dai->dobj.private = widget; + + return 0; +} + +/* Codec dai name: -aif */ +static int hdac_generic_create_dais(struct hdac_ext_device *edev, + struct snd_soc_dai_driver **dais, int num_dais) +{ + struct hdac_device *hdac = &edev->hdac; + struct hdac_generic_priv *hdac_priv = edev->private_data; + struct snd_soc_dai_driver *codec_dais; + char stream_name[HDAC_GENERIC_NAME_SIZE]; + struct hdac_ext_codec_widget *widget; + int i = 0; + int ret; + + codec_dais = devm_kcalloc(&hdac->dev, num_dais, + sizeof(*codec_dais), GFP_KERNEL); + if (!codec_dais) + return -ENOMEM; + + /* Iterate over the input adc and dac list to create DAIs */ + list_for_each_entry(widget, &edev->widget_list, head) { + switch (widget->type) { + case AC_WID_AUD_IN: + ret = fill_codec_dai_desc(hdac, &codec_dais[i], widget); + if (ret < 0) + return ret; + + hdac_priv->dai_map[i].cvt = widget; + snprintf(stream_name, sizeof(stream_name), + "Analog Capture-%d", widget->nid); + codec_dais[i].capture.stream_name = + devm_kstrdup(&hdac->dev, stream_name, + GFP_KERNEL); + if (!codec_dais[i].capture.stream_name) + return -ENOMEM; + + ret = fill_codec_dai_stream_info(hdac, + &codec_dais[i].capture, widget); + if (ret < 0) + return ret; + + i++; + break; + + case AC_WID_AUD_OUT: + ret = fill_codec_dai_desc(hdac, &codec_dais[i], widget); + if (ret < 0) + return ret; + hdac_priv->dai_map[i].cvt = widget; + if (widget->caps & AC_WCAP_DIGITAL) + snprintf(stream_name, sizeof(stream_name), + "Digital Playback-%d", widget->nid); + else + snprintf(stream_name, sizeof(stream_name), + "Analog Playback-%d", widget->nid); + + codec_dais[i].playback.stream_name = + devm_kstrdup(&hdac->dev, stream_name, + GFP_KERNEL); + if (!codec_dais[i].playback.stream_name) + return -ENOMEM; + + ret = fill_codec_dai_stream_info(hdac, + &codec_dais[i].playback, widget); + if (ret < 0) + return ret; + + i++; + + break; + default: + dev_warn(&hdac->dev, "Invalid widget type: %d\n", + widget->type); + break; + } + } + + *dais = codec_dais; + + return 0; +} + +static int hdac_generic_dev_probe(struct hdac_ext_device *edev) +{ + struct hdac_device *codec = &edev->hdac; + struct hdac_generic_priv *hdac_priv; + struct snd_soc_dai_driver *codec_dais = NULL; + int num_dais = 0; + int ret = 0; + + hdac_priv = devm_kzalloc(&codec->dev, sizeof(*hdac_priv), GFP_KERNEL); + if (hdac_priv == NULL) + return -ENOMEM; + + edev->private_data = hdac_priv; + dev_set_drvdata(&codec->dev, edev); + + hdac_generic_calc_dapm_widgets(edev); + + if (!hdac_priv->num_pins || + ((!hdac_priv->num_adcs) && + (!hdac_priv->num_dacs))) { + + dev_err(&codec->dev, "No port widgets or cvt widgets"); + return -EIO; + } + + num_dais = hdac_priv->num_adcs + hdac_priv->num_dacs; + + ret = hdac_generic_create_dais(edev, &codec_dais, num_dais); + if (ret < 0) { + dev_err(&codec->dev, "Failed to create dais with err: %d\n", + ret); + return ret; + } + + /* ASoC specific initialization */ + return snd_soc_register_codec(&codec->dev, &hdac_generic_codec, + codec_dais, num_dais); +} + +/* + * TODO: + * Driver_data will be used to perform any vendor specific init, register + * specific dai ops. + * Driver will implement it's own match function to retrieve driver data. + */ +static const struct hda_device_id codec_list[] = { + HDA_CODEC_EXT_ENTRY(0x10ec0286, 0x100002, "ALC286", 0), + {} +}; +MODULE_DEVICE_TABLE(hdaudio, codec_list); + +static struct hdac_ext_driver hdac_codec_driver = { + . hdac = { + .driver = { + .name = "HDA ASoC Codec", + /* Add PM */ + }, + .id_table = codec_list, + }, + .probe = hdac_generic_dev_probe, +}; + +static int __init hdac_generic_init(void) +{ + return snd_hda_ext_driver_register(&hdac_codec_driver); +} + +static void __exit hdac_generic_exit(void) +{ + snd_hda_ext_driver_unregister(&hdac_codec_driver); +} + +module_init(hdac_generic_init); +module_exit(hdac_generic_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("HDA ASoC codec"); +MODULE_AUTHOR("Subhransu S. Prusty"); diff --git a/sound/soc/codecs/hdac_generic.h b/sound/soc/codecs/hdac_generic.h new file mode 100644 index 0000000..5ca713a --- /dev/null +++ b/sound/soc/codecs/hdac_generic.h @@ -0,0 +1,25 @@ +/* + * hdac_generic.h - ASoc HDA generic codec driver + * + * Copyright (C) 2016 Intel Corp + * Author: Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __HDAC_GENERIC_H__ +#define __HDAC_GENERIC_H__ + +#define HDAC_GENERIC_NAME_SIZE 32 + +#endif /* __HDAC_GENERIC_H__ */