brintos

brintos / linux-shallow public Read only

0
0
Text · 18.8 KiB · 45d7628 Raw
748 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * sound.c - Sound component for Mostcore4 *5 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 10#include <linux/module.h>11#include <linux/printk.h>12#include <linux/kernel.h>13#include <linux/slab.h>14#include <linux/init.h>15#include <sound/core.h>16#include <sound/pcm.h>17#include <sound/pcm_params.h>18#include <linux/sched.h>19#include <linux/kthread.h>20#include <linux/most.h>21 22#define DRIVER_NAME "sound"23#define STRING_SIZE	8024 25static struct most_component comp;26 27/**28 * struct channel - private structure to keep channel specific data29 * @substream: stores the substream structure30 * @pcm_hardware: low-level hardware description31 * @iface: interface for which the channel belongs to32 * @cfg: channel configuration33 * @card: registered sound card34 * @list: list for private use35 * @id: channel index36 * @period_pos: current period position (ring buffer)37 * @buffer_pos: current buffer position (ring buffer)38 * @is_stream_running: identifies whether a stream is running or not39 * @opened: set when the stream is opened40 * @playback_task: playback thread41 * @playback_waitq: waitq used by playback thread42 * @copy_fn: copy function for PCM-specific format and width43 */44struct channel {45	struct snd_pcm_substream *substream;46	struct snd_pcm_hardware pcm_hardware;47	struct most_interface *iface;48	struct most_channel_config *cfg;49	struct snd_card *card;50	struct list_head list;51	int id;52	unsigned int period_pos;53	unsigned int buffer_pos;54	bool is_stream_running;55	struct task_struct *playback_task;56	wait_queue_head_t playback_waitq;57	void (*copy_fn)(void *alsa, void *most, unsigned int bytes);58};59 60struct sound_adapter {61	struct list_head dev_list;62	struct most_interface *iface;63	struct snd_card *card;64	struct list_head list;65	bool registered;66	int pcm_dev_idx;67};68 69static struct list_head adpt_list;70 71#define MOST_PCM_INFO (SNDRV_PCM_INFO_MMAP | \72		       SNDRV_PCM_INFO_MMAP_VALID | \73		       SNDRV_PCM_INFO_BATCH | \74		       SNDRV_PCM_INFO_INTERLEAVED | \75		       SNDRV_PCM_INFO_BLOCK_TRANSFER)76 77static void swap_copy16(u16 *dest, const u16 *source, unsigned int bytes)78{79	unsigned int i = 0;80 81	while (i < (bytes / 2)) {82		dest[i] = swab16(source[i]);83		i++;84	}85}86 87static void swap_copy24(u8 *dest, const u8 *source, unsigned int bytes)88{89	unsigned int i = 0;90 91	if (bytes < 2)92		return;93	while (i < bytes - 2) {94		dest[i] = source[i + 2];95		dest[i + 1] = source[i + 1];96		dest[i + 2] = source[i];97		i += 3;98	}99}100 101static void swap_copy32(u32 *dest, const u32 *source, unsigned int bytes)102{103	unsigned int i = 0;104 105	while (i < bytes / 4) {106		dest[i] = swab32(source[i]);107		i++;108	}109}110 111static void alsa_to_most_memcpy(void *alsa, void *most, unsigned int bytes)112{113	memcpy(most, alsa, bytes);114}115 116static void alsa_to_most_copy16(void *alsa, void *most, unsigned int bytes)117{118	swap_copy16(most, alsa, bytes);119}120 121static void alsa_to_most_copy24(void *alsa, void *most, unsigned int bytes)122{123	swap_copy24(most, alsa, bytes);124}125 126static void alsa_to_most_copy32(void *alsa, void *most, unsigned int bytes)127{128	swap_copy32(most, alsa, bytes);129}130 131static void most_to_alsa_memcpy(void *alsa, void *most, unsigned int bytes)132{133	memcpy(alsa, most, bytes);134}135 136static void most_to_alsa_copy16(void *alsa, void *most, unsigned int bytes)137{138	swap_copy16(alsa, most, bytes);139}140 141static void most_to_alsa_copy24(void *alsa, void *most, unsigned int bytes)142{143	swap_copy24(alsa, most, bytes);144}145 146static void most_to_alsa_copy32(void *alsa, void *most, unsigned int bytes)147{148	swap_copy32(alsa, most, bytes);149}150 151/**152 * get_channel - get pointer to channel153 * @iface: interface structure154 * @channel_id: channel ID155 *156 * This traverses the channel list and returns the channel matching the157 * ID and interface.158 *159 * Returns pointer to channel on success or NULL otherwise.160 */161static struct channel *get_channel(struct most_interface *iface,162				   int channel_id)163{164	struct sound_adapter *adpt = iface->priv;165	struct channel *channel;166 167	list_for_each_entry(channel, &adpt->dev_list, list) {168		if ((channel->iface == iface) && (channel->id == channel_id))169			return channel;170	}171	return NULL;172}173 174/**175 * copy_data - implements data copying function176 * @channel: channel177 * @mbo: MBO from core178 *179 * Copy data from/to ring buffer to/from MBO and update the buffer position180 */181static bool copy_data(struct channel *channel, struct mbo *mbo)182{183	struct snd_pcm_runtime *const runtime = channel->substream->runtime;184	unsigned int const frame_bytes = channel->cfg->subbuffer_size;185	unsigned int const buffer_size = runtime->buffer_size;186	unsigned int frames;187	unsigned int fr0;188 189	if (channel->cfg->direction & MOST_CH_RX)190		frames = mbo->processed_length / frame_bytes;191	else192		frames = mbo->buffer_length / frame_bytes;193	fr0 = min(buffer_size - channel->buffer_pos, frames);194 195	channel->copy_fn(runtime->dma_area + channel->buffer_pos * frame_bytes,196			 mbo->virt_address,197			 fr0 * frame_bytes);198 199	if (frames > fr0) {200		/* wrap around at end of ring buffer */201		channel->copy_fn(runtime->dma_area,202				 mbo->virt_address + fr0 * frame_bytes,203				 (frames - fr0) * frame_bytes);204	}205 206	channel->buffer_pos += frames;207	if (channel->buffer_pos >= buffer_size)208		channel->buffer_pos -= buffer_size;209	channel->period_pos += frames;210	if (channel->period_pos >= runtime->period_size) {211		channel->period_pos -= runtime->period_size;212		return true;213	}214	return false;215}216 217/**218 * playback_thread - function implements the playback thread219 * @data: private data220 *221 * Thread which does the playback functionality in a loop. It waits for a free222 * MBO from mostcore for a particular channel and copy the data from ring buffer223 * to MBO. Submit the MBO back to mostcore, after copying the data.224 *225 * Returns 0 on success or error code otherwise.226 */227static int playback_thread(void *data)228{229	struct channel *const channel = data;230 231	while (!kthread_should_stop()) {232		struct mbo *mbo = NULL;233		bool period_elapsed = false;234 235		wait_event_interruptible(236			channel->playback_waitq,237			kthread_should_stop() ||238			(channel->is_stream_running &&239			 (mbo = most_get_mbo(channel->iface, channel->id,240					     &comp))));241		if (!mbo)242			continue;243 244		if (channel->is_stream_running)245			period_elapsed = copy_data(channel, mbo);246		else247			memset(mbo->virt_address, 0, mbo->buffer_length);248 249		most_submit_mbo(mbo);250		if (period_elapsed)251			snd_pcm_period_elapsed(channel->substream);252	}253	return 0;254}255 256/**257 * pcm_open - implements open callback function for PCM middle layer258 * @substream: pointer to ALSA PCM substream259 *260 * This is called when a PCM substream is opened. At least, the function should261 * initialize the runtime->hw record.262 *263 * Returns 0 on success or error code otherwise.264 */265static int pcm_open(struct snd_pcm_substream *substream)266{267	struct channel *channel = substream->private_data;268	struct snd_pcm_runtime *runtime = substream->runtime;269	struct most_channel_config *cfg = channel->cfg;270	int ret;271 272	channel->substream = substream;273 274	if (cfg->direction == MOST_CH_TX) {275		channel->playback_task = kthread_run(playback_thread, channel,276						     "most_audio_playback");277		if (IS_ERR(channel->playback_task)) {278			pr_err("Couldn't start thread\n");279			return PTR_ERR(channel->playback_task);280		}281	}282 283	ret = most_start_channel(channel->iface, channel->id, &comp);284	if (ret) {285		pr_err("most_start_channel() failed!\n");286		if (cfg->direction == MOST_CH_TX)287			kthread_stop(channel->playback_task);288		return ret;289	}290 291	runtime->hw = channel->pcm_hardware;292	return 0;293}294 295/**296 * pcm_close - implements close callback function for PCM middle layer297 * @substream: sub-stream pointer298 *299 * Obviously, this is called when a PCM substream is closed. Any private300 * instance for a PCM substream allocated in the open callback will be301 * released here.302 *303 * Returns 0 on success or error code otherwise.304 */305static int pcm_close(struct snd_pcm_substream *substream)306{307	struct channel *channel = substream->private_data;308 309	if (channel->cfg->direction == MOST_CH_TX)310		kthread_stop(channel->playback_task);311	most_stop_channel(channel->iface, channel->id, &comp);312	return 0;313}314 315/**316 * pcm_prepare - implements prepare callback function for PCM middle layer317 * @substream: substream pointer318 *319 * This callback is called when the PCM is "prepared". Format rate, sample rate,320 * etc., can be set here. This callback can be called many times at each setup.321 *322 * Returns 0 on success or error code otherwise.323 */324static int pcm_prepare(struct snd_pcm_substream *substream)325{326	struct channel *channel = substream->private_data;327	struct snd_pcm_runtime *runtime = substream->runtime;328	struct most_channel_config *cfg = channel->cfg;329	int width = snd_pcm_format_physical_width(runtime->format);330 331	channel->copy_fn = NULL;332 333	if (cfg->direction == MOST_CH_TX) {334		if (snd_pcm_format_big_endian(runtime->format) || width == 8)335			channel->copy_fn = alsa_to_most_memcpy;336		else if (width == 16)337			channel->copy_fn = alsa_to_most_copy16;338		else if (width == 24)339			channel->copy_fn = alsa_to_most_copy24;340		else if (width == 32)341			channel->copy_fn = alsa_to_most_copy32;342	} else {343		if (snd_pcm_format_big_endian(runtime->format) || width == 8)344			channel->copy_fn = most_to_alsa_memcpy;345		else if (width == 16)346			channel->copy_fn = most_to_alsa_copy16;347		else if (width == 24)348			channel->copy_fn = most_to_alsa_copy24;349		else if (width == 32)350			channel->copy_fn = most_to_alsa_copy32;351	}352 353	if (!channel->copy_fn)354		return -EINVAL;355	channel->period_pos = 0;356	channel->buffer_pos = 0;357	return 0;358}359 360/**361 * pcm_trigger - implements trigger callback function for PCM middle layer362 * @substream: substream pointer363 * @cmd: action to perform364 *365 * This is called when the PCM is started, stopped or paused. The action will be366 * specified in the second argument, SNDRV_PCM_TRIGGER_XXX367 *368 * Returns 0 on success or error code otherwise.369 */370static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)371{372	struct channel *channel = substream->private_data;373 374	switch (cmd) {375	case SNDRV_PCM_TRIGGER_START:376		channel->is_stream_running = true;377		wake_up_interruptible(&channel->playback_waitq);378		return 0;379 380	case SNDRV_PCM_TRIGGER_STOP:381		channel->is_stream_running = false;382		return 0;383 384	default:385		return -EINVAL;386	}387	return 0;388}389 390/**391 * pcm_pointer - implements pointer callback function for PCM middle layer392 * @substream: substream pointer393 *394 * This callback is called when the PCM middle layer inquires the current395 * hardware position on the buffer. The position must be returned in frames,396 * ranging from 0 to buffer_size-1.397 */398static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)399{400	struct channel *channel = substream->private_data;401 402	return channel->buffer_pos;403}404 405/*406 * Initialization of struct snd_pcm_ops407 */408static const struct snd_pcm_ops pcm_ops = {409	.open       = pcm_open,410	.close      = pcm_close,411	.prepare    = pcm_prepare,412	.trigger    = pcm_trigger,413	.pointer    = pcm_pointer,414};415 416static int split_arg_list(char *buf, u16 *ch_num, char **sample_res)417{418	char *num;419	int ret;420 421	num = strsep(&buf, "x");422	if (!num)423		goto err;424	ret = kstrtou16(num, 0, ch_num);425	if (ret)426		goto err;427	*sample_res = strsep(&buf, ".\n");428	if (!*sample_res)429		goto err;430	return 0;431 432err:433	pr_err("Bad PCM format\n");434	return -EINVAL;435}436 437static const struct sample_resolution_info {438	const char *sample_res;439	int bytes;440	u64 formats;441} sinfo[] = {442	{ "8", 1, SNDRV_PCM_FMTBIT_S8 },443	{ "16", 2, SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE },444	{ "24", 3, SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE },445	{ "32", 4, SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE },446};447 448static int audio_set_hw_params(struct snd_pcm_hardware *pcm_hw,449			       u16 ch_num, char *sample_res,450			       struct most_channel_config *cfg)451{452	int i;453 454	for (i = 0; i < ARRAY_SIZE(sinfo); i++) {455		if (!strcmp(sample_res, sinfo[i].sample_res))456			goto found;457	}458	pr_err("Unsupported PCM format\n");459	return -EINVAL;460 461found:462	if (!ch_num) {463		pr_err("Bad number of channels\n");464		return -EINVAL;465	}466 467	if (cfg->subbuffer_size != ch_num * sinfo[i].bytes) {468		pr_err("Audio resolution doesn't fit subbuffer size\n");469		return -EINVAL;470	}471 472	pcm_hw->info = MOST_PCM_INFO;473	pcm_hw->rates = SNDRV_PCM_RATE_48000;474	pcm_hw->rate_min = 48000;475	pcm_hw->rate_max = 48000;476	pcm_hw->buffer_bytes_max = cfg->num_buffers * cfg->buffer_size;477	pcm_hw->period_bytes_min = cfg->buffer_size;478	pcm_hw->period_bytes_max = cfg->buffer_size;479	pcm_hw->periods_min = 1;480	pcm_hw->periods_max = cfg->num_buffers;481	pcm_hw->channels_min = ch_num;482	pcm_hw->channels_max = ch_num;483	pcm_hw->formats = sinfo[i].formats;484	return 0;485}486 487static void release_adapter(struct sound_adapter *adpt)488{489	struct channel *channel, *tmp;490 491	list_for_each_entry_safe(channel, tmp, &adpt->dev_list, list) {492		list_del(&channel->list);493		kfree(channel);494	}495	if (adpt->card)496		snd_card_free(adpt->card);497	list_del(&adpt->list);498	kfree(adpt);499}500 501/**502 * audio_probe_channel - probe function of the driver module503 * @iface: pointer to interface instance504 * @channel_id: channel index/ID505 * @cfg: pointer to actual channel configuration506 * @device_name: name of the device to be created in /dev507 * @arg_list: string that provides the desired audio resolution508 *509 * Creates sound card, pcm device, sets pcm ops and registers sound card.510 *511 * Returns 0 on success or error code otherwise.512 */513static int audio_probe_channel(struct most_interface *iface, int channel_id,514			       struct most_channel_config *cfg,515			       char *device_name, char *arg_list)516{517	struct channel *channel;518	struct sound_adapter *adpt;519	struct snd_pcm *pcm;520	int playback_count = 0;521	int capture_count = 0;522	int ret;523	int direction;524	u16 ch_num;525	char *sample_res;526	char arg_list_cpy[STRING_SIZE];527 528	if (cfg->data_type != MOST_CH_SYNC) {529		pr_err("Incompatible channel type\n");530		return -EINVAL;531	}532	strscpy(arg_list_cpy, arg_list, STRING_SIZE);533	ret = split_arg_list(arg_list_cpy, &ch_num, &sample_res);534	if (ret < 0)535		return ret;536 537	list_for_each_entry(adpt, &adpt_list, list) {538		if (adpt->iface != iface)539			continue;540		if (adpt->registered)541			return -ENOSPC;542		adpt->pcm_dev_idx++;543		goto skip_adpt_alloc;544	}545	adpt = kzalloc(sizeof(*adpt), GFP_KERNEL);546	if (!adpt)547		return -ENOMEM;548 549	adpt->iface = iface;550	INIT_LIST_HEAD(&adpt->dev_list);551	iface->priv = adpt;552	list_add_tail(&adpt->list, &adpt_list);553	ret = snd_card_new(iface->driver_dev, -1, "INIC", THIS_MODULE,554			   sizeof(*channel), &adpt->card);555	if (ret < 0)556		goto err_free_adpt;557	snprintf(adpt->card->driver, sizeof(adpt->card->driver),558		 "%s", DRIVER_NAME);559	snprintf(adpt->card->shortname, sizeof(adpt->card->shortname),560		 "Microchip INIC");561	snprintf(adpt->card->longname, sizeof(adpt->card->longname),562		 "%s at %s", adpt->card->shortname, iface->description);563skip_adpt_alloc:564	if (get_channel(iface, channel_id)) {565		pr_err("channel (%s:%d) is already linked\n",566		       iface->description, channel_id);567		return -EEXIST;568	}569 570	if (cfg->direction == MOST_CH_TX) {571		playback_count = 1;572		direction = SNDRV_PCM_STREAM_PLAYBACK;573	} else {574		capture_count = 1;575		direction = SNDRV_PCM_STREAM_CAPTURE;576	}577	channel = kzalloc(sizeof(*channel), GFP_KERNEL);578	if (!channel) {579		ret = -ENOMEM;580		goto err_free_adpt;581	}582	channel->card = adpt->card;583	channel->cfg = cfg;584	channel->iface = iface;585	channel->id = channel_id;586	init_waitqueue_head(&channel->playback_waitq);587	list_add_tail(&channel->list, &adpt->dev_list);588 589	ret = audio_set_hw_params(&channel->pcm_hardware, ch_num, sample_res,590				  cfg);591	if (ret)592		goto err_free_adpt;593 594	ret = snd_pcm_new(adpt->card, device_name, adpt->pcm_dev_idx,595			  playback_count, capture_count, &pcm);596 597	if (ret < 0)598		goto err_free_adpt;599 600	pcm->private_data = channel;601	strscpy(pcm->name, device_name, sizeof(pcm->name));602	snd_pcm_set_ops(pcm, direction, &pcm_ops);603	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);604	return 0;605 606err_free_adpt:607	release_adapter(adpt);608	return ret;609}610 611static int audio_create_sound_card(void)612{613	int ret;614	struct sound_adapter *adpt;615 616	list_for_each_entry(adpt, &adpt_list, list) {617		if (!adpt->registered)618			goto adpt_alloc;619	}620	return -ENODEV;621adpt_alloc:622	ret = snd_card_register(adpt->card);623	if (ret < 0) {624		release_adapter(adpt);625		return ret;626	}627	adpt->registered = true;628	return 0;629}630 631/**632 * audio_disconnect_channel - function to disconnect a channel633 * @iface: pointer to interface instance634 * @channel_id: channel index635 *636 * This frees allocated memory and removes the sound card from ALSA637 *638 * Returns 0 on success or error code otherwise.639 */640static int audio_disconnect_channel(struct most_interface *iface,641				    int channel_id)642{643	struct channel *channel;644	struct sound_adapter *adpt = iface->priv;645 646	channel = get_channel(iface, channel_id);647	if (!channel)648		return -EINVAL;649 650	list_del(&channel->list);651 652	kfree(channel);653	if (list_empty(&adpt->dev_list))654		release_adapter(adpt);655	return 0;656}657 658/**659 * audio_rx_completion - completion handler for rx channels660 * @mbo: pointer to buffer object that has completed661 *662 * This searches for the channel this MBO belongs to and copy the data from MBO663 * to ring buffer664 *665 * Returns 0 on success or error code otherwise.666 */667static int audio_rx_completion(struct mbo *mbo)668{669	struct channel *channel = get_channel(mbo->ifp, mbo->hdm_channel_id);670	bool period_elapsed = false;671 672	if (!channel)673		return -EINVAL;674	if (channel->is_stream_running)675		period_elapsed = copy_data(channel, mbo);676	most_put_mbo(mbo);677	if (period_elapsed)678		snd_pcm_period_elapsed(channel->substream);679	return 0;680}681 682/**683 * audio_tx_completion - completion handler for tx channels684 * @iface: pointer to interface instance685 * @channel_id: channel index/ID686 *687 * This searches the channel that belongs to this combination of interface688 * pointer and channel ID and wakes a process sitting in the wait queue of689 * this channel.690 *691 * Returns 0 on success or error code otherwise.692 */693static int audio_tx_completion(struct most_interface *iface, int channel_id)694{695	struct channel *channel = get_channel(iface, channel_id);696 697	if (!channel)698		return -EINVAL;699 700	wake_up_interruptible(&channel->playback_waitq);701	return 0;702}703 704/*705 * Initialization of the struct most_component706 */707static struct most_component comp = {708	.mod = THIS_MODULE,709	.name = DRIVER_NAME,710	.probe_channel = audio_probe_channel,711	.disconnect_channel = audio_disconnect_channel,712	.rx_completion = audio_rx_completion,713	.tx_completion = audio_tx_completion,714	.cfg_complete = audio_create_sound_card,715};716 717static int __init audio_init(void)718{719	int ret;720 721	INIT_LIST_HEAD(&adpt_list);722 723	ret = most_register_component(&comp);724	if (ret) {725		pr_err("Failed to register %s\n", comp.name);726		return ret;727	}728	ret = most_register_configfs_subsys(&comp);729	if (ret) {730		pr_err("Failed to register %s configfs subsys\n", comp.name);731		most_deregister_component(&comp);732	}733	return ret;734}735 736static void __exit audio_exit(void)737{738	most_deregister_configfs_subsys(&comp);739	most_deregister_component(&comp);740}741 742module_init(audio_init);743module_exit(audio_exit);744 745MODULE_LICENSE("GPL");746MODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>");747MODULE_DESCRIPTION("Sound Component Module for Mostcore");748