1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * HD audio Component Binding Interface
4 *
5 * Copyright (C) 2021, 2023 Cirrus Logic, Inc. and
6 * Cirrus Logic International Semiconductor Ltd.
7 */
8
9 #include <linux/acpi.h>
10 #include <linux/component.h>
11 #include <linux/module.h>
12 #include <linux/slab.h>
13 #include <sound/hda_codec.h>
14 #include "hda_component.h"
15 #include "hda_local.h"
16
17 #ifdef CONFIG_ACPI
hda_component_acpi_device_notify(struct hda_component_parent * parent,acpi_handle handle,u32 event,void * data)18 void hda_component_acpi_device_notify(struct hda_component_parent *parent,
19 acpi_handle handle, u32 event, void *data)
20 {
21 struct hda_component *comp;
22 int i;
23
24 guard(mutex)(&parent->mutex);
25 for (i = 0; i < ARRAY_SIZE(parent->comps); i++) {
26 comp = hda_component_from_index(parent, i);
27 if (comp->dev && comp->acpi_notify)
28 comp->acpi_notify(acpi_device_handle(comp->adev), event, comp->dev);
29 }
30 }
31 EXPORT_SYMBOL_NS_GPL(hda_component_acpi_device_notify, "SND_HDA_SCODEC_COMPONENT");
32
hda_component_manager_bind_acpi_notifications(struct hda_codec * cdc,struct hda_component_parent * parent,acpi_notify_handler handler,void * data)33 int hda_component_manager_bind_acpi_notifications(struct hda_codec *cdc,
34 struct hda_component_parent *parent,
35 acpi_notify_handler handler, void *data)
36 {
37 bool support_notifications = false;
38 struct acpi_device *adev;
39 struct hda_component *comp;
40 int ret;
41 int i;
42
43 adev = parent->comps[0].adev;
44 if (!acpi_device_handle(adev))
45 return 0;
46
47 for (i = 0; i < ARRAY_SIZE(parent->comps); i++) {
48 comp = hda_component_from_index(parent, i);
49 support_notifications = support_notifications ||
50 comp->acpi_notifications_supported;
51 }
52
53 if (support_notifications) {
54 ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
55 handler, data);
56 if (ret < 0) {
57 codec_warn(cdc, "Failed to install notify handler: %d\n", ret);
58 return 0;
59 }
60
61 codec_dbg(cdc, "Notify handler installed\n");
62 }
63
64 return 0;
65 }
66 EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind_acpi_notifications, "SND_HDA_SCODEC_COMPONENT");
67
hda_component_manager_unbind_acpi_notifications(struct hda_codec * cdc,struct hda_component_parent * parent,acpi_notify_handler handler)68 void hda_component_manager_unbind_acpi_notifications(struct hda_codec *cdc,
69 struct hda_component_parent *parent,
70 acpi_notify_handler handler)
71 {
72 struct acpi_device *adev;
73 int ret;
74
75 adev = parent->comps[0].adev;
76 if (!acpi_device_handle(adev))
77 return;
78
79 ret = acpi_remove_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, handler);
80 if (ret < 0)
81 codec_warn(cdc, "Failed to uninstall notify handler: %d\n", ret);
82 }
83 EXPORT_SYMBOL_NS_GPL(hda_component_manager_unbind_acpi_notifications, "SND_HDA_SCODEC_COMPONENT");
84 #endif /* ifdef CONFIG_ACPI */
85
hda_component_manager_playback_hook(struct hda_component_parent * parent,int action)86 void hda_component_manager_playback_hook(struct hda_component_parent *parent, int action)
87 {
88 struct hda_component *comp;
89 int i;
90
91 guard(mutex)(&parent->mutex);
92 for (i = 0; i < ARRAY_SIZE(parent->comps); i++) {
93 comp = hda_component_from_index(parent, i);
94 if (comp->dev && comp->pre_playback_hook)
95 comp->pre_playback_hook(comp->dev, action);
96 }
97 for (i = 0; i < ARRAY_SIZE(parent->comps); i++) {
98 comp = hda_component_from_index(parent, i);
99 if (comp->dev && comp->playback_hook)
100 comp->playback_hook(comp->dev, action);
101 }
102 for (i = 0; i < ARRAY_SIZE(parent->comps); i++) {
103 comp = hda_component_from_index(parent, i);
104 if (comp->dev && comp->post_playback_hook)
105 comp->post_playback_hook(comp->dev, action);
106 }
107 }
108 EXPORT_SYMBOL_NS_GPL(hda_component_manager_playback_hook, "SND_HDA_SCODEC_COMPONENT");
109
110 struct hda_scodec_match {
111 const char *bus;
112 const char *hid;
113 const char *match_str;
114 int index;
115 };
116
117 /* match the device name in a slightly relaxed manner */
hda_comp_match_dev_name(struct device * dev,void * data)118 static int hda_comp_match_dev_name(struct device *dev, void *data)
119 {
120 struct hda_scodec_match *p = data;
121 const char *d = dev_name(dev);
122 int n = strlen(p->bus);
123 char tmp[32];
124
125 /* check the bus name */
126 if (strncmp(d, p->bus, n))
127 return 0;
128 /* skip the bus number */
129 if (isdigit(d[n]))
130 n++;
131 /* the rest must be exact matching */
132 snprintf(tmp, sizeof(tmp), p->match_str, p->hid, p->index);
133 return !strcmp(d + n, tmp);
134 }
135
hda_component_manager_bind(struct hda_codec * cdc,struct hda_component_parent * parent)136 int hda_component_manager_bind(struct hda_codec *cdc,
137 struct hda_component_parent *parent)
138 {
139 /* Init shared and component specific data */
140 memset(parent->comps, 0, sizeof(parent->comps));
141
142 guard(mutex)(&parent->mutex);
143 return component_bind_all(hda_codec_dev(cdc), parent);
144 }
145 EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind, "SND_HDA_SCODEC_COMPONENT");
146
hda_component_manager_init(struct hda_codec * cdc,struct hda_component_parent * parent,int count,const char * bus,const char * hid,const char * match_str,const struct component_master_ops * ops)147 int hda_component_manager_init(struct hda_codec *cdc,
148 struct hda_component_parent *parent, int count,
149 const char *bus, const char *hid,
150 const char *match_str,
151 const struct component_master_ops *ops)
152 {
153 struct device *dev = hda_codec_dev(cdc);
154 struct component_match *match = NULL;
155 struct hda_scodec_match *sm;
156 int ret, i;
157
158 if (parent->codec) {
159 codec_err(cdc, "Component binding already created (SSID: %x)\n",
160 cdc->core.subsystem_id);
161 return -EINVAL;
162 }
163 parent->codec = cdc;
164
165 mutex_init(&parent->mutex);
166
167 for (i = 0; i < count; i++) {
168 sm = devm_kmalloc(dev, sizeof(*sm), GFP_KERNEL);
169 if (!sm)
170 return -ENOMEM;
171
172 sm->bus = bus;
173 sm->hid = hid;
174 sm->match_str = match_str;
175 sm->index = i;
176 component_match_add(dev, &match, hda_comp_match_dev_name, sm);
177 if (IS_ERR(match)) {
178 codec_err(cdc, "Fail to add component %ld\n", PTR_ERR(match));
179 return PTR_ERR(match);
180 }
181 }
182
183 ret = component_master_add_with_match(dev, ops, match);
184 if (ret)
185 codec_err(cdc, "Fail to register component aggregator %d\n", ret);
186
187 return ret;
188 }
189 EXPORT_SYMBOL_NS_GPL(hda_component_manager_init, "SND_HDA_SCODEC_COMPONENT");
190
hda_component_manager_free(struct hda_component_parent * parent,const struct component_master_ops * ops)191 void hda_component_manager_free(struct hda_component_parent *parent,
192 const struct component_master_ops *ops)
193 {
194 struct device *dev;
195
196 if (!parent->codec)
197 return;
198
199 dev = hda_codec_dev(parent->codec);
200
201 component_master_del(dev, ops);
202
203 parent->codec = NULL;
204 }
205 EXPORT_SYMBOL_NS_GPL(hda_component_manager_free, "SND_HDA_SCODEC_COMPONENT");
206
207 MODULE_DESCRIPTION("HD Audio component binding library");
208 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
209 MODULE_LICENSE("GPL");
210