2018-07-05 14:54:25 +01:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018, Linaro Limited
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/list.h>
# include <linux/slimbus.h>
# include <uapi/sound/asound.h>
# include "slimbus.h"
/**
* struct segdist_code - Segment Distributions code from
* Table 20 of SLIMbus Specs Version 2.0
*
* @ ratem : Channel Rate Multipler ( Segments per Superframe )
* @ seg_interval : Number of slots between the first Slot of Segment
* and the first slot of the next consecutive Segment .
* @ segdist_code : Segment Distribution Code SD [ 11 : 0 ]
* @ seg_offset_mask : Segment offset mask in SD [ 11 : 0 ]
* @ segdist_codes : List of all possible Segmet Distribution codes .
*/
static const struct segdist_code {
int ratem ;
int seg_interval ;
int segdist_code ;
u32 seg_offset_mask ;
} segdist_codes [ ] = {
{ 1 , 1536 , 0x200 , 0xdff } ,
{ 2 , 768 , 0x100 , 0xcff } ,
{ 4 , 384 , 0x080 , 0xc7f } ,
{ 8 , 192 , 0x040 , 0xc3f } ,
{ 16 , 96 , 0x020 , 0xc1f } ,
{ 32 , 48 , 0x010 , 0xc0f } ,
{ 64 , 24 , 0x008 , 0xc07 } ,
{ 128 , 12 , 0x004 , 0xc03 } ,
{ 256 , 6 , 0x002 , 0xc01 } ,
{ 512 , 3 , 0x001 , 0xc00 } ,
{ 3 , 512 , 0xe00 , 0x1ff } ,
{ 6 , 256 , 0xd00 , 0x0ff } ,
{ 12 , 128 , 0xc80 , 0x07f } ,
{ 24 , 64 , 0xc40 , 0x03f } ,
{ 48 , 32 , 0xc20 , 0x01f } ,
{ 96 , 16 , 0xc10 , 0x00f } ,
{ 192 , 8 , 0xc08 , 0x007 } ,
{ 364 , 4 , 0xc04 , 0x003 } ,
{ 768 , 2 , 0xc02 , 0x001 } ,
} ;
/*
* Presence Rate table for all Natural Frequencies
* The Presence rate of a constant bitrate stream is mean flow rate of the
* stream expressed in occupied Segments of that Data Channel per second .
* Table 66 from SLIMbus 2.0 Specs
*
* Index of the table corresponds to Presence rate code for the respective rate
* in the table .
*/
static const int slim_presence_rate_table [ ] = {
0 , /* Not Indicated */
12000 ,
24000 ,
48000 ,
96000 ,
192000 ,
384000 ,
768000 ,
0 , /* Reserved */
110250 ,
220500 ,
441000 ,
882000 ,
176400 ,
352800 ,
705600 ,
4000 ,
8000 ,
16000 ,
32000 ,
64000 ,
128000 ,
256000 ,
512000 ,
} ;
2019-05-24 14:57:40 -06:00
/**
2018-07-05 14:54:25 +01:00
* slim_stream_allocate ( ) - Allocate a new SLIMbus Stream
* @ dev : Slim device to be associated with
* @ name : name of the stream
*
* This is very first call for SLIMbus streaming , this API will allocate
* a new SLIMbus stream and return a valid stream runtime pointer for client
* to use it in subsequent stream apis . state of stream is set to ALLOCATED
*
* Return : valid pointer on success and error code on failure .
* From ASoC DPCM framework , this state is linked to startup ( ) operation .
*/
struct slim_stream_runtime * slim_stream_allocate ( struct slim_device * dev ,
const char * name )
{
struct slim_stream_runtime * rt ;
rt = kzalloc ( sizeof ( * rt ) , GFP_KERNEL ) ;
if ( ! rt )
return ERR_PTR ( - ENOMEM ) ;
rt - > name = kasprintf ( GFP_KERNEL , " slim-%s " , name ) ;
if ( ! rt - > name ) {
kfree ( rt ) ;
return ERR_PTR ( - ENOMEM ) ;
}
rt - > dev = dev ;
spin_lock ( & dev - > stream_list_lock ) ;
list_add_tail ( & rt - > node , & dev - > stream_list ) ;
spin_unlock ( & dev - > stream_list_lock ) ;
return rt ;
}
EXPORT_SYMBOL_GPL ( slim_stream_allocate ) ;
static int slim_connect_port_channel ( struct slim_stream_runtime * stream ,
struct slim_port * port )
{
struct slim_device * sdev = stream - > dev ;
u8 wbuf [ 2 ] ;
struct slim_val_inf msg = { 0 , 2 , NULL , wbuf , NULL } ;
u8 mc = SLIM_MSG_MC_CONNECT_SOURCE ;
DEFINE_SLIM_LDEST_TXN ( txn , mc , 6 , stream - > dev - > laddr , & msg ) ;
if ( port - > direction = = SLIM_PORT_SINK )
txn . mc = SLIM_MSG_MC_CONNECT_SINK ;
wbuf [ 0 ] = port - > id ;
wbuf [ 1 ] = port - > ch . id ;
port - > ch . state = SLIM_CH_STATE_ASSOCIATED ;
port - > state = SLIM_PORT_UNCONFIGURED ;
return slim_do_transfer ( sdev - > ctrl , & txn ) ;
}
static int slim_disconnect_port ( struct slim_stream_runtime * stream ,
struct slim_port * port )
{
struct slim_device * sdev = stream - > dev ;
u8 wbuf [ 1 ] ;
struct slim_val_inf msg = { 0 , 1 , NULL , wbuf , NULL } ;
u8 mc = SLIM_MSG_MC_DISCONNECT_PORT ;
DEFINE_SLIM_LDEST_TXN ( txn , mc , 5 , stream - > dev - > laddr , & msg ) ;
wbuf [ 0 ] = port - > id ;
port - > ch . state = SLIM_CH_STATE_DISCONNECTED ;
port - > state = SLIM_PORT_DISCONNECTED ;
return slim_do_transfer ( sdev - > ctrl , & txn ) ;
}
static int slim_deactivate_remove_channel ( struct slim_stream_runtime * stream ,
struct slim_port * port )
{
struct slim_device * sdev = stream - > dev ;
u8 wbuf [ 1 ] ;
struct slim_val_inf msg = { 0 , 1 , NULL , wbuf , NULL } ;
u8 mc = SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL ;
DEFINE_SLIM_LDEST_TXN ( txn , mc , 5 , stream - > dev - > laddr , & msg ) ;
int ret ;
wbuf [ 0 ] = port - > ch . id ;
ret = slim_do_transfer ( sdev - > ctrl , & txn ) ;
if ( ret )
return ret ;
txn . mc = SLIM_MSG_MC_NEXT_REMOVE_CHANNEL ;
port - > ch . state = SLIM_CH_STATE_REMOVED ;
return slim_do_transfer ( sdev - > ctrl , & txn ) ;
}
static int slim_get_prate_code ( int rate )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( slim_presence_rate_table ) ; i + + ) {
if ( rate = = slim_presence_rate_table [ i ] )
return i ;
}
return - EINVAL ;
}
2019-05-24 14:57:40 -06:00
/**
2018-07-05 14:54:25 +01:00
* slim_stream_prepare ( ) - Prepare a SLIMbus Stream
*
* @ rt : instance of slim stream runtime to configure
* @ cfg : new configuration for the stream
*
* This API will configure SLIMbus stream with config parameters from cfg .
* return zero on success and error code on failure . From ASoC DPCM framework ,
* this state is linked to hw_params ( ) operation .
*/
int slim_stream_prepare ( struct slim_stream_runtime * rt ,
struct slim_stream_config * cfg )
{
struct slim_controller * ctrl = rt - > dev - > ctrl ;
struct slim_port * port ;
int num_ports , i , port_id ;
if ( rt - > ports ) {
dev_err ( & rt - > dev - > dev , " Stream already Prepared \n " ) ;
return - EINVAL ;
}
num_ports = hweight32 ( cfg - > port_mask ) ;
rt - > ports = kcalloc ( num_ports , sizeof ( * port ) , GFP_KERNEL ) ;
if ( ! rt - > ports )
return - ENOMEM ;
rt - > num_ports = num_ports ;
rt - > rate = cfg - > rate ;
rt - > bps = cfg - > bps ;
rt - > direction = cfg - > direction ;
if ( cfg - > rate % ctrl - > a_framer - > superfreq ) {
/*
* data rate not exactly multiple of super frame ,
* use PUSH / PULL protocol
*/
if ( cfg - > direction = = SNDRV_PCM_STREAM_PLAYBACK )
rt - > prot = SLIM_PROTO_PUSH ;
else
rt - > prot = SLIM_PROTO_PULL ;
} else {
rt - > prot = SLIM_PROTO_ISO ;
}
rt - > ratem = cfg - > rate / ctrl - > a_framer - > superfreq ;
i = 0 ;
for_each_set_bit ( port_id , & cfg - > port_mask , SLIM_DEVICE_MAX_PORTS ) {
port = & rt - > ports [ i ] ;
port - > state = SLIM_PORT_DISCONNECTED ;
port - > id = port_id ;
port - > ch . prrate = slim_get_prate_code ( cfg - > rate ) ;
port - > ch . id = cfg - > chs [ i ] ;
port - > ch . data_fmt = SLIM_CH_DATA_FMT_NOT_DEFINED ;
port - > ch . aux_fmt = SLIM_CH_AUX_FMT_NOT_APPLICABLE ;
port - > ch . state = SLIM_CH_STATE_ALLOCATED ;
if ( cfg - > direction = = SNDRV_PCM_STREAM_PLAYBACK )
port - > direction = SLIM_PORT_SINK ;
else
port - > direction = SLIM_PORT_SOURCE ;
slim_connect_port_channel ( rt , port ) ;
i + + ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( slim_stream_prepare ) ;
static int slim_define_channel_content ( struct slim_stream_runtime * stream ,
struct slim_port * port )
{
struct slim_device * sdev = stream - > dev ;
u8 wbuf [ 4 ] ;
struct slim_val_inf msg = { 0 , 4 , NULL , wbuf , NULL } ;
u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CONTENT ;
DEFINE_SLIM_LDEST_TXN ( txn , mc , 8 , stream - > dev - > laddr , & msg ) ;
wbuf [ 0 ] = port - > ch . id ;
wbuf [ 1 ] = port - > ch . prrate ;
/* Frequency Locked for ISO Protocol */
if ( stream - > prot ! = SLIM_PROTO_ISO )
wbuf [ 1 ] | = SLIM_CHANNEL_CONTENT_FL ;
wbuf [ 2 ] = port - > ch . data_fmt | ( port - > ch . aux_fmt < < 4 ) ;
wbuf [ 3 ] = stream - > bps / SLIM_SLOT_LEN_BITS ;
port - > ch . state = SLIM_CH_STATE_CONTENT_DEFINED ;
return slim_do_transfer ( sdev - > ctrl , & txn ) ;
}
static int slim_get_segdist_code ( int ratem )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( segdist_codes ) ; i + + ) {
if ( segdist_codes [ i ] . ratem = = ratem )
return segdist_codes [ i ] . segdist_code ;
}
return - EINVAL ;
}
static int slim_define_channel ( struct slim_stream_runtime * stream ,
struct slim_port * port )
{
struct slim_device * sdev = stream - > dev ;
u8 wbuf [ 4 ] ;
struct slim_val_inf msg = { 0 , 4 , NULL , wbuf , NULL } ;
u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CHANNEL ;
DEFINE_SLIM_LDEST_TXN ( txn , mc , 8 , stream - > dev - > laddr , & msg ) ;
port - > ch . seg_dist = slim_get_segdist_code ( stream - > ratem ) ;
wbuf [ 0 ] = port - > ch . id ;
wbuf [ 1 ] = port - > ch . seg_dist & 0xFF ;
wbuf [ 2 ] = ( stream - > prot < < 4 ) | ( ( port - > ch . seg_dist & 0xF00 ) > > 8 ) ;
if ( stream - > prot = = SLIM_PROTO_ISO )
wbuf [ 3 ] = stream - > bps / SLIM_SLOT_LEN_BITS ;
else
wbuf [ 3 ] = stream - > bps / SLIM_SLOT_LEN_BITS + 1 ;
port - > ch . state = SLIM_CH_STATE_DEFINED ;
return slim_do_transfer ( sdev - > ctrl , & txn ) ;
}
static int slim_activate_channel ( struct slim_stream_runtime * stream ,
struct slim_port * port )
{
struct slim_device * sdev = stream - > dev ;
u8 wbuf [ 1 ] ;
struct slim_val_inf msg = { 0 , 1 , NULL , wbuf , NULL } ;
u8 mc = SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL ;
DEFINE_SLIM_LDEST_TXN ( txn , mc , 5 , stream - > dev - > laddr , & msg ) ;
txn . msg - > num_bytes = 1 ;
txn . msg - > wbuf = wbuf ;
wbuf [ 0 ] = port - > ch . id ;
port - > ch . state = SLIM_CH_STATE_ACTIVE ;
return slim_do_transfer ( sdev - > ctrl , & txn ) ;
}
2019-05-24 14:57:40 -06:00
/**
2018-07-05 14:54:25 +01:00
* slim_stream_enable ( ) - Enable a prepared SLIMbus Stream
*
* @ stream : instance of slim stream runtime to enable
*
* This API will enable all the ports and channels associated with
* SLIMbus stream
*
* Return : zero on success and error code on failure . From ASoC DPCM framework ,
* this state is linked to trigger ( ) start operation .
*/
int slim_stream_enable ( struct slim_stream_runtime * stream )
{
DEFINE_SLIM_BCAST_TXN ( txn , SLIM_MSG_MC_BEGIN_RECONFIGURATION ,
3 , SLIM_LA_MANAGER , NULL ) ;
struct slim_controller * ctrl = stream - > dev - > ctrl ;
int ret , i ;
if ( ctrl - > enable_stream ) {
ret = ctrl - > enable_stream ( stream ) ;
if ( ret )
return ret ;
for ( i = 0 ; i < stream - > num_ports ; i + + )
stream - > ports [ i ] . ch . state = SLIM_CH_STATE_ACTIVE ;
return ret ;
}
ret = slim_do_transfer ( ctrl , & txn ) ;
if ( ret )
return ret ;
/* define channels first before activating them */
for ( i = 0 ; i < stream - > num_ports ; i + + ) {
struct slim_port * port = & stream - > ports [ i ] ;
slim_define_channel ( stream , port ) ;
slim_define_channel_content ( stream , port ) ;
}
for ( i = 0 ; i < stream - > num_ports ; i + + ) {
struct slim_port * port = & stream - > ports [ i ] ;
slim_activate_channel ( stream , port ) ;
port - > state = SLIM_PORT_CONFIGURED ;
}
txn . mc = SLIM_MSG_MC_RECONFIGURE_NOW ;
return slim_do_transfer ( ctrl , & txn ) ;
}
EXPORT_SYMBOL_GPL ( slim_stream_enable ) ;
2019-05-24 14:57:40 -06:00
/**
2018-07-05 14:54:25 +01:00
* slim_stream_disable ( ) - Disable a SLIMbus Stream
*
* @ stream : instance of slim stream runtime to disable
*
* This API will disable all the ports and channels associated with
* SLIMbus stream
*
* Return : zero on success and error code on failure . From ASoC DPCM framework ,
* this state is linked to trigger ( ) pause operation .
*/
int slim_stream_disable ( struct slim_stream_runtime * stream )
{
DEFINE_SLIM_BCAST_TXN ( txn , SLIM_MSG_MC_BEGIN_RECONFIGURATION ,
3 , SLIM_LA_MANAGER , NULL ) ;
struct slim_controller * ctrl = stream - > dev - > ctrl ;
int ret , i ;
if ( ctrl - > disable_stream )
ctrl - > disable_stream ( stream ) ;
ret = slim_do_transfer ( ctrl , & txn ) ;
if ( ret )
return ret ;
for ( i = 0 ; i < stream - > num_ports ; i + + )
slim_deactivate_remove_channel ( stream , & stream - > ports [ i ] ) ;
txn . mc = SLIM_MSG_MC_RECONFIGURE_NOW ;
return slim_do_transfer ( ctrl , & txn ) ;
}
EXPORT_SYMBOL_GPL ( slim_stream_disable ) ;
2019-05-24 14:57:40 -06:00
/**
2018-07-05 14:54:25 +01:00
* slim_stream_unprepare ( ) - Un - prepare a SLIMbus Stream
*
* @ stream : instance of slim stream runtime to unprepare
*
* This API will un allocate all the ports and channels associated with
* SLIMbus stream
*
* Return : zero on success and error code on failure . From ASoC DPCM framework ,
* this state is linked to trigger ( ) stop operation .
*/
int slim_stream_unprepare ( struct slim_stream_runtime * stream )
{
int i ;
for ( i = 0 ; i < stream - > num_ports ; i + + )
slim_disconnect_port ( stream , & stream - > ports [ i ] ) ;
kfree ( stream - > ports ) ;
stream - > ports = NULL ;
stream - > num_ports = 0 ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( slim_stream_unprepare ) ;
2019-05-24 14:57:40 -06:00
/**
2018-07-05 14:54:25 +01:00
* slim_stream_free ( ) - Free a SLIMbus Stream
*
* @ stream : instance of slim stream runtime to free
*
* This API will un allocate all the memory associated with
* slim stream runtime , user is not allowed to make an dereference
* to stream after this call .
*
* Return : zero on success and error code on failure . From ASoC DPCM framework ,
* this state is linked to shutdown ( ) operation .
*/
int slim_stream_free ( struct slim_stream_runtime * stream )
{
struct slim_device * sdev = stream - > dev ;
spin_lock ( & sdev - > stream_list_lock ) ;
list_del ( & stream - > node ) ;
spin_unlock ( & sdev - > stream_list_lock ) ;
kfree ( stream - > name ) ;
kfree ( stream ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( slim_stream_free ) ;