2016-03-16 14:19:50 +01:00
/*
* Atmel SAMA5D2 - Compatible Shutdown Controller ( SHDWC ) driver .
* Found on some SoCs as the sama5d2 ( obviously ) .
*
* Copyright ( C ) 2015 Atmel Corporation ,
* Nicolas Ferre < nicolas . ferre @ atmel . com >
*
* Evolved from driver at91 - poweroff . c .
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*
* TODO :
* - addition to status of other wake - up inputs [ 1 - 15 ]
* - Analog Comparator wake - up alarm
* - Serial RX wake - up alarm
* - low power debouncer
*/
# include <linux/clk.h>
2018-08-30 14:50:06 +03:00
# include <linux/clk/at91_pmc.h>
2016-03-16 14:19:50 +01:00
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
2016-10-25 11:37:59 +02:00
# include <linux/of_address.h>
2016-03-16 14:19:50 +01:00
# include <linux/platform_device.h>
# include <linux/printk.h>
2016-10-25 11:37:59 +02:00
# include <soc/at91/at91sam9_ddrsdr.h>
2016-03-16 14:19:50 +01:00
# define SLOW_CLOCK_FREQ 32768
# define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
# define AT91_SHDW_SHDW BIT(0) /* Shut Down command */
# define AT91_SHDW_KEY (0xa5UL << 24) /* KEY Password */
# define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */
# define AT91_SHDW_WKUPDBC_SHIFT 24
# define AT91_SHDW_WKUPDBC_MASK GENMASK(31, 16)
# define AT91_SHDW_WKUPDBC(x) (((x) << AT91_SHDW_WKUPDBC_SHIFT) \
& AT91_SHDW_WKUPDBC_MASK )
# define AT91_SHDW_SR 0x08 /* Shut Down Status Register */
# define AT91_SHDW_WKUPIS_SHIFT 16
# define AT91_SHDW_WKUPIS_MASK GENMASK(31, 16)
# define AT91_SHDW_WKUPIS(x) ((1 << (x)) << AT91_SHDW_WKUPIS_SHIFT \
& AT91_SHDW_WKUPIS_MASK )
# define AT91_SHDW_WUIR 0x0c /* Shutdown Wake-up Inputs Register */
# define AT91_SHDW_WKUPEN_MASK GENMASK(15, 0)
# define AT91_SHDW_WKUPEN(x) ((1 << (x)) & AT91_SHDW_WKUPEN_MASK)
# define AT91_SHDW_WKUPT_SHIFT 16
# define AT91_SHDW_WKUPT_MASK GENMASK(31, 16)
# define AT91_SHDW_WKUPT(x) ((1 << (x)) << AT91_SHDW_WKUPT_SHIFT \
& AT91_SHDW_WKUPT_MASK )
# define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input))
# define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1)
2019-02-21 13:45:48 +00:00
# define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1)
2016-03-16 14:19:50 +01:00
# define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift))
2019-02-21 13:45:48 +00:00
# define SHDW_RTTWKEN(cfg) (1 << ((cfg)->mr_rttwk_shift))
2016-03-16 14:19:50 +01:00
# define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \
SLOW_CLOCK_FREQ )
2019-02-21 13:45:48 +00:00
# define SHDW_CFG_NOT_USED (32)
2019-12-20 17:31:17 +02:00
struct shdwc_reg_config {
2016-03-16 14:19:50 +01:00
u8 wkup_pin_input ;
u8 mr_rtcwk_shift ;
2019-02-21 13:45:48 +00:00
u8 mr_rttwk_shift ;
2016-03-16 14:19:50 +01:00
u8 sr_rtcwk_shift ;
2019-02-21 13:45:48 +00:00
u8 sr_rttwk_shift ;
2016-03-16 14:19:50 +01:00
} ;
2019-12-20 17:31:18 +02:00
struct pmc_reg_config {
u8 mckr ;
} ;
2019-12-20 17:31:17 +02:00
struct reg_config {
struct shdwc_reg_config shdwc ;
2019-12-20 17:31:18 +02:00
struct pmc_reg_config pmc ;
2019-12-20 17:31:17 +02:00
} ;
2016-03-16 14:19:50 +01:00
struct shdwc {
2019-12-20 17:31:17 +02:00
const struct reg_config * rcfg ;
2018-08-30 14:50:09 +03:00
struct clk * sclk ;
2018-08-30 14:50:10 +03:00
void __iomem * shdwc_base ;
2018-08-30 14:50:08 +03:00
void __iomem * mpddrc_base ;
2018-08-30 14:50:06 +03:00
void __iomem * pmc_base ;
2016-03-16 14:19:50 +01:00
} ;
/*
* Hold configuration here , cannot be more than one instance of the driver
* since pm_power_off itself is global .
*/
static struct shdwc * at91_shdwc ;
static const unsigned long long sdwc_dbc_period [ ] = {
0 , 3 , 32 , 512 , 4096 , 32768 ,
} ;
static void __init at91_wakeup_status ( struct platform_device * pdev )
{
struct shdwc * shdw = platform_get_drvdata ( pdev ) ;
2019-12-20 17:31:17 +02:00
const struct reg_config * rcfg = shdw - > rcfg ;
2016-03-16 14:19:50 +01:00
u32 reg ;
char * reason = " unknown " ;
2018-08-30 14:50:10 +03:00
reg = readl ( shdw - > shdwc_base + AT91_SHDW_SR ) ;
2016-03-16 14:19:50 +01:00
dev_dbg ( & pdev - > dev , " %s: status = %#x \n " , __func__ , reg ) ;
/* Simple power-on, just bail out */
if ( ! reg )
return ;
2019-12-20 17:31:17 +02:00
if ( SHDW_WK_PIN ( reg , & rcfg - > shdwc ) )
2016-03-16 14:19:50 +01:00
reason = " WKUP pin " ;
2019-12-20 17:31:17 +02:00
else if ( SHDW_RTCWK ( reg , & rcfg - > shdwc ) )
2016-03-16 14:19:50 +01:00
reason = " RTC " ;
2019-12-20 17:31:17 +02:00
else if ( SHDW_RTTWK ( reg , & rcfg - > shdwc ) )
2019-02-21 13:45:48 +00:00
reason = " RTT " ;
2016-03-16 14:19:50 +01:00
pr_info ( " AT91: Wake-Up source: %s \n " , reason ) ;
}
static void at91_poweroff ( void )
2016-10-25 11:37:59 +02:00
{
asm volatile (
/* Align to cache lines */
" .balign 32 \n \t "
/* Ensure AT91_SHDW_CR is in the TLB by reading it */
" ldr r6, [%2, # " __stringify ( AT91_SHDW_CR ) " ] \n \t "
/* Power down SDRAM0 */
2018-08-30 14:50:07 +03:00
" tst %0, #0 \n \t "
" beq 1f \n \t "
2016-10-25 11:37:59 +02:00
" str %1, [%0, # " __stringify ( AT91_DDRSDRC_LPR ) " ] \n \t "
2018-08-30 14:50:06 +03:00
/* Switch the master clock source to slow clock. */
2019-12-20 17:31:18 +02:00
" 1: ldr r6, [%4, %5] \n \t "
2018-08-30 14:50:06 +03:00
" bic r6, r6, # " __stringify ( AT91_PMC_CSS ) " \n \t "
2019-12-20 17:31:18 +02:00
" str r6, [%4, %5] \n \t "
2018-08-30 14:50:06 +03:00
/* Wait for clock switch. */
2018-08-30 14:50:07 +03:00
" 2: ldr r6, [%4, # " __stringify ( AT91_PMC_SR ) " ] \n \t "
2018-08-30 14:50:06 +03:00
" tst r6, # " __stringify ( AT91_PMC_MCKRDY ) " \n \t "
2018-08-30 14:50:07 +03:00
" beq 2b \n \t "
2018-08-30 14:50:06 +03:00
2016-10-25 11:37:59 +02:00
/* Shutdown CPU */
" str %3, [%2, # " __stringify ( AT91_SHDW_CR ) " ] \n \t "
" b . \n \t "
:
2018-08-30 14:50:08 +03:00
: " r " ( at91_shdwc - > mpddrc_base ) ,
2016-10-25 11:37:59 +02:00
" r " cpu_to_le32 ( AT91_DDRSDRC_LPDDR2_PWOFF ) ,
2018-08-30 14:50:10 +03:00
" r " ( at91_shdwc - > shdwc_base ) ,
2018-08-30 14:50:06 +03:00
" r " cpu_to_le32 ( AT91_SHDW_KEY | AT91_SHDW_SHDW ) ,
2019-12-20 17:31:18 +02:00
" r " ( at91_shdwc - > pmc_base ) ,
" r " ( at91_shdwc - > rcfg - > pmc . mckr )
2017-05-31 11:49:15 +02:00
: " r6 " ) ;
2016-10-25 11:37:59 +02:00
}
2016-03-16 14:19:50 +01:00
static u32 at91_shdwc_debouncer_value ( struct platform_device * pdev ,
u32 in_period_us )
{
int i ;
int max_idx = ARRAY_SIZE ( sdwc_dbc_period ) - 1 ;
unsigned long long period_us ;
unsigned long long max_period_us = DBC_PERIOD_US ( sdwc_dbc_period [ max_idx ] ) ;
if ( in_period_us > max_period_us ) {
dev_warn ( & pdev - > dev ,
" debouncer period %u too big, reduced to %llu us \n " ,
in_period_us , max_period_us ) ;
return max_idx ;
}
for ( i = max_idx - 1 ; i > 0 ; i - - ) {
period_us = DBC_PERIOD_US ( sdwc_dbc_period [ i ] ) ;
dev_dbg ( & pdev - > dev , " %s: ref[%d] = %llu \n " ,
__func__ , i , period_us ) ;
if ( in_period_us > period_us )
break ;
}
return i + 1 ;
}
static u32 at91_shdwc_get_wakeup_input ( struct platform_device * pdev ,
struct device_node * np )
{
struct device_node * cnp ;
u32 wk_input_mask ;
u32 wuir = 0 ;
u32 wk_input ;
for_each_child_of_node ( np , cnp ) {
if ( of_property_read_u32 ( cnp , " reg " , & wk_input ) ) {
2017-07-18 16:43:24 -05:00
dev_warn ( & pdev - > dev , " reg property is missing for %pOF \n " ,
cnp ) ;
2016-03-16 14:19:50 +01:00
continue ;
}
wk_input_mask = 1 < < wk_input ;
if ( ! ( wk_input_mask & AT91_SHDW_WKUPEN_MASK ) ) {
dev_warn ( & pdev - > dev ,
" wake-up input %d out of bounds ignore \n " ,
wk_input ) ;
continue ;
}
wuir | = wk_input_mask ;
if ( of_property_read_bool ( cnp , " atmel,wakeup-active-high " ) )
wuir | = AT91_SHDW_WKUPT ( wk_input ) ;
dev_dbg ( & pdev - > dev , " %s: (child %d) wuir = %#x \n " ,
__func__ , wk_input , wuir ) ;
}
return wuir ;
}
static void at91_shdwc_dt_configure ( struct platform_device * pdev )
{
struct shdwc * shdw = platform_get_drvdata ( pdev ) ;
2019-12-20 17:31:17 +02:00
const struct reg_config * rcfg = shdw - > rcfg ;
2016-03-16 14:19:50 +01:00
struct device_node * np = pdev - > dev . of_node ;
u32 mode = 0 , tmp , input ;
if ( ! np ) {
dev_err ( & pdev - > dev , " device node not found \n " ) ;
return ;
}
if ( ! of_property_read_u32 ( np , " debounce-delay-us " , & tmp ) )
mode | = AT91_SHDW_WKUPDBC ( at91_shdwc_debouncer_value ( pdev , tmp ) ) ;
if ( of_property_read_bool ( np , " atmel,wakeup-rtc-timer " ) )
2019-12-20 17:31:17 +02:00
mode | = SHDW_RTCWKEN ( & rcfg - > shdwc ) ;
2016-03-16 14:19:50 +01:00
2019-02-21 13:45:48 +00:00
if ( of_property_read_bool ( np , " atmel,wakeup-rtt-timer " ) )
2019-12-20 17:31:17 +02:00
mode | = SHDW_RTTWKEN ( & rcfg - > shdwc ) ;
2019-02-21 13:45:48 +00:00
2016-03-16 14:19:50 +01:00
dev_dbg ( & pdev - > dev , " %s: mode = %#x \n " , __func__ , mode ) ;
2018-08-30 14:50:10 +03:00
writel ( mode , shdw - > shdwc_base + AT91_SHDW_MR ) ;
2016-03-16 14:19:50 +01:00
input = at91_shdwc_get_wakeup_input ( pdev , np ) ;
2018-08-30 14:50:10 +03:00
writel ( input , shdw - > shdwc_base + AT91_SHDW_WUIR ) ;
2016-03-16 14:19:50 +01:00
}
2019-12-20 17:31:17 +02:00
static const struct reg_config sama5d2_reg_config = {
. shdwc = {
. wkup_pin_input = 0 ,
. mr_rtcwk_shift = 17 ,
. mr_rttwk_shift = SHDW_CFG_NOT_USED ,
. sr_rtcwk_shift = 5 ,
. sr_rttwk_shift = SHDW_CFG_NOT_USED ,
} ,
2019-12-20 17:31:18 +02:00
. pmc = {
. mckr = 0x30 ,
} ,
2019-02-21 13:45:48 +00:00
} ;
2019-02-21 13:45:51 +00:00
2019-12-20 17:31:17 +02:00
static const struct reg_config sam9x60_reg_config = {
. shdwc = {
. wkup_pin_input = 0 ,
. mr_rtcwk_shift = 17 ,
. mr_rttwk_shift = 16 ,
. sr_rtcwk_shift = 5 ,
. sr_rttwk_shift = 4 ,
} ,
2019-12-20 17:31:18 +02:00
. pmc = {
. mckr = 0x28 ,
} ,
2016-03-16 14:19:50 +01:00
} ;
static const struct of_device_id at91_shdwc_of_match [ ] = {
{
. compatible = " atmel,sama5d2-shdwc " ,
2019-12-20 17:31:17 +02:00
. data = & sama5d2_reg_config ,
2019-02-21 13:45:51 +00:00
} ,
{
. compatible = " microchip,sam9x60-shdwc " ,
2019-12-20 17:31:17 +02:00
. data = & sam9x60_reg_config ,
2016-03-16 14:19:50 +01:00
} , {
/*sentinel*/
}
} ;
MODULE_DEVICE_TABLE ( of , at91_shdwc_of_match ) ;
2019-09-26 13:25:37 +03:00
static const struct of_device_id at91_pmc_ids [ ] = {
{ . compatible = " atmel,sama5d2-pmc " } ,
{ . compatible = " microchip,sam9x60-pmc " } ,
{ /* Sentinel. */ }
} ;
2016-03-16 14:19:50 +01:00
static int __init at91_shdwc_probe ( struct platform_device * pdev )
{
struct resource * res ;
const struct of_device_id * match ;
2016-10-25 11:37:59 +02:00
struct device_node * np ;
u32 ddr_type ;
2016-03-16 14:19:50 +01:00
int ret ;
if ( ! pdev - > dev . of_node )
return - ENODEV ;
2018-08-30 14:50:11 +03:00
if ( at91_shdwc )
return - EBUSY ;
2016-03-16 14:19:50 +01:00
at91_shdwc = devm_kzalloc ( & pdev - > dev , sizeof ( * at91_shdwc ) , GFP_KERNEL ) ;
if ( ! at91_shdwc )
return - ENOMEM ;
platform_set_drvdata ( pdev , at91_shdwc ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2018-08-30 14:50:10 +03:00
at91_shdwc - > shdwc_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( at91_shdwc - > shdwc_base ) ) {
2016-03-16 14:19:50 +01:00
dev_err ( & pdev - > dev , " Could not map reset controller address \n " ) ;
2018-08-30 14:50:10 +03:00
return PTR_ERR ( at91_shdwc - > shdwc_base ) ;
2016-03-16 14:19:50 +01:00
}
match = of_match_node ( at91_shdwc_of_match , pdev - > dev . of_node ) ;
2019-12-20 17:31:17 +02:00
at91_shdwc - > rcfg = match - > data ;
2016-03-16 14:19:50 +01:00
2018-08-30 14:50:09 +03:00
at91_shdwc - > sclk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( at91_shdwc - > sclk ) )
return PTR_ERR ( at91_shdwc - > sclk ) ;
2016-03-16 14:19:50 +01:00
2018-08-30 14:50:09 +03:00
ret = clk_prepare_enable ( at91_shdwc - > sclk ) ;
2016-03-16 14:19:50 +01:00
if ( ret ) {
dev_err ( & pdev - > dev , " Could not enable slow clock \n " ) ;
return ret ;
}
at91_wakeup_status ( pdev ) ;
at91_shdwc_dt_configure ( pdev ) ;
2019-09-26 13:25:37 +03:00
np = of_find_matching_node ( NULL , at91_pmc_ids ) ;
2018-08-30 14:50:06 +03:00
if ( ! np ) {
ret = - ENODEV ;
goto clk_disable ;
}
at91_shdwc - > pmc_base = of_iomap ( np , 0 ) ;
of_node_put ( np ) ;
if ( ! at91_shdwc - > pmc_base ) {
ret = - ENOMEM ;
goto clk_disable ;
}
2016-03-16 14:19:50 +01:00
2016-10-25 11:37:59 +02:00
np = of_find_compatible_node ( NULL , NULL , " atmel,sama5d3-ddramc " ) ;
2018-08-30 14:50:06 +03:00
if ( ! np ) {
ret = - ENODEV ;
goto unmap ;
}
2016-10-25 11:37:59 +02:00
2018-08-30 14:50:08 +03:00
at91_shdwc - > mpddrc_base = of_iomap ( np , 0 ) ;
2016-10-25 11:37:59 +02:00
of_node_put ( np ) ;
2018-08-30 14:50:08 +03:00
if ( ! at91_shdwc - > mpddrc_base ) {
2018-08-30 14:50:06 +03:00
ret = - ENOMEM ;
goto unmap ;
}
pm_power_off = at91_poweroff ;
2016-10-25 11:37:59 +02:00
2018-08-30 14:50:08 +03:00
ddr_type = readl ( at91_shdwc - > mpddrc_base + AT91_DDRSDRC_MDR ) &
AT91_DDRSDRC_MD ;
2018-08-30 14:50:07 +03:00
if ( ddr_type ! = AT91_DDRSDRC_MD_LPDDR2 & &
ddr_type ! = AT91_DDRSDRC_MD_LPDDR3 ) {
2018-08-30 14:50:08 +03:00
iounmap ( at91_shdwc - > mpddrc_base ) ;
at91_shdwc - > mpddrc_base = NULL ;
2018-08-30 14:50:06 +03:00
}
2016-10-25 11:37:59 +02:00
2016-03-16 14:19:50 +01:00
return 0 ;
2018-08-30 14:50:06 +03:00
unmap :
iounmap ( at91_shdwc - > pmc_base ) ;
clk_disable :
2018-08-30 14:50:09 +03:00
clk_disable_unprepare ( at91_shdwc - > sclk ) ;
2018-08-30 14:50:06 +03:00
return ret ;
2016-03-16 14:19:50 +01:00
}
static int __exit at91_shdwc_remove ( struct platform_device * pdev )
{
struct shdwc * shdw = platform_get_drvdata ( pdev ) ;
2018-08-30 14:50:07 +03:00
if ( pm_power_off = = at91_poweroff )
2016-03-16 14:19:50 +01:00
pm_power_off = NULL ;
/* Reset values to disable wake-up features */
2018-08-30 14:50:10 +03:00
writel ( 0 , shdw - > shdwc_base + AT91_SHDW_MR ) ;
writel ( 0 , shdw - > shdwc_base + AT91_SHDW_WUIR ) ;
2016-03-16 14:19:50 +01:00
2018-08-30 14:50:08 +03:00
if ( shdw - > mpddrc_base )
iounmap ( shdw - > mpddrc_base ) ;
2018-08-30 14:50:06 +03:00
iounmap ( shdw - > pmc_base ) ;
2018-08-30 14:50:09 +03:00
clk_disable_unprepare ( shdw - > sclk ) ;
2016-03-16 14:19:50 +01:00
return 0 ;
}
static struct platform_driver at91_shdwc_driver = {
. remove = __exit_p ( at91_shdwc_remove ) ,
. driver = {
. name = " at91-shdwc " ,
. of_match_table = at91_shdwc_of_match ,
} ,
} ;
module_platform_driver_probe ( at91_shdwc_driver , at91_shdwc_probe ) ;
MODULE_AUTHOR ( " Nicolas Ferre <nicolas.ferre@atmel.com> " ) ;
MODULE_DESCRIPTION ( " Atmel shutdown controller driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;