2019-05-27 08:55:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-06-26 14:15:03 -07:00
/*
2023-08-30 15:47:47 -07:00
* Copyright © 2014 - 2023 Broadcom
2017-06-26 14:15:03 -07:00
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/clk.h>
# include <linux/device.h>
# include <linux/err.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/irqreturn.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pm.h>
# include <linux/pm_wakeup.h>
# include <linux/reboot.h>
# include <linux/rtc.h>
# include <linux/stat.h>
# include <linux/suspend.h>
struct brcmstb_waketmr {
struct rtc_device * rtc ;
struct device * dev ;
void __iomem * base ;
2023-01-20 11:01:45 -08:00
unsigned int wake_irq ;
2023-01-24 12:14:30 -08:00
unsigned int alarm_irq ;
2017-06-26 14:15:03 -07:00
struct notifier_block reboot_notifier ;
struct clk * clk ;
u32 rate ;
2023-01-20 11:01:44 -08:00
unsigned long rtc_alarm ;
bool alarm_en ;
2023-08-30 15:47:47 -07:00
bool alarm_expired ;
2017-06-26 14:15:03 -07:00
} ;
# define BRCMSTB_WKTMR_EVENT 0x00
2023-01-20 11:01:42 -08:00
# define WKTMR_ALARM_EVENT BIT(0)
2017-06-26 14:15:03 -07:00
# define BRCMSTB_WKTMR_COUNTER 0x04
# define BRCMSTB_WKTMR_ALARM 0x08
# define BRCMSTB_WKTMR_PRESCALER 0x0C
# define BRCMSTB_WKTMR_PRESCALER_VAL 0x10
# define BRCMSTB_WKTMR_DEFAULT_FREQ 27000000
2023-01-20 11:01:42 -08:00
static inline bool brcmstb_waketmr_is_pending ( struct brcmstb_waketmr * timer )
{
u32 reg ;
reg = readl_relaxed ( timer - > base + BRCMSTB_WKTMR_EVENT ) ;
return ! ! ( reg & WKTMR_ALARM_EVENT ) ;
}
2017-06-26 14:15:03 -07:00
static inline void brcmstb_waketmr_clear_alarm ( struct brcmstb_waketmr * timer )
{
2023-01-20 11:01:44 -08:00
u32 reg ;
2023-01-24 12:14:30 -08:00
if ( timer - > alarm_en & & timer - > alarm_irq )
disable_irq ( timer - > alarm_irq ) ;
2023-01-20 11:01:44 -08:00
timer - > alarm_en = false ;
reg = readl_relaxed ( timer - > base + BRCMSTB_WKTMR_COUNTER ) ;
writel_relaxed ( reg - 1 , timer - > base + BRCMSTB_WKTMR_ALARM ) ;
2023-01-20 11:01:42 -08:00
writel_relaxed ( WKTMR_ALARM_EVENT , timer - > base + BRCMSTB_WKTMR_EVENT ) ;
2017-06-26 14:15:03 -07:00
( void ) readl_relaxed ( timer - > base + BRCMSTB_WKTMR_EVENT ) ;
2023-08-30 15:47:47 -07:00
if ( timer - > alarm_expired ) {
timer - > alarm_expired = false ;
/* maintain call balance */
enable_irq ( timer - > alarm_irq ) ;
}
2017-06-26 14:15:03 -07:00
}
static void brcmstb_waketmr_set_alarm ( struct brcmstb_waketmr * timer ,
unsigned int secs )
{
2023-01-20 11:01:44 -08:00
unsigned int now ;
2017-06-26 14:15:03 -07:00
brcmstb_waketmr_clear_alarm ( timer ) ;
2018-02-26 12:09:58 -08:00
/* Make sure we are actually counting in seconds */
writel_relaxed ( timer - > rate , timer - > base + BRCMSTB_WKTMR_PRESCALER ) ;
2023-01-20 11:01:44 -08:00
writel_relaxed ( secs , timer - > base + BRCMSTB_WKTMR_ALARM ) ;
now = readl_relaxed ( timer - > base + BRCMSTB_WKTMR_COUNTER ) ;
while ( ( int ) ( secs - now ) < = 0 & &
! brcmstb_waketmr_is_pending ( timer ) ) {
secs = now + 1 ;
writel_relaxed ( secs , timer - > base + BRCMSTB_WKTMR_ALARM ) ;
now = readl_relaxed ( timer - > base + BRCMSTB_WKTMR_COUNTER ) ;
}
2017-06-26 14:15:03 -07:00
}
static irqreturn_t brcmstb_waketmr_irq ( int irq , void * data )
{
struct brcmstb_waketmr * timer = data ;
2023-01-24 12:14:30 -08:00
if ( ! timer - > alarm_irq )
pm_wakeup_event ( timer - > dev , 0 ) ;
return IRQ_HANDLED ;
}
static irqreturn_t brcmstb_alarm_irq ( int irq , void * data )
{
struct brcmstb_waketmr * timer = data ;
/* Ignore spurious interrupts */
if ( ! brcmstb_waketmr_is_pending ( timer ) )
return IRQ_HANDLED ;
if ( timer - > alarm_en ) {
2023-08-30 15:47:47 -07:00
if ( device_may_wakeup ( timer - > dev ) ) {
disable_irq_nosync ( irq ) ;
timer - > alarm_expired = true ;
} else {
2023-01-24 12:14:30 -08:00
writel_relaxed ( WKTMR_ALARM_EVENT ,
timer - > base + BRCMSTB_WKTMR_EVENT ) ;
2023-08-30 15:47:47 -07:00
}
2023-01-24 12:14:30 -08:00
rtc_update_irq ( timer - > rtc , 1 , RTC_IRQF | RTC_AF ) ;
2023-08-30 15:47:47 -07:00
} else {
writel_relaxed ( WKTMR_ALARM_EVENT ,
timer - > base + BRCMSTB_WKTMR_EVENT ) ;
2023-01-24 12:14:30 -08:00
}
2017-06-26 14:15:03 -07:00
return IRQ_HANDLED ;
}
struct wktmr_time {
u32 sec ;
u32 pre ;
} ;
static void wktmr_read ( struct brcmstb_waketmr * timer ,
struct wktmr_time * t )
{
u32 tmp ;
do {
t - > sec = readl_relaxed ( timer - > base + BRCMSTB_WKTMR_COUNTER ) ;
tmp = readl_relaxed ( timer - > base + BRCMSTB_WKTMR_PRESCALER_VAL ) ;
} while ( tmp > = timer - > rate ) ;
t - > pre = timer - > rate - tmp ;
}
static int brcmstb_waketmr_prepare_suspend ( struct brcmstb_waketmr * timer )
{
struct device * dev = timer - > dev ;
2023-01-24 12:14:30 -08:00
int ret ;
2017-06-26 14:15:03 -07:00
if ( device_may_wakeup ( dev ) ) {
2023-01-20 11:01:45 -08:00
ret = enable_irq_wake ( timer - > wake_irq ) ;
2017-06-26 14:15:03 -07:00
if ( ret ) {
dev_err ( dev , " failed to enable wake-up interrupt \n " ) ;
return ret ;
}
2023-01-24 12:14:30 -08:00
if ( timer - > alarm_en & & timer - > alarm_irq ) {
ret = enable_irq_wake ( timer - > alarm_irq ) ;
if ( ret ) {
dev_err ( dev , " failed to enable rtc interrupt \n " ) ;
disable_irq_wake ( timer - > wake_irq ) ;
return ret ;
}
}
2017-06-26 14:15:03 -07:00
}
2023-01-24 12:14:30 -08:00
return 0 ;
2017-06-26 14:15:03 -07:00
}
/* If enabled as a wakeup-source, arm the timer when powering off */
static int brcmstb_waketmr_reboot ( struct notifier_block * nb ,
unsigned long action , void * data )
{
struct brcmstb_waketmr * timer ;
timer = container_of ( nb , struct brcmstb_waketmr , reboot_notifier ) ;
/* Set timer for cold boot */
if ( action = = SYS_POWER_OFF )
brcmstb_waketmr_prepare_suspend ( timer ) ;
return NOTIFY_DONE ;
}
static int brcmstb_waketmr_gettime ( struct device * dev ,
struct rtc_time * tm )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
struct wktmr_time now ;
wktmr_read ( timer , & now ) ;
2019-03-25 18:17:19 +01:00
rtc_time64_to_tm ( now . sec , tm ) ;
2017-06-26 14:15:03 -07:00
return 0 ;
}
static int brcmstb_waketmr_settime ( struct device * dev ,
struct rtc_time * tm )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
time64_t sec ;
sec = rtc_tm_to_time64 ( tm ) ;
writel_relaxed ( sec , timer - > base + BRCMSTB_WKTMR_COUNTER ) ;
return 0 ;
}
static int brcmstb_waketmr_getalarm ( struct device * dev ,
struct rtc_wkalrm * alarm )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
2023-01-20 11:01:44 -08:00
alarm - > enabled = timer - > alarm_en ;
rtc_time64_to_tm ( timer - > rtc_alarm , & alarm - > time ) ;
2017-06-26 14:15:03 -07:00
2023-01-20 11:01:42 -08:00
alarm - > pending = brcmstb_waketmr_is_pending ( timer ) ;
2017-06-26 14:15:03 -07:00
return 0 ;
}
2023-01-20 11:01:43 -08:00
static int brcmstb_waketmr_alarm_enable ( struct device * dev ,
unsigned int enabled )
{
2023-01-20 11:01:44 -08:00
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
if ( enabled & & ! timer - > alarm_en ) {
if ( ( int ) ( readl_relaxed ( timer - > base + BRCMSTB_WKTMR_COUNTER ) -
readl_relaxed ( timer - > base + BRCMSTB_WKTMR_ALARM ) ) > = 0 & &
! brcmstb_waketmr_is_pending ( timer ) )
return - EINVAL ;
timer - > alarm_en = true ;
2023-08-30 15:47:47 -07:00
if ( timer - > alarm_irq ) {
if ( timer - > alarm_expired ) {
timer - > alarm_expired = false ;
/* maintain call balance */
enable_irq ( timer - > alarm_irq ) ;
}
2023-01-24 12:14:30 -08:00
enable_irq ( timer - > alarm_irq ) ;
2023-08-30 15:47:47 -07:00
}
2023-01-20 11:01:44 -08:00
} else if ( ! enabled & & timer - > alarm_en ) {
2023-01-24 12:14:30 -08:00
if ( timer - > alarm_irq )
disable_irq ( timer - > alarm_irq ) ;
2023-01-20 11:01:44 -08:00
timer - > alarm_en = false ;
}
2023-01-20 11:01:43 -08:00
return 0 ;
}
2017-06-26 14:15:03 -07:00
static int brcmstb_waketmr_setalarm ( struct device * dev ,
struct rtc_wkalrm * alarm )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
2023-01-20 11:01:44 -08:00
timer - > rtc_alarm = rtc_tm_to_time64 ( & alarm - > time ) ;
2017-06-26 14:15:03 -07:00
2023-01-20 11:01:44 -08:00
brcmstb_waketmr_set_alarm ( timer , timer - > rtc_alarm ) ;
2017-06-26 14:15:03 -07:00
2023-01-20 11:01:43 -08:00
return brcmstb_waketmr_alarm_enable ( dev , alarm - > enabled ) ;
2017-06-26 14:15:03 -07:00
}
static const struct rtc_class_ops brcmstb_waketmr_ops = {
. read_time = brcmstb_waketmr_gettime ,
. set_time = brcmstb_waketmr_settime ,
. read_alarm = brcmstb_waketmr_getalarm ,
. set_alarm = brcmstb_waketmr_setalarm ,
. alarm_irq_enable = brcmstb_waketmr_alarm_enable ,
} ;
static int brcmstb_waketmr_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct brcmstb_waketmr * timer ;
int ret ;
timer = devm_kzalloc ( dev , sizeof ( * timer ) , GFP_KERNEL ) ;
if ( ! timer )
return - ENOMEM ;
platform_set_drvdata ( pdev , timer ) ;
timer - > dev = dev ;
2019-10-06 18:29:20 +08:00
timer - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-06-26 14:15:03 -07:00
if ( IS_ERR ( timer - > base ) )
return PTR_ERR ( timer - > base ) ;
2018-05-20 22:27:29 +02:00
timer - > rtc = devm_rtc_allocate_device ( dev ) ;
if ( IS_ERR ( timer - > rtc ) )
return PTR_ERR ( timer - > rtc ) ;
2017-06-26 14:15:03 -07:00
/*
* Set wakeup capability before requesting wakeup interrupt , so we can
* process boot - time " wakeups " ( e . g . , from S5 soft - off )
*/
2023-01-20 11:01:43 -08:00
device_init_wakeup ( dev , true ) ;
2017-06-26 14:15:03 -07:00
2023-01-20 11:01:45 -08:00
ret = platform_get_irq ( pdev , 0 ) ;
if ( ret < 0 )
2017-06-26 14:15:03 -07:00
return - ENODEV ;
2023-01-20 11:01:45 -08:00
timer - > wake_irq = ( unsigned int ) ret ;
2017-06-26 14:15:03 -07:00
timer - > clk = devm_clk_get ( dev , NULL ) ;
if ( ! IS_ERR ( timer - > clk ) ) {
ret = clk_prepare_enable ( timer - > clk ) ;
if ( ret )
return ret ;
timer - > rate = clk_get_rate ( timer - > clk ) ;
if ( ! timer - > rate )
timer - > rate = BRCMSTB_WKTMR_DEFAULT_FREQ ;
} else {
timer - > rate = BRCMSTB_WKTMR_DEFAULT_FREQ ;
timer - > clk = NULL ;
}
2023-01-20 11:01:45 -08:00
ret = devm_request_irq ( dev , timer - > wake_irq , brcmstb_waketmr_irq , 0 ,
2017-06-26 14:15:03 -07:00
" brcmstb-waketimer " , timer ) ;
if ( ret < 0 )
2017-11-18 00:15:58 +03:00
goto err_clk ;
2017-06-26 14:15:03 -07:00
2023-01-24 12:14:30 -08:00
brcmstb_waketmr_clear_alarm ( timer ) ;
/* Attempt to initialize non-wake irq */
ret = platform_get_irq ( pdev , 1 ) ;
if ( ret > 0 ) {
timer - > alarm_irq = ( unsigned int ) ret ;
ret = devm_request_irq ( dev , timer - > alarm_irq , brcmstb_alarm_irq ,
IRQF_NO_AUTOEN , " brcmstb-waketimer-rtc " ,
timer ) ;
if ( ret < 0 )
timer - > alarm_irq = 0 ;
}
2017-06-26 14:15:03 -07:00
timer - > reboot_notifier . notifier_call = brcmstb_waketmr_reboot ;
register_reboot_notifier ( & timer - > reboot_notifier ) ;
2018-05-20 22:27:29 +02:00
timer - > rtc - > ops = & brcmstb_waketmr_ops ;
2018-05-20 22:27:30 +02:00
timer - > rtc - > range_max = U32_MAX ;
2018-05-20 22:27:29 +02:00
2020-11-09 17:34:08 +01:00
ret = devm_rtc_register_device ( timer - > rtc ) ;
2019-08-19 00:00:41 +02:00
if ( ret )
2017-11-18 00:15:58 +03:00
goto err_notifier ;
2017-06-26 14:15:03 -07:00
2017-11-18 00:15:58 +03:00
return 0 ;
err_notifier :
unregister_reboot_notifier ( & timer - > reboot_notifier ) ;
err_clk :
2020-11-13 07:45:38 +00:00
clk_disable_unprepare ( timer - > clk ) ;
2017-11-18 00:15:58 +03:00
2017-06-26 14:15:03 -07:00
return ret ;
}
2023-03-04 14:29:54 +01:00
static void brcmstb_waketmr_remove ( struct platform_device * pdev )
2017-06-26 14:15:03 -07:00
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( & pdev - > dev ) ;
unregister_reboot_notifier ( & timer - > reboot_notifier ) ;
2019-11-06 00:00:43 +08:00
clk_disable_unprepare ( timer - > clk ) ;
2017-06-26 14:15:03 -07:00
}
# ifdef CONFIG_PM_SLEEP
static int brcmstb_waketmr_suspend ( struct device * dev )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
return brcmstb_waketmr_prepare_suspend ( timer ) ;
}
2023-08-30 15:47:47 -07:00
static int brcmstb_waketmr_suspend_noirq ( struct device * dev )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
/* Catch any alarms occurring prior to noirq */
if ( timer - > alarm_expired & & device_may_wakeup ( dev ) )
return - EBUSY ;
return 0 ;
}
2017-06-26 14:15:03 -07:00
static int brcmstb_waketmr_resume ( struct device * dev )
{
struct brcmstb_waketmr * timer = dev_get_drvdata ( dev ) ;
int ret ;
if ( ! device_may_wakeup ( dev ) )
return 0 ;
2023-01-20 11:01:45 -08:00
ret = disable_irq_wake ( timer - > wake_irq ) ;
2023-01-24 12:14:30 -08:00
if ( timer - > alarm_en & & timer - > alarm_irq )
disable_irq_wake ( timer - > alarm_irq ) ;
2017-06-26 14:15:03 -07:00
brcmstb_waketmr_clear_alarm ( timer ) ;
return ret ;
}
2023-08-30 15:47:47 -07:00
# else
# define brcmstb_waketmr_suspend NULL
# define brcmstb_waketmr_suspend_noirq NULL
# define brcmstb_waketmr_resume NULL
2017-06-26 14:15:03 -07:00
# endif /* CONFIG_PM_SLEEP */
2023-08-30 15:47:47 -07:00
static const struct dev_pm_ops brcmstb_waketmr_pm_ops = {
. suspend = brcmstb_waketmr_suspend ,
. suspend_noirq = brcmstb_waketmr_suspend_noirq ,
. resume = brcmstb_waketmr_resume ,
} ;
2017-06-26 14:15:03 -07:00
2021-02-02 12:22:01 +01:00
static const __maybe_unused struct of_device_id brcmstb_waketmr_of_match [ ] = {
2017-06-26 14:15:03 -07:00
{ . compatible = " brcm,brcmstb-waketimer " } ,
{ /* sentinel */ } ,
} ;
static struct platform_driver brcmstb_waketmr_driver = {
. probe = brcmstb_waketmr_probe ,
2023-03-04 14:29:54 +01:00
. remove_new = brcmstb_waketmr_remove ,
2017-06-26 14:15:03 -07:00
. driver = {
. name = " brcmstb-waketimer " ,
. pm = & brcmstb_waketmr_pm_ops ,
. of_match_table = of_match_ptr ( brcmstb_waketmr_of_match ) ,
}
} ;
module_platform_driver ( brcmstb_waketmr_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Brian Norris " ) ;
MODULE_AUTHOR ( " Markus Mayer " ) ;
2023-01-24 12:14:30 -08:00
MODULE_AUTHOR ( " Doug Berger " ) ;
2017-06-26 14:15:03 -07:00
MODULE_DESCRIPTION ( " Wake-up timer driver for STB chips " ) ;