xref: /openbmc/linux/drivers/char/nwbutton.c (revision 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2)
1*1da177e4SLinus Torvalds /*
2*1da177e4SLinus Torvalds  * 	NetWinder Button Driver-
3*1da177e4SLinus Torvalds  *	Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
4*1da177e4SLinus Torvalds  *
5*1da177e4SLinus Torvalds  */
6*1da177e4SLinus Torvalds 
7*1da177e4SLinus Torvalds #include <linux/config.h>
8*1da177e4SLinus Torvalds #include <linux/module.h>
9*1da177e4SLinus Torvalds #include <linux/kernel.h>
10*1da177e4SLinus Torvalds #include <linux/sched.h>
11*1da177e4SLinus Torvalds #include <linux/interrupt.h>
12*1da177e4SLinus Torvalds #include <linux/time.h>
13*1da177e4SLinus Torvalds #include <linux/timer.h>
14*1da177e4SLinus Torvalds #include <linux/fs.h>
15*1da177e4SLinus Torvalds #include <linux/miscdevice.h>
16*1da177e4SLinus Torvalds #include <linux/string.h>
17*1da177e4SLinus Torvalds #include <linux/errno.h>
18*1da177e4SLinus Torvalds #include <linux/init.h>
19*1da177e4SLinus Torvalds 
20*1da177e4SLinus Torvalds #include <asm/uaccess.h>
21*1da177e4SLinus Torvalds #include <asm/irq.h>
22*1da177e4SLinus Torvalds #include <asm/mach-types.h>
23*1da177e4SLinus Torvalds 
24*1da177e4SLinus Torvalds #define __NWBUTTON_C		/* Tell the header file who we are */
25*1da177e4SLinus Torvalds #include "nwbutton.h"
26*1da177e4SLinus Torvalds 
27*1da177e4SLinus Torvalds static int button_press_count;		/* The count of button presses */
28*1da177e4SLinus Torvalds static struct timer_list button_timer;	/* Times for the end of a sequence */
29*1da177e4SLinus Torvalds static DECLARE_WAIT_QUEUE_HEAD(button_wait_queue); /* Used for blocking read */
30*1da177e4SLinus Torvalds static char button_output_buffer[32];	/* Stores data to write out of device */
31*1da177e4SLinus Torvalds static int bcount;			/* The number of bytes in the buffer */
32*1da177e4SLinus Torvalds static int bdelay = BUTTON_DELAY;	/* The delay, in jiffies */
33*1da177e4SLinus Torvalds static struct button_callback button_callback_list[32]; /* The callback list */
34*1da177e4SLinus Torvalds static int callback_count;		/* The number of callbacks registered */
35*1da177e4SLinus Torvalds static int reboot_count = NUM_PRESSES_REBOOT; /* Number of presses to reboot */
36*1da177e4SLinus Torvalds 
37*1da177e4SLinus Torvalds /*
38*1da177e4SLinus Torvalds  * This function is called by other drivers to register a callback function
39*1da177e4SLinus Torvalds  * to be called when a particular number of button presses occurs.
40*1da177e4SLinus Torvalds  * The callback list is a static array of 32 entries (I somehow doubt many
41*1da177e4SLinus Torvalds  * people are ever going to want to register more than 32 different actions
42*1da177e4SLinus Torvalds  * to be performed by the kernel on different numbers of button presses ;).
43*1da177e4SLinus Torvalds  * However, if an attempt to register a 33rd entry (perhaps a stuck loop
44*1da177e4SLinus Torvalds  * somewhere registering the same entry over and over?) it will fail to
45*1da177e4SLinus Torvalds  * do so and return -ENOMEM. If an attempt is made to register a null pointer,
46*1da177e4SLinus Torvalds  * it will fail to do so and return -EINVAL.
47*1da177e4SLinus Torvalds  * Because callbacks can be unregistered at random the list can become
48*1da177e4SLinus Torvalds  * fragmented, so we need to search through the list until we find the first
49*1da177e4SLinus Torvalds  * free entry.
50*1da177e4SLinus Torvalds  *
51*1da177e4SLinus Torvalds  * FIXME: Has anyone spotted any locking functions int his code recently ??
52*1da177e4SLinus Torvalds  */
53*1da177e4SLinus Torvalds 
54*1da177e4SLinus Torvalds int button_add_callback (void (*callback) (void), int count)
55*1da177e4SLinus Torvalds {
56*1da177e4SLinus Torvalds 	int lp = 0;
57*1da177e4SLinus Torvalds 	if (callback_count == 32) {
58*1da177e4SLinus Torvalds 		return -ENOMEM;
59*1da177e4SLinus Torvalds 	}
60*1da177e4SLinus Torvalds 	if (!callback) {
61*1da177e4SLinus Torvalds 		return -EINVAL;
62*1da177e4SLinus Torvalds 	}
63*1da177e4SLinus Torvalds 	callback_count++;
64*1da177e4SLinus Torvalds 	for (; (button_callback_list [lp].callback); lp++);
65*1da177e4SLinus Torvalds 	button_callback_list [lp].callback = callback;
66*1da177e4SLinus Torvalds 	button_callback_list [lp].count = count;
67*1da177e4SLinus Torvalds 	return 0;
68*1da177e4SLinus Torvalds }
69*1da177e4SLinus Torvalds 
70*1da177e4SLinus Torvalds /*
71*1da177e4SLinus Torvalds  * This function is called by other drivers to deregister a callback function.
72*1da177e4SLinus Torvalds  * If you attempt to unregister a callback which does not exist, it will fail
73*1da177e4SLinus Torvalds  * with -EINVAL. If there is more than one entry with the same address,
74*1da177e4SLinus Torvalds  * because it searches the list from end to beginning, it will unregister the
75*1da177e4SLinus Torvalds  * last one to be registered first (FILO- First In Last Out).
76*1da177e4SLinus Torvalds  * Note that this is not neccessarily true if the entries are not submitted
77*1da177e4SLinus Torvalds  * at the same time, because another driver could have unregistered a callback
78*1da177e4SLinus Torvalds  * between the submissions creating a gap earlier in the list, which would
79*1da177e4SLinus Torvalds  * be filled first at submission time.
80*1da177e4SLinus Torvalds  */
81*1da177e4SLinus Torvalds 
82*1da177e4SLinus Torvalds int button_del_callback (void (*callback) (void))
83*1da177e4SLinus Torvalds {
84*1da177e4SLinus Torvalds 	int lp = 31;
85*1da177e4SLinus Torvalds 	if (!callback) {
86*1da177e4SLinus Torvalds 		return -EINVAL;
87*1da177e4SLinus Torvalds 	}
88*1da177e4SLinus Torvalds 	while (lp >= 0) {
89*1da177e4SLinus Torvalds 		if ((button_callback_list [lp].callback) == callback) {
90*1da177e4SLinus Torvalds 			button_callback_list [lp].callback = NULL;
91*1da177e4SLinus Torvalds 			button_callback_list [lp].count = 0;
92*1da177e4SLinus Torvalds 			callback_count--;
93*1da177e4SLinus Torvalds 			return 0;
94*1da177e4SLinus Torvalds 		};
95*1da177e4SLinus Torvalds 		lp--;
96*1da177e4SLinus Torvalds 	};
97*1da177e4SLinus Torvalds 	return -EINVAL;
98*1da177e4SLinus Torvalds }
99*1da177e4SLinus Torvalds 
100*1da177e4SLinus Torvalds /*
101*1da177e4SLinus Torvalds  * This function is called by button_sequence_finished to search through the
102*1da177e4SLinus Torvalds  * list of callback functions, and call any of them whose count argument
103*1da177e4SLinus Torvalds  * matches the current count of button presses. It starts at the beginning
104*1da177e4SLinus Torvalds  * of the list and works up to the end. It will refuse to follow a null
105*1da177e4SLinus Torvalds  * pointer (which should never happen anyway).
106*1da177e4SLinus Torvalds  */
107*1da177e4SLinus Torvalds 
108*1da177e4SLinus Torvalds static void button_consume_callbacks (int bpcount)
109*1da177e4SLinus Torvalds {
110*1da177e4SLinus Torvalds 	int lp = 0;
111*1da177e4SLinus Torvalds 	for (; lp <= 31; lp++) {
112*1da177e4SLinus Torvalds 		if ((button_callback_list [lp].count) == bpcount) {
113*1da177e4SLinus Torvalds 			if (button_callback_list [lp].callback) {
114*1da177e4SLinus Torvalds 				button_callback_list[lp].callback();
115*1da177e4SLinus Torvalds 			}
116*1da177e4SLinus Torvalds 		}
117*1da177e4SLinus Torvalds 	}
118*1da177e4SLinus Torvalds }
119*1da177e4SLinus Torvalds 
120*1da177e4SLinus Torvalds /*
121*1da177e4SLinus Torvalds  * This function is called when the button_timer times out.
122*1da177e4SLinus Torvalds  * ie. When you don't press the button for bdelay jiffies, this is taken to
123*1da177e4SLinus Torvalds  * mean you have ended the sequence of key presses, and this function is
124*1da177e4SLinus Torvalds  * called to wind things up (write the press_count out to /dev/button, call
125*1da177e4SLinus Torvalds  * any matching registered function callbacks, initiate reboot, etc.).
126*1da177e4SLinus Torvalds  */
127*1da177e4SLinus Torvalds 
128*1da177e4SLinus Torvalds static void button_sequence_finished (unsigned long parameters)
129*1da177e4SLinus Torvalds {
130*1da177e4SLinus Torvalds #ifdef CONFIG_NWBUTTON_REBOOT		/* Reboot using button is enabled */
131*1da177e4SLinus Torvalds 	if (button_press_count == reboot_count) {
132*1da177e4SLinus Torvalds 		kill_proc (1, SIGINT, 1);	/* Ask init to reboot us */
133*1da177e4SLinus Torvalds 	}
134*1da177e4SLinus Torvalds #endif /* CONFIG_NWBUTTON_REBOOT */
135*1da177e4SLinus Torvalds 	button_consume_callbacks (button_press_count);
136*1da177e4SLinus Torvalds 	bcount = sprintf (button_output_buffer, "%d\n", button_press_count);
137*1da177e4SLinus Torvalds 	button_press_count = 0;		/* Reset the button press counter */
138*1da177e4SLinus Torvalds 	wake_up_interruptible (&button_wait_queue);
139*1da177e4SLinus Torvalds }
140*1da177e4SLinus Torvalds 
141*1da177e4SLinus Torvalds /*
142*1da177e4SLinus Torvalds  *  This handler is called when the orange button is pressed (GPIO 10 of the
143*1da177e4SLinus Torvalds  *  SuperIO chip, which maps to logical IRQ 26). If the press_count is 0,
144*1da177e4SLinus Torvalds  *  this is the first press, so it starts a timer and increments the counter.
145*1da177e4SLinus Torvalds  *  If it is higher than 0, it deletes the old timer, starts a new one, and
146*1da177e4SLinus Torvalds  *  increments the counter.
147*1da177e4SLinus Torvalds  */
148*1da177e4SLinus Torvalds 
149*1da177e4SLinus Torvalds static irqreturn_t button_handler (int irq, void *dev_id, struct pt_regs *regs)
150*1da177e4SLinus Torvalds {
151*1da177e4SLinus Torvalds 	if (button_press_count) {
152*1da177e4SLinus Torvalds 		del_timer (&button_timer);
153*1da177e4SLinus Torvalds 	}
154*1da177e4SLinus Torvalds 	button_press_count++;
155*1da177e4SLinus Torvalds 	init_timer (&button_timer);
156*1da177e4SLinus Torvalds 	button_timer.function = button_sequence_finished;
157*1da177e4SLinus Torvalds 	button_timer.expires = (jiffies + bdelay);
158*1da177e4SLinus Torvalds 	add_timer (&button_timer);
159*1da177e4SLinus Torvalds 
160*1da177e4SLinus Torvalds 	return IRQ_HANDLED;
161*1da177e4SLinus Torvalds }
162*1da177e4SLinus Torvalds 
163*1da177e4SLinus Torvalds /*
164*1da177e4SLinus Torvalds  * This function is called when a user space program attempts to read
165*1da177e4SLinus Torvalds  * /dev/nwbutton. It puts the device to sleep on the wait queue until
166*1da177e4SLinus Torvalds  * button_sequence_finished writes some data to the buffer and flushes
167*1da177e4SLinus Torvalds  * the queue, at which point it writes the data out to the device and
168*1da177e4SLinus Torvalds  * returns the number of characters it has written. This function is
169*1da177e4SLinus Torvalds  * reentrant, so that many processes can be attempting to read from the
170*1da177e4SLinus Torvalds  * device at any one time.
171*1da177e4SLinus Torvalds  */
172*1da177e4SLinus Torvalds 
173*1da177e4SLinus Torvalds static int button_read (struct file *filp, char __user *buffer,
174*1da177e4SLinus Torvalds 			size_t count, loff_t *ppos)
175*1da177e4SLinus Torvalds {
176*1da177e4SLinus Torvalds 	interruptible_sleep_on (&button_wait_queue);
177*1da177e4SLinus Torvalds 	return (copy_to_user (buffer, &button_output_buffer, bcount))
178*1da177e4SLinus Torvalds 		 ? -EFAULT : bcount;
179*1da177e4SLinus Torvalds }
180*1da177e4SLinus Torvalds 
181*1da177e4SLinus Torvalds /*
182*1da177e4SLinus Torvalds  * This structure is the file operations structure, which specifies what
183*1da177e4SLinus Torvalds  * callbacks functions the kernel should call when a user mode process
184*1da177e4SLinus Torvalds  * attempts to perform these operations on the device.
185*1da177e4SLinus Torvalds  */
186*1da177e4SLinus Torvalds 
187*1da177e4SLinus Torvalds static struct file_operations button_fops = {
188*1da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
189*1da177e4SLinus Torvalds 	.read		= button_read,
190*1da177e4SLinus Torvalds };
191*1da177e4SLinus Torvalds 
192*1da177e4SLinus Torvalds /*
193*1da177e4SLinus Torvalds  * This structure is the misc device structure, which specifies the minor
194*1da177e4SLinus Torvalds  * device number (158 in this case), the name of the device (for /proc/misc),
195*1da177e4SLinus Torvalds  * and the address of the above file operations structure.
196*1da177e4SLinus Torvalds  */
197*1da177e4SLinus Torvalds 
198*1da177e4SLinus Torvalds static struct miscdevice button_misc_device = {
199*1da177e4SLinus Torvalds 	BUTTON_MINOR,
200*1da177e4SLinus Torvalds 	"nwbutton",
201*1da177e4SLinus Torvalds 	&button_fops,
202*1da177e4SLinus Torvalds };
203*1da177e4SLinus Torvalds 
204*1da177e4SLinus Torvalds /*
205*1da177e4SLinus Torvalds  * This function is called to initialise the driver, either from misc.c at
206*1da177e4SLinus Torvalds  * bootup if the driver is compiled into the kernel, or from init_module
207*1da177e4SLinus Torvalds  * below at module insert time. It attempts to register the device node
208*1da177e4SLinus Torvalds  * and the IRQ and fails with a warning message if either fails, though
209*1da177e4SLinus Torvalds  * neither ever should because the device number and IRQ are unique to
210*1da177e4SLinus Torvalds  * this driver.
211*1da177e4SLinus Torvalds  */
212*1da177e4SLinus Torvalds 
213*1da177e4SLinus Torvalds static int __init nwbutton_init(void)
214*1da177e4SLinus Torvalds {
215*1da177e4SLinus Torvalds 	if (!machine_is_netwinder())
216*1da177e4SLinus Torvalds 		return -ENODEV;
217*1da177e4SLinus Torvalds 
218*1da177e4SLinus Torvalds 	printk (KERN_INFO "NetWinder Button Driver Version %s (C) Alex Holden "
219*1da177e4SLinus Torvalds 			"<alex@linuxhacker.org> 1998.\n", VERSION);
220*1da177e4SLinus Torvalds 
221*1da177e4SLinus Torvalds 	if (misc_register (&button_misc_device)) {
222*1da177e4SLinus Torvalds 		printk (KERN_WARNING "nwbutton: Couldn't register device 10, "
223*1da177e4SLinus Torvalds 				"%d.\n", BUTTON_MINOR);
224*1da177e4SLinus Torvalds 		return -EBUSY;
225*1da177e4SLinus Torvalds 	}
226*1da177e4SLinus Torvalds 
227*1da177e4SLinus Torvalds 	if (request_irq (IRQ_NETWINDER_BUTTON, button_handler, SA_INTERRUPT,
228*1da177e4SLinus Torvalds 			"nwbutton", NULL)) {
229*1da177e4SLinus Torvalds 		printk (KERN_WARNING "nwbutton: IRQ %d is not free.\n",
230*1da177e4SLinus Torvalds 				IRQ_NETWINDER_BUTTON);
231*1da177e4SLinus Torvalds 		misc_deregister (&button_misc_device);
232*1da177e4SLinus Torvalds 		return -EIO;
233*1da177e4SLinus Torvalds 	}
234*1da177e4SLinus Torvalds 	return 0;
235*1da177e4SLinus Torvalds }
236*1da177e4SLinus Torvalds 
237*1da177e4SLinus Torvalds static void __exit nwbutton_exit (void)
238*1da177e4SLinus Torvalds {
239*1da177e4SLinus Torvalds 	free_irq (IRQ_NETWINDER_BUTTON, NULL);
240*1da177e4SLinus Torvalds 	misc_deregister (&button_misc_device);
241*1da177e4SLinus Torvalds }
242*1da177e4SLinus Torvalds 
243*1da177e4SLinus Torvalds 
244*1da177e4SLinus Torvalds MODULE_AUTHOR("Alex Holden");
245*1da177e4SLinus Torvalds MODULE_LICENSE("GPL");
246*1da177e4SLinus Torvalds 
247*1da177e4SLinus Torvalds module_init(nwbutton_init);
248*1da177e4SLinus Torvalds module_exit(nwbutton_exit);
249