summaryrefslogtreecommitdiffstats
path: root/templates/base/local.conf.sample
blob: 72c16b25de29bb2bf5bafe7b72293d0d8338960e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#
# This file is your local configuration file and is where all local user settings
# are placed. The comments in this file give some guide to the options a new user
# to the system might want to change but pretty much any configuration option can
# be set in this file. More adventurous users can look at local.conf.extended 
# which contains other examples of configuration which can be placed in this file
# but new users likely won't need any of them initially.
#
# Lines starting with the '#' character are commented out and in some cases the 
# default values are provided as comments to show people example syntax. Enabling
# the option is a question of removing the # character and making any change to the
# variable as required.

#
# Machine Selection
#
# You need to select a specific machine to target the build with. There are a selection
# of emulated machines available which can boot and run in the QEMU emulator:
#
#MACHINE ?= "qemuarm"
#MACHINE ?= "qemuarm64"
#MACHINE ?= "qemumips"
#MACHINE ?= "qemuppc"
#MACHINE ?= "qemux86"
#MACHINE ?= "qemux86-64"
#
# There are also the following hardware board target machines included for 
# demonstration purposes:
#
#MACHINE ?= "beaglebone"
#MACHINE ?= "genericx86"
#MACHINE ?= "genericx86-64"
#MACHINE ?= "mpc8315e-rdb"
#MACHINE ?= "edgerouter"
#
# This sets the default machine to be qemux86 if no other machine is selected:
MACHINE ??= "qemux86"

#
# Where to place downloads
#
# During a first build the system will download many different source code tarballs
# from various upstream projects. This can take a while, particularly if your network
# connection is slow. These are all stored in DL_DIR. When wiping and rebuilding you
# can preserve this directory to speed up this part of subsequent builds. This directory
# is safe to share between multiple builds on the same machine too.
#
# The default is a downloads directory under TOPDIR which is the build directory.
#
#DL_DIR ?= "${TOPDIR}/downloads"

#
# Where to place shared-state files
#
# BitBake has the capability to accelerate builds based on previously built output.
# This is done using "shared state" files which can be thought of as cache objects
# and this option determines where those files are placed.
#
# You can wipe out TMPDIR leaving this directory intact and the build would regenerate
# from these files if no changes were made to the configuration. If changes were made
# to the configuration, only shared state files where the state was still valid would
# be used (done using checksums).
#
# The default is a sstate-cache directory under TOPDIR.
#
#SSTATE_DIR ?= "${TOPDIR}/sstate-cache"

#
# Where to place the build output
#
# This option specifies where the bulk of the building work should be done and
# where BitBake should place its temporary files and output. Keep in mind that
# this includes the extraction and compilation of many applications and the toolchain
# which can use Gigabytes of hard disk space.
#
# The default is a tmp directory under TOPDIR.
#
#TMPDIR = "${TOPDIR}/tmp"

#
# Default policy config
#
# The distribution setting controls which policy settings are used as defaults.
# The default value is fine for general Yocto project use, at least initially.
# Ultimately when creating custom policy, people will likely end up subclassing 
# these defaults.
#
DISTRO ?= "poky"
# As an example of a subclass there is a "bleeding" edge policy configuration
# where many versions are set to the absolute latest code from the upstream 
# source control systems. This is just mentioned here as an example, its not
# useful to most new users.
# DISTRO ?= "poky-bleeding"

#
# Package Management configuration
#
# This variable lists which packaging formats to enable. Multiple package backends 
# can be enabled at once and the first item listed in the variable will be used 
# to generate the root filesystems.
# Options are:
#  - 'package_deb' for debian style deb files
#  - 'package_ipk' for ipk files are used by opkg (a debian style embedded package manager)
#  - 'package_rpm' for rpm style packages
# E.g.: PACKAGE_CLASSES ?= "package_rpm package_deb package_ipk"
# We default to rpm:
PACKAGE_CLASSES ?= "package_rpm"

#
# SDK/ADT target architecture
#
# This variable specifies the architecture to build SDK/ADT items for and means
# you can build the SDK packages for architectures other than the machine you are 
# running the build on (i.e. building i686 packages on an x86_64 host).
# Supported values are i686 and x86_64
#SDKMACHINE ?= "i686"

