Merge tag 'char-misc-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[linux.git] / sound / pci / hda / hda_component.c
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
18 void hda_component_acpi_device_notify(struct hda_component *comps, int num_comps,
19                                       acpi_handle handle, u32 event, void *data)
20 {
21         int i;
22
23         for (i = 0; i < num_comps; i++) {
24                 if (comps[i].dev && comps[i].acpi_notify)
25                         comps[i].acpi_notify(acpi_device_handle(comps[i].adev), event,
26                                              comps[i].dev);
27         }
28 }
29 EXPORT_SYMBOL_NS_GPL(hda_component_acpi_device_notify, SND_HDA_SCODEC_COMPONENT);
30
31 int hda_component_manager_bind_acpi_notifications(struct hda_codec *cdc,
32                                                   struct hda_component *comps, int num_comps,
33                                                   acpi_notify_handler handler, void *data)
34 {
35         bool support_notifications = false;
36         struct acpi_device *adev;
37         int ret;
38         int i;
39
40         adev = comps[0].adev;
41         if (!acpi_device_handle(adev))
42                 return 0;
43
44         for (i = 0; i < num_comps; i++)
45                 support_notifications = support_notifications ||
46                         comps[i].acpi_notifications_supported;
47
48         if (support_notifications) {
49                 ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
50                                                   handler, data);
51                 if (ret < 0) {
52                         codec_warn(cdc, "Failed to install notify handler: %d\n", ret);
53                         return 0;
54                 }
55
56                 codec_dbg(cdc, "Notify handler installed\n");
57         }
58
59         return 0;
60 }
61 EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind_acpi_notifications, SND_HDA_SCODEC_COMPONENT);
62
63 void hda_component_manager_unbind_acpi_notifications(struct hda_codec *cdc,
64                                                      struct hda_component *comps,
65                                                      acpi_notify_handler handler)
66 {
67         struct acpi_device *adev;
68         int ret;
69
70         adev = comps[0].adev;
71         if (!acpi_device_handle(adev))
72                 return;
73
74         ret = acpi_remove_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, handler);
75         if (ret < 0)
76                 codec_warn(cdc, "Failed to uninstall notify handler: %d\n", ret);
77 }
78 EXPORT_SYMBOL_NS_GPL(hda_component_manager_unbind_acpi_notifications, SND_HDA_SCODEC_COMPONENT);
79 #endif /* ifdef CONFIG_ACPI */
80
81 void hda_component_manager_playback_hook(struct hda_component *comps, int num_comps, int action)
82 {
83         int i;
84
85         for (i = 0; i < num_comps; i++) {
86                 if (comps[i].dev && comps[i].pre_playback_hook)
87                         comps[i].pre_playback_hook(comps[i].dev, action);
88         }
89         for (i = 0; i < num_comps; i++) {
90                 if (comps[i].dev && comps[i].playback_hook)
91                         comps[i].playback_hook(comps[i].dev, action);
92         }
93         for (i = 0; i < num_comps; i++) {
94                 if (comps[i].dev && comps[i].post_playback_hook)
95                         comps[i].post_playback_hook(comps[i].dev, action);
96         }
97 }
98 EXPORT_SYMBOL_NS_GPL(hda_component_manager_playback_hook, SND_HDA_SCODEC_COMPONENT);
99
100 struct hda_scodec_match {
101         const char *bus;
102         const char *hid;
103         const char *match_str;
104         int index;
105 };
106
107 /* match the device name in a slightly relaxed manner */
108 static int hda_comp_match_dev_name(struct device *dev, void *data)
109 {
110         struct hda_scodec_match *p = data;
111         const char *d = dev_name(dev);
112         int n = strlen(p->bus);
113         char tmp[32];
114
115         /* check the bus name */
116         if (strncmp(d, p->bus, n))
117                 return 0;
118         /* skip the bus number */
119         if (isdigit(d[n]))
120                 n++;
121         /* the rest must be exact matching */
122         snprintf(tmp, sizeof(tmp), p->match_str, p->hid, p->index);
123         return !strcmp(d + n, tmp);
124 }
125
126 int hda_component_manager_init(struct hda_codec *cdc,
127                                struct hda_component *comps, int count,
128                                const char *bus, const char *hid,
129                                const char *match_str,
130                                const struct component_master_ops *ops)
131 {
132         struct device *dev = hda_codec_dev(cdc);
133         struct component_match *match = NULL;
134         struct hda_scodec_match *sm;
135         int ret, i;
136
137         for (i = 0; i < count; i++) {
138                 sm = devm_kmalloc(dev, sizeof(*sm), GFP_KERNEL);
139                 if (!sm)
140                         return -ENOMEM;
141
142                 sm->bus = bus;
143                 sm->hid = hid;
144                 sm->match_str = match_str;
145                 sm->index = i;
146                 comps[i].codec = cdc;
147                 component_match_add(dev, &match, hda_comp_match_dev_name, sm);
148         }
149
150         ret = component_master_add_with_match(dev, ops, match);
151         if (ret)
152                 codec_err(cdc, "Fail to register component aggregator %d\n", ret);
153
154         return ret;
155 }
156 EXPORT_SYMBOL_NS_GPL(hda_component_manager_init, SND_HDA_SCODEC_COMPONENT);
157
158 void hda_component_manager_free(struct hda_codec *cdc,
159                                 const struct component_master_ops *ops)
160 {
161         struct device *dev = hda_codec_dev(cdc);
162
163         component_master_del(dev, ops);
164 }
165 EXPORT_SYMBOL_NS_GPL(hda_component_manager_free, SND_HDA_SCODEC_COMPONENT);
166
167 MODULE_DESCRIPTION("HD Audio component binding library");
168 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
169 MODULE_LICENSE("GPL");