2011-03-15 09:53:21 +03:00
/*
* Connection Management Procedures ( IEC 61883 - 1 ) helper functions
*
* Copyright ( c ) Clemens Ladisch < clemens @ ladisch . de >
* Licensed under the terms of the GNU General Public License , version 2.
*/
# include <linux/device.h>
# include <linux/firewire.h>
# include <linux/firewire-constants.h>
# include <linux/module.h>
# include <linux/sched.h>
# include "lib.h"
# include "iso-resources.h"
# include "cmp.h"
2014-04-25 17:44:54 +04:00
/* MPR common fields */
# define MPR_SPEED_MASK 0xc0000000
# define MPR_SPEED_SHIFT 30
# define MPR_XSPEED_MASK 0x00000060
# define MPR_XSPEED_SHIFT 5
# define MPR_PLUGS_MASK 0x0000001f
/* PCR common fields */
# define PCR_ONLINE 0x80000000
# define PCR_BCAST_CONN 0x40000000
# define PCR_P2P_CONN_MASK 0x3f000000
# define PCR_P2P_CONN_SHIFT 24
# define PCR_CHANNEL_MASK 0x003f0000
# define PCR_CHANNEL_SHIFT 16
2011-03-15 09:53:21 +03:00
2014-04-25 17:44:56 +04:00
/* oPCR specific fields */
# define OPCR_XSPEED_MASK 0x00C00000
# define OPCR_XSPEED_SHIFT 22
# define OPCR_SPEED_MASK 0x0000C000
# define OPCR_SPEED_SHIFT 14
# define OPCR_OVERHEAD_ID_MASK 0x00003C00
# define OPCR_OVERHEAD_ID_SHIFT 10
2011-03-15 09:53:21 +03:00
enum bus_reset_handling {
ABORT_ON_BUS_RESET ,
SUCCEED_ON_BUS_RESET ,
} ;
2011-11-01 04:11:33 +04:00
static __printf ( 2 , 3 )
2011-03-15 09:53:21 +03:00
void cmp_error ( struct cmp_connection * c , const char * fmt , . . . )
{
va_list va ;
va_start ( va , fmt ) ;
dev_err ( & c - > resources . unit - > device , " %cPCR%u: %pV " ,
2014-04-25 17:44:56 +04:00
( c - > direction = = CMP_INPUT ) ? ' i ' : ' o ' ,
c - > pcr_index , & ( struct va_format ) { fmt , & va } ) ;
2011-03-15 09:53:21 +03:00
va_end ( va ) ;
}
2014-04-25 17:44:56 +04:00
static u64 mpr_address ( struct cmp_connection * c )
{
if ( c - > direction = = CMP_INPUT )
return CSR_REGISTER_BASE + CSR_IMPR ;
else
return CSR_REGISTER_BASE + CSR_OMPR ;
}
static u64 pcr_address ( struct cmp_connection * c )
{
if ( c - > direction = = CMP_INPUT )
return CSR_REGISTER_BASE + CSR_IPCR ( c - > pcr_index ) ;
else
return CSR_REGISTER_BASE + CSR_OPCR ( c - > pcr_index ) ;
}
2011-03-15 09:53:21 +03:00
static int pcr_modify ( struct cmp_connection * c ,
__be32 ( * modify ) ( struct cmp_connection * c , __be32 old ) ,
int ( * check ) ( struct cmp_connection * c , __be32 pcr ) ,
enum bus_reset_handling bus_reset_handling )
{
2011-04-22 17:13:54 +04:00
__be32 old_arg , buffer [ 2 ] ;
2011-03-15 09:53:21 +03:00
int err ;
buffer [ 0 ] = c - > last_pcr_value ;
for ( ; ; ) {
old_arg = buffer [ 0 ] ;
buffer [ 1 ] = modify ( c , buffer [ 0 ] ) ;
2011-09-05 00:17:38 +04:00
err = snd_fw_transaction (
c - > resources . unit , TCODE_LOCK_COMPARE_SWAP ,
2014-04-25 17:44:56 +04:00
pcr_address ( c ) , buffer , 8 ,
2011-09-05 00:17:38 +04:00
FW_FIXED_GENERATION | c - > resources . generation ) ;
if ( err < 0 ) {
if ( err = = - EAGAIN & &
bus_reset_handling = = SUCCEED_ON_BUS_RESET )
err = 0 ;
return err ;
}
if ( buffer [ 0 ] = = old_arg ) /* success? */
break ;
if ( check ) {
err = check ( c , buffer [ 0 ] ) ;
if ( err < 0 )
return err ;
}
2011-03-15 09:53:21 +03:00
}
c - > last_pcr_value = buffer [ 1 ] ;
return 0 ;
}
/**
* cmp_connection_init - initializes a connection manager
* @ c : the connection manager to initialize
* @ unit : a unit of the target device
2014-11-18 17:59:40 +03:00
* @ direction : input or output
2014-04-25 17:44:54 +04:00
* @ pcr_index : the index of the iPCR / oPCR on the target device
2011-03-15 09:53:21 +03:00
*/
int cmp_connection_init ( struct cmp_connection * c ,
struct fw_unit * unit ,
2014-04-25 17:44:55 +04:00
enum cmp_direction direction ,
2014-04-25 17:44:54 +04:00
unsigned int pcr_index )
2011-03-15 09:53:21 +03:00
{
2014-04-25 17:44:54 +04:00
__be32 mpr_be ;
u32 mpr ;
2011-03-15 09:53:21 +03:00
int err ;
2014-04-25 17:44:56 +04:00
c - > direction = direction ;
2011-03-15 09:53:21 +03:00
err = snd_fw_transaction ( unit , TCODE_READ_QUADLET_REQUEST ,
2014-04-25 17:44:56 +04:00
mpr_address ( c ) , & mpr_be , 4 , 0 ) ;
2011-03-15 09:53:21 +03:00
if ( err < 0 )
return err ;
2014-04-25 17:44:54 +04:00
mpr = be32_to_cpu ( mpr_be ) ;
2011-03-15 09:53:21 +03:00
2014-04-25 17:44:54 +04:00
if ( pcr_index > = ( mpr & MPR_PLUGS_MASK ) )
2011-03-15 09:53:21 +03:00
return - EINVAL ;
2011-03-15 09:55:50 +03:00
err = fw_iso_resources_init ( & c - > resources , unit ) ;
if ( err < 0 )
return err ;
2011-03-15 09:53:21 +03:00
c - > connected = false ;
mutex_init ( & c - > mutex ) ;
c - > last_pcr_value = cpu_to_be32 ( 0x80000000 ) ;
2014-04-25 17:44:54 +04:00
c - > pcr_index = pcr_index ;
c - > max_speed = ( mpr & MPR_SPEED_MASK ) > > MPR_SPEED_SHIFT ;
2011-03-15 09:53:21 +03:00
if ( c - > max_speed = = SCODE_BETA )
2014-04-25 17:44:54 +04:00
c - > max_speed + = ( mpr & MPR_XSPEED_MASK ) > > MPR_XSPEED_SHIFT ;
2011-03-15 09:53:21 +03:00
return 0 ;
}
EXPORT_SYMBOL ( cmp_connection_init ) ;
2014-04-25 17:44:57 +04:00
/**
* cmp_connection_check_used - check connection is already esablished or not
* @ c : the connection manager to be checked
2014-11-18 17:59:40 +03:00
* @ used : the pointer to store the result of checking the connection
2014-04-25 17:44:57 +04:00
*/
int cmp_connection_check_used ( struct cmp_connection * c , bool * used )
{
__be32 pcr ;
int err ;
err = snd_fw_transaction (
c - > resources . unit , TCODE_READ_QUADLET_REQUEST ,
pcr_address ( c ) , & pcr , 4 , 0 ) ;
if ( err > = 0 )
2014-05-27 19:14:37 +04:00
* used = ! ! ( pcr & cpu_to_be32 ( PCR_BCAST_CONN |
PCR_P2P_CONN_MASK ) ) ;
2014-04-25 17:44:57 +04:00
return err ;
}
EXPORT_SYMBOL ( cmp_connection_check_used ) ;
2011-03-15 09:53:21 +03:00
/**
* cmp_connection_destroy - free connection manager resources
* @ c : the connection manager
*/
void cmp_connection_destroy ( struct cmp_connection * c )
{
WARN_ON ( c - > connected ) ;
mutex_destroy ( & c - > mutex ) ;
fw_iso_resources_destroy ( & c - > resources ) ;
}
EXPORT_SYMBOL ( cmp_connection_destroy ) ;
static __be32 ipcr_set_modify ( struct cmp_connection * c , __be32 ipcr )
{
2014-04-25 17:44:54 +04:00
ipcr & = ~ cpu_to_be32 ( PCR_BCAST_CONN |
PCR_P2P_CONN_MASK |
PCR_CHANNEL_MASK ) ;
ipcr | = cpu_to_be32 ( 1 < < PCR_P2P_CONN_SHIFT ) ;
ipcr | = cpu_to_be32 ( c - > resources . channel < < PCR_CHANNEL_SHIFT ) ;
2011-03-15 09:53:21 +03:00
return ipcr ;
}
2014-04-25 17:44:56 +04:00
static int get_overhead_id ( struct cmp_connection * c )
{
int id ;
/*
* apply " oPCR overhead ID encoding "
* the encoding table can convert up to 512.
* here the value over 512 is converted as the same way as 512.
*/
for ( id = 1 ; id < 16 ; id + + ) {
if ( c - > resources . bandwidth_overhead < ( id < < 5 ) )
break ;
}
if ( id = = 16 )
id = 0 ;
return id ;
}
static __be32 opcr_set_modify ( struct cmp_connection * c , __be32 opcr )
{
unsigned int spd , xspd ;
/* generate speed and extended speed field value */
if ( c - > speed > SCODE_400 ) {
spd = SCODE_800 ;
xspd = c - > speed - SCODE_800 ;
} else {
spd = c - > speed ;
xspd = 0 ;
}
opcr & = ~ cpu_to_be32 ( PCR_BCAST_CONN |
PCR_P2P_CONN_MASK |
OPCR_XSPEED_MASK |
PCR_CHANNEL_MASK |
OPCR_SPEED_MASK |
OPCR_OVERHEAD_ID_MASK ) ;
opcr | = cpu_to_be32 ( 1 < < PCR_P2P_CONN_SHIFT ) ;
opcr | = cpu_to_be32 ( xspd < < OPCR_XSPEED_SHIFT ) ;
opcr | = cpu_to_be32 ( c - > resources . channel < < PCR_CHANNEL_SHIFT ) ;
opcr | = cpu_to_be32 ( spd < < OPCR_SPEED_SHIFT ) ;
opcr | = cpu_to_be32 ( get_overhead_id ( c ) < < OPCR_OVERHEAD_ID_SHIFT ) ;
return opcr ;
}
2014-04-25 17:44:54 +04:00
static int pcr_set_check ( struct cmp_connection * c , __be32 pcr )
2011-03-15 09:53:21 +03:00
{
2014-04-25 17:44:54 +04:00
if ( pcr & cpu_to_be32 ( PCR_BCAST_CONN |
PCR_P2P_CONN_MASK ) ) {
2011-03-15 09:53:21 +03:00
cmp_error ( c , " plug is already in use \n " ) ;
return - EBUSY ;
}
2014-04-25 17:44:54 +04:00
if ( ! ( pcr & cpu_to_be32 ( PCR_ONLINE ) ) ) {
2011-03-15 09:53:21 +03:00
cmp_error ( c , " plug is not on-line \n " ) ;
return - ECONNREFUSED ;
}
return 0 ;
}
/**
* cmp_connection_establish - establish a connection to the target
* @ c : the connection manager
* @ max_payload_bytes : the amount of data ( including CIP headers ) per packet
*
* This function establishes a point - to - point connection from the local
* computer to the target by allocating isochronous resources ( channel and
2014-04-25 17:44:54 +04:00
* bandwidth ) and setting the target ' s input / output plug control register .
* When this function succeeds , the caller is responsible for starting
* transmitting packets .
2011-03-15 09:53:21 +03:00
*/
int cmp_connection_establish ( struct cmp_connection * c ,
unsigned int max_payload_bytes )
{
int err ;
if ( WARN_ON ( c - > connected ) )
return - EISCONN ;
c - > speed = min ( c - > max_speed ,
fw_parent_device ( c - > resources . unit ) - > max_speed ) ;
mutex_lock ( & c - > mutex ) ;
retry_after_bus_reset :
err = fw_iso_resources_allocate ( & c - > resources ,
max_payload_bytes , c - > speed ) ;
if ( err < 0 )
goto err_mutex ;
2014-04-25 17:44:56 +04:00
if ( c - > direction = = CMP_OUTPUT )
err = pcr_modify ( c , opcr_set_modify , pcr_set_check ,
ABORT_ON_BUS_RESET ) ;
else
err = pcr_modify ( c , ipcr_set_modify , pcr_set_check ,
ABORT_ON_BUS_RESET ) ;
2011-03-15 09:53:21 +03:00
if ( err = = - EAGAIN ) {
fw_iso_resources_free ( & c - > resources ) ;
goto retry_after_bus_reset ;
}
if ( err < 0 )
goto err_resources ;
c - > connected = true ;
mutex_unlock ( & c - > mutex ) ;
return 0 ;
err_resources :
fw_iso_resources_free ( & c - > resources ) ;
err_mutex :
mutex_unlock ( & c - > mutex ) ;
return err ;
}
EXPORT_SYMBOL ( cmp_connection_establish ) ;
/**
* cmp_connection_update - update the connection after a bus reset
* @ c : the connection manager
*
2014-04-25 17:44:54 +04:00
* This function must be called from the driver ' s . update handler to
* reestablish any connection that might have been active .
2011-03-15 09:53:21 +03:00
*
* Returns zero on success , or a negative error code . On an error , the
* connection is broken and the caller must stop transmitting iso packets .
*/
int cmp_connection_update ( struct cmp_connection * c )
{
int err ;
mutex_lock ( & c - > mutex ) ;
if ( ! c - > connected ) {
mutex_unlock ( & c - > mutex ) ;
return 0 ;
}
err = fw_iso_resources_update ( & c - > resources ) ;
if ( err < 0 )
goto err_unconnect ;
2014-04-25 17:44:56 +04:00
if ( c - > direction = = CMP_OUTPUT )
err = pcr_modify ( c , opcr_set_modify , pcr_set_check ,
SUCCEED_ON_BUS_RESET ) ;
else
err = pcr_modify ( c , ipcr_set_modify , pcr_set_check ,
SUCCEED_ON_BUS_RESET ) ;
2011-03-15 09:53:21 +03:00
if ( err < 0 )
goto err_resources ;
mutex_unlock ( & c - > mutex ) ;
return 0 ;
err_resources :
fw_iso_resources_free ( & c - > resources ) ;
err_unconnect :
c - > connected = false ;
mutex_unlock ( & c - > mutex ) ;
return err ;
}
EXPORT_SYMBOL ( cmp_connection_update ) ;
2014-04-25 17:44:54 +04:00
static __be32 pcr_break_modify ( struct cmp_connection * c , __be32 pcr )
2011-03-15 09:53:21 +03:00
{
2014-04-25 17:44:54 +04:00
return pcr & ~ cpu_to_be32 ( PCR_BCAST_CONN | PCR_P2P_CONN_MASK ) ;
2011-03-15 09:53:21 +03:00
}
/**
* cmp_connection_break - break the connection to the target
* @ c : the connection manager
*
2014-04-25 17:44:54 +04:00
* This function deactives the connection in the target ' s input / output plug
* control register , and frees the isochronous resources of the connection .
* Before calling this function , the caller should cease transmitting packets .
2011-03-15 09:53:21 +03:00
*/
void cmp_connection_break ( struct cmp_connection * c )
{
int err ;
mutex_lock ( & c - > mutex ) ;
if ( ! c - > connected ) {
mutex_unlock ( & c - > mutex ) ;
return ;
}
2014-04-25 17:44:54 +04:00
err = pcr_modify ( c , pcr_break_modify , NULL , SUCCEED_ON_BUS_RESET ) ;
2011-03-15 09:53:21 +03:00
if ( err < 0 )
cmp_error ( c , " plug is still connected \n " ) ;
fw_iso_resources_free ( & c - > resources ) ;
c - > connected = false ;
mutex_unlock ( & c - > mutex ) ;
}
EXPORT_SYMBOL ( cmp_connection_break ) ;