#
# Extra image configuration defaults
#
# The EXTRA_IMAGE_FEATURES variable allows extra packages to be added to the generated 
# images. Some of these options are added to certain image types automatically. The
# variable can contain the following options:
#  "dbg-pkgs"       - add -dbg packages for all installed packages
#                     (adds symbol information for debugging/profiling)
#  "dev-pkgs"       - add -dev packages for all installed packages
#                     (useful if you want to develop against libs in the image)
#  "ptest-pkgs"     - add -ptest packages for all ptest-enabled packages
#                     (useful if you want to run the package test suites)
#  "tools-sdk"      - add development tools (gcc, make, pkgconfig etc.)
#  "tools-debug"    - add debugging tools (gdb, strace)
#  "eclipse-debug"  - add Eclipse remote debugging support
#  "tools-profile"  - add profiling tools (oprofile, exmap, lttng, valgrind)
#  "tools-testapps" - add useful testing tools (ts_print, aplay, arecord etc.)
#  "debug-tweaks"   - make an image suitable for development
#                     e.g. ssh root access has a blank password
# There are other application targets that can be used here too, see
# meta/classes/image.bbclass and meta/classes/core-image.bbclass for more details.
# We default to enabling the debugging tweaks.
EXTRA_IMAGE_FEATURES = "debug-tweaks"

#
# Additional image features
#
# The following is a list of additional classes to use when building images which
# enable extra features. Some available options which can be included in this variable 
# are:
#   - 'buildstats' collect build statistics
#   - 'image-mklibs' to reduce shared library files size for an image
#   - 'image-prelink' in order to prelink the filesystem image
#   - 'image-swab' to perform host system intrusion detection
# NOTE: if listing mklibs & prelink both, then make sure mklibs is before prelink
# NOTE: mklibs also needs to be explicitly enabled for a given image, see local.conf.extended
# NOTE: image-prelink is currently broken due to problems with the prelinker.  It is advised
# that you do NOT run the prelinker at this time. 
USER_CLASSES ?= "buildstats image-mklibs"

#
# Runtime testing of images
#
# The build system can test booting virtual machine images under qemu (an emulator)
# after any root filesystems are created and run tests against those images. To
# enable this uncomment this line. See classes/testimage(-auto).bbclass for
# further details.
#TEST_IMAGE = "1"
#
# Interactive shell configuration
#
# Under certain circumstances the system may need input from you and to do this it 
# can launch an interactive shell. It needs to do this since the build is 
# multithreaded and needs to be able to handle the case where more than one parallel
# process may require the user's attention. The default is iterate over the available
# terminal types to find one that works.
#
# Examples of the occasions this may happen are when resolving patches which cannot
# be applied, to use the devshell or the kernel menuconfig
#
# Supported values are auto, gnome, xfce, rxvt, screen, konsole (KDE 3.x only), none
# Note: currently, Konsole support only works for KDE 3.x due to the way
# newer Konsole versions behave
#OE_TERMINAL = "auto"
# By default disable interactive patch resolution (tasks will just fail instead):
PATCHRESOLVE = "noop"

#
# Disk Space Monitoring during the build
#
# Monitor the disk space during the build. If there is less that 1GB of space or less
# than 100K inodes in any key build location (TMPDIR, DL_DIR, SSTATE_DIR), gracefully
# shutdown the build. If there is less that 100MB or 1K inodes, perform a hard abort
# of the build. The reason for this is that running completely out of space can corrupt
# files and damages the build in ways which may not be easily recoverable.
# It's necesary to monitor /tmp, if there is no space left the build will fail
# with very exotic errors.
BB_DISKMON_DIRS = "\
    STOPTASKS,${TMPDIR},1G,100K \
    STOPTASKS,${DL_DIR},1G,100K \
    STOPTASKS,${SSTATE_DIR},1G,100K \
    STOPTASKS,/tmp,100M,100K \
    ABORT,${TMPDIR},100M,1K \
    ABORT,${DL_DIR},100M,1K \
    ABORT,${SSTATE_DIR},100M,1K \
    ABORT,/tmp,10M,1K"

