diff mbox series

[RFT] Prototype of new watchdog

Message ID 20230615104137.20304-2-oneukum@suse.com
State New
Headers show
Series [RFT] Prototype of new watchdog | expand

Commit Message

Oliver Neukum June 15, 2023, 10:40 a.m. UTC
---
 drivers/watchdog/Kconfig         |  16 ++
 drivers/watchdog/Makefile        |   1 +
 drivers/watchdog/pcwd_usb_nova.c | 425 +++++++++++++++++++++++++++++++
 3 files changed, 442 insertions(+)
 create mode 100644 drivers/watchdog/pcwd_usb_nova.c

Comments

Christophe Leroy June 15, 2023, 2:29 p.m. UTC | #1
Please explain what this driver is.

Is that a driver for a new type of pcwd_usb ?
Is that a new driver for an existing equipment ? Why a new driver then ?

If it is an evolution of the existing driver, please do it in place of 
the existing driver so that we see what is changed and what remains.

Christophe

Le 15/06/2023 à 12:40, Oliver Neukum a écrit :
> ---
>   drivers/watchdog/Kconfig         |  16 ++
>   drivers/watchdog/Makefile        |   1 +
>   drivers/watchdog/pcwd_usb_nova.c | 425 +++++++++++++++++++++++++++++++
>   3 files changed, 442 insertions(+)
>   create mode 100644 drivers/watchdog/pcwd_usb_nova.c
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index f22138709bf5..f7999fb7caaa 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -2210,6 +2210,22 @@ config USBPCWATCHDOG
>   
>   	  Most people will say N.
>   
> +config USBPCWATCHDOGNOVA
> +        tristate "Berkshire Products USB-PC Watchdog (new driver)"
> +        depends on USB
> +        help
> +          This is the driver for the Berkshire Products USB-PC Watchdog card.
> +          This card simply watches your kernel to make sure it doesn't freeze,
> +          and if it does, it reboots your computer after a certain amount of
> +          time. The card can also monitor the internal temperature of the PC.
> +          More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>.
> +
> +          To compile this driver as a module, choose M here: the
> +          module will be called pcwd_usb_nova.
> +
> +          Most people will say N.
> +
> +
>   config KEEMBAY_WATCHDOG
>   	tristate "Intel Keem Bay SoC non-secure watchdog"
>   	depends on ARCH_KEEMBAY || (ARM64 && COMPILE_TEST)
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index b4c4ccf2d703..8364e0184424 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_WDTPCI) += wdt_pci.o
>   
>   # USB-based Watchdog Cards
>   obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
> +obj-$(CONFIG_USBPCWATCHDOGNOVA) += pcwd_usb_nova.o
>   
>   # ALPHA Architecture
>   
> diff --git a/drivers/watchdog/pcwd_usb_nova.c b/drivers/watchdog/pcwd_usb_nova.c
> new file mode 100644
> index 000000000000..8a92ba8776ae
> --- /dev/null
> +++ b/drivers/watchdog/pcwd_usb_nova.c
> @@ -0,0 +1,425 @@
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/errno.h>
> +#include <linux/usb.h>
> +#include <linux/mutex.h>
> +
> +#include <linux/watchdog.h>
> +#include <linux/hid.h>
> +
> +#include <asm/byteorder.h>
> +#include <asm/unaligned.h>
> +
> +#define MIN_BUF_LENGTH 8
> +
> +/* stuff taken from the old driver */
> +
> +/* according to documentation max. time to process a command for the USB
> + * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
> +#define USB_COMMAND_TIMEOUT	250
> +
> +/* Watchdog's internal commands */
> +#define CMD_READ_TEMP			0x02    /* Read Temperature;
> +							Re-trigger Watchdog */
> +#define CMD_TRIGGER			CMD_READ_TEMP
> +#define CMD_GET_STATUS			0x04	/* Get Status Information */
> +#define CMD_GET_FIRMWARE_VERSION	0x08	/* Get Firmware Version */
> +#define CMD_GET_DIP_SWITCH_SETTINGS	0x0c	/* Get Dip Switch Settings */
> +#define CMD_READ_WATCHDOG_TIMEOUT	0x18	/* Read Current Watchdog Time */
> +#define CMD_WRITE_WATCHDOG_TIMEOUT	0x19	/* Write Current WatchdogTime */
> +#define CMD_ENABLE_WATCHDOG		0x30	/* Enable / Disable Watchdog */
> +#define CMD_DISABLE_WATCHDOG		CMD_ENABLE_WATCHDOG
> +
> +/* Watchdog's Dip Switch heartbeat values */
> +static const int heartbeat_tbl[] = {
> +	5,	/* OFF-OFF-OFF	=  5 Sec  */
> +	10,	/* OFF-OFF-ON	= 10 Sec  */
> +	30,	/* OFF-ON-OFF	= 30 Sec  */
> +	60,	/* OFF-ON-ON	=  1 Min  */
> +	300,	/* ON-OFF-OFF	=  5 Min  */
> +	600,	/* ON-OFF-ON	= 10 Min  */
> +	1800,	/* ON-ON-OFF	= 30 Min  */
> +	3600,	/* ON-ON-ON	=  1 hour */
> +};
> +
> +/* The vendor and product id's for the USB-PC Watchdog card */
> +#define USB_PCWD_VENDOR_ID	0x0c98
> +#define USB_PCWD_PRODUCT_ID	0x1140
> +
> +/*
> + * --- data structures ---
> + */
> +
> +struct pcwd_nova_command {
> +	u8 command;
> +	__be16 value;
> +	u8 reserved0;
> +	u8 reserved1;
> +	u8 reserved2;
> +}__attribute__ ((__packed__));
> +
> +struct pcwd_nova_wdt {
> +	struct watchdog_device wdd;
> +	struct mutex command_excl;
> +	struct completion response_available;
> +	int command_result;
> +	struct usb_interface *interface;
> +	int intr_size;
> +	struct pcwd_nova_command *intr_buffer;
> +	struct urb *intr_urb;
> +	struct pcwd_nova_command *command_buffer;
> +};
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
> +		__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +static const struct watchdog_info pcwd_nova_info;
> +/*
> + * --- helpers ---
> + */
> +
> +static bool ack_valid(int response)
> +{
> +	/* the upper byte can be garbage */
> +	return (response & 0xff) != 0x00;
> +}
> +
> +static int send_command(struct pcwd_nova_wdt *pwd, u8 command, u16 value)
> +{
> +	int result = -EIO;
> +	int transmitted;
> +	long left;
> +	struct pcwd_nova_command *cmd = pwd->command_buffer;
> +	struct usb_device *dev = interface_to_usbdev(pwd->interface);
> +
> +	mutex_lock(&pwd->command_excl);
> +
> +	cmd->command = command;
> +	cmd->value = cpu_to_be16(value);
> +	cmd->reserved0 = cmd->reserved1 = cmd->reserved2 = 0;
> +
> +	reinit_completion(&pwd->response_available);
> +	transmitted = usb_control_msg(
> +			dev,
> +			usb_sndctrlpipe(dev, 0),
> +			HID_REQ_SET_REPORT,
> +			HID_DT_REPORT,
> +			0x0200,
> +			pwd->interface->cur_altsetting->desc.bInterfaceNumber,
> +			cmd,
> +			sizeof(struct pcwd_nova_command),
> +			USB_CTRL_SET_TIMEOUT);
> +	if (transmitted != sizeof(struct pcwd_nova_command))
> +		goto error;
> +
> +	left = wait_for_completion_timeout(&pwd->response_available,
> +			msecs_to_jiffies(USB_COMMAND_TIMEOUT));
> +	if (!left)
> +		goto error;
> +	result = pwd->command_result;
> +error:
> +	mutex_unlock(&pwd->command_excl);
> +
> +	return result;
> +}
> +
> +static int usb_pcwd_stop(struct pcwd_nova_wdt *pwd)
> +{
> +	int retval;
> +
> +	retval = send_command(pwd, CMD_DISABLE_WATCHDOG, 0xA5C3);
> +	if (retval < 0)
> +		goto error;
> +
> +	if (!ack_valid(retval))
> +		retval = -EIO;
> +	else
> +		retval = 0;
> +error:
> +	return retval;
> +}
> +
> +static int usb_pcwd_start(struct pcwd_nova_wdt *pwd)
> +{
> +	int retval;
> +
> +	retval = send_command(pwd, CMD_ENABLE_WATCHDOG, 0x0000);
> +	if (retval < 0)
> +		goto error;
> +
> +	if (!ack_valid(retval))
> +		retval = -EIO;
> +	else
> +		retval = 0;
> +
> +error:
> +	return retval;
> +}
> +
> +static int usb_pcwd_set_heartbeat(struct pcwd_nova_wdt *pwd, u16 hb)
> +{
> +	int retval;
> +
> +	retval = send_command(pwd, CMD_WRITE_WATCHDOG_TIMEOUT, hb);
> +
> +	return retval;
> +}
> +
> +static int usb_pcwd_keepalive(struct pcwd_nova_wdt *pwd)
> +{
> +	int retval;
> +
> +	retval = send_command(pwd, CMD_TRIGGER, 0x00);
> +
> +	return retval;
> +}
> +
> +static int usb_pcwd_get_timeleft(struct pcwd_nova_wdt *pwd)
> +{
> +	int retval;
> +
> +	retval = send_command(pwd, CMD_READ_WATCHDOG_TIMEOUT, 0x00);
> +
> +	return retval < 0 ? -EIO : retval;
> +}
> +
> +static int evaluate_device(struct pcwd_nova_wdt *pwd)
> +{
> +	int rv;
> +	u16 heartbeat;
> +
> +	rv = usb_pcwd_stop(pwd);
> +	if (rv < 0)
> +		goto error;
> +
> +	/* errors intentionally ignored */
> +	send_command(pwd, CMD_GET_FIRMWARE_VERSION, 0);
> +
> +	rv = send_command(pwd, CMD_GET_DIP_SWITCH_SETTINGS, 0);
> +	if (rv < 0)
> +		goto error;
> +
> +	heartbeat = heartbeat_tbl[(rv & 0x07)];
> +	rv = usb_pcwd_set_heartbeat(pwd, heartbeat);
> +	if (rv < 0)
> +		/* retry with default */
> +		rv = usb_pcwd_set_heartbeat(pwd, 0);
> +
> +error:
> +	return rv;
> +}
> +
> +/*
> + * --- watchdog operations ---
> + */
> +
> +static int pcwd_nova_pm_start(struct watchdog_device *wdd)
> +{
> +	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
> +	int r;
> +
> +	r = usb_pcwd_start(pwd);
> +
> +	return r;
> +}
> +
> +static int pcwd_nova_pm_stop(struct watchdog_device *wdd)
> +{
> +	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
> +	int r;
> +
> +	r = usb_pcwd_stop(pwd);
> +
> +	return r;
> +}
> +
> +static int pcwd_nova_pm_ping(struct watchdog_device *wdd)
> +{
> +	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
> +	int r;
> +
> +	r = usb_pcwd_keepalive(pwd);
> +
> +	return r;
> +}	
> +
> +static int pcwd_nova_pm_set_heartbeat(struct watchdog_device *wdd,
> +		unsigned int new_timeout)
> +{
> +	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
> +	int r;
> +
> +	r = usb_pcwd_set_heartbeat(pwd, new_timeout);
> +
> +	return r;
> +}
> +
> +static unsigned int pcwd_nova_pm_get_timeleft(struct watchdog_device *wdd)
> +{
> +	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
> +	int r;
> +
> +	r = usb_pcwd_get_timeleft(pwd);
> +
> +	return r;
> +}
> +/*
> + * --- usb operations ---
> + */
> +
> +static void pwd_intr_callback(struct urb *u)
> +{
> +	struct pcwd_nova_wdt *pwd = u->context;
> +	struct pcwd_nova_command *result = pwd->intr_buffer;
> +	int status = u->status;
> +
> +	switch (status) {
> +	case -ECONNRESET:
> +	case -ENOENT:
> +	case -ESHUTDOWN:
> +		goto death;
> +	case 0:
> +		/* get the payload */
> +		pwd->command_result = be16_to_cpu(result->value);
> +		break;
> +	default:
> +		pwd->command_result = -EIO;
> +		goto resubmit;
> +	}
> +
> +	complete(&pwd->response_available);
> +resubmit:
> +	usb_submit_urb(u, GFP_ATOMIC);
> +	return;
> +death:
> +	pwd->command_result = -EIO;
> +	complete(&pwd->response_available);
> +}
> +
> +static int usb_pcwd_probe(struct usb_interface *interface,
> +		const struct usb_device_id *id)
> +{
> +	struct pcwd_nova_wdt *pwd;
> +	struct usb_endpoint_descriptor *int_in;
> +	int r = -EINVAL;
> +	int rv, size;
> +
> +	rv = usb_find_common_endpoints(interface->cur_altsetting,
> +		       	NULL, NULL, &int_in, NULL);
> +	if (rv < 0)
> +		goto err_abort;
> +
> +	/* keeping allocations together for error handling */
> +	r = -ENOMEM;
> +	pwd = kzalloc(sizeof(struct pcwd_nova_wdt), GFP_KERNEL);
> +	if (!pwd)
> +		goto err_abort;
> +	pwd->interface = interface;
> +	size = max(usb_endpoint_maxp(int_in), MIN_BUF_LENGTH);
> +	pwd->intr_buffer = kmalloc(size, GFP_KERNEL);
> +	if (!pwd->intr_buffer)
> +		goto err_free_pwd;
> +	pwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
> +	if (!pwd->intr_urb)
> +		goto err_free_buffer;
> +	pwd->command_buffer = kmalloc(sizeof(struct pcwd_nova_command), GFP_KERNEL);
> +	if (!pwd->command_buffer)
> +		goto err_free_urb;
> +
> +	mutex_init(&pwd->command_excl);
> +	init_completion(&pwd->response_available);
> +	usb_fill_int_urb(pwd->intr_urb,
> +			interface_to_usbdev(interface),
> +			usb_rcvintpipe(interface_to_usbdev(interface),
> +				int_in->bEndpointAddress),
> +			pwd->intr_buffer,
> +			size,
> +			pwd_intr_callback,
> +			pwd,
> +			int_in->bInterval);
> +
> +	/*
> +	 * we need to start IO now, because we need the device settings
> +	 * that means even an unused device takes up bandwidth
> +	 */
> +	r = usb_submit_urb(pwd->intr_urb, GFP_KERNEL);
> +	if (r < 0)
> +		goto err_free_all_buffers;
> +	r = evaluate_device(pwd);
> +	if (r < 0)
> +		goto err_free_all_buffers;
> +
> +	usb_set_intfdata(interface, pwd);
> +
> +	pwd->wdd.info = &pcwd_nova_info;
> +	watchdog_set_drvdata(&pwd->wdd, pwd);
> +	watchdog_set_nowayout(&pwd->wdd, nowayout);
> +	watchdog_stop_on_reboot(&pwd->wdd);
> +	watchdog_stop_on_unregister(&pwd->wdd);
> +
> +	r = watchdog_register_device(&pwd->wdd);
> +	if (r < 0)
> +		goto err_kill_urb;
> +
> +	return 0;
> +
> +err_kill_urb:
> +	usb_kill_urb(pwd->intr_urb);
> +	usb_set_intfdata(interface, NULL);
> +err_free_all_buffers:
> +	kfree(pwd->command_buffer);
> +err_free_urb:
> +	usb_free_urb(pwd->intr_urb);
> +err_free_buffer:
> +	kfree(pwd->intr_buffer);
> +err_free_pwd:
> +	kfree(pwd);
> +err_abort:
> +	return r;
> +}
> +
> +static void usb_pcwd_disconnect(struct usb_interface *interface)
> +{
> +	struct pcwd_nova_wdt *pwd;
> +
> +	pwd = usb_get_intfdata(interface);
> +	usb_kill_urb(pwd->intr_urb);
> +	complete(&pwd->response_available);
> +	watchdog_unregister_device(&pwd->wdd);
> +	usb_free_urb(pwd->intr_urb);
> +	kfree(pwd->intr_buffer);
> +	kfree(pwd->command_buffer);
> +	kfree(pwd);
> +}
> +
> +static const struct watchdog_info pcwd_nova_info = {
> +	.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
> +		WDIOF_SETTIMEOUT,
> +	.identity = "PC watchdog new driver",
> +};
> +
> +static const struct watchdog_ops pcwd_nova_ops = {
> +        .owner = THIS_MODULE,
> +        .start = pcwd_nova_pm_start,
> +        .stop = pcwd_nova_pm_stop,
> +	.ping = pcwd_nova_pm_ping,
> +	.set_timeout = pcwd_nova_pm_set_heartbeat,
> +	.get_timeleft = pcwd_nova_pm_get_timeleft,
> +};
> +
> +static const struct usb_device_id usb_pcwd_table[] = {
> +        { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) },
> +	{ },				/* Terminating entry */
> +};
> +MODULE_DEVICE_TABLE(usb, usb_pcwd_table);
> +
> +static struct usb_driver usb_pcwd_drivernova = {
> +	"New API PCWD",
> +	.probe =	usb_pcwd_probe,
> +	.disconnect =	usb_pcwd_disconnect,
> +	.id_table =	usb_pcwd_table,
> +};
> +
> +module_usb_driver(usb_pcwd_drivernova);
> +MODULE_LICENSE("GPL");
Guenter Roeck June 15, 2023, 2:45 p.m. UTC | #2
On 6/15/23 07:29, Christophe Leroy wrote:
> Please explain what this driver is.
> 
> Is that a driver for a new type of pcwd_usb ?
> Is that a new driver for an existing equipment ? Why a new driver then ?
> 
> If it is an evolution of the existing driver, please do it in place of
> the existing driver so that we see what is changed and what remains.
> 

