[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/Makefile b/src/kernel/linux/v4.14/sound/firewire/tascam/Makefile
new file mode 100644
index 0000000..0fc955d
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/Makefile
@@ -0,0 +1,4 @@
+snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \
+			    tascam-pcm.o tascam-hwdep.o tascam-transaction.o \
+			    tascam-midi.o tascam.o
+obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/amdtp-tascam.c b/src/kernel/linux/v4.14/sound/firewire/tascam/amdtp-tascam.c
new file mode 100644
index 0000000..6aff1fc
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/amdtp-tascam.c
@@ -0,0 +1,185 @@
+/*
+ * amdtp-tascam.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <sound/pcm.h>
+#include "tascam.h"
+
+#define AMDTP_FMT_TSCM_TX	0x1e
+#define AMDTP_FMT_TSCM_RX	0x3e
+
+struct amdtp_tscm {
+	unsigned int pcm_channels;
+};
+
+int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate)
+{
+	struct amdtp_tscm *p = s->protocol;
+	unsigned int data_channels;
+
+	if (amdtp_stream_running(s))
+		return -EBUSY;
+
+	data_channels = p->pcm_channels;
+
+	/* Packets in in-stream have extra 2 data channels. */
+	if (s->direction == AMDTP_IN_STREAM)
+		data_channels += 2;
+
+	return amdtp_stream_set_parameters(s, rate, data_channels);
+}
+
+static void write_pcm_s32(struct amdtp_stream *s,
+			  struct snd_pcm_substream *pcm,
+			  __be32 *buffer, unsigned int frames)
+{
+	struct amdtp_tscm *p = s->protocol;
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	unsigned int channels, remaining_frames, i, c;
+	const u32 *src;
+
+	channels = p->pcm_channels;
+	src = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	for (i = 0; i < frames; ++i) {
+		for (c = 0; c < channels; ++c) {
+			buffer[c] = cpu_to_be32(*src);
+			src++;
+		}
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			src = (void *)runtime->dma_area;
+	}
+}
+
+static void read_pcm_s32(struct amdtp_stream *s,
+			 struct snd_pcm_substream *pcm,
+			 __be32 *buffer, unsigned int frames)
+{
+	struct amdtp_tscm *p = s->protocol;
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	unsigned int channels, remaining_frames, i, c;
+	u32 *dst;
+
+	channels = p->pcm_channels;
+	dst  = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	/* The first data channel is for event counter. */
+	buffer += 1;
+
+	for (i = 0; i < frames; ++i) {
+		for (c = 0; c < channels; ++c) {
+			*dst = be32_to_cpu(buffer[c]);
+			dst++;
+		}
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			dst = (void *)runtime->dma_area;
+	}
+}
+
+static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer,
+			      unsigned int data_blocks)
+{
+	struct amdtp_tscm *p = s->protocol;
+	unsigned int channels, i, c;
+
+	channels = p->pcm_channels;
+
+	for (i = 0; i < data_blocks; ++i) {
+		for (c = 0; c < channels; ++c)
+			buffer[c] = 0x00000000;
+		buffer += s->data_block_quadlets;
+	}
+}
+
+int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s,
+				      struct snd_pcm_runtime *runtime)
+{
+	int err;
+
+	/*
+	 * Our implementation allows this protocol to deliver 24 bit sample in
+	 * 32bit data channel.
+	 */
+	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	if (err < 0)
+		return err;
+
+	return amdtp_stream_add_pcm_hw_constraints(s, runtime);
+}
+
+static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
+					   __be32 *buffer,
+					   unsigned int data_blocks,
+					   unsigned int *syt)
+{
+	struct snd_pcm_substream *pcm;
+
+	pcm = ACCESS_ONCE(s->pcm);
+	if (data_blocks > 0 && pcm)
+		read_pcm_s32(s, pcm, buffer, data_blocks);
+
+	/* A place holder for control messages. */
+
+	return data_blocks;
+}
+
+static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
+					   __be32 *buffer,
+					   unsigned int data_blocks,
+					   unsigned int *syt)
+{
+	struct snd_pcm_substream *pcm;
+
+	/* This field is not used. */
+	*syt = 0x0000;
+
+	pcm = ACCESS_ONCE(s->pcm);
+	if (pcm)
+		write_pcm_s32(s, pcm, buffer, data_blocks);
+	else
+		write_pcm_silence(s, buffer, data_blocks);
+
+	return data_blocks;
+}
+
+int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit,
+		    enum amdtp_stream_direction dir, unsigned int pcm_channels)
+{
+	amdtp_stream_process_data_blocks_t process_data_blocks;
+	struct amdtp_tscm *p;
+	unsigned int fmt;
+	int err;
+
+	if (dir == AMDTP_IN_STREAM) {
+		fmt = AMDTP_FMT_TSCM_TX;
+		process_data_blocks = process_tx_data_blocks;
+	} else {
+		fmt = AMDTP_FMT_TSCM_RX;
+		process_data_blocks = process_rx_data_blocks;
+	}
+
+	err = amdtp_stream_init(s, unit, dir,
+				CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt,
+				process_data_blocks, sizeof(struct amdtp_tscm));
+	if (err < 0)
+		return 0;
+
+	/* Use fixed value for FDF field. */
+	s->fdf = 0x00;
+
+	/* This protocol uses fixed number of data channels for PCM samples. */
+	p = s->protocol;
+	p->pcm_channels = pcm_channels;
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-hwdep.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-hwdep.c
new file mode 100644
index 0000000..8c4437d
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-hwdep.c
@@ -0,0 +1,189 @@
+/*
+ * tascam-hwdep.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * This codes give three functionality.
+ *
+ * 1.get firewire node information
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock stream
+ */
+
+#include "tascam.h"
+
+static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+		       loff_t *offset)
+{
+	struct snd_tscm *tscm = hwdep->private_data;
+	DEFINE_WAIT(wait);
+	union snd_firewire_event event = {
+		.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
+	};
+
+	spin_lock_irq(&tscm->lock);
+
+	while (!tscm->dev_lock_changed) {
+		prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&tscm->lock);
+		schedule();
+		finish_wait(&tscm->hwdep_wait, &wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		spin_lock_irq(&tscm->lock);
+	}
+
+	event.lock_status.status = (tscm->dev_lock_count > 0);
+	tscm->dev_lock_changed = false;
+
+	spin_unlock_irq(&tscm->lock);
+
+	count = min_t(long, count, sizeof(event.lock_status));
+
+	if (copy_to_user(buf, &event, count))
+		return -EFAULT;
+
+	return count;
+}
+
+static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
+			       poll_table *wait)
+{
+	struct snd_tscm *tscm = hwdep->private_data;
+	unsigned int events;
+
+	poll_wait(file, &tscm->hwdep_wait, wait);
+
+	spin_lock_irq(&tscm->lock);
+	if (tscm->dev_lock_changed)
+		events = POLLIN | POLLRDNORM;
+	else
+		events = 0;
+	spin_unlock_irq(&tscm->lock);
+
+	return events;
+}
+
+static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg)
+{
+	struct fw_device *dev = fw_parent_device(tscm->unit);
+	struct snd_firewire_get_info info;
+
+	memset(&info, 0, sizeof(info));
+	info.type = SNDRV_FIREWIRE_TYPE_TASCAM;
+	info.card = dev->card->index;
+	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
+	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
+	strlcpy(info.device_name, dev_name(&dev->device),
+		sizeof(info.device_name));
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int hwdep_lock(struct snd_tscm *tscm)
+{
+	int err;
+
+	spin_lock_irq(&tscm->lock);
+
+	if (tscm->dev_lock_count == 0) {
+		tscm->dev_lock_count = -1;
+		err = 0;
+	} else {
+		err = -EBUSY;
+	}
+
+	spin_unlock_irq(&tscm->lock);
+
+	return err;
+}
+
+static int hwdep_unlock(struct snd_tscm *tscm)
+{
+	int err;
+
+	spin_lock_irq(&tscm->lock);
+
+	if (tscm->dev_lock_count == -1) {
+		tscm->dev_lock_count = 0;
+		err = 0;
+	} else {
+		err = -EBADFD;
+	}
+
+	spin_unlock_irq(&tscm->lock);
+
+	return err;
+}
+
+static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+	struct snd_tscm *tscm = hwdep->private_data;
+
+	spin_lock_irq(&tscm->lock);
+	if (tscm->dev_lock_count == -1)
+		tscm->dev_lock_count = 0;
+	spin_unlock_irq(&tscm->lock);
+
+	return 0;
+}
+
+static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+	    unsigned int cmd, unsigned long arg)
+{
+	struct snd_tscm *tscm = hwdep->private_data;
+
+	switch (cmd) {
+	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+		return hwdep_get_info(tscm, (void __user *)arg);
+	case SNDRV_FIREWIRE_IOCTL_LOCK:
+		return hwdep_lock(tscm);
+	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+		return hwdep_unlock(tscm);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+#ifdef CONFIG_COMPAT
+static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
+			      unsigned int cmd, unsigned long arg)
+{
+	return hwdep_ioctl(hwdep, file, cmd,
+			   (unsigned long)compat_ptr(arg));
+}
+#else
+#define hwdep_compat_ioctl NULL
+#endif
+
+int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
+{
+	static const struct snd_hwdep_ops ops = {
+		.read		= hwdep_read,
+		.release	= hwdep_release,
+		.poll		= hwdep_poll,
+		.ioctl		= hwdep_ioctl,
+		.ioctl_compat	= hwdep_compat_ioctl,
+	};
+	struct snd_hwdep *hwdep;
+	int err;
+
+	err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep);
+	if (err < 0)
+		return err;
+
+	strcpy(hwdep->name, "Tascam");
+	hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM;
+	hwdep->ops = ops;
+	hwdep->private_data = tscm;
+	hwdep->exclusive = true;
+
+	return err;
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-midi.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-midi.c
new file mode 100644
index 0000000..4a74157
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-midi.c
@@ -0,0 +1,136 @@
+/*
+ * tascam-midi.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "tascam.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	/* Do nothing. */
+	return 0;
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_tscm *tscm = substream->rmidi->private_data;
+
+	snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]);
+
+	return 0;
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+	/* Do nothing. */
+	return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void midi_playback_drain(struct snd_rawmidi_substream *substream)
+{
+	struct snd_tscm *tscm = substream->rmidi->private_data;
+
+	snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]);
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_tscm *tscm = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tscm->lock, flags);
+
+	if (up)
+		tscm->tx_midi_substreams[substrm->number] = substrm;
+	else
+		tscm->tx_midi_substreams[substrm->number] = NULL;
+
+	spin_unlock_irqrestore(&tscm->lock, flags);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_tscm *tscm = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tscm->lock, flags);
+
+	if (up)
+		snd_fw_async_midi_port_run(&tscm->out_ports[substrm->number],
+					   substrm);
+
+	spin_unlock_irqrestore(&tscm->lock, flags);
+}
+
+int snd_tscm_create_midi_devices(struct snd_tscm *tscm)
+{
+	static const struct snd_rawmidi_ops capture_ops = {
+		.open		= midi_capture_open,
+		.close		= midi_capture_close,
+		.trigger	= midi_capture_trigger,
+	};
+	static const struct snd_rawmidi_ops playback_ops = {
+		.open		= midi_playback_open,
+		.close		= midi_playback_close,
+		.drain		= midi_playback_drain,
+		.trigger	= midi_playback_trigger,
+	};
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *stream;
+	struct snd_rawmidi_substream *subs;
+	int err;
+
+	err = snd_rawmidi_new(tscm->card, tscm->card->driver, 0,
+			      tscm->spec->midi_playback_ports,
+			      tscm->spec->midi_capture_ports,
+			      &rmidi);
+	if (err < 0)
+		return err;
+
+	snprintf(rmidi->name, sizeof(rmidi->name),
+		 "%s MIDI", tscm->card->shortname);
+	rmidi->private_data = tscm;
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &capture_ops);
+	stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+
+	/* Set port names for MIDI input. */
+	list_for_each_entry(subs, &stream->substreams, list) {
+		/* TODO: support virtual MIDI ports. */
+		if (subs->number < tscm->spec->midi_capture_ports) {
+			/* Hardware MIDI ports. */
+			snprintf(subs->name, sizeof(subs->name),
+				 "%s MIDI %d",
+				 tscm->card->shortname, subs->number + 1);
+		}
+	}
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &playback_ops);
+	stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+
+	/* Set port names for MIDI ourput. */
+	list_for_each_entry(subs, &stream->substreams, list) {
+		if (subs->number < tscm->spec->midi_playback_ports) {
+			/* Hardware MIDI ports only. */
+			snprintf(subs->name, sizeof(subs->name),
+				 "%s MIDI %d",
+				 tscm->card->shortname, subs->number + 1);
+		}
+	}
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-pcm.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-pcm.c
new file mode 100644
index 0000000..87da807
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-pcm.c
@@ -0,0 +1,301 @@
+/*
+ * tascam-pcm.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "tascam.h"
+
+static int pcm_init_hw_params(struct snd_tscm *tscm,
+			      struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_hardware *hw = &runtime->hw;
+	struct amdtp_stream *stream;
+	unsigned int pcm_channels;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
+		stream = &tscm->tx_stream;
+		pcm_channels = tscm->spec->pcm_capture_analog_channels;
+	} else {
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
+		stream = &tscm->rx_stream;
+		pcm_channels = tscm->spec->pcm_playback_analog_channels;
+	}
+
+	if (tscm->spec->has_adat)
+		pcm_channels += 8;
+	if (tscm->spec->has_spdif)
+		pcm_channels += 2;
+	runtime->hw.channels_min = runtime->hw.channels_max = pcm_channels;
+
+	hw->rates = SNDRV_PCM_RATE_44100 |
+		    SNDRV_PCM_RATE_48000 |
+		    SNDRV_PCM_RATE_88200 |
+		    SNDRV_PCM_RATE_96000;
+	snd_pcm_limit_hw_rates(runtime);
+
+	return amdtp_tscm_add_pcm_hw_constraints(stream, runtime);
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+	enum snd_tscm_clock clock;
+	unsigned int rate;
+	int err;
+
+	err = snd_tscm_stream_lock_try(tscm);
+	if (err < 0)
+		goto end;
+
+	err = pcm_init_hw_params(tscm, substream);
+	if (err < 0)
+		goto err_locked;
+
+	err = snd_tscm_stream_get_clock(tscm, &clock);
+	if (err < 0)
+		goto err_locked;
+
+	if (clock != SND_TSCM_CLOCK_INTERNAL ||
+	    amdtp_stream_pcm_running(&tscm->rx_stream) ||
+	    amdtp_stream_pcm_running(&tscm->tx_stream)) {
+		err = snd_tscm_stream_get_rate(tscm, &rate);
+		if (err < 0)
+			goto err_locked;
+		substream->runtime->hw.rate_min = rate;
+		substream->runtime->hw.rate_max = rate;
+	}
+
+	snd_pcm_set_sync(substream);
+end:
+	return err;
+err_locked:
+	snd_tscm_stream_lock_release(tscm);
+	return err;
+}
+
+static int pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	snd_tscm_stream_lock_release(tscm);
+
+	return 0;
+}
+
+static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_tscm *tscm = substream->private_data;
+	int err;
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		mutex_lock(&tscm->mutex);
+		tscm->substreams_counter++;
+		mutex_unlock(&tscm->mutex);
+	}
+
+	return 0;
+}
+
+static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_tscm *tscm = substream->private_data;
+	int err;
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		mutex_lock(&tscm->mutex);
+		tscm->substreams_counter++;
+		mutex_unlock(&tscm->mutex);
+	}
+
+	return 0;
+}
+
+static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	mutex_lock(&tscm->mutex);
+
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		tscm->substreams_counter--;
+
+	snd_tscm_stream_stop_duplex(tscm);
+
+	mutex_unlock(&tscm->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	mutex_lock(&tscm->mutex);
+
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		tscm->substreams_counter--;
+
+	snd_tscm_stream_stop_duplex(tscm);
+
+	mutex_unlock(&tscm->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	mutex_lock(&tscm->mutex);
+
+	err = snd_tscm_stream_start_duplex(tscm, runtime->rate);
+	if (err >= 0)
+		amdtp_stream_pcm_prepare(&tscm->tx_stream);
+
+	mutex_unlock(&tscm->mutex);
+
+	return err;
+}
+
+static int pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	mutex_lock(&tscm->mutex);
+
+	err = snd_tscm_stream_start_duplex(tscm, runtime->rate);
+	if (err >= 0)
+		amdtp_stream_pcm_prepare(&tscm->rx_stream);
+
+	mutex_unlock(&tscm->mutex);
+
+	return err;
+}
+
+static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&tscm->tx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&tscm->tx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&tscm->rx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&tscm->rx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_tscm *tscm = sbstrm->private_data;
+
+	return amdtp_stream_pcm_pointer(&tscm->tx_stream);
+}
+
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_tscm *tscm = sbstrm->private_data;
+
+	return amdtp_stream_pcm_pointer(&tscm->rx_stream);
+}
+
+static int pcm_capture_ack(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	return amdtp_stream_pcm_ack(&tscm->tx_stream);
+}
+
+static int pcm_playback_ack(struct snd_pcm_substream *substream)
+{
+	struct snd_tscm *tscm = substream->private_data;
+
+	return amdtp_stream_pcm_ack(&tscm->rx_stream);
+}
+
+int snd_tscm_create_pcm_devices(struct snd_tscm *tscm)
+{
+	static const struct snd_pcm_ops capture_ops = {
+		.open		= pcm_open,
+		.close		= pcm_close,
+		.ioctl		= snd_pcm_lib_ioctl,
+		.hw_params	= pcm_capture_hw_params,
+		.hw_free	= pcm_capture_hw_free,
+		.prepare	= pcm_capture_prepare,
+		.trigger	= pcm_capture_trigger,
+		.pointer	= pcm_capture_pointer,
+		.ack		= pcm_capture_ack,
+		.page		= snd_pcm_lib_get_vmalloc_page,
+	};
+	static const struct snd_pcm_ops playback_ops = {
+		.open		= pcm_open,
+		.close		= pcm_close,
+		.ioctl		= snd_pcm_lib_ioctl,
+		.hw_params	= pcm_playback_hw_params,
+		.hw_free	= pcm_playback_hw_free,
+		.prepare	= pcm_playback_prepare,
+		.trigger	= pcm_playback_trigger,
+		.pointer	= pcm_playback_pointer,
+		.ack		= pcm_playback_ack,
+		.page		= snd_pcm_lib_get_vmalloc_page,
+		.mmap		= snd_pcm_lib_mmap_vmalloc,
+	};
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(tscm->card, tscm->card->driver, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = tscm;
+	snprintf(pcm->name, sizeof(pcm->name),
+		 "%s PCM", tscm->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-proc.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-proc.c
new file mode 100644
index 0000000..bfd4a4c
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-proc.c
@@ -0,0 +1,88 @@
+/*
+ * tascam-proc.h - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./tascam.h"
+
+static void proc_read_firmware(struct snd_info_entry *entry,
+			       struct snd_info_buffer *buffer)
+{
+	struct snd_tscm *tscm = entry->private_data;
+	__be32 data;
+	unsigned int reg, fpga, arm, hw;
+	int err;
+
+	err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,
+			TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_REGISTER,
+			&data, sizeof(data), 0);
+	if (err < 0)
+		return;
+	reg = be32_to_cpu(data);
+
+	err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,
+			TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_FPGA,
+			&data, sizeof(data), 0);
+	if (err < 0)
+		return;
+	fpga = be32_to_cpu(data);
+
+	err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,
+			TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_ARM,
+			&data, sizeof(data), 0);
+	if (err < 0)
+		return;
+	arm = be32_to_cpu(data);
+
+	err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,
+			TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_HW,
+			&data, sizeof(data), 0);
+	if (err < 0)
+		return;
+	hw = be32_to_cpu(data);
+
+	snd_iprintf(buffer, "Register: %d (0x%08x)\n", reg & 0xffff, reg);
+	snd_iprintf(buffer, "FPGA:     %d (0x%08x)\n", fpga & 0xffff, fpga);
+	snd_iprintf(buffer, "ARM:      %d (0x%08x)\n", arm & 0xffff, arm);
+	snd_iprintf(buffer, "Hardware: %d (0x%08x)\n", hw >> 16, hw);
+}
+
+static void add_node(struct snd_tscm *tscm, struct snd_info_entry *root,
+		     const char *name,
+		     void (*op)(struct snd_info_entry *e,
+				struct snd_info_buffer *b))
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_card_entry(tscm->card, name, root);
+	if (entry == NULL)
+		return;
+
+	snd_info_set_text_ops(entry, tscm, op);
+	if (snd_info_register(entry) < 0)
+		snd_info_free_entry(entry);
+}
+
+void snd_tscm_proc_init(struct snd_tscm *tscm)
+{
+	struct snd_info_entry *root;
+
+	/*
+	 * All nodes are automatically removed at snd_card_disconnect(),
+	 * by following to link list.
+	 */
+	root = snd_info_create_card_entry(tscm->card, "firewire",
+					  tscm->card->proc_root);
+	if (root == NULL)
+		return;
+	root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+	if (snd_info_register(root) < 0) {
+		snd_info_free_entry(root);
+		return;
+	}
+
+	add_node(tscm, root, "firmware", proc_read_firmware);
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-stream.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-stream.c
new file mode 100644
index 0000000..a1308f1
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-stream.c
@@ -0,0 +1,508 @@
+/*
+ * tascam-stream.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/delay.h>
+#include "tascam.h"
+
+#define CLOCK_STATUS_MASK      0xffff0000
+#define CLOCK_CONFIG_MASK      0x0000ffff
+
+#define CALLBACK_TIMEOUT 500
+
+static int get_clock(struct snd_tscm *tscm, u32 *data)
+{
+	int trial = 0;
+	__be32 reg;
+	int err;
+
+	while (trial++ < 5) {
+		err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,
+				TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS,
+				&reg, sizeof(reg), 0);
+		if (err < 0)
+			return err;
+
+		*data = be32_to_cpu(reg);
+		if (*data & CLOCK_STATUS_MASK)
+			break;
+
+		// In intermediate state after changing clock status.
+		msleep(50);
+	}
+
+	// Still in the intermediate state.
+	if (trial >= 5)
+		return -EAGAIN;
+
+	return 0;
+}
+
+static int set_clock(struct snd_tscm *tscm, unsigned int rate,
+		     enum snd_tscm_clock clock)
+{
+	u32 data;
+	__be32 reg;
+	int err;
+
+	err = get_clock(tscm, &data);
+	if (err < 0)
+		return err;
+	data &= CLOCK_CONFIG_MASK;
+
+	if (rate > 0) {
+		data &= 0x000000ff;
+		/* Base rate. */
+		if ((rate % 44100) == 0) {
+			data |= 0x00000100;
+			/* Multiplier. */
+			if (rate / 44100 == 2)
+				data |= 0x00008000;
+		} else if ((rate % 48000) == 0) {
+			data |= 0x00000200;
+			/* Multiplier. */
+			if (rate / 48000 == 2)
+				data |= 0x00008000;
+		} else {
+			return -EAGAIN;
+		}
+	}
+
+	if (clock != INT_MAX) {
+		data &= 0x0000ff00;
+		data |= clock + 1;
+	}
+
+	reg = cpu_to_be32(data);
+
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	if (data & 0x00008000)
+		reg = cpu_to_be32(0x0000001a);
+	else
+		reg = cpu_to_be32(0x0000000d);
+
+	return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				  TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE,
+				  &reg, sizeof(reg), 0);
+}
+
+int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate)
+{
+	u32 data;
+	int err;
+
+	err = get_clock(tscm, &data);
+	if (err < 0)
+		return err;
+
+	data = (data & 0xff000000) >> 24;
+
+	/* Check base rate. */
+	if ((data & 0x0f) == 0x01)
+		*rate = 44100;
+	else if ((data & 0x0f) == 0x02)
+		*rate = 48000;
+	else
+		return -EAGAIN;
+
+	/* Check multiplier. */
+	if ((data & 0xf0) == 0x80)
+		*rate *= 2;
+	else if ((data & 0xf0) != 0x00)
+		return -EAGAIN;
+
+	return err;
+}
+
+int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock)
+{
+	u32 data;
+	int err;
+
+	err = get_clock(tscm, &data);
+	if (err < 0)
+		return err;
+
+	*clock = ((data & 0x00ff0000) >> 16) - 1;
+	if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT)
+		return -EIO;
+
+	return 0;
+}
+
+static int enable_data_channels(struct snd_tscm *tscm)
+{
+	__be32 reg;
+	u32 data;
+	unsigned int i;
+	int err;
+
+	data = 0;
+	for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i)
+		data |= BIT(i);
+	if (tscm->spec->has_adat)
+		data |= 0x0000ff00;
+	if (tscm->spec->has_spdif)
+		data |= 0x00030000;
+
+	reg = cpu_to_be32(data);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	data = 0;
+	for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i)
+		data |= BIT(i);
+	if (tscm->spec->has_adat)
+		data |= 0x0000ff00;
+	if (tscm->spec->has_spdif)
+		data |= 0x00030000;
+
+	reg = cpu_to_be32(data);
+	return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				  TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS,
+				  &reg, sizeof(reg), 0);
+}
+
+static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate)
+{
+	__be32 reg;
+	int err;
+
+	/* Set an option for unknown purpose. */
+	reg = cpu_to_be32(0x00200000);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	err = enable_data_channels(tscm);
+	if (err < 0)
+		return err;
+
+	return set_clock(tscm, rate, INT_MAX);
+}
+
+static void finish_session(struct snd_tscm *tscm)
+{
+	__be32 reg;
+
+	reg = 0;
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING,
+			   &reg, sizeof(reg), 0);
+
+	reg = 0;
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,
+			   &reg, sizeof(reg), 0);
+
+}
+
+static int begin_session(struct snd_tscm *tscm)
+{
+	__be32 reg;
+	int err;
+
+	reg = cpu_to_be32(0x00000001);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	reg = cpu_to_be32(0x00000001);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	/* Set an option for unknown purpose. */
+	reg = cpu_to_be32(0x00002000);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	/* Start multiplexing PCM samples on packets. */
+	reg = cpu_to_be32(0x00000001);
+	return snd_fw_transaction(tscm->unit,
+				  TCODE_WRITE_QUADLET_REQUEST,
+				  TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON,
+				  &reg, sizeof(reg), 0);
+}
+
+static void release_resources(struct snd_tscm *tscm)
+{
+	__be32 reg;
+
+	/* Unregister channels. */
+	reg = cpu_to_be32(0x00000000);
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH,
+			   &reg, sizeof(reg), 0);
+	reg = cpu_to_be32(0x00000000);
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,
+			   &reg, sizeof(reg), 0);
+	reg = cpu_to_be32(0x00000000);
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH,
+			   &reg, sizeof(reg), 0);
+
+	/* Release isochronous resources. */
+	fw_iso_resources_free(&tscm->tx_resources);
+	fw_iso_resources_free(&tscm->rx_resources);
+}
+
+static int keep_resources(struct snd_tscm *tscm, unsigned int rate)
+{
+	__be32 reg;
+	int err;
+
+	/* Keep resources for in-stream. */
+	err = amdtp_tscm_set_parameters(&tscm->tx_stream, rate);
+	if (err < 0)
+		return err;
+	err = fw_iso_resources_allocate(&tscm->tx_resources,
+			amdtp_stream_get_max_payload(&tscm->tx_stream),
+			fw_parent_device(tscm->unit)->max_speed);
+	if (err < 0)
+		goto error;
+
+	/* Keep resources for out-stream. */
+	err = amdtp_tscm_set_parameters(&tscm->rx_stream, rate);
+	if (err < 0)
+		return err;
+	err = fw_iso_resources_allocate(&tscm->rx_resources,
+			amdtp_stream_get_max_payload(&tscm->rx_stream),
+			fw_parent_device(tscm->unit)->max_speed);
+	if (err < 0)
+		return err;
+
+	/* Register the isochronous channel for transmitting stream. */
+	reg = cpu_to_be32(tscm->tx_resources.channel);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		goto error;
+
+	/* Unknown */
+	reg = cpu_to_be32(0x00000002);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		goto error;
+
+	/* Register the isochronous channel for receiving stream. */
+	reg = cpu_to_be32(tscm->rx_resources.channel);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		goto error;
+
+	return 0;
+error:
+	release_resources(tscm);
+	return err;
+}
+
+int snd_tscm_stream_init_duplex(struct snd_tscm *tscm)
+{
+	unsigned int pcm_channels;
+	int err;
+
+	/* For out-stream. */
+	err = fw_iso_resources_init(&tscm->rx_resources, tscm->unit);
+	if (err < 0)
+		return err;
+	pcm_channels = tscm->spec->pcm_playback_analog_channels;
+	if (tscm->spec->has_adat)
+		pcm_channels += 8;
+	if (tscm->spec->has_spdif)
+		pcm_channels += 2;
+	err = amdtp_tscm_init(&tscm->rx_stream, tscm->unit, AMDTP_OUT_STREAM,
+			      pcm_channels);
+	if (err < 0)
+		return err;
+
+	/* For in-stream. */
+	err = fw_iso_resources_init(&tscm->tx_resources, tscm->unit);
+	if (err < 0)
+		return err;
+	pcm_channels = tscm->spec->pcm_capture_analog_channels;
+	if (tscm->spec->has_adat)
+		pcm_channels += 8;
+	if (tscm->spec->has_spdif)
+		pcm_channels += 2;
+	err = amdtp_tscm_init(&tscm->tx_stream, tscm->unit, AMDTP_IN_STREAM,
+			      pcm_channels);
+	if (err < 0)
+		amdtp_stream_destroy(&tscm->rx_stream);
+
+	return err;
+}
+
+/* At bus reset, streaming is stopped and some registers are clear. */
+void snd_tscm_stream_update_duplex(struct snd_tscm *tscm)
+{
+	amdtp_stream_pcm_abort(&tscm->tx_stream);
+	amdtp_stream_stop(&tscm->tx_stream);
+
+	amdtp_stream_pcm_abort(&tscm->rx_stream);
+	amdtp_stream_stop(&tscm->rx_stream);
+}
+
+/*
+ * This function should be called before starting streams or after stopping
+ * streams.
+ */
+void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm)
+{
+	amdtp_stream_destroy(&tscm->rx_stream);
+	amdtp_stream_destroy(&tscm->tx_stream);
+
+	fw_iso_resources_destroy(&tscm->rx_resources);
+	fw_iso_resources_destroy(&tscm->tx_resources);
+}
+
+int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
+{
+	unsigned int curr_rate;
+	int err;
+
+	if (tscm->substreams_counter == 0)
+		return 0;
+
+	err = snd_tscm_stream_get_rate(tscm, &curr_rate);
+	if (err < 0)
+		return err;
+	if (curr_rate != rate ||
+	    amdtp_streaming_error(&tscm->rx_stream) ||
+	    amdtp_streaming_error(&tscm->tx_stream)) {
+		finish_session(tscm);
+
+		amdtp_stream_stop(&tscm->rx_stream);
+		amdtp_stream_stop(&tscm->tx_stream);
+
+		release_resources(tscm);
+	}
+
+	if (!amdtp_stream_running(&tscm->rx_stream)) {
+		err = keep_resources(tscm, rate);
+		if (err < 0)
+			goto error;
+
+		err = set_stream_formats(tscm, rate);
+		if (err < 0)
+			goto error;
+
+		err = begin_session(tscm);
+		if (err < 0)
+			goto error;
+
+		err = amdtp_stream_start(&tscm->rx_stream,
+				tscm->rx_resources.channel,
+				fw_parent_device(tscm->unit)->max_speed);
+		if (err < 0)
+			goto error;
+
+		if (!amdtp_stream_wait_callback(&tscm->rx_stream,
+						CALLBACK_TIMEOUT)) {
+			err = -ETIMEDOUT;
+			goto error;
+		}
+	}
+
+	if (!amdtp_stream_running(&tscm->tx_stream)) {
+		err = amdtp_stream_start(&tscm->tx_stream,
+				tscm->tx_resources.channel,
+				fw_parent_device(tscm->unit)->max_speed);
+		if (err < 0)
+			goto error;
+
+		if (!amdtp_stream_wait_callback(&tscm->tx_stream,
+						CALLBACK_TIMEOUT)) {
+			err = -ETIMEDOUT;
+			goto error;
+		}
+	}
+
+	return 0;
+error:
+	amdtp_stream_stop(&tscm->rx_stream);
+	amdtp_stream_stop(&tscm->tx_stream);
+
+	finish_session(tscm);
+	release_resources(tscm);
+
+	return err;
+}
+
+void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm)
+{
+	if (tscm->substreams_counter > 0)
+		return;
+
+	amdtp_stream_stop(&tscm->tx_stream);
+	amdtp_stream_stop(&tscm->rx_stream);
+
+	finish_session(tscm);
+	release_resources(tscm);
+}
+
+void snd_tscm_stream_lock_changed(struct snd_tscm *tscm)
+{
+	tscm->dev_lock_changed = true;
+	wake_up(&tscm->hwdep_wait);
+}
+
+int snd_tscm_stream_lock_try(struct snd_tscm *tscm)
+{
+	int err;
+
+	spin_lock_irq(&tscm->lock);
+
+	/* user land lock this */
+	if (tscm->dev_lock_count < 0) {
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* this is the first time */
+	if (tscm->dev_lock_count++ == 0)
+		snd_tscm_stream_lock_changed(tscm);
+	err = 0;
+end:
+	spin_unlock_irq(&tscm->lock);
+	return err;
+}
+
+void snd_tscm_stream_lock_release(struct snd_tscm *tscm)
+{
+	spin_lock_irq(&tscm->lock);
+
+	if (WARN_ON(tscm->dev_lock_count <= 0))
+		goto end;
+	if (--tscm->dev_lock_count == 0)
+		snd_tscm_stream_lock_changed(tscm);
+end:
+	spin_unlock_irq(&tscm->lock);
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-transaction.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-transaction.c
new file mode 100644
index 0000000..8967c52
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam-transaction.c
@@ -0,0 +1,400 @@
+/*
+ * tascam-transaction.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "tascam.h"
+
+/*
+ * When return minus value, given argument is not MIDI status.
+ * When return 0, given argument is a beginning of system exclusive.
+ * When return the others, given argument is MIDI data.
+ */
+static inline int calculate_message_bytes(u8 status)
+{
+	switch (status) {
+	case 0xf6:	/* Tune request. */
+	case 0xf8:	/* Timing clock. */
+	case 0xfa:	/* Start. */
+	case 0xfb:	/* Continue. */
+	case 0xfc:	/* Stop. */
+	case 0xfe:	/* Active sensing. */
+	case 0xff:	/* System reset. */
+		return 1;
+	case 0xf1:	/* MIDI time code quarter frame. */
+	case 0xf3:	/* Song select. */
+		return 2;
+	case 0xf2:	/* Song position pointer. */
+		return 3;
+	case 0xf0:	/* Exclusive. */
+		return 0;
+	case 0xf7:	/* End of exclusive. */
+		break;
+	case 0xf4:	/* Undefined. */
+	case 0xf5:	/* Undefined. */
+	case 0xf9:	/* Undefined. */
+	case 0xfd:	/* Undefined. */
+		break;
+	default:
+		switch (status & 0xf0) {
+		case 0x80:	/* Note on. */
+		case 0x90:	/* Note off. */
+		case 0xa0:	/* Polyphonic key pressure. */
+		case 0xb0:	/* Control change and Mode change. */
+		case 0xe0:	/* Pitch bend change. */
+			return 3;
+		case 0xc0:	/* Program change. */
+		case 0xd0:	/* Channel pressure. */
+			return 2;
+		default:
+		break;
+		}
+	break;
+	}
+
+	return -EINVAL;
+}
+
+static int fill_message(struct snd_fw_async_midi_port *port,
+			struct snd_rawmidi_substream *substream)
+{
+	int i, len, consume;
+	u8 *label, *msg;
+	u8 status;
+
+	/* The first byte is used for label, the rest for MIDI bytes. */
+	label = port->buf;
+	msg = port->buf + 1;
+
+	consume = snd_rawmidi_transmit_peek(substream, msg, 3);
+	if (consume == 0)
+		return 0;
+
+	/* On exclusive message. */
+	if (port->on_sysex) {
+		/* Seek the end of exclusives. */
+		for (i = 0; i < consume; ++i) {
+			if (msg[i] == 0xf7) {
+				port->on_sysex = false;
+				break;
+			}
+		}
+
+		/* At the end of exclusive message, use label 0x07. */
+		if (!port->on_sysex) {
+			consume = i + 1;
+			*label = (substream->number << 4) | 0x07;
+		/* During exclusive message, use label 0x04. */
+		} else if (consume == 3) {
+			*label = (substream->number << 4) | 0x04;
+		/* We need to fill whole 3 bytes. Go to next change. */
+		} else {
+			return 0;
+		}
+
+		len = consume;
+	} else {
+		/* The beginning of exclusives. */
+		if (msg[0] == 0xf0) {
+			/* Transfer it in next chance in another condition. */
+			port->on_sysex = true;
+			return 0;
+		} else {
+			/* On running-status. */
+			if ((msg[0] & 0x80) != 0x80)
+				status = port->running_status;
+			else
+				status = msg[0];
+
+			/* Calculate consume bytes. */
+			len = calculate_message_bytes(status);
+			if (len <= 0)
+				return 0;
+
+			/* On running-status. */
+			if ((msg[0] & 0x80) != 0x80) {
+				/* Enough MIDI bytes were not retrieved. */
+				if (consume < len - 1)
+					return 0;
+				consume = len - 1;
+
+				msg[2] = msg[1];
+				msg[1] = msg[0];
+				msg[0] = port->running_status;
+			} else {
+				/* Enough MIDI bytes were not retrieved. */
+				if (consume < len)
+					return 0;
+				consume = len;
+
+				port->running_status = msg[0];
+			}
+		}
+
+		*label = (substream->number << 4) | (msg[0] >> 4);
+	}
+
+	if (len > 0 && len < 3)
+		memset(msg + len, 0, 3 - len);
+
+	return consume;
+}
+
+static void async_midi_port_callback(struct fw_card *card, int rcode,
+				     void *data, size_t length,
+				     void *callback_data)
+{
+	struct snd_fw_async_midi_port *port = callback_data;
+	struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
+
+	/* This port is closed. */
+	if (substream == NULL)
+		return;
+
+	if (rcode == RCODE_COMPLETE)
+		snd_rawmidi_transmit_ack(substream, port->consume_bytes);
+	else if (!rcode_is_permanent_error(rcode))
+		/* To start next transaction immediately for recovery. */
+		port->next_ktime = 0;
+	else
+		/* Don't continue processing. */
+		port->error = true;
+
+	port->idling = true;
+
+	if (!snd_rawmidi_transmit_empty(substream))
+		schedule_work(&port->work);
+}
+
+static void midi_port_work(struct work_struct *work)
+{
+	struct snd_fw_async_midi_port *port =
+			container_of(work, struct snd_fw_async_midi_port, work);
+	struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
+	int generation;
+
+	/* Under transacting or error state. */
+	if (!port->idling || port->error)
+		return;
+
+	/* Nothing to do. */
+	if (substream == NULL || snd_rawmidi_transmit_empty(substream))
+		return;
+
+	/* Do it in next chance. */
+	if (ktime_after(port->next_ktime, ktime_get())) {
+		schedule_work(&port->work);
+		return;
+	}
+
+	/*
+	 * Fill the buffer. The callee must use snd_rawmidi_transmit_peek().
+	 * Later, snd_rawmidi_transmit_ack() is called.
+	 */
+	memset(port->buf, 0, 4);
+	port->consume_bytes = fill_message(port, substream);
+	if (port->consume_bytes <= 0) {
+		/* Do it in next chance, immediately. */
+		if (port->consume_bytes == 0) {
+			port->next_ktime = 0;
+			schedule_work(&port->work);
+		} else {
+			/* Fatal error. */
+			port->error = true;
+		}
+		return;
+	}
+
+	/* Set interval to next transaction. */
+	port->next_ktime = ktime_add_ns(ktime_get(),
+				port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
+
+	/* Start this transaction. */
+	port->idling = false;
+
+	/*
+	 * In Linux FireWire core, when generation is updated with memory
+	 * barrier, node id has already been updated. In this module, After
+	 * this smp_rmb(), load/store instructions to memory are completed.
+	 * Thus, both of generation and node id are available with recent
+	 * values. This is a light-serialization solution to handle bus reset
+	 * events on IEEE 1394 bus.
+	 */
+	generation = port->parent->generation;
+	smp_rmb();
+
+	fw_send_request(port->parent->card, &port->transaction,
+			TCODE_WRITE_QUADLET_REQUEST,
+			port->parent->node_id, generation,
+			port->parent->max_speed,
+			TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD,
+			port->buf, 4, async_midi_port_callback,
+			port);
+}
+
+void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port)
+{
+	port->idling = true;
+	port->error = false;
+	port->running_status = 0;
+	port->on_sysex = false;
+}
+
+static void handle_midi_tx(struct fw_card *card, struct fw_request *request,
+			   int tcode, int destination, int source,
+			   int generation, unsigned long long offset,
+			   void *data, size_t length, void *callback_data)
+{
+	struct snd_tscm *tscm = callback_data;
+	u32 *buf = (u32 *)data;
+	unsigned int messages;
+	unsigned int i;
+	unsigned int port;
+	struct snd_rawmidi_substream *substream;
+	u8 *b;
+	int bytes;
+
+	if (offset != tscm->async_handler.offset)
+		goto end;
+
+	messages = length / 8;
+	for (i = 0; i < messages; i++) {
+		b = (u8 *)(buf + i * 2);
+
+		port = b[0] >> 4;
+		/* TODO: support virtual MIDI ports. */
+		if (port >= tscm->spec->midi_capture_ports)
+			goto end;
+
+		/* Assume the message length. */
+		bytes = calculate_message_bytes(b[1]);
+		/* On MIDI data or exclusives. */
+		if (bytes <= 0) {
+			/* Seek the end of exclusives. */
+			for (bytes = 1; bytes < 4; bytes++) {
+				if (b[bytes] == 0xf7)
+					break;
+			}
+			if (bytes == 4)
+				bytes = 3;
+		}
+
+		substream = ACCESS_ONCE(tscm->tx_midi_substreams[port]);
+		if (substream != NULL)
+			snd_rawmidi_receive(substream, b + 1, bytes);
+	}
+end:
+	fw_send_response(card, request, RCODE_COMPLETE);
+}
+
+int snd_tscm_transaction_register(struct snd_tscm *tscm)
+{
+	static const struct fw_address_region resp_register_region = {
+		.start	= 0xffffe0000000ull,
+		.end	= 0xffffe000ffffull,
+	};
+	unsigned int i;
+	int err;
+
+	/*
+	 * Usually, two quadlets are transferred by one transaction. The first
+	 * quadlet has MIDI messages, the rest includes timestamp.
+	 * Sometimes, 8 set of the data is transferred by a block transaction.
+	 */
+	tscm->async_handler.length = 8 * 8;
+	tscm->async_handler.address_callback = handle_midi_tx;
+	tscm->async_handler.callback_data = tscm;
+
+	err = fw_core_add_address_handler(&tscm->async_handler,
+					  &resp_register_region);
+	if (err < 0)
+		return err;
+
+	err = snd_tscm_transaction_reregister(tscm);
+	if (err < 0)
+		goto error;
+
+	for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) {
+		tscm->out_ports[i].parent = fw_parent_device(tscm->unit);
+		tscm->out_ports[i].next_ktime = 0;
+		INIT_WORK(&tscm->out_ports[i].work, midi_port_work);
+	}
+
+	return err;
+error:
+	fw_core_remove_address_handler(&tscm->async_handler);
+	tscm->async_handler.callback_data = NULL;
+	return err;
+}
+
+/* At bus reset, these registers are cleared. */
+int snd_tscm_transaction_reregister(struct snd_tscm *tscm)
+{
+	struct fw_device *device = fw_parent_device(tscm->unit);
+	__be32 reg;
+	int err;
+
+	/* Register messaging address. Block transaction is not allowed. */
+	reg = cpu_to_be32((device->card->node_id << 16) |
+			  (tscm->async_handler.offset >> 32));
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	reg = cpu_to_be32(tscm->async_handler.offset);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO,
+				 &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	/* Turn on messaging. */
+	reg = cpu_to_be32(0x00000001);
+	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				  TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON,
+				  &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	/* Turn on FireWire LED. */
+	reg = cpu_to_be32(0x0001008e);
+	return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+				  TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER,
+				  &reg, sizeof(reg), 0);
+}
+
+void snd_tscm_transaction_unregister(struct snd_tscm *tscm)
+{
+	__be32 reg;
+
+	if (tscm->async_handler.callback_data == NULL)
+		return;
+
+	/* Turn off FireWire LED. */
+	reg = cpu_to_be32(0x0000008e);
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER,
+			   &reg, sizeof(reg), 0);
+
+	/* Turn off messaging. */
+	reg = cpu_to_be32(0x00000000);
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON,
+			   &reg, sizeof(reg), 0);
+
+	/* Unregister the address. */
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI,
+			   &reg, sizeof(reg), 0);
+	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO,
+			   &reg, sizeof(reg), 0);
+
+	fw_core_remove_address_handler(&tscm->async_handler);
+	tscm->async_handler.callback_data = NULL;
+}
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam.c b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam.c
new file mode 100644
index 0000000..1e61cdc
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam.c
@@ -0,0 +1,290 @@
+/*
+ * tascam.c - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "tascam.h"
+
+MODULE_DESCRIPTION("TASCAM FireWire series Driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL v2");
+
+static const struct snd_tscm_spec model_specs[] = {
+	{
+		.name = "FW-1884",
+		.has_adat = true,
+		.has_spdif = true,
+		.pcm_capture_analog_channels = 8,
+		.pcm_playback_analog_channels = 8,
+		.midi_capture_ports = 4,
+		.midi_playback_ports = 4,
+	},
+	{
+		.name = "FW-1082",
+		.has_adat = false,
+		.has_spdif = true,
+		.pcm_capture_analog_channels = 8,
+		.pcm_playback_analog_channels = 2,
+		.midi_capture_ports = 2,
+		.midi_playback_ports = 2,
+	},
+	{
+		.name = "FW-1804",
+		.has_adat = true,
+		.has_spdif = true,
+		.pcm_capture_analog_channels = 8,
+		.pcm_playback_analog_channels = 2,
+		.midi_capture_ports = 2,
+		.midi_playback_ports = 4,
+	},
+};
+
+static int identify_model(struct snd_tscm *tscm)
+{
+	struct fw_device *fw_dev = fw_parent_device(tscm->unit);
+	const u32 *config_rom = fw_dev->config_rom;
+	char model[9];
+	unsigned int i;
+	u8 c;
+
+	if (fw_dev->config_rom_length < 30) {
+		dev_err(&tscm->unit->device,
+			"Configuration ROM is too short.\n");
+		return -ENODEV;
+	}
+
+	/* Pick up model name from certain addresses. */
+	for (i = 0; i < 8; i++) {
+		c = config_rom[28 + i / 4] >> (24 - 8 * (i % 4));
+		if (c == '\0')
+			break;
+		model[i] = c;
+	}
+	model[i] = '\0';
+
+	for (i = 0; i < ARRAY_SIZE(model_specs); i++) {
+		if (strcmp(model, model_specs[i].name) == 0) {
+			tscm->spec = &model_specs[i];
+			break;
+		}
+	}
+	if (tscm->spec == NULL)
+		return -ENODEV;
+
+	strcpy(tscm->card->driver, "FW-TASCAM");
+	strcpy(tscm->card->shortname, model);
+	strcpy(tscm->card->mixername, model);
+	snprintf(tscm->card->longname, sizeof(tscm->card->longname),
+		 "TASCAM %s, GUID %08x%08x at %s, S%d", model,
+		 fw_dev->config_rom[3], fw_dev->config_rom[4],
+		 dev_name(&tscm->unit->device), 100 << fw_dev->max_speed);
+
+	return 0;
+}
+
+static void tscm_free(struct snd_tscm *tscm)
+{
+	snd_tscm_transaction_unregister(tscm);
+	snd_tscm_stream_destroy_duplex(tscm);
+
+	fw_unit_put(tscm->unit);
+
+	mutex_destroy(&tscm->mutex);
+	kfree(tscm);
+}
+
+static void tscm_card_free(struct snd_card *card)
+{
+	tscm_free(card->private_data);
+}
+
+static void do_registration(struct work_struct *work)
+{
+	struct snd_tscm *tscm = container_of(work, struct snd_tscm, dwork.work);
+	int err;
+
+	err = snd_card_new(&tscm->unit->device, -1, NULL, THIS_MODULE, 0,
+			   &tscm->card);
+	if (err < 0)
+		return;
+
+	err = identify_model(tscm);
+	if (err < 0)
+		goto error;
+
+	err = snd_tscm_transaction_register(tscm);
+	if (err < 0)
+		goto error;
+
+	err = snd_tscm_stream_init_duplex(tscm);
+	if (err < 0)
+		goto error;
+
+	snd_tscm_proc_init(tscm);
+
+	err = snd_tscm_create_pcm_devices(tscm);
+	if (err < 0)
+		goto error;
+
+	err = snd_tscm_create_midi_devices(tscm);
+	if (err < 0)
+		goto error;
+
+	err = snd_tscm_create_hwdep_device(tscm);
+	if (err < 0)
+		goto error;
+
+	err = snd_card_register(tscm->card);
+	if (err < 0)
+		goto error;
+
+	/*
+	 * After registered, tscm instance can be released corresponding to
+	 * releasing the sound card instance.
+	 */
+	tscm->card->private_free = tscm_card_free;
+	tscm->card->private_data = tscm;
+	tscm->registered = true;
+
+	return;
+error:
+	snd_tscm_transaction_unregister(tscm);
+	snd_tscm_stream_destroy_duplex(tscm);
+	snd_card_free(tscm->card);
+	dev_info(&tscm->unit->device,
+		 "Sound card registration failed: %d\n", err);
+}
+
+static int snd_tscm_probe(struct fw_unit *unit,
+			   const struct ieee1394_device_id *entry)
+{
+	struct snd_tscm *tscm;
+
+	/* Allocate this independent of sound card instance. */
+	tscm = kzalloc(sizeof(struct snd_tscm), GFP_KERNEL);
+	if (tscm == NULL)
+		return -ENOMEM;
+
+	/* initialize myself */
+	tscm->unit = fw_unit_get(unit);
+	dev_set_drvdata(&unit->device, tscm);
+
+	mutex_init(&tscm->mutex);
+	spin_lock_init(&tscm->lock);
+	init_waitqueue_head(&tscm->hwdep_wait);
+
+	/* Allocate and register this sound card later. */
+	INIT_DEFERRABLE_WORK(&tscm->dwork, do_registration);
+	snd_fw_schedule_registration(unit, &tscm->dwork);
+
+	return 0;
+}
+
+static void snd_tscm_update(struct fw_unit *unit)
+{
+	struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
+
+	/* Postpone a workqueue for deferred registration. */
+	if (!tscm->registered)
+		snd_fw_schedule_registration(unit, &tscm->dwork);
+
+	snd_tscm_transaction_reregister(tscm);
+
+	/*
+	 * After registration, userspace can start packet streaming, then this
+	 * code block works fine.
+	 */
+	if (tscm->registered) {
+		mutex_lock(&tscm->mutex);
+		snd_tscm_stream_update_duplex(tscm);
+		mutex_unlock(&tscm->mutex);
+	}
+}
+
+static void snd_tscm_remove(struct fw_unit *unit)
+{
+	struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
+
+	/*
+	 * Confirm to stop the work for registration before the sound card is
+	 * going to be released. The work is not scheduled again because bus
+	 * reset handler is not called anymore.
+	 */
+	cancel_delayed_work_sync(&tscm->dwork);
+
+	if (tscm->registered) {
+		/* No need to wait for releasing card object in this context. */
+		snd_card_free_when_closed(tscm->card);
+	} else {
+		/* Don't forget this case. */
+		tscm_free(tscm);
+	}
+}
+
+static const struct ieee1394_device_id snd_tscm_id_table[] = {
+	// Tascam, FW-1884.
+	{
+		.match_flags = IEEE1394_MATCH_VENDOR_ID |
+			       IEEE1394_MATCH_SPECIFIER_ID |
+			       IEEE1394_MATCH_VERSION,
+		.vendor_id = 0x00022e,
+		.specifier_id = 0x00022e,
+		.version = 0x800000,
+	},
+	// Tascam, FE-8 (.version = 0x800001)
+	// This kernel module doesn't support FE-8 because the most of features
+	// can be implemented in userspace without any specific support of this
+	// module.
+	//
+	// .version = 0x800002 is unknown.
+	//
+	// Tascam, FW-1082.
+	{
+		.match_flags = IEEE1394_MATCH_VENDOR_ID |
+			       IEEE1394_MATCH_SPECIFIER_ID |
+			       IEEE1394_MATCH_VERSION,
+		.vendor_id = 0x00022e,
+		.specifier_id = 0x00022e,
+		.version = 0x800003,
+	},
+	// Tascam, FW-1804.
+	{
+		.match_flags = IEEE1394_MATCH_VENDOR_ID |
+			       IEEE1394_MATCH_SPECIFIER_ID |
+			       IEEE1394_MATCH_VERSION,
+		.vendor_id = 0x00022e,
+		.specifier_id = 0x00022e,
+		.version = 0x800004,
+	},
+	/* FE-08 requires reverse-engineering because it just has faders. */
+	{}
+};
+MODULE_DEVICE_TABLE(ieee1394, snd_tscm_id_table);
+
+static struct fw_driver tscm_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "snd-firewire-tascam",
+		.bus = &fw_bus_type,
+	},
+	.probe    = snd_tscm_probe,
+	.update   = snd_tscm_update,
+	.remove   = snd_tscm_remove,
+	.id_table = snd_tscm_id_table,
+};
+
+static int __init snd_tscm_init(void)
+{
+	return driver_register(&tscm_driver.driver);
+}
+
+static void __exit snd_tscm_exit(void)
+{
+	driver_unregister(&tscm_driver.driver);
+}
+
+module_init(snd_tscm_init);
+module_exit(snd_tscm_exit);
diff --git a/src/kernel/linux/v4.14/sound/firewire/tascam/tascam.h b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam.h
new file mode 100644
index 0000000..a5bd167
--- /dev/null
+++ b/src/kernel/linux/v4.14/sound/firewire/tascam/tascam.h
@@ -0,0 +1,180 @@
+/*
+ * tascam.h - a part of driver for TASCAM FireWire series
+ *
+ * Copyright (c) 2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef SOUND_TASCAM_H_INCLUDED
+#define SOUND_TASCAM_H_INCLUDED
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+#include <linux/sched/signal.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
+#include <sound/rawmidi.h>
+
+#include "../lib.h"
+#include "../amdtp-stream.h"
+#include "../iso-resources.h"
+
+struct snd_tscm_spec {
+	const char *const name;
+	bool has_adat;
+	bool has_spdif;
+	unsigned int pcm_capture_analog_channels;
+	unsigned int pcm_playback_analog_channels;
+	unsigned int midi_capture_ports;
+	unsigned int midi_playback_ports;
+};
+
+#define TSCM_MIDI_IN_PORT_MAX	4
+#define TSCM_MIDI_OUT_PORT_MAX	4
+
+struct snd_fw_async_midi_port {
+	struct fw_device *parent;
+	struct work_struct work;
+	bool idling;
+	ktime_t next_ktime;
+	bool error;
+
+	struct fw_transaction transaction;
+
+	u8 buf[4];
+	u8 running_status;
+	bool on_sysex;
+
+	struct snd_rawmidi_substream *substream;
+	int consume_bytes;
+};
+
+struct snd_tscm {
+	struct snd_card *card;
+	struct fw_unit *unit;
+
+	struct mutex mutex;
+	spinlock_t lock;
+
+	bool registered;
+	struct delayed_work dwork;
+	const struct snd_tscm_spec *spec;
+
+	struct fw_iso_resources tx_resources;
+	struct fw_iso_resources rx_resources;
+	struct amdtp_stream tx_stream;
+	struct amdtp_stream rx_stream;
+	unsigned int substreams_counter;
+
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
+
+	/* For MIDI message incoming transactions. */
+	struct fw_address_handler async_handler;
+	struct snd_rawmidi_substream *tx_midi_substreams[TSCM_MIDI_IN_PORT_MAX];
+
+	/* For MIDI message outgoing transactions. */
+	struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX];
+};
+
+#define TSCM_ADDR_BASE			0xffff00000000ull
+
+#define TSCM_OFFSET_FIRMWARE_REGISTER	0x0000
+#define TSCM_OFFSET_FIRMWARE_FPGA	0x0004
+#define TSCM_OFFSET_FIRMWARE_ARM	0x0008
+#define TSCM_OFFSET_FIRMWARE_HW		0x000c
+
+#define TSCM_OFFSET_ISOC_TX_CH		0x0200
+#define TSCM_OFFSET_UNKNOWN		0x0204
+#define TSCM_OFFSET_START_STREAMING	0x0208
+#define TSCM_OFFSET_ISOC_RX_CH		0x020c
+#define TSCM_OFFSET_ISOC_RX_ON		0x0210	/* Little conviction. */
+#define TSCM_OFFSET_TX_PCM_CHANNELS	0x0214
+#define TSCM_OFFSET_RX_PCM_CHANNELS	0x0218
+#define TSCM_OFFSET_MULTIPLEX_MODE	0x021c
+#define TSCM_OFFSET_ISOC_TX_ON		0x0220
+/* Unknown				0x0224 */
+#define TSCM_OFFSET_CLOCK_STATUS	0x0228
+#define TSCM_OFFSET_SET_OPTION		0x022c
+
+#define TSCM_OFFSET_MIDI_TX_ON		0x0300
+#define TSCM_OFFSET_MIDI_TX_ADDR_HI	0x0304
+#define TSCM_OFFSET_MIDI_TX_ADDR_LO	0x0308
+
+#define TSCM_OFFSET_LED_POWER		0x0404
+
+#define TSCM_OFFSET_MIDI_RX_QUAD	0x4000
+
+enum snd_tscm_clock {
+	SND_TSCM_CLOCK_INTERNAL = 0,
+	SND_TSCM_CLOCK_WORD	= 1,
+	SND_TSCM_CLOCK_SPDIF	= 2,
+	SND_TSCM_CLOCK_ADAT	= 3,
+};
+
+int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit,
+		  enum amdtp_stream_direction dir, unsigned int pcm_channels);
+int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate);
+int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s,
+				      struct snd_pcm_runtime *runtime);
+
+int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate);
+int snd_tscm_stream_get_clock(struct snd_tscm *tscm,
+			      enum snd_tscm_clock *clock);
+int snd_tscm_stream_init_duplex(struct snd_tscm *tscm);
+void snd_tscm_stream_update_duplex(struct snd_tscm *tscm);
+void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm);
+int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate);
+void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm);
+
+void snd_tscm_stream_lock_changed(struct snd_tscm *tscm);
+int snd_tscm_stream_lock_try(struct snd_tscm *tscm);
+void snd_tscm_stream_lock_release(struct snd_tscm *tscm);
+
+void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port);
+
+static inline void
+snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port,
+			   struct snd_rawmidi_substream *substream)
+{
+	if (!port->error) {
+		port->substream = substream;
+		schedule_work(&port->work);
+	}
+}
+
+static inline void
+snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port)
+{
+	port->substream = NULL;
+	cancel_work_sync(&port->work);
+	port->error = false;
+}
+
+int snd_tscm_transaction_register(struct snd_tscm *tscm);
+int snd_tscm_transaction_reregister(struct snd_tscm *tscm);
+void snd_tscm_transaction_unregister(struct snd_tscm *tscm);
+
+void snd_tscm_proc_init(struct snd_tscm *tscm);
+
+int snd_tscm_create_pcm_devices(struct snd_tscm *tscm);
+
+int snd_tscm_create_midi_devices(struct snd_tscm *tscm);
+
+int snd_tscm_create_hwdep_device(struct snd_tscm *tscm);
+
+#endif