1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * motu-hwdep.c - a part of driver for MOTU FireWire series
4 *
5 * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6 */
7
8/*
9 * This codes have five functionalities.
10 *
11 * 1.get information about firewire node
12 * 2.get notification about starting/stopping stream
13 * 3.lock/unlock streaming
14 *
15 */
16
17#include "motu.h"
18
19static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
20		       loff_t *offset)
21{
22	struct snd_motu *motu = hwdep->private_data;
23	DEFINE_WAIT(wait);
24	union snd_firewire_event event;
25
26	spin_lock_irq(&motu->lock);
27
28	while (!motu->dev_lock_changed && motu->msg == 0) {
29		prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
30		spin_unlock_irq(&motu->lock);
31		schedule();
32		finish_wait(&motu->hwdep_wait, &wait);
33		if (signal_pending(current))
34			return -ERESTARTSYS;
35		spin_lock_irq(&motu->lock);
36	}
37
38	memset(&event, 0, sizeof(event));
39	if (motu->dev_lock_changed) {
40		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
41		event.lock_status.status = (motu->dev_lock_count > 0);
42		motu->dev_lock_changed = false;
43
44		count = min_t(long, count, sizeof(event.lock_status));
45	} else {
46		event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION;
47		event.motu_notification.message = motu->msg;
48		motu->msg = 0;
49
50		count = min_t(long, count, sizeof(event.motu_notification));
51	}
52
53	spin_unlock_irq(&motu->lock);
54
55	if (copy_to_user(buf, &event, count))
56		return -EFAULT;
57
58	return count;
59}
60
61static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
62			       poll_table *wait)
63{
64	struct snd_motu *motu = hwdep->private_data;
65	__poll_t events;
66
67	poll_wait(file, &motu->hwdep_wait, wait);
68
69	spin_lock_irq(&motu->lock);
70	if (motu->dev_lock_changed || motu->msg)
71		events = EPOLLIN | EPOLLRDNORM;
72	else
73		events = 0;
74	spin_unlock_irq(&motu->lock);
75
76	return events | EPOLLOUT;
77}
78
79static int hwdep_get_info(struct snd_motu *motu, void __user *arg)
80{
81	struct fw_device *dev = fw_parent_device(motu->unit);
82	struct snd_firewire_get_info info;
83
84	memset(&info, 0, sizeof(info));
85	info.type = SNDRV_FIREWIRE_TYPE_MOTU;
86	info.card = dev->card->index;
87	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
88	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
89	strlcpy(info.device_name, dev_name(&dev->device),
90		sizeof(info.device_name));
91
92	if (copy_to_user(arg, &info, sizeof(info)))
93		return -EFAULT;
94
95	return 0;
96}
97
98static int hwdep_lock(struct snd_motu *motu)
99{
100	int err;
101
102	spin_lock_irq(&motu->lock);
103
104	if (motu->dev_lock_count == 0) {
105		motu->dev_lock_count = -1;
106		err = 0;
107	} else {
108		err = -EBUSY;
109	}
110
111	spin_unlock_irq(&motu->lock);
112
113	return err;
114}
115
116static int hwdep_unlock(struct snd_motu *motu)
117{
118	int err;
119
120	spin_lock_irq(&motu->lock);
121
122	if (motu->dev_lock_count == -1) {
123		motu->dev_lock_count = 0;
124		err = 0;
125	} else {
126		err = -EBADFD;
127	}
128
129	spin_unlock_irq(&motu->lock);
130
131	return err;
132}
133
134static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
135{
136	struct snd_motu *motu = hwdep->private_data;
137
138	spin_lock_irq(&motu->lock);
139	if (motu->dev_lock_count == -1)
140		motu->dev_lock_count = 0;
141	spin_unlock_irq(&motu->lock);
142
143	return 0;
144}
145
146static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
147	    unsigned int cmd, unsigned long arg)
148{
149	struct snd_motu *motu = hwdep->private_data;
150
151	switch (cmd) {
152	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
153		return hwdep_get_info(motu, (void __user *)arg);
154	case SNDRV_FIREWIRE_IOCTL_LOCK:
155		return hwdep_lock(motu);
156	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
157		return hwdep_unlock(motu);
158	default:
159		return -ENOIOCTLCMD;
160	}
161}
162
163#ifdef CONFIG_COMPAT
164static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
165			      unsigned int cmd, unsigned long arg)
166{
167	return hwdep_ioctl(hwdep, file, cmd,
168			   (unsigned long)compat_ptr(arg));
169}
170#else
171#define hwdep_compat_ioctl NULL
172#endif
173
174int snd_motu_create_hwdep_device(struct snd_motu *motu)
175{
176	static const struct snd_hwdep_ops ops = {
177		.read		= hwdep_read,
178		.release	= hwdep_release,
179		.poll		= hwdep_poll,
180		.ioctl		= hwdep_ioctl,
181		.ioctl_compat	= hwdep_compat_ioctl,
182	};
183	struct snd_hwdep *hwdep;
184	int err;
185
186	err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep);
187	if (err < 0)
188		return err;
189
190	strcpy(hwdep->name, "MOTU");
191	hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU;
192	hwdep->ops = ops;
193	hwdep->private_data = motu;
194	hwdep->exclusive = true;
195
196	return 0;
197}
198