113b41b57SChristian Gromm // SPDX-License-Identifier: GPL-2.0
213b41b57SChristian Gromm /*
313b41b57SChristian Gromm * sound.c - Sound component for Mostcore
413b41b57SChristian Gromm *
513b41b57SChristian Gromm * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
613b41b57SChristian Gromm */
713b41b57SChristian Gromm
813b41b57SChristian Gromm #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
913b41b57SChristian Gromm
1013b41b57SChristian Gromm #include <linux/module.h>
1113b41b57SChristian Gromm #include <linux/printk.h>
1213b41b57SChristian Gromm #include <linux/kernel.h>
1313b41b57SChristian Gromm #include <linux/slab.h>
1413b41b57SChristian Gromm #include <linux/init.h>
1513b41b57SChristian Gromm #include <sound/core.h>
1613b41b57SChristian Gromm #include <sound/pcm.h>
1713b41b57SChristian Gromm #include <sound/pcm_params.h>
1813b41b57SChristian Gromm #include <linux/sched.h>
1913b41b57SChristian Gromm #include <linux/kthread.h>
2013b41b57SChristian Gromm #include <linux/most.h>
2113b41b57SChristian Gromm
2213b41b57SChristian Gromm #define DRIVER_NAME "sound"
2313b41b57SChristian Gromm #define STRING_SIZE 80
2413b41b57SChristian Gromm
2513b41b57SChristian Gromm static struct most_component comp;
2613b41b57SChristian Gromm
2713b41b57SChristian Gromm /**
2813b41b57SChristian Gromm * struct channel - private structure to keep channel specific data
2913b41b57SChristian Gromm * @substream: stores the substream structure
30*fba3993eSRandy Dunlap * @pcm_hardware: low-level hardware description
3113b41b57SChristian Gromm * @iface: interface for which the channel belongs to
3213b41b57SChristian Gromm * @cfg: channel configuration
3313b41b57SChristian Gromm * @card: registered sound card
3413b41b57SChristian Gromm * @list: list for private use
3513b41b57SChristian Gromm * @id: channel index
3613b41b57SChristian Gromm * @period_pos: current period position (ring buffer)
3713b41b57SChristian Gromm * @buffer_pos: current buffer position (ring buffer)
3813b41b57SChristian Gromm * @is_stream_running: identifies whether a stream is running or not
3913b41b57SChristian Gromm * @opened: set when the stream is opened
4013b41b57SChristian Gromm * @playback_task: playback thread
4113b41b57SChristian Gromm * @playback_waitq: waitq used by playback thread
42*fba3993eSRandy Dunlap * @copy_fn: copy function for PCM-specific format and width
4313b41b57SChristian Gromm */
4413b41b57SChristian Gromm struct channel {
4513b41b57SChristian Gromm struct snd_pcm_substream *substream;
4613b41b57SChristian Gromm struct snd_pcm_hardware pcm_hardware;
4713b41b57SChristian Gromm struct most_interface *iface;
4813b41b57SChristian Gromm struct most_channel_config *cfg;
4913b41b57SChristian Gromm struct snd_card *card;
5013b41b57SChristian Gromm struct list_head list;
5113b41b57SChristian Gromm int id;
5213b41b57SChristian Gromm unsigned int period_pos;
5313b41b57SChristian Gromm unsigned int buffer_pos;
5413b41b57SChristian Gromm bool is_stream_running;
5513b41b57SChristian Gromm struct task_struct *playback_task;
5613b41b57SChristian Gromm wait_queue_head_t playback_waitq;
5713b41b57SChristian Gromm void (*copy_fn)(void *alsa, void *most, unsigned int bytes);
5813b41b57SChristian Gromm };
5913b41b57SChristian Gromm
6013b41b57SChristian Gromm struct sound_adapter {
6113b41b57SChristian Gromm struct list_head dev_list;
6213b41b57SChristian Gromm struct most_interface *iface;
6313b41b57SChristian Gromm struct snd_card *card;
6413b41b57SChristian Gromm struct list_head list;
6513b41b57SChristian Gromm bool registered;
6613b41b57SChristian Gromm int pcm_dev_idx;
6713b41b57SChristian Gromm };
6813b41b57SChristian Gromm
6913b41b57SChristian Gromm static struct list_head adpt_list;
7013b41b57SChristian Gromm
7113b41b57SChristian Gromm #define MOST_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
7213b41b57SChristian Gromm SNDRV_PCM_INFO_MMAP_VALID | \
7313b41b57SChristian Gromm SNDRV_PCM_INFO_BATCH | \
7413b41b57SChristian Gromm SNDRV_PCM_INFO_INTERLEAVED | \
7513b41b57SChristian Gromm SNDRV_PCM_INFO_BLOCK_TRANSFER)
7613b41b57SChristian Gromm
swap_copy16(u16 * dest,const u16 * source,unsigned int bytes)7713b41b57SChristian Gromm static void swap_copy16(u16 *dest, const u16 *source, unsigned int bytes)
7813b41b57SChristian Gromm {
7913b41b57SChristian Gromm unsigned int i = 0;
8013b41b57SChristian Gromm
8113b41b57SChristian Gromm while (i < (bytes / 2)) {
8213b41b57SChristian Gromm dest[i] = swab16(source[i]);
8313b41b57SChristian Gromm i++;
8413b41b57SChristian Gromm }
8513b41b57SChristian Gromm }
8613b41b57SChristian Gromm
swap_copy24(u8 * dest,const u8 * source,unsigned int bytes)8713b41b57SChristian Gromm static void swap_copy24(u8 *dest, const u8 *source, unsigned int bytes)
8813b41b57SChristian Gromm {
8913b41b57SChristian Gromm unsigned int i = 0;
9013b41b57SChristian Gromm
9113b41b57SChristian Gromm if (bytes < 2)
9213b41b57SChristian Gromm return;
9313b41b57SChristian Gromm while (i < bytes - 2) {
9413b41b57SChristian Gromm dest[i] = source[i + 2];
9513b41b57SChristian Gromm dest[i + 1] = source[i + 1];
9613b41b57SChristian Gromm dest[i + 2] = source[i];
9713b41b57SChristian Gromm i += 3;
9813b41b57SChristian Gromm }
9913b41b57SChristian Gromm }
10013b41b57SChristian Gromm
swap_copy32(u32 * dest,const u32 * source,unsigned int bytes)10113b41b57SChristian Gromm static void swap_copy32(u32 *dest, const u32 *source, unsigned int bytes)
10213b41b57SChristian Gromm {
10313b41b57SChristian Gromm unsigned int i = 0;
10413b41b57SChristian Gromm
10513b41b57SChristian Gromm while (i < bytes / 4) {
10613b41b57SChristian Gromm dest[i] = swab32(source[i]);
10713b41b57SChristian Gromm i++;
10813b41b57SChristian Gromm }
10913b41b57SChristian Gromm }
11013b41b57SChristian Gromm
alsa_to_most_memcpy(void * alsa,void * most,unsigned int bytes)11113b41b57SChristian Gromm static void alsa_to_most_memcpy(void *alsa, void *most, unsigned int bytes)
11213b41b57SChristian Gromm {
11313b41b57SChristian Gromm memcpy(most, alsa, bytes);
11413b41b57SChristian Gromm }
11513b41b57SChristian Gromm
alsa_to_most_copy16(void * alsa,void * most,unsigned int bytes)11613b41b57SChristian Gromm static void alsa_to_most_copy16(void *alsa, void *most, unsigned int bytes)
11713b41b57SChristian Gromm {
11813b41b57SChristian Gromm swap_copy16(most, alsa, bytes);
11913b41b57SChristian Gromm }
12013b41b57SChristian Gromm
alsa_to_most_copy24(void * alsa,void * most,unsigned int bytes)12113b41b57SChristian Gromm static void alsa_to_most_copy24(void *alsa, void *most, unsigned int bytes)
12213b41b57SChristian Gromm {
12313b41b57SChristian Gromm swap_copy24(most, alsa, bytes);
12413b41b57SChristian Gromm }
12513b41b57SChristian Gromm
alsa_to_most_copy32(void * alsa,void * most,unsigned int bytes)12613b41b57SChristian Gromm static void alsa_to_most_copy32(void *alsa, void *most, unsigned int bytes)
12713b41b57SChristian Gromm {
12813b41b57SChristian Gromm swap_copy32(most, alsa, bytes);
12913b41b57SChristian Gromm }
13013b41b57SChristian Gromm
most_to_alsa_memcpy(void * alsa,void * most,unsigned int bytes)13113b41b57SChristian Gromm static void most_to_alsa_memcpy(void *alsa, void *most, unsigned int bytes)
13213b41b57SChristian Gromm {
13313b41b57SChristian Gromm memcpy(alsa, most, bytes);
13413b41b57SChristian Gromm }
13513b41b57SChristian Gromm
most_to_alsa_copy16(void * alsa,void * most,unsigned int bytes)13613b41b57SChristian Gromm static void most_to_alsa_copy16(void *alsa, void *most, unsigned int bytes)
13713b41b57SChristian Gromm {
13813b41b57SChristian Gromm swap_copy16(alsa, most, bytes);
13913b41b57SChristian Gromm }
14013b41b57SChristian Gromm
most_to_alsa_copy24(void * alsa,void * most,unsigned int bytes)14113b41b57SChristian Gromm static void most_to_alsa_copy24(void *alsa, void *most, unsigned int bytes)
14213b41b57SChristian Gromm {
14313b41b57SChristian Gromm swap_copy24(alsa, most, bytes);
14413b41b57SChristian Gromm }
14513b41b57SChristian Gromm
most_to_alsa_copy32(void * alsa,void * most,unsigned int bytes)14613b41b57SChristian Gromm static void most_to_alsa_copy32(void *alsa, void *most, unsigned int bytes)
14713b41b57SChristian Gromm {
14813b41b57SChristian Gromm swap_copy32(alsa, most, bytes);
14913b41b57SChristian Gromm }
15013b41b57SChristian Gromm
15113b41b57SChristian Gromm /**
15213b41b57SChristian Gromm * get_channel - get pointer to channel
15313b41b57SChristian Gromm * @iface: interface structure
15413b41b57SChristian Gromm * @channel_id: channel ID
15513b41b57SChristian Gromm *
15613b41b57SChristian Gromm * This traverses the channel list and returns the channel matching the
15713b41b57SChristian Gromm * ID and interface.
15813b41b57SChristian Gromm *
15913b41b57SChristian Gromm * Returns pointer to channel on success or NULL otherwise.
16013b41b57SChristian Gromm */
get_channel(struct most_interface * iface,int channel_id)16113b41b57SChristian Gromm static struct channel *get_channel(struct most_interface *iface,
16213b41b57SChristian Gromm int channel_id)
16313b41b57SChristian Gromm {
16413b41b57SChristian Gromm struct sound_adapter *adpt = iface->priv;
16513b41b57SChristian Gromm struct channel *channel;
16613b41b57SChristian Gromm
16713b41b57SChristian Gromm list_for_each_entry(channel, &adpt->dev_list, list) {
16813b41b57SChristian Gromm if ((channel->iface == iface) && (channel->id == channel_id))
16913b41b57SChristian Gromm return channel;
17013b41b57SChristian Gromm }
17113b41b57SChristian Gromm return NULL;
17213b41b57SChristian Gromm }
17313b41b57SChristian Gromm
17413b41b57SChristian Gromm /**
17513b41b57SChristian Gromm * copy_data - implements data copying function
17613b41b57SChristian Gromm * @channel: channel
17713b41b57SChristian Gromm * @mbo: MBO from core
17813b41b57SChristian Gromm *
17913b41b57SChristian Gromm * Copy data from/to ring buffer to/from MBO and update the buffer position
18013b41b57SChristian Gromm */
copy_data(struct channel * channel,struct mbo * mbo)18113b41b57SChristian Gromm static bool copy_data(struct channel *channel, struct mbo *mbo)
18213b41b57SChristian Gromm {
18313b41b57SChristian Gromm struct snd_pcm_runtime *const runtime = channel->substream->runtime;
18413b41b57SChristian Gromm unsigned int const frame_bytes = channel->cfg->subbuffer_size;
18513b41b57SChristian Gromm unsigned int const buffer_size = runtime->buffer_size;
18613b41b57SChristian Gromm unsigned int frames;
18713b41b57SChristian Gromm unsigned int fr0;
18813b41b57SChristian Gromm
18913b41b57SChristian Gromm if (channel->cfg->direction & MOST_CH_RX)
19013b41b57SChristian Gromm frames = mbo->processed_length / frame_bytes;
19113b41b57SChristian Gromm else
19213b41b57SChristian Gromm frames = mbo->buffer_length / frame_bytes;
19313b41b57SChristian Gromm fr0 = min(buffer_size - channel->buffer_pos, frames);
19413b41b57SChristian Gromm
19513b41b57SChristian Gromm channel->copy_fn(runtime->dma_area + channel->buffer_pos * frame_bytes,
19613b41b57SChristian Gromm mbo->virt_address,
19713b41b57SChristian Gromm fr0 * frame_bytes);
19813b41b57SChristian Gromm
19913b41b57SChristian Gromm if (frames > fr0) {
20013b41b57SChristian Gromm /* wrap around at end of ring buffer */
20113b41b57SChristian Gromm channel->copy_fn(runtime->dma_area,
20213b41b57SChristian Gromm mbo->virt_address + fr0 * frame_bytes,
20313b41b57SChristian Gromm (frames - fr0) * frame_bytes);
20413b41b57SChristian Gromm }
20513b41b57SChristian Gromm
20613b41b57SChristian Gromm channel->buffer_pos += frames;
20713b41b57SChristian Gromm if (channel->buffer_pos >= buffer_size)
20813b41b57SChristian Gromm channel->buffer_pos -= buffer_size;
20913b41b57SChristian Gromm channel->period_pos += frames;
21013b41b57SChristian Gromm if (channel->period_pos >= runtime->period_size) {
21113b41b57SChristian Gromm channel->period_pos -= runtime->period_size;
21213b41b57SChristian Gromm return true;
21313b41b57SChristian Gromm }
21413b41b57SChristian Gromm return false;
21513b41b57SChristian Gromm }
21613b41b57SChristian Gromm
21713b41b57SChristian Gromm /**
21813b41b57SChristian Gromm * playback_thread - function implements the playback thread
21913b41b57SChristian Gromm * @data: private data
22013b41b57SChristian Gromm *
22113b41b57SChristian Gromm * Thread which does the playback functionality in a loop. It waits for a free
22213b41b57SChristian Gromm * MBO from mostcore for a particular channel and copy the data from ring buffer
22313b41b57SChristian Gromm * to MBO. Submit the MBO back to mostcore, after copying the data.
22413b41b57SChristian Gromm *
22513b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
22613b41b57SChristian Gromm */
playback_thread(void * data)22713b41b57SChristian Gromm static int playback_thread(void *data)
22813b41b57SChristian Gromm {
22913b41b57SChristian Gromm struct channel *const channel = data;
23013b41b57SChristian Gromm
23113b41b57SChristian Gromm while (!kthread_should_stop()) {
23213b41b57SChristian Gromm struct mbo *mbo = NULL;
23313b41b57SChristian Gromm bool period_elapsed = false;
23413b41b57SChristian Gromm
23513b41b57SChristian Gromm wait_event_interruptible(
23613b41b57SChristian Gromm channel->playback_waitq,
23713b41b57SChristian Gromm kthread_should_stop() ||
23813b41b57SChristian Gromm (channel->is_stream_running &&
23913b41b57SChristian Gromm (mbo = most_get_mbo(channel->iface, channel->id,
24013b41b57SChristian Gromm &comp))));
24113b41b57SChristian Gromm if (!mbo)
24213b41b57SChristian Gromm continue;
24313b41b57SChristian Gromm
24413b41b57SChristian Gromm if (channel->is_stream_running)
24513b41b57SChristian Gromm period_elapsed = copy_data(channel, mbo);
24613b41b57SChristian Gromm else
24713b41b57SChristian Gromm memset(mbo->virt_address, 0, mbo->buffer_length);
24813b41b57SChristian Gromm
24913b41b57SChristian Gromm most_submit_mbo(mbo);
25013b41b57SChristian Gromm if (period_elapsed)
25113b41b57SChristian Gromm snd_pcm_period_elapsed(channel->substream);
25213b41b57SChristian Gromm }
25313b41b57SChristian Gromm return 0;
25413b41b57SChristian Gromm }
25513b41b57SChristian Gromm
25613b41b57SChristian Gromm /**
25713b41b57SChristian Gromm * pcm_open - implements open callback function for PCM middle layer
25813b41b57SChristian Gromm * @substream: pointer to ALSA PCM substream
25913b41b57SChristian Gromm *
26013b41b57SChristian Gromm * This is called when a PCM substream is opened. At least, the function should
26113b41b57SChristian Gromm * initialize the runtime->hw record.
26213b41b57SChristian Gromm *
26313b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
26413b41b57SChristian Gromm */
pcm_open(struct snd_pcm_substream * substream)26513b41b57SChristian Gromm static int pcm_open(struct snd_pcm_substream *substream)
26613b41b57SChristian Gromm {
26713b41b57SChristian Gromm struct channel *channel = substream->private_data;
26813b41b57SChristian Gromm struct snd_pcm_runtime *runtime = substream->runtime;
26913b41b57SChristian Gromm struct most_channel_config *cfg = channel->cfg;
27013b41b57SChristian Gromm int ret;
27113b41b57SChristian Gromm
27213b41b57SChristian Gromm channel->substream = substream;
27313b41b57SChristian Gromm
27413b41b57SChristian Gromm if (cfg->direction == MOST_CH_TX) {
27513b41b57SChristian Gromm channel->playback_task = kthread_run(playback_thread, channel,
27613b41b57SChristian Gromm "most_audio_playback");
27713b41b57SChristian Gromm if (IS_ERR(channel->playback_task)) {
27813b41b57SChristian Gromm pr_err("Couldn't start thread\n");
27913b41b57SChristian Gromm return PTR_ERR(channel->playback_task);
28013b41b57SChristian Gromm }
28113b41b57SChristian Gromm }
28213b41b57SChristian Gromm
28313b41b57SChristian Gromm ret = most_start_channel(channel->iface, channel->id, &comp);
28413b41b57SChristian Gromm if (ret) {
28513b41b57SChristian Gromm pr_err("most_start_channel() failed!\n");
28613b41b57SChristian Gromm if (cfg->direction == MOST_CH_TX)
28713b41b57SChristian Gromm kthread_stop(channel->playback_task);
28813b41b57SChristian Gromm return ret;
28913b41b57SChristian Gromm }
29013b41b57SChristian Gromm
29113b41b57SChristian Gromm runtime->hw = channel->pcm_hardware;
29213b41b57SChristian Gromm return 0;
29313b41b57SChristian Gromm }
29413b41b57SChristian Gromm
29513b41b57SChristian Gromm /**
29613b41b57SChristian Gromm * pcm_close - implements close callback function for PCM middle layer
29713b41b57SChristian Gromm * @substream: sub-stream pointer
29813b41b57SChristian Gromm *
29913b41b57SChristian Gromm * Obviously, this is called when a PCM substream is closed. Any private
30013b41b57SChristian Gromm * instance for a PCM substream allocated in the open callback will be
30113b41b57SChristian Gromm * released here.
30213b41b57SChristian Gromm *
30313b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
30413b41b57SChristian Gromm */
pcm_close(struct snd_pcm_substream * substream)30513b41b57SChristian Gromm static int pcm_close(struct snd_pcm_substream *substream)
30613b41b57SChristian Gromm {
30713b41b57SChristian Gromm struct channel *channel = substream->private_data;
30813b41b57SChristian Gromm
30913b41b57SChristian Gromm if (channel->cfg->direction == MOST_CH_TX)
31013b41b57SChristian Gromm kthread_stop(channel->playback_task);
31113b41b57SChristian Gromm most_stop_channel(channel->iface, channel->id, &comp);
31213b41b57SChristian Gromm return 0;
31313b41b57SChristian Gromm }
31413b41b57SChristian Gromm
31513b41b57SChristian Gromm /**
31613b41b57SChristian Gromm * pcm_prepare - implements prepare callback function for PCM middle layer
31713b41b57SChristian Gromm * @substream: substream pointer
31813b41b57SChristian Gromm *
31913b41b57SChristian Gromm * This callback is called when the PCM is "prepared". Format rate, sample rate,
32013b41b57SChristian Gromm * etc., can be set here. This callback can be called many times at each setup.
32113b41b57SChristian Gromm *
32213b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
32313b41b57SChristian Gromm */
pcm_prepare(struct snd_pcm_substream * substream)32413b41b57SChristian Gromm static int pcm_prepare(struct snd_pcm_substream *substream)
32513b41b57SChristian Gromm {
32613b41b57SChristian Gromm struct channel *channel = substream->private_data;
32713b41b57SChristian Gromm struct snd_pcm_runtime *runtime = substream->runtime;
32813b41b57SChristian Gromm struct most_channel_config *cfg = channel->cfg;
32913b41b57SChristian Gromm int width = snd_pcm_format_physical_width(runtime->format);
33013b41b57SChristian Gromm
33113b41b57SChristian Gromm channel->copy_fn = NULL;
33213b41b57SChristian Gromm
33313b41b57SChristian Gromm if (cfg->direction == MOST_CH_TX) {
33413b41b57SChristian Gromm if (snd_pcm_format_big_endian(runtime->format) || width == 8)
33513b41b57SChristian Gromm channel->copy_fn = alsa_to_most_memcpy;
33613b41b57SChristian Gromm else if (width == 16)
33713b41b57SChristian Gromm channel->copy_fn = alsa_to_most_copy16;
33813b41b57SChristian Gromm else if (width == 24)
33913b41b57SChristian Gromm channel->copy_fn = alsa_to_most_copy24;
34013b41b57SChristian Gromm else if (width == 32)
34113b41b57SChristian Gromm channel->copy_fn = alsa_to_most_copy32;
34213b41b57SChristian Gromm } else {
34313b41b57SChristian Gromm if (snd_pcm_format_big_endian(runtime->format) || width == 8)
34413b41b57SChristian Gromm channel->copy_fn = most_to_alsa_memcpy;
34513b41b57SChristian Gromm else if (width == 16)
34613b41b57SChristian Gromm channel->copy_fn = most_to_alsa_copy16;
34713b41b57SChristian Gromm else if (width == 24)
34813b41b57SChristian Gromm channel->copy_fn = most_to_alsa_copy24;
34913b41b57SChristian Gromm else if (width == 32)
35013b41b57SChristian Gromm channel->copy_fn = most_to_alsa_copy32;
35113b41b57SChristian Gromm }
35213b41b57SChristian Gromm
35313b41b57SChristian Gromm if (!channel->copy_fn)
35413b41b57SChristian Gromm return -EINVAL;
35513b41b57SChristian Gromm channel->period_pos = 0;
35613b41b57SChristian Gromm channel->buffer_pos = 0;
35713b41b57SChristian Gromm return 0;
35813b41b57SChristian Gromm }
35913b41b57SChristian Gromm
36013b41b57SChristian Gromm /**
36113b41b57SChristian Gromm * pcm_trigger - implements trigger callback function for PCM middle layer
36213b41b57SChristian Gromm * @substream: substream pointer
36313b41b57SChristian Gromm * @cmd: action to perform
36413b41b57SChristian Gromm *
36513b41b57SChristian Gromm * This is called when the PCM is started, stopped or paused. The action will be
36613b41b57SChristian Gromm * specified in the second argument, SNDRV_PCM_TRIGGER_XXX
36713b41b57SChristian Gromm *
36813b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
36913b41b57SChristian Gromm */
pcm_trigger(struct snd_pcm_substream * substream,int cmd)37013b41b57SChristian Gromm static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
37113b41b57SChristian Gromm {
37213b41b57SChristian Gromm struct channel *channel = substream->private_data;
37313b41b57SChristian Gromm
37413b41b57SChristian Gromm switch (cmd) {
37513b41b57SChristian Gromm case SNDRV_PCM_TRIGGER_START:
37613b41b57SChristian Gromm channel->is_stream_running = true;
37713b41b57SChristian Gromm wake_up_interruptible(&channel->playback_waitq);
37813b41b57SChristian Gromm return 0;
37913b41b57SChristian Gromm
38013b41b57SChristian Gromm case SNDRV_PCM_TRIGGER_STOP:
38113b41b57SChristian Gromm channel->is_stream_running = false;
38213b41b57SChristian Gromm return 0;
38313b41b57SChristian Gromm
38413b41b57SChristian Gromm default:
38513b41b57SChristian Gromm return -EINVAL;
38613b41b57SChristian Gromm }
38713b41b57SChristian Gromm return 0;
38813b41b57SChristian Gromm }
38913b41b57SChristian Gromm
39013b41b57SChristian Gromm /**
39113b41b57SChristian Gromm * pcm_pointer - implements pointer callback function for PCM middle layer
39213b41b57SChristian Gromm * @substream: substream pointer
39313b41b57SChristian Gromm *
39413b41b57SChristian Gromm * This callback is called when the PCM middle layer inquires the current
39513b41b57SChristian Gromm * hardware position on the buffer. The position must be returned in frames,
39613b41b57SChristian Gromm * ranging from 0 to buffer_size-1.
39713b41b57SChristian Gromm */
pcm_pointer(struct snd_pcm_substream * substream)39813b41b57SChristian Gromm static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
39913b41b57SChristian Gromm {
40013b41b57SChristian Gromm struct channel *channel = substream->private_data;
40113b41b57SChristian Gromm
40213b41b57SChristian Gromm return channel->buffer_pos;
40313b41b57SChristian Gromm }
40413b41b57SChristian Gromm
405*fba3993eSRandy Dunlap /*
40613b41b57SChristian Gromm * Initialization of struct snd_pcm_ops
40713b41b57SChristian Gromm */
40813b41b57SChristian Gromm static const struct snd_pcm_ops pcm_ops = {
40913b41b57SChristian Gromm .open = pcm_open,
41013b41b57SChristian Gromm .close = pcm_close,
41113b41b57SChristian Gromm .prepare = pcm_prepare,
41213b41b57SChristian Gromm .trigger = pcm_trigger,
41313b41b57SChristian Gromm .pointer = pcm_pointer,
41413b41b57SChristian Gromm };
41513b41b57SChristian Gromm
split_arg_list(char * buf,u16 * ch_num,char ** sample_res)41613b41b57SChristian Gromm static int split_arg_list(char *buf, u16 *ch_num, char **sample_res)
41713b41b57SChristian Gromm {
41813b41b57SChristian Gromm char *num;
41913b41b57SChristian Gromm int ret;
42013b41b57SChristian Gromm
42113b41b57SChristian Gromm num = strsep(&buf, "x");
42213b41b57SChristian Gromm if (!num)
42313b41b57SChristian Gromm goto err;
42413b41b57SChristian Gromm ret = kstrtou16(num, 0, ch_num);
42513b41b57SChristian Gromm if (ret)
42613b41b57SChristian Gromm goto err;
42713b41b57SChristian Gromm *sample_res = strsep(&buf, ".\n");
42813b41b57SChristian Gromm if (!*sample_res)
42913b41b57SChristian Gromm goto err;
43013b41b57SChristian Gromm return 0;
43113b41b57SChristian Gromm
43213b41b57SChristian Gromm err:
43313b41b57SChristian Gromm pr_err("Bad PCM format\n");
43413b41b57SChristian Gromm return -EINVAL;
43513b41b57SChristian Gromm }
43613b41b57SChristian Gromm
43713b41b57SChristian Gromm static const struct sample_resolution_info {
43813b41b57SChristian Gromm const char *sample_res;
43913b41b57SChristian Gromm int bytes;
44013b41b57SChristian Gromm u64 formats;
44113b41b57SChristian Gromm } sinfo[] = {
44213b41b57SChristian Gromm { "8", 1, SNDRV_PCM_FMTBIT_S8 },
44313b41b57SChristian Gromm { "16", 2, SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE },
44413b41b57SChristian Gromm { "24", 3, SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE },
44513b41b57SChristian Gromm { "32", 4, SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE },
44613b41b57SChristian Gromm };
44713b41b57SChristian Gromm
audio_set_hw_params(struct snd_pcm_hardware * pcm_hw,u16 ch_num,char * sample_res,struct most_channel_config * cfg)44813b41b57SChristian Gromm static int audio_set_hw_params(struct snd_pcm_hardware *pcm_hw,
44913b41b57SChristian Gromm u16 ch_num, char *sample_res,
45013b41b57SChristian Gromm struct most_channel_config *cfg)
45113b41b57SChristian Gromm {
45213b41b57SChristian Gromm int i;
45313b41b57SChristian Gromm
45413b41b57SChristian Gromm for (i = 0; i < ARRAY_SIZE(sinfo); i++) {
45513b41b57SChristian Gromm if (!strcmp(sample_res, sinfo[i].sample_res))
45613b41b57SChristian Gromm goto found;
45713b41b57SChristian Gromm }
45813b41b57SChristian Gromm pr_err("Unsupported PCM format\n");
45913b41b57SChristian Gromm return -EINVAL;
46013b41b57SChristian Gromm
46113b41b57SChristian Gromm found:
46213b41b57SChristian Gromm if (!ch_num) {
46313b41b57SChristian Gromm pr_err("Bad number of channels\n");
46413b41b57SChristian Gromm return -EINVAL;
46513b41b57SChristian Gromm }
46613b41b57SChristian Gromm
46713b41b57SChristian Gromm if (cfg->subbuffer_size != ch_num * sinfo[i].bytes) {
46813b41b57SChristian Gromm pr_err("Audio resolution doesn't fit subbuffer size\n");
46913b41b57SChristian Gromm return -EINVAL;
47013b41b57SChristian Gromm }
47113b41b57SChristian Gromm
47213b41b57SChristian Gromm pcm_hw->info = MOST_PCM_INFO;
47313b41b57SChristian Gromm pcm_hw->rates = SNDRV_PCM_RATE_48000;
47413b41b57SChristian Gromm pcm_hw->rate_min = 48000;
47513b41b57SChristian Gromm pcm_hw->rate_max = 48000;
47613b41b57SChristian Gromm pcm_hw->buffer_bytes_max = cfg->num_buffers * cfg->buffer_size;
47713b41b57SChristian Gromm pcm_hw->period_bytes_min = cfg->buffer_size;
47813b41b57SChristian Gromm pcm_hw->period_bytes_max = cfg->buffer_size;
47913b41b57SChristian Gromm pcm_hw->periods_min = 1;
48013b41b57SChristian Gromm pcm_hw->periods_max = cfg->num_buffers;
48113b41b57SChristian Gromm pcm_hw->channels_min = ch_num;
48213b41b57SChristian Gromm pcm_hw->channels_max = ch_num;
48313b41b57SChristian Gromm pcm_hw->formats = sinfo[i].formats;
48413b41b57SChristian Gromm return 0;
48513b41b57SChristian Gromm }
48613b41b57SChristian Gromm
release_adapter(struct sound_adapter * adpt)48713b41b57SChristian Gromm static void release_adapter(struct sound_adapter *adpt)
48813b41b57SChristian Gromm {
48913b41b57SChristian Gromm struct channel *channel, *tmp;
49013b41b57SChristian Gromm
49113b41b57SChristian Gromm list_for_each_entry_safe(channel, tmp, &adpt->dev_list, list) {
49213b41b57SChristian Gromm list_del(&channel->list);
49313b41b57SChristian Gromm kfree(channel);
49413b41b57SChristian Gromm }
49513b41b57SChristian Gromm if (adpt->card)
49613b41b57SChristian Gromm snd_card_free(adpt->card);
49713b41b57SChristian Gromm list_del(&adpt->list);
49813b41b57SChristian Gromm kfree(adpt);
49913b41b57SChristian Gromm }
50013b41b57SChristian Gromm
50113b41b57SChristian Gromm /**
50213b41b57SChristian Gromm * audio_probe_channel - probe function of the driver module
50313b41b57SChristian Gromm * @iface: pointer to interface instance
50413b41b57SChristian Gromm * @channel_id: channel index/ID
50513b41b57SChristian Gromm * @cfg: pointer to actual channel configuration
506*fba3993eSRandy Dunlap * @device_name: name of the device to be created in /dev
507*fba3993eSRandy Dunlap * @arg_list: string that provides the desired audio resolution
50813b41b57SChristian Gromm *
50913b41b57SChristian Gromm * Creates sound card, pcm device, sets pcm ops and registers sound card.
51013b41b57SChristian Gromm *
51113b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
51213b41b57SChristian Gromm */
audio_probe_channel(struct most_interface * iface,int channel_id,struct most_channel_config * cfg,char * device_name,char * arg_list)51313b41b57SChristian Gromm static int audio_probe_channel(struct most_interface *iface, int channel_id,
51413b41b57SChristian Gromm struct most_channel_config *cfg,
51513b41b57SChristian Gromm char *device_name, char *arg_list)
51613b41b57SChristian Gromm {
51713b41b57SChristian Gromm struct channel *channel;
51813b41b57SChristian Gromm struct sound_adapter *adpt;
51913b41b57SChristian Gromm struct snd_pcm *pcm;
52013b41b57SChristian Gromm int playback_count = 0;
52113b41b57SChristian Gromm int capture_count = 0;
52213b41b57SChristian Gromm int ret;
52313b41b57SChristian Gromm int direction;
52413b41b57SChristian Gromm u16 ch_num;
52513b41b57SChristian Gromm char *sample_res;
52613b41b57SChristian Gromm char arg_list_cpy[STRING_SIZE];
52713b41b57SChristian Gromm
52813b41b57SChristian Gromm if (cfg->data_type != MOST_CH_SYNC) {
52913b41b57SChristian Gromm pr_err("Incompatible channel type\n");
53013b41b57SChristian Gromm return -EINVAL;
53113b41b57SChristian Gromm }
53213b41b57SChristian Gromm strscpy(arg_list_cpy, arg_list, STRING_SIZE);
53313b41b57SChristian Gromm ret = split_arg_list(arg_list_cpy, &ch_num, &sample_res);
53413b41b57SChristian Gromm if (ret < 0)
53513b41b57SChristian Gromm return ret;
53613b41b57SChristian Gromm
53713b41b57SChristian Gromm list_for_each_entry(adpt, &adpt_list, list) {
53813b41b57SChristian Gromm if (adpt->iface != iface)
53913b41b57SChristian Gromm continue;
54013b41b57SChristian Gromm if (adpt->registered)
54113b41b57SChristian Gromm return -ENOSPC;
54213b41b57SChristian Gromm adpt->pcm_dev_idx++;
54313b41b57SChristian Gromm goto skip_adpt_alloc;
54413b41b57SChristian Gromm }
54513b41b57SChristian Gromm adpt = kzalloc(sizeof(*adpt), GFP_KERNEL);
54613b41b57SChristian Gromm if (!adpt)
54713b41b57SChristian Gromm return -ENOMEM;
54813b41b57SChristian Gromm
54913b41b57SChristian Gromm adpt->iface = iface;
55013b41b57SChristian Gromm INIT_LIST_HEAD(&adpt->dev_list);
55113b41b57SChristian Gromm iface->priv = adpt;
55213b41b57SChristian Gromm list_add_tail(&adpt->list, &adpt_list);
55313b41b57SChristian Gromm ret = snd_card_new(iface->driver_dev, -1, "INIC", THIS_MODULE,
55413b41b57SChristian Gromm sizeof(*channel), &adpt->card);
55513b41b57SChristian Gromm if (ret < 0)
55613b41b57SChristian Gromm goto err_free_adpt;
55713b41b57SChristian Gromm snprintf(adpt->card->driver, sizeof(adpt->card->driver),
55813b41b57SChristian Gromm "%s", DRIVER_NAME);
55913b41b57SChristian Gromm snprintf(adpt->card->shortname, sizeof(adpt->card->shortname),
56013b41b57SChristian Gromm "Microchip INIC");
56113b41b57SChristian Gromm snprintf(adpt->card->longname, sizeof(adpt->card->longname),
56213b41b57SChristian Gromm "%s at %s", adpt->card->shortname, iface->description);
56313b41b57SChristian Gromm skip_adpt_alloc:
56413b41b57SChristian Gromm if (get_channel(iface, channel_id)) {
56513b41b57SChristian Gromm pr_err("channel (%s:%d) is already linked\n",
56613b41b57SChristian Gromm iface->description, channel_id);
56713b41b57SChristian Gromm return -EEXIST;
56813b41b57SChristian Gromm }
56913b41b57SChristian Gromm
57013b41b57SChristian Gromm if (cfg->direction == MOST_CH_TX) {
57113b41b57SChristian Gromm playback_count = 1;
57213b41b57SChristian Gromm direction = SNDRV_PCM_STREAM_PLAYBACK;
57313b41b57SChristian Gromm } else {
57413b41b57SChristian Gromm capture_count = 1;
57513b41b57SChristian Gromm direction = SNDRV_PCM_STREAM_CAPTURE;
57613b41b57SChristian Gromm }
57713b41b57SChristian Gromm channel = kzalloc(sizeof(*channel), GFP_KERNEL);
57813b41b57SChristian Gromm if (!channel) {
57913b41b57SChristian Gromm ret = -ENOMEM;
58013b41b57SChristian Gromm goto err_free_adpt;
58113b41b57SChristian Gromm }
58213b41b57SChristian Gromm channel->card = adpt->card;
58313b41b57SChristian Gromm channel->cfg = cfg;
58413b41b57SChristian Gromm channel->iface = iface;
58513b41b57SChristian Gromm channel->id = channel_id;
58613b41b57SChristian Gromm init_waitqueue_head(&channel->playback_waitq);
58713b41b57SChristian Gromm list_add_tail(&channel->list, &adpt->dev_list);
58813b41b57SChristian Gromm
58913b41b57SChristian Gromm ret = audio_set_hw_params(&channel->pcm_hardware, ch_num, sample_res,
59013b41b57SChristian Gromm cfg);
59113b41b57SChristian Gromm if (ret)
59213b41b57SChristian Gromm goto err_free_adpt;
59313b41b57SChristian Gromm
59413b41b57SChristian Gromm ret = snd_pcm_new(adpt->card, device_name, adpt->pcm_dev_idx,
59513b41b57SChristian Gromm playback_count, capture_count, &pcm);
59613b41b57SChristian Gromm
59713b41b57SChristian Gromm if (ret < 0)
59813b41b57SChristian Gromm goto err_free_adpt;
59913b41b57SChristian Gromm
60013b41b57SChristian Gromm pcm->private_data = channel;
60113b41b57SChristian Gromm strscpy(pcm->name, device_name, sizeof(pcm->name));
60213b41b57SChristian Gromm snd_pcm_set_ops(pcm, direction, &pcm_ops);
60313b41b57SChristian Gromm snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
60413b41b57SChristian Gromm return 0;
60513b41b57SChristian Gromm
60613b41b57SChristian Gromm err_free_adpt:
60713b41b57SChristian Gromm release_adapter(adpt);
60813b41b57SChristian Gromm return ret;
60913b41b57SChristian Gromm }
61013b41b57SChristian Gromm
audio_create_sound_card(void)61113b41b57SChristian Gromm static int audio_create_sound_card(void)
61213b41b57SChristian Gromm {
61313b41b57SChristian Gromm int ret;
61413b41b57SChristian Gromm struct sound_adapter *adpt;
61513b41b57SChristian Gromm
61613b41b57SChristian Gromm list_for_each_entry(adpt, &adpt_list, list) {
61713b41b57SChristian Gromm if (!adpt->registered)
61813b41b57SChristian Gromm goto adpt_alloc;
61913b41b57SChristian Gromm }
62013b41b57SChristian Gromm return -ENODEV;
62113b41b57SChristian Gromm adpt_alloc:
62213b41b57SChristian Gromm ret = snd_card_register(adpt->card);
62313b41b57SChristian Gromm if (ret < 0) {
62413b41b57SChristian Gromm release_adapter(adpt);
62513b41b57SChristian Gromm return ret;
62613b41b57SChristian Gromm }
62713b41b57SChristian Gromm adpt->registered = true;
62813b41b57SChristian Gromm return 0;
62913b41b57SChristian Gromm }
63013b41b57SChristian Gromm
63113b41b57SChristian Gromm /**
63213b41b57SChristian Gromm * audio_disconnect_channel - function to disconnect a channel
63313b41b57SChristian Gromm * @iface: pointer to interface instance
63413b41b57SChristian Gromm * @channel_id: channel index
63513b41b57SChristian Gromm *
63613b41b57SChristian Gromm * This frees allocated memory and removes the sound card from ALSA
63713b41b57SChristian Gromm *
63813b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
63913b41b57SChristian Gromm */
audio_disconnect_channel(struct most_interface * iface,int channel_id)64013b41b57SChristian Gromm static int audio_disconnect_channel(struct most_interface *iface,
64113b41b57SChristian Gromm int channel_id)
64213b41b57SChristian Gromm {
64313b41b57SChristian Gromm struct channel *channel;
64413b41b57SChristian Gromm struct sound_adapter *adpt = iface->priv;
64513b41b57SChristian Gromm
64613b41b57SChristian Gromm channel = get_channel(iface, channel_id);
64713b41b57SChristian Gromm if (!channel)
64813b41b57SChristian Gromm return -EINVAL;
64913b41b57SChristian Gromm
65013b41b57SChristian Gromm list_del(&channel->list);
65113b41b57SChristian Gromm
65213b41b57SChristian Gromm kfree(channel);
65313b41b57SChristian Gromm if (list_empty(&adpt->dev_list))
65413b41b57SChristian Gromm release_adapter(adpt);
65513b41b57SChristian Gromm return 0;
65613b41b57SChristian Gromm }
65713b41b57SChristian Gromm
65813b41b57SChristian Gromm /**
65913b41b57SChristian Gromm * audio_rx_completion - completion handler for rx channels
66013b41b57SChristian Gromm * @mbo: pointer to buffer object that has completed
66113b41b57SChristian Gromm *
66213b41b57SChristian Gromm * This searches for the channel this MBO belongs to and copy the data from MBO
66313b41b57SChristian Gromm * to ring buffer
66413b41b57SChristian Gromm *
66513b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
66613b41b57SChristian Gromm */
audio_rx_completion(struct mbo * mbo)66713b41b57SChristian Gromm static int audio_rx_completion(struct mbo *mbo)
66813b41b57SChristian Gromm {
66913b41b57SChristian Gromm struct channel *channel = get_channel(mbo->ifp, mbo->hdm_channel_id);
67013b41b57SChristian Gromm bool period_elapsed = false;
67113b41b57SChristian Gromm
67213b41b57SChristian Gromm if (!channel)
67313b41b57SChristian Gromm return -EINVAL;
67413b41b57SChristian Gromm if (channel->is_stream_running)
67513b41b57SChristian Gromm period_elapsed = copy_data(channel, mbo);
67613b41b57SChristian Gromm most_put_mbo(mbo);
67713b41b57SChristian Gromm if (period_elapsed)
67813b41b57SChristian Gromm snd_pcm_period_elapsed(channel->substream);
67913b41b57SChristian Gromm return 0;
68013b41b57SChristian Gromm }
68113b41b57SChristian Gromm
68213b41b57SChristian Gromm /**
68313b41b57SChristian Gromm * audio_tx_completion - completion handler for tx channels
68413b41b57SChristian Gromm * @iface: pointer to interface instance
68513b41b57SChristian Gromm * @channel_id: channel index/ID
68613b41b57SChristian Gromm *
68713b41b57SChristian Gromm * This searches the channel that belongs to this combination of interface
68813b41b57SChristian Gromm * pointer and channel ID and wakes a process sitting in the wait queue of
68913b41b57SChristian Gromm * this channel.
69013b41b57SChristian Gromm *
69113b41b57SChristian Gromm * Returns 0 on success or error code otherwise.
69213b41b57SChristian Gromm */
audio_tx_completion(struct most_interface * iface,int channel_id)69313b41b57SChristian Gromm static int audio_tx_completion(struct most_interface *iface, int channel_id)
69413b41b57SChristian Gromm {
69513b41b57SChristian Gromm struct channel *channel = get_channel(iface, channel_id);
69613b41b57SChristian Gromm
69713b41b57SChristian Gromm if (!channel)
69813b41b57SChristian Gromm return -EINVAL;
69913b41b57SChristian Gromm
70013b41b57SChristian Gromm wake_up_interruptible(&channel->playback_waitq);
70113b41b57SChristian Gromm return 0;
70213b41b57SChristian Gromm }
70313b41b57SChristian Gromm
704*fba3993eSRandy Dunlap /*
70513b41b57SChristian Gromm * Initialization of the struct most_component
70613b41b57SChristian Gromm */
70713b41b57SChristian Gromm static struct most_component comp = {
70813b41b57SChristian Gromm .mod = THIS_MODULE,
70913b41b57SChristian Gromm .name = DRIVER_NAME,
71013b41b57SChristian Gromm .probe_channel = audio_probe_channel,
71113b41b57SChristian Gromm .disconnect_channel = audio_disconnect_channel,
71213b41b57SChristian Gromm .rx_completion = audio_rx_completion,
71313b41b57SChristian Gromm .tx_completion = audio_tx_completion,
71413b41b57SChristian Gromm .cfg_complete = audio_create_sound_card,
71513b41b57SChristian Gromm };
71613b41b57SChristian Gromm
audio_init(void)71713b41b57SChristian Gromm static int __init audio_init(void)
71813b41b57SChristian Gromm {
71913b41b57SChristian Gromm int ret;
72013b41b57SChristian Gromm
72113b41b57SChristian Gromm INIT_LIST_HEAD(&adpt_list);
72213b41b57SChristian Gromm
72313b41b57SChristian Gromm ret = most_register_component(&comp);
72413b41b57SChristian Gromm if (ret) {
72513b41b57SChristian Gromm pr_err("Failed to register %s\n", comp.name);
72613b41b57SChristian Gromm return ret;
72713b41b57SChristian Gromm }
72813b41b57SChristian Gromm ret = most_register_configfs_subsys(&comp);
72913b41b57SChristian Gromm if (ret) {
73013b41b57SChristian Gromm pr_err("Failed to register %s configfs subsys\n", comp.name);
73113b41b57SChristian Gromm most_deregister_component(&comp);
73213b41b57SChristian Gromm }
73313b41b57SChristian Gromm return ret;
73413b41b57SChristian Gromm }
73513b41b57SChristian Gromm
audio_exit(void)73613b41b57SChristian Gromm static void __exit audio_exit(void)
73713b41b57SChristian Gromm {
73813b41b57SChristian Gromm most_deregister_configfs_subsys(&comp);
73913b41b57SChristian Gromm most_deregister_component(&comp);
74013b41b57SChristian Gromm }
74113b41b57SChristian Gromm
74213b41b57SChristian Gromm module_init(audio_init);
74313b41b57SChristian Gromm module_exit(audio_exit);
74413b41b57SChristian Gromm
74513b41b57SChristian Gromm MODULE_LICENSE("GPL");
74613b41b57SChristian Gromm MODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>");
74713b41b57SChristian Gromm MODULE_DESCRIPTION("Sound Component Module for Mostcore");
748