// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for watchdog aspect of for Zodiac Inflight Innovations RAVE
* Supervisory Processor ( SP ) MCU
*
* Copyright ( C ) 2017 Zodiac Inflight Innovation
*
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/mfd/rave-sp.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/watchdog.h>
enum {
RAVE_SP_RESET_BYTE = 1 ,
RAVE_SP_RESET_REASON_NORMAL = 0 ,
RAVE_SP_RESET_DELAY_MS = 500 ,
};
/**
* struct rave_sp_wdt_variant - RAVE SP watchdog variant
*
* @ max_timeout : Largest possible watchdog timeout setting
* @ min_timeout : Smallest possible watchdog timeout setting
*
* @ configure : Function to send configuration command
* @ restart : Function to send " restart " command
*/
struct rave_sp_wdt_variant {
unsigned int max_timeout;
unsigned int min_timeout;
int (*configure)(struct watchdog_device *, bool );
int (*restart)(struct watchdog_device *);
};
/**
* struct rave_sp_wdt - RAVE SP watchdog
*
* @ wdd : Underlying watchdog device
* @ sp : Pointer to parent RAVE SP device
* @ variant : Device specific variant information
* @ reboot_notifier : Reboot notifier implementing machine reset
*/
struct rave_sp_wdt {
struct watchdog_device wdd;
struct rave_sp *sp;
const struct rave_sp_wdt_variant *variant;
struct notifier_block reboot_notifier;
};
static struct rave_sp_wdt *to_rave_sp_wdt(struct watchdog_device *wdd)
{
return container_of(wdd, struct rave_sp_wdt, wdd);
}
static int rave_sp_wdt_exec(struct watchdog_device *wdd, void *data,
size_t data_size)
{
return rave_sp_exec(to_rave_sp_wdt(wdd)->sp,
data, data_size, NULL, 0 );
}
static int rave_sp_wdt_legacy_configure(struct watchdog_device *wdd, bool on)
{
u8 cmd[] = {
[0 ] = RAVE_SP_CMD_SW_WDT,
[1 ] = 0 ,
[2 ] = 0 ,
[3 ] = on,
[4 ] = on ? wdd->timeout : 0 ,
};
return rave_sp_wdt_exec(wdd, cmd, sizeof (cmd));
}
static int rave_sp_wdt_rdu_configure(struct watchdog_device *wdd, bool on)
{
u8 cmd[] = {
[0 ] = RAVE_SP_CMD_SW_WDT,
[1 ] = 0 ,
[2 ] = on,
[3 ] = (u8)wdd->timeout,
[4 ] = (u8)(wdd->timeout >> 8 ),
};
return rave_sp_wdt_exec(wdd, cmd, sizeof (cmd));
}
/**
* rave_sp_wdt_configure - Configure watchdog device
*
* @ wdd : Device to configure
* @ on : Desired state of the watchdog timer ( ON / OFF )
*
* This function configures two aspects of the watchdog timer :
*
* - Wheither it is ON or OFF
* - Its timeout duration
*
* with first aspect specified via function argument and second via
* the value of ' wdd - > timeout ' .
*/
static int rave_sp_wdt_configure(struct watchdog_device *wdd, bool on)
{
return to_rave_sp_wdt(wdd)->variant->configure(wdd, on);
}
static int rave_sp_wdt_legacy_restart(struct watchdog_device *wdd)
{
u8 cmd[] = {
[0 ] = RAVE_SP_CMD_RESET,
[1 ] = 0 ,
[2 ] = RAVE_SP_RESET_BYTE
};
return rave_sp_wdt_exec(wdd, cmd, sizeof (cmd));
}
static int rave_sp_wdt_rdu_restart(struct watchdog_device *wdd)
{
u8 cmd[] = {
[0 ] = RAVE_SP_CMD_RESET,
[1 ] = 0 ,
[2 ] = RAVE_SP_RESET_BYTE,
[3 ] = RAVE_SP_RESET_REASON_NORMAL
};
return rave_sp_wdt_exec(wdd, cmd, sizeof (cmd));
}
static int rave_sp_wdt_reboot_notifier(struct notifier_block *nb,
unsigned long action, void *data)
{
/*
* Restart handler is called in atomic context which means we
* can ' t communicate to SP via UART . Luckily for use SP will
* wait 500 ms before actually resetting us , so we ask it to do
* so here and let the rest of the system go on wrapping
* things up .
*/
if (action == SYS_DOWN || action == SYS_HALT) {
struct rave_sp_wdt *sp_wd =
container_of(nb, struct rave_sp_wdt, reboot_notifier);
const int ret = sp_wd->variant->restart(&sp_wd->wdd);
if (ret < 0 )
dev_err(sp_wd->wdd.parent,
"Failed to issue restart command (%d)" , ret);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static int rave_sp_wdt_restart(struct watchdog_device *wdd,
unsigned long action, void *data)
{
/*
* The actual work was done by reboot notifier above . SP
* firmware waits 500 ms before issuing reset , so let ' s hang
* here for twice that delay and hopefuly we ' d never reach
* the return statement .
*/
mdelay(2 * RAVE_SP_RESET_DELAY_MS);
return -EIO;
}
static int rave_sp_wdt_start(struct watchdog_device *wdd)
{
int ret;
ret = rave_sp_wdt_configure(wdd, true );
if (!ret)
set_bit(WDOG_HW_RUNNING, &wdd->status);
return ret;
}
static int rave_sp_wdt_stop(struct watchdog_device *wdd)
{
return rave_sp_wdt_configure(wdd, false );
}
static int rave_sp_wdt_set_timeout(struct watchdog_device *wdd,
unsigned int timeout)
{
wdd->timeout = timeout;
return rave_sp_wdt_configure(wdd, watchdog_active(wdd));
}
static int rave_sp_wdt_ping(struct watchdog_device *wdd)
{
u8 cmd[] = {
[0 ] = RAVE_SP_CMD_PET_WDT,
[1 ] = 0 ,
};
return rave_sp_wdt_exec(wdd, cmd, sizeof (cmd));
}
static const struct watchdog_info rave_sp_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.identity = "RAVE SP Watchdog" ,
};
static const struct watchdog_ops rave_sp_wdt_ops = {
.owner = THIS_MODULE,
.start = rave_sp_wdt_start,
.stop = rave_sp_wdt_stop,
.ping = rave_sp_wdt_ping,
.set_timeout = rave_sp_wdt_set_timeout,
.restart = rave_sp_wdt_restart,
};
static const struct rave_sp_wdt_variant rave_sp_wdt_legacy = {
.max_timeout = 255 ,
.min_timeout = 1 ,
.configure = rave_sp_wdt_legacy_configure,
.restart = rave_sp_wdt_legacy_restart,
};
static const struct rave_sp_wdt_variant rave_sp_wdt_rdu = {
.max_timeout = 180 ,
.min_timeout = 60 ,
.configure = rave_sp_wdt_rdu_configure,
.restart = rave_sp_wdt_rdu_restart,
};
static const struct of_device_id rave_sp_wdt_of_match[] = {
{
.compatible = "zii,rave-sp-watchdog-legacy" ,
.data = &rave_sp_wdt_legacy,
},
{
.compatible = "zii,rave-sp-watchdog" ,
.data = &rave_sp_wdt_rdu,
},
{ /* sentinel */ }
};
static int rave_sp_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct watchdog_device *wdd;
struct rave_sp_wdt *sp_wd;
struct nvmem_cell *cell;
__le16 timeout = 0 ;
int ret;
sp_wd = devm_kzalloc(dev, sizeof (*sp_wd), GFP_KERNEL);
if (!sp_wd)
return -ENOMEM;
sp_wd->variant = of_device_get_match_data(dev);
sp_wd->sp = dev_get_drvdata(dev->parent);
wdd = &sp_wd->wdd;
wdd->parent = dev;
wdd->info = &rave_sp_wdt_info;
wdd->ops = &rave_sp_wdt_ops;
wdd->min_timeout = sp_wd->variant->min_timeout;
wdd->max_timeout = sp_wd->variant->max_timeout;
wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS;
wdd->timeout = 60 ;
cell = nvmem_cell_get(dev, "wdt-timeout" );
if (!IS_ERR(cell)) {
size_t len;
void *value = nvmem_cell_read(cell, &len);
if (!IS_ERR(value)) {
memcpy(&timeout, value, min(len, sizeof (timeout)));
kfree(value);
}
nvmem_cell_put(cell);
}
watchdog_init_timeout(wdd, le16_to_cpu(timeout), dev);
watchdog_set_restart_priority(wdd, 255 );
watchdog_stop_on_unregister(wdd);
sp_wd->reboot_notifier.notifier_call = rave_sp_wdt_reboot_notifier;
ret = devm_register_reboot_notifier(dev, &sp_wd->reboot_notifier);
if (ret) {
dev_err(dev, "Failed to register reboot notifier\n" );
return ret;
}
/*
* We don ' t know if watchdog is running now . To be sure , let ' s
* start it and depend on watchdog core to ping it
*/
wdd->max_hw_heartbeat_ms = wdd->max_timeout * 1000 ;
ret = rave_sp_wdt_start(wdd);
if (ret) {
dev_err(dev, "Watchdog didn't start\n" );
return ret;
}
ret = devm_watchdog_register_device(dev, wdd);
if (ret) {
rave_sp_wdt_stop(wdd);
return ret;
}
return 0 ;
}
static struct platform_driver rave_sp_wdt_driver = {
.probe = rave_sp_wdt_probe,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = rave_sp_wdt_of_match,
},
};
module_platform_driver(rave_sp_wdt_driver);
MODULE_DEVICE_TABLE(of, rave_sp_wdt_of_match);
MODULE_LICENSE("GPL" );
MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>" );
MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>" );
MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>" );
MODULE_DESCRIPTION("RAVE SP Watchdog driver" );
MODULE_ALIAS("platform:rave-sp-watchdog" );
Messung V0.5 in Prozent C=98 H=100 G=98
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland