2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-05-18 11:21:00 +03:00
/*
* Copyright 2016 IBM Corporation
*
* Joel Stanley < joel @ jms . id . au >
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
struct aspeed_wdt {
struct watchdog_device wdd ;
void __iomem * base ;
u32 ctrl ;
} ;
2017-08-02 07:45:29 +03:00
struct aspeed_wdt_config {
u32 ext_pulse_width_mask ;
} ;
static const struct aspeed_wdt_config ast2400_config = {
. ext_pulse_width_mask = 0xff ,
} ;
static const struct aspeed_wdt_config ast2500_config = {
. ext_pulse_width_mask = 0xfffff ,
} ;
2016-05-18 11:21:00 +03:00
static const struct of_device_id aspeed_wdt_of_table [ ] = {
2017-08-02 07:45:29 +03:00
{ . compatible = " aspeed,ast2400-wdt " , . data = & ast2400_config } ,
{ . compatible = " aspeed,ast2500-wdt " , . data = & ast2500_config } ,
2019-08-19 08:17:38 +03:00
{ . compatible = " aspeed,ast2600-wdt " , . data = & ast2500_config } ,
2016-05-18 11:21:00 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , aspeed_wdt_of_table ) ;
# define WDT_STATUS 0x00
# define WDT_RELOAD_VALUE 0x04
# define WDT_RESTART 0x08
# define WDT_CTRL 0x0C
2018-03-15 19:02:06 +03:00
# define WDT_CTRL_BOOT_SECONDARY BIT(7)
2016-05-18 11:21:00 +03:00
# define WDT_CTRL_RESET_MODE_SOC (0x00 << 5)
# define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
2017-07-17 22:25:39 +03:00
# define WDT_CTRL_RESET_MODE_ARM_CPU (0x10 << 5)
2016-05-18 11:21:00 +03:00
# define WDT_CTRL_1MHZ_CLK BIT(4)
# define WDT_CTRL_WDT_EXT BIT(3)
# define WDT_CTRL_WDT_INTR BIT(2)
# define WDT_CTRL_RESET_SYSTEM BIT(1)
# define WDT_CTRL_ENABLE BIT(0)
2018-03-27 23:09:27 +03:00
# define WDT_TIMEOUT_STATUS 0x10
# define WDT_TIMEOUT_STATUS_BOOT_SECONDARY BIT(1)
2019-08-28 13:24:01 +03:00
# define WDT_CLEAR_TIMEOUT_STATUS 0x14
# define WDT_CLEAR_TIMEOUT_AND_BOOT_CODE_SELECTION BIT(0)
2016-05-18 11:21:00 +03:00
2017-08-02 07:45:29 +03:00
/*
* WDT_RESET_WIDTH controls the characteristics of the external pulse ( if
* enabled ) , specifically :
*
* * Pulse duration
* * Drive mode : push - pull vs open - drain
* * Polarity : Active high or active low
*
* Pulse duration configuration is available on both the AST2400 and AST2500 ,
* though the field changes between SoCs :
*
* AST2400 : Bits 7 : 0
* AST2500 : Bits 19 : 0
*
* This difference is captured in struct aspeed_wdt_config .
*
* The AST2500 exposes the drive mode and polarity options , but not in a
* regular fashion . For read purposes , bit 31 represents active high or low ,
* and bit 30 represents push - pull or open - drain . With respect to write , magic
* values need to be written to the top byte to change the state of the drive
* mode and polarity bits . Any other value written to the top byte has no
* effect on the state of the drive mode or polarity bits . However , the pulse
* width value must be preserved ( as desired ) if written .
*/
# define WDT_RESET_WIDTH 0x18
# define WDT_RESET_WIDTH_ACTIVE_HIGH BIT(31)
# define WDT_ACTIVE_HIGH_MAGIC (0xA5 << 24)
# define WDT_ACTIVE_LOW_MAGIC (0x5A << 24)
# define WDT_RESET_WIDTH_PUSH_PULL BIT(30)
# define WDT_PUSH_PULL_MAGIC (0xA8 << 24)
# define WDT_OPEN_DRAIN_MAGIC (0x8A << 24)
2016-05-18 11:21:00 +03:00
# define WDT_RESTART_MAGIC 0x4755
/* 32 bits at 1MHz, in milliseconds */
# define WDT_MAX_TIMEOUT_MS 4294967
# define WDT_DEFAULT_TIMEOUT 30
# define WDT_RATE_1MHZ 1000000
static struct aspeed_wdt * to_aspeed_wdt ( struct watchdog_device * wdd )
{
return container_of ( wdd , struct aspeed_wdt , wdd ) ;
}
static void aspeed_wdt_enable ( struct aspeed_wdt * wdt , int count )
{
wdt - > ctrl | = WDT_CTRL_ENABLE ;
writel ( 0 , wdt - > base + WDT_CTRL ) ;
writel ( count , wdt - > base + WDT_RELOAD_VALUE ) ;
writel ( WDT_RESTART_MAGIC , wdt - > base + WDT_RESTART ) ;
writel ( wdt - > ctrl , wdt - > base + WDT_CTRL ) ;
}
static int aspeed_wdt_start ( struct watchdog_device * wdd )
{
struct aspeed_wdt * wdt = to_aspeed_wdt ( wdd ) ;
aspeed_wdt_enable ( wdt , wdd - > timeout * WDT_RATE_1MHZ ) ;
return 0 ;
}
static int aspeed_wdt_stop ( struct watchdog_device * wdd )
{
struct aspeed_wdt * wdt = to_aspeed_wdt ( wdd ) ;
wdt - > ctrl & = ~ WDT_CTRL_ENABLE ;
writel ( wdt - > ctrl , wdt - > base + WDT_CTRL ) ;
return 0 ;
}
static int aspeed_wdt_ping ( struct watchdog_device * wdd )
{
struct aspeed_wdt * wdt = to_aspeed_wdt ( wdd ) ;
writel ( WDT_RESTART_MAGIC , wdt - > base + WDT_RESTART ) ;
return 0 ;
}
static int aspeed_wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
struct aspeed_wdt * wdt = to_aspeed_wdt ( wdd ) ;
u32 actual ;
wdd - > timeout = timeout ;
actual = min ( timeout , wdd - > max_hw_heartbeat_ms * 1000 ) ;
writel ( actual * WDT_RATE_1MHZ , wdt - > base + WDT_RELOAD_VALUE ) ;
writel ( WDT_RESTART_MAGIC , wdt - > base + WDT_RESTART ) ;
return 0 ;
}
static int aspeed_wdt_restart ( struct watchdog_device * wdd ,
unsigned long action , void * data )
{
struct aspeed_wdt * wdt = to_aspeed_wdt ( wdd ) ;
2018-03-15 19:02:06 +03:00
wdt - > ctrl & = ~ WDT_CTRL_BOOT_SECONDARY ;
2016-05-18 11:21:00 +03:00
aspeed_wdt_enable ( wdt , 128 * WDT_RATE_1MHZ / 1000 ) ;
mdelay ( 1000 ) ;
return 0 ;
}
2019-08-28 13:24:01 +03:00
/* access_cs0 shows if cs0 is accessible, hence the reverted bit */
static ssize_t access_cs0_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct aspeed_wdt * wdt = dev_get_drvdata ( dev ) ;
u32 status = readl ( wdt - > base + WDT_TIMEOUT_STATUS ) ;
return sprintf ( buf , " %u \n " ,
! ( status & WDT_TIMEOUT_STATUS_BOOT_SECONDARY ) ) ;
}
static ssize_t access_cs0_store ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t size )
{
struct aspeed_wdt * wdt = dev_get_drvdata ( dev ) ;
unsigned long val ;
if ( kstrtoul ( buf , 10 , & val ) )
return - EINVAL ;
if ( val )
writel ( WDT_CLEAR_TIMEOUT_AND_BOOT_CODE_SELECTION ,
wdt - > base + WDT_CLEAR_TIMEOUT_STATUS ) ;
return size ;
}
/*
* This attribute exists only if the system has booted from the alternate
* flash with ' alt - boot ' option .
*
* At alternate flash the ' access_cs0 ' sysfs node provides :
* ast2400 : a way to get access to the primary SPI flash chip at CS0
* after booting from the alternate chip at CS1 .
* ast2500 : a way to restore the normal address mapping from
* ( CS0 - > CS1 , CS1 - > CS0 ) to ( CS0 - > CS0 , CS1 - > CS1 ) .
*
* Clearing the boot code selection and timeout counter also resets to the
* initial state the chip select line mapping . When the SoC is in normal
* mapping state ( i . e . booted from CS0 ) , clearing those bits does nothing for
* both versions of the SoC . For alternate boot mode ( booted from CS1 due to
* wdt2 expiration ) the behavior differs as described above .
*
* This option can be used with wdt2 ( watchdog1 ) only .
*/
static DEVICE_ATTR_RW ( access_cs0 ) ;
static struct attribute * bswitch_attrs [ ] = {
& dev_attr_access_cs0 . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( bswitch ) ;
2016-05-18 11:21:00 +03:00
static const struct watchdog_ops aspeed_wdt_ops = {
. start = aspeed_wdt_start ,
. stop = aspeed_wdt_stop ,
. ping = aspeed_wdt_ping ,
. set_timeout = aspeed_wdt_set_timeout ,
. restart = aspeed_wdt_restart ,
. owner = THIS_MODULE ,
} ;
static const struct watchdog_info aspeed_wdt_info = {
. options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT ,
. identity = KBUILD_MODNAME ,
} ;
static int aspeed_wdt_probe ( struct platform_device * pdev )
{
2019-04-08 22:38:27 +03:00
struct device * dev = & pdev - > dev ;
2017-08-02 07:45:29 +03:00
const struct aspeed_wdt_config * config ;
const struct of_device_id * ofdid ;
2016-05-18 11:21:00 +03:00
struct aspeed_wdt * wdt ;
2017-07-17 22:25:39 +03:00
struct device_node * np ;
const char * reset_type ;
2017-08-02 07:45:29 +03:00
u32 duration ;
2018-03-27 23:09:27 +03:00
u32 status ;
2016-05-18 11:21:00 +03:00
int ret ;
2019-04-08 22:38:27 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2016-05-18 11:21:00 +03:00
if ( ! wdt )
return - ENOMEM ;
watchdog: Convert to use devm_platform_ioremap_resource
Use devm_platform_ioremap_resource to reduce source code size,
improve readability, and reduce the likelyhood of bugs.
The conversion was done automatically with coccinelle using the
following semantic patch.
@r@
identifier res, pdev;
expression a;
expression index;
expression e;
@@
<+...
- res = platform_get_resource(pdev, IORESOURCE_MEM, index);
- a = devm_ioremap_resource(e, res);
+ a = devm_platform_ioremap_resource(pdev, index);
...+>
@depends on r@
identifier r.res;
@@
- struct resource *res;
... when != res
@@
identifier res, pdev;
expression index;
expression a;
@@
- struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, index);
- a = devm_ioremap_resource(&pdev->dev, res);
+ a = devm_platform_ioremap_resource(pdev, index);
Cc: Joel Stanley <joel@jms.id.au>
Cc: Nicolas Ferre <nicolas.ferre@microchip.com>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Baruch Siach <baruch@tkos.co.il>
Cc: Keguang Zhang <keguang.zhang@gmail.com>
Cc: Vladimir Zapolskiy <vz@mleia.com>
Cc: Kevin Hilman <khilman@baylibre.com>
Cc: Matthias Brugger <matthias.bgg@gmail.com>
Cc: Avi Fishman <avifishman70@gmail.com>
Cc: Nancy Yuen <yuenn@google.com>
Cc: Brendan Higgins <brendanhiggins@google.com>
Cc: Wan ZongShun <mcuos.com@gmail.com>
Cc: Michal Simek <michal.simek@xilinx.com>
Cc: Sylvain Lemieux <slemieux.tyco@gmail.com>
Cc: Kukjin Kim <kgene@kernel.org>
Cc: Barry Song <baohua@kernel.org>
Cc: Orson Zhai <orsonzhai@gmail.com>
Cc: Patrice Chotard <patrice.chotard@st.com>
Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Cc: Maxime Ripard <maxime.ripard@bootlin.com>
Cc: Chen-Yu Tsai <wens@csie.org>
Cc: Marc Gonzalez <marc.w.gonzalez@free.fr>
Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: Shawn Guo <shawnguo@kernel.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Tested-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Acked-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
Acked-by: Michal Simek <michal.simek@xilinx.com> (cadence/xilinx wdts)
Acked-by: Thierry Reding <treding@nvidia.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Acked-by: Patrice Chotard <patrice.chotard@st.com>
Acked-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
2019-04-02 22:01:53 +03:00
wdt - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2016-05-18 11:21:00 +03:00
if ( IS_ERR ( wdt - > base ) )
return PTR_ERR ( wdt - > base ) ;
/*
* The ast2400 wdt can run at PCLK , or 1 MHz . The ast2500 only
* runs at 1 MHz . We chose to always run at 1 MHz , as there ' s no
* good reason to have a faster watchdog counter .
*/
wdt - > wdd . info = & aspeed_wdt_info ;
wdt - > wdd . ops = & aspeed_wdt_ops ;
wdt - > wdd . max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS ;
2019-04-08 22:38:27 +03:00
wdt - > wdd . parent = dev ;
2016-05-18 11:21:00 +03:00
wdt - > wdd . timeout = WDT_DEFAULT_TIMEOUT ;
2019-04-08 22:38:27 +03:00
watchdog_init_timeout ( & wdt - > wdd , 0 , dev ) ;
2016-05-18 11:21:00 +03:00
2019-04-08 22:38:27 +03:00
np = dev - > of_node ;
2017-08-02 07:45:29 +03:00
ofdid = of_match_node ( aspeed_wdt_of_table , np ) ;
if ( ! ofdid )
return - EINVAL ;
config = ofdid - > data ;
2017-07-17 22:25:39 +03:00
wdt - > ctrl = WDT_CTRL_1MHZ_CLK ;
2016-05-18 11:21:00 +03:00
/*
* Control reset on a per - device basis to ensure the
2017-07-17 22:25:39 +03:00
* host is not affected by a BMC reboot
2016-05-18 11:21:00 +03:00
*/
2017-07-17 22:25:39 +03:00
ret = of_property_read_string ( np , " aspeed,reset-type " , & reset_type ) ;
if ( ret ) {
wdt - > ctrl | = WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM ;
} else {
if ( ! strcmp ( reset_type , " cpu " ) )
2018-03-10 00:58:19 +03:00
wdt - > ctrl | = WDT_CTRL_RESET_MODE_ARM_CPU |
WDT_CTRL_RESET_SYSTEM ;
2017-07-17 22:25:39 +03:00
else if ( ! strcmp ( reset_type , " soc " ) )
2018-03-10 00:58:19 +03:00
wdt - > ctrl | = WDT_CTRL_RESET_MODE_SOC |
WDT_CTRL_RESET_SYSTEM ;
2017-07-17 22:25:39 +03:00
else if ( ! strcmp ( reset_type , " system " ) )
2018-03-10 00:58:19 +03:00
wdt - > ctrl | = WDT_CTRL_RESET_MODE_FULL_CHIP |
WDT_CTRL_RESET_SYSTEM ;
2017-07-17 22:25:39 +03:00
else if ( strcmp ( reset_type , " none " ) )
return - EINVAL ;
}
if ( of_property_read_bool ( np , " aspeed,external-signal " ) )
wdt - > ctrl | = WDT_CTRL_WDT_EXT ;
2018-03-15 19:02:06 +03:00
if ( of_property_read_bool ( np , " aspeed,alt-boot " ) )
wdt - > ctrl | = WDT_CTRL_BOOT_SECONDARY ;
2017-07-17 22:25:39 +03:00
2016-05-18 11:21:00 +03:00
if ( readl ( wdt - > base + WDT_CTRL ) & WDT_CTRL_ENABLE ) {
2017-09-20 08:30:17 +03:00
/*
* The watchdog is running , but invoke aspeed_wdt_start ( ) to
* write wdt - > ctrl to WDT_CTRL to ensure the watchdog ' s
* configuration conforms to the driver ' s expectations .
* Primarily , ensure we ' re using the 1 MHz clock source .
*/
2016-05-18 11:21:00 +03:00
aspeed_wdt_start ( & wdt - > wdd ) ;
set_bit ( WDOG_HW_RUNNING , & wdt - > wdd . status ) ;
}
2019-08-19 08:17:38 +03:00
if ( ( of_device_is_compatible ( np , " aspeed,ast2500-wdt " ) ) | |
( of_device_is_compatible ( np , " aspeed,ast2600-wdt " ) ) ) {
2017-08-02 07:45:29 +03:00
u32 reg = readl ( wdt - > base + WDT_RESET_WIDTH ) ;
reg & = config - > ext_pulse_width_mask ;
if ( of_property_read_bool ( np , " aspeed,ext-push-pull " ) )
reg | = WDT_PUSH_PULL_MAGIC ;
else
reg | = WDT_OPEN_DRAIN_MAGIC ;
writel ( reg , wdt - > base + WDT_RESET_WIDTH ) ;
reg & = config - > ext_pulse_width_mask ;
if ( of_property_read_bool ( np , " aspeed,ext-active-high " ) )
reg | = WDT_ACTIVE_HIGH_MAGIC ;
else
reg | = WDT_ACTIVE_LOW_MAGIC ;
writel ( reg , wdt - > base + WDT_RESET_WIDTH ) ;
}
if ( ! of_property_read_u32 ( np , " aspeed,ext-pulse-duration " , & duration ) ) {
u32 max_duration = config - > ext_pulse_width_mask + 1 ;
if ( duration = = 0 | | duration > max_duration ) {
2019-04-08 22:38:27 +03:00
dev_err ( dev , " Invalid pulse duration: %uus \n " ,
duration ) ;
2017-08-02 07:45:29 +03:00
duration = max ( 1U , min ( max_duration , duration ) ) ;
2019-04-08 22:38:27 +03:00
dev_info ( dev , " Pulse duration set to %uus \n " ,
duration ) ;
2017-08-02 07:45:29 +03:00
}
/*
* The watchdog is always configured with a 1 MHz source , so
* there is no need to scale the microsecond value . However we
* need to offset it - from the datasheet :
*
* " This register decides the asserting duration of wdt_ext and
* wdt_rstarm signal . The default value is 0xFF . It means the
* default asserting duration of wdt_ext and wdt_rstarm is
* 256u s . "
*
* This implies a value of 0 gives a 1u s pulse .
*/
writel ( duration - 1 , wdt - > base + WDT_RESET_WIDTH ) ;
}
2018-03-27 23:09:27 +03:00
status = readl ( wdt - > base + WDT_TIMEOUT_STATUS ) ;
2019-08-28 13:24:01 +03:00
if ( status & WDT_TIMEOUT_STATUS_BOOT_SECONDARY ) {
2018-03-27 23:09:27 +03:00
wdt - > wdd . bootstatus = WDIOF_CARDRESET ;
2019-08-28 13:24:01 +03:00
if ( of_device_is_compatible ( np , " aspeed,ast2400-wdt " ) | |
of_device_is_compatible ( np , " aspeed,ast2500-wdt " ) )
wdt - > wdd . groups = bswitch_groups ;
}
dev_set_drvdata ( dev , wdt ) ;
2019-05-19 00:27:18 +03:00
return devm_watchdog_register_device ( dev , & wdt - > wdd ) ;
2016-05-18 11:21:00 +03:00
}
static struct platform_driver aspeed_watchdog_driver = {
. probe = aspeed_wdt_probe ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = of_match_ptr ( aspeed_wdt_of_table ) ,
} ,
} ;
2017-09-20 08:30:20 +03:00
static int __init aspeed_wdt_init ( void )
{
return platform_driver_register ( & aspeed_watchdog_driver ) ;
}
arch_initcall ( aspeed_wdt_init ) ;
static void __exit aspeed_wdt_exit ( void )
{
platform_driver_unregister ( & aspeed_watchdog_driver ) ;
}
module_exit ( aspeed_wdt_exit ) ;
2016-05-18 11:21:00 +03:00
MODULE_DESCRIPTION ( " Aspeed Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;