#
# Shared-state files from other locations
#
# As mentioned above, shared state files are prebuilt cache data objects which can 
# used to accelerate build time. This variable can be used to configure the system
# to search other mirror locations for these objects before it builds the data itself.
#
# This can be a filesystem directory, or a remote url such as http or ftp. These
# would contain the sstate-cache results from previous builds (possibly from other 
# machines). This variable works like fetcher MIRRORS/PREMIRRORS and points to the 
# cache locations to check for the shared objects.
# NOTE: if the mirror uses the same structure as SSTATE_DIR, you need to add PATH
# at the end as shown in the examples below. This will be substituted with the
# correct path within the directory structure.
#SSTATE_MIRRORS ?= "\
#file://.* http://someserver.tld/share/sstate/PATH;downloadfilename=PATH \n \
#file://.* file:///some/local/dir/sstate/PATH"


#
# Qemu configuration
#
# By default qemu will build with a builtin VNC server where graphical output can be
# seen. The two lines below enable the SDL backend too. By default libsdl-native will
# be built, if you want to use your host's libSDL instead of the minimal libsdl built
# by libsdl-native then uncomment the ASSUME_PROVIDED line below.
PACKAGECONFIG_append_pn-qemu-native = " sdl"
PACKAGECONFIG_append_pn-nativesdk-qemu = " sdl"
#ASSUME_PROVIDED += "libsdl-native"


# CONF_VERSION is increased each time build/conf/ changes incompatibly and is used to
# track the version of this file when it was generated. This can safely be ignored if
# this doesn't mean anything to you.
CONF_VERSION = "1"
ct */ .highlight .sa { color: #e6db74 } /* Literal.String.Affix */ .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ .highlight .sc { color: #e6db74 } /* Literal.String.Char */ .highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ .highlight .se { color: #ae81ff } /* Literal.String.Escape */ .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ .highlight .sx { color: #e6db74 } /* Literal.String.Other */ .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #a6e22e } /* Name.Function.Magic */ .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } @media (prefers-color-scheme: light) { .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
/*
 * sound.c - Audio Application Interface Module for Mostcore
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * This file is licensed under GPLv2.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <mostcore.h>

#define DRIVER_NAME "sound"

static struct list_head dev_list;
static struct most_aim audio_aim;

/**
 * struct channel - private structure to keep channel specific data
 * @substream: stores the substream structure
 * @iface: interface for which the channel belongs to
 * @cfg: channel configuration
 * @card: registered sound card
 * @list: list for private use
 * @id: channel index
 * @period_pos: current period position (ring buffer)
 * @buffer_pos: current buffer position (ring buffer)
 * @is_stream_running: identifies whether a stream is running or not
 * @opened: set when the stream is opened
 * @playback_task: playback thread
 * @playback_waitq: waitq used by playback thread
 */
struct channel {
	struct snd_pcm_substream *substream;
	struct snd_pcm_hardware pcm_hardware;
	struct most_interface *iface;
	struct most_channel_config *cfg;
	struct snd_card *card;
	struct list_head list;
	int id;
	unsigned int period_pos;
	unsigned int buffer_pos;
	bool is_stream_running;

	struct task_struct *playback_task;
	wait_queue_head_t playback_waitq;

	void (*copy_fn)(void *alsa, void *most, unsigned int bytes);
};

#define MOST_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
		       SNDRV_PCM_INFO_MMAP_VALID | \
		       SNDRV_PCM_INFO_BATCH | \
		       SNDRV_PCM_INFO_INTERLEAVED | \
		       SNDRV_PCM_INFO_BLOCK_TRANSFER)

#define swap16(val) ( \
	(((u16)(val) << 8) & (u16)0xFF00) | \
	(((u16)(val) >> 8) & (u16)0x00FF))

#define swap32(val) ( \
	(((u32)(val) << 24) & (u32)0xFF000000) | \
	(((u32)(val) <<  8) & (u32)0x00FF0000) | \
	(((u32)(val) >>  8) & (u32)0x0000FF00) | \
	(((u32)(val) >> 24) & (u32)0x000000FF))

