2019-05-30 02:57:59 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-10-01 16:02:15 +03:00
/*
* tascam - stream . c - a part of driver for TASCAM FireWire series
*
* Copyright ( c ) 2015 Takashi Sakamoto
*/
# include <linux/delay.h>
# include "tascam.h"
2019-09-10 16:51:52 +03:00
# define CLOCK_STATUS_MASK 0xffff0000
# define CLOCK_CONFIG_MASK 0x0000ffff
2021-05-31 05:51:02 +03:00
# define READY_TIMEOUT_MS 4000
2015-10-01 16:02:15 +03:00
static int get_clock ( struct snd_tscm * tscm , u32 * data )
{
2019-09-10 16:51:52 +03:00
int trial = 0 ;
2015-10-01 16:02:15 +03:00
__be32 reg ;
int err ;
2019-09-10 16:51:52 +03:00
while ( trial + + < 5 ) {
err = snd_fw_transaction ( tscm - > unit , TCODE_READ_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
2015-10-01 16:02:15 +03:00
* data = be32_to_cpu ( reg ) ;
2019-09-10 16:51:52 +03:00
if ( * data & CLOCK_STATUS_MASK )
break ;
2015-10-01 16:02:15 +03:00
2019-09-10 16:51:52 +03:00
// In intermediate state after changing clock status.
msleep ( 50 ) ;
}
// Still in the intermediate state.
if ( trial > = 5 )
return - EAGAIN ;
return 0 ;
2015-10-01 16:02:15 +03:00
}
static int set_clock ( struct snd_tscm * tscm , unsigned int rate ,
enum snd_tscm_clock clock )
{
u32 data ;
__be32 reg ;
int err ;
err = get_clock ( tscm , & data ) ;
if ( err < 0 )
return err ;
2019-09-10 16:51:52 +03:00
data & = CLOCK_CONFIG_MASK ;
2015-10-01 16:02:15 +03:00
if ( rate > 0 ) {
data & = 0x000000ff ;
/* Base rate. */
if ( ( rate % 44100 ) = = 0 ) {
data | = 0x00000100 ;
/* Multiplier. */
if ( rate / 44100 = = 2 )
data | = 0x00008000 ;
} else if ( ( rate % 48000 ) = = 0 ) {
data | = 0x00000200 ;
/* Multiplier. */
if ( rate / 48000 = = 2 )
data | = 0x00008000 ;
} else {
return - EAGAIN ;
}
}
if ( clock ! = INT_MAX ) {
data & = 0x0000ff00 ;
data | = clock + 1 ;
}
reg = cpu_to_be32 ( data ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
if ( data & 0x00008000 )
reg = cpu_to_be32 ( 0x0000001a ) ;
else
reg = cpu_to_be32 ( 0x0000000d ) ;
return snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE ,
& reg , sizeof ( reg ) , 0 ) ;
}
int snd_tscm_stream_get_rate ( struct snd_tscm * tscm , unsigned int * rate )
{
2019-09-10 16:51:52 +03:00
u32 data ;
2015-10-01 16:02:15 +03:00
int err ;
2019-09-10 16:51:52 +03:00
err = get_clock ( tscm , & data ) ;
if ( err < 0 )
return err ;
2015-10-01 16:02:15 +03:00
2019-09-10 16:51:52 +03:00
data = ( data & 0xff000000 ) > > 24 ;
2015-10-01 16:02:15 +03:00
/* Check base rate. */
if ( ( data & 0x0f ) = = 0x01 )
* rate = 44100 ;
else if ( ( data & 0x0f ) = = 0x02 )
* rate = 48000 ;
else
return - EAGAIN ;
/* Check multiplier. */
if ( ( data & 0xf0 ) = = 0x80 )
* rate * = 2 ;
else if ( ( data & 0xf0 ) ! = 0x00 )
return - EAGAIN ;
return err ;
}
int snd_tscm_stream_get_clock ( struct snd_tscm * tscm , enum snd_tscm_clock * clock )
{
u32 data ;
int err ;
err = get_clock ( tscm , & data ) ;
if ( err < 0 )
return err ;
* clock = ( ( data & 0x00ff0000 ) > > 16 ) - 1 ;
if ( * clock < 0 | | * clock > SND_TSCM_CLOCK_ADAT )
return - EIO ;
return 0 ;
}
static int enable_data_channels ( struct snd_tscm * tscm )
{
__be32 reg ;
u32 data ;
unsigned int i ;
int err ;
data = 0 ;
for ( i = 0 ; i < tscm - > spec - > pcm_capture_analog_channels ; + + i )
data | = BIT ( i ) ;
if ( tscm - > spec - > has_adat )
data | = 0x0000ff00 ;
if ( tscm - > spec - > has_spdif )
data | = 0x00030000 ;
reg = cpu_to_be32 ( data ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
data = 0 ;
for ( i = 0 ; i < tscm - > spec - > pcm_playback_analog_channels ; + + i )
data | = BIT ( i ) ;
if ( tscm - > spec - > has_adat )
data | = 0x0000ff00 ;
if ( tscm - > spec - > has_spdif )
data | = 0x00030000 ;
reg = cpu_to_be32 ( data ) ;
return snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS ,
& reg , sizeof ( reg ) , 0 ) ;
}
static int set_stream_formats ( struct snd_tscm * tscm , unsigned int rate )
{
__be32 reg ;
int err ;
2019-06-02 10:12:48 +03:00
// Set an option for unknown purpose.
2015-10-01 16:02:15 +03:00
reg = cpu_to_be32 ( 0x00200000 ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
2019-06-02 10:12:48 +03:00
return enable_data_channels ( tscm ) ;
2015-10-01 16:02:15 +03:00
}
static void finish_session ( struct snd_tscm * tscm )
{
__be32 reg ;
reg = 0 ;
snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING ,
& reg , sizeof ( reg ) , 0 ) ;
reg = 0 ;
snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON ,
& reg , sizeof ( reg ) , 0 ) ;
2019-06-02 10:12:45 +03:00
// Unregister channels.
reg = cpu_to_be32 ( 0x00000000 ) ;
snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH ,
& reg , sizeof ( reg ) , 0 ) ;
reg = cpu_to_be32 ( 0x00000000 ) ;
snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN ,
& reg , sizeof ( reg ) , 0 ) ;
reg = cpu_to_be32 ( 0x00000000 ) ;
snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH ,
& reg , sizeof ( reg ) , 0 ) ;
2015-10-01 16:02:15 +03:00
}
static int begin_session ( struct snd_tscm * tscm )
{
__be32 reg ;
int err ;
2019-06-02 10:12:45 +03:00
// Register the isochronous channel for transmitting stream.
reg = cpu_to_be32 ( tscm - > tx_resources . channel ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
// Unknown.
reg = cpu_to_be32 ( 0x00000002 ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
// Register the isochronous channel for receiving stream.
reg = cpu_to_be32 ( tscm - > rx_resources . channel ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
2015-10-01 16:02:15 +03:00
reg = cpu_to_be32 ( 0x00000001 ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
reg = cpu_to_be32 ( 0x00000001 ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
2019-06-02 10:12:45 +03:00
// Set an option for unknown purpose.
2015-10-01 16:02:15 +03:00
reg = cpu_to_be32 ( 0x00002000 ) ;
err = snd_fw_transaction ( tscm - > unit , TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION ,
& reg , sizeof ( reg ) , 0 ) ;
if ( err < 0 )
return err ;
2019-06-02 10:12:45 +03:00
// Start multiplexing PCM samples on packets.
2015-10-01 16:02:15 +03:00
reg = cpu_to_be32 ( 0x00000001 ) ;
return snd_fw_transaction ( tscm - > unit ,
TCODE_WRITE_QUADLET_REQUEST ,
TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON ,
& reg , sizeof ( reg ) , 0 ) ;
}
2019-06-02 10:12:46 +03:00
static int keep_resources ( struct snd_tscm * tscm , unsigned int rate ,
struct amdtp_stream * stream )
2015-10-01 16:02:15 +03:00
{
2019-06-02 10:12:46 +03:00
struct fw_iso_resources * resources ;
2015-10-01 16:02:15 +03:00
int err ;
2019-06-02 10:12:46 +03:00
if ( stream = = & tscm - > tx_stream )
resources = & tscm - > tx_resources ;
else
resources = & tscm - > rx_resources ;
2015-10-01 16:02:15 +03:00
2019-06-02 10:12:46 +03:00
err = amdtp_tscm_set_parameters ( stream , rate ) ;
2015-10-01 16:02:15 +03:00
if ( err < 0 )
return err ;
2019-06-02 10:12:46 +03:00
return fw_iso_resources_allocate ( resources ,
amdtp_stream_get_max_payload ( stream ) ,
fw_parent_device ( tscm - > unit ) - > max_speed ) ;
2015-10-01 16:02:15 +03:00
}
2019-08-04 09:21:27 +03:00
static int init_stream ( struct snd_tscm * tscm , struct amdtp_stream * s )
2015-10-01 16:02:15 +03:00
{
2019-08-04 09:21:27 +03:00
struct fw_iso_resources * resources ;
enum amdtp_stream_direction dir ;
2015-10-01 16:02:15 +03:00
unsigned int pcm_channels ;
int err ;
2019-08-04 09:21:27 +03:00
if ( s = = & tscm - > tx_stream ) {
resources = & tscm - > tx_resources ;
dir = AMDTP_IN_STREAM ;
pcm_channels = tscm - > spec - > pcm_capture_analog_channels ;
} else {
resources = & tscm - > rx_resources ;
dir = AMDTP_OUT_STREAM ;
pcm_channels = tscm - > spec - > pcm_playback_analog_channels ;
}
2015-10-01 16:02:15 +03:00
if ( tscm - > spec - > has_adat )
pcm_channels + = 8 ;
if ( tscm - > spec - > has_spdif )
pcm_channels + = 2 ;
2019-08-04 09:21:27 +03:00
err = fw_iso_resources_init ( resources , tscm - > unit ) ;
2015-10-01 16:02:15 +03:00
if ( err < 0 )
return err ;
2019-08-04 09:21:27 +03:00
err = amdtp_tscm_init ( s , tscm - > unit , dir , pcm_channels ) ;
2015-10-01 16:02:15 +03:00
if ( err < 0 )
2019-08-04 09:21:27 +03:00
fw_iso_resources_free ( resources ) ;
return err ;
}
static void destroy_stream ( struct snd_tscm * tscm , struct amdtp_stream * s )
{
amdtp_stream_destroy ( s ) ;
if ( s = = & tscm - > tx_stream )
fw_iso_resources_destroy ( & tscm - > tx_resources ) ;
else
fw_iso_resources_destroy ( & tscm - > rx_resources ) ;
}
int snd_tscm_stream_init_duplex ( struct snd_tscm * tscm )
{
int err ;
err = init_stream ( tscm , & tscm - > tx_stream ) ;
2015-10-01 16:02:15 +03:00
if ( err < 0 )
return err ;
2019-08-04 09:21:27 +03:00
err = init_stream ( tscm , & tscm - > rx_stream ) ;
2019-08-04 09:21:35 +03:00
if ( err < 0 ) {
destroy_stream ( tscm , & tscm - > tx_stream ) ;
return err ;
}
err = amdtp_domain_init ( & tscm - > domain ) ;
if ( err < 0 ) {
2019-08-04 09:21:27 +03:00
destroy_stream ( tscm , & tscm - > tx_stream ) ;
2019-08-04 09:21:35 +03:00
destroy_stream ( tscm , & tscm - > rx_stream ) ;
}
2015-10-01 16:02:15 +03:00
2017-01-03 05:58:33 +03:00
return err ;
2015-10-01 16:02:15 +03:00
}
2019-06-02 10:12:49 +03:00
// At bus reset, streaming is stopped and some registers are clear.
2015-10-01 16:02:15 +03:00
void snd_tscm_stream_update_duplex ( struct snd_tscm * tscm )
{
2019-08-04 09:21:35 +03:00
amdtp_domain_stop ( & tscm - > domain ) ;
2015-10-01 16:02:15 +03:00
2019-08-04 09:21:35 +03:00
amdtp_stream_pcm_abort ( & tscm - > tx_stream ) ;
2015-10-01 16:02:15 +03:00
amdtp_stream_pcm_abort ( & tscm - > rx_stream ) ;
}
2019-08-04 09:21:27 +03:00
// This function should be called before starting streams or after stopping
// streams.
2015-10-01 16:02:15 +03:00
void snd_tscm_stream_destroy_duplex ( struct snd_tscm * tscm )
{
2019-08-04 09:21:35 +03:00
amdtp_domain_destroy ( & tscm - > domain ) ;
2015-10-01 16:02:15 +03:00
2019-08-04 09:21:27 +03:00
destroy_stream ( tscm , & tscm - > rx_stream ) ;
destroy_stream ( tscm , & tscm - > tx_stream ) ;
2015-10-01 16:02:15 +03:00
}
2019-10-07 14:05:22 +03:00
int snd_tscm_stream_reserve_duplex ( struct snd_tscm * tscm , unsigned int rate ,
2019-10-17 18:54:19 +03:00
unsigned int frames_per_period ,
unsigned int frames_per_buffer )
2015-10-01 16:02:15 +03:00
{
unsigned int curr_rate ;
int err ;
err = snd_tscm_stream_get_rate ( tscm , & curr_rate ) ;
if ( err < 0 )
return err ;
2019-06-02 10:12:48 +03:00
if ( tscm - > substreams_counter = = 0 | | rate ! = curr_rate ) {
2019-08-04 09:21:35 +03:00
amdtp_domain_stop ( & tscm - > domain ) ;
2019-06-02 10:12:48 +03:00
finish_session ( tscm ) ;
2019-06-02 10:12:47 +03:00
fw_iso_resources_free ( & tscm - > tx_resources ) ;
fw_iso_resources_free ( & tscm - > rx_resources ) ;
2015-10-01 16:02:15 +03:00
2019-06-02 10:12:48 +03:00
err = set_clock ( tscm , rate , INT_MAX ) ;
if ( err < 0 )
return err ;
2019-06-02 10:12:46 +03:00
err = keep_resources ( tscm , rate , & tscm - > tx_stream ) ;
if ( err < 0 )
2019-06-02 10:12:48 +03:00
return err ;
2019-06-02 10:12:46 +03:00
err = keep_resources ( tscm , rate , & tscm - > rx_stream ) ;
2019-06-02 10:12:48 +03:00
if ( err < 0 ) {
fw_iso_resources_free ( & tscm - > tx_resources ) ;
return err ;
}
2019-10-07 14:05:22 +03:00
err = amdtp_domain_set_events_per_period ( & tscm - > domain ,
2019-10-17 18:54:19 +03:00
frames_per_period , frames_per_buffer ) ;
2019-10-07 14:05:22 +03:00
if ( err < 0 ) {
fw_iso_resources_free ( & tscm - > tx_resources ) ;
fw_iso_resources_free ( & tscm - > rx_resources ) ;
return err ;
}
2021-05-31 05:51:02 +03:00
tscm - > need_long_tx_init_skip = ( rate ! = curr_rate ) ;
2019-06-02 10:12:48 +03:00
}
return 0 ;
}
int snd_tscm_stream_start_duplex ( struct snd_tscm * tscm , unsigned int rate )
{
2019-06-02 10:12:49 +03:00
unsigned int generation = tscm - > rx_resources . generation ;
2019-06-02 10:12:48 +03:00
int err ;
if ( tscm - > substreams_counter = = 0 )
return 0 ;
if ( amdtp_streaming_error ( & tscm - > rx_stream ) | |
2019-08-04 09:21:35 +03:00
amdtp_streaming_error ( & tscm - > tx_stream ) ) {
amdtp_domain_stop ( & tscm - > domain ) ;
2019-06-02 10:12:48 +03:00
finish_session ( tscm ) ;
2019-08-04 09:21:35 +03:00
}
2019-06-02 10:12:48 +03:00
2019-06-02 10:12:49 +03:00
if ( generation ! = fw_parent_device ( tscm - > unit ) - > card - > generation ) {
err = fw_iso_resources_update ( & tscm - > tx_resources ) ;
if ( err < 0 )
goto error ;
err = fw_iso_resources_update ( & tscm - > rx_resources ) ;
if ( err < 0 )
goto error ;
}
2019-06-02 10:12:48 +03:00
if ( ! amdtp_stream_running ( & tscm - > rx_stream ) ) {
2019-08-04 09:21:35 +03:00
int spd = fw_parent_device ( tscm - > unit ) - > max_speed ;
2021-05-31 05:51:02 +03:00
unsigned int tx_init_skip_cycles ;
2019-08-04 09:21:35 +03:00
2015-10-01 16:02:15 +03:00
err = set_stream_formats ( tscm , rate ) ;
if ( err < 0 )
goto error ;
err = begin_session ( tscm ) ;
if ( err < 0 )
goto error ;
2019-08-04 09:21:35 +03:00
err = amdtp_domain_add_stream ( & tscm - > domain , & tscm - > rx_stream ,
tscm - > rx_resources . channel , spd ) ;
2015-10-01 16:02:15 +03:00
if ( err < 0 )
goto error ;
2019-08-04 09:21:35 +03:00
err = amdtp_domain_add_stream ( & tscm - > domain , & tscm - > tx_stream ,
tscm - > tx_resources . channel , spd ) ;
if ( err < 0 )
2015-10-01 16:02:15 +03:00
goto error ;
2021-05-31 05:51:02 +03:00
if ( tscm - > need_long_tx_init_skip )
tx_init_skip_cycles = 16000 ;
else
tx_init_skip_cycles = 0 ;
// MEMO: Just after starting packet streaming, it transfers packets without any
// event. Enough after receiving the sequence of packets, it multiplexes events into
// the packet. However, just after changing sampling transfer frequency, it stops
// multiplexing during packet transmission. Enough after, it restarts multiplexing
// again. The device ignores presentation time expressed by the value of syt field
// of CIP header in received packets. The sequence of the number of data blocks per
// packet is important for media clock recovery.
err = amdtp_domain_start ( & tscm - > domain , tx_init_skip_cycles , true , true ) ;
2015-10-01 16:02:15 +03:00
if ( err < 0 )
2023-04-06 16:28:01 +03:00
goto error ;
2015-10-01 16:02:15 +03:00
2021-05-20 07:01:54 +03:00
if ( ! amdtp_domain_wait_ready ( & tscm - > domain , READY_TIMEOUT_MS ) ) {
2015-10-01 16:02:15 +03:00
err = - ETIMEDOUT ;
goto error ;
}
}
return 0 ;
error :
2019-08-04 09:21:35 +03:00
amdtp_domain_stop ( & tscm - > domain ) ;
2015-10-01 16:02:15 +03:00
finish_session ( tscm ) ;
2019-06-02 10:12:47 +03:00
2015-10-01 16:02:15 +03:00
return err ;
}
void snd_tscm_stream_stop_duplex ( struct snd_tscm * tscm )
{
2019-06-18 16:26:15 +03:00
if ( tscm - > substreams_counter = = 0 ) {
2019-08-04 09:21:35 +03:00
amdtp_domain_stop ( & tscm - > domain ) ;
2019-06-02 10:12:48 +03:00
finish_session ( tscm ) ;
2019-06-18 16:26:15 +03:00
fw_iso_resources_free ( & tscm - > tx_resources ) ;
fw_iso_resources_free ( & tscm - > rx_resources ) ;
2021-05-31 05:51:02 +03:00
tscm - > need_long_tx_init_skip = false ;
2019-06-18 16:26:15 +03:00
}
2015-10-01 16:02:15 +03:00
}
2015-10-01 16:02:17 +03:00
void snd_tscm_stream_lock_changed ( struct snd_tscm * tscm )
{
tscm - > dev_lock_changed = true ;
wake_up ( & tscm - > hwdep_wait ) ;
}
int snd_tscm_stream_lock_try ( struct snd_tscm * tscm )
{
int err ;
spin_lock_irq ( & tscm - > lock ) ;
/* user land lock this */
if ( tscm - > dev_lock_count < 0 ) {
err = - EBUSY ;
goto end ;
}
/* this is the first time */
if ( tscm - > dev_lock_count + + = = 0 )
snd_tscm_stream_lock_changed ( tscm ) ;
err = 0 ;
end :
spin_unlock_irq ( & tscm - > lock ) ;
return err ;
}
void snd_tscm_stream_lock_release ( struct snd_tscm * tscm )
{
spin_lock_irq ( & tscm - > lock ) ;
if ( WARN_ON ( tscm - > dev_lock_count < = 0 ) )
goto end ;
if ( - - tscm - > dev_lock_count = = 0 )
snd_tscm_stream_lock_changed ( tscm ) ;
end :
spin_unlock_irq ( & tscm - > lock ) ;
}