2019-06-20 19:28:46 +03:00
// SPDX-License-Identifier: GPL-2.0+
2010-07-24 21:16:05 +04:00
/*
* Octeon Watchdog driver
*
2017-08-29 18:40:36 +03:00
* Copyright ( C ) 2007 - 2017 Cavium , Inc .
2010-07-24 21:16:05 +04:00
*
2015-03-28 21:05:38 +03:00
* Converted to use WATCHDOG_CORE by Aaro Koskinen < aaro . koskinen @ iki . fi > .
*
2010-07-24 21:16:05 +04:00
* Some parts derived from wdt . c
*
* ( c ) Copyright 1996 - 1997 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
*
* Neither Alan Cox nor CymruNet Ltd . admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*
* ( c ) Copyright 1995 Alan Cox < alan @ lxorguk . ukuu . org . uk >
*
* The OCTEON watchdog has a maximum timeout of 2 ^ 32 * io_clock .
* For most systems this is less than 10 seconds , so to allow for
* software to request longer watchdog heartbeats , we maintain software
* counters to count multiples of the base rate . If the system locks
* up in such a manner that we can not run the software counters , the
* only result is a watchdog reset sooner than was requested . But
* that is OK , because in this case userspace would likely not be able
* to do anything anyhow .
*
* The hardware watchdog interval we call the period . The OCTEON
* watchdog goes through several stages , after the first period an
* irq is asserted , then if it is not reset , after the next period NMI
* is asserted , then after an additional period a chip wide soft reset .
* So for the software counters , we reset watchdog after each period
* and decrement the counter . But for the last two periods we need to
* let the watchdog progress to the NMI stage so we disable the irq
* and let it proceed . Once in the NMI , we print the register state
* to the serial port and then wait for the reset .
*
* A watchdog is maintained for each CPU in the system , that way if
* one CPU suffers a lockup , we also get a register dump and reset .
* The userspace ping resets the watchdog on all CPUs .
*
* Before userspace opens the watchdog device , we still run the
* watchdogs to catch any lockups that may be kernel related .
*
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-07-24 21:16:05 +04:00
# include <linux/interrupt.h>
# include <linux/watchdog.h>
# include <linux/cpumask.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/cpu.h>
2010-10-07 17:08:54 +04:00
# include <linux/irq.h>
2018-09-13 11:30:34 +03:00
# include <linux/irqdomain.h>
2010-07-24 21:16:05 +04:00
# include <asm/mipsregs.h>
# include <asm/uasm.h>
# include <asm/octeon/octeon.h>
2017-08-29 18:40:32 +03:00
# include <asm/octeon/cvmx-boot-vector.h>
2017-08-29 18:40:37 +03:00
# include <asm/octeon/cvmx-ciu2-defs.h>
2017-08-29 18:40:38 +03:00
# include <asm/octeon/cvmx-rst-defs.h>
/* Watchdog interrupt major block number (8 MSBs of intsn) */
# define WD_BLOCK_NUMBER 0x01
2017-08-29 18:40:37 +03:00
static int divisor ;
2010-07-24 21:16:05 +04:00
/* The count needed to achieve timeout_sec. */
static unsigned int timeout_cnt ;
/* The maximum period supported. */
static unsigned int max_timeout_sec ;
/* The current period. */
static unsigned int timeout_sec ;
/* Set to non-zero when userspace countdown mode active */
2017-08-29 18:40:36 +03:00
static bool do_countdown ;
2010-07-24 21:16:05 +04:00
static unsigned int countdown_reset ;
static unsigned int per_cpu_countdown [ NR_CPUS ] ;
static cpumask_t irq_enabled_cpus ;
# define WD_TIMO 60 /* Default heartbeat = 60 seconds */
2017-08-29 18:40:38 +03:00
# define CVMX_GSERX_SCRATCH(offset) (CVMX_ADD_IO_SEG(0x0001180090000020ull) + ((offset) & 15) * 0x1000000ull)
2010-07-24 21:16:05 +04:00
static int heartbeat = WD_TIMO ;
2017-08-29 18:40:36 +03:00
module_param ( heartbeat , int , 0444 ) ;
2010-07-24 21:16:05 +04:00
MODULE_PARM_DESC ( heartbeat ,
" Watchdog heartbeat in seconds. (0 < heartbeat, default= "
__MODULE_STRING ( WD_TIMO ) " ) " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2017-08-29 18:40:36 +03:00
module_param ( nowayout , bool , 0444 ) ;
2010-07-24 21:16:05 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2017-08-29 18:40:36 +03:00
static int disable ;
module_param ( disable , int , 0444 ) ;
MODULE_PARM_DESC ( disable ,
" Disable the watchdog entirely (default=0) " ) ;
2017-08-29 18:40:32 +03:00
static struct cvmx_boot_vector_element * octeon_wdt_bootvector ;
2010-07-24 21:16:05 +04:00
void octeon_wdt_nmi_stage2 ( void ) ;
static int cpu2core ( int cpu )
{
# ifdef CONFIG_SMP
2017-08-29 18:40:38 +03:00
return cpu_logical_map ( cpu ) & 0x3f ;
2010-07-24 21:16:05 +04:00
# else
return cvmx_get_core_num ( ) ;
# endif
}
/**
2021-03-22 09:53:37 +03:00
* octeon_wdt_poke_irq - Poke the watchdog when an interrupt is received
2010-07-24 21:16:05 +04:00
*
* @ cpl :
* @ dev_id :
*
* Returns
*/
static irqreturn_t octeon_wdt_poke_irq ( int cpl , void * dev_id )
{
2017-08-29 18:40:38 +03:00
int cpu = raw_smp_processor_id ( ) ;
unsigned int core = cpu2core ( cpu ) ;
int node = cpu_to_node ( cpu ) ;
2010-07-24 21:16:05 +04:00
2017-08-29 18:40:36 +03:00
if ( do_countdown ) {
2010-07-24 21:16:05 +04:00
if ( per_cpu_countdown [ cpu ] > 0 ) {
/* We're alive, poke the watchdog */
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
2010-07-24 21:16:05 +04:00
per_cpu_countdown [ cpu ] - - ;
} else {
/* Bad news, you are about to reboot. */
disable_irq_nosync ( cpl ) ;
cpumask_clear_cpu ( cpu , & irq_enabled_cpus ) ;
}
} else {
/* Not open, just ping away... */
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
2010-07-24 21:16:05 +04:00
}
return IRQ_HANDLED ;
}
/* From setup.c */
extern int prom_putchar ( char c ) ;
/**
2021-03-22 09:53:37 +03:00
* octeon_wdt_write_string - Write a string to the uart
2010-07-24 21:16:05 +04:00
*
* @ str : String to write
*/
static void octeon_wdt_write_string ( const char * str )
{
/* Just loop writing one byte at a time */
while ( * str )
prom_putchar ( * str + + ) ;
}
/**
2021-03-22 09:53:37 +03:00
* octeon_wdt_write_hex ( ) - Write a hex number out of the uart
2010-07-24 21:16:05 +04:00
*
* @ value : Number to display
* @ digits : Number of digits to print ( 1 to 16 )
*/
static void octeon_wdt_write_hex ( u64 value , int digits )
{
int d ;
int v ;
2015-03-28 21:05:39 +03:00
2010-07-24 21:16:05 +04:00
for ( d = 0 ; d < digits ; d + + ) {
v = ( value > > ( ( digits - d - 1 ) * 4 ) ) & 0xf ;
if ( v > = 10 )
prom_putchar ( ' a ' + v - 10 ) ;
else
prom_putchar ( ' 0 ' + v ) ;
}
}
2015-03-28 21:05:40 +03:00
static const char reg_name [ ] [ 3 ] = {
2010-07-24 21:16:05 +04:00
" $0 " , " at " , " v0 " , " v1 " , " a0 " , " a1 " , " a2 " , " a3 " ,
" a4 " , " a5 " , " a6 " , " a7 " , " t0 " , " t1 " , " t2 " , " t3 " ,
" s0 " , " s1 " , " s2 " , " s3 " , " s4 " , " s5 " , " s6 " , " s7 " ,
" t8 " , " t9 " , " k0 " , " k1 " , " gp " , " sp " , " s8 " , " ra "
} ;
/**
2021-03-22 09:53:37 +03:00
* octeon_wdt_nmi_stage3 :
*
2010-07-24 21:16:05 +04:00
* NMI stage 3 handler . NMIs are handled in the following manner :
* 1 ) The first NMI handler enables CVMSEG and transfers from
* the bootbus region into normal memory . It is careful to not
* destroy any registers .
* 2 ) The second stage handler uses CVMSEG to save the registers
* and create a stack for C code . It then calls the third level
* handler with one argument , a pointer to the register values .
* 3 ) The third , and final , level handler is the following C
* function that prints out some useful infomration .
*
* @ reg : Pointer to register state before the NMI
*/
void octeon_wdt_nmi_stage3 ( u64 reg [ 32 ] )
{
u64 i ;
unsigned int coreid = cvmx_get_core_num ( ) ;
/*
* Save status and cause early to get them before any changes
* might happen .
*/
u64 cp0_cause = read_c0_cause ( ) ;
u64 cp0_status = read_c0_status ( ) ;
u64 cp0_error_epc = read_c0_errorepc ( ) ;
u64 cp0_epc = read_c0_epc ( ) ;
/* Delay so output from all cores output is not jumbled together. */
2017-08-29 18:40:37 +03:00
udelay ( 85000 * coreid ) ;
2010-07-24 21:16:05 +04:00
octeon_wdt_write_string ( " \r \n *** NMI Watchdog interrupt on Core 0x " ) ;
2017-08-29 18:40:37 +03:00
octeon_wdt_write_hex ( coreid , 2 ) ;
2010-07-24 21:16:05 +04:00
octeon_wdt_write_string ( " *** \r \n " ) ;
for ( i = 0 ; i < 32 ; i + + ) {
octeon_wdt_write_string ( " \t " ) ;
octeon_wdt_write_string ( reg_name [ i ] ) ;
octeon_wdt_write_string ( " \t 0x " ) ;
octeon_wdt_write_hex ( reg [ i ] , 16 ) ;
if ( i & 1 )
octeon_wdt_write_string ( " \r \n " ) ;
}
octeon_wdt_write_string ( " \t err_epc \t 0x " ) ;
octeon_wdt_write_hex ( cp0_error_epc , 16 ) ;
octeon_wdt_write_string ( " \t epc \t 0x " ) ;
octeon_wdt_write_hex ( cp0_epc , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
octeon_wdt_write_string ( " \t status \t 0x " ) ;
octeon_wdt_write_hex ( cp0_status , 16 ) ;
octeon_wdt_write_string ( " \t cause \t 0x " ) ;
octeon_wdt_write_hex ( cp0_cause , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
2017-08-29 18:40:37 +03:00
/* The CIU register is different for each Octeon model. */
if ( OCTEON_IS_MODEL ( OCTEON_CN68XX ) ) {
octeon_wdt_write_string ( " \t src_wd \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU2_SRC_PPX_IP2_WDOG ( coreid ) ) , 16 ) ;
octeon_wdt_write_string ( " \t en_wd \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU2_EN_PPX_IP2_WDOG ( coreid ) ) , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
octeon_wdt_write_string ( " \t src_rml \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU2_SRC_PPX_IP2_RML ( coreid ) ) , 16 ) ;
octeon_wdt_write_string ( " \t en_rml \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU2_EN_PPX_IP2_RML ( coreid ) ) , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
octeon_wdt_write_string ( " \t sum \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU2_SUM_PPX_IP2 ( coreid ) ) , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
} else if ( ! octeon_has_feature ( OCTEON_FEATURE_CIU3 ) ) {
octeon_wdt_write_string ( " \t sum0 \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU_INTX_SUM0 ( coreid * 2 ) ) , 16 ) ;
octeon_wdt_write_string ( " \t en0 \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU_INTX_EN0 ( coreid * 2 ) ) , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
}
2010-07-24 21:16:05 +04:00
octeon_wdt_write_string ( " *** Chip soft reset soon *** \r \n " ) ;
2017-08-29 18:40:38 +03:00
/*
* G - 30204 : We must trigger a soft reset before watchdog
* does an incomplete job of doing it .
*/
if ( OCTEON_IS_OCTEON3 ( ) & & ! OCTEON_IS_MODEL ( OCTEON_CN70XX ) ) {
u64 scr ;
unsigned int node = cvmx_get_node_num ( ) ;
unsigned int lcore = cvmx_get_local_core_num ( ) ;
union cvmx_ciu_wdogx ciu_wdog ;
/*
* Wait for other cores to print out information , but
* not too long . Do the soft reset before watchdog
* can trigger it .
*/
do {
ciu_wdog . u64 = cvmx_read_csr_node ( node , CVMX_CIU_WDOGX ( lcore ) ) ;
} while ( ciu_wdog . s . cnt > 0x10000 ) ;
scr = cvmx_read_csr_node ( 0 , CVMX_GSERX_SCRATCH ( 0 ) ) ;
scr | = 1 < < 11 ; /* Indicate watchdog in bit 11 */
cvmx_write_csr_node ( 0 , CVMX_GSERX_SCRATCH ( 0 ) , scr ) ;
cvmx_write_csr_node ( 0 , CVMX_RST_SOFT_RST , 1 ) ;
}
}
static int octeon_wdt_cpu_to_irq ( int cpu )
{
unsigned int coreid ;
int node ;
int irq ;
coreid = cpu2core ( cpu ) ;
node = cpu_to_node ( cpu ) ;
if ( octeon_has_feature ( OCTEON_FEATURE_CIU3 ) ) {
struct irq_domain * domain ;
int hwirq ;
domain = octeon_irq_get_block_domain ( node ,
WD_BLOCK_NUMBER ) ;
hwirq = WD_BLOCK_NUMBER < < 12 | 0x200 | coreid ;
irq = irq_find_mapping ( domain , hwirq ) ;
} else {
irq = OCTEON_IRQ_WDOG0 + coreid ;
}
return irq ;
2010-07-24 21:16:05 +04:00
}
2016-11-17 21:35:32 +03:00
static int octeon_wdt_cpu_pre_down ( unsigned int cpu )
2010-07-24 21:16:05 +04:00
{
unsigned int core ;
2017-08-29 18:40:38 +03:00
int node ;
2010-07-24 21:16:05 +04:00
union cvmx_ciu_wdogx ciu_wdog ;
core = cpu2core ( cpu ) ;
2017-08-29 18:40:38 +03:00
node = cpu_to_node ( cpu ) ;
2010-07-24 21:16:05 +04:00
/* Poke the watchdog to clear out its state */
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
2010-07-24 21:16:05 +04:00
/* Disable the hardware. */
ciu_wdog . u64 = 0 ;
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_WDOGX ( core ) , ciu_wdog . u64 ) ;
2010-07-24 21:16:05 +04:00
2017-08-29 18:40:38 +03:00
free_irq ( octeon_wdt_cpu_to_irq ( cpu ) , octeon_wdt_poke_irq ) ;
2016-11-17 21:35:32 +03:00
return 0 ;
2010-07-24 21:16:05 +04:00
}
2016-11-17 21:35:32 +03:00
static int octeon_wdt_cpu_online ( unsigned int cpu )
2010-07-24 21:16:05 +04:00
{
unsigned int core ;
unsigned int irq ;
union cvmx_ciu_wdogx ciu_wdog ;
2017-08-29 18:40:38 +03:00
int node ;
struct irq_domain * domain ;
int hwirq ;
2010-07-24 21:16:05 +04:00
core = cpu2core ( cpu ) ;
2017-08-29 18:40:38 +03:00
node = cpu_to_node ( cpu ) ;
2010-07-24 21:16:05 +04:00
2017-08-29 18:40:32 +03:00
octeon_wdt_bootvector [ core ] . target_ptr = ( u64 ) octeon_wdt_nmi_stage2 ;
2010-07-24 21:16:05 +04:00
/* Disable it before doing anything with the interrupts. */
ciu_wdog . u64 = 0 ;
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_WDOGX ( core ) , ciu_wdog . u64 ) ;
2010-07-24 21:16:05 +04:00
per_cpu_countdown [ cpu ] = countdown_reset ;
2017-08-29 18:40:38 +03:00
if ( octeon_has_feature ( OCTEON_FEATURE_CIU3 ) ) {
/* Must get the domain for the watchdog block */
domain = octeon_irq_get_block_domain ( node , WD_BLOCK_NUMBER ) ;
/* Get a irq for the wd intsn (hardware interrupt) */
hwirq = WD_BLOCK_NUMBER < < 12 | 0x200 | core ;
irq = irq_create_mapping ( domain , hwirq ) ;
irqd_set_trigger_type ( irq_get_irq_data ( irq ) ,
IRQ_TYPE_EDGE_RISING ) ;
} else
irq = OCTEON_IRQ_WDOG0 + core ;
2010-07-24 21:16:05 +04:00
if ( request_irq ( irq , octeon_wdt_poke_irq ,
2011-10-04 04:22:04 +04:00
IRQF_NO_THREAD , " octeon_wdt " , octeon_wdt_poke_irq ) )
2010-07-24 21:16:05 +04:00
panic ( " octeon_wdt: Couldn't obtain irq %d " , irq ) ;
2017-08-29 18:40:38 +03:00
/* Must set the irq affinity here */
if ( octeon_has_feature ( OCTEON_FEATURE_CIU3 ) ) {
cpumask_t mask ;
cpumask_clear ( & mask ) ;
cpumask_set_cpu ( cpu , & mask ) ;
irq_set_affinity ( irq , & mask ) ;
}
2010-07-24 21:16:05 +04:00
cpumask_set_cpu ( cpu , & irq_enabled_cpus ) ;
/* Poke the watchdog to clear out its state */
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
2010-07-24 21:16:05 +04:00
/* Finally enable the watchdog now that all handlers are installed */
ciu_wdog . u64 = 0 ;
ciu_wdog . s . len = timeout_cnt ;
ciu_wdog . s . mode = 3 ; /* 3 = Interrupt + NMI + Soft-Reset */
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_WDOGX ( core ) , ciu_wdog . u64 ) ;
2010-07-24 21:16:05 +04:00
2016-11-17 21:35:32 +03:00
return 0 ;
2010-07-24 21:16:05 +04:00
}
2015-03-28 21:05:38 +03:00
static int octeon_wdt_ping ( struct watchdog_device __always_unused * wdog )
2010-07-24 21:16:05 +04:00
{
int cpu ;
int coreid ;
2017-08-29 18:40:38 +03:00
int node ;
2010-07-24 21:16:05 +04:00
2017-08-29 18:40:36 +03:00
if ( disable )
return 0 ;
2010-07-24 21:16:05 +04:00
for_each_online_cpu ( cpu ) {
coreid = cpu2core ( cpu ) ;
2017-08-29 18:40:38 +03:00
node = cpu_to_node ( cpu ) ;
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( coreid ) , 1 ) ;
2010-07-24 21:16:05 +04:00
per_cpu_countdown [ cpu ] = countdown_reset ;
2017-08-29 18:40:36 +03:00
if ( ( countdown_reset | | ! do_countdown ) & &
2010-07-24 21:16:05 +04:00
! cpumask_test_cpu ( cpu , & irq_enabled_cpus ) ) {
/* We have to enable the irq */
2017-08-29 18:40:38 +03:00
enable_irq ( octeon_wdt_cpu_to_irq ( cpu ) ) ;
2010-07-24 21:16:05 +04:00
cpumask_set_cpu ( cpu , & irq_enabled_cpus ) ;
}
}
2015-03-28 21:05:38 +03:00
return 0 ;
2010-07-24 21:16:05 +04:00
}
static void octeon_wdt_calc_parameters ( int t )
{
unsigned int periods ;
timeout_sec = max_timeout_sec ;
/*
* Find the largest interrupt period , that can evenly divide
* the requested heartbeat time .
*/
while ( ( t % timeout_sec ) ! = 0 )
timeout_sec - - ;
periods = t / timeout_sec ;
/*
* The last two periods are after the irq is disabled , and
* then to the nmi , so we subtract them off .
*/
countdown_reset = periods > 2 ? periods - 2 : 0 ;
heartbeat = t ;
2017-08-29 18:40:37 +03:00
timeout_cnt = ( ( octeon_get_io_clock_rate ( ) / divisor ) * timeout_sec ) > > 8 ;
2010-07-24 21:16:05 +04:00
}
2015-03-28 21:05:38 +03:00
static int octeon_wdt_set_timeout ( struct watchdog_device * wdog ,
unsigned int t )
2010-07-24 21:16:05 +04:00
{
int cpu ;
int coreid ;
union cvmx_ciu_wdogx ciu_wdog ;
2017-08-29 18:40:38 +03:00
int node ;
2010-07-24 21:16:05 +04:00
if ( t < = 0 )
return - 1 ;
octeon_wdt_calc_parameters ( t ) ;
2017-08-29 18:40:36 +03:00
if ( disable )
return 0 ;
2010-07-24 21:16:05 +04:00
for_each_online_cpu ( cpu ) {
coreid = cpu2core ( cpu ) ;
2017-08-29 18:40:38 +03:00
node = cpu_to_node ( cpu ) ;
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( coreid ) , 1 ) ;
2010-07-24 21:16:05 +04:00
ciu_wdog . u64 = 0 ;
ciu_wdog . s . len = timeout_cnt ;
ciu_wdog . s . mode = 3 ; /* 3 = Interrupt + NMI + Soft-Reset */
2017-08-29 18:40:38 +03:00
cvmx_write_csr_node ( node , CVMX_CIU_WDOGX ( coreid ) , ciu_wdog . u64 ) ;
cvmx_write_csr_node ( node , CVMX_CIU_PP_POKEX ( coreid ) , 1 ) ;
2010-07-24 21:16:05 +04:00
}
2015-03-28 21:05:38 +03:00
octeon_wdt_ping ( wdog ) ; /* Get the irqs back on. */
2010-07-24 21:16:05 +04:00
return 0 ;
}
2015-03-28 21:05:38 +03:00
static int octeon_wdt_start ( struct watchdog_device * wdog )
2010-07-24 21:16:05 +04:00
{
2015-03-28 21:05:38 +03:00
octeon_wdt_ping ( wdog ) ;
2017-08-29 18:40:36 +03:00
do_countdown = 1 ;
2015-03-28 21:05:38 +03:00
return 0 ;
2010-07-24 21:16:05 +04:00
}
2015-03-28 21:05:38 +03:00
static int octeon_wdt_stop ( struct watchdog_device * wdog )
2010-07-24 21:16:05 +04:00
{
2017-08-29 18:40:36 +03:00
do_countdown = 0 ;
2015-03-28 21:05:38 +03:00
octeon_wdt_ping ( wdog ) ;
2010-07-24 21:16:05 +04:00
return 0 ;
}
2015-03-28 21:05:38 +03:00
static const struct watchdog_info octeon_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING ,
. identity = " OCTEON " ,
2010-07-24 21:16:05 +04:00
} ;
2015-03-28 21:05:38 +03:00
static const struct watchdog_ops octeon_wdt_ops = {
. owner = THIS_MODULE ,
. start = octeon_wdt_start ,
. stop = octeon_wdt_stop ,
. ping = octeon_wdt_ping ,
. set_timeout = octeon_wdt_set_timeout ,
2010-07-24 21:16:05 +04:00
} ;
2015-03-28 21:05:38 +03:00
static struct watchdog_device octeon_wdt = {
. info = & octeon_wdt_info ,
. ops = & octeon_wdt_ops ,
} ;
2010-07-24 21:16:05 +04:00
2016-11-17 21:35:32 +03:00
static enum cpuhp_state octeon_wdt_online ;
2010-07-24 21:16:05 +04:00
/**
2021-03-22 09:53:37 +03:00
* octeon_wdt_init - Module / driver initialization .
2010-07-24 21:16:05 +04:00
*
* Returns Zero on success
*/
static int __init octeon_wdt_init ( void )
{
int ret ;
2017-08-29 18:40:32 +03:00
octeon_wdt_bootvector = cvmx_boot_vector_get ( ) ;
if ( ! octeon_wdt_bootvector ) {
pr_err ( " Error: Cannot allocate boot vector. \n " ) ;
return - ENOMEM ;
}
2017-08-29 18:40:37 +03:00
if ( OCTEON_IS_MODEL ( OCTEON_CN68XX ) )
divisor = 0x200 ;
2017-08-29 18:40:38 +03:00
else if ( OCTEON_IS_MODEL ( OCTEON_CN78XX ) )
divisor = 0x400 ;
2017-08-29 18:40:37 +03:00
else
divisor = 0x100 ;
2010-07-24 21:16:05 +04:00
/*
* Watchdog time expiration length = The 16 bits of LEN
* represent the most significant bits of a 24 bit decrementer
2017-08-29 18:40:37 +03:00
* that decrements every divisor cycle .
2010-07-24 21:16:05 +04:00
*
* Try for a timeout of 5 sec , if that fails a smaller number
* of even seconds ,
*/
max_timeout_sec = 6 ;
do {
max_timeout_sec - - ;
2017-08-29 18:40:37 +03:00
timeout_cnt = ( ( octeon_get_io_clock_rate ( ) / divisor ) * max_timeout_sec ) > > 8 ;
2010-07-24 21:16:05 +04:00
} while ( timeout_cnt > 65535 ) ;
BUG_ON ( timeout_cnt = = 0 ) ;
octeon_wdt_calc_parameters ( heartbeat ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Initial granularity %d Sec \n " , timeout_sec ) ;
2010-07-24 21:16:05 +04:00
2015-03-28 21:05:38 +03:00
octeon_wdt . timeout = timeout_sec ;
octeon_wdt . max_timeout = UINT_MAX ;
watchdog_set_nowayout ( & octeon_wdt , nowayout ) ;
ret = watchdog_register_device ( & octeon_wdt ) ;
2010-07-24 21:16:05 +04:00
if ( ret ) {
2015-03-28 21:05:38 +03:00
pr_err ( " watchdog_register_device() failed: %d \n " , ret ) ;
return ret ;
2010-07-24 21:16:05 +04:00
}
2017-08-29 18:40:36 +03:00
if ( disable ) {
pr_notice ( " disabled \n " ) ;
return 0 ;
}
2010-07-24 21:16:05 +04:00
cpumask_clear ( & irq_enabled_cpus ) ;
2016-11-17 21:35:32 +03:00
ret = cpuhp_setup_state ( CPUHP_AP_ONLINE_DYN , " watchdog/octeon:online " ,
octeon_wdt_cpu_online , octeon_wdt_cpu_pre_down ) ;
if ( ret < 0 )
goto err ;
octeon_wdt_online = ret ;
2015-03-28 21:05:38 +03:00
return 0 ;
2016-11-17 21:35:32 +03:00
err :
cvmx_write_csr ( CVMX_MIO_BOOT_LOC_CFGX ( 0 ) , 0 ) ;
watchdog_unregister_device ( & octeon_wdt ) ;
return ret ;
2010-07-24 21:16:05 +04:00
}
/**
2021-03-22 09:53:37 +03:00
* octeon_wdt_cleanup - Module / driver shutdown
2010-07-24 21:16:05 +04:00
*/
static void __exit octeon_wdt_cleanup ( void )
{
2015-03-28 21:05:38 +03:00
watchdog_unregister_device ( & octeon_wdt ) ;
2017-08-29 18:40:36 +03:00
if ( disable )
return ;
2016-11-17 21:35:32 +03:00
cpuhp_remove_state ( octeon_wdt_online ) ;
2014-03-11 00:40:43 +04:00
2010-07-24 21:16:05 +04:00
/*
* Disable the boot - bus memory , the code it points to is soon
* to go missing .
*/
cvmx_write_csr ( CVMX_MIO_BOOT_LOC_CFGX ( 0 ) , 0 ) ;
}
MODULE_LICENSE ( " GPL " ) ;
2017-08-29 18:40:36 +03:00
MODULE_AUTHOR ( " Cavium Inc. <support@cavium.com> " ) ;
MODULE_DESCRIPTION ( " Cavium Inc. OCTEON Watchdog driver. " ) ;
2010-07-24 21:16:05 +04:00
module_init ( octeon_wdt_init ) ;
module_exit ( octeon_wdt_cleanup ) ;