2016-08-10 16:48:20 +02:00
/*
* Copyright ( C ) 2015 - 2016 Samsung Electronics Co . , Ltd .
*
* Authors : Inha Song < ideal . song @ samsung . com >
* Sylwester Nawrocki < s . nawrocki @ samsung . com >
*
* Samsung Exynos SoC series Low Power Audio Subsystem driver .
*
* This module provides regmap for the Top SFR region and instantiates
* devices for IP blocks like DMAC , I2S , UART .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation .
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/mfd/syscon.h>
# include <linux/mfd/syscon/exynos5-pmu.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/types.h>
/* LPASS Top register definitions */
# define SFR_LPASS_CORE_SW_RESET 0x08
# define LPASS_SB_SW_RESET BIT(11)
# define LPASS_UART_SW_RESET BIT(10)
# define LPASS_PCM_SW_RESET BIT(9)
# define LPASS_I2S_SW_RESET BIT(8)
# define LPASS_WDT1_SW_RESET BIT(4)
# define LPASS_WDT0_SW_RESET BIT(3)
# define LPASS_TIMER_SW_RESET BIT(2)
# define LPASS_MEM_SW_RESET BIT(1)
# define LPASS_DMA_SW_RESET BIT(0)
# define SFR_LPASS_INTR_CA5_MASK 0x48
# define SFR_LPASS_INTR_CPU_MASK 0x58
# define LPASS_INTR_APM BIT(9)
# define LPASS_INTR_MIF BIT(8)
# define LPASS_INTR_TIMER BIT(7)
# define LPASS_INTR_DMA BIT(6)
# define LPASS_INTR_GPIO BIT(5)
# define LPASS_INTR_I2S BIT(4)
# define LPASS_INTR_PCM BIT(3)
# define LPASS_INTR_SLIMBUS BIT(2)
# define LPASS_INTR_UART BIT(1)
# define LPASS_INTR_SFR BIT(0)
struct exynos_lpass {
/* pointer to the Power Management Unit regmap */
struct regmap * pmu ;
/* pointer to the LPASS TOP regmap */
struct regmap * top ;
} ;
static void exynos_lpass_core_sw_reset ( struct exynos_lpass * lpass , int mask )
{
unsigned int val = 0 ;
regmap_read ( lpass - > top , SFR_LPASS_CORE_SW_RESET , & val ) ;
val & = ~ mask ;
regmap_write ( lpass - > top , SFR_LPASS_CORE_SW_RESET , val ) ;
usleep_range ( 100 , 150 ) ;
val | = mask ;
regmap_write ( lpass - > top , SFR_LPASS_CORE_SW_RESET , val ) ;
}
static void exynos_lpass_enable ( struct exynos_lpass * lpass )
{
/* Unmask SFR, DMA and I2S interrupt */
regmap_write ( lpass - > top , SFR_LPASS_INTR_CA5_MASK ,
LPASS_INTR_SFR | LPASS_INTR_DMA | LPASS_INTR_I2S ) ;
regmap_write ( lpass - > top , SFR_LPASS_INTR_CPU_MASK ,
LPASS_INTR_SFR | LPASS_INTR_DMA | LPASS_INTR_I2S ) ;
/* Activate related PADs from retention state */
regmap_write ( lpass - > pmu , EXYNOS5433_PAD_RETENTION_AUD_OPTION ,
EXYNOS5433_PAD_INITIATE_WAKEUP_FROM_LOWPWR ) ;
exynos_lpass_core_sw_reset ( lpass , LPASS_I2S_SW_RESET ) ;
exynos_lpass_core_sw_reset ( lpass , LPASS_DMA_SW_RESET ) ;
exynos_lpass_core_sw_reset ( lpass , LPASS_MEM_SW_RESET ) ;
}
static void exynos_lpass_disable ( struct exynos_lpass * lpass )
{
/* Mask any unmasked IP interrupt sources */
regmap_write ( lpass - > top , SFR_LPASS_INTR_CPU_MASK , 0 ) ;
regmap_write ( lpass - > top , SFR_LPASS_INTR_CA5_MASK , 0 ) ;
/* Deactivate related PADs from retention state */
regmap_write ( lpass - > pmu , EXYNOS5433_PAD_RETENTION_AUD_OPTION , 0 ) ;
}
static const struct regmap_config exynos_lpass_reg_conf = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = 0xfc ,
. fast_io = true ,
} ;
static int exynos_lpass_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct exynos_lpass * lpass ;
void __iomem * base_top ;
struct resource * res ;
lpass = devm_kzalloc ( dev , sizeof ( * lpass ) , GFP_KERNEL ) ;
if ( ! lpass )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
base_top = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( base_top ) )
return PTR_ERR ( base_top ) ;
lpass - > top = regmap_init_mmio ( dev , base_top ,
& exynos_lpass_reg_conf ) ;
if ( IS_ERR ( lpass - > top ) ) {
dev_err ( dev , " LPASS top regmap initialization failed \n " ) ;
return PTR_ERR ( lpass - > top ) ;
}
lpass - > pmu = syscon_regmap_lookup_by_phandle ( dev - > of_node ,
" samsung,pmu-syscon " ) ;
if ( IS_ERR ( lpass - > pmu ) ) {
dev_err ( dev , " Failed to lookup PMU regmap \n " ) ;
return PTR_ERR ( lpass - > pmu ) ;
}
platform_set_drvdata ( pdev , lpass ) ;
exynos_lpass_enable ( lpass ) ;
return of_platform_populate ( dev - > of_node , NULL , NULL , dev ) ;
}
2016-09-06 15:24:00 +02:00
static int __maybe_unused exynos_lpass_suspend ( struct device * dev )
2016-08-10 16:48:20 +02:00
{
struct exynos_lpass * lpass = dev_get_drvdata ( dev ) ;
exynos_lpass_disable ( lpass ) ;
return 0 ;
}
2016-09-06 15:24:00 +02:00
static int __maybe_unused exynos_lpass_resume ( struct device * dev )
2016-08-10 16:48:20 +02:00
{
struct exynos_lpass * lpass = dev_get_drvdata ( dev ) ;
exynos_lpass_enable ( lpass ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( lpass_pm_ops , exynos_lpass_suspend ,
exynos_lpass_resume ) ;
static const struct of_device_id exynos_lpass_of_match [ ] = {
{ . compatible = " samsung,exynos5433-lpass " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_lpass_of_match ) ;
static struct platform_driver exynos_lpass_driver = {
. driver = {
. name = " exynos-lpass " ,
. pm = & lpass_pm_ops ,
. of_match_table = exynos_lpass_of_match ,
} ,
. probe = exynos_lpass_probe ,
} ;
module_platform_driver ( exynos_lpass_driver ) ;
MODULE_DESCRIPTION ( " Samsung Low Power Audio Subsystem driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;