2018-01-10 11:13:10 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part the core part STM32 DFSDM driver
*
* Copyright ( C ) 2017 , STMicroelectronics - All Rights Reserved
* Author ( s ) : Arnaud Pouliquen < arnaud . pouliquen @ st . com > for STMicroelectronics .
*/
# include <linux/clk.h>
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/of_device.h>
2019-03-25 15:49:28 +01:00
# include <linux/pinctrl/consumer.h>
# include <linux/pm_runtime.h>
2018-01-10 11:13:10 +01:00
# include <linux/regmap.h>
# include <linux/slab.h>
# include "stm32-dfsdm.h"
struct stm32_dfsdm_dev_data {
unsigned int num_filters ;
unsigned int num_channels ;
const struct regmap_config * regmap_cfg ;
} ;
# define STM32H7_DFSDM_NUM_FILTERS 4
# define STM32H7_DFSDM_NUM_CHANNELS 8
2018-05-02 15:05:23 +02:00
# define STM32MP1_DFSDM_NUM_FILTERS 6
# define STM32MP1_DFSDM_NUM_CHANNELS 8
2018-01-10 11:13:10 +01:00
static bool stm32_dfsdm_volatile_reg ( struct device * dev , unsigned int reg )
{
if ( reg < DFSDM_FILTER_BASE_ADR )
return false ;
/*
* Mask is done on register to avoid to list registers of all
* filter instances .
*/
switch ( reg & DFSDM_FILTER_REG_MASK ) {
case DFSDM_CR1 ( 0 ) & DFSDM_FILTER_REG_MASK :
case DFSDM_ISR ( 0 ) & DFSDM_FILTER_REG_MASK :
case DFSDM_JDATAR ( 0 ) & DFSDM_FILTER_REG_MASK :
case DFSDM_RDATAR ( 0 ) & DFSDM_FILTER_REG_MASK :
return true ;
}
return false ;
}
static const struct regmap_config stm32h7_dfsdm_regmap_cfg = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = sizeof ( u32 ) ,
. max_register = 0x2B8 ,
. volatile_reg = stm32_dfsdm_volatile_reg ,
. fast_io = true ,
} ;
static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = {
. num_filters = STM32H7_DFSDM_NUM_FILTERS ,
. num_channels = STM32H7_DFSDM_NUM_CHANNELS ,
. regmap_cfg = & stm32h7_dfsdm_regmap_cfg ,
} ;
2018-05-02 15:05:23 +02:00
static const struct regmap_config stm32mp1_dfsdm_regmap_cfg = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = sizeof ( u32 ) ,
. max_register = 0x7fc ,
. volatile_reg = stm32_dfsdm_volatile_reg ,
. fast_io = true ,
} ;
static const struct stm32_dfsdm_dev_data stm32mp1_dfsdm_data = {
. num_filters = STM32MP1_DFSDM_NUM_FILTERS ,
. num_channels = STM32MP1_DFSDM_NUM_CHANNELS ,
. regmap_cfg = & stm32mp1_dfsdm_regmap_cfg ,
} ;
2018-01-10 11:13:10 +01:00
struct dfsdm_priv {
struct platform_device * pdev ; /* platform device */
struct stm32_dfsdm dfsdm ; /* common data exported for all instances */
unsigned int spi_clk_out_div ; /* SPI clkout divider value */
atomic_t n_active_ch ; /* number of current active channels */
struct clk * clk ; /* DFSDM clock */
struct clk * aclk ; /* audio clock */
} ;
2019-03-25 15:49:28 +01:00
static inline struct dfsdm_priv * to_stm32_dfsdm_priv ( struct stm32_dfsdm * dfsdm )
{
return container_of ( dfsdm , struct dfsdm_priv , dfsdm ) ;
}
static int stm32_dfsdm_clk_prepare_enable ( struct stm32_dfsdm * dfsdm )
{
struct dfsdm_priv * priv = to_stm32_dfsdm_priv ( dfsdm ) ;
int ret ;
ret = clk_prepare_enable ( priv - > clk ) ;
if ( ret | | ! priv - > aclk )
return ret ;
ret = clk_prepare_enable ( priv - > aclk ) ;
if ( ret )
clk_disable_unprepare ( priv - > clk ) ;
return ret ;
}
static void stm32_dfsdm_clk_disable_unprepare ( struct stm32_dfsdm * dfsdm )
{
struct dfsdm_priv * priv = to_stm32_dfsdm_priv ( dfsdm ) ;
2020-12-31 08:53:22 +00:00
clk_disable_unprepare ( priv - > aclk ) ;
2019-03-25 15:49:28 +01:00
clk_disable_unprepare ( priv - > clk ) ;
}
2018-01-10 11:13:10 +01:00
/**
* stm32_dfsdm_start_dfsdm - start global dfsdm interface .
*
* Enable interface if n_active_ch is not null .
* @ dfsdm : Handle used to retrieve dfsdm context .
*/
int stm32_dfsdm_start_dfsdm ( struct stm32_dfsdm * dfsdm )
{
2019-03-25 15:49:28 +01:00
struct dfsdm_priv * priv = to_stm32_dfsdm_priv ( dfsdm ) ;
2018-01-10 11:13:10 +01:00
struct device * dev = & priv - > pdev - > dev ;
2018-02-23 13:50:57 +01:00
unsigned int clk_div = priv - > spi_clk_out_div , clk_src ;
2018-01-10 11:13:10 +01:00
int ret ;
if ( atomic_inc_return ( & priv - > n_active_ch ) = = 1 ) {
2021-05-09 12:33:41 +01:00
ret = pm_runtime_resume_and_get ( dev ) ;
if ( ret < 0 )
2018-01-10 11:13:10 +01:00
goto error_ret ;
2018-02-23 13:50:57 +01:00
/* select clock source, e.g. 0 for "dfsdm" or 1 for "audio" */
clk_src = priv - > aclk ? 1 : 0 ;
ret = regmap_update_bits ( dfsdm - > regmap , DFSDM_CHCFGR1 ( 0 ) ,
DFSDM_CHCFGR1_CKOUTSRC_MASK ,
DFSDM_CHCFGR1_CKOUTSRC ( clk_src ) ) ;
if ( ret < 0 )
2019-03-25 15:49:28 +01:00
goto pm_put ;
2018-02-23 13:50:57 +01:00
2018-01-10 11:13:10 +01:00
/* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */
ret = regmap_update_bits ( dfsdm - > regmap , DFSDM_CHCFGR1 ( 0 ) ,
DFSDM_CHCFGR1_CKOUTDIV_MASK ,
DFSDM_CHCFGR1_CKOUTDIV ( clk_div ) ) ;
if ( ret < 0 )
2019-03-25 15:49:28 +01:00
goto pm_put ;
2018-01-10 11:13:10 +01:00
/* Global enable of DFSDM interface */
ret = regmap_update_bits ( dfsdm - > regmap , DFSDM_CHCFGR1 ( 0 ) ,
DFSDM_CHCFGR1_DFSDMEN_MASK ,
DFSDM_CHCFGR1_DFSDMEN ( 1 ) ) ;
if ( ret < 0 )
2019-03-25 15:49:28 +01:00
goto pm_put ;
2018-01-10 11:13:10 +01:00
}
dev_dbg ( dev , " %s: n_active_ch %d \n " , __func__ ,
atomic_read ( & priv - > n_active_ch ) ) ;
return 0 ;
2019-03-25 15:49:28 +01:00
pm_put :
pm_runtime_put_sync ( dev ) ;
2018-01-10 11:13:10 +01:00
error_ret :
atomic_dec ( & priv - > n_active_ch ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( stm32_dfsdm_start_dfsdm ) ;
/**
* stm32_dfsdm_stop_dfsdm - stop global DFSDM interface .
*
* Disable interface if n_active_ch is null
* @ dfsdm : Handle used to retrieve dfsdm context .
*/
int stm32_dfsdm_stop_dfsdm ( struct stm32_dfsdm * dfsdm )
{
2019-03-25 15:49:28 +01:00
struct dfsdm_priv * priv = to_stm32_dfsdm_priv ( dfsdm ) ;
2018-01-10 11:13:10 +01:00
int ret ;
if ( atomic_dec_and_test ( & priv - > n_active_ch ) ) {
/* Global disable of DFSDM interface */
ret = regmap_update_bits ( dfsdm - > regmap , DFSDM_CHCFGR1 ( 0 ) ,
DFSDM_CHCFGR1_DFSDMEN_MASK ,
DFSDM_CHCFGR1_DFSDMEN ( 0 ) ) ;
if ( ret < 0 )
return ret ;
/* Stop SPI CLKOUT */
ret = regmap_update_bits ( dfsdm - > regmap , DFSDM_CHCFGR1 ( 0 ) ,
DFSDM_CHCFGR1_CKOUTDIV_MASK ,
DFSDM_CHCFGR1_CKOUTDIV ( 0 ) ) ;
if ( ret < 0 )
return ret ;
2019-03-25 15:49:28 +01:00
pm_runtime_put_sync ( & priv - > pdev - > dev ) ;
2018-01-10 11:13:10 +01:00
}
dev_dbg ( & priv - > pdev - > dev , " %s: n_active_ch %d \n " , __func__ ,
atomic_read ( & priv - > n_active_ch ) ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( stm32_dfsdm_stop_dfsdm ) ;
static int stm32_dfsdm_parse_of ( struct platform_device * pdev ,
struct dfsdm_priv * priv )
{
struct device_node * node = pdev - > dev . of_node ;
struct resource * res ;
2019-03-21 17:47:22 +01:00
unsigned long clk_freq , divider ;
2018-01-10 11:13:10 +01:00
unsigned int spi_freq , rem ;
int ret ;
if ( ! node )
return - EINVAL ;
2020-09-18 16:31:42 +08:00
priv - > dfsdm . base = devm_platform_get_and_ioremap_resource ( pdev , 0 ,
& res ) ;
2019-04-24 14:51:26 +02:00
if ( IS_ERR ( priv - > dfsdm . base ) )
return PTR_ERR ( priv - > dfsdm . base ) ;
2018-01-10 11:13:10 +01:00
2020-09-18 16:31:42 +08:00
priv - > dfsdm . phys_base = res - > start ;
2018-01-10 11:13:10 +01:00
/*
* " dfsdm " clock is mandatory for DFSDM peripheral clocking .
* " dfsdm " or " audio " clocks can be used as source clock for
* the SPI clock out signal and internal processing , depending
* on use case .
*/
priv - > clk = devm_clk_get ( & pdev - > dev , " dfsdm " ) ;
2020-08-29 08:47:16 +02:00
if ( IS_ERR ( priv - > clk ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( priv - > clk ) ,
" Failed to get clock \n " ) ;
2018-01-10 11:13:10 +01:00
priv - > aclk = devm_clk_get ( & pdev - > dev , " audio " ) ;
if ( IS_ERR ( priv - > aclk ) )
priv - > aclk = NULL ;
if ( priv - > aclk )
clk_freq = clk_get_rate ( priv - > aclk ) ;
else
clk_freq = clk_get_rate ( priv - > clk ) ;
/* SPI clock out frequency */
ret = of_property_read_u32 ( pdev - > dev . of_node , " spi-max-frequency " ,
& spi_freq ) ;
if ( ret < 0 ) {
/* No SPI master mode */
return 0 ;
}
2019-03-21 17:47:22 +01:00
divider = div_u64_rem ( clk_freq , spi_freq , & rem ) ;
/* Round up divider when ckout isn't precise, not to exceed spi_freq */
if ( rem )
divider + + ;
/* programmable divider is in range of [2:256] */
if ( divider < 2 | | divider > 256 ) {
2018-02-23 13:51:01 +01:00
dev_err ( & pdev - > dev , " spi-max-frequency not achievable \n " ) ;
return - EINVAL ;
}
2019-03-21 17:47:22 +01:00
/* SPI clock output divider is: divider = CKOUTDIV + 1 */
priv - > spi_clk_out_div = divider - 1 ;
priv - > dfsdm . spi_master_freq = clk_freq / ( priv - > spi_clk_out_div + 1 ) ;
2018-01-10 11:13:10 +01:00
if ( rem ) {
dev_warn ( & pdev - > dev , " SPI clock not accurate \n " ) ;
dev_warn ( & pdev - > dev , " %ld = %d * %d + %d \n " ,
clk_freq , spi_freq , priv - > spi_clk_out_div + 1 , rem ) ;
}
return 0 ;
} ;
static const struct of_device_id stm32_dfsdm_of_match [ ] = {
{
. compatible = " st,stm32h7-dfsdm " ,
. data = & stm32h7_dfsdm_data ,
} ,
2018-05-02 15:05:23 +02:00
{
. compatible = " st,stm32mp1-dfsdm " ,
. data = & stm32mp1_dfsdm_data ,
} ,
2018-01-10 11:13:10 +01:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , stm32_dfsdm_of_match ) ;
static int stm32_dfsdm_probe ( struct platform_device * pdev )
{
struct dfsdm_priv * priv ;
const struct stm32_dfsdm_dev_data * dev_data ;
struct stm32_dfsdm * dfsdm ;
int ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > pdev = pdev ;
2018-01-15 09:57:39 +01:00
dev_data = of_device_get_match_data ( & pdev - > dev ) ;
2018-01-10 11:13:10 +01:00
dfsdm = & priv - > dfsdm ;
dfsdm - > fl_list = devm_kcalloc ( & pdev - > dev , dev_data - > num_filters ,
sizeof ( * dfsdm - > fl_list ) , GFP_KERNEL ) ;
if ( ! dfsdm - > fl_list )
return - ENOMEM ;
dfsdm - > num_fls = dev_data - > num_filters ;
dfsdm - > ch_list = devm_kcalloc ( & pdev - > dev , dev_data - > num_channels ,
sizeof ( * dfsdm - > ch_list ) ,
GFP_KERNEL ) ;
if ( ! dfsdm - > ch_list )
return - ENOMEM ;
dfsdm - > num_chs = dev_data - > num_channels ;
ret = stm32_dfsdm_parse_of ( pdev , priv ) ;
if ( ret < 0 )
return ret ;
dfsdm - > regmap = devm_regmap_init_mmio_clk ( & pdev - > dev , " dfsdm " ,
dfsdm - > base ,
2018-02-23 13:50:55 +01:00
dev_data - > regmap_cfg ) ;
2018-01-10 11:13:10 +01:00
if ( IS_ERR ( dfsdm - > regmap ) ) {
ret = PTR_ERR ( dfsdm - > regmap ) ;
dev_err ( & pdev - > dev , " %s: Failed to allocate regmap: %d \n " ,
__func__ , ret ) ;
return ret ;
}
platform_set_drvdata ( pdev , dfsdm ) ;
2019-03-25 15:49:28 +01:00
ret = stm32_dfsdm_clk_prepare_enable ( dfsdm ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to start clock \n " ) ;
return ret ;
}
pm_runtime_get_noresume ( & pdev - > dev ) ;
pm_runtime_set_active ( & pdev - > dev ) ;
pm_runtime_enable ( & pdev - > dev ) ;
ret = of_platform_populate ( pdev - > dev . of_node , NULL , NULL , & pdev - > dev ) ;
if ( ret )
goto pm_put ;
pm_runtime_put ( & pdev - > dev ) ;
return 0 ;
pm_put :
pm_runtime_disable ( & pdev - > dev ) ;
pm_runtime_set_suspended ( & pdev - > dev ) ;
pm_runtime_put_noidle ( & pdev - > dev ) ;
stm32_dfsdm_clk_disable_unprepare ( dfsdm ) ;
return ret ;
}
static int stm32_dfsdm_core_remove ( struct platform_device * pdev )
{
struct stm32_dfsdm * dfsdm = platform_get_drvdata ( pdev ) ;
pm_runtime_get_sync ( & pdev - > dev ) ;
of_platform_depopulate ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
pm_runtime_set_suspended ( & pdev - > dev ) ;
pm_runtime_put_noidle ( & pdev - > dev ) ;
stm32_dfsdm_clk_disable_unprepare ( dfsdm ) ;
return 0 ;
}
2022-01-30 19:31:47 +00:00
static int stm32_dfsdm_core_suspend ( struct device * dev )
2019-03-25 15:49:28 +01:00
{
struct stm32_dfsdm * dfsdm = dev_get_drvdata ( dev ) ;
struct dfsdm_priv * priv = to_stm32_dfsdm_priv ( dfsdm ) ;
int ret ;
ret = pm_runtime_force_suspend ( dev ) ;
if ( ret )
return ret ;
/* Balance devm_regmap_init_mmio_clk() clk_prepare() */
clk_unprepare ( priv - > clk ) ;
return pinctrl_pm_select_sleep_state ( dev ) ;
}
2022-01-30 19:31:47 +00:00
static int stm32_dfsdm_core_resume ( struct device * dev )
2019-03-25 15:49:28 +01:00
{
struct stm32_dfsdm * dfsdm = dev_get_drvdata ( dev ) ;
struct dfsdm_priv * priv = to_stm32_dfsdm_priv ( dfsdm ) ;
int ret ;
ret = pinctrl_pm_select_default_state ( dev ) ;
if ( ret )
return ret ;
ret = clk_prepare ( priv - > clk ) ;
if ( ret )
return ret ;
return pm_runtime_force_resume ( dev ) ;
2018-01-10 11:13:10 +01:00
}
2022-01-30 19:31:47 +00:00
static int stm32_dfsdm_core_runtime_suspend ( struct device * dev )
2019-03-25 15:49:28 +01:00
{
struct stm32_dfsdm * dfsdm = dev_get_drvdata ( dev ) ;
stm32_dfsdm_clk_disable_unprepare ( dfsdm ) ;
return 0 ;
}
2022-01-30 19:31:47 +00:00
static int stm32_dfsdm_core_runtime_resume ( struct device * dev )
2019-03-25 15:49:28 +01:00
{
struct stm32_dfsdm * dfsdm = dev_get_drvdata ( dev ) ;
return stm32_dfsdm_clk_prepare_enable ( dfsdm ) ;
}
static const struct dev_pm_ops stm32_dfsdm_core_pm_ops = {
2022-01-30 19:31:47 +00:00
SYSTEM_SLEEP_PM_OPS ( stm32_dfsdm_core_suspend , stm32_dfsdm_core_resume )
RUNTIME_PM_OPS ( stm32_dfsdm_core_runtime_suspend ,
stm32_dfsdm_core_runtime_resume ,
NULL )
2019-03-25 15:49:28 +01:00
} ;
2018-01-10 11:13:10 +01:00
static struct platform_driver stm32_dfsdm_driver = {
. probe = stm32_dfsdm_probe ,
2019-03-25 15:49:28 +01:00
. remove = stm32_dfsdm_core_remove ,
2018-01-10 11:13:10 +01:00
. driver = {
. name = " stm32-dfsdm " ,
. of_match_table = stm32_dfsdm_of_match ,
2022-01-30 19:31:47 +00:00
. pm = pm_ptr ( & stm32_dfsdm_core_pm_ops ) ,
2018-01-10 11:13:10 +01:00
} ,
} ;
module_platform_driver ( stm32_dfsdm_driver ) ;
MODULE_AUTHOR ( " Arnaud Pouliquen <arnaud.pouliquen@st.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics STM32 dfsdm driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;