static void swap_copy16(u16 *dest, const u16 *source, unsigned int bytes)
{
	unsigned int i = 0;

	while (i < (bytes / 2)) {
		dest[i] = swap16(source[i]);
		i++;
	}
}

static void swap_copy24(u8 *dest, const u8 *source, unsigned int bytes)
{
	unsigned int i = 0;

	while (i < bytes - 2) {
		dest[i] = source[i + 2];
		dest[i + 1] = source[i + 1];
		dest[i + 2] = source[i];
		i += 3;
	}
}

static void swap_copy32(u32 *dest, const u32 *source, unsigned int bytes)
{
	unsigned int i = 0;

	while (i < bytes / 4) {
		dest[i] = swap32(source[i]);
		i++;
	}
}

static void alsa_to_most_memcpy(void *alsa, void *most, unsigned int bytes)
{
	memcpy(most, alsa, bytes);
}

static void alsa_to_most_copy16(void *alsa, void *most, unsigned int bytes)
{
	swap_copy16(most, alsa, bytes);
}

static void alsa_to_most_copy24(void *alsa, void *most, unsigned int bytes)
{
	swap_copy24(most, alsa, bytes);
}

static void alsa_to_most_copy32(void *alsa, void *most, unsigned int bytes)
{
	swap_copy32(most, alsa, bytes);
}

static void most_to_alsa_memcpy(void *alsa, void *most, unsigned int bytes)
{
	memcpy(alsa, most, bytes);
}

static void most_to_alsa_copy16(void *alsa, void *most, unsigned int bytes)
{
	swap_copy16(alsa, most, bytes);
}

static void most_to_alsa_copy24(void *alsa, void *most, unsigned int bytes)
{
	swap_copy24(alsa, most, bytes);
}

static void most_to_alsa_copy32(void *alsa, void *most, unsigned int bytes)
{
	swap_copy32(alsa, most, bytes);
}

/**
 * get_channel - get pointer to channel
 * @iface: interface structure
 * @channel_id: channel ID
 *
 * This traverses the channel list and returns the channel matching the
 * ID and interface.
 *
 * Returns pointer to channel on success or NULL otherwise.
 */
static struct channel *get_channel(struct most_interface *iface,
				   int channel_id)
{
	struct channel *channel, *tmp;

	list_for_each_entry_safe(channel, tmp, &dev_list, list) {
		if ((channel->iface == iface) && (channel->id == channel_id))
			return channel;
	}

	return NULL;
}

/**
 * copy_data - implements data copying function
 * @channel: channel
 * @mbo: MBO from core
 *
 * Copy data from/to ring buffer to/from MBO and update the buffer position
 */
static bool copy_data(struct channel *channel, struct mbo *mbo)
{
	struct snd_pcm_runtime *const runtime = channel->substream->runtime;
	unsigned int const frame_bytes = channel->cfg->subbuffer_size;
	unsigned int const buffer_size = runtime->buffer_size;
	unsigned int frames;
	unsigned int fr0;

	if (channel->cfg->direction & MOST_CH_RX)
		frames = mbo->processed_length / frame_bytes;
	else
		frames = mbo->buffer_length / frame_bytes;
	fr0 = min(buffer_size - channel->buffer_pos, frames);

	channel->copy_fn(runtime->dma_area + channel->buffer_pos * frame_bytes,
			 mbo->virt_address,
			 fr0 * frame_bytes);

	if (frames > fr0) {
		/* wrap around at end of ring buffer */
		channel->copy_fn(runtime->dma_area,
				 mbo->virt_address + fr0 * frame_bytes,
				 (frames - fr0) * frame_bytes);
	}

	channel->buffer_pos += frames;
	if (channel->buffer_pos >= buffer_size)
		channel->buffer_pos -= buffer_size;
	channel->period_pos += frames;
	if (channel->period_pos >= runtime->period_size) {
		channel->period_pos -= runtime->period_size;
		return true;
	}

	return false;
}

/**
 * playback_thread - function implements the playback thread
 * @data: private data
 *
 * Thread which does the playback functionality in a loop. It waits for a free
 * MBO from mostcore for a particular channel and copy the data from ring buffer
 * to MBO. Submit the MBO back to mostcore, after copying the data.
 *
 * Returns 0 on success or error code otherwise.
 */
