linux/drivers/watchdog/menf21bmc_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  MEN 14F021P00 Board Management Controller (BMC) Watchdog Driver.
   4 *
   5 *  Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH
   6 */
   7
   8#include <linux/kernel.h>
   9#include <linux/device.h>
  10#include <linux/module.h>
  11#include <linux/watchdog.h>
  12#include <linux/platform_device.h>
  13#include <linux/i2c.h>
  14
  15#define DEVNAME "menf21bmc_wdt"
  16
  17#define BMC_CMD_WD_ON           0x11
  18#define BMC_CMD_WD_OFF          0x12
  19#define BMC_CMD_WD_TRIG         0x13
  20#define BMC_CMD_WD_TIME         0x14
  21#define BMC_CMD_WD_STATE        0x17
  22#define BMC_WD_OFF_VAL          0x69
  23#define BMC_CMD_RST_RSN         0x92
  24
  25#define BMC_WD_TIMEOUT_MIN      1       /* in sec */
  26#define BMC_WD_TIMEOUT_MAX      6553    /* in sec */
  27
  28static bool nowayout = WATCHDOG_NOWAYOUT;
  29module_param(nowayout, bool, 0);
  30MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  31                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  32
  33struct menf21bmc_wdt {
  34        struct watchdog_device wdt;
  35        struct i2c_client *i2c_client;
  36};
  37
  38static int menf21bmc_wdt_set_bootstatus(struct menf21bmc_wdt *data)
  39{
  40        int rst_rsn;
  41
  42        rst_rsn = i2c_smbus_read_byte_data(data->i2c_client, BMC_CMD_RST_RSN);
  43        if (rst_rsn < 0)
  44                return rst_rsn;
  45
  46        if (rst_rsn == 0x02)
  47                data->wdt.bootstatus |= WDIOF_CARDRESET;
  48        else if (rst_rsn == 0x05)
  49                data->wdt.bootstatus |= WDIOF_EXTERN1;
  50        else if (rst_rsn == 0x06)
  51                data->wdt.bootstatus |= WDIOF_EXTERN2;
  52        else if (rst_rsn == 0x0A)
  53                data->wdt.bootstatus |= WDIOF_POWERUNDER;
  54
  55        return 0;
  56}
  57
  58static int menf21bmc_wdt_start(struct watchdog_device *wdt)
  59{
  60        struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
  61
  62        return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_ON);
  63}
  64
  65static int menf21bmc_wdt_stop(struct watchdog_device *wdt)
  66{
  67        struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
  68
  69        return i2c_smbus_write_byte_data(drv_data->i2c_client,
  70                                         BMC_CMD_WD_OFF, BMC_WD_OFF_VAL);
  71}
  72
  73static int
  74menf21bmc_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout)
  75{
  76        int ret;
  77        struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
  78
  79        /*
  80         *  BMC Watchdog does have a resolution of 100ms.
  81         *  Watchdog API defines the timeout in seconds, so we have to
  82         *  multiply the value.
  83         */
  84        ret = i2c_smbus_write_word_data(drv_data->i2c_client,
  85                                        BMC_CMD_WD_TIME, timeout * 10);
  86        if (ret < 0)
  87                return ret;
  88
  89        wdt->timeout = timeout;
  90
  91        return 0;
  92}
  93
  94static int menf21bmc_wdt_ping(struct watchdog_device *wdt)
  95{
  96        struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
  97
  98        return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_TRIG);
  99}
 100
 101static const struct watchdog_info menf21bmc_wdt_info = {
 102        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 103        .identity = DEVNAME,
 104};
 105
 106static const struct watchdog_ops menf21bmc_wdt_ops = {
 107        .owner          = THIS_MODULE,
 108        .start          = menf21bmc_wdt_start,
 109        .stop           = menf21bmc_wdt_stop,
 110        .ping           = menf21bmc_wdt_ping,
 111        .set_timeout    = menf21bmc_wdt_settimeout,
 112};
 113
 114static int menf21bmc_wdt_probe(struct platform_device *pdev)
 115{
 116        struct device *dev = &pdev->dev;
 117        int ret, bmc_timeout;
 118        struct menf21bmc_wdt *drv_data;
 119        struct i2c_client *i2c_client = to_i2c_client(dev->parent);
 120
 121        drv_data = devm_kzalloc(dev, sizeof(struct menf21bmc_wdt), GFP_KERNEL);
 122        if (!drv_data)
 123                return -ENOMEM;
 124
 125        drv_data->wdt.ops = &menf21bmc_wdt_ops;
 126        drv_data->wdt.info = &menf21bmc_wdt_info;
 127        drv_data->wdt.min_timeout = BMC_WD_TIMEOUT_MIN;
 128        drv_data->wdt.max_timeout = BMC_WD_TIMEOUT_MAX;
 129        drv_data->wdt.parent = dev;
 130        drv_data->i2c_client = i2c_client;
 131
 132        /*
 133         * Get the current wdt timeout value from the BMC because
 134         * the BMC will save the value set before if the system restarts.
 135         */
 136        bmc_timeout = i2c_smbus_read_word_data(drv_data->i2c_client,
 137                                               BMC_CMD_WD_TIME);
 138        if (bmc_timeout < 0) {
 139                dev_err(dev, "failed to get current WDT timeout\n");
 140                return bmc_timeout;
 141        }
 142
 143        watchdog_init_timeout(&drv_data->wdt, bmc_timeout / 10, dev);
 144        watchdog_set_nowayout(&drv_data->wdt, nowayout);
 145        watchdog_set_drvdata(&drv_data->wdt, drv_data);
 146        platform_set_drvdata(pdev, drv_data);
 147
 148        ret = menf21bmc_wdt_set_bootstatus(drv_data);
 149        if (ret < 0) {
 150                dev_err(dev, "failed to set Watchdog bootstatus\n");
 151                return ret;
 152        }
 153
 154        ret = devm_watchdog_register_device(dev, &drv_data->wdt);
 155        if (ret)
 156                return ret;
 157
 158        dev_info(dev, "MEN 14F021P00 BMC Watchdog device enabled\n");
 159
 160        return 0;
 161}
 162
 163static void menf21bmc_wdt_shutdown(struct platform_device *pdev)
 164{
 165        struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev);
 166
 167        i2c_smbus_write_word_data(drv_data->i2c_client,
 168                                  BMC_CMD_WD_OFF, BMC_WD_OFF_VAL);
 169}
 170
 171static struct  platform_driver menf21bmc_wdt = {
 172        .driver         = {
 173                .name   = DEVNAME,
 174        },
 175        .probe          = menf21bmc_wdt_probe,
 176        .shutdown       = menf21bmc_wdt_shutdown,
 177};
 178
 179module_platform_driver(menf21bmc_wdt);
 180
 181MODULE_DESCRIPTION("MEN 14F021P00 BMC Watchdog driver");
 182MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>");
 183MODULE_LICENSE("GPL v2");
 184MODULE_ALIAS("platform:menf21bmc_wdt");
 185