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