Agreed, this should be a patch modifying pcwd_usb.c to use the watchdog core.
Any cleanups should be sent as separate patches.

Thanks,
Guenter
diff mbox series

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f22138709bf5..f7999fb7caaa 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -2210,6 +2210,22 @@  config USBPCWATCHDOG
 
 	  Most people will say N.
 
+config USBPCWATCHDOGNOVA
+        tristate "Berkshire Products USB-PC Watchdog (new driver)"
+        depends on USB
+        help
+          This is the driver for the Berkshire Products USB-PC Watchdog card.
+          This card simply watches your kernel to make sure it doesn't freeze,
+          and if it does, it reboots your computer after a certain amount of
+          time. The card can also monitor the internal temperature of the PC.
+          More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>.
+        
+          To compile this driver as a module, choose M here: the
+          module will be called pcwd_usb_nova.
+          
+          Most people will say N.
+
+
 config KEEMBAY_WATCHDOG
 	tristate "Intel Keem Bay SoC non-secure watchdog"
 	depends on ARCH_KEEMBAY || (ARM64 && COMPILE_TEST)
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index b4c4ccf2d703..8364e0184424 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -33,6 +33,7 @@  obj-$(CONFIG_WDTPCI) += wdt_pci.o
 
 # USB-based Watchdog Cards
 obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