static int playback_thread(void *data)
{
	struct channel *const channel = data;

	while (!kthread_should_stop()) {
		struct mbo *mbo = NULL;
		bool period_elapsed = false;
		int ret;

		wait_event_interruptible(
			channel->playback_waitq,
			kthread_should_stop() ||
			(channel->is_stream_running &&
			 (mbo = most_get_mbo(channel->iface, channel->id,
					     &audio_aim))));
		if (!mbo)
			continue;

		if (channel->is_stream_running)
			period_elapsed = copy_data(channel, mbo);
		else
			memset(mbo->virt_address, 0, mbo->buffer_length);

		ret = most_submit_mbo(mbo);
		if (ret)
			channel->is_stream_running = false;

		if (period_elapsed)
			snd_pcm_period_elapsed(channel->substream);
	}

	return 0;
}

/**
 * pcm_open - implements open callback function for PCM middle layer
 * @substream: pointer to ALSA PCM substream
 *
 * This is called when a PCM substream is opened. At least, the function should
 * initialize the runtime->hw record.
 *
 * Returns 0 on success or error code otherwise.
 */
static int pcm_open(struct snd_pcm_substream *substream)
{
	struct channel *channel = substream->private_data;
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct most_channel_config *cfg = channel->cfg;

	channel->substream = substream;

	if (cfg->direction == MOST_CH_TX) {
		channel->playback_task = kthread_run(playback_thread, channel,
						     "most_audio_playback");
		if (IS_ERR(channel->playback_task)) {
			pr_err("Couldn't start thread\n");
			return PTR_ERR(channel->playback_task);
		}
	}

	if (most_start_channel(channel->iface, channel->id, &audio_aim)) {
		pr_err("most_start_channel() failed!\n");
		if (cfg->direction == MOST_CH_TX)
			kthread_stop(channel->playback_task);
		return -EBUSY;
	}

	runtime->hw = channel->pcm_hardware;
	return 0;
}

/**
 * pcm_close - implements close callback function for PCM middle layer
 * @substream: sub-stream pointer
 *
 * Obviously, this is called when a PCM substream is closed. Any private
 * instance for a PCM substream allocated in the open callback will be
 * released here.
 *
 * Returns 0 on success or error code otherwise.
 */
static int pcm_close(struct snd_pcm_substream *substream)
{
	struct channel *channel = substream->private_data;

	if (channel->cfg->direction == MOST_CH_TX)
		kthread_stop(channel->playback_task);
	most_stop_channel(channel->iface, channel->id, &audio_aim);

	return 0;
}

/**
 * pcm_hw_params - implements hw_params callback function for PCM middle layer
 * @substream: sub-stream pointer
 * @hw_params: contains the hardware parameters set by the application
 *
 * This is called when the hardware parameters is set by the application, that
 * is, once when the buffer size, the period size, the format, etc. are defined
 * for the PCM substream. Many hardware setups should be done is this callback,
 * including the allocation of buffers.
 *
 * Returns 0 on success or error code otherwise.
 */
static int pcm_hw_params(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *hw_params)
{
	struct channel *channel = substream->private_data;

	if ((params_channels(hw_params) > channel->pcm_hardware.channels_max) ||
	    (params_channels(hw_params) < channel->pcm_hardware.channels_min)) {
		pr_err("Requested number of channels not supported.\n");
		return -EINVAL;
	}
	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
						params_buffer_bytes(hw_params));
}

/**
 * pcm_hw_free - implements hw_free callback function for PCM middle layer
 * @substream: substream pointer
 *
 * This is called to release the resources allocated via hw_params.
 * This function will be always called before the close callback is called.
 *
 * Returns 0 on success or error code otherwise.
 */
static int pcm_hw_free(struct snd_pcm_substream *substream)
{
	return snd_pcm_lib_free_vmalloc_buffer(substream);
}

/**
 * pcm_prepare - implements prepare callback function for PCM middle layer
 * @substream: substream pointer
 *
 * This callback is called when the PCM is "prepared". Format rate, sample rate,
 * etc., can be set here. This callback can be called many times at each setup.
 *
 * Returns 0 on success or error code otherwise.
 */
