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