+obj-$(CONFIG_USBPCWATCHDOGNOVA) += pcwd_usb_nova.o
 
 # ALPHA Architecture
 
diff --git a/drivers/watchdog/pcwd_usb_nova.c b/drivers/watchdog/pcwd_usb_nova.c
new file mode 100644
index 000000000000..8a92ba8776ae
--- /dev/null
+++ b/drivers/watchdog/pcwd_usb_nova.c
@@ -0,0 +1,425 @@ 
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+
+#include <linux/watchdog.h>
+#include <linux/hid.h>
+
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#define MIN_BUF_LENGTH 8
+
+/* stuff taken from the old driver */
+
+/* according to documentation max. time to process a command for the USB
+ * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
+#define USB_COMMAND_TIMEOUT	250
+
+/* Watchdog's internal commands */
+#define CMD_READ_TEMP			0x02    /* Read Temperature;
+							Re-trigger Watchdog */
+#define CMD_TRIGGER			CMD_READ_TEMP
+#define CMD_GET_STATUS			0x04	/* Get Status Information */
+#define CMD_GET_FIRMWARE_VERSION	0x08	/* Get Firmware Version */
+#define CMD_GET_DIP_SWITCH_SETTINGS	0x0c	/* Get Dip Switch Settings */
+#define CMD_READ_WATCHDOG_TIMEOUT	0x18	/* Read Current Watchdog Time */
+#define CMD_WRITE_WATCHDOG_TIMEOUT	0x19	/* Write Current WatchdogTime */
+#define CMD_ENABLE_WATCHDOG		0x30	/* Enable / Disable Watchdog */
+#define CMD_DISABLE_WATCHDOG		CMD_ENABLE_WATCHDOG
+
+/* Watchdog's Dip Switch heartbeat values */
+static const int heartbeat_tbl[] = {
+	5,	/* OFF-OFF-OFF	=  5 Sec  */
+	10,	/* OFF-OFF-ON	= 10 Sec  */
+	30,	/* OFF-ON-OFF	= 30 Sec  */
+	60,	/* OFF-ON-ON	=  1 Min  */
+	300,	/* ON-OFF-OFF	=  5 Min  */
+	600,	/* ON-OFF-ON	= 10 Min  */
+	1800,	/* ON-ON-OFF	= 30 Min  */
+	3600,	/* ON-ON-ON	=  1 hour */
+};
+
+/* The vendor and product id's for the USB-PC Watchdog card */
+#define USB_PCWD_VENDOR_ID	0x0c98
+#define USB_PCWD_PRODUCT_ID	0x1140
+
+/*
+ * --- data structures ---
+ */
+
+struct pcwd_nova_command {
+	u8 command;
+	__be16 value;
+	u8 reserved0;
+	u8 reserved1;
+	u8 reserved2;
+}__attribute__ ((__packed__));
+
+struct pcwd_nova_wdt {
+	struct watchdog_device wdd;
+	struct mutex command_excl;
+	struct completion response_available;
+	int command_result;
+	struct usb_interface *interface;
+	int intr_size;
+	struct pcwd_nova_command *intr_buffer;
+	struct urb *intr_urb;
+	struct pcwd_nova_command *command_buffer;
+};
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+		__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static const struct watchdog_info pcwd_nova_info;
+/*
+ * --- helpers ---
+ */
+
+static bool ack_valid(int response)
+{
+	/* the upper byte can be garbage */
+	return (response & 0xff) != 0x00;
+}
+
+static int send_command(struct pcwd_nova_wdt *pwd, u8 command, u16 value)
+{
+	int result = -EIO;
+	int transmitted;
+	long left;
+	struct pcwd_nova_command *cmd = pwd->command_buffer;
+	struct usb_device *dev = interface_to_usbdev(pwd->interface);
+
+	mutex_lock(&pwd->command_excl);
+
+	cmd->command = command;
+	cmd->value = cpu_to_be16(value);
+	cmd->reserved0 = cmd->reserved1 = cmd->reserved2 = 0;
+
+	reinit_completion(&pwd->response_available);
+	transmitted = usb_control_msg(
+			dev,
+			usb_sndctrlpipe(dev, 0),
+			HID_REQ_SET_REPORT,
+			HID_DT_REPORT,
+			0x0200,
+			pwd->interface->cur_altsetting->desc.bInterfaceNumber,
+			cmd,
+			sizeof(struct pcwd_nova_command),
+			USB_CTRL_SET_TIMEOUT);
+	if (transmitted != sizeof(struct pcwd_nova_command)) 
+		goto error;
+
+	left = wait_for_completion_timeout(&pwd->response_available,
+			msecs_to_jiffies(USB_COMMAND_TIMEOUT));
+	if (!left)
+		goto error;
+	result = pwd->command_result;
+error:
+	mutex_unlock(&pwd->command_excl);
+
+	return result;
+}
+
+static int usb_pcwd_stop(struct pcwd_nova_wdt *pwd)
+{
+	int retval;
+
+	retval = send_command(pwd, CMD_DISABLE_WATCHDOG, 0xA5C3);
+	if (retval < 0)
+		goto error;
+
+	if (!ack_valid(retval))
+		retval = -EIO;
+	else
+		retval = 0;
+error:
+	return retval;
+}
+
+static int usb_pcwd_start(struct pcwd_nova_wdt *pwd)
+{
+	int retval;
+
+	retval = send_command(pwd, CMD_ENABLE_WATCHDOG, 0x0000);
+	if (retval < 0)
+		goto error;
+
+	if (!ack_valid(retval))
+		retval = -EIO;
+	else
+		retval = 0;
+
+error:
+	return retval;
+}
+
+static int usb_pcwd_set_heartbeat(struct pcwd_nova_wdt *pwd, u16 hb)
+{
+	int retval;
+
+	retval = send_command(pwd, CMD_WRITE_WATCHDOG_TIMEOUT, hb);
+
+	return retval;
+}
+
+static int usb_pcwd_keepalive(struct pcwd_nova_wdt *pwd)
+{
+	int retval;
+
+	retval = send_command(pwd, CMD_TRIGGER, 0x00);
+
+	return retval;
+}
+
+static int usb_pcwd_get_timeleft(struct pcwd_nova_wdt *pwd)
+{
+	int retval;
+
+	retval = send_command(pwd, CMD_READ_WATCHDOG_TIMEOUT, 0x00);
+
+	return retval < 0 ? -EIO : retval;
+}
+
+static int evaluate_device(struct pcwd_nova_wdt *pwd)
+{
+	int rv;
+	u16 heartbeat;
+
+	rv = usb_pcwd_stop(pwd);
+	if (rv < 0)
+		goto error;
+
+	/* errors intentionally ignored */
+	send_command(pwd, CMD_GET_FIRMWARE_VERSION, 0);
+
+	rv = send_command(pwd, CMD_GET_DIP_SWITCH_SETTINGS, 0);
+	if (rv < 0)
+		goto error;
+
+	heartbeat = heartbeat_tbl[(rv & 0x07)];
+	rv = usb_pcwd_set_heartbeat(pwd, heartbeat);
+	if (rv < 0)
+		/* retry with default */
+		rv = usb_pcwd_set_heartbeat(pwd, 0);
+
+error:
+	return rv;
+}
+
+/*
+ * --- watchdog operations ---
+ */
+
+static int pcwd_nova_pm_start(struct watchdog_device *wdd)
+{
+	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
+	int r;
+
+	r = usb_pcwd_start(pwd);
+
+	return r;
+}
+
+static int pcwd_nova_pm_stop(struct watchdog_device *wdd)
+{
+	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
+	int r;
+
+	r = usb_pcwd_stop(pwd);
+
+	return r;
+}
+
+static int pcwd_nova_pm_ping(struct watchdog_device *wdd)
+{
+	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
+	int r;
+
+	r = usb_pcwd_keepalive(pwd);
+
+	return r;
+}	
+
+static int pcwd_nova_pm_set_heartbeat(struct watchdog_device *wdd,
+		unsigned int new_timeout)
+{
+	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
+	int r;
+
+	r = usb_pcwd_set_heartbeat(pwd, new_timeout);
+
+	return r;
+}
+
+static unsigned int pcwd_nova_pm_get_timeleft(struct watchdog_device *wdd)
+{
+	struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd;
+	int r;
+
+	r = usb_pcwd_get_timeleft(pwd);
+
+	return r;
+}
+/*
+ * --- usb operations ---
+ */
+
+static void pwd_intr_callback(struct urb *u)
+{
+	struct pcwd_nova_wdt *pwd = u->context;
+	struct pcwd_nova_command *result = pwd->intr_buffer;
+	int status = u->status;
+
+	switch (status) {
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		goto death;
+	case 0:
+		/* get the payload */
+		pwd->command_result = be16_to_cpu(result->value);
+		break;
+	default:
+		pwd->command_result = -EIO;
+		goto resubmit;
+	}
+
+	complete(&pwd->response_available);
+resubmit:
+	usb_submit_urb(u, GFP_ATOMIC);
+	return;
+death:
+	pwd->command_result = -EIO;
+	complete(&pwd->response_available);
+}
+
+static int usb_pcwd_probe(struct usb_interface *interface,
+		const struct usb_device_id *id)
+{
+	struct pcwd_nova_wdt *pwd;
+	struct usb_endpoint_descriptor *int_in;
+	int r = -EINVAL;
+	int rv, size;
+
+	rv = usb_find_common_endpoints(interface->cur_altsetting,
+		       	NULL, NULL, &int_in, NULL);
+	if (rv < 0)
+		goto err_abort;
+
+	/* keeping allocations together for error handling */
+	r = -ENOMEM;
+	pwd = kzalloc(sizeof(struct pcwd_nova_wdt), GFP_KERNEL);
+	if (!pwd)
+		goto err_abort;
+	pwd->interface = interface;
+	size = max(usb_endpoint_maxp(int_in), MIN_BUF_LENGTH);
+	pwd->intr_buffer = kmalloc(size, GFP_KERNEL);
+	if (!pwd->intr_buffer)
+		goto err_free_pwd;
+	pwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pwd->intr_urb)
+		goto err_free_buffer;
+	pwd->command_buffer = kmalloc(sizeof(struct pcwd_nova_command), GFP_KERNEL);
+	if (!pwd->command_buffer)
+		goto err_free_urb;
+
+	mutex_init(&pwd->command_excl);
+	init_completion(&pwd->response_available);
+	usb_fill_int_urb(pwd->intr_urb,
+			interface_to_usbdev(interface),
+			usb_rcvintpipe(interface_to_usbdev(interface),
+				int_in->bEndpointAddress),
+			pwd->intr_buffer,
+			size,
+			pwd_intr_callback,
+			pwd,
+			int_in->bInterval);
+
+	/*
+	 * we need to start IO now, because we need the device settings
+	 * that means even an unused device takes up bandwidth
+	 */
+	r = usb_submit_urb(pwd->intr_urb, GFP_KERNEL);
+	if (r < 0)
+		goto err_free_all_buffers;
+	r = evaluate_device(pwd);
+	if (r < 0)
+		goto err_free_all_buffers;
+
+	usb_set_intfdata(interface, pwd);
+
+	pwd->wdd.info = &pcwd_nova_info;
+	watchdog_set_drvdata(&pwd->wdd, pwd);
+	watchdog_set_nowayout(&pwd->wdd, nowayout);
+	watchdog_stop_on_reboot(&pwd->wdd);
+	watchdog_stop_on_unregister(&pwd->wdd);
+
+	r = watchdog_register_device(&pwd->wdd);
+	if (r < 0)
+		goto err_kill_urb;
+
+	return 0;
+
+err_kill_urb:
+	usb_kill_urb(pwd->intr_urb);
+	usb_set_intfdata(interface, NULL);
+err_free_all_buffers:
+	kfree(pwd->command_buffer);
+err_free_urb:
+	usb_free_urb(pwd->intr_urb);
+err_free_buffer:
+	kfree(pwd->intr_buffer);
+err_free_pwd:
+	kfree(pwd);
+err_abort:
+	return r;
+}
+
+static void usb_pcwd_disconnect(struct usb_interface *interface)
+{
+	struct pcwd_nova_wdt *pwd;
+
+	pwd = usb_get_intfdata(interface);
+	usb_kill_urb(pwd->intr_urb);
+	complete(&pwd->response_available);
+	watchdog_unregister_device(&pwd->wdd);
+	usb_free_urb(pwd->intr_urb);
+	kfree(pwd->intr_buffer);
+	kfree(pwd->command_buffer);
+	kfree(pwd);
+}
+
+static const struct watchdog_info pcwd_nova_info = {
+	.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
+		WDIOF_SETTIMEOUT,
+	.identity = "PC watchdog new driver",
+};
+
+static const struct watchdog_ops pcwd_nova_ops = {
+        .owner = THIS_MODULE,
+        .start = pcwd_nova_pm_start,
+        .stop = pcwd_nova_pm_stop,
+	.ping = pcwd_nova_pm_ping,
+	.set_timeout = pcwd_nova_pm_set_heartbeat,
+	.get_timeleft = pcwd_nova_pm_get_timeleft,
+};
+
+static const struct usb_device_id usb_pcwd_table[] = {
+        { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) },
+	{ },				/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, usb_pcwd_table);
+
+static struct usb_driver usb_pcwd_drivernova = {
+	"New API PCWD",
+	.probe =	usb_pcwd_probe,
+	.disconnect =	usb_pcwd_disconnect,
+	.id_table =	usb_pcwd_table,
+};
+
+module_usb_driver(usb_pcwd_drivernova);
+MODULE_LICENSE("GPL");