162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * 	NetWinder Button Driver-
462306a36Sopenharmony_ci *	Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/sched/signal.h>
1162306a36Sopenharmony_ci#include <linux/interrupt.h>
1262306a36Sopenharmony_ci#include <linux/time.h>
1362306a36Sopenharmony_ci#include <linux/timer.h>
1462306a36Sopenharmony_ci#include <linux/fs.h>
1562306a36Sopenharmony_ci#include <linux/miscdevice.h>
1662306a36Sopenharmony_ci#include <linux/string.h>
1762306a36Sopenharmony_ci#include <linux/errno.h>
1862306a36Sopenharmony_ci#include <linux/init.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/uaccess.h>
2162306a36Sopenharmony_ci#include <asm/irq.h>
2262306a36Sopenharmony_ci#include <asm/mach-types.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define __NWBUTTON_C		/* Tell the header file who we are */
2562306a36Sopenharmony_ci#include "nwbutton.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic void button_sequence_finished(struct timer_list *unused);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic int button_press_count;		/* The count of button presses */
3062306a36Sopenharmony_ci/* Times for the end of a sequence */
3162306a36Sopenharmony_cistatic DEFINE_TIMER(button_timer, button_sequence_finished);
3262306a36Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(button_wait_queue); /* Used for blocking read */
3362306a36Sopenharmony_cistatic char button_output_buffer[32];	/* Stores data to write out of device */
3462306a36Sopenharmony_cistatic int bcount;			/* The number of bytes in the buffer */
3562306a36Sopenharmony_cistatic int bdelay = BUTTON_DELAY;	/* The delay, in jiffies */
3662306a36Sopenharmony_cistatic struct button_callback button_callback_list[32]; /* The callback list */
3762306a36Sopenharmony_cistatic int callback_count;		/* The number of callbacks registered */
3862306a36Sopenharmony_cistatic int reboot_count = NUM_PRESSES_REBOOT; /* Number of presses to reboot */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/*
4162306a36Sopenharmony_ci * This function is called by other drivers to register a callback function
4262306a36Sopenharmony_ci * to be called when a particular number of button presses occurs.
4362306a36Sopenharmony_ci * The callback list is a static array of 32 entries (I somehow doubt many
4462306a36Sopenharmony_ci * people are ever going to want to register more than 32 different actions
4562306a36Sopenharmony_ci * to be performed by the kernel on different numbers of button presses ;).
4662306a36Sopenharmony_ci * However, if an attempt to register a 33rd entry (perhaps a stuck loop
4762306a36Sopenharmony_ci * somewhere registering the same entry over and over?) it will fail to
4862306a36Sopenharmony_ci * do so and return -ENOMEM. If an attempt is made to register a null pointer,
4962306a36Sopenharmony_ci * it will fail to do so and return -EINVAL.
5062306a36Sopenharmony_ci * Because callbacks can be unregistered at random the list can become
5162306a36Sopenharmony_ci * fragmented, so we need to search through the list until we find the first
5262306a36Sopenharmony_ci * free entry.
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * FIXME: Has anyone spotted any locking functions int his code recently ??
5562306a36Sopenharmony_ci */
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ciint button_add_callback (void (*callback) (void), int count)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	int lp = 0;
6062306a36Sopenharmony_ci	if (callback_count == 32) {
6162306a36Sopenharmony_ci		return -ENOMEM;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci	if (!callback) {
6462306a36Sopenharmony_ci		return -EINVAL;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci	callback_count++;
6762306a36Sopenharmony_ci	for (; (button_callback_list [lp].callback); lp++);
6862306a36Sopenharmony_ci	button_callback_list [lp].callback = callback;
6962306a36Sopenharmony_ci	button_callback_list [lp].count = count;
7062306a36Sopenharmony_ci	return 0;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/*
7462306a36Sopenharmony_ci * This function is called by other drivers to deregister a callback function.
7562306a36Sopenharmony_ci * If you attempt to unregister a callback which does not exist, it will fail
7662306a36Sopenharmony_ci * with -EINVAL. If there is more than one entry with the same address,
7762306a36Sopenharmony_ci * because it searches the list from end to beginning, it will unregister the
7862306a36Sopenharmony_ci * last one to be registered first (FILO- First In Last Out).
7962306a36Sopenharmony_ci * Note that this is not necessarily true if the entries are not submitted
8062306a36Sopenharmony_ci * at the same time, because another driver could have unregistered a callback
8162306a36Sopenharmony_ci * between the submissions creating a gap earlier in the list, which would
8262306a36Sopenharmony_ci * be filled first at submission time.
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ciint button_del_callback (void (*callback) (void))
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	int lp = 31;
8862306a36Sopenharmony_ci	if (!callback) {
8962306a36Sopenharmony_ci		return -EINVAL;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci	while (lp >= 0) {
9262306a36Sopenharmony_ci		if ((button_callback_list [lp].callback) == callback) {
9362306a36Sopenharmony_ci			button_callback_list [lp].callback = NULL;
9462306a36Sopenharmony_ci			button_callback_list [lp].count = 0;
9562306a36Sopenharmony_ci			callback_count--;
9662306a36Sopenharmony_ci			return 0;
9762306a36Sopenharmony_ci		}
9862306a36Sopenharmony_ci		lp--;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci	return -EINVAL;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/*
10462306a36Sopenharmony_ci * This function is called by button_sequence_finished to search through the
10562306a36Sopenharmony_ci * list of callback functions, and call any of them whose count argument
10662306a36Sopenharmony_ci * matches the current count of button presses. It starts at the beginning
10762306a36Sopenharmony_ci * of the list and works up to the end. It will refuse to follow a null
10862306a36Sopenharmony_ci * pointer (which should never happen anyway).
10962306a36Sopenharmony_ci */
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic void button_consume_callbacks (int bpcount)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	int lp = 0;
11462306a36Sopenharmony_ci	for (; lp <= 31; lp++) {
11562306a36Sopenharmony_ci		if ((button_callback_list [lp].count) == bpcount) {
11662306a36Sopenharmony_ci			if (button_callback_list [lp].callback) {
11762306a36Sopenharmony_ci				button_callback_list[lp].callback();
11862306a36Sopenharmony_ci			}
11962306a36Sopenharmony_ci		}
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci/*
12462306a36Sopenharmony_ci * This function is called when the button_timer times out.
12562306a36Sopenharmony_ci * ie. When you don't press the button for bdelay jiffies, this is taken to
12662306a36Sopenharmony_ci * mean you have ended the sequence of key presses, and this function is
12762306a36Sopenharmony_ci * called to wind things up (write the press_count out to /dev/button, call
12862306a36Sopenharmony_ci * any matching registered function callbacks, initiate reboot, etc.).
12962306a36Sopenharmony_ci */
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic void button_sequence_finished(struct timer_list *unused)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	if (IS_ENABLED(CONFIG_NWBUTTON_REBOOT) &&
13462306a36Sopenharmony_ci	    button_press_count == reboot_count)
13562306a36Sopenharmony_ci		kill_cad_pid(SIGINT, 1);	/* Ask init to reboot us */
13662306a36Sopenharmony_ci	button_consume_callbacks (button_press_count);
13762306a36Sopenharmony_ci	bcount = sprintf (button_output_buffer, "%d\n", button_press_count);
13862306a36Sopenharmony_ci	button_press_count = 0;		/* Reset the button press counter */
13962306a36Sopenharmony_ci	wake_up_interruptible (&button_wait_queue);
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci/*
14362306a36Sopenharmony_ci *  This handler is called when the orange button is pressed (GPIO 10 of the
14462306a36Sopenharmony_ci *  SuperIO chip, which maps to logical IRQ 26). If the press_count is 0,
14562306a36Sopenharmony_ci *  this is the first press, so it starts a timer and increments the counter.
14662306a36Sopenharmony_ci *  If it is higher than 0, it deletes the old timer, starts a new one, and
14762306a36Sopenharmony_ci *  increments the counter.
14862306a36Sopenharmony_ci */
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic irqreturn_t button_handler (int irq, void *dev_id)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	button_press_count++;
15362306a36Sopenharmony_ci	mod_timer(&button_timer, jiffies + bdelay);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	return IRQ_HANDLED;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci/*
15962306a36Sopenharmony_ci * This function is called when a user space program attempts to read
16062306a36Sopenharmony_ci * /dev/nwbutton. It puts the device to sleep on the wait queue until
16162306a36Sopenharmony_ci * button_sequence_finished writes some data to the buffer and flushes
16262306a36Sopenharmony_ci * the queue, at which point it writes the data out to the device and
16362306a36Sopenharmony_ci * returns the number of characters it has written. This function is
16462306a36Sopenharmony_ci * reentrant, so that many processes can be attempting to read from the
16562306a36Sopenharmony_ci * device at any one time.
16662306a36Sopenharmony_ci */
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int button_read (struct file *filp, char __user *buffer,
16962306a36Sopenharmony_ci			size_t count, loff_t *ppos)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	DEFINE_WAIT(wait);
17262306a36Sopenharmony_ci	prepare_to_wait(&button_wait_queue, &wait, TASK_INTERRUPTIBLE);
17362306a36Sopenharmony_ci	schedule();
17462306a36Sopenharmony_ci	finish_wait(&button_wait_queue, &wait);
17562306a36Sopenharmony_ci	return (copy_to_user (buffer, &button_output_buffer, bcount))
17662306a36Sopenharmony_ci		 ? -EFAULT : bcount;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci/*
18062306a36Sopenharmony_ci * This structure is the file operations structure, which specifies what
18162306a36Sopenharmony_ci * callbacks functions the kernel should call when a user mode process
18262306a36Sopenharmony_ci * attempts to perform these operations on the device.
18362306a36Sopenharmony_ci */
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic const struct file_operations button_fops = {
18662306a36Sopenharmony_ci	.owner		= THIS_MODULE,
18762306a36Sopenharmony_ci	.read		= button_read,
18862306a36Sopenharmony_ci	.llseek		= noop_llseek,
18962306a36Sopenharmony_ci};
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci/*
19262306a36Sopenharmony_ci * This structure is the misc device structure, which specifies the minor
19362306a36Sopenharmony_ci * device number (158 in this case), the name of the device (for /proc/misc),
19462306a36Sopenharmony_ci * and the address of the above file operations structure.
19562306a36Sopenharmony_ci */
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic struct miscdevice button_misc_device = {
19862306a36Sopenharmony_ci	BUTTON_MINOR,
19962306a36Sopenharmony_ci	"nwbutton",
20062306a36Sopenharmony_ci	&button_fops,
20162306a36Sopenharmony_ci};
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci/*
20462306a36Sopenharmony_ci * This function is called to initialise the driver, either from misc.c at
20562306a36Sopenharmony_ci * bootup if the driver is compiled into the kernel, or from init_module
20662306a36Sopenharmony_ci * below at module insert time. It attempts to register the device node
20762306a36Sopenharmony_ci * and the IRQ and fails with a warning message if either fails, though
20862306a36Sopenharmony_ci * neither ever should because the device number and IRQ are unique to
20962306a36Sopenharmony_ci * this driver.
21062306a36Sopenharmony_ci */
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic int __init nwbutton_init(void)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	if (!machine_is_netwinder())
21562306a36Sopenharmony_ci		return -ENODEV;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	printk (KERN_INFO "NetWinder Button Driver Version %s (C) Alex Holden "
21862306a36Sopenharmony_ci			"<alex@linuxhacker.org> 1998.\n", VERSION);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	if (misc_register (&button_misc_device)) {
22162306a36Sopenharmony_ci		printk (KERN_WARNING "nwbutton: Couldn't register device 10, "
22262306a36Sopenharmony_ci				"%d.\n", BUTTON_MINOR);
22362306a36Sopenharmony_ci		return -EBUSY;
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	if (request_irq (IRQ_NETWINDER_BUTTON, button_handler, 0,
22762306a36Sopenharmony_ci			"nwbutton", NULL)) {
22862306a36Sopenharmony_ci		printk (KERN_WARNING "nwbutton: IRQ %d is not free.\n",
22962306a36Sopenharmony_ci				IRQ_NETWINDER_BUTTON);
23062306a36Sopenharmony_ci		misc_deregister (&button_misc_device);
23162306a36Sopenharmony_ci		return -EIO;
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci	return 0;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic void __exit nwbutton_exit (void)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	free_irq (IRQ_NETWINDER_BUTTON, NULL);
23962306a36Sopenharmony_ci	misc_deregister (&button_misc_device);
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ciMODULE_AUTHOR("Alex Holden");
24462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cimodule_init(nwbutton_init);
24762306a36Sopenharmony_cimodule_exit(nwbutton_exit);
248