2018-07-17 18:42:58 +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 <linux/regmap.h>
2019-07-03 15:07:49 +03:00
# include <linux/reset.h>
2018-07-17 18:42:58 +03:00
# include <sound/soc.h>
# include "axg-tdm-formatter.h"
struct axg_tdm_formatter {
struct list_head list ;
struct axg_tdm_stream * stream ;
const struct axg_tdm_formatter_driver * drv ;
struct clk * pclk ;
struct clk * sclk ;
struct clk * lrclk ;
struct clk * sclk_sel ;
struct clk * lrclk_sel ;
2019-07-03 15:07:49 +03:00
struct reset_control * reset ;
2018-07-17 18:42:58 +03:00
bool enabled ;
struct regmap * map ;
} ;
int axg_tdm_formatter_set_channel_masks ( struct regmap * map ,
struct axg_tdm_stream * ts ,
unsigned int offset )
{
unsigned int val , ch = ts - > channels ;
unsigned long mask ;
int i , j ;
/*
* Distribute the channels of the stream over the available slots
* of each TDM lane
*/
for ( i = 0 ; i < AXG_TDM_NUM_LANES ; i + + ) {
val = 0 ;
mask = ts - > mask [ i ] ;
for ( j = find_first_bit ( & mask , 32 ) ;
( j < 32 ) & & ch ;
j = find_next_bit ( & mask , 32 , j + 1 ) ) {
val | = 1 < < j ;
ch - = 1 ;
}
regmap_write ( map , offset , val ) ;
offset + = regmap_get_reg_stride ( map ) ;
}
/*
* If we still have channel left at the end of the process , it means
* the stream has more channels than we can accommodate and we should
* have caught this earlier .
*/
if ( WARN_ON ( ch ! = 0 ) ) {
pr_err ( " channel mask error \n " ) ;
return - EINVAL ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_formatter_set_channel_masks ) ;
static int axg_tdm_formatter_enable ( struct axg_tdm_formatter * formatter )
{
struct axg_tdm_stream * ts = formatter - > stream ;
2019-04-04 14:17:32 +03:00
bool invert = formatter - > drv - > quirks - > invert_sclk ;
2018-07-17 18:42:58 +03:00
int ret ;
/* Do nothing if the formatter is already enabled */
if ( formatter - > enabled )
return 0 ;
2019-07-03 15:07:49 +03:00
/*
* On the g12a ( and possibly other SoCs ) , when a stream using
* multiple lanes is restarted , it will sometimes not start
* from the first lane , but randomly from another used one .
* The result is an unexpected and random channel shift .
*
* The hypothesis is that an HW counter is not properly reset
* and the formatter simply starts on the lane it stopped
* before . Unfortunately , there does not seems to be a way to
* reset this through the registers of the block .
*
* However , the g12a has indenpendent reset lines for each audio
* devices . Using this reset before each start solves the issue .
*/
ret = reset_control_reset ( formatter - > reset ) ;
if ( ret )
return ret ;
2018-07-17 18:42:58 +03:00
/*
* If sclk is inverted , invert it back and provide the inversion
* required by the formatter
*/
invert ^ = axg_tdm_sclk_invert ( ts - > iface - > fmt ) ;
ret = clk_set_phase ( formatter - > sclk , invert ? 180 : 0 ) ;
if ( ret )
return ret ;
/* Setup the stream parameter in the formatter */
2019-04-04 14:17:32 +03:00
ret = formatter - > drv - > ops - > prepare ( formatter - > map ,
formatter - > drv - > quirks ,
formatter - > stream ) ;
2018-07-17 18:42:58 +03:00
if ( ret )
return ret ;
/* Enable the signal clocks feeding the formatter */
ret = clk_prepare_enable ( formatter - > sclk ) ;
if ( ret )
return ret ;
ret = clk_prepare_enable ( formatter - > lrclk ) ;
if ( ret ) {
clk_disable_unprepare ( formatter - > sclk ) ;
return ret ;
}
/* Finally, actually enable the formatter */
formatter - > drv - > ops - > enable ( formatter - > map ) ;
formatter - > enabled = true ;
return 0 ;
}
static void axg_tdm_formatter_disable ( struct axg_tdm_formatter * formatter )
{
/* Do nothing if the formatter is already disabled */
if ( ! formatter - > enabled )
return ;
formatter - > drv - > ops - > disable ( formatter - > map ) ;
clk_disable_unprepare ( formatter - > lrclk ) ;
clk_disable_unprepare ( formatter - > sclk ) ;
formatter - > enabled = false ;
}
static int axg_tdm_formatter_attach ( struct axg_tdm_formatter * formatter )
{
struct axg_tdm_stream * ts = formatter - > stream ;
int ret = 0 ;
mutex_lock ( & ts - > lock ) ;
/* Catch up if the stream is already running when we attach */
if ( ts - > ready ) {
ret = axg_tdm_formatter_enable ( formatter ) ;
if ( ret ) {
pr_err ( " failed to enable formatter \n " ) ;
goto out ;
}
}
list_add_tail ( & formatter - > list , & ts - > formatter_list ) ;
out :
mutex_unlock ( & ts - > lock ) ;
return ret ;
}
static void axg_tdm_formatter_dettach ( struct axg_tdm_formatter * formatter )
{
struct axg_tdm_stream * ts = formatter - > stream ;
mutex_lock ( & ts - > lock ) ;
list_del ( & formatter - > list ) ;
mutex_unlock ( & ts - > lock ) ;
axg_tdm_formatter_disable ( formatter ) ;
}
static int axg_tdm_formatter_power_up ( struct axg_tdm_formatter * formatter ,
struct snd_soc_dapm_widget * w )
{
struct axg_tdm_stream * ts = formatter - > drv - > ops - > get_stream ( w ) ;
int ret ;
/*
* If we don ' t get a stream at this stage , it would mean that the
* widget is powering up but is not attached to any backend DAI .
* It should not happen , ever !
*/
if ( WARN_ON ( ! ts ) )
return - ENODEV ;
/* Clock our device */
ret = clk_prepare_enable ( formatter - > pclk ) ;
if ( ret )
return ret ;
/* Reparent the bit clock to the TDM interface */
ret = clk_set_parent ( formatter - > sclk_sel , ts - > iface - > sclk ) ;
if ( ret )
goto disable_pclk ;
/* Reparent the sample clock to the TDM interface */
ret = clk_set_parent ( formatter - > lrclk_sel , ts - > iface - > lrclk ) ;
if ( ret )
goto disable_pclk ;
formatter - > stream = ts ;
ret = axg_tdm_formatter_attach ( formatter ) ;
if ( ret )
goto disable_pclk ;
return 0 ;
disable_pclk :
clk_disable_unprepare ( formatter - > pclk ) ;
return ret ;
}
static void axg_tdm_formatter_power_down ( struct axg_tdm_formatter * formatter )
{
axg_tdm_formatter_dettach ( formatter ) ;
clk_disable_unprepare ( formatter - > pclk ) ;
formatter - > stream = NULL ;
}
int axg_tdm_formatter_event ( struct snd_soc_dapm_widget * w ,
struct snd_kcontrol * control ,
int event )
{
struct snd_soc_component * c = snd_soc_dapm_to_component ( w - > dapm ) ;
struct axg_tdm_formatter * formatter = snd_soc_component_get_drvdata ( c ) ;
int ret = 0 ;
switch ( event ) {
case SND_SOC_DAPM_PRE_PMU :
ret = axg_tdm_formatter_power_up ( formatter , w ) ;
break ;
case SND_SOC_DAPM_PRE_PMD :
axg_tdm_formatter_power_down ( formatter ) ;
break ;
default :
dev_err ( c - > dev , " Unexpected event %d \n " , event ) ;
return - EINVAL ;
}
return ret ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_formatter_event ) ;
int axg_tdm_formatter_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
const struct axg_tdm_formatter_driver * drv ;
struct axg_tdm_formatter * formatter ;
void __iomem * regs ;
int ret ;
drv = of_device_get_match_data ( dev ) ;
if ( ! drv ) {
dev_err ( dev , " failed to match device \n " ) ;
return - ENODEV ;
}
formatter = devm_kzalloc ( dev , sizeof ( * formatter ) , GFP_KERNEL ) ;
if ( ! formatter )
return - ENOMEM ;
platform_set_drvdata ( pdev , formatter ) ;
formatter - > drv = drv ;
2019-07-27 18:07:30 +03:00
regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-07-17 18:42:58 +03:00
if ( IS_ERR ( regs ) )
return PTR_ERR ( regs ) ;
formatter - > map = devm_regmap_init_mmio ( dev , regs , drv - > regmap_cfg ) ;
if ( IS_ERR ( formatter - > map ) ) {
dev_err ( dev , " failed to init regmap: %ld \n " ,
PTR_ERR ( formatter - > map ) ) ;
return PTR_ERR ( formatter - > map ) ;
}
/* Peripharal clock */
formatter - > pclk = devm_clk_get ( dev , " pclk " ) ;
if ( IS_ERR ( formatter - > pclk ) ) {
ret = PTR_ERR ( formatter - > pclk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get pclk: %d \n " , ret ) ;
return ret ;
}
/* Formatter bit clock */
formatter - > sclk = devm_clk_get ( dev , " sclk " ) ;
if ( IS_ERR ( formatter - > sclk ) ) {
ret = PTR_ERR ( formatter - > sclk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get sclk: %d \n " , ret ) ;
return ret ;
}
/* Formatter sample clock */
formatter - > lrclk = devm_clk_get ( dev , " lrclk " ) ;
if ( IS_ERR ( formatter - > lrclk ) ) {
ret = PTR_ERR ( formatter - > lrclk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get lrclk: %d \n " , ret ) ;
return ret ;
}
/* Formatter bit clock input multiplexer */
formatter - > sclk_sel = devm_clk_get ( dev , " sclk_sel " ) ;
if ( IS_ERR ( formatter - > sclk_sel ) ) {
ret = PTR_ERR ( formatter - > sclk_sel ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get sclk_sel: %d \n " , ret ) ;
return ret ;
}
/* Formatter sample clock input multiplexer */
formatter - > lrclk_sel = devm_clk_get ( dev , " lrclk_sel " ) ;
if ( IS_ERR ( formatter - > lrclk_sel ) ) {
ret = PTR_ERR ( formatter - > lrclk_sel ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get lrclk_sel: %d \n " , ret ) ;
return ret ;
}
2019-07-03 15:07:49 +03:00
/* Formatter dedicated reset line */
2019-08-20 15:34:13 +03:00
formatter - > reset = devm_reset_control_get_optional_exclusive ( dev , NULL ) ;
2019-07-03 15:07:49 +03:00
if ( IS_ERR ( formatter - > reset ) ) {
ret = PTR_ERR ( formatter - > reset ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get reset: %d \n " , ret ) ;
return ret ;
}
2018-07-17 18:42:58 +03:00
return devm_snd_soc_register_component ( dev , drv - > component_drv ,
NULL , 0 ) ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_formatter_probe ) ;
int axg_tdm_stream_start ( struct axg_tdm_stream * ts )
{
struct axg_tdm_formatter * formatter ;
int ret = 0 ;
mutex_lock ( & ts - > lock ) ;
ts - > ready = true ;
/* Start all the formatters attached to the stream */
list_for_each_entry ( formatter , & ts - > formatter_list , list ) {
ret = axg_tdm_formatter_enable ( formatter ) ;
if ( ret ) {
pr_err ( " failed to start tdm stream \n " ) ;
goto out ;
}
}
out :
mutex_unlock ( & ts - > lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_stream_start ) ;
void axg_tdm_stream_stop ( struct axg_tdm_stream * ts )
{
struct axg_tdm_formatter * formatter ;
mutex_lock ( & ts - > lock ) ;
ts - > ready = false ;
/* Stop all the formatters attached to the stream */
list_for_each_entry ( formatter , & ts - > formatter_list , list ) {
axg_tdm_formatter_disable ( formatter ) ;
}
mutex_unlock ( & ts - > lock ) ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_stream_stop ) ;
struct axg_tdm_stream * axg_tdm_stream_alloc ( struct axg_tdm_iface * iface )
{
struct axg_tdm_stream * ts ;
ts = kzalloc ( sizeof ( * ts ) , GFP_KERNEL ) ;
if ( ts ) {
INIT_LIST_HEAD ( & ts - > formatter_list ) ;
mutex_init ( & ts - > lock ) ;
ts - > iface = iface ;
}
return ts ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_stream_alloc ) ;
void axg_tdm_stream_free ( struct axg_tdm_stream * ts )
{
/*
* If the list is not empty , it would mean that one of the formatter
* widget is still powered and attached to the interface while we
* we are removing the TDM DAI . It should not be possible
*/
WARN_ON ( ! list_empty ( & ts - > formatter_list ) ) ;
mutex_destroy ( & ts - > lock ) ;
kfree ( ts ) ;
}
EXPORT_SYMBOL_GPL ( axg_tdm_stream_free ) ;
MODULE_DESCRIPTION ( " Amlogic AXG TDM formatter driver " ) ;
MODULE_AUTHOR ( " Jerome Brunet <jbrunet@baylibre.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;