2014-07-03 14:07:18 +02:00
/*
* Atmel AT91 SAM9 SoCs reset code
*
* Copyright ( C ) 2007 Atmel Corporation .
* Copyright ( C ) 2011 Jean - Christophe PLAGNIOL - VILLARD < plagnioj @ jcrosoft . com >
* Copyright ( C ) 2014 Free Electrons
*
* 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 .
*/
2015-08-11 11:12:50 +02:00
# include <linux/clk.h>
2014-07-03 14:07:18 +02: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>
2014-07-03 14:07:18 +02:00
# include <linux/platform_device.h>
# include <linux/printk.h>
2016-10-25 11:37:59 +02:00
# include <soc/at91/at91sam9_ddrsdr.h>
2014-07-03 14:07:18 +02:00
# define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
# define AT91_SHDW_SHDW BIT(0) /* Shut Down command */
# define AT91_SHDW_KEY (0xa5 << 24) /* KEY Password */
# define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */
# define AT91_SHDW_WKMODE0 GENMASK(2, 0) /* Wake-up 0 Mode Selection */
# define AT91_SHDW_CPTWK0_MAX 0xf /* Maximum Counter On Wake Up 0 */
# define AT91_SHDW_CPTWK0 (AT91_SHDW_CPTWK0_MAX << 4) /* Counter On Wake Up 0 */
# define AT91_SHDW_CPTWK0_(x) ((x) << 4)
# define AT91_SHDW_RTTWKEN BIT(16) /* Real Time Timer Wake-up Enable */
# define AT91_SHDW_RTCWKEN BIT(17) /* Real Time Clock Wake-up Enable */
# define AT91_SHDW_SR 0x08 /* Shut Down Status Register */
# define AT91_SHDW_WAKEUP0 BIT(0) /* Wake-up 0 Status */
# define AT91_SHDW_RTTWK BIT(16) /* Real-time Timer Wake-up */
# define AT91_SHDW_RTCWK BIT(17) /* Real-time Clock Wake-up [SAM9RL] */
enum wakeup_type {
AT91_SHDW_WKMODE0_NONE = 0 ,
AT91_SHDW_WKMODE0_HIGH = 1 ,
AT91_SHDW_WKMODE0_LOW = 2 ,
AT91_SHDW_WKMODE0_ANYLEVEL = 3 ,
} ;
static const char * shdwc_wakeup_modes [ ] = {
[ AT91_SHDW_WKMODE0_NONE ] = " none " ,
[ AT91_SHDW_WKMODE0_HIGH ] = " high " ,
[ AT91_SHDW_WKMODE0_LOW ] = " low " ,
[ AT91_SHDW_WKMODE0_ANYLEVEL ] = " any " ,
} ;
2018-12-06 17:29:46 +00:00
static struct shdwc {
struct clk * sclk ;
void __iomem * shdwc_base ;
void __iomem * mpddrc_base ;
} at91_shdwc ;
2014-07-03 14:07:18 +02:00
2023-11-20 15:35:32 -07:00
static void at91_wakeup_status ( struct platform_device * pdev )
2014-07-03 14:07:18 +02:00
{
2018-03-16 11:02:05 +01:00
const char * reason ;
2018-12-06 17:29:46 +00:00
u32 reg = readl ( at91_shdwc . shdwc_base + AT91_SHDW_SR ) ;
2014-07-03 14:07:18 +02:00
/* Simple power-on, just bail out */
if ( ! reg )
return ;
if ( reg & AT91_SHDW_RTTWK )
reason = " RTT " ;
else if ( reg & AT91_SHDW_RTCWK )
reason = " RTC " ;
2018-03-16 11:02:05 +01:00
else
reason = " unknown " ;
2014-07-03 14:07:18 +02:00
2018-03-16 11:02:05 +01:00
dev_info ( & pdev - > dev , " Wake-Up source: %s \n " , reason ) ;
2014-07-03 14:07:18 +02:00
}
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-11-05 11:14:23 +00: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 "
/* Shutdown CPU */
2018-11-05 11:14:23 +00:00
" 1: str %3, [%2, # " __stringify ( AT91_SHDW_CR ) " ] \n \t "
2016-10-25 11:37:59 +02:00
" b . \n \t "
:
2018-12-06 17:29:46 +00:00
: " r " ( at91_shdwc . mpddrc_base ) ,
2016-10-25 11:37:59 +02:00
" r " cpu_to_le32 ( AT91_DDRSDRC_LPDDR2_PWOFF ) ,
2018-12-06 17:29:46 +00:00
" r " ( at91_shdwc . shdwc_base ) ,
2016-10-25 11:37:59 +02:00
" r " cpu_to_le32 ( AT91_SHDW_KEY | AT91_SHDW_SHDW )
2017-05-31 11:49:14 +02:00
: " r6 " ) ;
2016-10-25 11:37:59 +02:00
}
2014-10-10 17:41:17 -07:00
static int at91_poweroff_get_wakeup_mode ( struct device_node * np )
2014-07-03 14:07:18 +02:00
{
const char * pm ;
2014-10-10 17:41:17 -07:00
unsigned int i ;
int err ;
2014-07-03 14:07:18 +02:00
err = of_property_read_string ( np , " atmel,wakeup-mode " , & pm ) ;
if ( err < 0 )
return AT91_SHDW_WKMODE0_ANYLEVEL ;
for ( i = 0 ; i < ARRAY_SIZE ( shdwc_wakeup_modes ) ; i + + )
if ( ! strcasecmp ( pm , shdwc_wakeup_modes [ i ] ) )
return i ;
return - ENODEV ;
}
static void at91_poweroff_dt_set_wakeup_mode ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
2014-10-10 17:41:17 -07:00
int wakeup_mode ;
2014-07-03 14:07:18 +02:00
u32 mode = 0 , tmp ;
wakeup_mode = at91_poweroff_get_wakeup_mode ( np ) ;
if ( wakeup_mode < 0 ) {
dev_warn ( & pdev - > dev , " shdwc unknown wakeup mode \n " ) ;
return ;
}
if ( ! of_property_read_u32 ( np , " atmel,wakeup-counter " , & tmp ) ) {
if ( tmp > AT91_SHDW_CPTWK0_MAX ) {
dev_warn ( & pdev - > dev ,
" shdwc wakeup counter 0x%x > 0x%x reduce it to 0x%x \n " ,
tmp , AT91_SHDW_CPTWK0_MAX , AT91_SHDW_CPTWK0_MAX ) ;
tmp = AT91_SHDW_CPTWK0_MAX ;
}
mode | = AT91_SHDW_CPTWK0_ ( tmp ) ;
}
if ( of_property_read_bool ( np , " atmel,wakeup-rtc-timer " ) )
mode | = AT91_SHDW_RTCWKEN ;
if ( of_property_read_bool ( np , " atmel,wakeup-rtt-timer " ) )
mode | = AT91_SHDW_RTTWKEN ;
2018-12-06 17:29:46 +00:00
writel ( wakeup_mode | mode , at91_shdwc . shdwc_base + AT91_SHDW_MR ) ;
2014-07-03 14:07:18 +02:00
}
2023-11-04 22:15:03 +01:00
static int at91_poweroff_probe ( struct platform_device * pdev )
2014-07-03 14:07:18 +02:00
{
2016-10-25 11:37:59 +02:00
struct device_node * np ;
u32 ddr_type ;
2015-08-11 11:12:50 +02:00
int ret ;
2014-07-03 14:07:18 +02:00
2023-07-04 21:03:03 +08:00
at91_shdwc . shdwc_base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-12-06 17:29:46 +00:00
if ( IS_ERR ( at91_shdwc . shdwc_base ) )
return PTR_ERR ( at91_shdwc . shdwc_base ) ;
2014-07-03 14:07:18 +02:00
2018-12-06 17:29:46 +00:00
at91_shdwc . sclk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( at91_shdwc . sclk ) )
return PTR_ERR ( at91_shdwc . sclk ) ;
2015-08-11 11:12:50 +02:00
2018-12-06 17:29:46 +00:00
ret = clk_prepare_enable ( at91_shdwc . sclk ) ;
2015-08-11 11:12:50 +02:00
if ( ret ) {
dev_err ( & pdev - > dev , " Could not enable slow clock \n " ) ;
return ret ;
}
2018-03-16 11:02:05 +01:00
at91_wakeup_status ( pdev ) ;
2014-07-03 14:07:18 +02:00
if ( pdev - > dev . of_node )
at91_poweroff_dt_set_wakeup_mode ( pdev ) ;
2016-10-25 11:37:59 +02:00
np = of_find_compatible_node ( NULL , NULL , " atmel,sama5d3-ddramc " ) ;
2018-11-05 11:14:23 +00:00
if ( np ) {
2018-12-06 17:29:46 +00:00
at91_shdwc . mpddrc_base = of_iomap ( np , 0 ) ;
2018-11-05 11:14:23 +00:00
of_node_put ( np ) ;
2016-10-25 11:37:59 +02:00
2018-12-06 17:29:46 +00:00
if ( ! at91_shdwc . mpddrc_base ) {
2018-11-05 11:14:23 +00:00
ret = - ENOMEM ;
goto clk_disable ;
}
2016-10-25 11:37:59 +02:00
2018-12-06 17:29:46 +00:00
ddr_type = readl ( at91_shdwc . mpddrc_base + AT91_DDRSDRC_MDR ) &
2018-11-05 11:14:23 +00:00
AT91_DDRSDRC_MD ;
if ( ddr_type ! = AT91_DDRSDRC_MD_LPDDR2 & &
ddr_type ! = AT91_DDRSDRC_MD_LPDDR3 ) {
2018-12-06 17:29:46 +00:00
iounmap ( at91_shdwc . mpddrc_base ) ;
at91_shdwc . mpddrc_base = NULL ;
2018-11-05 11:14:23 +00:00
}
}
2016-10-25 11:37:59 +02:00
2018-11-05 11:14:23 +00:00
pm_power_off = at91_poweroff ;
2016-10-25 11:37:59 +02:00
2014-07-03 14:07:18 +02:00
return 0 ;
2018-11-05 11:14:23 +00:00
clk_disable :
2018-12-06 17:29:46 +00:00
clk_disable_unprepare ( at91_shdwc . sclk ) ;
2018-11-05 11:14:23 +00:00
return ret ;
2014-07-03 14:07:18 +02:00
}
2023-11-04 22:15:07 +01:00
static void at91_poweroff_remove ( struct platform_device * pdev )
2015-08-11 11:12:49 +02:00
{
2018-11-05 11:14:23 +00:00
if ( pm_power_off = = at91_poweroff )
2015-08-11 11:12:49 +02:00
pm_power_off = NULL ;
2018-12-06 17:29:46 +00:00
if ( at91_shdwc . mpddrc_base )
iounmap ( at91_shdwc . mpddrc_base ) ;
2018-11-05 11:14:23 +00:00
2018-12-06 17:29:46 +00:00
clk_disable_unprepare ( at91_shdwc . sclk ) ;
2015-08-11 11:12:49 +02:00
}
2015-03-16 20:17:12 +01:00
static const struct of_device_id at91_poweroff_of_match [ ] = {
2014-07-03 14:07:18 +02:00
{ . compatible = " atmel,at91sam9260-shdwc " , } ,
{ . compatible = " atmel,at91sam9rl-shdwc " , } ,
{ . compatible = " atmel,at91sam9x5-shdwc " , } ,
{ /*sentinel*/ }
} ;
2016-10-17 15:36:13 -03:00
MODULE_DEVICE_TABLE ( of , at91_poweroff_of_match ) ;
2014-07-03 14:07:18 +02:00
static struct platform_driver at91_poweroff_driver = {
2023-11-04 22:15:03 +01:00
. probe = at91_poweroff_probe ,
2023-11-04 22:15:07 +01:00
. remove_new = at91_poweroff_remove ,
2014-07-03 14:07:18 +02:00
. driver = {
. name = " at91-poweroff " ,
. of_match_table = at91_poweroff_of_match ,
} ,
} ;
2023-11-04 22:15:03 +01:00
module_platform_driver ( at91_poweroff_driver ) ;
2015-08-11 11:12:49 +02:00
MODULE_AUTHOR ( " Atmel Corporation " ) ;
MODULE_DESCRIPTION ( " Shutdown driver for Atmel SoCs " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;