static int pcm_prepare(struct snd_pcm_substream *substream)
{
	struct channel *channel = substream->private_data;
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct most_channel_config *cfg = channel->cfg;
	int width = snd_pcm_format_physical_width(runtime->format);

	channel->copy_fn = NULL;

	if (cfg->direction == MOST_CH_TX) {
		if (snd_pcm_format_big_endian(runtime->format) || width == 8)
			channel->copy_fn = alsa_to_most_memcpy;
		else if (width == 16)
			channel->copy_fn = alsa_to_most_copy16;
		else if (width == 24)
			channel->copy_fn = alsa_to_most_copy24;
		else if (width == 32)
			channel->copy_fn = alsa_to_most_copy32;
	} else {
		if (snd_pcm_format_big_endian(runtime->format) || width == 8)
			channel->copy_fn = most_to_alsa_memcpy;
		else if (width == 16)
			channel->copy_fn = most_to_alsa_copy16;
		else if (width == 24)
			channel->copy_fn = most_to_alsa_copy24;
		else if (width == 32)
			channel->copy_fn = most_to_alsa_copy32;
	}

	if (!channel->copy_fn) {
		pr_err("unsupported format\n");
		return -EINVAL;
	}

	channel->period_pos = 0;
	channel->buffer_pos = 0;

	return 0;
}

/**
 * pcm_trigger - implements trigger callback function for PCM middle layer
 * @substream: substream pointer
 * @cmd: action to perform
 *
 * This is called when the PCM is started, stopped or paused. The action will be
 * specified in the second argument, SNDRV_PCM_TRIGGER_XXX
 *
 * Returns 0 on success or error code otherwise.
 */
static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct channel *channel = substream->private_data;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		channel->is_stream_running = true;
		wake_up_interruptible(&channel->playback_waitq);
		return 0;

	case SNDRV_PCM_TRIGGER_STOP:
		channel->is_stream_running = false;
		return 0;

	default:
		pr_info("pcm_trigger(), invalid\n");
		return -EINVAL;
	}
	return 0;
}

/**
 * pcm_pointer - implements pointer callback function for PCM middle layer
 * @substream: substream pointer
 *
 * This callback is called when the PCM middle layer inquires the current
 * hardware position on the buffer. The position must be returned in frames,
 * ranging from 0 to buffer_size-1.
 */
static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
{
	struct channel *channel = substream->private_data;

	return channel->buffer_pos;
}

/**
 * Initialization of struct snd_pcm_ops
 */
static struct snd_pcm_ops pcm_ops = {
	.open       = pcm_open,
	.close      = pcm_close,
	.ioctl      = snd_pcm_lib_ioctl,
	.hw_params  = pcm_hw_params,
	.hw_free    = pcm_hw_free,
	.prepare    = pcm_prepare,
	.trigger    = pcm_trigger,
	.pointer    = pcm_pointer,
	.page       = snd_pcm_lib_get_vmalloc_page,
	.mmap       = snd_pcm_lib_mmap_vmalloc,
};

static int split_arg_list(char *buf, char **card_name, char **pcm_format)
{
	*card_name = strsep(&buf, ".");
	if (!*card_name)
		return -EIO;
	*pcm_format = strsep(&buf, ".\n");
	if (!*pcm_format)
		return -EIO;
	return 0;
}

static int audio_set_hw_params(struct snd_pcm_hardware *pcm_hw,
			       char *pcm_format,
			       struct most_channel_config *cfg)
{
	pcm_hw->info = MOST_PCM_INFO;
	pcm_hw->rates = SNDRV_PCM_RATE_48000;
	pcm_hw->rate_min = 48000;
	pcm_hw->rate_max = 48000;
	pcm_hw->buffer_bytes_max = cfg->num_buffers * cfg->buffer_size;
	pcm_hw->period_bytes_min = cfg->buffer_size;
	pcm_hw->period_bytes_max = cfg->buffer_size;
	pcm_hw->periods_min = 1;
	pcm_hw->periods_max = cfg->num_buffers;

