[alsa-devel] [PATCH 00/12] ASoC: hdac: Add hdac generic driver
HDA devices generically can be modelled with DAPM in ASoC.
We would like to create a framework for the HDA devices in ASoC. This will follow HDA standard. This will be registered as generic "virtual" class device (Not implemented in this series). If there is no match found for vendor id and device id, then the class driver will be loaded. For vendor specific devices, vendor quirks will be loaded based on the match. Additional widgets, controls and route will be created in a vendor specific way through vendor ops.
This series adds a framework in ASoC to model HDA codecs. HDA widgets are enumerated and one or multiple DAPM widget(s) are created. Connection list is queried for each widget to identify the connection between two endpoints and modelled using DAPM graph.
Set of event handlers are defined for each widget type. Based on DAPM events required verbs are sent to program codec.
Finally a function is exported to query for the device endpoint configuration to create machine controls.
Features yet to be added: - Optional features of widgets will be added using additional dapm widgets or controls - Pin retasking will be done by adding all possible dapm route map - Class driver to load the generic driver if no match found - Jack detection
Subhransu S. Prusty (12): ALSA: hdac: Add codec helper library ALSA: hda - Add macro to test pin widget's input capability ASoC: hdac: Add a generic hdac driver framework ASoC: hdac: Create DAPM model for HDA widgets ALSA: hdac: Move 'to_hda_ext_device' helper to core ASoC: hdac_hdmi: use get_edev helper from core ASoC: hdac: Build DAPM graph by querying through widget connection list ASoC: hdac: Register widget event handlers ALSA: hda - macro to get default config device of pin widgets ASoC: hdac: Export API to create machine controls ALSA: hdac: Implement a match function ASoC: hdac: Add coefficient init callback for RT286
include/sound/hdaudio.h | 1 + include/sound/hdaudio_ext.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 ++++ sound/hda/ext/hdac_codec.h | 60 ++ sound/hda/ext/hdac_ext_bus.c | 12 + sound/hda/local.h | 13 + sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdac_generic.c | 1754 +++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/hdac_generic.h | 36 + sound/soc/codecs/hdac_hdmi.c | 8 +- 12 files changed, 2069 insertions(+), 8 deletions(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h create mode 100644 sound/soc/codecs/hdac_generic.c create mode 100644 sound/soc/codecs/hdac_generic.h
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */ + struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
diff --git a/sound/hda/ext/Makefile b/sound/hda/ext/Makefile index 9b6f641..0aebd9d 100644 --- a/sound/hda/ext/Makefile +++ b/sound/hda/ext/Makefile @@ -1,3 +1,4 @@ -snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o hdac_ext_stream.o +snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o hdac_ext_stream.o \ + hdac_codec.o
obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o diff --git a/sound/hda/ext/hdac_codec.c b/sound/hda/ext/hdac_codec.c new file mode 100644 index 0000000..ba87af0 --- /dev/null +++ b/sound/hda/ext/hdac_codec.c @@ -0,0 +1,182 @@ +/* + * hdac_codec.c - HDA codec library + * + * Copyright (C) 2016 Intel Corp + * Author: Subhransu S. Prusty subhransu.s.prusty@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/hdaudio_ext.h> +#include "../../hda/local.h" +#include "hdac_codec.h" +#include <sound/hda_verbs.h> + +static int hdac_generic_query_connlist(struct hdac_device *hdac, + struct hdac_codec_widget *wid) +{ + hda_nid_t mux_nids[HDA_MAX_CONNECTIONS]; + unsigned int caps; + int i; + + if (!(get_wcaps(hdac, wid->nid) & AC_WCAP_CONN_LIST)) { + dev_dbg(&hdac->dev, + "HDAC: wid %d wcaps %#x doesn't support connection list\n", + wid->nid, get_wcaps(hdac, wid->nid)); + + return 0; + } + + wid->num_inputs = snd_hdac_get_connections(hdac, wid->nid, + mux_nids, HDA_MAX_CONNECTIONS); + + if (wid->num_inputs == 0) { + dev_warn(&hdac->dev, "No connections found for wid: %d\n", + wid->nid); + return 0; + } + + for (i = 0; i < wid->num_inputs; i++) { + wid->conn_list[i].nid = mux_nids[i]; + caps = get_wcaps(hdac, mux_nids[i]); + wid->conn_list[i].type = get_wcaps_type(caps); + } + + dev_dbg(&hdac->dev, "num_inputs %d for wid: %d\n", + wid->num_inputs, wid->nid); + + return wid->num_inputs; +} + +static int hdac_codec_add_widget(struct hdac_device *codec, hda_nid_t nid, + unsigned int type, unsigned int caps) +{ + struct hdac_codec_widget *widget; + unsigned int *cfg; + + widget = kzalloc(sizeof(*widget), GFP_KERNEL); + if (!widget) + return -ENOMEM; + + widget->nid = nid; + widget->type = type; + widget->caps = caps; + list_add_tail(&widget->head, &codec->widget_list); + + if (type == AC_WID_PIN) { + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + *cfg = snd_hdac_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + widget->params = cfg; + } + + return hdac_generic_query_connlist(codec, widget); +} + +/** + * snd_hdac_parse_widgets - Iterates over the hda codec, enumerates the + * widgets and its connections. + * @hdac: pointer to HDAC devcie + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_parse_widgets(struct hdac_device *hdac) +{ + hda_nid_t nid; + int num_nodes, i; + struct hdac_codec_widget *wid; + struct hdac_codec_widget *tmp; + int ret = 0; + + num_nodes = snd_hdac_get_sub_nodes(hdac, hdac->afg, &nid); + if (!nid || num_nodes <= 0) { + dev_err(&hdac->dev, "HDAC: failed to get afg sub nodes\n"); + return -EINVAL; + } + hdac->num_nodes = num_nodes; + hdac->start_nid = nid; + + for (i = 0; i < hdac->num_nodes; i++, nid++) { + unsigned int caps; + unsigned int type; + + caps = get_wcaps(hdac, nid); + type = get_wcaps_type(caps); + + ret = hdac_codec_add_widget(hdac, nid, type, caps); + if (ret < 0) + goto fail_add_widget; + + } + + hdac->end_nid = nid; + + /* Cache input connection to a widget */ + list_for_each_entry(wid, &hdac->widget_list, head) { + if (!wid->num_inputs) + continue; + + for (i = 0; i < wid->num_inputs; i++) { + list_for_each_entry(tmp, &hdac->widget_list, head) { + if (wid->conn_list[i].nid == tmp->nid) { + wid->conn_list[i].input_w = tmp; + break; + } + } + } + } + + return 0; + +fail_add_widget: + snd_hdac_codec_cleanup(hdac); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hdac_parse_widgets); + +/** + * snd_hdac_codec_init - Initialize some more hdac device elements + * @hdac: pointer to hdac device + * + * Returns 0 if successful. + */ +int snd_hdac_codec_init(struct hdac_device *hdac) +{ + INIT_LIST_HEAD(&hdac->widget_list); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_codec_init); + +/** + * snd_hdac_codec_cleanup - Cleanup resources allocated during device + * initialization. + * @hdac: pointer to hdac device + */ +void snd_hdac_codec_cleanup(struct hdac_device *hdac) +{ + struct hdac_codec_widget *wid, *tmp; + + list_for_each_entry_safe(wid, tmp, &hdac->widget_list, head) { + kfree(wid->params); + list_del(&wid->head); + kfree(wid); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_codec_cleanup); diff --git a/sound/hda/ext/hdac_codec.h b/sound/hda/ext/hdac_codec.h new file mode 100644 index 0000000..7f47a7e --- /dev/null +++ b/sound/hda/ext/hdac_codec.h @@ -0,0 +1,53 @@ +/* + * hdac_codec.h - HDA codec library + * + * Copyright (C) 2016 Intel Corp + * Author: Subhransu S. Prusty subhransu.s.prusty@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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_CODEC_H__ +#define __HDAC_CODEC_H__ + +#define HDA_MAX_CONNECTIONS 32 +/* amp values */ +#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8)) +#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8)) +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 + +struct hdac_codec_widget; + +struct hdac_codec_connection_list { + hda_nid_t nid; + unsigned int type; + struct hdac_codec_widget *input_w; +}; + +struct hdac_codec_widget { + struct list_head head; + hda_nid_t nid; + unsigned int caps; + unsigned int type; + int num_inputs; + struct hdac_codec_connection_list conn_list[HDA_MAX_CONNECTIONS]; + void *priv; /* Codec specific widget data */ + void *params; /* Widget specific parameters */ +}; + +int snd_hdac_parse_widgets(struct hdac_device *hdac); +int snd_hdac_codec_init(struct hdac_device *hdac); +void snd_hdac_codec_cleanup(struct hdac_device *hdac); + +#endif /* __HDAC_CODEC_H__ */
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote:
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */
- struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
thanks,
Takashi
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote:
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */
- struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Regards, Subhransu
thanks,
Takashi
--
On Mon, 29 Aug 2016 14:47:33 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote:
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */
- struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Doesn't struct hdac_ext_device fit better?
Takashi
On Mon, Aug 29, 2016 at 02:55:41PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 14:47:33 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote:
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */
- struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Doesn't struct hdac_ext_device fit better?
The snd_hdac_parse_widgets helper defined in hdac_codec.c populates members of hdac_device structure like num_nodes, start_nid etc. Even though widget_list is used in hda/ext, this looked generic to be added in hdac_device. I am inclined to add these helpers in hdac_device.c or may be move hdac_codec.c to sound/hda if you are ok.
Regards, Subhransu
Takashi
--
On Mon, 29 Aug 2016 15:17:50 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 02:55:41PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 14:47:33 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote:
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */
- struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Doesn't struct hdac_ext_device fit better?
The snd_hdac_parse_widgets helper defined in hdac_codec.c populates members of hdac_device structure like num_nodes, start_nid etc. Even though widget_list is used in hda/ext, this looked generic to be added in hdac_device.
If it's used only in hda/ext, why it's generic, i.e. putting to the place used commonly for legacy drivers? I don't follow this logic...
I am inclined to add these helpers in hdac_device.c or may be move hdac_codec.c to sound/hda if you are ok.
Even if we put it in hdac_device, initializing in another function is wrong. It must be initialized in snd_hdac_device_init(), if it's a part of the common struct.
Takashi
On Mon, Aug 29, 2016 at 03:28:35PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 15:17:50 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 02:55:41PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 14:47:33 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote:
Add hdac helpers to enumerate the HDA widgets and fill connection lists for each.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hdaudio.h | 1 + sound/hda/ext/Makefile | 3 +- sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ sound/hda/ext/hdac_codec.h | 53 +++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_codec.c create mode 100644 sound/hda/ext/hdac_codec.h
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 93e63c5..79502dc 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -71,6 +71,7 @@ struct hdac_device { unsigned int flags, unsigned int *res);
/* widgets */
- struct list_head widget_list; unsigned int num_nodes; hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Doesn't struct hdac_ext_device fit better?
The snd_hdac_parse_widgets helper defined in hdac_codec.c populates members of hdac_device structure like num_nodes, start_nid etc. Even though widget_list is used in hda/ext, this looked generic to be added in hdac_device.
If it's used only in hda/ext, why it's generic, i.e. putting to the place used commonly for legacy drivers? I don't follow this logic...
I am inclined to add these helpers in hdac_device.c or may be move hdac_codec.c to sound/hda if you are ok.
Even if we put it in hdac_device, initializing in another function is wrong. It must be initialized in snd_hdac_device_init(), if it's a part of the common struct.
Ok, got it. Will move this to hdac_ext_device.
Regards, Subhransu --
On Mon, 29 Aug 2016 15:33:57 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 03:28:35PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 15:17:50 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 02:55:41PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 14:47:33 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:15 +0200, Subhransu S. Prusty wrote: > > Add hdac helpers to enumerate the HDA widgets and fill connection > lists for each. > > Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com > Signed-off-by: Vinod Koul vinod.koul@intel.com > --- > include/sound/hdaudio.h | 1 + > sound/hda/ext/Makefile | 3 +- > sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ > sound/hda/ext/hdac_codec.h | 53 +++++++++++++ > 4 files changed, 238 insertions(+), 1 deletion(-) > create mode 100644 sound/hda/ext/hdac_codec.c > create mode 100644 sound/hda/ext/hdac_codec.h > > diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h > index 93e63c5..79502dc 100644 > --- a/include/sound/hdaudio.h > +++ b/include/sound/hdaudio.h > @@ -71,6 +71,7 @@ struct hdac_device { > unsigned int flags, unsigned int *res); > > /* widgets */ > + struct list_head widget_list; > unsigned int num_nodes; > hda_nid_t start_nid, end_nid;
Any reason to add this to hdac_device although it's used only by hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Doesn't struct hdac_ext_device fit better?
The snd_hdac_parse_widgets helper defined in hdac_codec.c populates members of hdac_device structure like num_nodes, start_nid etc. Even though widget_list is used in hda/ext, this looked generic to be added in hdac_device.
If it's used only in hda/ext, why it's generic, i.e. putting to the place used commonly for legacy drivers? I don't follow this logic...
I am inclined to add these helpers in hdac_device.c or may be move hdac_codec.c to sound/hda if you are ok.
Even if we put it in hdac_device, initializing in another function is wrong. It must be initialized in snd_hdac_device_init(), if it's a part of the common struct.
Ok, got it. Will move this to hdac_ext_device.
Don't get me wrong: I'd like to understand why it's put to hdac_device. If this makes the code easier / simpler, it's a good justification. Without the explanation, placing to the relevant struct makes more sense. That's my point.
Takashi
On Mon, Aug 29, 2016 at 03:43:27PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 15:33:57 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 03:28:35PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 15:17:50 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 02:55:41PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 14:47:33 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:41:22AM +0200, Takashi Iwai wrote: > On Mon, 29 Aug 2016 08:23:15 +0200, > Subhransu S. Prusty wrote: > > > > Add hdac helpers to enumerate the HDA widgets and fill connection > > lists for each. > > > > Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com > > Signed-off-by: Vinod Koul vinod.koul@intel.com > > --- > > include/sound/hdaudio.h | 1 + > > sound/hda/ext/Makefile | 3 +- > > sound/hda/ext/hdac_codec.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ > > sound/hda/ext/hdac_codec.h | 53 +++++++++++++ > > 4 files changed, 238 insertions(+), 1 deletion(-) > > create mode 100644 sound/hda/ext/hdac_codec.c > > create mode 100644 sound/hda/ext/hdac_codec.h > > > > diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h > > index 93e63c5..79502dc 100644 > > --- a/include/sound/hdaudio.h > > +++ b/include/sound/hdaudio.h > > @@ -71,6 +71,7 @@ struct hdac_device { > > unsigned int flags, unsigned int *res); > > > > /* widgets */ > > + struct list_head widget_list; > > unsigned int num_nodes; > > hda_nid_t start_nid, end_nid; > > Any reason to add this to hdac_device although it's used only by > hda/ext?
Hi Takashi,
Added this in the hdac_device, as this looked to be the more suitable data structure for the widget_list and is more specific to the hdac device.
Doesn't struct hdac_ext_device fit better?
The snd_hdac_parse_widgets helper defined in hdac_codec.c populates members of hdac_device structure like num_nodes, start_nid etc. Even though widget_list is used in hda/ext, this looked generic to be added in hdac_device.
If it's used only in hda/ext, why it's generic, i.e. putting to the place used commonly for legacy drivers? I don't follow this logic...
I am inclined to add these helpers in hdac_device.c or may be move hdac_codec.c to sound/hda if you are ok.
Even if we put it in hdac_device, initializing in another function is wrong. It must be initialized in snd_hdac_device_init(), if it's a part of the common struct.
Ok, got it. Will move this to hdac_ext_device.
Don't get me wrong: I'd like to understand why it's put to hdac_device. If this makes the code easier / simpler, it's a good justification. Without the explanation, placing to the relevant struct makes more sense. That's my point.
I understand what you meant to convey.
In this patch a generic widget structure is created to abstract the properties of HDA widgets. These widgets are added to the widget_list member as n when these are enumerated. I am unsure whether this hda_codec_widget struture and the widget_list can be used through a common library by both legacy and ASoC drivers. May not be now but in future. So adding this to hdac_ext_device for now.
Regards Subhransu
--
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/hda/local.h | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/sound/hda/local.h b/sound/hda/local.h index 0d5bb15..a51f5f6 100644 --- a/sound/hda/local.h +++ b/sound/hda/local.h @@ -8,6 +8,16 @@ #define get_wcaps(codec, nid) \ snd_hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP)
+#define get_pcaps(codec, nid) \ + snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP) + +static inline int is_input_pin(struct hdac_device *codec, hda_nid_t nid) +{ + unsigned int pincap = get_pcaps(codec, nid); + + return (pincap & AC_PINCAP_IN) != 0; +} + /* get the widget type from widget capability bits */ static inline int get_wcaps_type(unsigned int wcaps) {
On Mon, 29 Aug 2016 08:23:16 +0200, Subhransu S. Prusty wrote:
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
.... missing the reason why?
sound/hda/local.h | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/sound/hda/local.h b/sound/hda/local.h index 0d5bb15..a51f5f6 100644 --- a/sound/hda/local.h +++ b/sound/hda/local.h @@ -8,6 +8,16 @@ #define get_wcaps(codec, nid) \ snd_hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP)
+#define get_pcaps(codec, nid) \
- snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)
+static inline int is_input_pin(struct hdac_device *codec, hda_nid_t nid)
Better to make it bool.
thanks,
Takashi
On Mon, Aug 29, 2016 at 10:42:27AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:16 +0200, Subhransu S. Prusty wrote:
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
.... missing the reason why?
This macro will be used to identify pins input capability and create DAPM widgets. Also based on input capability widget parsing is done for DAPM graph creation.
Will add these details in the commit message.
sound/hda/local.h | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/sound/hda/local.h b/sound/hda/local.h index 0d5bb15..a51f5f6 100644 --- a/sound/hda/local.h +++ b/sound/hda/local.h @@ -8,6 +8,16 @@ #define get_wcaps(codec, nid) \ snd_hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP)
+#define get_pcaps(codec, nid) \
- snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)
+static inline int is_input_pin(struct hdac_device *codec, hda_nid_t nid)
Better to make it bool.
Sure.
Regards, Subhransu
thanks,
Takashi
--
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 and hw_params, program_stream_tag callback support are added in the dai_ops to program the stream parameters.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdac_generic.c | 439 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/hdac_generic.h | 25 +++ 4 files changed, 471 insertions(+) create mode 100644 sound/soc/codecs/hdac_generic.c create mode 100644 sound/soc/codecs/hdac_generic.h
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..99e624e --- /dev/null +++ b/sound/soc/codecs/hdac_generic.c @@ -0,0 +1,439 @@ +/* + * hdac_generic.c - ASoc HDA generic codec driver + * + * Copyright (C) 2016 Intel Corp + * Author: Subhransu S. Prusty subhransu.s.prusty@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_regmap.h> +#include "../../hda/local.h" +#include "../../hda/ext/hdac_codec.h" +#include "hdac_generic.h" + +#define HDA_MAX_CVTS 10 + +struct hdac_generic_dai_map { + struct hdac_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_codec_widget *wid; + + if (list_empty(&edev->hdac.widget_list)) + return; + + /* + * PIN widget with output capable are represented with an additional + * virtual mux widgets. + */ + list_for_each_entry(wid, &edev->hdac.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_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_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: <vendor id>-aif<widget nid> */ +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_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->hdac.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].capture, 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; + + ret = snd_hdac_codec_init(codec); + if (ret < 0) + return ret; + + edev->private_data = hdac_priv; + dev_set_drvdata(&codec->dev, edev); + + ret = snd_hdac_parse_widgets(codec); + if (ret < 0) { + dev_err(&codec->dev, "Failed to parse widgets with err: %d\n", + ret); + return ret; + } + + 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); +} + +static int hdac_generic_dev_remove(struct hdac_ext_device *edev) +{ + snd_hdac_codec_cleanup(&edev->hdac); + return 0; +} + +/* + * 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, + .remove = hdac_generic_dev_remove, +}; + +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. Prustysubhransu.s.prusty@intel.com"); 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 subhransu.s.prusty@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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__ */
HDA device widgets are mapped to dapm widgets to take advantage of DAPM. Each HDA widget can be mapped to one or multiple dapm widgets based on interface and how it is connected with other widgets.
For example, a PIN widget 2 or 3 dapm widgets are created depending on the capability. A dapm input/out widget is created to represent the input/output capability, a pga widget is created so that pin with retasking capability can be properly represented in a dapm graph and a mux widget for output pin is created, if the connection list has more than one input.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_generic.c | 478 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 477 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c index 99e624e..7f3a2a0 100644 --- a/sound/soc/codecs/hdac_generic.c +++ b/sound/soc/codecs/hdac_generic.c @@ -43,6 +43,16 @@ struct hdac_generic_priv { unsigned int num_dapm_widgets; };
+/* + * Widgets types can be accessed using an array with an index except vendor + * types. So fill NULL for the invalid indexes. + */ +static char *wid_names[] = { + "dac", "adc", "mixer", "mux", "pin", "power", + "volme knob", "beep", NULL, NULL, NULL, NULL, + NULL, NULL, NULL, "vendor", +}; + static void hdac_generic_set_power_state(struct hdac_ext_device *edev, hda_nid_t nid, unsigned int pwr_state) { @@ -52,6 +62,467 @@ static void hdac_generic_set_power_state(struct hdac_ext_device *edev, AC_VERB_SET_POWER_STATE, pwr_state); }
+static int hdac_generic_fill_widget_info(struct device *dev, + struct snd_soc_dapm_widget *w, enum snd_soc_dapm_type id, + void *priv, const char *wname, const char *stream, + struct snd_kcontrol_new *wc, int numkc, + int (*event)(struct snd_soc_dapm_widget *, + struct snd_kcontrol *, int), unsigned short event_flags) +{ + w->id = id; + w->name = devm_kstrdup(dev, wname, GFP_KERNEL); + if (!w->name) + return -ENOMEM; + + w->sname = stream; + w->reg = SND_SOC_NOPM; + w->shift = 0; + w->kcontrol_news = wc; + w->num_kcontrols = numkc; + w->priv = priv; + w->event = event; + w->event_flags = event_flags; + + return 0; +} + +static int hdac_generic_alloc_mux_widget(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets, int index, + struct hdac_codec_widget *wid) + +{ + struct snd_kcontrol_new *kc; + struct soc_enum *se; + char kc_name[HDAC_GENERIC_NAME_SIZE]; + char mux_items[HDAC_GENERIC_NAME_SIZE]; + char widget_name[HDAC_GENERIC_NAME_SIZE]; + const char *name; + /* To hold inputs to the Pin mux */ + char *items[HDA_MAX_CONNECTIONS]; + int i = 0, ret; + int num_items = wid->num_inputs + 1; + + if (wid->type == AC_WID_AUD_SEL) + sprintf(widget_name, "Mux %x", wid->nid); + else if (wid->type == AC_WID_PIN) + sprintf(widget_name, "Pin %x Mux", wid->nid); + else + return -EINVAL; + + kc = devm_kzalloc(dapm->dev, sizeof(*kc), GFP_KERNEL); + if (!kc) + return -ENOMEM; + + se = devm_kzalloc(dapm->dev, sizeof(*se), GFP_KERNEL); + if (!se) + return -ENOMEM; + + sprintf(kc_name, "Mux %d Input", wid->nid); + kc->name = devm_kstrdup(dapm->dev, kc_name, GFP_KERNEL); + if (!kc->name) + return -ENOMEM; + + kc->private_value = (long)se; + kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc->access = 0; + kc->info = snd_soc_info_enum_double; + kc->put = snd_soc_dapm_put_enum_double; + kc->get = snd_soc_dapm_get_enum_double; + + se->reg = SND_SOC_NOPM; + + se->items = num_items; + se->mask = roundup_pow_of_two(se->items) - 1; + + sprintf(mux_items, "NONE"); + items[i] = devm_kstrdup(dapm->dev, mux_items, GFP_KERNEL); + if (!items[i]) + return -ENOMEM; + + for (i = 0; i < wid->num_inputs; i++) { + name = wid_names[wid->conn_list[i].type]; + if (!name) + return -EINVAL; + + sprintf(mux_items, "%s %x", name, wid->conn_list[i].nid); + items[i + 1] = devm_kstrdup(dapm->dev, mux_items, GFP_KERNEL); + if (!items[i]) + return -ENOMEM; + } + + se->texts = devm_kmemdup(dapm->dev, items, + (num_items * sizeof(char *)), GFP_KERNEL); + if (!se->texts) + return -ENOMEM; + + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], + snd_soc_dapm_mux, wid, widget_name, NULL, kc, 1, + NULL, 0); + + if (ret < 0) + return ret; + + wid->priv = &widgets[index]; + + return 0; +} + +static const char *get_dai_stream(struct snd_soc_dai_driver *dai_drv, + int num_dais, struct hdac_codec_widget *wid) +{ + int i; + struct hdac_codec_widget *tmp; + + for (i = 0; i < num_dais; i++) { + tmp = dai_drv[i].dobj.private; + if (tmp->nid == wid->nid) { + if (wid->type == AC_WID_AUD_IN) + return dai_drv[i].capture.stream_name; + else + return dai_drv[i].playback.stream_name; + } + } + + return NULL; +} + +static int hdac_codec_alloc_cvt_widget(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets, int index, + struct hdac_codec_widget *wid) +{ + struct snd_soc_dai_driver *dai_drv = dapm->component->dai_drv; + char widget_name[HDAC_GENERIC_NAME_SIZE]; + const char *dai_strm_name; + int ret = 0; + + dai_strm_name = get_dai_stream(dai_drv, + dapm->component->num_dai, wid); + if (!dai_strm_name) + return -EINVAL; + + if (wid->type == AC_WID_AUD_IN) { + sprintf(widget_name, "ADC %x", wid->nid); + } else { + sprintf(widget_name, "%s DAC %x", + (wid->caps & AC_WCAP_DIGITAL) ? "Digital" : "Analog", + wid->nid); + } + + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], + wid->type == AC_WID_AUD_IN ? + snd_soc_dapm_aif_in : snd_soc_dapm_aif_out, + wid, widget_name, dai_strm_name, NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + wid->priv = &widgets[index]; + + return 0; +} + +static int hdac_codec_alloc_mixer_widget(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *w, int index, + struct hdac_codec_widget *wid) +{ + struct snd_kcontrol_new *kc; + struct soc_mixer_control *mc; + char kc_name[HDAC_GENERIC_NAME_SIZE]; + char widget_name[HDAC_GENERIC_NAME_SIZE]; + const char *name; + int i, ret; + + kc = devm_kcalloc(dapm->dev, wid->num_inputs, sizeof(*kc), GFP_KERNEL); + if (!kc) + return -ENOMEM; + + for (i = 0; i < wid->num_inputs; i++) { + name = wid_names[wid->conn_list[i].type]; + if (!name) + return -EINVAL; + + sprintf(kc_name, "%s %x in Switch", + name, wid->conn_list[i].nid); + kc[i].name = devm_kstrdup(dapm->dev, kc_name, GFP_KERNEL); + if (!kc[i].name) + return -ENOMEM; + + mc = devm_kzalloc(dapm->dev, (sizeof(*mc)), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + mc->reg = SND_SOC_NOPM; + mc->rreg = SND_SOC_NOPM; + mc->max = 1; + + kc[i].private_value = (long)mc; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].info = snd_soc_info_volsw; + kc[i].put = snd_soc_dapm_put_volsw; + kc[i].get = snd_soc_dapm_get_volsw; + } + + sprintf(widget_name, "Mixer %x", wid->nid); + ret = hdac_generic_fill_widget_info(dapm->dev, &w[index], + snd_soc_dapm_mixer, wid, widget_name, NULL, + kc, wid->num_inputs, NULL, 0); + if (ret < 0) + return ret; + + wid->priv = &w[index]; + + return 0; +} + +/* + * Each Pin widget will be represented with: + * DAPM input/output - Based on out/in capability queried + * DAPM PGA - To program the PIN configuration + * DAPM Mux - Create a virtual Mux widget, if output capable pin can + * select from multiple inputs. + * + * Returns number of dapm widgets created on success else returns -ve error + * code. + */ +static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets, int index, + struct hdac_codec_widget *wid) +{ + struct hdac_ext_device *edev = to_hda_ext_device(dapm->dev); + char widget_name[HDAC_GENERIC_NAME_SIZE]; + int i = index; + int ret; + bool input; + /* + * Pin complex are represented with multiple dapm widgets. Cache them + * for easy reference. wid_ref[0]->input/output, wid_ref[1]->pga, + * wid_ref[2]->mux. + */ + struct snd_soc_dapm_widget **wid_ref; + + input = is_input_pin(&edev->hdac, wid->nid); + + wid_ref = devm_kmalloc_array(dapm->dev, 3, sizeof(wid_ref), GFP_KERNEL); + if (!wid_ref) + return -ENOMEM; + + /* Create output/input widget */ + sprintf(widget_name, "Pin %x %s", wid->nid, + input ? "Input" : "Output"); + + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], + input ? snd_soc_dapm_input : snd_soc_dapm_output, + wid, widget_name, NULL, NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + wid_ref[0] = &widgets[i++]; + + /* Create PGA widget */ + sprintf(widget_name, "Pin %x PGA", wid->nid); + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], + snd_soc_dapm_pga, wid, widget_name, NULL, + NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + wid_ref[1] = &widgets[i++]; + + /* Create Mux if Pin widget can select from multiple inputs */ + if (!input && wid->num_inputs > 1) { + sprintf(widget_name, "Pin %x Mux", wid->nid); + ret = hdac_generic_alloc_mux_widget(dapm, widgets, i, wid); + if (ret < 0) + return ret; + + wid_ref[2] = &widgets[i++]; + } + + /* override hda widget private with dapm widget group */ + wid->priv = wid_ref; + + /* Return number of dapm widgets created */ + return i - index; +} + +static int hdac_codec_alloc_power_widget(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets, int index, + struct hdac_codec_widget *wid) +{ + char widget_name[HDAC_GENERIC_NAME_SIZE]; + int ret = 0; + + sprintf(widget_name, "Power %x", wid->nid); + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], + snd_soc_dapm_supply, wid, widget_name, + NULL, NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + wid->priv = &widgets[index]; + + return 0; +} + +/* + * Each Beep hda widget will be represented with two dapm widget a siggen + * and a PGA. A virtual switch control will be added to turn on/off DAPM. + */ +static int hdac_codec_alloc_beep_widget(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets, int index, + struct hdac_codec_widget *wid) +{ + char widget_name[HDAC_GENERIC_NAME_SIZE]; + int i = index, ret = 0; + struct soc_mixer_control *mc; + struct snd_kcontrol_new *kc; + char kc_name[HDAC_GENERIC_NAME_SIZE]; + /* + * Beep widgets are represented with multiple dapm widgets. Cache them + * for each reference. wid_ref[0]->siggen, wid_ref[1]->pga. + */ + struct snd_soc_dapm_widget **wid_ref; + + wid_ref = devm_kmalloc_array(dapm->dev, 2, sizeof(wid_ref), GFP_KERNEL); + if (!wid_ref) + return -ENOMEM; + + sprintf(widget_name, "Beep Gen %x", wid->nid); + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i++], + snd_soc_dapm_siggen, wid, widget_name, + NULL, NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + kc = devm_kzalloc(dapm->dev, + (sizeof(*kc) * wid->num_inputs), + GFP_KERNEL); + if (!kc) + return -ENOMEM; + + sprintf(kc_name, "%s %x in Switch", wid_names[wid->type], wid->nid); + kc[i].name = devm_kstrdup(dapm->dev, kc_name, GFP_KERNEL); + if (!kc[i].name) + return -ENOMEM; + mc = devm_kzalloc(dapm->dev, (sizeof(*mc)), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + mc->reg = SND_SOC_NOPM; + mc->rreg = SND_SOC_NOPM; + mc->max = 1; + + kc[i].private_value = (long)mc; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].info = snd_soc_info_volsw; + kc[i].put = snd_soc_dapm_get_volsw; + kc[i].get = snd_soc_dapm_put_volsw; + + sprintf(widget_name, "Beep Gen %x PGA", wid->nid); + ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], + snd_soc_dapm_pga, wid, widget_name, + NULL, kc, 1, NULL, 0); + if (ret < 0) + return ret; + + wid->priv = wid_ref; + + return 0; +} + +/* Create DAPM widgets to represent each codec widget */ +static int hdac_codec_alloc_widgets(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets) +{ + struct hdac_ext_device *edev = to_hda_ext_device(dapm->dev); + struct hdac_codec_widget *wid; + int index = 0; + int ret = 0; + + list_for_each_entry(wid, &edev->hdac.widget_list, head) { + switch (wid->type) { + case AC_WID_AUD_IN: + case AC_WID_AUD_OUT: + ret = hdac_codec_alloc_cvt_widget(dapm, widgets, + index, wid); + if (ret < 0) + return ret; + index++; + break; + + case AC_WID_PIN: + ret = hdac_codec_alloc_pin_widget(dapm, widgets, + index, wid); + if (ret < 0) + return ret; + index += ret; + break; + + case AC_WID_AUD_MIX: + ret = hdac_codec_alloc_mixer_widget(dapm, widgets, + index, wid); + if (ret < 0) + return ret; + index++; + break; + + case AC_WID_AUD_SEL: + ret = hdac_generic_alloc_mux_widget(dapm, widgets, + index, wid); + if (ret < 0) + return ret; + index++; + break; + + case AC_WID_POWER: + ret = hdac_codec_alloc_power_widget(dapm, widgets, + index, wid); + if (ret < 0) + return ret; + index++; + break; + + case AC_WID_BEEP: + ret = hdac_codec_alloc_beep_widget(dapm, widgets, + index, wid); + if (ret < 0) + return ret; + index += 2; + break; + + default: + dev_warn(&edev->hdac.dev, + "dapm widget not allocated for type: %d\n", + wid->type); + break; + } + } + + return ret; +} + +static int hdac_generic_create_fill_widget_route_map( + struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_dapm_widget *widgets; + struct hdac_ext_device *edev = to_hda_ext_device(dapm->dev); + struct hdac_generic_priv *hdac_priv = edev->private_data; + + widgets = devm_kzalloc(dapm->dev, + (sizeof(*widgets) * hdac_priv->num_dapm_widgets), + GFP_KERNEL); + if (!widgets) + return -ENOMEM; + + /* Create DAPM widgets */ + hdac_codec_alloc_widgets(dapm, widgets); + + snd_soc_dapm_new_controls(dapm, widgets, hdac_priv->num_dapm_widgets); + + /* TODO: Add each path to dapm graph when enumerated */ + + return 0; +} + static void hdac_generic_calc_dapm_widgets(struct hdac_ext_device *edev) { struct hdac_generic_priv *hdac_priv = edev->private_data; @@ -192,10 +663,15 @@ 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); + int ret;
edev->scodec = codec;
- /* TODO: create widget, route and controls */ + /* create widget, route and controls */ + ret = hdac_generic_create_fill_widget_route_map(dapm); + if (ret < 0) + return ret; + /* TODO: jack sense */
/* Imp: Store the card pointer in hda_codec */
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/hda/ext/hdac_codec.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/hda/ext/hdac_codec.h b/sound/hda/ext/hdac_codec.h index 7f47a7e..9159139 100644 --- a/sound/hda/ext/hdac_codec.h +++ b/sound/hda/ext/hdac_codec.h @@ -46,6 +46,13 @@ struct hdac_codec_widget { void *params; /* Widget specific parameters */ };
+static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) +{ + struct hdac_device *hdac = dev_to_hdac_dev(dev); + + return to_ehdac_device(hdac); +} + int snd_hdac_parse_widgets(struct hdac_device *hdac); int snd_hdac_codec_init(struct hdac_device *hdac); void snd_hdac_codec_cleanup(struct hdac_device *hdac);
On Mon, 29 Aug 2016 08:23:19 +0200, Subhransu S. Prusty wrote:
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
sound/hda/ext/hdac_codec.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/hda/ext/hdac_codec.h b/sound/hda/ext/hdac_codec.h index 7f47a7e..9159139 100644 --- a/sound/hda/ext/hdac_codec.h +++ b/sound/hda/ext/hdac_codec.h @@ -46,6 +46,13 @@ struct hdac_codec_widget { void *params; /* Widget specific parameters */ };
+static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) +{
- struct hdac_device *hdac = dev_to_hdac_dev(dev);
- return to_ehdac_device(hdac);
+}
int snd_hdac_parse_widgets(struct hdac_device *hdac); int snd_hdac_codec_init(struct hdac_device *hdac); void snd_hdac_codec_cleanup(struct hdac_device *hdac);
"Move" is to move from point A to B, but you're only adding a new one here...
Takashi
On Mon, Aug 29, 2016 at 10:44:38AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:19 +0200, Subhransu S. Prusty wrote:
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
sound/hda/ext/hdac_codec.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/hda/ext/hdac_codec.h b/sound/hda/ext/hdac_codec.h index 7f47a7e..9159139 100644 --- a/sound/hda/ext/hdac_codec.h +++ b/sound/hda/ext/hdac_codec.h @@ -46,6 +46,13 @@ struct hdac_codec_widget { void *params; /* Widget specific parameters */ };
+static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) +{
- struct hdac_device *hdac = dev_to_hdac_dev(dev);
- return to_ehdac_device(hdac);
+}
int snd_hdac_parse_widgets(struct hdac_device *hdac); int snd_hdac_codec_init(struct hdac_device *hdac); void snd_hdac_codec_cleanup(struct hdac_device *hdac);
"Move" is to move from point A to B, but you're only adding a new one here...
to_hda_ext_device was defined in hdac_hdmi.c. As ASoC hda codec framework will use this, so moved this to core. The next patch removes this helper from hdac_hdmi.c. As I didn't want intermix two different modules in the same patch, so submitted as separate patches. May be I will rename the commit message to "copy ... " instead of "move". What do you suggest?
Regards, Subhransu
Takashi
--
On Mon, 29 Aug 2016 15:06:23 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:44:38AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:19 +0200, Subhransu S. Prusty wrote:
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
sound/hda/ext/hdac_codec.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/hda/ext/hdac_codec.h b/sound/hda/ext/hdac_codec.h index 7f47a7e..9159139 100644 --- a/sound/hda/ext/hdac_codec.h +++ b/sound/hda/ext/hdac_codec.h @@ -46,6 +46,13 @@ struct hdac_codec_widget { void *params; /* Widget specific parameters */ };
+static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) +{
- struct hdac_device *hdac = dev_to_hdac_dev(dev);
- return to_ehdac_device(hdac);
+}
int snd_hdac_parse_widgets(struct hdac_device *hdac); int snd_hdac_codec_init(struct hdac_device *hdac); void snd_hdac_codec_cleanup(struct hdac_device *hdac);
"Move" is to move from point A to B, but you're only adding a new one here...
to_hda_ext_device was defined in hdac_hdmi.c. As ASoC hda codec framework will use this, so moved this to core. The next patch removes this helper from hdac_hdmi.c. As I didn't want intermix two different modules in the same patch, so submitted as separate patches. May be I will rename the commit message to "copy ... " instead of "move". What do you suggest?
Well, at this moment, hdac_hdmi.c is the only user of this helper, so I don't see any merit to split the move. It's just factoring out the helper to external.
Takashi
On Mon, Aug 29, 2016 at 03:21:22PM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 15:06:23 +0200, Subhransu S. Prusty wrote:
On Mon, Aug 29, 2016 at 10:44:38AM +0200, Takashi Iwai wrote:
On Mon, 29 Aug 2016 08:23:19 +0200, Subhransu S. Prusty wrote:
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
sound/hda/ext/hdac_codec.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/hda/ext/hdac_codec.h b/sound/hda/ext/hdac_codec.h index 7f47a7e..9159139 100644 --- a/sound/hda/ext/hdac_codec.h +++ b/sound/hda/ext/hdac_codec.h @@ -46,6 +46,13 @@ struct hdac_codec_widget { void *params; /* Widget specific parameters */ };
+static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) +{
- struct hdac_device *hdac = dev_to_hdac_dev(dev);
- return to_ehdac_device(hdac);
+}
int snd_hdac_parse_widgets(struct hdac_device *hdac); int snd_hdac_codec_init(struct hdac_device *hdac); void snd_hdac_codec_cleanup(struct hdac_device *hdac);
"Move" is to move from point A to B, but you're only adding a new one here...
to_hda_ext_device was defined in hdac_hdmi.c. As ASoC hda codec framework will use this, so moved this to core. The next patch removes this helper from hdac_hdmi.c. As I didn't want intermix two different modules in the same patch, so submitted as separate patches. May be I will rename the commit message to "copy ... " instead of "move". What do you suggest?
Well, at this moment, hdac_hdmi.c is the only user of this helper, so I don't see any merit to split the move. It's just factoring out the helper to external.
Ok, will merge these two patches and submit a single patch for the move.
Regards, Subhransu
--
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_hdmi.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index 4e181b2..7ab63bd 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -31,6 +31,7 @@ #include <sound/pcm_drm_eld.h> #include <sound/hda_chmap.h> #include "../../hda/local.h" +#include "../../hda/ext/hdac_codec.h" #include "hdac_hdmi.h"
#define NAME_SIZE 32 @@ -127,13 +128,6 @@ static struct hdac_hdmi_pcm *get_hdmi_pcm_from_id(struct hdac_hdmi_priv *hdmi, return NULL; }
-static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) -{ - struct hdac_device *hdac = dev_to_hdac_dev(dev); - - return to_ehdac_device(hdac); -} - static unsigned int sad_format(const u8 *sad) { return ((sad[0] >> 0x3) & 0x1f);
This patch recursively traverses the HDA codec widgets and builds the graph by querying connection list of each.
Only output pin widget and adc widget endpoints can support connection list. So for a playback path query happens from output pin widget end and for a capture path query happens from adc widget end till an input endpoint is found.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_generic.c | 329 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 328 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c index 7f3a2a0..6bd7b2b 100644 --- a/sound/soc/codecs/hdac_generic.c +++ b/sound/soc/codecs/hdac_generic.c @@ -53,6 +53,19 @@ static char *wid_names[] = { NULL, NULL, NULL, "vendor", };
+struct route_map { + struct list_head head; + const char *sink; + char *control; + const char *src; +}; + +struct widget_node_entries { + struct hdac_codec_widget *wid; + struct snd_soc_dapm_widget *w; + int num_nodes; +}; + static void hdac_generic_set_power_state(struct hdac_ext_device *edev, hda_nid_t nid, unsigned int pwr_state) { @@ -62,6 +75,316 @@ static void hdac_generic_set_power_state(struct hdac_ext_device *edev, AC_VERB_SET_POWER_STATE, pwr_state); }
+static bool is_duplicate_route(struct list_head *route_list, + const char *sink, const char *control, const char *src) +{ + struct route_map *map; + + list_for_each_entry(map, route_list, head) { + + if (strcmp(src, map->src)) + continue; + if (strcmp(sink, map->sink)) + continue; + if (!control && !map->control) + return true; + if ((control && map->control) && + !strcmp(control, map->control)) + return true; + } + + return false; +} + +static int hdac_generic_add_route(struct snd_soc_dapm_context *dapm, + const char *sink, const char *control, const char *src, + struct list_head *route_list) +{ + struct snd_soc_dapm_route route; + struct route_map *map; + + /* + * During parsing a loop can be created from input pin to output pin. + * An input pin is represented with pga and input dapm widgets. + * There is possibility of duplicate route between these two pga and + * input widgets as the input can appear for multiple output pins or + * adcs during connection list query. So check the duplication. + */ + if (is_duplicate_route(route_list, sink, control, src)) + return 0; + + route.sink = sink; + route.source = src; + route.control = control; + route.connected = NULL; + + snd_soc_dapm_add_routes(dapm, &route, 1); + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->sink = sink; + map->src = src; + if (control) { + map->control = kcalloc(SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + sizeof(char), GFP_KERNEL); + if (!map->control) { + kfree(map); + return -ENOMEM; + } + + strcpy(map->control, control); + } + + list_add_tail(&map->head, route_list); + + return 0; +} + +/* Returns the only dapm widget which can be connected to other hda widgets */ +static struct snd_soc_dapm_widget *hda_widget_to_dapm_widget( + struct hdac_ext_device *edev, + struct hdac_codec_widget *wid) +{ + struct snd_soc_dapm_widget **wid_ref; + + switch (wid->type) { + case AC_WID_PIN: + wid_ref = wid->priv; + + if (is_input_pin(&edev->hdac, wid->nid)) + return wid_ref[1]; + + if (wid->num_inputs == 1) + return wid_ref[1]; + + return wid_ref[2]; + + case AC_WID_BEEP: + wid_ref = wid->priv; + + return wid_ref[1]; + + case AC_WID_AUD_OUT: + case AC_WID_AUD_IN: + case AC_WID_AUD_MIX: + case AC_WID_AUD_SEL: + case AC_WID_POWER: + return wid->priv; + + default: + dev_info(&edev->hdac.dev, "Widget type %d not handled\n", + wid->type); + return NULL; + } + + return NULL; +} + +static void fill_pinout_next_wid_entry(struct hdac_ext_device *edev, + struct widget_node_entries *next, + struct widget_node_entries *wid_entry, + const char **control, int index) +{ + struct snd_soc_dapm_widget **wid_ref = wid_entry->wid->priv; + const struct snd_kcontrol_new *kc; + struct soc_enum *se; + + switch (wid_entry->w->id) { + case snd_soc_dapm_output: + next->w = wid_ref[1]; + next->num_nodes = 1; + next->wid = wid_entry->wid; + + break; + + case snd_soc_dapm_pga: + if (wid_entry->wid->num_inputs == 1) { + next->wid = wid_entry->wid->conn_list[index].input_w; + next->w = hda_widget_to_dapm_widget( + edev, next->wid); + next->num_nodes = next->wid->num_inputs; + } else { + next->wid = wid_entry->wid; + next->w = wid_ref[2]; + next->num_nodes = wid_entry->wid->num_inputs; + } + + break; + + case snd_soc_dapm_mux: + kc = wid_entry->w->kcontrol_news; + se = (struct soc_enum *)kc->private_value; + + next->wid = wid_entry->wid->conn_list[index].input_w; + next->num_nodes = next->wid->num_inputs; + next->w = hda_widget_to_dapm_widget(edev, next->wid); + + *control = se->texts[index + 1]; + + break; + + default: + dev_warn(&edev->hdac.dev, + "widget nid: %d id: %d not handled\n", + wid_entry->wid->nid, wid_entry->w->id); + break; + } +} + +static int parse_node_and_add_route(struct snd_soc_dapm_context *dapm, + struct widget_node_entries *wid_entry, + struct list_head *route_list) +{ + struct hdac_ext_device *edev = to_hda_ext_device(dapm->dev); + int i, ret; + struct widget_node_entries next; + const char *control = NULL; + + if (!wid_entry->num_nodes) + return 0; + + if ((wid_entry->w->id == snd_soc_dapm_dac) || + (wid_entry->w->id == snd_soc_dapm_input) || + (wid_entry->w->id == snd_soc_dapm_siggen)) { + + return 0; + } + + for (i = 0; i < wid_entry->num_nodes; i++) { + + if (wid_entry->wid->type == AC_WID_PIN) { + control = NULL; + + if (is_input_pin(&edev->hdac, wid_entry->wid->nid)) { + + struct snd_soc_dapm_widget **wid_ref = + wid_entry->wid->priv; + + if (wid_entry->w->id == snd_soc_dapm_pga) { + next.w = wid_ref[0]; + next.num_nodes = 1; + next.wid = wid_entry->wid; + } + } else { /* if output pin */ + fill_pinout_next_wid_entry(edev, &next, + wid_entry, &control, i); + } + } else { + struct snd_soc_dapm_widget *w = wid_entry->wid->priv; + const struct snd_kcontrol_new *kc; + struct soc_enum *se; + + next.wid = wid_entry->wid->conn_list[i].input_w; + next.w = hda_widget_to_dapm_widget(edev, next.wid); + if (next.wid->type == AC_WID_PIN && + is_input_pin(&edev->hdac, + next.wid->nid)) + next.num_nodes = 1; + else + next.num_nodes = next.wid->num_inputs; + + switch (w->id) { + case snd_soc_dapm_mux: + kc = &w->kcontrol_news[0]; + se = (struct soc_enum *)kc->private_value; + control = se->texts[i + 1]; + + break; + + case snd_soc_dapm_mixer: + kc = &w->kcontrol_news[i]; + control = kc->name; + + break; + default: + break; + } + } + + ret = hdac_generic_add_route(dapm, wid_entry->w->name, + control, next.w->name, route_list); + if (ret < 0) + return ret; + + ret = parse_node_and_add_route(dapm, &next, route_list); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Example graph connection from a output PIN to a DAC: + * DAC1-> + * Mixer 1 -------> + * DAC2-> Virtual Mux -> PIN PGA -> OUTPUT PIN + * -> + * LOUT1 ----------------| + * + * Widget connection map can be created by querying the connection list for + * each widget. The parsing can happen from two endpoints: + * 1) PIN widget 2) ADC widget. + * + * This goes through both pin list and adc list and builds the graph. + */ + +static int hdac_generic_add_route_to_list(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *widgets) +{ + struct hdac_ext_device *edev = to_hda_ext_device(dapm->dev); + struct hdac_codec_widget *wid; + struct snd_soc_dapm_widget **wid_ref; + struct widget_node_entries wid_entry; + struct list_head route_list; + struct route_map *map, *tmp; + int ret = 0; + + /* + * manage the routes through a temp list to identify duplicate + * routes from being added. + */ + INIT_LIST_HEAD(&route_list); + list_for_each_entry(wid, &edev->hdac.widget_list, head) { + if ((wid->type != AC_WID_PIN) && (wid->type != AC_WID_AUD_IN)) + continue; + /* + * input capable pins don't have a connection list, so skip + * them. + */ + if ((wid->type == AC_WID_PIN) && + (is_input_pin(&edev->hdac, wid->nid))) + continue; + + if (wid->type == AC_WID_PIN) { + wid_ref = wid->priv; + + wid_entry.wid = wid; + wid_entry.num_nodes = 1; + wid_entry.w = wid_ref[0]; + } else { + wid_entry.wid = wid; + wid_entry.num_nodes = wid->num_inputs; + wid_entry.w = wid->priv; + } + + ret = parse_node_and_add_route(dapm, &wid_entry, &route_list); + if (ret < 0) + goto fail; + } + +fail: + list_for_each_entry_safe(map, tmp, &route_list, head) { + kfree(map->control); + list_del(&map->head); + kfree(map); + } + + return ret; +} + static int hdac_generic_fill_widget_info(struct device *dev, struct snd_soc_dapm_widget *w, enum snd_soc_dapm_type id, void *priv, const char *wname, const char *stream, @@ -518,7 +841,11 @@ static int hdac_generic_create_fill_widget_route_map(
snd_soc_dapm_new_controls(dapm, widgets, hdac_priv->num_dapm_widgets);
- /* TODO: Add each path to dapm graph when enumerated */ + /* Add each path to dapm graph when enumerated */ + hdac_generic_add_route_to_list(dapm, widgets); + + snd_soc_dapm_new_widgets(dapm->card); +
return 0; }
Register the event handlers and program the codec by sending required verbs in the event handlers.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_generic.c | 329 +++++++++++++++++++++++++++++++++++++++- sound/soc/codecs/hdac_generic.h | 3 + 2 files changed, 326 insertions(+), 6 deletions(-)
diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c index 6bd7b2b..354e81d 100644 --- a/sound/soc/codecs/hdac_generic.c +++ b/sound/soc/codecs/hdac_generic.c @@ -75,6 +75,305 @@ static void hdac_generic_set_power_state(struct hdac_ext_device *edev, AC_VERB_SET_POWER_STATE, pwr_state); }
+/* TODO: Add feature to program by querying capability */ +static void hdac_generic_set_eapd(struct hdac_ext_device *edev, + hda_nid_t nid, bool enable) +{ + u32 pin_caps = snd_hdac_read_parm_uncached(&edev->hdac, + nid, AC_PAR_PIN_CAP); + + if (pin_caps & AC_PINCAP_EAPD) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_EAPD_BTLENABLE, enable ? 2 : 0); +} + +static int hdac_generic_pin_io_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + int val; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + val = snd_hdac_codec_read(&edev->hdac, wid->nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (w->id == snd_soc_dapm_output) { + /* TODO: program using a eapd widget */ + hdac_generic_set_eapd(edev, wid->nid, true); + val |= AC_PINCTL_OUT_EN; + } else { + + /* TODO: program vref using a mixer control */ + val |= AC_PINCTL_VREF_80; + val |= AC_PINCTL_IN_EN; + } + + break; + + case SND_SOC_DAPM_POST_PMD: + if (w->id == snd_soc_dapm_output) { + /* TODO: program using a eapd widget */ + hdac_generic_set_eapd(edev, wid->nid, false); + val &= ~AC_PINCTL_OUT_EN; + } else { + val &= AC_PINCTL_VREF_HIZ; + val &= ~AC_PINCTL_IN_EN; + } + + break; + + default: + dev_warn(&edev->hdac.dev, "Event %d not handled\n", event); + return 0; + } + + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, val); + + return 0; +} + +static int hdac_generic_pin_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + int mux_idx; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + if (!kc) + kc = w->kcontrols[0]; + + mux_idx = dapm_kcontrol_get_value(kc); + if (mux_idx > 0) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_CONNECT_SEL, (mux_idx - 1)); + } + + return 0; +} + +static int hdac_generic_pin_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + if (event == SND_SOC_DAPM_POST_PMD) { + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D3); + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + } else { + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D0); + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + } + + return 0; +} + +static int hdac_generic_widget_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + hdac_generic_set_power_state(edev, wid->nid, + (event == SND_SOC_DAPM_POST_PMD ? AC_PWRST_D3:AC_PWRST_D0)); + + return 0; +} + +static int hdac_generic_cvt_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + hdac_generic_widget_power_event(w, kc, event); + + if (event == SND_SOC_DAPM_POST_PMD) + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(0)); + else + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0) | 0x5b); + + return 0; +} + +static int get_mixer_control_index(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc) +{ + int i; + + for (i = 0; i < w->num_kcontrols; i++) { + if (w->kcontrols[i] == kc) + return i; + } + + return -EINVAL; +} + +static int hdac_generic_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + bool no_input = true; + int i; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D0); + + + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(i)); + } + } + + return 0; + + case SND_SOC_DAPM_POST_PMD: + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(i)); + } + } + + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D3); + + return 0; + + case SND_SOC_DAPM_POST_REG: + i = get_mixer_control_index(w, kc); + if (i < 0) { + dev_err(&edev->hdac.dev, + "%s: Wrong kcontrol event: %s\n", + __func__, kc->id.name); + return i; + } + if (dapm_kcontrol_get_value(kc)) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(i)); + no_input = false; + } else { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(i)); + } + + if (no_input) + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + return 0; + + default: + dev_warn(&edev->hdac.dev, "Event %d not handled\n", event); + return 0; + } + + return 0; +} + +static void update_mux_amp_switch(struct hdac_ext_device *edev, + hda_nid_t nid, struct snd_kcontrol *kc, bool enable) +{ + struct soc_enum *e = (struct soc_enum *)kc->private_value; + int mux_idx, i; + + mux_idx = dapm_kcontrol_get_value(kc); + + if (!enable || !mux_idx) { + for (i = 1; i < (e->items - 1); i++) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(i - 1)); + + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + } else { + for (i = 1; i < (e->items - 1); i++) { + if (i == mux_idx) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(i - 1)); + else + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(i - 1)); + } + + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + } +} + +static int hdac_generic_selector_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D0); + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_CONNECT_SEL, + (dapm_kcontrol_get_value(w->kcontrols[0]) - 1)); + update_mux_amp_switch(edev, wid->nid, w->kcontrols[0], true); + + return 0; + + case SND_SOC_DAPM_POST_REG: + update_mux_amp_switch(edev, wid->nid, kc, true); + + return 0; + + case SND_SOC_DAPM_POST_PMD: + update_mux_amp_switch(edev, wid->nid, w->kcontrols[0], false); + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D3); + + return 0; + + default: + dev_warn(&edev->hdac.dev, "Event %d not handled\n", event); + return 0; + } + + return 0; +} + static bool is_duplicate_route(struct list_head *route_list, const char *sink, const char *control, const char *src) { @@ -480,7 +779,8 @@ static int hdac_generic_alloc_mux_widget(struct snd_soc_dapm_context *dapm,
ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], snd_soc_dapm_mux, wid, widget_name, NULL, kc, 1, - NULL, 0); + hdac_generic_selector_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_REG);
if (ret < 0) return ret; @@ -534,7 +834,9 @@ static int hdac_codec_alloc_cvt_widget(struct snd_soc_dapm_context *dapm, ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], wid->type == AC_WID_AUD_IN ? snd_soc_dapm_aif_in : snd_soc_dapm_aif_out, - wid, widget_name, dai_strm_name, NULL, 0, NULL, 0); + wid, widget_name, dai_strm_name, NULL, 0, + hdac_generic_cvt_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
@@ -587,7 +889,9 @@ static int hdac_codec_alloc_mixer_widget(struct snd_soc_dapm_context *dapm, sprintf(widget_name, "Mixer %x", wid->nid); ret = hdac_generic_fill_widget_info(dapm->dev, &w[index], snd_soc_dapm_mixer, wid, widget_name, NULL, - kc, wid->num_inputs, NULL, 0); + kc, wid->num_inputs, hdac_generic_mixer_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_POST_REG); if (ret < 0) return ret;
@@ -634,7 +938,9 @@ static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm,
ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], input ? snd_soc_dapm_input : snd_soc_dapm_output, - wid, widget_name, NULL, NULL, 0, NULL, 0); + wid, widget_name, NULL, NULL, 0, + hdac_generic_pin_io_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
@@ -644,7 +950,8 @@ static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm, sprintf(widget_name, "Pin %x PGA", wid->nid); ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], snd_soc_dapm_pga, wid, widget_name, NULL, - NULL, 0, NULL, 0); + NULL, 0, hdac_generic_pin_pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
@@ -656,6 +963,15 @@ static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm, ret = hdac_generic_alloc_mux_widget(dapm, widgets, i, wid); if (ret < 0) return ret; + /* + * Pin mux will not use generic selector handler, so + * override. Also mux widget create will increment the + * index, so assign the previous widget. + */ + widgets[i].event_flags = SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_POST_REG; + widgets[i].event = hdac_generic_pin_mux_event;
wid_ref[2] = &widgets[i++]; } @@ -677,7 +993,8 @@ static int hdac_codec_alloc_power_widget(struct snd_soc_dapm_context *dapm, sprintf(widget_name, "Power %x", wid->nid); ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], snd_soc_dapm_supply, wid, widget_name, - NULL, NULL, 0, NULL, 0); + NULL, NULL, 0, hdac_generic_widget_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
diff --git a/sound/soc/codecs/hdac_generic.h b/sound/soc/codecs/hdac_generic.h index 5ca713a..a938b50 100644 --- a/sound/soc/codecs/hdac_generic.h +++ b/sound/soc/codecs/hdac_generic.h @@ -21,5 +21,8 @@ #define __HDAC_GENERIC_H__
#define HDAC_GENERIC_NAME_SIZE 32 +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define PIN_OUT (AC_PINCTL_OUT_EN)
#endif /* __HDAC_GENERIC_H__ */
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/hda/local.h | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/sound/hda/local.h b/sound/hda/local.h index a51f5f6..cceeac2 100644 --- a/sound/hda/local.h +++ b/sound/hda/local.h @@ -11,6 +11,9 @@ #define get_pcaps(codec, nid) \ snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)
+#define get_defcfg_device(cfg) \ + ((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT) + static inline int is_input_pin(struct hdac_device *codec, hda_nid_t nid) { unsigned int pincap = get_pcaps(codec, nid);
The default configuration of pin complex is queried and machine controls are created.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_generic.c | 170 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/hdac_generic.h | 3 + 2 files changed, 173 insertions(+)
diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c index 354e81d..6eb0dc2 100644 --- a/sound/soc/codecs/hdac_generic.c +++ b/sound/soc/codecs/hdac_generic.c @@ -708,6 +708,176 @@ static int hdac_generic_fill_widget_info(struct device *dev, return 0; }
+static int hdac_generic_add_machine_control(struct snd_soc_dapm_context *dapm, + enum snd_soc_dapm_type id, const char *name, + struct hdac_codec_widget *wid, int dir) +{ + struct snd_soc_dapm_widget **wid_ref; + struct snd_soc_dapm_widget *dapm_w; + struct snd_soc_dapm_route route; + char widget_name[HDAC_GENERIC_NAME_SIZE]; + int ret; + + dapm_w = devm_kzalloc(dapm->dev, sizeof(*dapm_w), GFP_KERNEL); + if (!dapm_w) + return -ENOMEM; + + sprintf(widget_name, "%s %x", name, wid->nid); + + ret = hdac_generic_fill_widget_info(dapm->dev, dapm_w, + id, NULL, widget_name, + NULL, NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + wid_ref = wid->priv; + + snd_soc_dapm_new_control(dapm, dapm_w); + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + route.sink = dapm_w->name; + route.control = NULL; + route.source = wid_ref[0]->name; + route.connected = NULL; + } else { + route.sink = wid_ref[0]->name; + route.control = NULL; + route.source = dapm_w->name; + route.connected = NULL; + } + snd_soc_dapm_add_routes(dapm, &route, 1); + snd_soc_dapm_new_widgets(dapm->card); + + return 0; +} + +/** + * hdac_generic_machine_control_init - Add machine widgets and graph + * @dapm: machine dapm context + * @codec: codec object + * + * Reads the default configuration parameters and builds the machine + * controls and graph. + */ +int hdac_generic_machine_control_init(struct snd_soc_dapm_context *dapm, + struct snd_soc_codec *codec) +{ + struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec); + struct hdac_codec_widget *wid; + struct snd_soc_dapm_widget **wid_ref; + unsigned int *cfg; + short dev; + int ret; + + list_for_each_entry(wid, &edev->hdac.widget_list, head) { + + dev_dbg(dapm->dev, "For wid: %x type: %d\n", + wid->nid, wid->type); + if (wid->type != AC_WID_PIN) + continue; + + cfg = wid->params; + dev = get_defcfg_device(*cfg); + + if (is_input_pin(&edev->hdac, wid->nid) && + ((dev == AC_JACK_LINE_OUT) || + (dev == AC_JACK_SPEAKER) || + (dev == AC_JACK_DIG_OTHER_OUT) || + (dev == AC_JACK_HP_OUT) || + (dev == AC_JACK_SPDIF_OUT))) { + + dev_warn(dapm->dev, + "Wrong pin and dev configuration, nid: %x\n", + wid->nid); + + continue; + } + + switch (dev) { + case AC_JACK_LINE_OUT: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_line, "Lineout", + wid, SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + return ret; + break; + case AC_JACK_SPEAKER: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_spk, "spk", + wid, SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + return ret; + break; + case AC_JACK_SPDIF_OUT: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_spk, "spdif_out", + wid, SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + return ret; + break; + case AC_JACK_DIG_OTHER_OUT: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_spk, "dig_other_out", + wid, SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + return ret; + break; + + case AC_JACK_HP_OUT: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_hp, "hp", + wid, SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + return ret; + break; + + case AC_JACK_AUX: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_line, "Aux", + wid, SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + return ret; + break; + case AC_JACK_LINE_IN: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_line, "Linein", + wid, SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + return ret; + break; + + case AC_JACK_MIC_IN: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_mic, "MIC", + wid, SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + return ret; + break; + case AC_JACK_SPDIF_IN: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_mic, "spdif_in", + wid, SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + return ret; + break; + case AC_JACK_DIG_OTHER_IN: + ret = hdac_generic_add_machine_control(dapm, + snd_soc_dapm_mic, "dig_other_in", + wid, SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + return ret; + break; + + default: + wid_ref = wid->priv; + snd_soc_dapm_nc_pin(dapm, wid_ref[0]->name); + break; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(hdac_generic_machine_control_init); + static int hdac_generic_alloc_mux_widget(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_widget *widgets, int index, struct hdac_codec_widget *wid) diff --git a/sound/soc/codecs/hdac_generic.h b/sound/soc/codecs/hdac_generic.h index a938b50..36eafbd 100644 --- a/sound/soc/codecs/hdac_generic.h +++ b/sound/soc/codecs/hdac_generic.h @@ -25,4 +25,7 @@ #define AMP_OUT_UNMUTE 0xb000 #define PIN_OUT (AC_PINCTL_OUT_EN)
+int hdac_generic_machine_control_init(struct snd_soc_dapm_context *dapm, + struct snd_soc_codec *codec); + #endif /* __HDAC_GENERIC_H__ */
Ext driver match function will be used to apply any hdac device specific quirks.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/hdaudio_ext.h | 1 + sound/hda/ext/hdac_ext_bus.c | 12 ++++++++++++ 2 files changed, 13 insertions(+)
diff --git a/include/sound/hdaudio_ext.h b/include/sound/hdaudio_ext.h index b9593b2..42c2fa6 100644 --- a/include/sound/hdaudio_ext.h +++ b/include/sound/hdaudio_ext.h @@ -217,6 +217,7 @@ struct hdac_ext_device { struct snd_card *card; void *scodec; void *private_data; + const struct hda_device_id *id_entry; };
struct hdac_ext_dma_params { diff --git a/sound/hda/ext/hdac_ext_bus.c b/sound/hda/ext/hdac_ext_bus.c index 31b510c..c5ca488 100644 --- a/sound/hda/ext/hdac_ext_bus.c +++ b/sound/hda/ext/hdac_ext_bus.c @@ -235,6 +235,17 @@ static void hdac_ext_drv_shutdown(struct device *dev) return (get_edrv(dev))->shutdown(get_edev(dev)); }
+static int hdac_ext_drv_match(struct hdac_device *dev, struct hdac_driver *drv) +{ + struct hdac_ext_device *edev = get_edev(&dev->dev); + + edev->id_entry = hdac_get_device_id(dev, drv); + if (edev->id_entry) + return 1; + else + return 0; +} + /** * snd_hda_ext_driver_register - register a driver for ext hda devices * @@ -244,6 +255,7 @@ int snd_hda_ext_driver_register(struct hdac_ext_driver *drv) { drv->hdac.type = HDA_DEV_ASOC; drv->hdac.driver.bus = &snd_hda_bus_type; + drv->hdac.match = hdac_ext_drv_match; /* we use default match */
if (drv->probe)
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_generic.c | 39 ++++++++++++++++++++++++++++++++------- sound/soc/codecs/hdac_generic.h | 5 +++++ 2 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c index 6eb0dc2..cf556ee 100644 --- a/sound/soc/codecs/hdac_generic.c +++ b/sound/soc/codecs/hdac_generic.c @@ -41,6 +41,7 @@ struct hdac_generic_priv { unsigned int num_adcs; unsigned int num_dacs; unsigned int num_dapm_widgets; + struct hdac_generic_vendor_ops *vendor_ops; };
/* @@ -1636,6 +1637,7 @@ 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; + struct hdac_generic_vendor_ops *ops; int num_dais = 0; int ret = 0;
@@ -1676,6 +1678,16 @@ static int hdac_generic_dev_probe(struct hdac_ext_device *edev) return ret; }
+ /* codec specific init if any */ + ops = (struct hdac_generic_vendor_ops *)edev->id_entry->driver_data; + if (ops && ops->init) { + ret = ops->init(edev); + if (ret < 0) + return ret; + } + + hdac_priv->vendor_ops = ops; + /* ASoC specific initialization */ return snd_soc_register_codec(&codec->dev, &hdac_generic_codec, codec_dais, num_dais); @@ -1687,14 +1699,27 @@ static int hdac_generic_dev_remove(struct hdac_ext_device *edev) return 0; }
-/* - * 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 void write_def_coeffs(struct hdac_ext_device *edev) +{ + snd_hdac_codec_write(&edev->hdac, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 0x4f); + snd_hdac_codec_write(&edev->hdac, 0x20, 0, + AC_VERB_SET_PROC_COEF, 0x5000); +} + +int hdac_realtek_init(struct hdac_ext_device *edev) +{ + write_def_coeffs(edev); + + return 0; +} + +struct hdac_generic_vendor_ops realtek_ops = { + .init = hdac_realtek_init, +}; + static const struct hda_device_id codec_list[] = { - HDA_CODEC_EXT_ENTRY(0x10ec0286, 0x100002, "ALC286", 0), + HDA_CODEC_EXT_ENTRY(0x10ec0286, 0x100002, "ALC286", &realtek_ops), {} }; MODULE_DEVICE_TABLE(hdaudio, codec_list); diff --git a/sound/soc/codecs/hdac_generic.h b/sound/soc/codecs/hdac_generic.h index 36eafbd..e7cd2e2 100644 --- a/sound/soc/codecs/hdac_generic.h +++ b/sound/soc/codecs/hdac_generic.h @@ -25,6 +25,11 @@ #define AMP_OUT_UNMUTE 0xb000 #define PIN_OUT (AC_PINCTL_OUT_EN)
+struct hdac_generic_vendor_ops { + int (*init)(struct hdac_ext_device *edev); + int (*cleanup)(struct hdac_ext_device *edev); +}; + int hdac_generic_machine_control_init(struct snd_soc_dapm_context *dapm, struct snd_soc_codec *codec);
participants (2)
-
Subhransu S. Prusty
-
Takashi Iwai