root/drivers/platform/cznic/turris-omnia-mcu-watchdog.c
// SPDX-License-Identifier: GPL-2.0
/*
 * CZ.NIC's Turris Omnia MCU watchdog driver
 *
 * 2024 by Marek BehĂșn <kabel@kernel.org>
 */

#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/units.h>
#include <linux/watchdog.h>

#include <linux/turris-omnia-mcu-interface.h>
#include "turris-omnia-mcu.h"

#define WATCHDOG_TIMEOUT                120

static unsigned int timeout;
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");

static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
                           __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

static int omnia_wdt_start(struct watchdog_device *wdt)
{
        struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

        return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
}

static int omnia_wdt_stop(struct watchdog_device *wdt)
{
        struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

        return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 0);
}

static int omnia_wdt_ping(struct watchdog_device *wdt)
{
        struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

        return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
}

static int omnia_wdt_set_timeout(struct watchdog_device *wdt,
                                 unsigned int timeout)
{
        struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

        return omnia_cmd_write_u16(mcu->client, OMNIA_CMD_SET_WDT_TIMEOUT,
                                   timeout * DECI);
}

static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt)
{
        struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
        u16 timeleft;
        int err;

        err = omnia_cmd_read_u16(mcu->client, OMNIA_CMD_GET_WDT_TIMELEFT,
                                 &timeleft);
        if (err) {
                dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n",
                        err);
                return 0;
        }

        return timeleft / DECI;
}

static const struct watchdog_info omnia_wdt_info = {
        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
        .identity = "Turris Omnia MCU Watchdog",
};

static const struct watchdog_ops omnia_wdt_ops = {
        .owner          = THIS_MODULE,
        .start          = omnia_wdt_start,
        .stop           = omnia_wdt_stop,
        .ping           = omnia_wdt_ping,
        .set_timeout    = omnia_wdt_set_timeout,
        .get_timeleft   = omnia_wdt_get_timeleft,
};

int omnia_mcu_register_watchdog(struct omnia_mcu *mcu)
{
        struct device *dev = &mcu->client->dev;
        u8 state;
        int err;

        if (!(mcu->features & OMNIA_FEAT_WDT_PING))
                return 0;

        mcu->wdt.info = &omnia_wdt_info;
        mcu->wdt.ops = &omnia_wdt_ops;
        mcu->wdt.parent = dev;
        mcu->wdt.min_timeout = 1;
        mcu->wdt.max_timeout = 65535 / DECI;

        mcu->wdt.timeout = WATCHDOG_TIMEOUT;
        watchdog_init_timeout(&mcu->wdt, timeout, dev);

        watchdog_set_drvdata(&mcu->wdt, mcu);

        omnia_wdt_set_timeout(&mcu->wdt, mcu->wdt.timeout);

        err = omnia_cmd_read_u8(mcu->client, OMNIA_CMD_GET_WATCHDOG_STATE,
                                &state);
        if (err)
                return dev_err_probe(dev, err,
                                     "Cannot get MCU watchdog state\n");

        if (state)
                set_bit(WDOG_HW_RUNNING, &mcu->wdt.status);

        watchdog_set_nowayout(&mcu->wdt, nowayout);
        watchdog_stop_on_reboot(&mcu->wdt);
        err = devm_watchdog_register_device(dev, &mcu->wdt);
        if (err)
                return dev_err_probe(dev, err,
                                     "Cannot register MCU watchdog\n");

        return 0;
}