	if (!strcmp(pcm_format, "1x8")) {
		if (cfg->subbuffer_size != 1)
			goto error;
		pr_info("PCM format is 8-bit mono\n");
		pcm_hw->channels_min = 1;
		pcm_hw->channels_max = 1;
		pcm_hw->formats = SNDRV_PCM_FMTBIT_S8;
	} else if (!strcmp(pcm_format, "2x16")) {
		if (cfg->subbuffer_size != 4)
			goto error;
		pr_info("PCM format is 16-bit stereo\n");
		pcm_hw->channels_min = 2;
		pcm_hw->channels_max = 2;
		pcm_hw->formats = SNDRV_PCM_FMTBIT_S16_LE |
				  SNDRV_PCM_FMTBIT_S16_BE;
	} else if (!strcmp(pcm_format, "2x24")) {
		if (cfg->subbuffer_size != 6)
			goto error;
		pr_info("PCM format is 24-bit stereo\n");
		pcm_hw->channels_min = 2;
		pcm_hw->channels_max = 2;
		pcm_hw->formats = SNDRV_PCM_FMTBIT_S24_3LE |
				  SNDRV_PCM_FMTBIT_S24_3BE;
	} else if (!strcmp(pcm_format, "2x32")) {
		if (cfg->subbuffer_size != 8)
			goto error;
		pr_info("PCM format is 32-bit stereo\n");
		pcm_hw->channels_min = 2;
		pcm_hw->channels_max = 2;
		pcm_hw->formats = SNDRV_PCM_FMTBIT_S32_LE |
				  SNDRV_PCM_FMTBIT_S32_BE;
	} else if (!strcmp(pcm_format, "6x16")) {
		if (cfg->subbuffer_size != 12)
			goto error;
		pr_info("PCM format is 16-bit 5.1 multi channel\n");
		pcm_hw->channels_min = 6;
		pcm_hw->channels_max = 6;
		pcm_hw->formats = SNDRV_PCM_FMTBIT_S16_LE |
				  SNDRV_PCM_FMTBIT_S16_BE;
	} else {
		pr_err("PCM format %s not supported\n", pcm_format);
		return -EIO;
	}
	return 0;
error:
	pr_err("Audio resolution doesn't fit subbuffer size\n");
	return -EINVAL;
}

/**
 * audio_probe_channel - probe function of the driver module
 * @iface: pointer to interface instance
 * @channel_id: channel index/ID
 * @cfg: pointer to actual channel configuration
 * @parent: pointer to kobject (needed for sysfs hook-up)
 * @arg_list: string that provides the name of the device to be created in /dev
 *	      plus the desired audio resolution
 *
 * Creates sound card, pcm device, sets pcm ops and registers sound card.
 *
 * Returns 0 on success or error code otherwise.
 */
static int audio_probe_channel(struct most_interface *iface, int channel_id,
			       struct most_channel_config *cfg,
			       struct kobject *parent, char *arg_list)
{
	struct channel *channel;
	struct snd_card *card;
	struct snd_pcm *pcm;
	int playback_count = 0;
	int capture_count = 0;
	int ret;
	int direction;
	char *card_name;
	char *pcm_format;

	if (!iface)
		return -EINVAL;

	if (cfg->data_type != MOST_CH_SYNC) {
		pr_err("Incompatible channel type\n");
		return -EINVAL;
	}

	if (get_channel(iface, channel_id)) {
		pr_err("channel (%s:%d) is already linked\n",
		       iface->description, channel_id);
		return -EINVAL;
	}

	if (cfg->direction == MOST_CH_TX) {
		playback_count = 1;
		direction = SNDRV_PCM_STREAM_PLAYBACK;
	} else {
		capture_count = 1;
		direction = SNDRV_PCM_STREAM_CAPTURE;
	}

	ret = split_arg_list(arg_list, &card_name, &pcm_format);
	if (ret < 0) {
		pr_info("PCM format missing\n");
		return ret;
	}

	ret = snd_card_create(-1, card_name, THIS_MODULE,
-			      sizeof(*channel), &card);
	if (ret < 0)
		return ret;

	channel = card->private_data;
	channel->card = card;
	channel->cfg = cfg;
	channel->iface = iface;
	channel->id = channel_id;
	init_waitqueue_head(&channel->playback_waitq);

