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 32 static bool nowayout = WATCHDOG_NOWAYOUT; 33 module_param(nowayout, bool, 0); 34 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 35 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 36 37 struct menf21bmc_wdt { 38 struct watchdog_device wdt; 39 struct i2c_client *i2c_client; 40 }; 41 42 static 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 62 static 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 69 static 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 77 static int 78 menf21bmc_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 98 static 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 105 static const struct watchdog_info menf21bmc_wdt_info = { 106 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 107 .identity = DEVNAME, 108 }; 109 110 static 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 118 static int menf21bmc_wdt_probe(struct platform_device *pdev) 119 { 120 struct device *dev = &pdev->dev; 121 int ret, bmc_timeout; 122 struct menf21bmc_wdt *drv_data; 123 struct i2c_client *i2c_client = to_i2c_client(dev->parent); 124 125 drv_data = devm_kzalloc(dev, 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 = 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(dev, "failed to get current WDT timeout\n"); 144 return bmc_timeout; 145 } 146 147 watchdog_init_timeout(&drv_data->wdt, bmc_timeout / 10, 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(dev, "failed to set Watchdog bootstatus\n"); 155 return ret; 156 } 157 158 ret = devm_watchdog_register_device(dev, &drv_data->wdt); 159 if (ret) { 160 dev_err(dev, "failed to register Watchdog device\n"); 161 return ret; 162 } 163 164 dev_info(dev, "MEN 14F021P00 BMC Watchdog device enabled\n"); 165 166 return 0; 167 } 168 169 static void menf21bmc_wdt_shutdown(struct platform_device *pdev) 170 { 171 struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev); 172 173 i2c_smbus_write_word_data(drv_data->i2c_client, 174 BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); 175 } 176 177 static struct platform_driver menf21bmc_wdt = { 178 .driver = { 179 .name = DEVNAME, 180 }, 181 .probe = menf21bmc_wdt_probe, 182 .shutdown = menf21bmc_wdt_shutdown, 183 }; 184 185 module_platform_driver(menf21bmc_wdt); 186 187 MODULE_DESCRIPTION("MEN 14F021P00 BMC Watchdog driver"); 188 MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); 189 MODULE_LICENSE("GPL v2"); 190 MODULE_ALIAS("platform:menf21bmc_wdt"); 191