diff options
Diffstat (limited to 'sound/pci/hda/hda_hwdep.c')
-rw-r--r-- | sound/pci/hda/hda_hwdep.c | 234 |
1 files changed, 233 insertions, 1 deletions
diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c index 6e18a422d993..300ab407cf42 100644 --- a/sound/pci/hda/hda_hwdep.c +++ b/sound/pci/hda/hda_hwdep.c | |||
@@ -23,10 +23,12 @@ | |||
23 | #include <linux/pci.h> | 23 | #include <linux/pci.h> |
24 | #include <linux/compat.h> | 24 | #include <linux/compat.h> |
25 | #include <linux/mutex.h> | 25 | #include <linux/mutex.h> |
26 | #include <linux/ctype.h> | ||
26 | #include <sound/core.h> | 27 | #include <sound/core.h> |
27 | #include "hda_codec.h" | 28 | #include "hda_codec.h" |
28 | #include "hda_local.h" | 29 | #include "hda_local.h" |
29 | #include <sound/hda_hwdep.h> | 30 | #include <sound/hda_hwdep.h> |
31 | #include <sound/minors.h> | ||
30 | 32 | ||
31 | /* | 33 | /* |
32 | * write/read an out-of-bound verb | 34 | * write/read an out-of-bound verb |
@@ -95,7 +97,26 @@ static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file) | |||
95 | return 0; | 97 | return 0; |
96 | } | 98 | } |
97 | 99 | ||
98 | int __devinit snd_hda_create_hwdep(struct hda_codec *codec) | 100 | static void clear_hwdep_elements(struct hda_codec *codec) |
101 | { | ||
102 | char **head; | ||
103 | int i; | ||
104 | |||
105 | /* clear init verbs */ | ||
106 | snd_array_free(&codec->init_verbs); | ||
107 | /* clear hints */ | ||
108 | head = codec->hints.list; | ||
109 | for (i = 0; i < codec->hints.used; i++, head++) | ||
110 | kfree(*head); | ||
111 | snd_array_free(&codec->hints); | ||
112 | } | ||
113 | |||
114 | static void hwdep_free(struct snd_hwdep *hwdep) | ||
115 | { | ||
116 | clear_hwdep_elements(hwdep->private_data); | ||
117 | } | ||
118 | |||
119 | int /*__devinit*/ snd_hda_create_hwdep(struct hda_codec *codec) | ||
99 | { | 120 | { |
100 | char hwname[16]; | 121 | char hwname[16]; |
101 | struct snd_hwdep *hwdep; | 122 | struct snd_hwdep *hwdep; |
@@ -109,6 +130,7 @@ int __devinit snd_hda_create_hwdep(struct hda_codec *codec) | |||
109 | sprintf(hwdep->name, "HDA Codec %d", codec->addr); | 130 | sprintf(hwdep->name, "HDA Codec %d", codec->addr); |
110 | hwdep->iface = SNDRV_HWDEP_IFACE_HDA; | 131 | hwdep->iface = SNDRV_HWDEP_IFACE_HDA; |
111 | hwdep->private_data = codec; | 132 | hwdep->private_data = codec; |
133 | hwdep->private_free = hwdep_free; | ||
112 | hwdep->exclusive = 1; | 134 | hwdep->exclusive = 1; |
113 | 135 | ||
114 | hwdep->ops.open = hda_hwdep_open; | 136 | hwdep->ops.open = hda_hwdep_open; |
@@ -117,5 +139,215 @@ int __devinit snd_hda_create_hwdep(struct hda_codec *codec) | |||
117 | hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat; | 139 | hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat; |
118 | #endif | 140 | #endif |
119 | 141 | ||
142 | snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32); | ||
143 | snd_array_init(&codec->hints, sizeof(char *), 32); | ||
144 | |||
120 | return 0; | 145 | return 0; |
121 | } | 146 | } |
147 | |||
148 | #ifdef CONFIG_SND_HDA_RECONFIG | ||
149 | |||
150 | /* | ||
151 | * sysfs interface | ||
152 | */ | ||
153 | |||
154 | static int clear_codec(struct hda_codec *codec) | ||
155 | { | ||
156 | snd_hda_codec_reset(codec); | ||
157 | clear_hwdep_elements(codec); | ||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | static int reconfig_codec(struct hda_codec *codec) | ||
162 | { | ||
163 | int err; | ||
164 | |||
165 | snd_printk(KERN_INFO "hda-codec: reconfiguring\n"); | ||
166 | snd_hda_codec_reset(codec); | ||
167 | err = snd_hda_codec_configure(codec); | ||
168 | if (err < 0) | ||
169 | return err; | ||
170 | /* rebuild PCMs */ | ||
171 | err = snd_hda_codec_build_pcms(codec); | ||
172 | if (err < 0) | ||
173 | return err; | ||
174 | /* rebuild mixers */ | ||
175 | err = snd_hda_codec_build_controls(codec); | ||
176 | if (err < 0) | ||
177 | return err; | ||
178 | return 0; | ||
179 | } | ||
180 | |||
181 | /* | ||
182 | * allocate a string at most len chars, and remove the trailing EOL | ||
183 | */ | ||
184 | static char *kstrndup_noeol(const char *src, size_t len) | ||
185 | { | ||
186 | char *s = kstrndup(src, len, GFP_KERNEL); | ||
187 | char *p; | ||
188 | if (!s) | ||
189 | return NULL; | ||
190 | p = strchr(s, '\n'); | ||
191 | if (p) | ||
192 | *p = 0; | ||
193 | return s; | ||
194 | } | ||
195 | |||
196 | #define CODEC_INFO_SHOW(type) \ | ||
197 | static ssize_t type##_show(struct device *dev, \ | ||
198 | struct device_attribute *attr, \ | ||
199 | char *buf) \ | ||
200 | { \ | ||
201 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); \ | ||
202 | struct hda_codec *codec = hwdep->private_data; \ | ||
203 | return sprintf(buf, "0x%x\n", codec->type); \ | ||
204 | } | ||
205 | |||
206 | #define CODEC_INFO_STR_SHOW(type) \ | ||
207 | static ssize_t type##_show(struct device *dev, \ | ||
208 | struct device_attribute *attr, \ | ||
209 | char *buf) \ | ||
210 | { \ | ||
211 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); \ | ||
212 | struct hda_codec *codec = hwdep->private_data; \ | ||
213 | return sprintf(buf, "%s\n", \ | ||
214 | codec->type ? codec->type : ""); \ | ||
215 | } | ||
216 | |||
217 | CODEC_INFO_SHOW(vendor_id); | ||
218 | CODEC_INFO_SHOW(subsystem_id); | ||
219 | CODEC_INFO_SHOW(revision_id); | ||
220 | CODEC_INFO_SHOW(afg); | ||
221 | CODEC_INFO_SHOW(mfg); | ||
222 | CODEC_INFO_STR_SHOW(name); | ||
223 | CODEC_INFO_STR_SHOW(modelname); | ||
224 | |||
225 | #define CODEC_INFO_STORE(type) \ | ||
226 | static ssize_t type##_store(struct device *dev, \ | ||
227 | struct device_attribute *attr, \ | ||
228 | const char *buf, size_t count) \ | ||
229 | { \ | ||
230 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); \ | ||
231 | struct hda_codec *codec = hwdep->private_data; \ | ||
232 | char *after; \ | ||
233 | codec->type = simple_strtoul(buf, &after, 0); \ | ||
234 | return count; \ | ||
235 | } | ||
236 | |||
237 | #define CODEC_INFO_STR_STORE(type) \ | ||
238 | static ssize_t type##_store(struct device *dev, \ | ||
239 | struct device_attribute *attr, \ | ||
240 | const char *buf, size_t count) \ | ||
241 | { \ | ||
242 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); \ | ||
243 | struct hda_codec *codec = hwdep->private_data; \ | ||
244 | char *s = kstrndup_noeol(buf, 64); \ | ||
245 | if (!s) \ | ||
246 | return -ENOMEM; \ | ||
247 | kfree(codec->type); \ | ||
248 | codec->type = s; \ | ||
249 | return count; \ | ||
250 | } | ||
251 | |||
252 | CODEC_INFO_STORE(vendor_id); | ||
253 | CODEC_INFO_STORE(subsystem_id); | ||
254 | CODEC_INFO_STORE(revision_id); | ||
255 | CODEC_INFO_STR_STORE(name); | ||
256 | CODEC_INFO_STR_STORE(modelname); | ||
257 | |||
258 | #define CODEC_ACTION_STORE(type) \ | ||
259 | static ssize_t type##_store(struct device *dev, \ | ||
260 | struct device_attribute *attr, \ | ||
261 | const char *buf, size_t count) \ | ||
262 | { \ | ||
263 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); \ | ||
264 | struct hda_codec *codec = hwdep->private_data; \ | ||
265 | int err = 0; \ | ||
266 | if (*buf) \ | ||
267 | err = type##_codec(codec); \ | ||
268 | return err < 0 ? err : count; \ | ||
269 | } | ||
270 | |||
271 | CODEC_ACTION_STORE(reconfig); | ||
272 | CODEC_ACTION_STORE(clear); | ||
273 | |||
274 | static ssize_t init_verbs_store(struct device *dev, | ||
275 | struct device_attribute *attr, | ||
276 | const char *buf, size_t count) | ||
277 | { | ||
278 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
279 | struct hda_codec *codec = hwdep->private_data; | ||
280 | char *p; | ||
281 | struct hda_verb verb, *v; | ||
282 | |||
283 | verb.nid = simple_strtoul(buf, &p, 0); | ||
284 | verb.verb = simple_strtoul(p, &p, 0); | ||
285 | verb.param = simple_strtoul(p, &p, 0); | ||
286 | if (!verb.nid || !verb.verb || !verb.param) | ||
287 | return -EINVAL; | ||
288 | v = snd_array_new(&codec->init_verbs); | ||
289 | if (!v) | ||
290 | return -ENOMEM; | ||
291 | *v = verb; | ||
292 | return count; | ||
293 | } | ||
294 | |||
295 | static ssize_t hints_store(struct device *dev, | ||
296 | struct device_attribute *attr, | ||
297 | const char *buf, size_t count) | ||
298 | { | ||
299 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
300 | struct hda_codec *codec = hwdep->private_data; | ||
301 | char *p; | ||
302 | char **hint; | ||
303 | |||
304 | if (!*buf || isspace(*buf) || *buf == '#' || *buf == '\n') | ||
305 | return count; | ||
306 | p = kstrndup_noeol(buf, 1024); | ||
307 | if (!p) | ||
308 | return -ENOMEM; | ||
309 | hint = snd_array_new(&codec->hints); | ||
310 | if (!hint) { | ||
311 | kfree(p); | ||
312 | return -ENOMEM; | ||
313 | } | ||
314 | *hint = p; | ||
315 | return count; | ||
316 | } | ||
317 | |||
318 | #define CODEC_ATTR_RW(type) \ | ||
319 | __ATTR(type, 0644, type##_show, type##_store) | ||
320 | #define CODEC_ATTR_RO(type) \ | ||
321 | __ATTR_RO(type) | ||
322 | #define CODEC_ATTR_WO(type) \ | ||
323 | __ATTR(type, 0200, NULL, type##_store) | ||
324 | |||
325 | static struct device_attribute codec_attrs[] = { | ||
326 | CODEC_ATTR_RW(vendor_id), | ||
327 | CODEC_ATTR_RW(subsystem_id), | ||
328 | CODEC_ATTR_RW(revision_id), | ||
329 | CODEC_ATTR_RO(afg), | ||
330 | CODEC_ATTR_RO(mfg), | ||
331 | CODEC_ATTR_RW(name), | ||
332 | CODEC_ATTR_RW(modelname), | ||
333 | CODEC_ATTR_WO(init_verbs), | ||
334 | CODEC_ATTR_WO(hints), | ||
335 | CODEC_ATTR_WO(reconfig), | ||
336 | CODEC_ATTR_WO(clear), | ||
337 | }; | ||
338 | |||
339 | /* | ||
340 | * create sysfs files on hwdep directory | ||
341 | */ | ||
342 | int snd_hda_hwdep_add_sysfs(struct hda_codec *codec) | ||
343 | { | ||
344 | struct snd_hwdep *hwdep = codec->hwdep; | ||
345 | int i; | ||
346 | |||
347 | for (i = 0; i < ARRAY_SIZE(codec_attrs); i++) | ||
348 | snd_add_device_sysfs_file(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, | ||
349 | hwdep->device, &codec_attrs[i]); | ||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | #endif /* CONFIG_SND_HDA_RECONFIG */ | ||