diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /sound/drivers/vx | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'sound/drivers/vx')
| -rw-r--r-- | sound/drivers/vx/Makefile | 9 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_cmd.c | 96 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_cmd.h | 233 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_core.c | 808 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_hwdep.c | 112 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_mixer.c | 982 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_pcm.c | 1233 | ||||
| -rw-r--r-- | sound/drivers/vx/vx_uer.c | 294 |
8 files changed, 3767 insertions, 0 deletions
diff --git a/sound/drivers/vx/Makefile b/sound/drivers/vx/Makefile new file mode 100644 index 000000000..ae1b3e092 --- /dev/null +++ b/sound/drivers/vx/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-vx-lib-y := vx_core.o vx_hwdep.o vx_pcm.o vx_mixer.o vx_cmd.o vx_uer.o + +obj-$(CONFIG_SND_VX_LIB) += snd-vx-lib.o diff --git a/sound/drivers/vx/vx_cmd.c b/sound/drivers/vx/vx_cmd.c new file mode 100644 index 000000000..b0970a048 --- /dev/null +++ b/sound/drivers/vx/vx_cmd.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * DSP commands + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + +/* + * Array of DSP commands + */ +static const struct vx_cmd_info vx_dsp_cmds[] = { +[CMD_VERSION] = { 0x010000, 2, RMH_SSIZE_FIXED, 1 }, +[CMD_SUPPORTED] = { 0x020000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_TEST_IT] = { 0x040000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_SEND_IRQA] = { 0x070001, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_IBL] = { 0x080000, 1, RMH_SSIZE_FIXED, 4 }, +[CMD_ASYNC] = { 0x0A0000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_RES_PIPE] = { 0x400000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_FREE_PIPE] = { 0x410000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_CONF_PIPE] = { 0x42A101, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_ABORT_CONF_PIPE] = { 0x42A100, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_PARAM_OUTPUT_PIPE] = { 0x43A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_STOP_PIPE] = { 0x470004, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_PIPE_STATE] = { 0x480000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_PIPE_SPL_COUNT] = { 0x49A000, 2, RMH_SSIZE_FIXED, 2 }, +[CMD_CAN_START_PIPE] = { 0x4b0000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_SIZE_HBUFFER] = { 0x4C0000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_START_STREAM] = { 0x80A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_START_ONE_STREAM] = { 0x800000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_PAUSE_STREAM] = { 0x81A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_PAUSE_ONE_STREAM] = { 0x810000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM_OUT_LEVEL_ADJUST] = { 0x828000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_STOP_STREAM] = { 0x830000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_FORMAT_STREAM_OUT] = { 0x868000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_FORMAT_STREAM_IN] = { 0x878800, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_STREAM_STATE] = { 0x890001, 2, RMH_SSIZE_FIXED, 1 }, +[CMD_DROP_BYTES_AWAY] = { 0x8A8000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_REMAINING_BYTES] = { 0x8D0800, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_CONNECT_AUDIO] = { 0xC10000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_AUDIO_LEVEL_ADJUST] = { 0xC2A000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_AUDIO_VU_PIC_METER] = { 0xC3A003, 2, RMH_SSIZE_FIXED, 1 }, +[CMD_GET_AUDIO_LEVELS] = { 0xC4A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_NOTIFY_EVENT] = { 0x4D0000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_INFO_NOTIFIED] = { 0x0B0000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_ACCESS_IO_FCT] = { 0x098000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_STATUS_R_BUFFERS] = { 0x440000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_UPDATE_R_BUFFERS] = { 0x848000, 4, RMH_SSIZE_FIXED, 0 }, +[CMD_LOAD_EFFECT_CONTEXT] = { 0x0c8000, 3, RMH_SSIZE_FIXED, 1 }, +[CMD_EFFECT_ONE_PIPE] = { 0x458000, 0, RMH_SSIZE_FIXED, 0 }, +[CMD_MODIFY_CLOCK] = { 0x0d0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM1_OUT_SET_N_LEVELS] ={ 0x858000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_PURGE_STREAM_DCMDS] = { 0x8b8000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_NOTIFY_PIPE_TIME] = { 0x4e0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_LOAD_EFFECT_CONTEXT_PACKET] = { 0x0c8000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_RELIC_R_BUFFER] = { 0x8e0800, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_RESYNC_AUDIO_INPUTS] = { 0x0e0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_NOTIFY_STREAM_TIME] = { 0x8f0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM_SAMPLE_COUNT] = { 0x900000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_CONFIG_TIME_CODE] = { 0x050000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_TIME_CODE] = { 0x060000, 1, RMH_SSIZE_FIXED, 5 }, +[CMD_MANAGE_SIGNAL] = { 0x0f0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_PARAMETER_STREAM_OUT] = { 0x91A000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_READ_BOARD_FREQ] = { 0x030000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_GET_STREAM_LEVELS] = { 0x8c0000, 1, RMH_SSIZE_FIXED, 3 }, +[CMD_PURGE_PIPE_DCMDS] = { 0x4f8000, 3, RMH_SSIZE_FIXED, 0 }, +// [CMD_SET_STREAM_OUT_EFFECTS] = { 0x888000, 34, RMH_SSIZE_FIXED, 0 }, +// [CMD_GET_STREAM_OUT_EFFECTS] = { 0x928000, 2, RMH_SSIZE_FIXED, 32 }, +[CMD_CONNECT_MONITORING] = { 0xC00000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM2_OUT_SET_N_LEVELS] = { 0x938000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_CANCEL_R_BUFFERS] = { 0x948000, 4, RMH_SSIZE_FIXED, 0 }, +[CMD_NOTIFY_END_OF_BUFFER] = { 0x950000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_STREAM_VU_METER] = { 0x95A000, 2, RMH_SSIZE_ARG, 0 }, +}; + +/** + * vx_init_rmh - initialize the RMH instance + * @rmh: the rmh pointer to be initialized + * @cmd: the rmh command to be set + */ +void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd) +{ + if (snd_BUG_ON(cmd >= CMD_LAST_INDEX)) + return; + rmh->LgCmd = vx_dsp_cmds[cmd].length; + rmh->LgStat = vx_dsp_cmds[cmd].st_length; + rmh->DspStat = vx_dsp_cmds[cmd].st_type; + rmh->Cmd[0] = vx_dsp_cmds[cmd].opcode; +} + diff --git a/sound/drivers/vx/vx_cmd.h b/sound/drivers/vx/vx_cmd.h new file mode 100644 index 000000000..1fc70c98f --- /dev/null +++ b/sound/drivers/vx/vx_cmd.h @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram VX soundcards + * + * Definitions of DSP commands + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __VX_CMD_H +#define __VX_CMD_H + +enum { + CMD_VERSION, + CMD_SUPPORTED, + CMD_TEST_IT, + CMD_SEND_IRQA, + CMD_IBL, + CMD_ASYNC, + CMD_RES_PIPE, + CMD_FREE_PIPE, + CMD_CONF_PIPE, + CMD_ABORT_CONF_PIPE, + CMD_PARAM_OUTPUT_PIPE, + CMD_STOP_PIPE, + CMD_PIPE_STATE, + CMD_PIPE_SPL_COUNT, + CMD_CAN_START_PIPE, + CMD_SIZE_HBUFFER, + CMD_START_STREAM, + CMD_START_ONE_STREAM, + CMD_PAUSE_STREAM, + CMD_PAUSE_ONE_STREAM, + CMD_STREAM_OUT_LEVEL_ADJUST, + CMD_STOP_STREAM, + CMD_FORMAT_STREAM_OUT, + CMD_FORMAT_STREAM_IN, + CMD_GET_STREAM_STATE, + CMD_DROP_BYTES_AWAY, + CMD_GET_REMAINING_BYTES, + CMD_CONNECT_AUDIO, + CMD_AUDIO_LEVEL_ADJUST, + CMD_AUDIO_VU_PIC_METER, + CMD_GET_AUDIO_LEVELS, + CMD_GET_NOTIFY_EVENT, + CMD_INFO_NOTIFIED, + CMD_ACCESS_IO_FCT, + CMD_STATUS_R_BUFFERS, + CMD_UPDATE_R_BUFFERS, + CMD_LOAD_EFFECT_CONTEXT, + CMD_EFFECT_ONE_PIPE, + CMD_MODIFY_CLOCK, + CMD_STREAM1_OUT_SET_N_LEVELS, + CMD_PURGE_STREAM_DCMDS, + CMD_NOTIFY_PIPE_TIME, + CMD_LOAD_EFFECT_CONTEXT_PACKET, + CMD_RELIC_R_BUFFER, + CMD_RESYNC_AUDIO_INPUTS, + CMD_NOTIFY_STREAM_TIME, + CMD_STREAM_SAMPLE_COUNT, + CMD_CONFIG_TIME_CODE, + CMD_GET_TIME_CODE, + CMD_MANAGE_SIGNAL, + CMD_PARAMETER_STREAM_OUT, + CMD_READ_BOARD_FREQ, + CMD_GET_STREAM_LEVELS, + CMD_PURGE_PIPE_DCMDS, + // CMD_SET_STREAM_OUT_EFFECTS, + // CMD_GET_STREAM_OUT_EFFECTS, + CMD_CONNECT_MONITORING, + CMD_STREAM2_OUT_SET_N_LEVELS, + CMD_CANCEL_R_BUFFERS, + CMD_NOTIFY_END_OF_BUFFER, + CMD_GET_STREAM_VU_METER, + CMD_LAST_INDEX +}; + +struct vx_cmd_info { + unsigned int opcode; /* command word */ + int length; /* command length (in words) */ + int st_type; /* status type (RMH_SSIZE_XXX) */ + int st_length; /* fixed length */ +}; + +/* Family and code op of some DSP requests. */ +#define CODE_OP_PIPE_TIME 0x004e0000 +#define CODE_OP_START_STREAM 0x00800000 +#define CODE_OP_PAUSE_STREAM 0x00810000 +#define CODE_OP_OUT_STREAM_LEVEL 0x00820000 +#define CODE_OP_UPDATE_R_BUFFERS 0x00840000 +#define CODE_OP_OUT_STREAM1_LEVEL_CURVE 0x00850000 +#define CODE_OP_OUT_STREAM2_LEVEL_CURVE 0x00930000 +#define CODE_OP_OUT_STREAM_FORMAT 0x00860000 +#define CODE_OP_STREAM_TIME 0x008f0000 +#define CODE_OP_OUT_STREAM_EXTRAPARAMETER 0x00910000 +#define CODE_OP_OUT_AUDIO_LEVEL 0x00c20000 + +#define NOTIFY_LAST_COMMAND 0x00400000 + +/* Values for a user delay */ +#define DC_DIFFERED_DELAY (1<<BIT_DIFFERED_COMMAND) +#define DC_NOTIFY_DELAY (1<<BIT_NOTIFIED_COMMAND) +#define DC_HBUFFER_DELAY (1<<BIT_TIME_RELATIVE_TO_BUFFER) +#define DC_MULTIPLE_DELAY (1<<BIT_RESERVED) +#define DC_STREAM_TIME_DELAY (1<<BIT_STREAM_TIME) +#define DC_CANCELLED_DELAY (1<<BIT_CANCELLED_COMMAND) + +/* Values for tiDelayed field in TIME_INFO structure, + * and for pbPause field in PLAY_BUFFER_INFO structure + */ +#define BIT_DIFFERED_COMMAND 0 +#define BIT_NOTIFIED_COMMAND 1 +#define BIT_TIME_RELATIVE_TO_BUFFER 2 +#define BIT_RESERVED 3 +#define BIT_STREAM_TIME 4 +#define BIT_CANCELLED_COMMAND 5 + +/* Access to the "Size" field of the response of the CMD_GET_NOTIFY_EVENT request. */ +#define GET_NOTIFY_EVENT_SIZE_FIELD_MASK 0x000000ff + +/* DSP commands general masks */ +#define OPCODE_MASK 0x00ff0000 +#define DSP_DIFFERED_COMMAND_MASK 0x0000C000 + +/* Notifications (NOTIFY_INFO) */ +#define ALL_CMDS_NOTIFIED 0x0000 // reserved +#define START_STREAM_NOTIFIED 0x0001 +#define PAUSE_STREAM_NOTIFIED 0x0002 +#define OUT_STREAM_LEVEL_NOTIFIED 0x0003 +#define OUT_STREAM_PARAMETER_NOTIFIED 0x0004 // left for backward compatibility +#define OUT_STREAM_FORMAT_NOTIFIED 0x0004 +#define PIPE_TIME_NOTIFIED 0x0005 +#define OUT_AUDIO_LEVEL_NOTIFIED 0x0006 +#define OUT_STREAM_LEVEL_CURVE_NOTIFIED 0x0007 +#define STREAM_TIME_NOTIFIED 0x0008 +#define OUT_STREAM_EXTRAPARAMETER_NOTIFIED 0x0009 +#define UNKNOWN_COMMAND_NOTIFIED 0xffff + +/* Output pipe parameters setting */ +#define MASK_VALID_PIPE_MPEG_PARAM 0x000040 +#define MASK_VALID_PIPE_BACKWARD_PARAM 0x000020 +#define MASK_SET_PIPE_MPEG_PARAM 0x000002 +#define MASK_SET_PIPE_BACKWARD_PARAM 0x000001 + +#define MASK_DSP_WORD 0x00FFFFFF +#define MASK_ALL_STREAM 0x00FFFFFF +#define MASK_DSP_WORD_LEVEL 0x000001FF +#define MASK_FIRST_FIELD 0x0000001F +#define FIELD_SIZE 5 + +#define COMMAND_RECORD_MASK 0x000800 + +/* PipeManagement definition bits (PIPE_DECL_INFO) */ +#define P_UNDERRUN_SKIP_SOUND_MASK 0x01 +#define P_PREPARE_FOR_MPEG3_MASK 0x02 +#define P_DO_NOT_RESET_ANALOG_LEVELS 0x04 +#define P_ALLOW_UNDER_ALLOCATION_MASK 0x08 +#define P_DATA_MODE_MASK 0x10 +#define P_ASIO_BUFFER_MANAGEMENT_MASK 0x20 + +#define BIT_SKIP_SOUND 0x08 // bit 3 +#define BIT_DATA_MODE 0x10 // bit 4 + +/* Bits in the CMD_MODIFY_CLOCK request. */ +#define CMD_MODIFY_CLOCK_FD_BIT 0x00000001 +#define CMD_MODIFY_CLOCK_T_BIT 0x00000002 +#define CMD_MODIFY_CLOCK_S_BIT 0x00000004 + +/* Access to the results of the CMD_GET_TIME_CODE RMH. */ +#define TIME_CODE_V_MASK 0x00800000 +#define TIME_CODE_N_MASK 0x00400000 +#define TIME_CODE_B_MASK 0x00200000 +#define TIME_CODE_W_MASK 0x00100000 + +/* Values for the CMD_MANAGE_SIGNAL RMH. */ +#define MANAGE_SIGNAL_TIME_CODE 0x01 +#define MANAGE_SIGNAL_MIDI 0x02 + +/* Values for the CMD_CONFIG_TIME_CODE RMH. */ +#define CONFIG_TIME_CODE_CANCEL 0x00001000 + +/* Mask to get only the effective time from the + * high word out of the 2 returned by the DSP + */ +#define PCX_TIME_HI_MASK 0x000fffff + +/* Values for setting a H-Buffer time */ +#define HBUFFER_TIME_HIGH 0x00200000 +#define HBUFFER_TIME_LOW 0x00000000 + +#define NOTIFY_MASK_TIME_HIGH 0x00400000 +#define MULTIPLE_MASK_TIME_HIGH 0x00100000 +#define STREAM_MASK_TIME_HIGH 0x00800000 + + +/* + * + */ +void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd); + +/** + * vx_set_pipe_cmd_params - fill first command word for pipe commands + * @rmh: the rmh to be modified + * @is_capture: 0 = playback, 1 = capture operation + * @param1: first pipe-parameter + * @param2: second pipe-parameter + */ +static inline void vx_set_pipe_cmd_params(struct vx_rmh *rmh, int is_capture, + int param1, int param2) +{ + if (is_capture) + rmh->Cmd[0] |= COMMAND_RECORD_MASK; + rmh->Cmd[0] |= (((u32)param1 & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD; + + if (param2) + rmh->Cmd[0] |= ((u32)param2 & MASK_FIRST_FIELD) & MASK_DSP_WORD; + +} + +/** + * vx_set_stream_cmd_params - fill first command word for stream commands + * @rmh: the rmh to be modified + * @is_capture: 0 = playback, 1 = capture operation + * @pipe: the pipe index (zero-based) + */ +static inline void vx_set_stream_cmd_params(struct vx_rmh *rmh, int is_capture, int pipe) +{ + if (is_capture) + rmh->Cmd[0] |= COMMAND_RECORD_MASK; + rmh->Cmd[0] |= (((u32)pipe & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD; +} + +#endif /* __VX_CMD_H */ diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c new file mode 100644 index 000000000..52b93407b --- /dev/null +++ b/sound/drivers/vx/vx_core.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * Hardware core part + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/asoundef.h> +#include <sound/info.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("Common routines for Digigram VX drivers"); +MODULE_LICENSE("GPL"); + + +/* + * vx_check_reg_bit - wait for the specified bit is set/reset on a register + * @reg: register to check + * @mask: bit mask + * @bit: resultant bit to be checked + * @time: time-out of loop in msec + * + * returns zero if a bit matches, or a negative error code. + */ +int snd_vx_check_reg_bit(struct vx_core *chip, int reg, int mask, int bit, int time) +{ + unsigned long end_time = jiffies + (time * HZ + 999) / 1000; + static const char * const reg_names[VX_REG_MAX] = { + "ICR", "CVR", "ISR", "IVR", "RXH", "RXM", "RXL", + "DMA", "CDSP", "RFREQ", "RUER/V2", "DATA", "MEMIRQ", + "ACQ", "BIT0", "BIT1", "MIC0", "MIC1", "MIC2", + "MIC3", "INTCSR", "CNTRL", "GPIOC", + "LOFREQ", "HIFREQ", "CSUER", "RUER" + }; + + do { + if ((snd_vx_inb(chip, reg) & mask) == bit) + return 0; + //msleep(10); + } while (time_after_eq(end_time, jiffies)); + dev_dbg(chip->card->dev, + "vx_check_reg_bit: timeout, reg=%s, mask=0x%x, val=0x%x\n", + reg_names[reg], mask, snd_vx_inb(chip, reg)); + return -EIO; +} + +EXPORT_SYMBOL(snd_vx_check_reg_bit); + +/* + * vx_send_irq_dsp - set command irq bit + * @num: the requested IRQ type, IRQ_XXX + * + * this triggers the specified IRQ request + * returns 0 if successful, or a negative error code. + * + */ +static int vx_send_irq_dsp(struct vx_core *chip, int num) +{ + int nirq; + + /* wait for Hc = 0 */ + if (snd_vx_check_reg_bit(chip, VX_CVR, CVR_HC, 0, 200) < 0) + return -EIO; + + nirq = num; + if (vx_has_new_dsp(chip)) + nirq += VXP_IRQ_OFFSET; + vx_outb(chip, CVR, (nirq >> 1) | CVR_HC); + return 0; +} + + +/* + * vx_reset_chk - reset CHK bit on ISR + * + * returns 0 if successful, or a negative error code. + */ +static int vx_reset_chk(struct vx_core *chip) +{ + /* Reset irq CHK */ + if (vx_send_irq_dsp(chip, IRQ_RESET_CHK) < 0) + return -EIO; + /* Wait until CHK = 0 */ + if (vx_check_isr(chip, ISR_CHK, 0, 200) < 0) + return -EIO; + return 0; +} + +/* + * vx_transfer_end - terminate message transfer + * @cmd: IRQ message to send (IRQ_MESS_XXX_END) + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * NB: call with mutex held! + */ +static int vx_transfer_end(struct vx_core *chip, int cmd) +{ + int err; + + err = vx_reset_chk(chip); + if (err < 0) + return err; + + /* irq MESS_READ/WRITE_END */ + err = vx_send_irq_dsp(chip, cmd); + if (err < 0) + return err; + + /* Wait CHK = 1 */ + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + + /* If error, Read RX */ + err = vx_inb(chip, ISR); + if (err & ISR_ERR) { + err = vx_wait_for_rx_full(chip); + if (err < 0) { + dev_dbg(chip->card->dev, + "transfer_end: error in rx_full\n"); + return err; + } + err = vx_inb(chip, RXH) << 16; + err |= vx_inb(chip, RXM) << 8; + err |= vx_inb(chip, RXL); + dev_dbg(chip->card->dev, "transfer_end: error = 0x%x\n", err); + return -(VX_ERR_MASK | err); + } + return 0; +} + +/* + * vx_read_status - return the status rmh + * @rmh: rmh record to store the status + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * NB: call with mutex held! + */ +static int vx_read_status(struct vx_core *chip, struct vx_rmh *rmh) +{ + int i, err, val, size; + + /* no read necessary? */ + if (rmh->DspStat == RMH_SSIZE_FIXED && rmh->LgStat == 0) + return 0; + + /* Wait for RX full (with timeout protection) + * The first word of status is in RX + */ + err = vx_wait_for_rx_full(chip); + if (err < 0) + return err; + + /* Read RX */ + val = vx_inb(chip, RXH) << 16; + val |= vx_inb(chip, RXM) << 8; + val |= vx_inb(chip, RXL); + + /* If status given by DSP, let's decode its size */ + switch (rmh->DspStat) { + case RMH_SSIZE_ARG: + size = val & 0xff; + rmh->Stat[0] = val & 0xffff00; + rmh->LgStat = size + 1; + break; + case RMH_SSIZE_MASK: + /* Let's count the arg numbers from a mask */ + rmh->Stat[0] = val; + size = 0; + while (val) { + if (val & 0x01) + size++; + val >>= 1; + } + rmh->LgStat = size + 1; + break; + default: + /* else retrieve the status length given by the driver */ + size = rmh->LgStat; + rmh->Stat[0] = val; /* Val is the status 1st word */ + size--; /* hence adjust remaining length */ + break; + } + + if (size < 1) + return 0; + if (snd_BUG_ON(size >= SIZE_MAX_STATUS)) + return -EINVAL; + + for (i = 1; i <= size; i++) { + /* trigger an irq MESS_WRITE_NEXT */ + err = vx_send_irq_dsp(chip, IRQ_MESS_WRITE_NEXT); + if (err < 0) + return err; + /* Wait for RX full (with timeout protection) */ + err = vx_wait_for_rx_full(chip); + if (err < 0) + return err; + rmh->Stat[i] = vx_inb(chip, RXH) << 16; + rmh->Stat[i] |= vx_inb(chip, RXM) << 8; + rmh->Stat[i] |= vx_inb(chip, RXL); + } + + return vx_transfer_end(chip, IRQ_MESS_WRITE_END); +} + + +#define MASK_MORE_THAN_1_WORD_COMMAND 0x00008000 +#define MASK_1_WORD_COMMAND 0x00ff7fff + +/* + * vx_send_msg_nolock - send a DSP message and read back the status + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * + * this function doesn't call mutex lock at all. + */ +int vx_send_msg_nolock(struct vx_core *chip, struct vx_rmh *rmh) +{ + int i, err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + err = vx_reset_chk(chip); + if (err < 0) { + dev_dbg(chip->card->dev, "vx_send_msg: vx_reset_chk error\n"); + return err; + } + + /* Check bit M is set according to length of the command */ + if (rmh->LgCmd > 1) + rmh->Cmd[0] |= MASK_MORE_THAN_1_WORD_COMMAND; + else + rmh->Cmd[0] &= MASK_1_WORD_COMMAND; + + /* Wait for TX empty */ + err = vx_wait_isr_bit(chip, ISR_TX_EMPTY); + if (err < 0) { + dev_dbg(chip->card->dev, "vx_send_msg: wait tx empty error\n"); + return err; + } + + /* Write Cmd[0] */ + vx_outb(chip, TXH, (rmh->Cmd[0] >> 16) & 0xff); + vx_outb(chip, TXM, (rmh->Cmd[0] >> 8) & 0xff); + vx_outb(chip, TXL, rmh->Cmd[0] & 0xff); + + /* Trigger irq MESSAGE */ + err = vx_send_irq_dsp(chip, IRQ_MESSAGE); + if (err < 0) { + dev_dbg(chip->card->dev, + "vx_send_msg: send IRQ_MESSAGE error\n"); + return err; + } + + /* Wait for CHK = 1 */ + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + + /* If error, get error value from RX */ + if (vx_inb(chip, ISR) & ISR_ERR) { + err = vx_wait_for_rx_full(chip); + if (err < 0) { + dev_dbg(chip->card->dev, + "vx_send_msg: rx_full read error\n"); + return err; + } + err = vx_inb(chip, RXH) << 16; + err |= vx_inb(chip, RXM) << 8; + err |= vx_inb(chip, RXL); + dev_dbg(chip->card->dev, + "msg got error = 0x%x at cmd[0]\n", err); + err = -(VX_ERR_MASK | err); + return err; + } + + /* Send the other words */ + if (rmh->LgCmd > 1) { + for (i = 1; i < rmh->LgCmd; i++) { + /* Wait for TX ready */ + err = vx_wait_isr_bit(chip, ISR_TX_READY); + if (err < 0) { + dev_dbg(chip->card->dev, + "vx_send_msg: tx_ready error\n"); + return err; + } + + /* Write Cmd[i] */ + vx_outb(chip, TXH, (rmh->Cmd[i] >> 16) & 0xff); + vx_outb(chip, TXM, (rmh->Cmd[i] >> 8) & 0xff); + vx_outb(chip, TXL, rmh->Cmd[i] & 0xff); + + /* Trigger irq MESS_READ_NEXT */ + err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT); + if (err < 0) { + dev_dbg(chip->card->dev, + "vx_send_msg: IRQ_READ_NEXT error\n"); + return err; + } + } + /* Wait for TX empty */ + err = vx_wait_isr_bit(chip, ISR_TX_READY); + if (err < 0) { + dev_dbg(chip->card->dev, + "vx_send_msg: TX_READY error\n"); + return err; + } + /* End of transfer */ + err = vx_transfer_end(chip, IRQ_MESS_READ_END); + if (err < 0) + return err; + } + + return vx_read_status(chip, rmh); +} + + +/* + * vx_send_msg - send a DSP message with mutex + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + * see vx_send_msg_nolock(). + */ +int vx_send_msg(struct vx_core *chip, struct vx_rmh *rmh) +{ + guard(mutex)(&chip->lock); + return vx_send_msg_nolock(chip, rmh); +} + + +/* + * vx_send_rih_nolock - send an RIH to xilinx + * @cmd: the command to send + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * + * this function doesn't call mutex at all. + * + * unlike RMH, no command is sent to DSP. + */ +int vx_send_rih_nolock(struct vx_core *chip, int cmd) +{ + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + err = vx_reset_chk(chip); + if (err < 0) + return err; + /* send the IRQ */ + err = vx_send_irq_dsp(chip, cmd); + if (err < 0) + return err; + /* Wait CHK = 1 */ + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + /* If error, read RX */ + if (vx_inb(chip, ISR) & ISR_ERR) { + err = vx_wait_for_rx_full(chip); + if (err < 0) + return err; + err = vx_inb(chip, RXH) << 16; + err |= vx_inb(chip, RXM) << 8; + err |= vx_inb(chip, RXL); + return -(VX_ERR_MASK | err); + } + return 0; +} + + +/* + * vx_send_rih - send an RIH with mutex + * @cmd: the command to send + * + * see vx_send_rih_nolock(). + */ +int vx_send_rih(struct vx_core *chip, int cmd) +{ + guard(mutex)(&chip->lock); + return vx_send_rih_nolock(chip, cmd); +} + +#define END_OF_RESET_WAIT_TIME 500 /* us */ + +/** + * snd_vx_load_boot_image - boot up the xilinx interface + * @chip: VX core instance + * @boot: the boot record to load + */ +int snd_vx_load_boot_image(struct vx_core *chip, const struct firmware *boot) +{ + unsigned int i; + int no_fillup = vx_has_new_dsp(chip); + + /* check the length of boot image */ + if (boot->size <= 0) + return -EINVAL; + if (boot->size % 3) + return -EINVAL; +#if 0 + { + /* more strict check */ + unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2]; + if (boot->size != (c + 2) * 3) + return -EINVAL; + } +#endif + + /* reset dsp */ + vx_reset_dsp(chip); + + udelay(END_OF_RESET_WAIT_TIME); /* another wait? */ + + /* download boot strap */ + for (i = 0; i < 0x600; i += 3) { + if (i >= boot->size) { + if (no_fillup) + break; + if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) { + dev_err(chip->card->dev, "dsp boot failed at %d\n", i); + return -EIO; + } + vx_outb(chip, TXH, 0); + vx_outb(chip, TXM, 0); + vx_outb(chip, TXL, 0); + } else { + const unsigned char *image = boot->data + i; + if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) { + dev_err(chip->card->dev, "dsp boot failed at %d\n", i); + return -EIO; + } + vx_outb(chip, TXH, image[0]); + vx_outb(chip, TXM, image[1]); + vx_outb(chip, TXL, image[2]); + } + } + return 0; +} + +EXPORT_SYMBOL(snd_vx_load_boot_image); + +/* + * vx_test_irq_src - query the source of interrupts + * + * called from irq handler only + */ +static int vx_test_irq_src(struct vx_core *chip, unsigned int *ret) +{ + int err; + + vx_init_rmh(&chip->irq_rmh, CMD_TEST_IT); + guard(mutex)(&chip->lock); + err = vx_send_msg_nolock(chip, &chip->irq_rmh); + if (err < 0) + *ret = 0; + else + *ret = chip->irq_rmh.Stat[0]; + return err; +} + + +/* + * snd_vx_threaded_irq_handler - threaded irq handler + */ +irqreturn_t snd_vx_threaded_irq_handler(int irq, void *dev) +{ + struct vx_core *chip = dev; + unsigned int events; + + if (chip->chip_status & VX_STAT_IS_STALE) + return IRQ_HANDLED; + + if (vx_test_irq_src(chip, &events) < 0) + return IRQ_HANDLED; + + /* We must prevent any application using this DSP + * and block any further request until the application + * either unregisters or reloads the DSP + */ + if (events & FATAL_DSP_ERROR) { + dev_err(chip->card->dev, "vx_core: fatal DSP error!!\n"); + return IRQ_HANDLED; + } + + /* The start on time code conditions are filled (ie the time code + * received by the board is equal to one of those given to it). + */ + if (events & TIME_CODE_EVENT_PENDING) { + ; /* so far, nothing to do yet */ + } + + /* The frequency has changed on the board (UER mode). */ + if (events & FREQUENCY_CHANGE_EVENT_PENDING) + vx_change_frequency(chip); + + /* update the pcm streams */ + vx_pcm_update_intr(chip, events); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(snd_vx_threaded_irq_handler); + +/** + * snd_vx_irq_handler - interrupt handler + * @irq: irq number + * @dev: VX core instance + */ +irqreturn_t snd_vx_irq_handler(int irq, void *dev) +{ + struct vx_core *chip = dev; + + if (! (chip->chip_status & VX_STAT_CHIP_INIT) || + (chip->chip_status & VX_STAT_IS_STALE)) + return IRQ_NONE; + if (! vx_test_and_ack(chip)) + return IRQ_WAKE_THREAD; + return IRQ_NONE; +} + +EXPORT_SYMBOL(snd_vx_irq_handler); + +/* + */ +static void vx_reset_board(struct vx_core *chip, int cold_reset) +{ + if (snd_BUG_ON(!chip->ops->reset_board)) + return; + + /* current source, later sync'ed with target */ + chip->audio_source = VX_AUDIO_SRC_LINE; + if (cold_reset) { + chip->audio_source_target = chip->audio_source; + chip->clock_source = INTERNAL_QUARTZ; + chip->clock_mode = VX_CLOCK_MODE_AUTO; + chip->freq = 48000; + chip->uer_detected = VX_UER_MODE_NOT_PRESENT; + chip->uer_bits = SNDRV_PCM_DEFAULT_CON_SPDIF; + } + + chip->ops->reset_board(chip, cold_reset); + + vx_reset_codec(chip, cold_reset); + + vx_set_internal_clock(chip, chip->freq); + + /* Reset the DSP */ + vx_reset_dsp(chip); + + if (vx_is_pcmcia(chip)) { + /* Acknowledge any pending IRQ and reset the MEMIRQ flag. */ + vx_test_and_ack(chip); + vx_validate_irq(chip, 1); + } + + /* init CBits */ + vx_set_iec958_status(chip, chip->uer_bits); +} + + +/* + * proc interface + */ + +static void vx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct vx_core *chip = entry->private_data; + static const char * const audio_src_vxp[] = { "Line", "Mic", "Digital" }; + static const char * const audio_src_vx2[] = { "Analog", "Analog", "Digital" }; + static const char * const clock_mode[] = { "Auto", "Internal", "External" }; + static const char * const clock_src[] = { "Internal", "External" }; + static const char * const uer_type[] = { "Consumer", "Professional", "Not Present" }; + + snd_iprintf(buffer, "%s\n", chip->card->longname); + snd_iprintf(buffer, "Xilinx Firmware: %s\n", + (chip->chip_status & VX_STAT_XILINX_LOADED) ? "Loaded" : "No"); + snd_iprintf(buffer, "Device Initialized: %s\n", + (chip->chip_status & VX_STAT_DEVICE_INIT) ? "Yes" : "No"); + snd_iprintf(buffer, "DSP audio info:"); + if (chip->audio_info & VX_AUDIO_INFO_REAL_TIME) + snd_iprintf(buffer, " realtime"); + if (chip->audio_info & VX_AUDIO_INFO_OFFLINE) + snd_iprintf(buffer, " offline"); + if (chip->audio_info & VX_AUDIO_INFO_MPEG1) + snd_iprintf(buffer, " mpeg1"); + if (chip->audio_info & VX_AUDIO_INFO_MPEG2) + snd_iprintf(buffer, " mpeg2"); + if (chip->audio_info & VX_AUDIO_INFO_LINEAR_8) + snd_iprintf(buffer, " linear8"); + if (chip->audio_info & VX_AUDIO_INFO_LINEAR_16) + snd_iprintf(buffer, " linear16"); + if (chip->audio_info & VX_AUDIO_INFO_LINEAR_24) + snd_iprintf(buffer, " linear24"); + snd_iprintf(buffer, "\n"); + snd_iprintf(buffer, "Input Source: %s\n", vx_is_pcmcia(chip) ? + audio_src_vxp[chip->audio_source] : + audio_src_vx2[chip->audio_source]); + snd_iprintf(buffer, "Clock Mode: %s\n", clock_mode[chip->clock_mode]); + snd_iprintf(buffer, "Clock Source: %s\n", clock_src[chip->clock_source]); + snd_iprintf(buffer, "Frequency: %d\n", chip->freq); + snd_iprintf(buffer, "Detected Frequency: %d\n", chip->freq_detected); + snd_iprintf(buffer, "Detected UER type: %s\n", uer_type[chip->uer_detected]); + snd_iprintf(buffer, "Min/Max/Cur IBL: %d/%d/%d (granularity=%d)\n", + chip->ibl.min_size, chip->ibl.max_size, chip->ibl.size, + chip->ibl.granularity); +} + +static void vx_proc_init(struct vx_core *chip) +{ + snd_card_ro_proc_new(chip->card, "vx-status", chip, vx_proc_read); +} + + +/** + * snd_vx_dsp_boot - load the DSP boot + * @chip: VX core instance + * @boot: firmware data + */ +int snd_vx_dsp_boot(struct vx_core *chip, const struct firmware *boot) +{ + int err; + int cold_reset = !(chip->chip_status & VX_STAT_DEVICE_INIT); + + vx_reset_board(chip, cold_reset); + vx_validate_irq(chip, 0); + + err = snd_vx_load_boot_image(chip, boot); + if (err < 0) + return err; + msleep(10); + + return 0; +} + +EXPORT_SYMBOL(snd_vx_dsp_boot); + +/** + * snd_vx_dsp_load - load the DSP image + * @chip: VX core instance + * @dsp: firmware data + */ +int snd_vx_dsp_load(struct vx_core *chip, const struct firmware *dsp) +{ + unsigned int i; + int err; + unsigned int csum = 0; + const unsigned char *image, *cptr; + + if (dsp->size % 3) + return -EINVAL; + + vx_toggle_dac_mute(chip, 1); + + /* Transfert data buffer from PC to DSP */ + for (i = 0; i < dsp->size; i += 3) { + image = dsp->data + i; + /* Wait DSP ready for a new read */ + err = vx_wait_isr_bit(chip, ISR_TX_EMPTY); + if (err < 0) { + dev_err(chip->card->dev, + "dsp loading error at position %d\n", i); + return err; + } + cptr = image; + csum ^= *cptr; + csum = (csum >> 24) | (csum << 8); + vx_outb(chip, TXH, *cptr++); + csum ^= *cptr; + csum = (csum >> 24) | (csum << 8); + vx_outb(chip, TXM, *cptr++); + csum ^= *cptr; + csum = (csum >> 24) | (csum << 8); + vx_outb(chip, TXL, *cptr++); + } + + msleep(200); + + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + + vx_toggle_dac_mute(chip, 0); + + vx_test_and_ack(chip); + vx_validate_irq(chip, 1); + + return 0; +} + +EXPORT_SYMBOL(snd_vx_dsp_load); + +#ifdef CONFIG_PM +/* + * suspend + */ +int snd_vx_suspend(struct vx_core *chip) +{ + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + chip->chip_status |= VX_STAT_IN_SUSPEND; + + return 0; +} + +EXPORT_SYMBOL(snd_vx_suspend); + +/* + * resume + */ +int snd_vx_resume(struct vx_core *chip) +{ + int i, err; + + chip->chip_status &= ~VX_STAT_CHIP_INIT; + + for (i = 0; i < 4; i++) { + if (! chip->firmware[i]) + continue; + err = chip->ops->load_dsp(chip, i, chip->firmware[i]); + if (err < 0) { + dev_err(chip->card->dev, + "vx: firmware resume error at DSP %d\n", i); + return -EIO; + } + } + + chip->chip_status |= VX_STAT_CHIP_INIT; + chip->chip_status &= ~VX_STAT_IN_SUSPEND; + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + return 0; +} + +EXPORT_SYMBOL(snd_vx_resume); +#endif + +static void snd_vx_release(struct device *dev, void *data) +{ + snd_vx_free_firmware(data); +} + +/** + * snd_vx_create - constructor for struct vx_core + * @card: card instance + * @hw: hardware specific record + * @ops: VX ops pointer + * @extra_size: extra byte size to allocate appending to chip + * + * this function allocates the instance and prepare for the hardware + * initialization. + * + * The object is managed via devres, and will be automatically released. + * + * return the instance pointer if successful, NULL in error. + */ +struct vx_core *snd_vx_create(struct snd_card *card, + const struct snd_vx_hardware *hw, + const struct snd_vx_ops *ops, + int extra_size) +{ + struct vx_core *chip; + + if (snd_BUG_ON(!card || !hw || !ops)) + return NULL; + + chip = devres_alloc(snd_vx_release, sizeof(*chip) + extra_size, + GFP_KERNEL); + if (!chip) + return NULL; + mutex_init(&chip->lock); + chip->irq = -1; + chip->hw = hw; + chip->type = hw->type; + chip->ops = ops; + mutex_init(&chip->mixer_mutex); + + chip->card = card; + card->private_data = chip; + strscpy(card->driver, hw->name); + sprintf(card->shortname, "Digigram %s", hw->name); + + vx_proc_init(chip); + + return chip; +} + +EXPORT_SYMBOL(snd_vx_create); diff --git a/sound/drivers/vx/vx_hwdep.c b/sound/drivers/vx/vx_hwdep.c new file mode 100644 index 000000000..a7f8ddf4d --- /dev/null +++ b/sound/drivers/vx/vx_hwdep.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * DSP firmware management + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hwdep.h> +#include <sound/vx_core.h> + +MODULE_FIRMWARE("vx/bx_1_vxp.b56"); +MODULE_FIRMWARE("vx/bx_1_vp4.b56"); +MODULE_FIRMWARE("vx/x1_1_vx2.xlx"); +MODULE_FIRMWARE("vx/x1_2_v22.xlx"); +MODULE_FIRMWARE("vx/x1_1_vxp.xlx"); +MODULE_FIRMWARE("vx/x1_1_vp4.xlx"); +MODULE_FIRMWARE("vx/bd56002.boot"); +MODULE_FIRMWARE("vx/bd563v2.boot"); +MODULE_FIRMWARE("vx/bd563s3.boot"); +MODULE_FIRMWARE("vx/l_1_vx2.d56"); +MODULE_FIRMWARE("vx/l_1_v22.d56"); +MODULE_FIRMWARE("vx/l_1_vxp.d56"); +MODULE_FIRMWARE("vx/l_1_vp4.d56"); + +int snd_vx_setup_firmware(struct vx_core *chip) +{ + static const char * const fw_files[VX_TYPE_NUMS][4] = { + [VX_TYPE_BOARD] = { + NULL, "x1_1_vx2.xlx", "bd56002.boot", "l_1_vx2.d56", + }, + [VX_TYPE_V2] = { + NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56", + }, + [VX_TYPE_MIC] = { + NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56", + }, + [VX_TYPE_VXPOCKET] = { + "bx_1_vxp.b56", "x1_1_vxp.xlx", "bd563s3.boot", "l_1_vxp.d56" + }, + [VX_TYPE_VXP440] = { + "bx_1_vp4.b56", "x1_1_vp4.xlx", "bd563s3.boot", "l_1_vp4.d56" + }, + }; + + int i, err; + + for (i = 0; i < 4; i++) { + char path[32]; + const struct firmware *fw; + if (! fw_files[chip->type][i]) + continue; + sprintf(path, "vx/%s", fw_files[chip->type][i]); + if (request_firmware(&fw, path, chip->card->dev)) { + dev_err(chip->card->dev, "vx: can't load firmware %s\n", path); + return -ENOENT; + } + err = chip->ops->load_dsp(chip, i, fw); + if (err < 0) { + release_firmware(fw); + return err; + } + if (i == 1) + chip->chip_status |= VX_STAT_XILINX_LOADED; +#ifdef CONFIG_PM + chip->firmware[i] = fw; +#else + release_firmware(fw); +#endif + } + + /* ok, we reached to the last one */ + /* create the devices if not built yet */ + err = snd_vx_pcm_new(chip); + if (err < 0) + return err; + + err = snd_vx_mixer_new(chip); + if (err < 0) + return err; + + if (chip->ops->add_controls) { + err = chip->ops->add_controls(chip); + if (err < 0) + return err; + } + + chip->chip_status |= VX_STAT_DEVICE_INIT; + chip->chip_status |= VX_STAT_CHIP_INIT; + + return snd_card_register(chip->card); +} + +/* exported */ +void snd_vx_free_firmware(struct vx_core *chip) +{ +#ifdef CONFIG_PM + int i; + for (i = 0; i < 4; i++) + release_firmware(chip->firmware[i]); +#endif +} + +EXPORT_SYMBOL(snd_vx_setup_firmware); +EXPORT_SYMBOL(snd_vx_free_firmware); diff --git a/sound/drivers/vx/vx_mixer.c b/sound/drivers/vx/vx_mixer.c new file mode 100644 index 000000000..9dc5cecaa --- /dev/null +++ b/sound/drivers/vx/vx_mixer.c @@ -0,0 +1,982 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * Common mixer part + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + + +/* + * write a codec data (24bit) + */ +static void vx_write_codec_reg(struct vx_core *chip, int codec, unsigned int data) +{ + if (snd_BUG_ON(!chip->ops->write_codec)) + return; + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + guard(mutex)(&chip->lock); + chip->ops->write_codec(chip, codec, data); +} + +/* + * Data type used to access the Codec + */ +union vx_codec_data { + u32 l; +#ifdef SNDRV_BIG_ENDIAN + struct w { + u16 h; + u16 l; + } w; + struct b { + u8 hh; + u8 mh; + u8 ml; + u8 ll; + } b; +#else /* LITTLE_ENDIAN */ + struct w { + u16 l; + u16 h; + } w; + struct b { + u8 ll; + u8 ml; + u8 mh; + u8 hh; + } b; +#endif +}; + +#define SET_CDC_DATA_SEL(di,s) ((di).b.mh = (u8) (s)) +#define SET_CDC_DATA_REG(di,r) ((di).b.ml = (u8) (r)) +#define SET_CDC_DATA_VAL(di,d) ((di).b.ll = (u8) (d)) +#define SET_CDC_DATA_INIT(di) ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR)) + +/* + * set up codec register and write the value + * @codec: the codec id, 0 or 1 + * @reg: register index + * @val: data value + */ +static void vx_set_codec_reg(struct vx_core *chip, int codec, int reg, int val) +{ + union vx_codec_data data; + /* DAC control register */ + SET_CDC_DATA_INIT(data); + SET_CDC_DATA_REG(data, reg); + SET_CDC_DATA_VAL(data, val); + vx_write_codec_reg(chip, codec, data.l); +} + + +/* + * vx_set_analog_output_level - set the output attenuation level + * @codec: the output codec, 0 or 1. (1 for VXP440 only) + * @left: left output level, 0 = mute + * @right: right output level + */ +static void vx_set_analog_output_level(struct vx_core *chip, int codec, int left, int right) +{ + left = chip->hw->output_level_max - left; + right = chip->hw->output_level_max - right; + + if (chip->ops->akm_write) { + chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left); + chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right); + } else { + /* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */ + vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left); + vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right); + } +} + + +/* + * vx_toggle_dac_mute - mute/unmute DAC + * @mute: 0 = unmute, 1 = mute + */ + +#define DAC_ATTEN_MIN 0x08 +#define DAC_ATTEN_MAX 0x38 + +void vx_toggle_dac_mute(struct vx_core *chip, int mute) +{ + unsigned int i; + for (i = 0; i < chip->hw->num_codecs; i++) { + if (chip->ops->akm_write) + chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */ + else + vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, + mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN); + } +} + +/* + * vx_reset_codec - reset and initialize the codecs + */ +void vx_reset_codec(struct vx_core *chip, int cold_reset) +{ + unsigned int i; + int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65; + + chip->ops->reset_codec(chip); + + /* AKM codecs should be initialized in reset_codec callback */ + if (! chip->ops->akm_write) { + /* initialize old codecs */ + for (i = 0; i < chip->hw->num_codecs; i++) { + /* DAC control register (change level when zero crossing + mute) */ + vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX); + /* ADC control register */ + vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00); + /* Port mode register */ + vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port); + /* Clock control register */ + vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00); + } + } + + /* mute analog output */ + for (i = 0; i < chip->hw->num_codecs; i++) { + chip->output_level[i][0] = 0; + chip->output_level[i][1] = 0; + vx_set_analog_output_level(chip, i, 0, 0); + } +} + +/* + * change the audio input source + * @src: the target source (VX_AUDIO_SRC_XXX) + */ +static void vx_change_audio_source(struct vx_core *chip, int src) +{ + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + guard(mutex)(&chip->lock); + chip->ops->change_audio_source(chip, src); +} + + +/* + * change the audio source if necessary and possible + * returns 1 if the source is actually changed. + */ +int vx_sync_audio_source(struct vx_core *chip) +{ + if (chip->audio_source_target == chip->audio_source || + chip->pcm_running) + return 0; + vx_change_audio_source(chip, chip->audio_source_target); + chip->audio_source = chip->audio_source_target; + return 1; +} + + +/* + * audio level, mute, monitoring + */ +struct vx_audio_level { + unsigned int has_level: 1; + unsigned int has_monitor_level: 1; + unsigned int has_mute: 1; + unsigned int has_monitor_mute: 1; + unsigned int mute; + unsigned int monitor_mute; + short level; + short monitor_level; +}; + +static int vx_adjust_audio_level(struct vx_core *chip, int audio, int capture, + struct vx_audio_level *info) +{ + struct vx_rmh rmh; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + vx_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST); + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + /* Add Audio IO mask */ + rmh.Cmd[1] = 1 << audio; + rmh.Cmd[2] = 0; + if (info->has_level) { + rmh.Cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL; + rmh.Cmd[2] |= info->level; + } + if (info->has_monitor_level) { + rmh.Cmd[0] |= VALID_AUDIO_IO_MONITORING_LEVEL; + rmh.Cmd[2] |= ((unsigned int)info->monitor_level << 10); + } + if (info->has_mute) { + rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_LEVEL; + if (info->mute) + rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_LEVEL; + } + if (info->has_monitor_mute) { + /* validate flag for M2 at least to unmute it */ + rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_MONITORING_1 | VALID_AUDIO_IO_MUTE_MONITORING_2; + if (info->monitor_mute) + rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_MONITORING_1; + } + + return vx_send_msg(chip, &rmh); +} + + +#if 0 // not used +static int vx_read_audio_level(struct vx_core *chip, int audio, int capture, + struct vx_audio_level *info) +{ + int err; + struct vx_rmh rmh; + + memset(info, 0, sizeof(*info)); + vx_init_rmh(&rmh, CMD_GET_AUDIO_LEVELS); + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + /* Add Audio IO mask */ + rmh.Cmd[1] = 1 << audio; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + info.level = rmh.Stat[0] & MASK_DSP_WORD_LEVEL; + info.monitor_level = (rmh.Stat[0] >> 10) & MASK_DSP_WORD_LEVEL; + info.mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_LEVEL) ? 1 : 0; + info.monitor_mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_MONITORING_1) ? 1 : 0; + return 0; +} +#endif // not used + +/* + * set the monitoring level and mute state of the given audio + * no more static, because must be called from vx_pcm to demute monitoring + */ +int vx_set_monitor_level(struct vx_core *chip, int audio, int level, int active) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_monitor_level = 1; + info.monitor_level = level; + info.has_monitor_mute = 1; + info.monitor_mute = !active; + chip->audio_monitor[audio] = level; + chip->audio_monitor_active[audio] = active; + return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */ +} + + +/* + * set the mute status of the given audio + */ +static int vx_set_audio_switch(struct vx_core *chip, int audio, int active) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_mute = 1; + info.mute = !active; + chip->audio_active[audio] = active; + return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */ +} + +/* + * set the mute status of the given audio + */ +static int vx_set_audio_gain(struct vx_core *chip, int audio, int capture, int level) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_level = 1; + info.level = level; + chip->audio_gain[capture][audio] = level; + return vx_adjust_audio_level(chip, audio, capture, &info); +} + +/* + * reset all audio levels + */ +static void vx_reset_audio_levels(struct vx_core *chip) +{ + unsigned int i, c; + struct vx_audio_level info; + + memset(chip->audio_gain, 0, sizeof(chip->audio_gain)); + memset(chip->audio_active, 0, sizeof(chip->audio_active)); + memset(chip->audio_monitor, 0, sizeof(chip->audio_monitor)); + memset(chip->audio_monitor_active, 0, sizeof(chip->audio_monitor_active)); + + for (c = 0; c < 2; c++) { + for (i = 0; i < chip->hw->num_ins * 2; i++) { + memset(&info, 0, sizeof(info)); + if (c == 0) { + info.has_monitor_level = 1; + info.has_mute = 1; + info.has_monitor_mute = 1; + } + info.has_level = 1; + info.level = CVAL_0DB; /* default: 0dB */ + vx_adjust_audio_level(chip, i, c, &info); + chip->audio_gain[c][i] = CVAL_0DB; + chip->audio_monitor[i] = CVAL_0DB; + } + } +} + + +/* + * VU, peak meter record + */ + +#define VU_METER_CHANNELS 2 + +struct vx_vu_meter { + int saturated; + int vu_level; + int peak_level; +}; + +/* + * get the VU and peak meter values + * @audio: the audio index + * @capture: 0 = playback, 1 = capture operation + * @info: the array of vx_vu_meter records (size = 2). + */ +static int vx_get_audio_vu_meter(struct vx_core *chip, int audio, int capture, struct vx_vu_meter *info) +{ + struct vx_rmh rmh; + int i, err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + vx_init_rmh(&rmh, CMD_AUDIO_VU_PIC_METER); + rmh.LgStat += 2 * VU_METER_CHANNELS; + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + + /* Add Audio IO mask */ + rmh.Cmd[1] = 0; + for (i = 0; i < VU_METER_CHANNELS; i++) + rmh.Cmd[1] |= 1 << (audio + i); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + /* Read response */ + for (i = 0; i < 2 * VU_METER_CHANNELS; i +=2) { + info->saturated = (rmh.Stat[0] & (1 << (audio + i))) ? 1 : 0; + info->vu_level = rmh.Stat[i + 1]; + info->peak_level = rmh.Stat[i + 2]; + info++; + } + return 0; +} + + +/* + * control API entries + */ + +/* + * output level control + */ +static int vx_output_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = chip->hw->output_level_max; + return 0; +} + +static int vx_output_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->id.index; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->output_level[codec][0]; + ucontrol->value.integer.value[1] = chip->output_level[codec][1]; + return 0; +} + +static int vx_output_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->id.index; + unsigned int val[2], vmax; + + vmax = chip->hw->output_level_max; + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > vmax || val[1] > vmax) + return -EINVAL; + guard(mutex)(&chip->mixer_mutex); + if (val[0] != chip->output_level[codec][0] || + val[1] != chip->output_level[codec][1]) { + vx_set_analog_output_level(chip, codec, val[0], val[1]); + chip->output_level[codec][0] = val[0]; + chip->output_level[codec][1] = val[1]; + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new vx_control_output_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = vx_output_level_info, + .get = vx_output_level_get, + .put = vx_output_level_put, + /* tlv will be filled later */ +}; + +/* + * audio source select + */ +static int vx_audio_src_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts_mic[3] = { + "Digital", "Line", "Mic" + }; + static const char * const texts_vx2[2] = { + "Digital", "Analog" + }; + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (chip->type >= VX_TYPE_VXPOCKET) + return snd_ctl_enum_info(uinfo, 1, 3, texts_mic); + else + return snd_ctl_enum_info(uinfo, 1, 2, texts_vx2); +} + +static int vx_audio_src_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->audio_source_target; + return 0; +} + +static int vx_audio_src_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (chip->type >= VX_TYPE_VXPOCKET) { + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + } else { + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + } + guard(mutex)(&chip->mixer_mutex); + if (chip->audio_source_target != ucontrol->value.enumerated.item[0]) { + chip->audio_source_target = ucontrol->value.enumerated.item[0]; + vx_sync_audio_source(chip); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new vx_control_audio_src = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = vx_audio_src_info, + .get = vx_audio_src_get, + .put = vx_audio_src_put, +}; + +/* + * clock mode selection + */ +static int vx_clock_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Auto", "Internal", "External" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int vx_clock_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->clock_mode; + return 0; +} + +static int vx_clock_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + guard(mutex)(&chip->mixer_mutex); + if (chip->clock_mode != ucontrol->value.enumerated.item[0]) { + chip->clock_mode = ucontrol->value.enumerated.item[0]; + vx_set_clock(chip, chip->freq); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new vx_control_clock_mode = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Mode", + .info = vx_clock_mode_info, + .get = vx_clock_mode_get, + .put = vx_clock_mode_put, +}; + +/* + * Audio Gain + */ +static int vx_audio_gain_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = CVAL_MAX; + return 0; +} + +static int vx_audio_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio]; + ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1]; + return 0; +} + +static int vx_audio_gain_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + unsigned int val[2]; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > CVAL_MAX || val[1] > CVAL_MAX) + return -EINVAL; + guard(mutex)(&chip->mixer_mutex); + if (val[0] != chip->audio_gain[capture][audio] || + val[1] != chip->audio_gain[capture][audio+1]) { + vx_set_audio_gain(chip, audio, capture, val[0]); + vx_set_audio_gain(chip, audio+1, capture, val[1]); + return 1; + } + return 0; +} + +static int vx_audio_monitor_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1]; + return 0; +} + +static int vx_audio_monitor_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + unsigned int val[2]; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > CVAL_MAX || val[1] > CVAL_MAX) + return -EINVAL; + + guard(mutex)(&chip->mixer_mutex); + if (val[0] != chip->audio_monitor[audio] || + val[1] != chip->audio_monitor[audio+1]) { + vx_set_monitor_level(chip, audio, val[0], + chip->audio_monitor_active[audio]); + vx_set_monitor_level(chip, audio+1, val[1], + chip->audio_monitor_active[audio+1]); + return 1; + } + return 0; +} + +#define vx_audio_sw_info snd_ctl_boolean_stereo_info + +static int vx_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_active[audio+1]; + return 0; +} + +static int vx_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + guard(mutex)(&chip->mixer_mutex); + if (ucontrol->value.integer.value[0] != chip->audio_active[audio] || + ucontrol->value.integer.value[1] != chip->audio_active[audio+1]) { + vx_set_audio_switch(chip, audio, + !!ucontrol->value.integer.value[0]); + vx_set_audio_switch(chip, audio+1, + !!ucontrol->value.integer.value[1]); + return 1; + } + return 0; +} + +static int vx_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1]; + return 0; +} + +static int vx_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + guard(mutex)(&chip->mixer_mutex); + if (ucontrol->value.integer.value[0] != chip->audio_monitor_active[audio] || + ucontrol->value.integer.value[1] != chip->audio_monitor_active[audio+1]) { + vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], + !!ucontrol->value.integer.value[0]); + vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], + !!ucontrol->value.integer.value[1]); + return 1; + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_audio_gain, -10975, 25, 0); + +static const struct snd_kcontrol_new vx_control_audio_gain = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + .info = vx_audio_gain_info, + .get = vx_audio_gain_get, + .put = vx_audio_gain_put, + .tlv = { .p = db_scale_audio_gain }, +}; +static const struct snd_kcontrol_new vx_control_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = vx_audio_sw_info, + .get = vx_audio_sw_get, + .put = vx_audio_sw_put +}; +static const struct snd_kcontrol_new vx_control_monitor_gain = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Volume", + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = vx_audio_gain_info, /* shared */ + .get = vx_audio_monitor_get, + .put = vx_audio_monitor_put, + .tlv = { .p = db_scale_audio_gain }, +}; +static const struct snd_kcontrol_new vx_control_monitor_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Switch", + .info = vx_audio_sw_info, /* shared */ + .get = vx_monitor_sw_get, + .put = vx_monitor_sw_put +}; + + +/* + * IEC958 status bits + */ +static int vx_iec958_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int vx_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.iec958.status[0] = (chip->uer_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (chip->uer_bits >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (chip->uer_bits >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (chip->uer_bits >> 24) & 0xff; + return 0; +} + +static int vx_iec958_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static int vx_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + unsigned int val; + + val = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + guard(mutex)(&chip->mixer_mutex); + if (chip->uer_bits != val) { + chip->uer_bits = val; + vx_set_iec958_status(chip, val); + return 1; + } + return 0; +} + +static const struct snd_kcontrol_new vx_control_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = vx_iec958_info, /* shared */ + .get = vx_iec958_mask_get, +}; + +static const struct snd_kcontrol_new vx_control_iec958 = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = vx_iec958_info, + .get = vx_iec958_get, + .put = vx_iec958_put +}; + + +/* + * VU meter + */ + +#define METER_MAX 0xff +#define METER_SHIFT 16 + +static int vx_vu_meter_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = METER_MAX; + return 0; +} + +static int vx_vu_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + vx_get_audio_vu_meter(chip, audio, capture, meter); + ucontrol->value.integer.value[0] = meter[0].vu_level >> METER_SHIFT; + ucontrol->value.integer.value[1] = meter[1].vu_level >> METER_SHIFT; + return 0; +} + +static int vx_peak_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + vx_get_audio_vu_meter(chip, audio, capture, meter); + ucontrol->value.integer.value[0] = meter[0].peak_level >> METER_SHIFT; + ucontrol->value.integer.value[1] = meter[1].peak_level >> METER_SHIFT; + return 0; +} + +#define vx_saturation_info snd_ctl_boolean_stereo_info + +static int vx_saturation_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + + vx_get_audio_vu_meter(chip, audio, 1, meter); /* capture only */ + ucontrol->value.integer.value[0] = meter[0].saturated; + ucontrol->value.integer.value[1] = meter[1].saturated; + return 0; +} + +static const struct snd_kcontrol_new vx_control_vu_meter = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + /* name will be filled later */ + .info = vx_vu_meter_info, + .get = vx_vu_meter_get, +}; + +static const struct snd_kcontrol_new vx_control_peak_meter = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + /* name will be filled later */ + .info = vx_vu_meter_info, /* shared */ + .get = vx_peak_meter_get, +}; + +static const struct snd_kcontrol_new vx_control_saturation = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Saturation", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = vx_saturation_info, + .get = vx_saturation_get, +}; + + + +/* + * + */ + +int snd_vx_mixer_new(struct vx_core *chip) +{ + unsigned int i, c; + int err; + struct snd_kcontrol_new temp; + struct snd_card *card = chip->card; + char name[32]; + + strscpy(card->mixername, card->driver); + + /* output level controls */ + for (i = 0; i < chip->hw->num_outs; i++) { + temp = vx_control_output_level; + temp.index = i; + temp.tlv.p = chip->hw->output_level_db_scale; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + + /* PCM volumes, switches, monitoring */ + for (i = 0; i < chip->hw->num_outs; i++) { + int val = i * 2; + temp = vx_control_audio_gain; + temp.index = i; + temp.name = "PCM Playback Volume"; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + temp = vx_control_output_switch; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + temp = vx_control_monitor_gain; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + temp = vx_control_monitor_switch; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + for (i = 0; i < chip->hw->num_outs; i++) { + temp = vx_control_audio_gain; + temp.index = i; + temp.name = "PCM Capture Volume"; + temp.private_value = (i * 2) | (1 << 8); + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + + /* Audio source */ + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_audio_src, chip)); + if (err < 0) + return err; + /* clock mode */ + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_clock_mode, chip)); + if (err < 0) + return err; + /* IEC958 controls */ + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958_mask, chip)); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958, chip)); + if (err < 0) + return err; + /* VU, peak, saturation meters */ + for (c = 0; c < 2; c++) { + static const char * const dir[2] = { "Output", "Input" }; + for (i = 0; i < chip->hw->num_ins; i++) { + int val = (i * 2) | (c << 8); + if (c == 1) { + temp = vx_control_saturation; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + sprintf(name, "%s VU Meter", dir[c]); + temp = vx_control_vu_meter; + temp.index = i; + temp.name = name; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + sprintf(name, "%s Peak Meter", dir[c]); + temp = vx_control_peak_meter; + temp.index = i; + temp.name = name; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + } + vx_reset_audio_levels(chip); + return 0; +} diff --git a/sound/drivers/vx/vx_pcm.c b/sound/drivers/vx/vx_pcm.c new file mode 100644 index 000000000..d9b945807 --- /dev/null +++ b/sound/drivers/vx/vx_pcm.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * PCM part + * + * Copyright (c) 2002,2003 by Takashi Iwai <tiwai@suse.de> + * + * STRATEGY + * for playback, we send series of "chunks", which size is equal with the + * IBL size, typically 126 samples. at each end of chunk, the end-of-buffer + * interrupt is notified, and the interrupt handler will feed the next chunk. + * + * the current position is calculated from the sample count RMH. + * pipe->transferred is the counter of data which has been already transferred. + * if this counter reaches to the period size, snd_pcm_period_elapsed() will + * be issued. + * + * for capture, the situation is much easier. + * to get a low latency response, we'll check the capture streams at each + * interrupt (capture stream has no EOB notification). if the pending + * data is accumulated to the period size, snd_pcm_period_elapsed() is + * called and the pointer is updated. + * + * the current point of read buffer is kept in pipe->hw_ptr. note that + * this is in bytes. + * + * TODO + * - linked trigger for full-duplex mode. + * - scheduled action on the stream. + */ + +#include <linux/slab.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/asoundef.h> +#include <sound/pcm.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + + +/* + * read three pending pcm bytes via inb() + */ +static void vx_pcm_read_per_bytes(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe) +{ + int offset = pipe->hw_ptr; + unsigned char *buf = (unsigned char *)(runtime->dma_area + offset); + *buf++ = vx_inb(chip, RXH); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + *buf++ = vx_inb(chip, RXM); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + *buf++ = vx_inb(chip, RXL); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + } + pipe->hw_ptr = offset; +} + +/* + * vx_set_pcx_time - convert from the PC time to the RMH status time. + * @pc_time: the pointer for the PC-time to set + * @dsp_time: the pointer for RMH status time array + */ +static void vx_set_pcx_time(struct vx_core *chip, pcx_time_t *pc_time, + unsigned int *dsp_time) +{ + dsp_time[0] = (unsigned int)((*pc_time) >> 24) & PCX_TIME_HI_MASK; + dsp_time[1] = (unsigned int)(*pc_time) & MASK_DSP_WORD; +} + +/* + * vx_set_differed_time - set the differed time if specified + * @rmh: the rmh record to modify + * @pipe: the pipe to be checked + * + * if the pipe is programmed with the differed time, set the DSP time + * on the rmh and changes its command length. + * + * returns the increase of the command length. + */ +static int vx_set_differed_time(struct vx_core *chip, struct vx_rmh *rmh, + struct vx_pipe *pipe) +{ + /* Update The length added to the RMH command by the timestamp */ + if (! (pipe->differed_type & DC_DIFFERED_DELAY)) + return 0; + + /* Set the T bit */ + rmh->Cmd[0] |= DSP_DIFFERED_COMMAND_MASK; + + /* Time stamp is the 1st following parameter */ + vx_set_pcx_time(chip, &pipe->pcx_time, &rmh->Cmd[1]); + + /* Add the flags to a notified differed command */ + if (pipe->differed_type & DC_NOTIFY_DELAY) + rmh->Cmd[1] |= NOTIFY_MASK_TIME_HIGH ; + + /* Add the flags to a multiple differed command */ + if (pipe->differed_type & DC_MULTIPLE_DELAY) + rmh->Cmd[1] |= MULTIPLE_MASK_TIME_HIGH; + + /* Add the flags to a stream-time differed command */ + if (pipe->differed_type & DC_STREAM_TIME_DELAY) + rmh->Cmd[1] |= STREAM_MASK_TIME_HIGH; + + rmh->LgCmd += 2; + return 2; +} + +/* + * vx_set_stream_format - send the stream format command + * @pipe: the affected pipe + * @data: format bitmask + */ +static int vx_set_stream_format(struct vx_core *chip, struct vx_pipe *pipe, + unsigned int data) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, pipe->is_capture ? + CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); + rmh.Cmd[0] |= pipe->number << FIELD_SIZE; + + /* Command might be longer since we may have to add a timestamp */ + vx_set_differed_time(chip, &rmh, pipe); + + rmh.Cmd[rmh.LgCmd] = (data & 0xFFFFFF00) >> 8; + rmh.Cmd[rmh.LgCmd + 1] = (data & 0xFF) << 16 /*| (datal & 0xFFFF00) >> 8*/; + rmh.LgCmd += 2; + + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_set_format - set the format of a pipe + * @pipe: the affected pipe + * @runtime: pcm runtime instance to be referred + * + * returns 0 if successful, or a negative error code. + */ +static int vx_set_format(struct vx_core *chip, struct vx_pipe *pipe, + struct snd_pcm_runtime *runtime) +{ + unsigned int header = HEADER_FMT_BASE; + + if (runtime->channels == 1) + header |= HEADER_FMT_MONO; + if (snd_pcm_format_little_endian(runtime->format)) + header |= HEADER_FMT_INTEL; + if (runtime->rate < 32000 && runtime->rate > 11025) + header |= HEADER_FMT_UPTO32; + else if (runtime->rate <= 11025) + header |= HEADER_FMT_UPTO11; + + switch (snd_pcm_format_physical_width(runtime->format)) { + // case 8: break; + case 16: header |= HEADER_FMT_16BITS; break; + case 24: header |= HEADER_FMT_24BITS; break; + default : + snd_BUG(); + return -EINVAL; + } + + return vx_set_stream_format(chip, pipe, header); +} + +/* + * set / query the IBL size + */ +static int vx_set_ibl(struct vx_core *chip, struct vx_ibl_info *info) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_IBL); + rmh.Cmd[0] |= info->size & 0x03ffff; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + info->size = rmh.Stat[0]; + info->max_size = rmh.Stat[1]; + info->min_size = rmh.Stat[2]; + info->granularity = rmh.Stat[3]; + dev_dbg(chip->card->dev, + "%s: size = %d, max = %d, min = %d, gran = %d\n", + __func__, info->size, info->max_size, info->min_size, + info->granularity); + return 0; +} + + +/* + * vx_get_pipe_state - get the state of a pipe + * @pipe: the pipe to be checked + * @state: the pointer for the returned state + * + * checks the state of a given pipe, and stores the state (1 = running, + * 0 = paused) on the given pointer. + * + * called from trigger callback only + */ +static int vx_get_pipe_state(struct vx_core *chip, struct vx_pipe *pipe, int *state) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_PIPE_STATE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (! err) + *state = (rmh.Stat[0] & (1 << pipe->number)) ? 1 : 0; + return err; +} + + +/* + * vx_query_hbuffer_size - query available h-buffer size in bytes + * @pipe: the pipe to be checked + * + * return the available size on h-buffer in bytes, + * or a negative error code. + * + * NOTE: calling this function always switches to the stream mode. + * you'll need to disconnect the host to get back to the + * normal mode. + */ +static int vx_query_hbuffer_size(struct vx_core *chip, struct vx_pipe *pipe) +{ + int result; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_SIZE_HBUFFER); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + if (pipe->is_capture) + rmh.Cmd[0] |= 0x00000001; + result = vx_send_msg(chip, &rmh); + if (! result) + result = rmh.Stat[0] & 0xffff; + return result; +} + + +/* + * vx_pipe_can_start - query whether a pipe is ready for start + * @pipe: the pipe to be checked + * + * return 1 if ready, 0 if not ready, and negative value on error. + * + * called from trigger callback only + */ +static int vx_pipe_can_start(struct vx_core *chip, struct vx_pipe *pipe) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_CAN_START_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + rmh.Cmd[0] |= 1; + + err = vx_send_msg(chip, &rmh); + if (! err) { + if (rmh.Stat[0]) + err = 1; + } + return err; +} + +/* + * vx_conf_pipe - tell the pipe to stand by and wait for IRQA. + * @pipe: the pipe to be configured + */ +static int vx_conf_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_CONF_PIPE); + if (pipe->is_capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + rmh.Cmd[1] = 1 << pipe->number; + return vx_send_msg(chip, &rmh); +} + +/* + * vx_send_irqa - trigger IRQA + */ +static int vx_send_irqa(struct vx_core *chip) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_SEND_IRQA); + return vx_send_msg(chip, &rmh); +} + + +#define MAX_WAIT_FOR_DSP 250 +/* + * vx boards do not support inter-card sync, besides + * only 126 samples require to be prepared before a pipe can start + */ +#define CAN_START_DELAY 2 /* wait 2ms only before asking if the pipe is ready*/ +#define WAIT_STATE_DELAY 2 /* wait 2ms after irqA was requested and check if the pipe state toggled*/ + +/* + * vx_toggle_pipe - start / pause a pipe + * @pipe: the pipe to be triggered + * @state: start = 1, pause = 0 + * + * called from trigger callback only + * + */ +static int vx_toggle_pipe(struct vx_core *chip, struct vx_pipe *pipe, int state) +{ + int err, i, cur_state; + + /* Check the pipe is not already in the requested state */ + if (vx_get_pipe_state(chip, pipe, &cur_state) < 0) + return -EBADFD; + if (state == cur_state) + return 0; + + /* If a start is requested, ask the DSP to get prepared + * and wait for a positive acknowledge (when there are + * enough sound buffer for this pipe) + */ + if (state) { + for (i = 0 ; i < MAX_WAIT_FOR_DSP; i++) { + err = vx_pipe_can_start(chip, pipe); + if (err > 0) + break; + /* Wait for a few, before asking again + * to avoid flooding the DSP with our requests + */ + mdelay(1); + } + } + + err = vx_conf_pipe(chip, pipe); + if (err < 0) + return err; + + err = vx_send_irqa(chip); + if (err < 0) + return err; + + /* If it completes successfully, wait for the pipes + * reaching the expected state before returning + * Check one pipe only (since they are synchronous) + */ + for (i = 0; i < MAX_WAIT_FOR_DSP; i++) { + err = vx_get_pipe_state(chip, pipe, &cur_state); + if (err < 0 || cur_state == state) + break; + err = -EIO; + mdelay(1); + } + return err < 0 ? -EIO : 0; +} + + +/* + * vx_stop_pipe - stop a pipe + * @pipe: the pipe to be stopped + * + * called from trigger callback only + */ +static int vx_stop_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + vx_init_rmh(&rmh, CMD_STOP_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_alloc_pipe - allocate a pipe and initialize the pipe instance + * @capture: 0 = playback, 1 = capture operation + * @audioid: the audio id to be assigned + * @num_audio: number of audio channels + * @pipep: the returned pipe instance + * + * return 0 on success, or a negative error code. + */ +static int vx_alloc_pipe(struct vx_core *chip, int capture, + int audioid, int num_audio, + struct vx_pipe **pipep) +{ + int err; + struct vx_pipe *pipe; + struct vx_rmh rmh; + int data_mode; + + *pipep = NULL; + vx_init_rmh(&rmh, CMD_RES_PIPE); + vx_set_pipe_cmd_params(&rmh, capture, audioid, num_audio); +#if 0 // NYI + if (underrun_skip_sound) + rmh.Cmd[0] |= BIT_SKIP_SOUND; +#endif // NYI + data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; + if (! capture && data_mode) + rmh.Cmd[0] |= BIT_DATA_MODE; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + + /* initialize the pipe record */ + pipe = kzalloc_obj(*pipe); + if (! pipe) { + /* release the pipe */ + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, capture, audioid, 0); + vx_send_msg(chip, &rmh); + return -ENOMEM; + } + + /* the pipe index should be identical with the audio index */ + pipe->number = audioid; + pipe->is_capture = capture; + pipe->channels = num_audio; + pipe->differed_type = 0; + pipe->pcx_time = 0; + pipe->data_mode = data_mode; + *pipep = pipe; + + return 0; +} + + +/* + * vx_free_pipe - release a pipe + * @pipe: pipe to be released + */ +static int vx_free_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + vx_send_msg(chip, &rmh); + + kfree(pipe); + return 0; +} + + +/* + * vx_start_stream - start the stream + * + * called from trigger callback only + */ +static int vx_start_stream(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_START_ONE_STREAM); + vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); + vx_set_differed_time(chip, &rmh, pipe); + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_stop_stream - stop the stream + * + * called from trigger callback only + */ +static int vx_stop_stream(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_STOP_STREAM); + vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); + return vx_send_msg(chip, &rmh); +} + + +/* + * playback hw information + */ + +static const struct snd_pcm_hardware vx_pcm_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ + /*SNDRV_PCM_INFO_RESUME*/), + .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 126, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = VX_MAX_PERIODS, + .fifo_size = 126, +}; + + +/* + * vx_pcm_playback_open - open callback for playback + */ +static int vx_pcm_playback_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe = NULL; + unsigned int audio; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + audio = subs->pcm->device * 2; + if (snd_BUG_ON(audio >= chip->audio_outs)) + return -EINVAL; + + /* playback pipe may have been already allocated for monitoring */ + pipe = chip->playback_pipes[audio]; + if (! pipe) { + /* not allocated yet */ + err = vx_alloc_pipe(chip, 0, audio, 2, &pipe); /* stereo playback */ + if (err < 0) + return err; + } + /* open for playback */ + pipe->references++; + + pipe->substream = subs; + chip->playback_pipes[audio] = pipe; + + runtime->hw = vx_pcm_playback_hw; + runtime->hw.period_bytes_min = chip->ibl.size; + runtime->private_data = pipe; + + /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + return 0; +} + +/* + * vx_pcm_playback_close - close callback for playback + */ +static int vx_pcm_playback_close(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + + if (! subs->runtime->private_data) + return -EINVAL; + + pipe = subs->runtime->private_data; + + if (--pipe->references == 0) { + chip->playback_pipes[pipe->number] = NULL; + vx_free_pipe(chip, pipe); + } + + return 0; + +} + + +/* + * vx_notify_end_of_buffer - send "end-of-buffer" notifier at the given pipe + * @pipe: the pipe to notify + * + * NB: call with a certain lock. + */ +static int vx_notify_end_of_buffer(struct vx_core *chip, struct vx_pipe *pipe) +{ + int err; + struct vx_rmh rmh; /* use a temporary rmh here */ + + /* Toggle Dsp Host Interface into Message mode */ + vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); + vx_init_rmh(&rmh, CMD_NOTIFY_END_OF_BUFFER); + vx_set_stream_cmd_params(&rmh, 0, pipe->number); + err = vx_send_msg_nolock(chip, &rmh); + if (err < 0) + return err; + /* Toggle Dsp Host Interface back to sound transfer mode */ + vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); + return 0; +} + +/* + * vx_pcm_playback_transfer_chunk - transfer a single chunk + * @subs: substream + * @pipe: the pipe to transfer + * @size: chunk size in bytes + * + * transfer a single buffer chunk. EOB notificaton is added after that. + * called from the interrupt handler, too. + * + * return 0 if ok. + */ +static int vx_pcm_playback_transfer_chunk(struct vx_core *chip, + struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int size) +{ + int space, err = 0; + + space = vx_query_hbuffer_size(chip, pipe); + if (space < 0) { + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + dev_dbg(chip->card->dev, "error hbuffer\n"); + return space; + } + if (space < size) { + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + dev_dbg(chip->card->dev, "no enough hbuffer space %d\n", space); + return -EIO; /* XRUN */ + } + + /* we don't need irqsave here, because this function + * is called from either trigger callback or irq handler + */ + guard(mutex)(&chip->lock); + vx_pseudo_dma_write(chip, runtime, pipe, size); + err = vx_notify_end_of_buffer(chip, pipe); + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT); + return err; +} + +/* + * update the position of the given pipe. + * pipe->position is updated and wrapped within the buffer size. + * pipe->transferred is updated, too, but the size is not wrapped, + * so that the caller can check the total transferred size later + * (to call snd_pcm_period_elapsed). + */ +static int vx_update_pipe_position(struct vx_core *chip, + struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + int err, update; + u64 count; + + vx_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + + count = ((u64)(rmh.Stat[0] & 0xfffff) << 24) | (u64)rmh.Stat[1]; + update = (int)(count - pipe->cur_count); + pipe->cur_count = count; + pipe->position += update; + if (pipe->position >= (int)runtime->buffer_size) + pipe->position %= runtime->buffer_size; + pipe->transferred += update; + return 0; +} + +/* + * transfer the pending playback buffer data to DSP + * called from interrupt handler + */ +static void vx_pcm_playback_transfer(struct vx_core *chip, + struct snd_pcm_substream *subs, + struct vx_pipe *pipe, int nchunks) +{ + int i, err; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE)) + return; + for (i = 0; i < nchunks; i++) { + err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe, + chip->ibl.size); + if (err < 0) + return; + } +} + +/* + * update the playback position and call snd_pcm_period_elapsed() if necessary + * called from interrupt handler + */ +static void vx_pcm_playback_update(struct vx_core *chip, + struct snd_pcm_substream *subs, + struct vx_pipe *pipe) +{ + int err; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (pipe->running && ! (chip->chip_status & VX_STAT_IS_STALE)) { + err = vx_update_pipe_position(chip, runtime, pipe); + if (err < 0) + return; + if (pipe->transferred >= (int)runtime->period_size) { + pipe->transferred %= runtime->period_size; + snd_pcm_period_elapsed(subs); + } + } +} + +/* + * vx_pcm_playback_trigger - trigger callback for playback + */ +static int vx_pcm_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe = subs->runtime->private_data; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (! pipe->is_capture) + vx_pcm_playback_transfer(chip, subs, pipe, 2); + err = vx_start_stream(chip, pipe); + if (err < 0) { + pr_debug("vx: cannot start stream\n"); + return err; + } + err = vx_toggle_pipe(chip, pipe, 1); + if (err < 0) { + pr_debug("vx: cannot start pipe\n"); + vx_stop_stream(chip, pipe); + return err; + } + chip->pcm_running++; + pipe->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + vx_toggle_pipe(chip, pipe, 0); + vx_stop_pipe(chip, pipe); + vx_stop_stream(chip, pipe); + chip->pcm_running--; + pipe->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + err = vx_toggle_pipe(chip, pipe, 0); + if (err < 0) + return err; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + err = vx_toggle_pipe(chip, pipe, 1); + if (err < 0) + return err; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * vx_pcm_playback_pointer - pointer callback for playback + */ +static snd_pcm_uframes_t vx_pcm_playback_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + return pipe->position; +} + +/* + * vx_pcm_prepare - prepare callback for playback and capture + */ +static int vx_pcm_prepare(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + int err, data_mode; + // int max_size, nchunks; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; + if (data_mode != pipe->data_mode && ! pipe->is_capture) { + /* IEC958 status (raw-mode) was changed */ + /* we reopen the pipe */ + struct vx_rmh rmh; + dev_dbg(chip->card->dev, + "reopen the pipe with data_mode = %d\n", data_mode); + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, 0, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + vx_init_rmh(&rmh, CMD_RES_PIPE); + vx_set_pipe_cmd_params(&rmh, 0, pipe->number, pipe->channels); + if (data_mode) + rmh.Cmd[0] |= BIT_DATA_MODE; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + pipe->data_mode = data_mode; + } + + if (chip->pcm_running && chip->freq != runtime->rate) { + dev_err(chip->card->dev, + "vx: cannot set different clock %d from the current %d\n", + runtime->rate, chip->freq); + return -EINVAL; + } + vx_set_clock(chip, runtime->rate); + + err = vx_set_format(chip, pipe, runtime); + if (err < 0) + return err; + + if (vx_is_pcmcia(chip)) { + pipe->align = 2; /* 16bit word */ + } else { + pipe->align = 4; /* 32bit word */ + } + + pipe->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size); + pipe->period_bytes = frames_to_bytes(runtime, runtime->period_size); + pipe->hw_ptr = 0; + + /* set the timestamp */ + vx_update_pipe_position(chip, runtime, pipe); + /* clear again */ + pipe->transferred = 0; + pipe->position = 0; + + pipe->prepared = 1; + + return 0; +} + + +/* + * operators for PCM playback + */ +static const struct snd_pcm_ops vx_pcm_playback_ops = { + .open = vx_pcm_playback_open, + .close = vx_pcm_playback_close, + .prepare = vx_pcm_prepare, + .trigger = vx_pcm_trigger, + .pointer = vx_pcm_playback_pointer, +}; + + +/* + * playback hw information + */ + +static const struct snd_pcm_hardware vx_pcm_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ + /*SNDRV_PCM_INFO_RESUME*/), + .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 126, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = VX_MAX_PERIODS, + .fifo_size = 126, +}; + + +/* + * vx_pcm_capture_open - open callback for capture + */ +static int vx_pcm_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + struct vx_pipe *pipe_out_monitoring = NULL; + unsigned int audio; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + audio = subs->pcm->device * 2; + if (snd_BUG_ON(audio >= chip->audio_ins)) + return -EINVAL; + err = vx_alloc_pipe(chip, 1, audio, 2, &pipe); + if (err < 0) + return err; + pipe->substream = subs; + chip->capture_pipes[audio] = pipe; + + /* check if monitoring is needed */ + if (chip->audio_monitor_active[audio]) { + pipe_out_monitoring = chip->playback_pipes[audio]; + if (! pipe_out_monitoring) { + /* allocate a pipe */ + err = vx_alloc_pipe(chip, 0, audio, 2, &pipe_out_monitoring); + if (err < 0) + return err; + chip->playback_pipes[audio] = pipe_out_monitoring; + } + pipe_out_monitoring->references++; + /* + if an output pipe is available, it's audios still may need to be + unmuted. hence we'll have to call a mixer entry point. + */ + vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], + chip->audio_monitor_active[audio]); + /* assuming stereo */ + vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], + chip->audio_monitor_active[audio+1]); + } + + pipe->monitoring_pipe = pipe_out_monitoring; /* default value NULL */ + + runtime->hw = vx_pcm_capture_hw; + runtime->hw.period_bytes_min = chip->ibl.size; + runtime->private_data = pipe; + + /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + return 0; +} + +/* + * vx_pcm_capture_close - close callback for capture + */ +static int vx_pcm_capture_close(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + struct vx_pipe *pipe_out_monitoring; + + if (! subs->runtime->private_data) + return -EINVAL; + pipe = subs->runtime->private_data; + chip->capture_pipes[pipe->number] = NULL; + + pipe_out_monitoring = pipe->monitoring_pipe; + + /* + if an output pipe is attached to this input, + check if it needs to be released. + */ + if (pipe_out_monitoring) { + if (--pipe_out_monitoring->references == 0) { + vx_free_pipe(chip, pipe_out_monitoring); + chip->playback_pipes[pipe->number] = NULL; + pipe->monitoring_pipe = NULL; + } + } + + vx_free_pipe(chip, pipe); + return 0; +} + + + +#define DMA_READ_ALIGN 6 /* hardware alignment for read */ + +/* + * vx_pcm_capture_update - update the capture buffer + */ +static void vx_pcm_capture_update(struct vx_core *chip, struct snd_pcm_substream *subs, + struct vx_pipe *pipe) +{ + int size, space, count; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (!pipe->running || (chip->chip_status & VX_STAT_IS_STALE)) + return; + + size = runtime->buffer_size - snd_pcm_capture_avail(runtime); + if (! size) + return; + size = frames_to_bytes(runtime, size); + space = vx_query_hbuffer_size(chip, pipe); + if (space < 0) + goto _error; + if (size > space) + size = space; + size = (size / 3) * 3; /* align to 3 bytes */ + if (size < DMA_READ_ALIGN) + goto _error; + + /* keep the last 6 bytes, they will be read after disconnection */ + count = size - DMA_READ_ALIGN; + /* read bytes until the current pointer reaches to the aligned position + * for word-transfer + */ + while (count > 0) { + if ((pipe->hw_ptr % pipe->align) == 0) + break; + if (vx_wait_for_rx_full(chip) < 0) + goto _error; + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + if (count > 0) { + /* ok, let's accelerate! */ + int align = pipe->align * 3; + space = (count / align) * align; + if (space > 0) { + vx_pseudo_dma_read(chip, runtime, pipe, space); + count -= space; + } + } + /* read the rest of bytes */ + while (count > 0) { + if (vx_wait_for_rx_full(chip) < 0) + goto _error; + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + /* read the last pending 6 bytes */ + count = DMA_READ_ALIGN; + while (count > 0) { + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + /* update the position */ + pipe->transferred += size; + if (pipe->transferred >= pipe->period_bytes) { + pipe->transferred %= pipe->period_bytes; + snd_pcm_period_elapsed(subs); + } + return; + + _error: + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + return; +} + +/* + * vx_pcm_capture_pointer - pointer callback for capture + */ +static snd_pcm_uframes_t vx_pcm_capture_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + return bytes_to_frames(runtime, pipe->hw_ptr); +} + +/* + * operators for PCM capture + */ +static const struct snd_pcm_ops vx_pcm_capture_ops = { + .open = vx_pcm_capture_open, + .close = vx_pcm_capture_close, + .prepare = vx_pcm_prepare, + .trigger = vx_pcm_trigger, + .pointer = vx_pcm_capture_pointer, +}; + + +/* + * interrupt handler for pcm streams + */ +void vx_pcm_update_intr(struct vx_core *chip, unsigned int events) +{ + unsigned int i; + struct vx_pipe *pipe; + +#define EVENT_MASK (END_OF_BUFFER_EVENTS_PENDING|ASYNC_EVENTS_PENDING) + + if (events & EVENT_MASK) { + vx_init_rmh(&chip->irq_rmh, CMD_ASYNC); + if (events & ASYNC_EVENTS_PENDING) + chip->irq_rmh.Cmd[0] |= 0x00000001; /* SEL_ASYNC_EVENTS */ + if (events & END_OF_BUFFER_EVENTS_PENDING) + chip->irq_rmh.Cmd[0] |= 0x00000002; /* SEL_END_OF_BUF_EVENTS */ + + if (vx_send_msg(chip, &chip->irq_rmh) < 0) { + dev_dbg(chip->card->dev, "msg send error!!\n"); + return; + } + + i = 1; + while (i < chip->irq_rmh.LgStat) { + int p, buf, capture, eob; + p = chip->irq_rmh.Stat[i] & MASK_FIRST_FIELD; + capture = (chip->irq_rmh.Stat[i] & 0x400000) ? 1 : 0; + eob = (chip->irq_rmh.Stat[i] & 0x800000) ? 1 : 0; + i++; + if (events & ASYNC_EVENTS_PENDING) + i++; + buf = 1; /* force to transfer */ + if (events & END_OF_BUFFER_EVENTS_PENDING) { + if (eob) + buf = chip->irq_rmh.Stat[i]; + i++; + } + if (capture) + continue; + if (snd_BUG_ON(p < 0 || p >= chip->audio_outs)) + continue; + pipe = chip->playback_pipes[p]; + if (pipe && pipe->substream) { + vx_pcm_playback_update(chip, pipe->substream, pipe); + vx_pcm_playback_transfer(chip, pipe->substream, pipe, buf); + } + } + } + + /* update the capture pcm pointers as frequently as possible */ + for (i = 0; i < chip->audio_ins; i++) { + pipe = chip->capture_pipes[i]; + if (pipe && pipe->substream) + vx_pcm_capture_update(chip, pipe->substream, pipe); + } +} + + +/* + * vx_init_audio_io - check the available audio i/o and allocate pipe arrays + */ +static int vx_init_audio_io(struct vx_core *chip) +{ + struct vx_rmh rmh; + int preferred; + + vx_init_rmh(&rmh, CMD_SUPPORTED); + if (vx_send_msg(chip, &rmh) < 0) { + dev_err(chip->card->dev, + "vx: cannot get the supported audio data\n"); + return -ENXIO; + } + + chip->audio_outs = rmh.Stat[0] & MASK_FIRST_FIELD; + chip->audio_ins = (rmh.Stat[0] >> (FIELD_SIZE*2)) & MASK_FIRST_FIELD; + chip->audio_info = rmh.Stat[1]; + + /* allocate pipes */ + chip->playback_pipes = kzalloc_objs(struct vx_pipe *, chip->audio_outs); + if (!chip->playback_pipes) + return -ENOMEM; + chip->capture_pipes = kzalloc_objs(struct vx_pipe *, chip->audio_ins); + if (!chip->capture_pipes) { + kfree(chip->playback_pipes); + return -ENOMEM; + } + + preferred = chip->ibl.size; + chip->ibl.size = 0; + vx_set_ibl(chip, &chip->ibl); /* query the info */ + if (preferred > 0) { + chip->ibl.size = roundup(preferred, chip->ibl.granularity); + if (chip->ibl.size > chip->ibl.max_size) + chip->ibl.size = chip->ibl.max_size; + } else + chip->ibl.size = chip->ibl.min_size; /* set to the minimum */ + vx_set_ibl(chip, &chip->ibl); + + return 0; +} + + +/* + * free callback for pcm + */ +static void snd_vx_pcm_free(struct snd_pcm *pcm) +{ + struct vx_core *chip = pcm->private_data; + chip->pcm[pcm->device] = NULL; + kfree(chip->playback_pipes); + chip->playback_pipes = NULL; + kfree(chip->capture_pipes); + chip->capture_pipes = NULL; +} + +/* + * snd_vx_pcm_new - create and initialize a pcm + */ +int snd_vx_pcm_new(struct vx_core *chip) +{ + struct snd_pcm *pcm; + unsigned int i; + int err; + + err = vx_init_audio_io(chip); + if (err < 0) + return err; + + for (i = 0; i < chip->hw->num_codecs; i++) { + unsigned int outs, ins; + outs = chip->audio_outs > i * 2 ? 1 : 0; + ins = chip->audio_ins > i * 2 ? 1 : 0; + if (! outs && ! ins) + break; + err = snd_pcm_new(chip->card, "VX PCM", i, + outs, ins, &pcm); + if (err < 0) + return err; + if (outs) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops); + if (ins) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + + pcm->private_data = chip; + pcm->private_free = snd_vx_pcm_free; + pcm->info_flags = 0; + pcm->nonatomic = true; + strscpy(pcm->name, chip->card->shortname); + chip->pcm[i] = pcm; + } + + return 0; +} diff --git a/sound/drivers/vx/vx_uer.c b/sound/drivers/vx/vx_uer.c new file mode 100644 index 000000000..1d90db3b0 --- /dev/null +++ b/sound/drivers/vx/vx_uer.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * IEC958 stuff + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + + +/* + * vx_modify_board_clock - tell the board that its clock has been modified + * @sync: DSP needs to resynchronize its FIFO + */ +static int vx_modify_board_clock(struct vx_core *chip, int sync) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_MODIFY_CLOCK); + /* Ask the DSP to resynchronize its FIFO. */ + if (sync) + rmh.Cmd[0] |= CMD_MODIFY_CLOCK_S_BIT; + return vx_send_msg(chip, &rmh); +} + +/* + * vx_modify_board_inputs - resync audio inputs + */ +static int vx_modify_board_inputs(struct vx_core *chip) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS); + rmh.Cmd[0] |= 1 << 0; /* reference: AUDIO 0 */ + return vx_send_msg(chip, &rmh); +} + +/* + * vx_read_one_cbit - read one bit from UER config + * @index: the bit index + * returns 0 or 1. + */ +static int vx_read_one_cbit(struct vx_core *chip, int index) +{ + int val; + + guard(mutex)(&chip->lock); + if (chip->type >= VX_TYPE_VXPOCKET) { + vx_outb(chip, CSUER, 1); /* read */ + vx_outb(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK); + val = (vx_inb(chip, RUER) >> 7) & 0x01; + } else { + vx_outl(chip, CSUER, 1); /* read */ + vx_outl(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK); + val = (vx_inl(chip, RUER) >> 7) & 0x01; + } + return val; +} + +/* + * vx_write_one_cbit - write one bit to UER config + * @index: the bit index + * @val: bit value, 0 or 1 + */ +static void vx_write_one_cbit(struct vx_core *chip, int index, int val) +{ + val = !!val; /* 0 or 1 */ + guard(mutex)(&chip->lock); + if (vx_is_pcmcia(chip)) { + vx_outb(chip, CSUER, 0); /* write */ + vx_outb(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK)); + } else { + vx_outl(chip, CSUER, 0); /* write */ + vx_outl(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK)); + } +} + +/* + * vx_read_uer_status - read the current UER status + * @mode: pointer to store the UER mode, VX_UER_MODE_XXX + * + * returns the frequency of UER, or 0 if not sync, + * or a negative error code. + */ +static int vx_read_uer_status(struct vx_core *chip, unsigned int *mode) +{ + int val, freq; + + /* Default values */ + freq = 0; + + /* Read UER status */ + if (vx_is_pcmcia(chip)) + val = vx_inb(chip, CSUER); + else + val = vx_inl(chip, CSUER); + if (val < 0) + return val; + /* If clock is present, read frequency */ + if (val & VX_SUER_CLOCK_PRESENT_MASK) { + switch (val & VX_SUER_FREQ_MASK) { + case VX_SUER_FREQ_32KHz_MASK: + freq = 32000; + break; + case VX_SUER_FREQ_44KHz_MASK: + freq = 44100; + break; + case VX_SUER_FREQ_48KHz_MASK: + freq = 48000; + break; + } + } + if (val & VX_SUER_DATA_PRESENT_MASK) + /* bit 0 corresponds to consumer/professional bit */ + *mode = vx_read_one_cbit(chip, 0) ? + VX_UER_MODE_PROFESSIONAL : VX_UER_MODE_CONSUMER; + else + *mode = VX_UER_MODE_NOT_PRESENT; + + return freq; +} + + +/* + * compute the sample clock value from frequency + * + * The formula is as follows: + * + * HexFreq = (dword) ((double) ((double) 28224000 / (double) Frequency)) + * switch ( HexFreq & 0x00000F00 ) + * case 0x00000100: ; + * case 0x00000200: + * case 0x00000300: HexFreq -= 0x00000201 ; + * case 0x00000400: + * case 0x00000500: + * case 0x00000600: + * case 0x00000700: HexFreq = (dword) (((double) 28224000 / (double) (Frequency*2)) - 1) + * default : HexFreq = (dword) ((double) 28224000 / (double) (Frequency*4)) - 0x000001FF + */ + +static int vx_calc_clock_from_freq(struct vx_core *chip, int freq) +{ + int hexfreq; + + if (snd_BUG_ON(freq <= 0)) + return 0; + + hexfreq = (28224000 * 10) / freq; + hexfreq = (hexfreq + 5) / 10; + + /* max freq = 55125 Hz */ + if (snd_BUG_ON(hexfreq <= 0x00000200)) + return 0; + + if (hexfreq <= 0x03ff) + return hexfreq - 0x00000201; + if (hexfreq <= 0x07ff) + return (hexfreq / 2) - 1; + if (hexfreq <= 0x0fff) + return (hexfreq / 4) + 0x000001ff; + + return 0x5fe; /* min freq = 6893 Hz */ +} + + +/* + * vx_change_clock_source - change the clock source + * @source: the new source + */ +static void vx_change_clock_source(struct vx_core *chip, int source) +{ + /* we mute DAC to prevent clicks */ + vx_toggle_dac_mute(chip, 1); + scoped_guard(mutex, &chip->lock) { + chip->ops->set_clock_source(chip, source); + chip->clock_source = source; + } + /* unmute */ + vx_toggle_dac_mute(chip, 0); +} + + +/* + * set the internal clock + */ +void vx_set_internal_clock(struct vx_core *chip, unsigned int freq) +{ + int clock; + + /* Get real clock value */ + clock = vx_calc_clock_from_freq(chip, freq); + dev_dbg(chip->card->dev, + "set internal clock to 0x%x from freq %d\n", clock, freq); + guard(mutex)(&chip->lock); + if (vx_is_pcmcia(chip)) { + vx_outb(chip, HIFREQ, (clock >> 8) & 0x0f); + vx_outb(chip, LOFREQ, clock & 0xff); + } else { + vx_outl(chip, HIFREQ, (clock >> 8) & 0x0f); + vx_outl(chip, LOFREQ, clock & 0xff); + } +} + + +/* + * set the iec958 status bits + * @bits: 32-bit status bits + */ +void vx_set_iec958_status(struct vx_core *chip, unsigned int bits) +{ + int i; + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + for (i = 0; i < 32; i++) + vx_write_one_cbit(chip, i, bits & (1 << i)); +} + + +/* + * vx_set_clock - change the clock and audio source if necessary + */ +int vx_set_clock(struct vx_core *chip, unsigned int freq) +{ + int src_changed = 0; + + if (chip->chip_status & VX_STAT_IS_STALE) + return 0; + + /* change the audio source if possible */ + vx_sync_audio_source(chip); + + if (chip->clock_mode == VX_CLOCK_MODE_EXTERNAL || + (chip->clock_mode == VX_CLOCK_MODE_AUTO && + chip->audio_source == VX_AUDIO_SRC_DIGITAL)) { + if (chip->clock_source != UER_SYNC) { + vx_change_clock_source(chip, UER_SYNC); + mdelay(6); + src_changed = 1; + } + } else if (chip->clock_mode == VX_CLOCK_MODE_INTERNAL || + (chip->clock_mode == VX_CLOCK_MODE_AUTO && + chip->audio_source != VX_AUDIO_SRC_DIGITAL)) { + if (chip->clock_source != INTERNAL_QUARTZ) { + vx_change_clock_source(chip, INTERNAL_QUARTZ); + src_changed = 1; + } + if (chip->freq == freq) + return 0; + vx_set_internal_clock(chip, freq); + if (src_changed) + vx_modify_board_inputs(chip); + } + if (chip->freq == freq) + return 0; + chip->freq = freq; + vx_modify_board_clock(chip, 1); + return 0; +} + + +/* + * vx_change_frequency - called from interrupt handler + */ +int vx_change_frequency(struct vx_core *chip) +{ + int freq; + + if (chip->chip_status & VX_STAT_IS_STALE) + return 0; + + if (chip->clock_source == INTERNAL_QUARTZ) + return 0; + /* + * Read the real UER board frequency + */ + freq = vx_read_uer_status(chip, &chip->uer_detected); + if (freq < 0) + return freq; + /* + * The frequency computed by the DSP is good and + * is different from the previous computed. + */ + if (freq == 48000 || freq == 44100 || freq == 32000) + chip->freq_detected = freq; + + return 0; +} |
