xref: /openbmc/linux/drivers/watchdog/pcwd_usb.c (revision b7e04f8c61a46d742de23af5d7ca2b41b33e40ac)
1*b7e04f8cSWim Van Sebroeck /*
2*b7e04f8cSWim Van Sebroeck  *	Berkshire USB-PC Watchdog Card Driver
3*b7e04f8cSWim Van Sebroeck  *
4*b7e04f8cSWim Van Sebroeck  *	(c) Copyright 2004-2007 Wim Van Sebroeck <wim@iguana.be>.
5*b7e04f8cSWim Van Sebroeck  *
6*b7e04f8cSWim Van Sebroeck  *	Based on source code of the following authors:
7*b7e04f8cSWim Van Sebroeck  *	  Ken Hollis <kenji@bitgate.com>,
8*b7e04f8cSWim Van Sebroeck  *	  Alan Cox <alan@redhat.com>,
9*b7e04f8cSWim Van Sebroeck  *	  Matt Domsch <Matt_Domsch@dell.com>,
10*b7e04f8cSWim Van Sebroeck  *	  Rob Radez <rob@osinvestor.com>,
11*b7e04f8cSWim Van Sebroeck  *	  Greg Kroah-Hartman <greg@kroah.com>
12*b7e04f8cSWim Van Sebroeck  *
13*b7e04f8cSWim Van Sebroeck  *	This program is free software; you can redistribute it and/or
14*b7e04f8cSWim Van Sebroeck  *	modify it under the terms of the GNU General Public License
15*b7e04f8cSWim Van Sebroeck  *	as published by the Free Software Foundation; either version
16*b7e04f8cSWim Van Sebroeck  *	2 of the License, or (at your option) any later version.
17*b7e04f8cSWim Van Sebroeck  *
18*b7e04f8cSWim Van Sebroeck  *	Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
19*b7e04f8cSWim Van Sebroeck  *	provide warranty for any of this software. This material is
20*b7e04f8cSWim Van Sebroeck  *	provided "AS-IS" and at no charge.
21*b7e04f8cSWim Van Sebroeck  *
22*b7e04f8cSWim Van Sebroeck  *	Thanks also to Simon Machell at Berkshire Products Inc. for
23*b7e04f8cSWim Van Sebroeck  *	providing the test hardware. More info is available at
24*b7e04f8cSWim Van Sebroeck  *	http://www.berkprod.com/ or http://www.pcwatchdog.com/
25*b7e04f8cSWim Van Sebroeck  */
26*b7e04f8cSWim Van Sebroeck 
27*b7e04f8cSWim Van Sebroeck #include <linux/module.h>	/* For module specific items */
28*b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>	/* For new moduleparam's */
29*b7e04f8cSWim Van Sebroeck #include <linux/types.h>	/* For standard types (like size_t) */
30*b7e04f8cSWim Van Sebroeck #include <linux/errno.h>	/* For the -ENODEV/... values */
31*b7e04f8cSWim Van Sebroeck #include <linux/kernel.h>	/* For printk/panic/... */
32*b7e04f8cSWim Van Sebroeck #include <linux/delay.h>	/* For mdelay function */
33*b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h>	/* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */
34*b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>	/* For the watchdog specific items */
35*b7e04f8cSWim Van Sebroeck #include <linux/notifier.h>	/* For notifier support */
36*b7e04f8cSWim Van Sebroeck #include <linux/reboot.h>	/* For reboot_notifier stuff */
37*b7e04f8cSWim Van Sebroeck #include <linux/init.h>		/* For __init/__exit/... */
38*b7e04f8cSWim Van Sebroeck #include <linux/fs.h>		/* For file operations */
39*b7e04f8cSWim Van Sebroeck #include <linux/usb.h>		/* For USB functions */
40*b7e04f8cSWim Van Sebroeck #include <linux/slab.h>		/* For kmalloc, ... */
41*b7e04f8cSWim Van Sebroeck #include <linux/mutex.h>	/* For mutex locking */
42*b7e04f8cSWim Van Sebroeck #include <linux/hid.h>		/* For HID_REQ_SET_REPORT & HID_DT_REPORT */
43*b7e04f8cSWim Van Sebroeck 
44*b7e04f8cSWim Van Sebroeck #include <asm/uaccess.h>	/* For copy_to_user/put_user/... */
45*b7e04f8cSWim Van Sebroeck 
46*b7e04f8cSWim Van Sebroeck 
47*b7e04f8cSWim Van Sebroeck #ifdef CONFIG_USB_DEBUG
48*b7e04f8cSWim Van Sebroeck 	static int debug = 1;
49*b7e04f8cSWim Van Sebroeck #else
50*b7e04f8cSWim Van Sebroeck 	static int debug;
51*b7e04f8cSWim Van Sebroeck #endif
52*b7e04f8cSWim Van Sebroeck 
53*b7e04f8cSWim Van Sebroeck /* Use our own dbg macro */
54*b7e04f8cSWim Van Sebroeck #undef dbg
55*b7e04f8cSWim Van Sebroeck #define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0)
56*b7e04f8cSWim Van Sebroeck 
57*b7e04f8cSWim Van Sebroeck 
58*b7e04f8cSWim Van Sebroeck /* Module and Version Information */
59*b7e04f8cSWim Van Sebroeck #define DRIVER_VERSION "1.02"
60*b7e04f8cSWim Van Sebroeck #define DRIVER_DATE "21 Jan 2007"
61*b7e04f8cSWim Van Sebroeck #define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
62*b7e04f8cSWim Van Sebroeck #define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
63*b7e04f8cSWim Van Sebroeck #define DRIVER_LICENSE "GPL"
64*b7e04f8cSWim Van Sebroeck #define DRIVER_NAME "pcwd_usb"
65*b7e04f8cSWim Van Sebroeck #define PFX DRIVER_NAME ": "
66*b7e04f8cSWim Van Sebroeck 
67*b7e04f8cSWim Van Sebroeck MODULE_AUTHOR(DRIVER_AUTHOR);
68*b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION(DRIVER_DESC);
69*b7e04f8cSWim Van Sebroeck MODULE_LICENSE(DRIVER_LICENSE);
70*b7e04f8cSWim Van Sebroeck MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
71*b7e04f8cSWim Van Sebroeck MODULE_ALIAS_MISCDEV(TEMP_MINOR);
72*b7e04f8cSWim Van Sebroeck 
73*b7e04f8cSWim Van Sebroeck /* Module Parameters */
74*b7e04f8cSWim Van Sebroeck module_param(debug, int, 0);
75*b7e04f8cSWim Van Sebroeck MODULE_PARM_DESC(debug, "Debug enabled or not");
76*b7e04f8cSWim Van Sebroeck 
77*b7e04f8cSWim Van Sebroeck #define WATCHDOG_HEARTBEAT 0	/* default heartbeat = delay-time from dip-switches */
78*b7e04f8cSWim Van Sebroeck static int heartbeat = WATCHDOG_HEARTBEAT;
79*b7e04f8cSWim Van Sebroeck module_param(heartbeat, int, 0);
80*b7e04f8cSWim Van Sebroeck MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536 or 0=delay-time from dip-switches, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
81*b7e04f8cSWim Van Sebroeck 
82*b7e04f8cSWim Van Sebroeck static int nowayout = WATCHDOG_NOWAYOUT;
83*b7e04f8cSWim Van Sebroeck module_param(nowayout, int, 0);
84*b7e04f8cSWim Van Sebroeck MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
85*b7e04f8cSWim Van Sebroeck 
86*b7e04f8cSWim Van Sebroeck /* The vendor and product id's for the USB-PC Watchdog card */
87*b7e04f8cSWim Van Sebroeck #define USB_PCWD_VENDOR_ID	0x0c98
88*b7e04f8cSWim Van Sebroeck #define USB_PCWD_PRODUCT_ID	0x1140
89*b7e04f8cSWim Van Sebroeck 
90*b7e04f8cSWim Van Sebroeck /* table of devices that work with this driver */
91*b7e04f8cSWim Van Sebroeck static struct usb_device_id usb_pcwd_table [] = {
92*b7e04f8cSWim Van Sebroeck 	{ USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) },
93*b7e04f8cSWim Van Sebroeck 	{ }					/* Terminating entry */
94*b7e04f8cSWim Van Sebroeck };
95*b7e04f8cSWim Van Sebroeck MODULE_DEVICE_TABLE (usb, usb_pcwd_table);
96*b7e04f8cSWim Van Sebroeck 
97*b7e04f8cSWim Van Sebroeck /* according to documentation max. time to process a command for the USB
98*b7e04f8cSWim Van Sebroeck  * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
99*b7e04f8cSWim Van Sebroeck #define USB_COMMAND_TIMEOUT	250
100*b7e04f8cSWim Van Sebroeck 
101*b7e04f8cSWim Van Sebroeck /* Watchdog's internal commands */
102*b7e04f8cSWim Van Sebroeck #define CMD_READ_TEMP			0x02	/* Read Temperature; Re-trigger Watchdog */
103*b7e04f8cSWim Van Sebroeck #define CMD_TRIGGER			CMD_READ_TEMP
104*b7e04f8cSWim Van Sebroeck #define CMD_GET_STATUS			0x04	/* Get Status Information */
105*b7e04f8cSWim Van Sebroeck #define CMD_GET_FIRMWARE_VERSION	0x08	/* Get Firmware Version */
106*b7e04f8cSWim Van Sebroeck #define CMD_GET_DIP_SWITCH_SETTINGS	0x0c	/* Get Dip Switch Settings */
107*b7e04f8cSWim Van Sebroeck #define CMD_READ_WATCHDOG_TIMEOUT	0x18	/* Read Current Watchdog Time */
108*b7e04f8cSWim Van Sebroeck #define CMD_WRITE_WATCHDOG_TIMEOUT	0x19	/* Write Current Watchdog Time */
109*b7e04f8cSWim Van Sebroeck #define CMD_ENABLE_WATCHDOG		0x30	/* Enable / Disable Watchdog */
110*b7e04f8cSWim Van Sebroeck #define CMD_DISABLE_WATCHDOG		CMD_ENABLE_WATCHDOG
111*b7e04f8cSWim Van Sebroeck 
112*b7e04f8cSWim Van Sebroeck /* Watchdog's Dip Switch heartbeat values */
113*b7e04f8cSWim Van Sebroeck static const int heartbeat_tbl [] = {
114*b7e04f8cSWim Van Sebroeck 	5,	/* OFF-OFF-OFF	=  5 Sec  */
115*b7e04f8cSWim Van Sebroeck 	10,	/* OFF-OFF-ON	= 10 Sec  */
116*b7e04f8cSWim Van Sebroeck 	30,	/* OFF-ON-OFF	= 30 Sec  */
117*b7e04f8cSWim Van Sebroeck 	60,	/* OFF-ON-ON	=  1 Min  */
118*b7e04f8cSWim Van Sebroeck 	300,	/* ON-OFF-OFF	=  5 Min  */
119*b7e04f8cSWim Van Sebroeck 	600,	/* ON-OFF-ON	= 10 Min  */
120*b7e04f8cSWim Van Sebroeck 	1800,	/* ON-ON-OFF	= 30 Min  */
121*b7e04f8cSWim Van Sebroeck 	3600,	/* ON-ON-ON	=  1 hour */
122*b7e04f8cSWim Van Sebroeck };
123*b7e04f8cSWim Van Sebroeck 
124*b7e04f8cSWim Van Sebroeck /* We can only use 1 card due to the /dev/watchdog restriction */
125*b7e04f8cSWim Van Sebroeck static int cards_found;
126*b7e04f8cSWim Van Sebroeck 
127*b7e04f8cSWim Van Sebroeck /* some internal variables */
128*b7e04f8cSWim Van Sebroeck static unsigned long is_active;
129*b7e04f8cSWim Van Sebroeck static char expect_release;
130*b7e04f8cSWim Van Sebroeck 
131*b7e04f8cSWim Van Sebroeck /* Structure to hold all of our device specific stuff */
132*b7e04f8cSWim Van Sebroeck struct usb_pcwd_private {
133*b7e04f8cSWim Van Sebroeck 	struct usb_device *	udev;			/* save off the usb device pointer */
134*b7e04f8cSWim Van Sebroeck 	struct usb_interface *	interface;		/* the interface for this device */
135*b7e04f8cSWim Van Sebroeck 
136*b7e04f8cSWim Van Sebroeck 	unsigned int		interface_number;	/* the interface number used for cmd's */
137*b7e04f8cSWim Van Sebroeck 
138*b7e04f8cSWim Van Sebroeck 	unsigned char *		intr_buffer;		/* the buffer to intr data */
139*b7e04f8cSWim Van Sebroeck 	dma_addr_t		intr_dma;		/* the dma address for the intr buffer */
140*b7e04f8cSWim Van Sebroeck 	size_t			intr_size;		/* the size of the intr buffer */
141*b7e04f8cSWim Van Sebroeck 	struct urb *		intr_urb;		/* the urb used for the intr pipe */
142*b7e04f8cSWim Van Sebroeck 
143*b7e04f8cSWim Van Sebroeck 	unsigned char		cmd_command;		/* The command that is reported back */
144*b7e04f8cSWim Van Sebroeck 	unsigned char		cmd_data_msb;		/* The data MSB that is reported back */
145*b7e04f8cSWim Van Sebroeck 	unsigned char		cmd_data_lsb;		/* The data LSB that is reported back */
146*b7e04f8cSWim Van Sebroeck 	atomic_t		cmd_received;		/* true if we received a report after a command */
147*b7e04f8cSWim Van Sebroeck 
148*b7e04f8cSWim Van Sebroeck 	int			exists;			/* Wether or not the device exists */
149*b7e04f8cSWim Van Sebroeck 	struct mutex		mtx;			/* locks this structure */
150*b7e04f8cSWim Van Sebroeck };
151*b7e04f8cSWim Van Sebroeck static struct usb_pcwd_private *usb_pcwd_device;
152*b7e04f8cSWim Van Sebroeck 
153*b7e04f8cSWim Van Sebroeck /* prevent races between open() and disconnect() */
154*b7e04f8cSWim Van Sebroeck static DEFINE_MUTEX(disconnect_mutex);
155*b7e04f8cSWim Van Sebroeck 
156*b7e04f8cSWim Van Sebroeck /* local function prototypes */
157*b7e04f8cSWim Van Sebroeck static int usb_pcwd_probe	(struct usb_interface *interface, const struct usb_device_id *id);
158*b7e04f8cSWim Van Sebroeck static void usb_pcwd_disconnect	(struct usb_interface *interface);
159*b7e04f8cSWim Van Sebroeck 
160*b7e04f8cSWim Van Sebroeck /* usb specific object needed to register this driver with the usb subsystem */
161*b7e04f8cSWim Van Sebroeck static struct usb_driver usb_pcwd_driver = {
162*b7e04f8cSWim Van Sebroeck 	.name =		DRIVER_NAME,
163*b7e04f8cSWim Van Sebroeck 	.probe =	usb_pcwd_probe,
164*b7e04f8cSWim Van Sebroeck 	.disconnect =	usb_pcwd_disconnect,
165*b7e04f8cSWim Van Sebroeck 	.id_table =	usb_pcwd_table,
166*b7e04f8cSWim Van Sebroeck };
167*b7e04f8cSWim Van Sebroeck 
168*b7e04f8cSWim Van Sebroeck 
169*b7e04f8cSWim Van Sebroeck static void usb_pcwd_intr_done(struct urb *urb)
170*b7e04f8cSWim Van Sebroeck {
171*b7e04f8cSWim Van Sebroeck 	struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context;
172*b7e04f8cSWim Van Sebroeck 	unsigned char *data = usb_pcwd->intr_buffer;
173*b7e04f8cSWim Van Sebroeck 	int retval;
174*b7e04f8cSWim Van Sebroeck 
175*b7e04f8cSWim Van Sebroeck 	switch (urb->status) {
176*b7e04f8cSWim Van Sebroeck 	case 0:			/* success */
177*b7e04f8cSWim Van Sebroeck 		break;
178*b7e04f8cSWim Van Sebroeck 	case -ECONNRESET:	/* unlink */
179*b7e04f8cSWim Van Sebroeck 	case -ENOENT:
180*b7e04f8cSWim Van Sebroeck 	case -ESHUTDOWN:
181*b7e04f8cSWim Van Sebroeck 		/* this urb is terminated, clean up */
182*b7e04f8cSWim Van Sebroeck 		dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
183*b7e04f8cSWim Van Sebroeck 		return;
184*b7e04f8cSWim Van Sebroeck 	/* -EPIPE:  should clear the halt */
185*b7e04f8cSWim Van Sebroeck 	default:		/* error */
186*b7e04f8cSWim Van Sebroeck 		dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status);
187*b7e04f8cSWim Van Sebroeck 		goto resubmit;
188*b7e04f8cSWim Van Sebroeck 	}
189*b7e04f8cSWim Van Sebroeck 
190*b7e04f8cSWim Van Sebroeck 	dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
191*b7e04f8cSWim Van Sebroeck 		data[0], data[1], data[2]);
192*b7e04f8cSWim Van Sebroeck 
193*b7e04f8cSWim Van Sebroeck 	usb_pcwd->cmd_command  = data[0];
194*b7e04f8cSWim Van Sebroeck 	usb_pcwd->cmd_data_msb = data[1];
195*b7e04f8cSWim Van Sebroeck 	usb_pcwd->cmd_data_lsb = data[2];
196*b7e04f8cSWim Van Sebroeck 
197*b7e04f8cSWim Van Sebroeck 	/* notify anyone waiting that the cmd has finished */
198*b7e04f8cSWim Van Sebroeck 	atomic_set (&usb_pcwd->cmd_received, 1);
199*b7e04f8cSWim Van Sebroeck 
200*b7e04f8cSWim Van Sebroeck resubmit:
201*b7e04f8cSWim Van Sebroeck 	retval = usb_submit_urb (urb, GFP_ATOMIC);
202*b7e04f8cSWim Van Sebroeck 	if (retval)
203*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n",
204*b7e04f8cSWim Van Sebroeck 			retval);
205*b7e04f8cSWim Van Sebroeck }
206*b7e04f8cSWim Van Sebroeck 
207*b7e04f8cSWim Van Sebroeck static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned char cmd,
208*b7e04f8cSWim Van Sebroeck 	unsigned char *msb, unsigned char *lsb)
209*b7e04f8cSWim Van Sebroeck {
210*b7e04f8cSWim Van Sebroeck 	int got_response, count;
211*b7e04f8cSWim Van Sebroeck 	unsigned char buf[6];
212*b7e04f8cSWim Van Sebroeck 
213*b7e04f8cSWim Van Sebroeck 	/* We will not send any commands if the USB PCWD device does not exist */
214*b7e04f8cSWim Van Sebroeck 	if ((!usb_pcwd) || (!usb_pcwd->exists))
215*b7e04f8cSWim Van Sebroeck 		return -1;
216*b7e04f8cSWim Van Sebroeck 
217*b7e04f8cSWim Van Sebroeck 	/* The USB PC Watchdog uses a 6 byte report format. The board currently uses
218*b7e04f8cSWim Van Sebroeck 	 * only 3 of the six bytes of the report. */
219*b7e04f8cSWim Van Sebroeck 	buf[0] = cmd;			/* Byte 0 = CMD */
220*b7e04f8cSWim Van Sebroeck 	buf[1] = *msb;			/* Byte 1 = Data MSB */
221*b7e04f8cSWim Van Sebroeck 	buf[2] = *lsb;			/* Byte 2 = Data LSB */
222*b7e04f8cSWim Van Sebroeck 	buf[3] = buf[4] = buf[5] = 0;	/* All other bytes not used */
223*b7e04f8cSWim Van Sebroeck 
224*b7e04f8cSWim Van Sebroeck 	dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
225*b7e04f8cSWim Van Sebroeck 		buf[0], buf[1], buf[2]);
226*b7e04f8cSWim Van Sebroeck 
227*b7e04f8cSWim Van Sebroeck 	atomic_set (&usb_pcwd->cmd_received, 0);
228*b7e04f8cSWim Van Sebroeck 
229*b7e04f8cSWim Van Sebroeck 	if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0),
230*b7e04f8cSWim Van Sebroeck 			HID_REQ_SET_REPORT, HID_DT_REPORT,
231*b7e04f8cSWim Van Sebroeck 			0x0200, usb_pcwd->interface_number, buf, sizeof(buf),
232*b7e04f8cSWim Van Sebroeck 			USB_COMMAND_TIMEOUT) != sizeof(buf)) {
233*b7e04f8cSWim Van Sebroeck 		dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb);
234*b7e04f8cSWim Van Sebroeck 	}
235*b7e04f8cSWim Van Sebroeck 	/* wait till the usb card processed the command,
236*b7e04f8cSWim Van Sebroeck 	 * with a max. timeout of USB_COMMAND_TIMEOUT */
237*b7e04f8cSWim Van Sebroeck 	got_response = 0;
238*b7e04f8cSWim Van Sebroeck 	for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); count++) {
239*b7e04f8cSWim Van Sebroeck 		mdelay(1);
240*b7e04f8cSWim Van Sebroeck 		if (atomic_read (&usb_pcwd->cmd_received))
241*b7e04f8cSWim Van Sebroeck 			got_response = 1;
242*b7e04f8cSWim Van Sebroeck 	}
243*b7e04f8cSWim Van Sebroeck 
244*b7e04f8cSWim Van Sebroeck 	if ((got_response) && (cmd == usb_pcwd->cmd_command)) {
245*b7e04f8cSWim Van Sebroeck 		/* read back response */
246*b7e04f8cSWim Van Sebroeck 		*msb = usb_pcwd->cmd_data_msb;
247*b7e04f8cSWim Van Sebroeck 		*lsb = usb_pcwd->cmd_data_lsb;
248*b7e04f8cSWim Van Sebroeck 	}
249*b7e04f8cSWim Van Sebroeck 
250*b7e04f8cSWim Van Sebroeck 	return got_response;
251*b7e04f8cSWim Van Sebroeck }
252*b7e04f8cSWim Van Sebroeck 
253*b7e04f8cSWim Van Sebroeck static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd)
254*b7e04f8cSWim Van Sebroeck {
255*b7e04f8cSWim Van Sebroeck 	unsigned char msb = 0x00;
256*b7e04f8cSWim Van Sebroeck 	unsigned char lsb = 0x00;
257*b7e04f8cSWim Van Sebroeck 	int retval;
258*b7e04f8cSWim Van Sebroeck 
259*b7e04f8cSWim Van Sebroeck 	/* Enable Watchdog */
260*b7e04f8cSWim Van Sebroeck 	retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb);
261*b7e04f8cSWim Van Sebroeck 
262*b7e04f8cSWim Van Sebroeck 	if ((retval == 0) || (lsb == 0)) {
263*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n");
264*b7e04f8cSWim Van Sebroeck 		return -1;
265*b7e04f8cSWim Van Sebroeck 	}
266*b7e04f8cSWim Van Sebroeck 
267*b7e04f8cSWim Van Sebroeck 	return 0;
268*b7e04f8cSWim Van Sebroeck }
269*b7e04f8cSWim Van Sebroeck 
270*b7e04f8cSWim Van Sebroeck static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd)
271*b7e04f8cSWim Van Sebroeck {
272*b7e04f8cSWim Van Sebroeck 	unsigned char msb = 0xA5;
273*b7e04f8cSWim Van Sebroeck 	unsigned char lsb = 0xC3;
274*b7e04f8cSWim Van Sebroeck 	int retval;
275*b7e04f8cSWim Van Sebroeck 
276*b7e04f8cSWim Van Sebroeck 	/* Disable Watchdog */
277*b7e04f8cSWim Van Sebroeck 	retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb);
278*b7e04f8cSWim Van Sebroeck 
279*b7e04f8cSWim Van Sebroeck 	if ((retval == 0) || (lsb != 0)) {
280*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n");
281*b7e04f8cSWim Van Sebroeck 		return -1;
282*b7e04f8cSWim Van Sebroeck 	}
283*b7e04f8cSWim Van Sebroeck 
284*b7e04f8cSWim Van Sebroeck 	return 0;
285*b7e04f8cSWim Van Sebroeck }
286*b7e04f8cSWim Van Sebroeck 
287*b7e04f8cSWim Van Sebroeck static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd)
288*b7e04f8cSWim Van Sebroeck {
289*b7e04f8cSWim Van Sebroeck 	unsigned char dummy;
290*b7e04f8cSWim Van Sebroeck 
291*b7e04f8cSWim Van Sebroeck 	/* Re-trigger Watchdog */
292*b7e04f8cSWim Van Sebroeck 	usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy);
293*b7e04f8cSWim Van Sebroeck 
294*b7e04f8cSWim Van Sebroeck 	return 0;
295*b7e04f8cSWim Van Sebroeck }
296*b7e04f8cSWim Van Sebroeck 
297*b7e04f8cSWim Van Sebroeck static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t)
298*b7e04f8cSWim Van Sebroeck {
299*b7e04f8cSWim Van Sebroeck 	unsigned char msb = t / 256;
300*b7e04f8cSWim Van Sebroeck 	unsigned char lsb = t % 256;
301*b7e04f8cSWim Van Sebroeck 
302*b7e04f8cSWim Van Sebroeck 	if ((t < 0x0001) || (t > 0xFFFF))
303*b7e04f8cSWim Van Sebroeck 		return -EINVAL;
304*b7e04f8cSWim Van Sebroeck 
305*b7e04f8cSWim Van Sebroeck 	/* Write new heartbeat to watchdog */
306*b7e04f8cSWim Van Sebroeck 	usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb);
307*b7e04f8cSWim Van Sebroeck 
308*b7e04f8cSWim Van Sebroeck 	heartbeat = t;
309*b7e04f8cSWim Van Sebroeck 	return 0;
310*b7e04f8cSWim Van Sebroeck }
311*b7e04f8cSWim Van Sebroeck 
312*b7e04f8cSWim Van Sebroeck static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature)
313*b7e04f8cSWim Van Sebroeck {
314*b7e04f8cSWim Van Sebroeck 	unsigned char msb, lsb;
315*b7e04f8cSWim Van Sebroeck 
316*b7e04f8cSWim Van Sebroeck 	usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb);
317*b7e04f8cSWim Van Sebroeck 
318*b7e04f8cSWim Van Sebroeck 	/*
319*b7e04f8cSWim Van Sebroeck 	 * Convert celsius to fahrenheit, since this was
320*b7e04f8cSWim Van Sebroeck 	 * the decided 'standard' for this return value.
321*b7e04f8cSWim Van Sebroeck 	 */
322*b7e04f8cSWim Van Sebroeck 	*temperature = (lsb * 9 / 5) + 32;
323*b7e04f8cSWim Van Sebroeck 
324*b7e04f8cSWim Van Sebroeck 	return 0;
325*b7e04f8cSWim Van Sebroeck }
326*b7e04f8cSWim Van Sebroeck 
327*b7e04f8cSWim Van Sebroeck static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd, int *time_left)
328*b7e04f8cSWim Van Sebroeck {
329*b7e04f8cSWim Van Sebroeck 	unsigned char msb, lsb;
330*b7e04f8cSWim Van Sebroeck 
331*b7e04f8cSWim Van Sebroeck 	/* Read the time that's left before rebooting */
332*b7e04f8cSWim Van Sebroeck 	/* Note: if the board is not yet armed then we will read 0xFFFF */
333*b7e04f8cSWim Van Sebroeck 	usb_pcwd_send_command(usb_pcwd, CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb);
334*b7e04f8cSWim Van Sebroeck 
335*b7e04f8cSWim Van Sebroeck 	*time_left = (msb << 8) + lsb;
336*b7e04f8cSWim Van Sebroeck 
337*b7e04f8cSWim Van Sebroeck 	return 0;
338*b7e04f8cSWim Van Sebroeck }
339*b7e04f8cSWim Van Sebroeck 
340*b7e04f8cSWim Van Sebroeck /*
341*b7e04f8cSWim Van Sebroeck  *	/dev/watchdog handling
342*b7e04f8cSWim Van Sebroeck  */
343*b7e04f8cSWim Van Sebroeck 
344*b7e04f8cSWim Van Sebroeck static ssize_t usb_pcwd_write(struct file *file, const char __user *data,
345*b7e04f8cSWim Van Sebroeck 			      size_t len, loff_t *ppos)
346*b7e04f8cSWim Van Sebroeck {
347*b7e04f8cSWim Van Sebroeck 	/* See if we got the magic character 'V' and reload the timer */
348*b7e04f8cSWim Van Sebroeck 	if (len) {
349*b7e04f8cSWim Van Sebroeck 		if (!nowayout) {
350*b7e04f8cSWim Van Sebroeck 			size_t i;
351*b7e04f8cSWim Van Sebroeck 
352*b7e04f8cSWim Van Sebroeck 			/* note: just in case someone wrote the magic character
353*b7e04f8cSWim Van Sebroeck 			 * five months ago... */
354*b7e04f8cSWim Van Sebroeck 			expect_release = 0;
355*b7e04f8cSWim Van Sebroeck 
356*b7e04f8cSWim Van Sebroeck 			/* scan to see whether or not we got the magic character */
357*b7e04f8cSWim Van Sebroeck 			for (i = 0; i != len; i++) {
358*b7e04f8cSWim Van Sebroeck 				char c;
359*b7e04f8cSWim Van Sebroeck 				if(get_user(c, data+i))
360*b7e04f8cSWim Van Sebroeck 					return -EFAULT;
361*b7e04f8cSWim Van Sebroeck 				if (c == 'V')
362*b7e04f8cSWim Van Sebroeck 					expect_release = 42;
363*b7e04f8cSWim Van Sebroeck 			}
364*b7e04f8cSWim Van Sebroeck 		}
365*b7e04f8cSWim Van Sebroeck 
366*b7e04f8cSWim Van Sebroeck 		/* someone wrote to us, we should reload the timer */
367*b7e04f8cSWim Van Sebroeck 		usb_pcwd_keepalive(usb_pcwd_device);
368*b7e04f8cSWim Van Sebroeck 	}
369*b7e04f8cSWim Van Sebroeck 	return len;
370*b7e04f8cSWim Van Sebroeck }
371*b7e04f8cSWim Van Sebroeck 
372*b7e04f8cSWim Van Sebroeck static int usb_pcwd_ioctl(struct inode *inode, struct file *file,
373*b7e04f8cSWim Van Sebroeck 			  unsigned int cmd, unsigned long arg)
374*b7e04f8cSWim Van Sebroeck {
375*b7e04f8cSWim Van Sebroeck 	void __user *argp = (void __user *)arg;
376*b7e04f8cSWim Van Sebroeck 	int __user *p = argp;
377*b7e04f8cSWim Van Sebroeck 	static struct watchdog_info ident = {
378*b7e04f8cSWim Van Sebroeck 		.options =		WDIOF_KEEPALIVEPING |
379*b7e04f8cSWim Van Sebroeck 					WDIOF_SETTIMEOUT |
380*b7e04f8cSWim Van Sebroeck 					WDIOF_MAGICCLOSE,
381*b7e04f8cSWim Van Sebroeck 		.firmware_version =	1,
382*b7e04f8cSWim Van Sebroeck 		.identity =		DRIVER_NAME,
383*b7e04f8cSWim Van Sebroeck 	};
384*b7e04f8cSWim Van Sebroeck 
385*b7e04f8cSWim Van Sebroeck 	switch (cmd) {
386*b7e04f8cSWim Van Sebroeck 		case WDIOC_GETSUPPORT:
387*b7e04f8cSWim Van Sebroeck 			return copy_to_user(argp, &ident,
388*b7e04f8cSWim Van Sebroeck 				sizeof (ident)) ? -EFAULT : 0;
389*b7e04f8cSWim Van Sebroeck 
390*b7e04f8cSWim Van Sebroeck 		case WDIOC_GETSTATUS:
391*b7e04f8cSWim Van Sebroeck 		case WDIOC_GETBOOTSTATUS:
392*b7e04f8cSWim Van Sebroeck 			return put_user(0, p);
393*b7e04f8cSWim Van Sebroeck 
394*b7e04f8cSWim Van Sebroeck 		case WDIOC_GETTEMP:
395*b7e04f8cSWim Van Sebroeck 		{
396*b7e04f8cSWim Van Sebroeck 			int temperature;
397*b7e04f8cSWim Van Sebroeck 
398*b7e04f8cSWim Van Sebroeck 			if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
399*b7e04f8cSWim Van Sebroeck 				return -EFAULT;
400*b7e04f8cSWim Van Sebroeck 
401*b7e04f8cSWim Van Sebroeck 			return put_user(temperature, p);
402*b7e04f8cSWim Van Sebroeck 		}
403*b7e04f8cSWim Van Sebroeck 
404*b7e04f8cSWim Van Sebroeck 		case WDIOC_KEEPALIVE:
405*b7e04f8cSWim Van Sebroeck 			usb_pcwd_keepalive(usb_pcwd_device);
406*b7e04f8cSWim Van Sebroeck 			return 0;
407*b7e04f8cSWim Van Sebroeck 
408*b7e04f8cSWim Van Sebroeck 		case WDIOC_SETOPTIONS:
409*b7e04f8cSWim Van Sebroeck 		{
410*b7e04f8cSWim Van Sebroeck 			int new_options, retval = -EINVAL;
411*b7e04f8cSWim Van Sebroeck 
412*b7e04f8cSWim Van Sebroeck 			if (get_user (new_options, p))
413*b7e04f8cSWim Van Sebroeck 				return -EFAULT;
414*b7e04f8cSWim Van Sebroeck 
415*b7e04f8cSWim Van Sebroeck 			if (new_options & WDIOS_DISABLECARD) {
416*b7e04f8cSWim Van Sebroeck 				usb_pcwd_stop(usb_pcwd_device);
417*b7e04f8cSWim Van Sebroeck 				retval = 0;
418*b7e04f8cSWim Van Sebroeck 			}
419*b7e04f8cSWim Van Sebroeck 
420*b7e04f8cSWim Van Sebroeck 			if (new_options & WDIOS_ENABLECARD) {
421*b7e04f8cSWim Van Sebroeck 				usb_pcwd_start(usb_pcwd_device);
422*b7e04f8cSWim Van Sebroeck 				retval = 0;
423*b7e04f8cSWim Van Sebroeck 			}
424*b7e04f8cSWim Van Sebroeck 
425*b7e04f8cSWim Van Sebroeck 			return retval;
426*b7e04f8cSWim Van Sebroeck 		}
427*b7e04f8cSWim Van Sebroeck 
428*b7e04f8cSWim Van Sebroeck 		case WDIOC_SETTIMEOUT:
429*b7e04f8cSWim Van Sebroeck 		{
430*b7e04f8cSWim Van Sebroeck 			int new_heartbeat;
431*b7e04f8cSWim Van Sebroeck 
432*b7e04f8cSWim Van Sebroeck 			if (get_user(new_heartbeat, p))
433*b7e04f8cSWim Van Sebroeck 				return -EFAULT;
434*b7e04f8cSWim Van Sebroeck 
435*b7e04f8cSWim Van Sebroeck 			if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat))
436*b7e04f8cSWim Van Sebroeck 			    return -EINVAL;
437*b7e04f8cSWim Van Sebroeck 
438*b7e04f8cSWim Van Sebroeck 			usb_pcwd_keepalive(usb_pcwd_device);
439*b7e04f8cSWim Van Sebroeck 			/* Fall */
440*b7e04f8cSWim Van Sebroeck 		}
441*b7e04f8cSWim Van Sebroeck 
442*b7e04f8cSWim Van Sebroeck 		case WDIOC_GETTIMEOUT:
443*b7e04f8cSWim Van Sebroeck 			return put_user(heartbeat, p);
444*b7e04f8cSWim Van Sebroeck 
445*b7e04f8cSWim Van Sebroeck 		case WDIOC_GETTIMELEFT:
446*b7e04f8cSWim Van Sebroeck 		{
447*b7e04f8cSWim Van Sebroeck 			int time_left;
448*b7e04f8cSWim Van Sebroeck 
449*b7e04f8cSWim Van Sebroeck 			if (usb_pcwd_get_timeleft(usb_pcwd_device, &time_left))
450*b7e04f8cSWim Van Sebroeck 				return -EFAULT;
451*b7e04f8cSWim Van Sebroeck 
452*b7e04f8cSWim Van Sebroeck 			return put_user(time_left, p);
453*b7e04f8cSWim Van Sebroeck 		}
454*b7e04f8cSWim Van Sebroeck 
455*b7e04f8cSWim Van Sebroeck 		default:
456*b7e04f8cSWim Van Sebroeck 			return -ENOTTY;
457*b7e04f8cSWim Van Sebroeck 	}
458*b7e04f8cSWim Van Sebroeck }
459*b7e04f8cSWim Van Sebroeck 
460*b7e04f8cSWim Van Sebroeck static int usb_pcwd_open(struct inode *inode, struct file *file)
461*b7e04f8cSWim Van Sebroeck {
462*b7e04f8cSWim Van Sebroeck 	/* /dev/watchdog can only be opened once */
463*b7e04f8cSWim Van Sebroeck 	if (test_and_set_bit(0, &is_active))
464*b7e04f8cSWim Van Sebroeck 		return -EBUSY;
465*b7e04f8cSWim Van Sebroeck 
466*b7e04f8cSWim Van Sebroeck 	/* Activate */
467*b7e04f8cSWim Van Sebroeck 	usb_pcwd_start(usb_pcwd_device);
468*b7e04f8cSWim Van Sebroeck 	usb_pcwd_keepalive(usb_pcwd_device);
469*b7e04f8cSWim Van Sebroeck 	return nonseekable_open(inode, file);
470*b7e04f8cSWim Van Sebroeck }
471*b7e04f8cSWim Van Sebroeck 
472*b7e04f8cSWim Van Sebroeck static int usb_pcwd_release(struct inode *inode, struct file *file)
473*b7e04f8cSWim Van Sebroeck {
474*b7e04f8cSWim Van Sebroeck 	/*
475*b7e04f8cSWim Van Sebroeck 	 *      Shut off the timer.
476*b7e04f8cSWim Van Sebroeck 	 */
477*b7e04f8cSWim Van Sebroeck 	if (expect_release == 42) {
478*b7e04f8cSWim Van Sebroeck 		usb_pcwd_stop(usb_pcwd_device);
479*b7e04f8cSWim Van Sebroeck 	} else {
480*b7e04f8cSWim Van Sebroeck 		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
481*b7e04f8cSWim Van Sebroeck 		usb_pcwd_keepalive(usb_pcwd_device);
482*b7e04f8cSWim Van Sebroeck 	}
483*b7e04f8cSWim Van Sebroeck 	expect_release = 0;
484*b7e04f8cSWim Van Sebroeck 	clear_bit(0, &is_active);
485*b7e04f8cSWim Van Sebroeck 	return 0;
486*b7e04f8cSWim Van Sebroeck }
487*b7e04f8cSWim Van Sebroeck 
488*b7e04f8cSWim Van Sebroeck /*
489*b7e04f8cSWim Van Sebroeck  *	/dev/temperature handling
490*b7e04f8cSWim Van Sebroeck  */
491*b7e04f8cSWim Van Sebroeck 
492*b7e04f8cSWim Van Sebroeck static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data,
493*b7e04f8cSWim Van Sebroeck 				size_t len, loff_t *ppos)
494*b7e04f8cSWim Van Sebroeck {
495*b7e04f8cSWim Van Sebroeck 	int temperature;
496*b7e04f8cSWim Van Sebroeck 
497*b7e04f8cSWim Van Sebroeck 	if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
498*b7e04f8cSWim Van Sebroeck 		return -EFAULT;
499*b7e04f8cSWim Van Sebroeck 
500*b7e04f8cSWim Van Sebroeck 	if (copy_to_user(data, &temperature, 1))
501*b7e04f8cSWim Van Sebroeck 		return -EFAULT;
502*b7e04f8cSWim Van Sebroeck 
503*b7e04f8cSWim Van Sebroeck 	return 1;
504*b7e04f8cSWim Van Sebroeck }
505*b7e04f8cSWim Van Sebroeck 
506*b7e04f8cSWim Van Sebroeck static int usb_pcwd_temperature_open(struct inode *inode, struct file *file)
507*b7e04f8cSWim Van Sebroeck {
508*b7e04f8cSWim Van Sebroeck 	return nonseekable_open(inode, file);
509*b7e04f8cSWim Van Sebroeck }
510*b7e04f8cSWim Van Sebroeck 
511*b7e04f8cSWim Van Sebroeck static int usb_pcwd_temperature_release(struct inode *inode, struct file *file)
512*b7e04f8cSWim Van Sebroeck {
513*b7e04f8cSWim Van Sebroeck 	return 0;
514*b7e04f8cSWim Van Sebroeck }
515*b7e04f8cSWim Van Sebroeck 
516*b7e04f8cSWim Van Sebroeck /*
517*b7e04f8cSWim Van Sebroeck  *	Notify system
518*b7e04f8cSWim Van Sebroeck  */
519*b7e04f8cSWim Van Sebroeck 
520*b7e04f8cSWim Van Sebroeck static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
521*b7e04f8cSWim Van Sebroeck {
522*b7e04f8cSWim Van Sebroeck 	if (code==SYS_DOWN || code==SYS_HALT) {
523*b7e04f8cSWim Van Sebroeck 		/* Turn the WDT off */
524*b7e04f8cSWim Van Sebroeck 		usb_pcwd_stop(usb_pcwd_device);
525*b7e04f8cSWim Van Sebroeck 	}
526*b7e04f8cSWim Van Sebroeck 
527*b7e04f8cSWim Van Sebroeck 	return NOTIFY_DONE;
528*b7e04f8cSWim Van Sebroeck }
529*b7e04f8cSWim Van Sebroeck 
530*b7e04f8cSWim Van Sebroeck /*
531*b7e04f8cSWim Van Sebroeck  *	Kernel Interfaces
532*b7e04f8cSWim Van Sebroeck  */
533*b7e04f8cSWim Van Sebroeck 
534*b7e04f8cSWim Van Sebroeck static const struct file_operations usb_pcwd_fops = {
535*b7e04f8cSWim Van Sebroeck 	.owner =	THIS_MODULE,
536*b7e04f8cSWim Van Sebroeck 	.llseek =	no_llseek,
537*b7e04f8cSWim Van Sebroeck 	.write =	usb_pcwd_write,
538*b7e04f8cSWim Van Sebroeck 	.ioctl =	usb_pcwd_ioctl,
539*b7e04f8cSWim Van Sebroeck 	.open =		usb_pcwd_open,
540*b7e04f8cSWim Van Sebroeck 	.release =	usb_pcwd_release,
541*b7e04f8cSWim Van Sebroeck };
542*b7e04f8cSWim Van Sebroeck 
543*b7e04f8cSWim Van Sebroeck static struct miscdevice usb_pcwd_miscdev = {
544*b7e04f8cSWim Van Sebroeck 	.minor =	WATCHDOG_MINOR,
545*b7e04f8cSWim Van Sebroeck 	.name =		"watchdog",
546*b7e04f8cSWim Van Sebroeck 	.fops =		&usb_pcwd_fops,
547*b7e04f8cSWim Van Sebroeck };
548*b7e04f8cSWim Van Sebroeck 
549*b7e04f8cSWim Van Sebroeck static const struct file_operations usb_pcwd_temperature_fops = {
550*b7e04f8cSWim Van Sebroeck 	.owner =	THIS_MODULE,
551*b7e04f8cSWim Van Sebroeck 	.llseek =	no_llseek,
552*b7e04f8cSWim Van Sebroeck 	.read =		usb_pcwd_temperature_read,
553*b7e04f8cSWim Van Sebroeck 	.open =		usb_pcwd_temperature_open,
554*b7e04f8cSWim Van Sebroeck 	.release =	usb_pcwd_temperature_release,
555*b7e04f8cSWim Van Sebroeck };
556*b7e04f8cSWim Van Sebroeck 
557*b7e04f8cSWim Van Sebroeck static struct miscdevice usb_pcwd_temperature_miscdev = {
558*b7e04f8cSWim Van Sebroeck 	.minor =	TEMP_MINOR,
559*b7e04f8cSWim Van Sebroeck 	.name =		"temperature",
560*b7e04f8cSWim Van Sebroeck 	.fops =		&usb_pcwd_temperature_fops,
561*b7e04f8cSWim Van Sebroeck };
562*b7e04f8cSWim Van Sebroeck 
563*b7e04f8cSWim Van Sebroeck static struct notifier_block usb_pcwd_notifier = {
564*b7e04f8cSWim Van Sebroeck 	.notifier_call =	usb_pcwd_notify_sys,
565*b7e04f8cSWim Van Sebroeck };
566*b7e04f8cSWim Van Sebroeck 
567*b7e04f8cSWim Van Sebroeck /**
568*b7e04f8cSWim Van Sebroeck  *	usb_pcwd_delete
569*b7e04f8cSWim Van Sebroeck  */
570*b7e04f8cSWim Van Sebroeck static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd)
571*b7e04f8cSWim Van Sebroeck {
572*b7e04f8cSWim Van Sebroeck 	usb_free_urb(usb_pcwd->intr_urb);
573*b7e04f8cSWim Van Sebroeck 	if (usb_pcwd->intr_buffer != NULL)
574*b7e04f8cSWim Van Sebroeck 		usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size,
575*b7e04f8cSWim Van Sebroeck 				usb_pcwd->intr_buffer, usb_pcwd->intr_dma);
576*b7e04f8cSWim Van Sebroeck 	kfree (usb_pcwd);
577*b7e04f8cSWim Van Sebroeck }
578*b7e04f8cSWim Van Sebroeck 
579*b7e04f8cSWim Van Sebroeck /**
580*b7e04f8cSWim Van Sebroeck  *	usb_pcwd_probe
581*b7e04f8cSWim Van Sebroeck  *
582*b7e04f8cSWim Van Sebroeck  *	Called by the usb core when a new device is connected that it thinks
583*b7e04f8cSWim Van Sebroeck  *	this driver might be interested in.
584*b7e04f8cSWim Van Sebroeck  */
585*b7e04f8cSWim Van Sebroeck static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_device_id *id)
586*b7e04f8cSWim Van Sebroeck {
587*b7e04f8cSWim Van Sebroeck 	struct usb_device *udev = interface_to_usbdev(interface);
588*b7e04f8cSWim Van Sebroeck 	struct usb_host_interface *iface_desc;
589*b7e04f8cSWim Van Sebroeck 	struct usb_endpoint_descriptor *endpoint;
590*b7e04f8cSWim Van Sebroeck 	struct usb_pcwd_private *usb_pcwd = NULL;
591*b7e04f8cSWim Van Sebroeck 	int pipe, maxp;
592*b7e04f8cSWim Van Sebroeck 	int retval = -ENOMEM;
593*b7e04f8cSWim Van Sebroeck 	int got_fw_rev;
594*b7e04f8cSWim Van Sebroeck 	unsigned char fw_rev_major, fw_rev_minor;
595*b7e04f8cSWim Van Sebroeck 	char fw_ver_str[20];
596*b7e04f8cSWim Van Sebroeck 	unsigned char option_switches, dummy;
597*b7e04f8cSWim Van Sebroeck 
598*b7e04f8cSWim Van Sebroeck 	cards_found++;
599*b7e04f8cSWim Van Sebroeck 	if (cards_found > 1) {
600*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "This driver only supports 1 device\n");
601*b7e04f8cSWim Van Sebroeck 		return -ENODEV;
602*b7e04f8cSWim Van Sebroeck 	}
603*b7e04f8cSWim Van Sebroeck 
604*b7e04f8cSWim Van Sebroeck 	/* get the active interface descriptor */
605*b7e04f8cSWim Van Sebroeck 	iface_desc = interface->cur_altsetting;
606*b7e04f8cSWim Van Sebroeck 
607*b7e04f8cSWim Van Sebroeck 	/* check out that we have a HID device */
608*b7e04f8cSWim Van Sebroeck 	if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) {
609*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "The device isn't a Human Interface Device\n");
610*b7e04f8cSWim Van Sebroeck 		return -ENODEV;
611*b7e04f8cSWim Van Sebroeck 	}
612*b7e04f8cSWim Van Sebroeck 
613*b7e04f8cSWim Van Sebroeck 	/* check out the endpoint: it has to be Interrupt & IN */
614*b7e04f8cSWim Van Sebroeck 	endpoint = &iface_desc->endpoint[0].desc;
615*b7e04f8cSWim Van Sebroeck 
616*b7e04f8cSWim Van Sebroeck 	if (!((endpoint->bEndpointAddress & USB_DIR_IN) &&
617*b7e04f8cSWim Van Sebroeck 	     ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
618*b7e04f8cSWim Van Sebroeck 				== USB_ENDPOINT_XFER_INT))) {
619*b7e04f8cSWim Van Sebroeck 		/* we didn't find a Interrupt endpoint with direction IN */
620*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n");
621*b7e04f8cSWim Van Sebroeck 		return -ENODEV;
622*b7e04f8cSWim Van Sebroeck 	}
623*b7e04f8cSWim Van Sebroeck 
624*b7e04f8cSWim Van Sebroeck 	/* get a handle to the interrupt data pipe */
625*b7e04f8cSWim Van Sebroeck 	pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
626*b7e04f8cSWim Van Sebroeck 	maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));
627*b7e04f8cSWim Van Sebroeck 
628*b7e04f8cSWim Van Sebroeck 	/* allocate memory for our device and initialize it */
629*b7e04f8cSWim Van Sebroeck 	usb_pcwd = kzalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL);
630*b7e04f8cSWim Van Sebroeck 	if (usb_pcwd == NULL) {
631*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Out of memory\n");
632*b7e04f8cSWim Van Sebroeck 		goto error;
633*b7e04f8cSWim Van Sebroeck 	}
634*b7e04f8cSWim Van Sebroeck 
635*b7e04f8cSWim Van Sebroeck 	usb_pcwd_device = usb_pcwd;
636*b7e04f8cSWim Van Sebroeck 
637*b7e04f8cSWim Van Sebroeck 	mutex_init(&usb_pcwd->mtx);
638*b7e04f8cSWim Van Sebroeck 	usb_pcwd->udev = udev;
639*b7e04f8cSWim Van Sebroeck 	usb_pcwd->interface = interface;
640*b7e04f8cSWim Van Sebroeck 	usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber;
641*b7e04f8cSWim Van Sebroeck 	usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? le16_to_cpu(endpoint->wMaxPacketSize) : 8);
642*b7e04f8cSWim Van Sebroeck 
643*b7e04f8cSWim Van Sebroeck 	/* set up the memory buffer's */
644*b7e04f8cSWim Van Sebroeck 	if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, GFP_ATOMIC, &usb_pcwd->intr_dma))) {
645*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Out of memory\n");
646*b7e04f8cSWim Van Sebroeck 		goto error;
647*b7e04f8cSWim Van Sebroeck 	}
648*b7e04f8cSWim Van Sebroeck 
649*b7e04f8cSWim Van Sebroeck 	/* allocate the urb's */
650*b7e04f8cSWim Van Sebroeck 	usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
651*b7e04f8cSWim Van Sebroeck 	if (!usb_pcwd->intr_urb) {
652*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Out of memory\n");
653*b7e04f8cSWim Van Sebroeck 		goto error;
654*b7e04f8cSWim Van Sebroeck 	}
655*b7e04f8cSWim Van Sebroeck 
656*b7e04f8cSWim Van Sebroeck 	/* initialise the intr urb's */
657*b7e04f8cSWim Van Sebroeck 	usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe,
658*b7e04f8cSWim Van Sebroeck 			usb_pcwd->intr_buffer, usb_pcwd->intr_size,
659*b7e04f8cSWim Van Sebroeck 			usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval);
660*b7e04f8cSWim Van Sebroeck 	usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma;
661*b7e04f8cSWim Van Sebroeck 	usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
662*b7e04f8cSWim Van Sebroeck 
663*b7e04f8cSWim Van Sebroeck 	/* register our interrupt URB with the USB system */
664*b7e04f8cSWim Van Sebroeck 	if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) {
665*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "Problem registering interrupt URB\n");
666*b7e04f8cSWim Van Sebroeck 		retval = -EIO; /* failure */
667*b7e04f8cSWim Van Sebroeck 		goto error;
668*b7e04f8cSWim Van Sebroeck 	}
669*b7e04f8cSWim Van Sebroeck 
670*b7e04f8cSWim Van Sebroeck 	/* The device exists and can be communicated with */
671*b7e04f8cSWim Van Sebroeck 	usb_pcwd->exists = 1;
672*b7e04f8cSWim Van Sebroeck 
673*b7e04f8cSWim Van Sebroeck 	/* disable card */
674*b7e04f8cSWim Van Sebroeck 	usb_pcwd_stop(usb_pcwd);
675*b7e04f8cSWim Van Sebroeck 
676*b7e04f8cSWim Van Sebroeck 	/* Get the Firmware Version */
677*b7e04f8cSWim Van Sebroeck 	got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor);
678*b7e04f8cSWim Van Sebroeck 	if (got_fw_rev) {
679*b7e04f8cSWim Van Sebroeck 		sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
680*b7e04f8cSWim Van Sebroeck 	} else {
681*b7e04f8cSWim Van Sebroeck 		sprintf(fw_ver_str, "<card no answer>");
682*b7e04f8cSWim Van Sebroeck 	}
683*b7e04f8cSWim Van Sebroeck 
684*b7e04f8cSWim Van Sebroeck 	printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n",
685*b7e04f8cSWim Van Sebroeck 		fw_ver_str);
686*b7e04f8cSWim Van Sebroeck 
687*b7e04f8cSWim Van Sebroeck 	/* Get switch settings */
688*b7e04f8cSWim Van Sebroeck 	usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, &option_switches);
689*b7e04f8cSWim Van Sebroeck 
690*b7e04f8cSWim Van Sebroeck 	printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
691*b7e04f8cSWim Van Sebroeck 		option_switches,
692*b7e04f8cSWim Van Sebroeck 		((option_switches & 0x10) ? "ON" : "OFF"),
693*b7e04f8cSWim Van Sebroeck 		((option_switches & 0x08) ? "ON" : "OFF"));
694*b7e04f8cSWim Van Sebroeck 
695*b7e04f8cSWim Van Sebroeck 	/* If heartbeat = 0 then we use the heartbeat from the dip-switches */
696*b7e04f8cSWim Van Sebroeck 	if (heartbeat == 0)
697*b7e04f8cSWim Van Sebroeck 		heartbeat = heartbeat_tbl[(option_switches & 0x07)];
698*b7e04f8cSWim Van Sebroeck 
699*b7e04f8cSWim Van Sebroeck 	/* Check that the heartbeat value is within it's range ; if not reset to the default */
700*b7e04f8cSWim Van Sebroeck 	if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) {
701*b7e04f8cSWim Van Sebroeck 		usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT);
702*b7e04f8cSWim Van Sebroeck 		printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
703*b7e04f8cSWim Van Sebroeck 			WATCHDOG_HEARTBEAT);
704*b7e04f8cSWim Van Sebroeck 	}
705*b7e04f8cSWim Van Sebroeck 
706*b7e04f8cSWim Van Sebroeck 	retval = register_reboot_notifier(&usb_pcwd_notifier);
707*b7e04f8cSWim Van Sebroeck 	if (retval != 0) {
708*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
709*b7e04f8cSWim Van Sebroeck 			retval);
710*b7e04f8cSWim Van Sebroeck 		goto error;
711*b7e04f8cSWim Van Sebroeck 	}
712*b7e04f8cSWim Van Sebroeck 
713*b7e04f8cSWim Van Sebroeck 	retval = misc_register(&usb_pcwd_temperature_miscdev);
714*b7e04f8cSWim Van Sebroeck 	if (retval != 0) {
715*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
716*b7e04f8cSWim Van Sebroeck 			TEMP_MINOR, retval);
717*b7e04f8cSWim Van Sebroeck 		goto err_out_unregister_reboot;
718*b7e04f8cSWim Van Sebroeck 	}
719*b7e04f8cSWim Van Sebroeck 
720*b7e04f8cSWim Van Sebroeck 	retval = misc_register(&usb_pcwd_miscdev);
721*b7e04f8cSWim Van Sebroeck 	if (retval != 0) {
722*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
723*b7e04f8cSWim Van Sebroeck 			WATCHDOG_MINOR, retval);
724*b7e04f8cSWim Van Sebroeck 		goto err_out_misc_deregister;
725*b7e04f8cSWim Van Sebroeck 	}
726*b7e04f8cSWim Van Sebroeck 
727*b7e04f8cSWim Van Sebroeck 	/* we can register the device now, as it is ready */
728*b7e04f8cSWim Van Sebroeck 	usb_set_intfdata (interface, usb_pcwd);
729*b7e04f8cSWim Van Sebroeck 
730*b7e04f8cSWim Van Sebroeck 	printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
731*b7e04f8cSWim Van Sebroeck 		heartbeat, nowayout);
732*b7e04f8cSWim Van Sebroeck 
733*b7e04f8cSWim Van Sebroeck 	return 0;
734*b7e04f8cSWim Van Sebroeck 
735*b7e04f8cSWim Van Sebroeck err_out_misc_deregister:
736*b7e04f8cSWim Van Sebroeck 	misc_deregister(&usb_pcwd_temperature_miscdev);
737*b7e04f8cSWim Van Sebroeck err_out_unregister_reboot:
738*b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&usb_pcwd_notifier);
739*b7e04f8cSWim Van Sebroeck error:
740*b7e04f8cSWim Van Sebroeck 	if (usb_pcwd)
741*b7e04f8cSWim Van Sebroeck 		usb_pcwd_delete(usb_pcwd);
742*b7e04f8cSWim Van Sebroeck 	usb_pcwd_device = NULL;
743*b7e04f8cSWim Van Sebroeck 	return retval;
744*b7e04f8cSWim Van Sebroeck }
745*b7e04f8cSWim Van Sebroeck 
746*b7e04f8cSWim Van Sebroeck 
747*b7e04f8cSWim Van Sebroeck /**
748*b7e04f8cSWim Van Sebroeck  *	usb_pcwd_disconnect
749*b7e04f8cSWim Van Sebroeck  *
750*b7e04f8cSWim Van Sebroeck  *	Called by the usb core when the device is removed from the system.
751*b7e04f8cSWim Van Sebroeck  *
752*b7e04f8cSWim Van Sebroeck  *	This routine guarantees that the driver will not submit any more urbs
753*b7e04f8cSWim Van Sebroeck  *	by clearing dev->udev.
754*b7e04f8cSWim Van Sebroeck  */
755*b7e04f8cSWim Van Sebroeck static void usb_pcwd_disconnect(struct usb_interface *interface)
756*b7e04f8cSWim Van Sebroeck {
757*b7e04f8cSWim Van Sebroeck 	struct usb_pcwd_private *usb_pcwd;
758*b7e04f8cSWim Van Sebroeck 
759*b7e04f8cSWim Van Sebroeck 	/* prevent races with open() */
760*b7e04f8cSWim Van Sebroeck 	mutex_lock(&disconnect_mutex);
761*b7e04f8cSWim Van Sebroeck 
762*b7e04f8cSWim Van Sebroeck 	usb_pcwd = usb_get_intfdata (interface);
763*b7e04f8cSWim Van Sebroeck 	usb_set_intfdata (interface, NULL);
764*b7e04f8cSWim Van Sebroeck 
765*b7e04f8cSWim Van Sebroeck 	mutex_lock(&usb_pcwd->mtx);
766*b7e04f8cSWim Van Sebroeck 
767*b7e04f8cSWim Van Sebroeck 	/* Stop the timer before we leave */
768*b7e04f8cSWim Van Sebroeck 	if (!nowayout)
769*b7e04f8cSWim Van Sebroeck 		usb_pcwd_stop(usb_pcwd);
770*b7e04f8cSWim Van Sebroeck 
771*b7e04f8cSWim Van Sebroeck 	/* We should now stop communicating with the USB PCWD device */
772*b7e04f8cSWim Van Sebroeck 	usb_pcwd->exists = 0;
773*b7e04f8cSWim Van Sebroeck 
774*b7e04f8cSWim Van Sebroeck 	/* Deregister */
775*b7e04f8cSWim Van Sebroeck 	misc_deregister(&usb_pcwd_miscdev);
776*b7e04f8cSWim Van Sebroeck 	misc_deregister(&usb_pcwd_temperature_miscdev);
777*b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&usb_pcwd_notifier);
778*b7e04f8cSWim Van Sebroeck 
779*b7e04f8cSWim Van Sebroeck 	mutex_unlock(&usb_pcwd->mtx);
780*b7e04f8cSWim Van Sebroeck 
781*b7e04f8cSWim Van Sebroeck 	/* Delete the USB PCWD device */
782*b7e04f8cSWim Van Sebroeck 	usb_pcwd_delete(usb_pcwd);
783*b7e04f8cSWim Van Sebroeck 
784*b7e04f8cSWim Van Sebroeck 	cards_found--;
785*b7e04f8cSWim Van Sebroeck 
786*b7e04f8cSWim Van Sebroeck 	mutex_unlock(&disconnect_mutex);
787*b7e04f8cSWim Van Sebroeck 
788*b7e04f8cSWim Van Sebroeck 	printk(KERN_INFO PFX "USB PC Watchdog disconnected\n");
789*b7e04f8cSWim Van Sebroeck }
790*b7e04f8cSWim Van Sebroeck 
791*b7e04f8cSWim Van Sebroeck 
792*b7e04f8cSWim Van Sebroeck 
793*b7e04f8cSWim Van Sebroeck /**
794*b7e04f8cSWim Van Sebroeck  *	usb_pcwd_init
795*b7e04f8cSWim Van Sebroeck  */
796*b7e04f8cSWim Van Sebroeck static int __init usb_pcwd_init(void)
797*b7e04f8cSWim Van Sebroeck {
798*b7e04f8cSWim Van Sebroeck 	int result;
799*b7e04f8cSWim Van Sebroeck 
800*b7e04f8cSWim Van Sebroeck 	/* register this driver with the USB subsystem */
801*b7e04f8cSWim Van Sebroeck 	result = usb_register(&usb_pcwd_driver);
802*b7e04f8cSWim Van Sebroeck 	if (result) {
803*b7e04f8cSWim Van Sebroeck 		printk(KERN_ERR PFX "usb_register failed. Error number %d\n",
804*b7e04f8cSWim Van Sebroeck 		    result);
805*b7e04f8cSWim Van Sebroeck 		return result;
806*b7e04f8cSWim Van Sebroeck 	}
807*b7e04f8cSWim Van Sebroeck 
808*b7e04f8cSWim Van Sebroeck 	printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n");
809*b7e04f8cSWim Van Sebroeck 	return 0;
810*b7e04f8cSWim Van Sebroeck }
811*b7e04f8cSWim Van Sebroeck 
812*b7e04f8cSWim Van Sebroeck 
813*b7e04f8cSWim Van Sebroeck /**
814*b7e04f8cSWim Van Sebroeck  *	usb_pcwd_exit
815*b7e04f8cSWim Van Sebroeck  */
816*b7e04f8cSWim Van Sebroeck static void __exit usb_pcwd_exit(void)
817*b7e04f8cSWim Van Sebroeck {
818*b7e04f8cSWim Van Sebroeck 	/* deregister this driver with the USB subsystem */
819*b7e04f8cSWim Van Sebroeck 	usb_deregister(&usb_pcwd_driver);
820*b7e04f8cSWim Van Sebroeck }
821*b7e04f8cSWim Van Sebroeck 
822*b7e04f8cSWim Van Sebroeck 
823*b7e04f8cSWim Van Sebroeck module_init (usb_pcwd_init);
824*b7e04f8cSWim Van Sebroeck module_exit (usb_pcwd_exit);
825