2020-01-15 19:30:30 +08:00
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright ( c ) 2019 Amlogic , Inc .
* Author : Jianxin Pan < jianxin . pan @ amlogic . com >
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/io.h>
2023-07-14 11:51:23 -06:00
# include <linux/of.h>
2020-01-15 19:30:30 +08:00
# include <linux/platform_device.h>
# include <linux/pm_domain.h>
# include <dt-bindings/power/meson-a1-power.h>
2023-07-07 08:37:09 +08:00
# include <dt-bindings/power/amlogic,c3-pwrc.h>
2022-03-07 10:53:57 +08:00
# include <dt-bindings/power/meson-s4-power.h>
2023-09-11 10:52:22 +08:00
# include <dt-bindings/power/amlogic,t7-pwrc.h>
2020-01-15 19:30:30 +08:00
# include <linux/arm-smccc.h>
# include <linux/firmware/meson/meson_sm.h>
2020-10-19 15:46:49 -07:00
# include <linux/module.h>
2020-01-15 19:30:30 +08:00
# define PWRC_ON 1
# define PWRC_OFF 0
2023-09-11 10:52:19 +08:00
# define PWRC_NO_PARENT UINT_MAX
2020-01-15 19:30:30 +08:00
struct meson_secure_pwrc_domain {
struct generic_pm_domain base ;
unsigned int index ;
2023-09-11 10:52:19 +08:00
unsigned int parent ;
2020-01-15 19:30:30 +08:00
struct meson_secure_pwrc * pwrc ;
} ;
struct meson_secure_pwrc {
struct meson_secure_pwrc_domain * domains ;
struct genpd_onecell_data xlate ;
struct meson_sm_firmware * fw ;
} ;
struct meson_secure_pwrc_domain_desc {
unsigned int index ;
2023-09-11 10:52:19 +08:00
unsigned int parent ;
2020-01-15 19:30:30 +08:00
unsigned int flags ;
char * name ;
bool ( * is_off ) ( struct meson_secure_pwrc_domain * pwrc_domain ) ;
} ;
struct meson_secure_pwrc_domain_data {
unsigned int count ;
struct meson_secure_pwrc_domain_desc * domains ;
} ;
static bool pwrc_secure_is_off ( struct meson_secure_pwrc_domain * pwrc_domain )
{
int is_off = 1 ;
if ( meson_sm_call ( pwrc_domain - > pwrc - > fw , SM_A1_PWRC_GET , & is_off ,
pwrc_domain - > index , 0 , 0 , 0 , 0 ) < 0 )
pr_err ( " failed to get power domain status \n " ) ;
return is_off ;
}
static int meson_secure_pwrc_off ( struct generic_pm_domain * domain )
{
int ret = 0 ;
struct meson_secure_pwrc_domain * pwrc_domain =
container_of ( domain , struct meson_secure_pwrc_domain , base ) ;
if ( meson_sm_call ( pwrc_domain - > pwrc - > fw , SM_A1_PWRC_SET , NULL ,
pwrc_domain - > index , PWRC_OFF , 0 , 0 , 0 ) < 0 ) {
pr_err ( " failed to set power domain off \n " ) ;
ret = - EINVAL ;
}
return ret ;
}
static int meson_secure_pwrc_on ( struct generic_pm_domain * domain )
{
int ret = 0 ;
struct meson_secure_pwrc_domain * pwrc_domain =
container_of ( domain , struct meson_secure_pwrc_domain , base ) ;
if ( meson_sm_call ( pwrc_domain - > pwrc - > fw , SM_A1_PWRC_SET , NULL ,
pwrc_domain - > index , PWRC_ON , 0 , 0 , 0 ) < 0 ) {
pr_err ( " failed to set power domain on \n " ) ;
ret = - EINVAL ;
}
return ret ;
}
# define SEC_PD(__name, __flag) \
[ PWRC_ # # __name # # _ID ] = \
{ \
. name = # __name , \
. index = PWRC_ # # __name # # _ID , \
2023-09-11 10:52:19 +08:00
. is_off = pwrc_secure_is_off , \
2020-01-15 19:30:30 +08:00
. flags = __flag , \
2023-09-11 10:52:19 +08:00
. parent = PWRC_NO_PARENT , \
}
# define TOP_PD(__name, __flag, __parent) \
[ PWRC_ # # __name # # _ID ] = \
{ \
. name = # __name , \
. index = PWRC_ # # __name # # _ID , \
. is_off = pwrc_secure_is_off , \
. flags = __flag , \
. parent = __parent , \
2020-01-15 19:30:30 +08:00
}
static struct meson_secure_pwrc_domain_desc a1_pwrc_domains [ ] = {
SEC_PD ( DSPA , 0 ) ,
SEC_PD ( DSPB , 0 ) ,
/* UART should keep working in ATF after suspend and before resume */
SEC_PD ( UART , GENPD_FLAG_ALWAYS_ON ) ,
/* DMC is for DDR PHY ana/dig and DMC, and should be always on */
SEC_PD ( DMC , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( I2C , 0 ) ,
SEC_PD ( PSRAM , 0 ) ,
SEC_PD ( ACODEC , 0 ) ,
SEC_PD ( AUDIO , 0 ) ,
SEC_PD ( OTP , 0 ) ,
2023-06-10 12:04:14 +03:00
SEC_PD ( DMA , GENPD_FLAG_ALWAYS_ON | GENPD_FLAG_IRQ_SAFE ) ,
2020-01-15 19:30:30 +08:00
SEC_PD ( SD_EMMC , 0 ) ,
SEC_PD ( RAMA , 0 ) ,
/* SRAMB is used as ATF runtime memory, and should be always on */
SEC_PD ( RAMB , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( IR , 0 ) ,
SEC_PD ( SPICC , 0 ) ,
SEC_PD ( SPIFC , 0 ) ,
SEC_PD ( USB , 0 ) ,
/* NIC is for the Arm NIC-400 interconnect, and should be always on */
SEC_PD ( NIC , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( PDMIN , 0 ) ,
SEC_PD ( RSA , 0 ) ,
} ;
2023-07-07 08:37:09 +08:00
static struct meson_secure_pwrc_domain_desc c3_pwrc_domains [ ] = {
2023-09-11 10:52:18 +08:00
SEC_PD ( C3_NNA , 0 ) ,
SEC_PD ( C3_AUDIO , 0 ) ,
SEC_PD ( C3_SDIOA , 0 ) ,
SEC_PD ( C3_EMMC , 0 ) ,
SEC_PD ( C3_USB_COMB , 0 ) ,
SEC_PD ( C3_SDCARD , 0 ) ,
/* ETH is for ethernet online wakeup, and should be always on */
SEC_PD ( C3_ETH , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( C3_GE2D , 0 ) ,
SEC_PD ( C3_CVE , 0 ) ,
SEC_PD ( C3_GDC_WRAP , 0 ) ,
SEC_PD ( C3_ISP_TOP , 0 ) ,
SEC_PD ( C3_MIPI_ISP_WRAP , 0 ) ,
2023-07-07 08:37:09 +08:00
SEC_PD ( C3_VCODEC , 0 ) ,
} ;
2022-03-07 10:53:57 +08:00
static struct meson_secure_pwrc_domain_desc s4_pwrc_domains [ ] = {
SEC_PD ( S4_DOS_HEVC , 0 ) ,
SEC_PD ( S4_DOS_VDEC , 0 ) ,
SEC_PD ( S4_VPU_HDMI , 0 ) ,
SEC_PD ( S4_USB_COMB , 0 ) ,
SEC_PD ( S4_GE2D , 0 ) ,
/* ETH is for ethernet online wakeup, and should be always on */
SEC_PD ( S4_ETH , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( S4_DEMOD , 0 ) ,
SEC_PD ( S4_AUDIO , 0 ) ,
} ;
2023-09-11 10:52:22 +08:00
static struct meson_secure_pwrc_domain_desc t7_pwrc_domains [ ] = {
SEC_PD ( T7_DSPA , 0 ) ,
SEC_PD ( T7_DSPB , 0 ) ,
TOP_PD ( T7_DOS_HCODEC , 0 , PWRC_T7_NIC3_ID ) ,
TOP_PD ( T7_DOS_HEVC , 0 , PWRC_T7_NIC3_ID ) ,
TOP_PD ( T7_DOS_VDEC , 0 , PWRC_T7_NIC3_ID ) ,
TOP_PD ( T7_DOS_WAVE , 0 , PWRC_T7_NIC3_ID ) ,
SEC_PD ( T7_VPU_HDMI , 0 ) ,
SEC_PD ( T7_USB_COMB , 0 ) ,
SEC_PD ( T7_PCIE , 0 ) ,
TOP_PD ( T7_GE2D , 0 , PWRC_T7_NIC3_ID ) ,
/* SRAMA is used as ATF runtime memory, and should be always on */
SEC_PD ( T7_SRAMA , GENPD_FLAG_ALWAYS_ON ) ,
/* SRAMB is used as ATF runtime memory, and should be always on */
SEC_PD ( T7_SRAMB , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( T7_HDMIRX , 0 ) ,
SEC_PD ( T7_VI_CLK1 , 0 ) ,
SEC_PD ( T7_VI_CLK2 , 0 ) ,
/* ETH is for ethernet online wakeup, and should be always on */
SEC_PD ( T7_ETH , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( T7_ISP , 0 ) ,
SEC_PD ( T7_MIPI_ISP , 0 ) ,
TOP_PD ( T7_GDC , 0 , PWRC_T7_NIC3_ID ) ,
TOP_PD ( T7_DEWARP , 0 , PWRC_T7_NIC3_ID ) ,
SEC_PD ( T7_SDIO_A , 0 ) ,
SEC_PD ( T7_SDIO_B , 0 ) ,
SEC_PD ( T7_EMMC , 0 ) ,
TOP_PD ( T7_MALI_SC0 , 0 , PWRC_T7_NNA_TOP_ID ) ,
TOP_PD ( T7_MALI_SC1 , 0 , PWRC_T7_NNA_TOP_ID ) ,
TOP_PD ( T7_MALI_SC2 , 0 , PWRC_T7_NNA_TOP_ID ) ,
TOP_PD ( T7_MALI_SC3 , 0 , PWRC_T7_NNA_TOP_ID ) ,
SEC_PD ( T7_MALI_TOP , 0 ) ,
TOP_PD ( T7_NNA_CORE0 , 0 , PWRC_T7_NNA_TOP_ID ) ,
TOP_PD ( T7_NNA_CORE1 , 0 , PWRC_T7_NNA_TOP_ID ) ,
TOP_PD ( T7_NNA_CORE2 , 0 , PWRC_T7_NNA_TOP_ID ) ,
TOP_PD ( T7_NNA_CORE3 , 0 , PWRC_T7_NNA_TOP_ID ) ,
SEC_PD ( T7_NNA_TOP , 0 ) ,
SEC_PD ( T7_DDR0 , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( T7_DDR1 , GENPD_FLAG_ALWAYS_ON ) ,
/* DMC0 is for DDR PHY ana/dig and DMC, and should be always on */
SEC_PD ( T7_DMC0 , GENPD_FLAG_ALWAYS_ON ) ,
/* DMC1 is for DDR PHY ana/dig and DMC, and should be always on */
SEC_PD ( T7_DMC1 , GENPD_FLAG_ALWAYS_ON ) ,
/* NOC is related to clk bus, and should be always on */
SEC_PD ( T7_NOC , GENPD_FLAG_ALWAYS_ON ) ,
/* NIC is for the Arm NIC-400 interconnect, and should be always on */
SEC_PD ( T7_NIC2 , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( T7_NIC3 , 0 ) ,
/* CPU accesses the interleave data to the ddr need cci, and should be always on */
SEC_PD ( T7_CCI , GENPD_FLAG_ALWAYS_ON ) ,
SEC_PD ( T7_MIPI_DSI0 , 0 ) ,
SEC_PD ( T7_SPICC0 , 0 ) ,
SEC_PD ( T7_SPICC1 , 0 ) ,
SEC_PD ( T7_SPICC2 , 0 ) ,
SEC_PD ( T7_SPICC3 , 0 ) ,
SEC_PD ( T7_SPICC4 , 0 ) ,
SEC_PD ( T7_SPICC5 , 0 ) ,
SEC_PD ( T7_EDP0 , 0 ) ,
SEC_PD ( T7_EDP1 , 0 ) ,
SEC_PD ( T7_MIPI_DSI1 , 0 ) ,
SEC_PD ( T7_AUDIO , 0 ) ,
} ;
2020-01-15 19:30:30 +08:00
static int meson_secure_pwrc_probe ( struct platform_device * pdev )
{
int i ;
struct device_node * sm_np ;
struct meson_secure_pwrc * pwrc ;
const struct meson_secure_pwrc_domain_data * match ;
match = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! match ) {
dev_err ( & pdev - > dev , " failed to get match data \n " ) ;
return - ENODEV ;
}
sm_np = of_find_compatible_node ( NULL , NULL , " amlogic,meson-gxbb-sm " ) ;
if ( ! sm_np ) {
dev_err ( & pdev - > dev , " no secure-monitor node \n " ) ;
return - ENODEV ;
}
pwrc = devm_kzalloc ( & pdev - > dev , sizeof ( * pwrc ) , GFP_KERNEL ) ;
2022-06-16 22:49:15 +08:00
if ( ! pwrc ) {
of_node_put ( sm_np ) ;
2020-01-15 19:30:30 +08:00
return - ENOMEM ;
2022-06-16 22:49:15 +08:00
}
2020-01-15 19:30:30 +08:00
pwrc - > fw = meson_sm_get ( sm_np ) ;
of_node_put ( sm_np ) ;
if ( ! pwrc - > fw )
return - EPROBE_DEFER ;
pwrc - > xlate . domains = devm_kcalloc ( & pdev - > dev , match - > count ,
sizeof ( * pwrc - > xlate . domains ) ,
GFP_KERNEL ) ;
if ( ! pwrc - > xlate . domains )
return - ENOMEM ;
pwrc - > domains = devm_kcalloc ( & pdev - > dev , match - > count ,
sizeof ( * pwrc - > domains ) , GFP_KERNEL ) ;
if ( ! pwrc - > domains )
return - ENOMEM ;
pwrc - > xlate . num_domains = match - > count ;
platform_set_drvdata ( pdev , pwrc ) ;
for ( i = 0 ; i < match - > count ; + + i ) {
struct meson_secure_pwrc_domain * dom = & pwrc - > domains [ i ] ;
2023-07-07 08:37:07 +08:00
if ( ! match - > domains [ i ] . name )
2020-01-15 19:30:30 +08:00
continue ;
dom - > pwrc = pwrc ;
dom - > index = match - > domains [ i ] . index ;
2023-09-11 10:52:19 +08:00
dom - > parent = match - > domains [ i ] . parent ;
2020-01-15 19:30:30 +08:00
dom - > base . name = match - > domains [ i ] . name ;
dom - > base . flags = match - > domains [ i ] . flags ;
dom - > base . power_on = meson_secure_pwrc_on ;
dom - > base . power_off = meson_secure_pwrc_off ;
2023-09-11 10:52:20 +08:00
if ( match - > domains [ i ] . is_off ( dom ) & & ( dom - > base . flags & GENPD_FLAG_ALWAYS_ON ) )
meson_secure_pwrc_on ( & dom - > base ) ;
2020-01-15 19:30:30 +08:00
pm_genpd_init ( & dom - > base , NULL , match - > domains [ i ] . is_off ( dom ) ) ;
pwrc - > xlate . domains [ i ] = & dom - > base ;
}
2023-09-11 10:52:19 +08:00
for ( i = 0 ; i < match - > count ; i + + ) {
struct meson_secure_pwrc_domain * dom = pwrc - > domains ;
if ( ! match - > domains [ i ] . name | | match - > domains [ i ] . parent = = PWRC_NO_PARENT )
continue ;
pm_genpd_add_subdomain ( & dom [ dom [ i ] . parent ] . base , & dom [ i ] . base ) ;
}
2020-01-15 19:30:30 +08:00
return of_genpd_add_provider_onecell ( pdev - > dev . of_node , & pwrc - > xlate ) ;
}
static struct meson_secure_pwrc_domain_data meson_secure_a1_pwrc_data = {
. domains = a1_pwrc_domains ,
. count = ARRAY_SIZE ( a1_pwrc_domains ) ,
} ;
2023-07-07 08:37:09 +08:00
static struct meson_secure_pwrc_domain_data amlogic_secure_c3_pwrc_data = {
. domains = c3_pwrc_domains ,
. count = ARRAY_SIZE ( c3_pwrc_domains ) ,
} ;
2022-03-07 10:53:57 +08:00
static struct meson_secure_pwrc_domain_data meson_secure_s4_pwrc_data = {
. domains = s4_pwrc_domains ,
. count = ARRAY_SIZE ( s4_pwrc_domains ) ,
} ;
2023-09-11 10:52:22 +08:00
static struct meson_secure_pwrc_domain_data amlogic_secure_t7_pwrc_data = {
. domains = t7_pwrc_domains ,
. count = ARRAY_SIZE ( t7_pwrc_domains ) ,
} ;
2020-01-15 19:30:30 +08:00
static const struct of_device_id meson_secure_pwrc_match_table [ ] = {
{
. compatible = " amlogic,meson-a1-pwrc " ,
. data = & meson_secure_a1_pwrc_data ,
} ,
2023-07-07 08:37:09 +08:00
{
. compatible = " amlogic,c3-pwrc " ,
. data = & amlogic_secure_c3_pwrc_data ,
} ,
2022-03-07 10:53:57 +08:00
{
. compatible = " amlogic,meson-s4-pwrc " ,
. data = & meson_secure_s4_pwrc_data ,
} ,
2023-09-11 10:52:22 +08:00
{
. compatible = " amlogic,t7-pwrc " ,
. data = & amlogic_secure_t7_pwrc_data ,
} ,
2020-01-15 19:30:30 +08:00
{ /* sentinel */ }
} ;
2020-10-19 15:46:49 -07:00
MODULE_DEVICE_TABLE ( of , meson_secure_pwrc_match_table ) ;
2020-01-15 19:30:30 +08:00
static struct platform_driver meson_secure_pwrc_driver = {
. probe = meson_secure_pwrc_probe ,
. driver = {
. name = " meson_secure_pwrc " ,
. of_match_table = meson_secure_pwrc_match_table ,
} ,
} ;
2020-10-19 15:46:49 -07:00
module_platform_driver ( meson_secure_pwrc_driver ) ;
MODULE_LICENSE ( " Dual MIT/GPL " ) ;