2018-07-17 18:42:59 +03:00
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
# include <linux/clk.h>
# include <linux/module.h>
# include <linux/of_platform.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/soc-dai.h>
# include "axg-tdm.h"
enum {
TDM_IFACE_PAD ,
TDM_IFACE_LOOPBACK ,
} ;
static unsigned int axg_tdm_slots_total ( u32 * mask )
{
unsigned int slots = 0 ;
int i ;
if ( ! mask )
return 0 ;
/* Count the total number of slots provided by all 4 lanes */
for ( i = 0 ; i < AXG_TDM_NUM_LANES ; i + + )
slots + = hweight32 ( mask [ i ] ) ;
return slots ;
}
int axg_tdm_set_tdm_slots ( struct snd_soc_dai * dai , u32 * tx_mask ,
u32 * rx_mask , unsigned int slots ,
unsigned int slot_width )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
struct axg_tdm_stream * tx = ( struct axg_tdm_stream * )
dai - > playback_dma_data ;
struct axg_tdm_stream * rx = ( struct axg_tdm_stream * )
dai - > capture_dma_data ;
unsigned int tx_slots , rx_slots ;
2018-08-27 17:21:07 +03:00
unsigned int fmt = 0 ;
2018-07-17 18:42:59 +03:00
tx_slots = axg_tdm_slots_total ( tx_mask ) ;
rx_slots = axg_tdm_slots_total ( rx_mask ) ;
/* We should at least have a slot for a valid interface */
if ( ! tx_slots & & ! rx_slots ) {
dev_err ( dai - > dev , " interface has no slot \n " ) ;
return - EINVAL ;
}
iface - > slots = slots ;
switch ( slot_width ) {
case 0 :
2018-08-27 17:21:07 +03:00
slot_width = 32 ;
/* Fall-through */
2018-07-17 18:42:59 +03:00
case 32 :
2018-08-27 17:21:07 +03:00
fmt | = SNDRV_PCM_FMTBIT_S32_LE ;
/* Fall-through */
case 24 :
fmt | = SNDRV_PCM_FMTBIT_S24_LE ;
fmt | = SNDRV_PCM_FMTBIT_S20_LE ;
/* Fall-through */
case 16 :
fmt | = SNDRV_PCM_FMTBIT_S16_LE ;
/* Fall-through */
case 8 :
fmt | = SNDRV_PCM_FMTBIT_S8 ;
2018-07-17 18:42:59 +03:00
break ;
default :
dev_err ( dai - > dev , " unsupported slot width: %d \n " , slot_width ) ;
return - EINVAL ;
}
2018-08-27 17:21:07 +03:00
iface - > slot_width = slot_width ;
/* Amend the dai driver and let dpcm merge do its job */
if ( tx ) {
tx - > mask = tx_mask ;
dai - > driver - > playback . channels_max = tx_slots ;
dai - > driver - > playback . formats = fmt ;
}
if ( rx ) {
rx - > mask = rx_mask ;
dai - > driver - > capture . channels_max = rx_slots ;
dai - > driver - > capture . formats = fmt ;
}
2018-07-17 18:42:59 +03:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_set_tdm_slots ) ;
static int axg_tdm_iface_set_sysclk ( struct snd_soc_dai * dai , int clk_id ,
unsigned int freq , int dir )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
int ret = - ENOTSUPP ;
if ( dir = = SND_SOC_CLOCK_OUT & & clk_id = = 0 ) {
if ( ! iface - > mclk ) {
dev_warn ( dai - > dev , " master clock not provided \n " ) ;
} else {
ret = clk_set_rate ( iface - > mclk , freq ) ;
if ( ! ret )
iface - > mclk_rate = freq ;
}
}
return ret ;
}
static int axg_tdm_iface_set_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
/* These modes are not supported */
if ( fmt & ( SND_SOC_DAIFMT_CBS_CFM | SND_SOC_DAIFMT_CBM_CFS ) ) {
dev_err ( dai - > dev , " only CBS_CFS and CBM_CFM are supported \n " ) ;
return - EINVAL ;
}
/* If the TDM interface is the clock master, it requires mclk */
if ( ! iface - > mclk & & ( fmt & SND_SOC_DAIFMT_CBS_CFS ) ) {
dev_err ( dai - > dev , " cpu clock master: mclk missing \n " ) ;
return - ENODEV ;
}
iface - > fmt = fmt ;
return 0 ;
}
static int axg_tdm_iface_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
struct axg_tdm_stream * ts =
snd_soc_dai_get_dma_data ( dai , substream ) ;
int ret ;
if ( ! axg_tdm_slots_total ( ts - > mask ) ) {
dev_err ( dai - > dev , " interface has not slots \n " ) ;
return - EINVAL ;
}
/* Apply component wide rate symmetry */
2020-05-15 03:47:36 +03:00
if ( snd_soc_component_active ( dai - > component ) ) {
2018-07-17 18:42:59 +03:00
ret = snd_pcm_hw_constraint_single ( substream - > runtime ,
SNDRV_PCM_HW_PARAM_RATE ,
iface - > rate ) ;
if ( ret < 0 ) {
dev_err ( dai - > dev ,
" can't set iface rate constraint \n " ) ;
return ret ;
}
}
return 0 ;
}
static int axg_tdm_iface_set_stream ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
struct axg_tdm_stream * ts = snd_soc_dai_get_dma_data ( dai , substream ) ;
unsigned int channels = params_channels ( params ) ;
unsigned int width = params_width ( params ) ;
/* Save rate and sample_bits for component symmetry */
iface - > rate = params_rate ( params ) ;
/* Make sure this interface can cope with the stream */
if ( axg_tdm_slots_total ( ts - > mask ) < channels ) {
dev_err ( dai - > dev , " not enough slots for channels \n " ) ;
return - EINVAL ;
}
if ( iface - > slot_width < width ) {
dev_err ( dai - > dev , " incompatible slots width for stream \n " ) ;
return - EINVAL ;
}
/* Save the parameter for tdmout/tdmin widgets */
ts - > physical_width = params_physical_width ( params ) ;
ts - > width = params_width ( params ) ;
ts - > channels = params_channels ( params ) ;
return 0 ;
}
static int axg_tdm_iface_set_lrclk ( struct snd_soc_dai * dai ,
struct snd_pcm_hw_params * params )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
unsigned int ratio_num ;
int ret ;
ret = clk_set_rate ( iface - > lrclk , params_rate ( params ) ) ;
if ( ret ) {
dev_err ( dai - > dev , " setting sample clock failed: %d \n " , ret ) ;
return ret ;
}
switch ( iface - > fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
case SND_SOC_DAIFMT_LEFT_J :
case SND_SOC_DAIFMT_RIGHT_J :
/* 50% duty cycle ratio */
ratio_num = 1 ;
break ;
case SND_SOC_DAIFMT_DSP_A :
case SND_SOC_DAIFMT_DSP_B :
/*
* A zero duty cycle ratio will result in setting the mininum
* ratio possible which , for this clock , is 1 cycle of the
* parent bclk clock high and the rest low , This is exactly
* what we want here .
*/
ratio_num = 0 ;
break ;
default :
return - EINVAL ;
}
ret = clk_set_duty_cycle ( iface - > lrclk , ratio_num , 2 ) ;
if ( ret ) {
dev_err ( dai - > dev ,
" setting sample clock duty cycle failed: %d \n " , ret ) ;
return ret ;
}
/* Set sample clock inversion */
ret = clk_set_phase ( iface - > lrclk ,
axg_tdm_lrclk_invert ( iface - > fmt ) ? 180 : 0 ) ;
if ( ret ) {
dev_err ( dai - > dev ,
" setting sample clock phase failed: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static int axg_tdm_iface_set_sclk ( struct snd_soc_dai * dai ,
struct snd_pcm_hw_params * params )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
unsigned long srate ;
int ret ;
srate = iface - > slots * iface - > slot_width * params_rate ( params ) ;
if ( ! iface - > mclk_rate ) {
/* If no specific mclk is requested, default to bit clock * 4 */
clk_set_rate ( iface - > mclk , 4 * srate ) ;
} else {
/* Check if we can actually get the bit clock from mclk */
if ( iface - > mclk_rate % srate ) {
dev_err ( dai - > dev ,
" can't derive sclk %lu from mclk %lu \n " ,
srate , iface - > mclk_rate ) ;
return - EINVAL ;
}
}
ret = clk_set_rate ( iface - > sclk , srate ) ;
if ( ret ) {
dev_err ( dai - > dev , " setting bit clock failed: %d \n " , ret ) ;
return ret ;
}
/* Set the bit clock inversion */
ret = clk_set_phase ( iface - > sclk ,
axg_tdm_sclk_invert ( iface - > fmt ) ? 0 : 180 ) ;
if ( ret ) {
dev_err ( dai - > dev , " setting bit clock phase failed: %d \n " , ret ) ;
return ret ;
}
return ret ;
}
static int axg_tdm_iface_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
int ret ;
switch ( iface - > fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
case SND_SOC_DAIFMT_LEFT_J :
case SND_SOC_DAIFMT_RIGHT_J :
if ( iface - > slots > 2 ) {
dev_err ( dai - > dev , " bad slot number for format: %d \n " ,
iface - > slots ) ;
return - EINVAL ;
}
break ;
2019-06-13 14:42:33 +03:00
case SND_SOC_DAIFMT_DSP_A :
case SND_SOC_DAIFMT_DSP_B :
2018-07-17 18:42:59 +03:00
break ;
default :
dev_err ( dai - > dev , " unsupported dai format \n " ) ;
return - EINVAL ;
}
ret = axg_tdm_iface_set_stream ( substream , params , dai ) ;
if ( ret )
return ret ;
if ( iface - > fmt & SND_SOC_DAIFMT_CBS_CFS ) {
ret = axg_tdm_iface_set_sclk ( dai , params ) ;
if ( ret )
return ret ;
ret = axg_tdm_iface_set_lrclk ( dai , params ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int axg_tdm_iface_hw_free ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct axg_tdm_stream * ts = snd_soc_dai_get_dma_data ( dai , substream ) ;
/* Stop all attached formatters */
axg_tdm_stream_stop ( ts ) ;
return 0 ;
}
static int axg_tdm_iface_prepare ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct axg_tdm_stream * ts = snd_soc_dai_get_dma_data ( dai , substream ) ;
/* Force all attached formatters to update */
return axg_tdm_stream_reset ( ts ) ;
}
static int axg_tdm_iface_remove_dai ( struct snd_soc_dai * dai )
{
if ( dai - > capture_dma_data )
axg_tdm_stream_free ( dai - > capture_dma_data ) ;
if ( dai - > playback_dma_data )
axg_tdm_stream_free ( dai - > playback_dma_data ) ;
return 0 ;
}
static int axg_tdm_iface_probe_dai ( struct snd_soc_dai * dai )
{
struct axg_tdm_iface * iface = snd_soc_dai_get_drvdata ( dai ) ;
if ( dai - > capture_widget ) {
dai - > capture_dma_data = axg_tdm_stream_alloc ( iface ) ;
if ( ! dai - > capture_dma_data )
return - ENOMEM ;
}
if ( dai - > playback_widget ) {
dai - > playback_dma_data = axg_tdm_stream_alloc ( iface ) ;
if ( ! dai - > playback_dma_data ) {
axg_tdm_iface_remove_dai ( dai ) ;
return - ENOMEM ;
}
}
return 0 ;
}
static const struct snd_soc_dai_ops axg_tdm_iface_ops = {
. set_sysclk = axg_tdm_iface_set_sysclk ,
. set_fmt = axg_tdm_iface_set_fmt ,
. startup = axg_tdm_iface_startup ,
. hw_params = axg_tdm_iface_hw_params ,
. prepare = axg_tdm_iface_prepare ,
. hw_free = axg_tdm_iface_hw_free ,
} ;
/* TDM Backend DAIs */
static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv [ ] = {
[ TDM_IFACE_PAD ] = {
. name = " TDM Pad " ,
. playback = {
. stream_name = " Playback " ,
. channels_min = 1 ,
. channels_max = AXG_TDM_CHANNEL_MAX ,
. rates = AXG_TDM_RATES ,
. formats = AXG_TDM_FORMATS ,
} ,
. capture = {
. stream_name = " Capture " ,
. channels_min = 1 ,
. channels_max = AXG_TDM_CHANNEL_MAX ,
. rates = AXG_TDM_RATES ,
. formats = AXG_TDM_FORMATS ,
} ,
. id = TDM_IFACE_PAD ,
. ops = & axg_tdm_iface_ops ,
. probe = axg_tdm_iface_probe_dai ,
. remove = axg_tdm_iface_remove_dai ,
} ,
[ TDM_IFACE_LOOPBACK ] = {
. name = " TDM Loopback " ,
. capture = {
. stream_name = " Loopback " ,
. channels_min = 1 ,
. channels_max = AXG_TDM_CHANNEL_MAX ,
. rates = AXG_TDM_RATES ,
. formats = AXG_TDM_FORMATS ,
} ,
. id = TDM_IFACE_LOOPBACK ,
. ops = & axg_tdm_iface_ops ,
. probe = axg_tdm_iface_probe_dai ,
. remove = axg_tdm_iface_remove_dai ,
} ,
} ;
static int axg_tdm_iface_set_bias_level ( struct snd_soc_component * component ,
enum snd_soc_bias_level level )
{
struct axg_tdm_iface * iface = snd_soc_component_get_drvdata ( component ) ;
enum snd_soc_bias_level now =
snd_soc_component_get_bias_level ( component ) ;
int ret = 0 ;
switch ( level ) {
case SND_SOC_BIAS_PREPARE :
if ( now = = SND_SOC_BIAS_STANDBY )
ret = clk_prepare_enable ( iface - > mclk ) ;
break ;
case SND_SOC_BIAS_STANDBY :
if ( now = = SND_SOC_BIAS_PREPARE )
clk_disable_unprepare ( iface - > mclk ) ;
break ;
case SND_SOC_BIAS_OFF :
case SND_SOC_BIAS_ON :
break ;
}
return ret ;
}
static const struct snd_soc_component_driver axg_tdm_iface_component_drv = {
. set_bias_level = axg_tdm_iface_set_bias_level ,
} ;
static const struct of_device_id axg_tdm_iface_of_match [ ] = {
{ . compatible = " amlogic,axg-tdm-iface " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , axg_tdm_iface_of_match ) ;
static int axg_tdm_iface_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct snd_soc_dai_driver * dai_drv ;
struct axg_tdm_iface * iface ;
int ret , i ;
iface = devm_kzalloc ( dev , sizeof ( * iface ) , GFP_KERNEL ) ;
if ( ! iface )
return - ENOMEM ;
platform_set_drvdata ( pdev , iface ) ;
/*
* Duplicate dai driver : depending on the slot masks configuration
* We ' ll change the number of channel provided by DAI stream , so dpcm
* channel merge can be done properly
*/
dai_drv = devm_kcalloc ( dev , ARRAY_SIZE ( axg_tdm_iface_dai_drv ) ,
sizeof ( * dai_drv ) , GFP_KERNEL ) ;
if ( ! dai_drv )
return - ENOMEM ;
for ( i = 0 ; i < ARRAY_SIZE ( axg_tdm_iface_dai_drv ) ; i + + )
memcpy ( & dai_drv [ i ] , & axg_tdm_iface_dai_drv [ i ] ,
sizeof ( * dai_drv ) ) ;
/* Bit clock provided on the pad */
iface - > sclk = devm_clk_get ( dev , " sclk " ) ;
if ( IS_ERR ( iface - > sclk ) ) {
ret = PTR_ERR ( iface - > sclk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get sclk: %d \n " , ret ) ;
return ret ;
}
/* Sample clock provided on the pad */
iface - > lrclk = devm_clk_get ( dev , " lrclk " ) ;
if ( IS_ERR ( iface - > lrclk ) ) {
ret = PTR_ERR ( iface - > lrclk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get lrclk: %d \n " , ret ) ;
return ret ;
}
/*
* mclk maybe be missing when the cpu dai is in slave mode and
* the codec does not require it to provide a master clock .
* At this point , ignore the error if mclk is missing . We ' ll
* throw an error if the cpu dai is master and mclk is missing
*/
iface - > mclk = devm_clk_get ( dev , " mclk " ) ;
if ( IS_ERR ( iface - > mclk ) ) {
ret = PTR_ERR ( iface - > mclk ) ;
if ( ret = = - ENOENT ) {
iface - > mclk = NULL ;
} else {
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get mclk: %d \n " , ret ) ;
return ret ;
}
}
return devm_snd_soc_register_component ( dev ,
& axg_tdm_iface_component_drv , dai_drv ,
ARRAY_SIZE ( axg_tdm_iface_dai_drv ) ) ;
}
static struct platform_driver axg_tdm_iface_pdrv = {
. probe = axg_tdm_iface_probe ,
. driver = {
. name = " axg-tdm-iface " ,
. of_match_table = axg_tdm_iface_of_match ,
} ,
} ;
module_platform_driver ( axg_tdm_iface_pdrv ) ;
MODULE_DESCRIPTION ( " Amlogic AXG TDM interface driver " ) ;
MODULE_AUTHOR ( " Jerome Brunet <jbrunet@baylibre.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;