	if (audio_set_hw_params(&channel->pcm_hardware, pcm_format, cfg))
		goto err_free_card;

	snprintf(card->driver, sizeof(card->driver), "%s", DRIVER_NAME);
	snprintf(card->shortname, sizeof(card->shortname), "Microchip MOST:%d",
		 card->number);
	snprintf(card->longname, sizeof(card->longname), "%s at %s, ch %d",
		 card->shortname, iface->description, channel_id);

	ret = snd_pcm_new(card, card_name, 0, playback_count,
			  capture_count, &pcm);
	if (ret < 0)
		goto err_free_card;

	pcm->private_data = channel;

	snd_pcm_set_ops(pcm, direction, &pcm_ops);

	ret = snd_card_register(card);
	if (ret < 0)
		goto err_free_card;

	list_add_tail(&channel->list, &dev_list);

	return 0;

err_free_card:
	snd_card_free(card);
	return ret;
}

/**
 * audio_disconnect_channel - function to disconnect a channel
 * @iface: pointer to interface instance
 * @channel_id: channel index
 *
 * This frees allocated memory and removes the sound card from ALSA
 *
 * Returns 0 on success or error code otherwise.
 */
static int audio_disconnect_channel(struct most_interface *iface,
				    int channel_id)
{
	struct channel *channel;

	channel = get_channel(iface, channel_id);
	if (!channel) {
		pr_err("sound_disconnect_channel(), invalid channel %d\n",
		       channel_id);
		return -EINVAL;
	}

	list_del(&channel->list);
	snd_card_free(channel->card);

	return 0;
}

/**
 * audio_rx_completion - completion handler for rx channels
 * @mbo: pointer to buffer object that has completed
 *
 * This searches for the channel this MBO belongs to and copy the data from MBO
 * to ring buffer
 *
 * Returns 0 on success or error code otherwise.
 */
static int audio_rx_completion(struct mbo *mbo)
{
	struct channel *channel = get_channel(mbo->ifp, mbo->hdm_channel_id);
	bool period_elapsed = false;

	if (!channel) {
		pr_err("sound_rx_completion(), invalid channel %d\n",
		       mbo->hdm_channel_id);
		return -EINVAL;
	}

	if (channel->is_stream_running)
		period_elapsed = copy_data(channel, mbo);

	most_put_mbo(mbo);

	if (period_elapsed)
		snd_pcm_period_elapsed(channel->substream);

	return 0;
}

/**
 * audio_tx_completion - completion handler for tx channels
 * @iface: pointer to interface instance
 * @channel_id: channel index/ID
 *
 * This searches the channel that belongs to this combination of interface
 * pointer and channel ID and wakes a process sitting in the wait queue of
 * this channel.
 *
 * Returns 0 on success or error code otherwise.
 */
static int audio_tx_completion(struct most_interface *iface, int channel_id)
{
	struct channel *channel = get_channel(iface, channel_id);

	if (!channel) {
		pr_err("sound_tx_completion(), invalid channel %d\n",
		       channel_id);
		return -EINVAL;
	}

	wake_up_interruptible(&channel->playback_waitq);

	return 0;
}

/**
 * Initialization of the struct most_aim
 */
static struct most_aim audio_aim = {
	.name = DRIVER_NAME,
	.probe_channel = audio_probe_channel,
	.disconnect_channel = audio_disconnect_channel,
	.rx_completion = audio_rx_completion,
	.tx_completion = audio_tx_completion,
};

static int __init audio_init(void)
{
	pr_info("init()\n");

	INIT_LIST_HEAD(&dev_list);

	return most_register_aim(&audio_aim);
}

static void __exit audio_exit(void)
{
	struct channel *channel, *tmp;

	pr_info("exit()\n");

	list_for_each_entry_safe(channel, tmp, &dev_list, list) {
		list_del(&channel->list);
		snd_card_free(channel->card);
	}

	most_deregister_aim(&audio_aim);
}

module_init(audio_init);
module_exit(audio_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>");
MODULE_DESCRIPTION("Audio Application Interface Module for MostCore");