2019-05-27 08:55:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2013-01-05 02:18:43 +01:00
/*
* tegra20_ac97 . c - Tegra20 AC97 platform driver
*
* Copyright ( c ) 2012 Lucas Stach < dev @ lynxeye . de >
*
* Partly based on code copyright / by :
*
* Copyright ( c ) 2011 , 2012 Toradex Inc .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/gpio.h>
# include <linux/io.h>
# include <linux/jiffies.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_gpio.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/regmap.h>
2021-03-14 18:44:43 +03:00
# include <linux/reset.h>
2013-01-05 02:18:43 +01:00
# include <linux/slab.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
2013-04-03 11:06:03 +02:00
# include <sound/dmaengine_pcm.h>
2013-01-05 02:18:43 +01:00
# include "tegra20_ac97.h"
# define DRV_NAME "tegra20-ac97"
static struct tegra20_ac97 * workdata ;
static void tegra20_ac97_codec_reset ( struct snd_ac97 * ac97 )
{
u32 readback ;
unsigned long timeout ;
/* reset line is not driven by DAC pad group, have to toggle GPIO */
gpio_set_value ( workdata - > reset_gpio , 0 ) ;
udelay ( 2 ) ;
gpio_set_value ( workdata - > reset_gpio , 1 ) ;
udelay ( 2 ) ;
timeout = jiffies + msecs_to_jiffies ( 100 ) ;
do {
regmap_read ( workdata - > regmap , TEGRA20_AC97_STATUS1 , & readback ) ;
if ( readback & TEGRA20_AC97_STATUS1_CODEC1_RDY )
break ;
usleep_range ( 1000 , 2000 ) ;
} while ( ! time_after ( jiffies , timeout ) ) ;
}
static void tegra20_ac97_codec_warm_reset ( struct snd_ac97 * ac97 )
{
u32 readback ;
unsigned long timeout ;
/*
* although sync line is driven by the DAC pad group warm reset using
* the controller cmd is not working , have to toggle sync line
* manually .
*/
gpio_request ( workdata - > sync_gpio , " codec-sync " ) ;
gpio_direction_output ( workdata - > sync_gpio , 1 ) ;
udelay ( 2 ) ;
gpio_set_value ( workdata - > sync_gpio , 0 ) ;
udelay ( 2 ) ;
gpio_free ( workdata - > sync_gpio ) ;
timeout = jiffies + msecs_to_jiffies ( 100 ) ;
do {
regmap_read ( workdata - > regmap , TEGRA20_AC97_STATUS1 , & readback ) ;
if ( readback & TEGRA20_AC97_STATUS1_CODEC1_RDY )
break ;
usleep_range ( 1000 , 2000 ) ;
} while ( ! time_after ( jiffies , timeout ) ) ;
}
static unsigned short tegra20_ac97_codec_read ( struct snd_ac97 * ac97_snd ,
unsigned short reg )
{
u32 readback ;
unsigned long timeout ;
regmap_write ( workdata - > regmap , TEGRA20_AC97_CMD ,
( ( ( reg | 0x80 ) < < TEGRA20_AC97_CMD_CMD_ADDR_SHIFT ) &
TEGRA20_AC97_CMD_CMD_ADDR_MASK ) |
TEGRA20_AC97_CMD_BUSY ) ;
timeout = jiffies + msecs_to_jiffies ( 100 ) ;
do {
regmap_read ( workdata - > regmap , TEGRA20_AC97_STATUS1 , & readback ) ;
if ( readback & TEGRA20_AC97_STATUS1_STA_VALID1 )
break ;
usleep_range ( 1000 , 2000 ) ;
} while ( ! time_after ( jiffies , timeout ) ) ;
return ( ( readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK ) > >
TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT ) ;
}
static void tegra20_ac97_codec_write ( struct snd_ac97 * ac97_snd ,
unsigned short reg , unsigned short val )
{
u32 readback ;
unsigned long timeout ;
regmap_write ( workdata - > regmap , TEGRA20_AC97_CMD ,
( ( reg < < TEGRA20_AC97_CMD_CMD_ADDR_SHIFT ) &
TEGRA20_AC97_CMD_CMD_ADDR_MASK ) |
( ( val < < TEGRA20_AC97_CMD_CMD_DATA_SHIFT ) &
TEGRA20_AC97_CMD_CMD_DATA_MASK ) |
TEGRA20_AC97_CMD_BUSY ) ;
timeout = jiffies + msecs_to_jiffies ( 100 ) ;
do {
regmap_read ( workdata - > regmap , TEGRA20_AC97_CMD , & readback ) ;
if ( ! ( readback & TEGRA20_AC97_CMD_BUSY ) )
break ;
usleep_range ( 1000 , 2000 ) ;
} while ( ! time_after ( jiffies , timeout ) ) ;
}
2013-06-26 12:45:59 +01:00
static struct snd_ac97_bus_ops tegra20_ac97_ops = {
2013-01-05 02:18:43 +01:00
. read = tegra20_ac97_codec_read ,
. write = tegra20_ac97_codec_write ,
. reset = tegra20_ac97_codec_reset ,
. warm_reset = tegra20_ac97_codec_warm_reset ,
} ;
static inline void tegra20_ac97_start_playback ( struct tegra20_ac97 * ac97 )
{
regmap_update_bits ( ac97 - > regmap , TEGRA20_AC97_FIFO1_SCR ,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN ,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN ) ;
regmap_update_bits ( ac97 - > regmap , TEGRA20_AC97_CTRL ,
TEGRA20_AC97_CTRL_PCM_DAC_EN |
TEGRA20_AC97_CTRL_STM_EN ,
TEGRA20_AC97_CTRL_PCM_DAC_EN |
TEGRA20_AC97_CTRL_STM_EN ) ;
}
static inline void tegra20_ac97_stop_playback ( struct tegra20_ac97 * ac97 )
{
regmap_update_bits ( ac97 - > regmap , TEGRA20_AC97_FIFO1_SCR ,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN , 0 ) ;
regmap_update_bits ( ac97 - > regmap , TEGRA20_AC97_CTRL ,
TEGRA20_AC97_CTRL_PCM_DAC_EN , 0 ) ;
}
static inline void tegra20_ac97_start_capture ( struct tegra20_ac97 * ac97 )
{
regmap_update_bits ( ac97 - > regmap , TEGRA20_AC97_FIFO1_SCR ,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN ,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN ) ;
}
static inline void tegra20_ac97_stop_capture ( struct tegra20_ac97 * ac97 )
{
regmap_update_bits ( ac97 - > regmap , TEGRA20_AC97_FIFO1_SCR ,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN , 0 ) ;
}
static int tegra20_ac97_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
{
struct tegra20_ac97 * ac97 = snd_soc_dai_get_drvdata ( dai ) ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
case SNDRV_PCM_TRIGGER_RESUME :
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
tegra20_ac97_start_playback ( ac97 ) ;
else
tegra20_ac97_start_capture ( ac97 ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
case SNDRV_PCM_TRIGGER_SUSPEND :
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
tegra20_ac97_stop_playback ( ac97 ) ;
else
tegra20_ac97_stop_capture ( ac97 ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = {
. trigger = tegra20_ac97_trigger ,
} ;
static int tegra20_ac97_probe ( struct snd_soc_dai * dai )
{
struct tegra20_ac97 * ac97 = snd_soc_dai_get_drvdata ( dai ) ;
dai - > capture_dma_data = & ac97 - > capture_dma_data ;
dai - > playback_dma_data = & ac97 - > playback_dma_data ;
return 0 ;
}
static struct snd_soc_dai_driver tegra20_ac97_dai = {
. name = " tegra-ac97-pcm " ,
. probe = tegra20_ac97_probe ,
. playback = {
. stream_name = " PCM Playback " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
} ,
. capture = {
. stream_name = " PCM Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
} ,
. ops = & tegra20_ac97_dai_ops ,
} ;
2013-03-21 03:37:44 -07:00
static const struct snd_soc_component_driver tegra20_ac97_component = {
. name = DRV_NAME ,
} ;
2013-01-05 02:18:43 +01:00
static bool tegra20_ac97_wr_rd_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case TEGRA20_AC97_CTRL :
case TEGRA20_AC97_CMD :
case TEGRA20_AC97_STATUS1 :
case TEGRA20_AC97_FIFO1_SCR :
case TEGRA20_AC97_FIFO_TX1 :
case TEGRA20_AC97_FIFO_RX1 :
return true ;
default :
break ;
}
return false ;
}
static bool tegra20_ac97_volatile_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case TEGRA20_AC97_STATUS1 :
case TEGRA20_AC97_FIFO1_SCR :
case TEGRA20_AC97_FIFO_TX1 :
case TEGRA20_AC97_FIFO_RX1 :
return true ;
default :
break ;
}
return false ;
}
static bool tegra20_ac97_precious_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case TEGRA20_AC97_FIFO_TX1 :
case TEGRA20_AC97_FIFO_RX1 :
return true ;
default :
break ;
}
return false ;
}
static const struct regmap_config tegra20_ac97_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = TEGRA20_AC97_FIFO_RX1 ,
. writeable_reg = tegra20_ac97_wr_rd_reg ,
. readable_reg = tegra20_ac97_wr_rd_reg ,
. volatile_reg = tegra20_ac97_volatile_reg ,
. precious_reg = tegra20_ac97_precious_reg ,
2014-03-17 22:08:49 -07:00
. cache_type = REGCACHE_FLAT ,
2013-01-05 02:18:43 +01:00
} ;
static int tegra20_ac97_platform_probe ( struct platform_device * pdev )
{
struct tegra20_ac97 * ac97 ;
2013-06-28 21:53:25 +02:00
struct resource * mem ;
2013-01-05 02:18:43 +01:00
void __iomem * regs ;
int ret = 0 ;
ac97 = devm_kzalloc ( & pdev - > dev , sizeof ( struct tegra20_ac97 ) ,
GFP_KERNEL ) ;
if ( ! ac97 ) {
ret = - ENOMEM ;
goto err ;
}
dev_set_drvdata ( & pdev - > dev , ac97 ) ;
2021-03-14 18:44:43 +03:00
ac97 - > reset = devm_reset_control_get_exclusive ( & pdev - > dev , " ac97 " ) ;
if ( IS_ERR ( ac97 - > reset ) ) {
dev_err ( & pdev - > dev , " Can't retrieve ac97 reset \n " ) ;
return PTR_ERR ( ac97 - > reset ) ;
}
2013-06-26 12:16:50 +01:00
ac97 - > clk_ac97 = devm_clk_get ( & pdev - > dev , NULL ) ;
2013-01-05 02:18:43 +01:00
if ( IS_ERR ( ac97 - > clk_ac97 ) ) {
dev_err ( & pdev - > dev , " Can't retrieve ac97 clock \n " ) ;
ret = PTR_ERR ( ac97 - > clk_ac97 ) ;
goto err ;
}
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-06-26 12:18:39 +01:00
regs = devm_ioremap_resource ( & pdev - > dev , mem ) ;
if ( IS_ERR ( regs ) ) {
ret = PTR_ERR ( regs ) ;
2013-01-05 02:18:43 +01:00
goto err_clk_put ;
}
ac97 - > regmap = devm_regmap_init_mmio ( & pdev - > dev , regs ,
& tegra20_ac97_regmap_config ) ;
if ( IS_ERR ( ac97 - > regmap ) ) {
dev_err ( & pdev - > dev , " regmap init failed \n " ) ;
ret = PTR_ERR ( ac97 - > regmap ) ;
goto err_clk_put ;
}
ac97 - > reset_gpio = of_get_named_gpio ( pdev - > dev . of_node ,
" nvidia,codec-reset-gpio " , 0 ) ;
if ( gpio_is_valid ( ac97 - > reset_gpio ) ) {
ret = devm_gpio_request_one ( & pdev - > dev , ac97 - > reset_gpio ,
GPIOF_OUT_INIT_HIGH , " codec-reset " ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " could not get codec-reset GPIO \n " ) ;
goto err_clk_put ;
}
} else {
dev_err ( & pdev - > dev , " no codec-reset GPIO supplied \n " ) ;
goto err_clk_put ;
}
ac97 - > sync_gpio = of_get_named_gpio ( pdev - > dev . of_node ,
" nvidia,codec-sync-gpio " , 0 ) ;
if ( ! gpio_is_valid ( ac97 - > sync_gpio ) ) {
dev_err ( & pdev - > dev , " no codec-sync GPIO supplied \n " ) ;
goto err_clk_put ;
}
ac97 - > capture_dma_data . addr = mem - > start + TEGRA20_AC97_FIFO_RX1 ;
2013-04-03 11:06:03 +02:00
ac97 - > capture_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES ;
ac97 - > capture_dma_data . maxburst = 4 ;
2013-01-05 02:18:43 +01:00
ac97 - > playback_dma_data . addr = mem - > start + TEGRA20_AC97_FIFO_TX1 ;
2013-07-21 10:34:09 +08:00
ac97 - > playback_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES ;
ac97 - > playback_dma_data . maxburst = 4 ;
2013-01-05 02:18:43 +01:00
2021-03-14 18:44:43 +03:00
ret = reset_control_assert ( ac97 - > reset ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to assert AC'97 reset: %d \n " , ret ) ;
goto err_clk_put ;
}
2013-01-05 02:18:43 +01:00
ret = clk_prepare_enable ( ac97 - > clk_ac97 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " clk_enable failed: %d \n " , ret ) ;
2018-07-20 10:04:23 +02:00
goto err_clk_put ;
2013-01-05 02:18:43 +01:00
}
2021-03-14 18:44:43 +03:00
usleep_range ( 10 , 100 ) ;
ret = reset_control_deassert ( ac97 - > reset ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to deassert AC'97 reset: %d \n " , ret ) ;
goto err_clk_disable_unprepare ;
}
2013-06-26 12:45:59 +01:00
ret = snd_soc_set_ac97_ops ( & tegra20_ac97_ops ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to set AC'97 ops: %d \n " , ret ) ;
2013-12-14 13:29:11 +08:00
goto err_clk_disable_unprepare ;
2013-06-26 12:45:59 +01:00
}
2013-06-26 12:27:58 +01:00
ret = snd_soc_register_component ( & pdev - > dev , & tegra20_ac97_component ,
& tegra20_ac97_dai , 1 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Could not register DAI: %d \n " , ret ) ;
ret = - ENOMEM ;
2013-12-14 13:29:11 +08:00
goto err_clk_disable_unprepare ;
2013-06-26 12:27:58 +01:00
}
ret = tegra_pcm_platform_register ( & pdev - > dev ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Could not register PCM: %d \n " , ret ) ;
goto err_unregister_component ;
}
2013-01-05 02:18:43 +01:00
/* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */
workdata = ac97 ;
return 0 ;
2013-03-21 03:37:44 -07:00
err_unregister_component :
snd_soc_unregister_component ( & pdev - > dev ) ;
2013-12-14 13:29:11 +08:00
err_clk_disable_unprepare :
clk_disable_unprepare ( ac97 - > clk_ac97 ) ;
2013-01-05 02:18:43 +01:00
err_clk_put :
err :
2013-06-26 12:45:59 +01:00
snd_soc_set_ac97_ops ( NULL ) ;
2013-01-05 02:18:43 +01:00
return ret ;
}
static int tegra20_ac97_platform_remove ( struct platform_device * pdev )
{
struct tegra20_ac97 * ac97 = dev_get_drvdata ( & pdev - > dev ) ;
tegra_pcm_platform_unregister ( & pdev - > dev ) ;
2013-03-21 03:37:44 -07:00
snd_soc_unregister_component ( & pdev - > dev ) ;
2013-01-05 02:18:43 +01:00
clk_disable_unprepare ( ac97 - > clk_ac97 ) ;
2013-06-26 12:45:59 +01:00
snd_soc_set_ac97_ops ( NULL ) ;
2013-01-05 02:18:43 +01:00
return 0 ;
}
2013-01-24 14:51:16 +05:30
static const struct of_device_id tegra20_ac97_of_match [ ] = {
2013-01-05 02:18:43 +01:00
{ . compatible = " nvidia,tegra20-ac97 " , } ,
{ } ,
} ;
static struct platform_driver tegra20_ac97_driver = {
. driver = {
. name = DRV_NAME ,
. of_match_table = tegra20_ac97_of_match ,
} ,
. probe = tegra20_ac97_platform_probe ,
. remove = tegra20_ac97_platform_remove ,
} ;
module_platform_driver ( tegra20_ac97_driver ) ;
MODULE_AUTHOR ( " Lucas Stach " ) ;
MODULE_DESCRIPTION ( " Tegra20 AC97 ASoC driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
MODULE_DEVICE_TABLE ( of , tegra20_ac97_of_match ) ;