18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * 	NetWinder Button Driver-
48c2ecf20Sopenharmony_ci *	Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/sched/signal.h>
118c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
128c2ecf20Sopenharmony_ci#include <linux/time.h>
138c2ecf20Sopenharmony_ci#include <linux/timer.h>
148c2ecf20Sopenharmony_ci#include <linux/fs.h>
158c2ecf20Sopenharmony_ci#include <linux/miscdevice.h>
168c2ecf20Sopenharmony_ci#include <linux/string.h>
178c2ecf20Sopenharmony_ci#include <linux/errno.h>
188c2ecf20Sopenharmony_ci#include <linux/init.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
218c2ecf20Sopenharmony_ci#include <asm/irq.h>
228c2ecf20Sopenharmony_ci#include <asm/mach-types.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define __NWBUTTON_C		/* Tell the header file who we are */
258c2ecf20Sopenharmony_ci#include "nwbutton.h"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic void button_sequence_finished(struct timer_list *unused);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic int button_press_count;		/* The count of button presses */
308c2ecf20Sopenharmony_ci/* Times for the end of a sequence */
318c2ecf20Sopenharmony_cistatic DEFINE_TIMER(button_timer, button_sequence_finished);
328c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(button_wait_queue); /* Used for blocking read */
338c2ecf20Sopenharmony_cistatic char button_output_buffer[32];	/* Stores data to write out of device */
348c2ecf20Sopenharmony_cistatic int bcount;			/* The number of bytes in the buffer */
358c2ecf20Sopenharmony_cistatic int bdelay = BUTTON_DELAY;	/* The delay, in jiffies */
368c2ecf20Sopenharmony_cistatic struct button_callback button_callback_list[32]; /* The callback list */
378c2ecf20Sopenharmony_cistatic int callback_count;		/* The number of callbacks registered */
388c2ecf20Sopenharmony_cistatic int reboot_count = NUM_PRESSES_REBOOT; /* Number of presses to reboot */
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci/*
418c2ecf20Sopenharmony_ci * This function is called by other drivers to register a callback function
428c2ecf20Sopenharmony_ci * to be called when a particular number of button presses occurs.
438c2ecf20Sopenharmony_ci * The callback list is a static array of 32 entries (I somehow doubt many
448c2ecf20Sopenharmony_ci * people are ever going to want to register more than 32 different actions
458c2ecf20Sopenharmony_ci * to be performed by the kernel on different numbers of button presses ;).
468c2ecf20Sopenharmony_ci * However, if an attempt to register a 33rd entry (perhaps a stuck loop
478c2ecf20Sopenharmony_ci * somewhere registering the same entry over and over?) it will fail to
488c2ecf20Sopenharmony_ci * do so and return -ENOMEM. If an attempt is made to register a null pointer,
498c2ecf20Sopenharmony_ci * it will fail to do so and return -EINVAL.
508c2ecf20Sopenharmony_ci * Because callbacks can be unregistered at random the list can become
518c2ecf20Sopenharmony_ci * fragmented, so we need to search through the list until we find the first
528c2ecf20Sopenharmony_ci * free entry.
538c2ecf20Sopenharmony_ci *
548c2ecf20Sopenharmony_ci * FIXME: Has anyone spotted any locking functions int his code recently ??
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ciint button_add_callback (void (*callback) (void), int count)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	int lp = 0;
608c2ecf20Sopenharmony_ci	if (callback_count == 32) {
618c2ecf20Sopenharmony_ci		return -ENOMEM;
628c2ecf20Sopenharmony_ci	}
638c2ecf20Sopenharmony_ci	if (!callback) {
648c2ecf20Sopenharmony_ci		return -EINVAL;
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci	callback_count++;
678c2ecf20Sopenharmony_ci	for (; (button_callback_list [lp].callback); lp++);
688c2ecf20Sopenharmony_ci	button_callback_list [lp].callback = callback;
698c2ecf20Sopenharmony_ci	button_callback_list [lp].count = count;
708c2ecf20Sopenharmony_ci	return 0;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci/*
748c2ecf20Sopenharmony_ci * This function is called by other drivers to deregister a callback function.
758c2ecf20Sopenharmony_ci * If you attempt to unregister a callback which does not exist, it will fail
768c2ecf20Sopenharmony_ci * with -EINVAL. If there is more than one entry with the same address,
778c2ecf20Sopenharmony_ci * because it searches the list from end to beginning, it will unregister the
788c2ecf20Sopenharmony_ci * last one to be registered first (FILO- First In Last Out).
798c2ecf20Sopenharmony_ci * Note that this is not necessarily true if the entries are not submitted
808c2ecf20Sopenharmony_ci * at the same time, because another driver could have unregistered a callback
818c2ecf20Sopenharmony_ci * between the submissions creating a gap earlier in the list, which would
828c2ecf20Sopenharmony_ci * be filled first at submission time.
838c2ecf20Sopenharmony_ci */
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ciint button_del_callback (void (*callback) (void))
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci	int lp = 31;
888c2ecf20Sopenharmony_ci	if (!callback) {
898c2ecf20Sopenharmony_ci		return -EINVAL;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci	while (lp >= 0) {
928c2ecf20Sopenharmony_ci		if ((button_callback_list [lp].callback) == callback) {
938c2ecf20Sopenharmony_ci			button_callback_list [lp].callback = NULL;
948c2ecf20Sopenharmony_ci			button_callback_list [lp].count = 0;
958c2ecf20Sopenharmony_ci			callback_count--;
968c2ecf20Sopenharmony_ci			return 0;
978c2ecf20Sopenharmony_ci		}
988c2ecf20Sopenharmony_ci		lp--;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci	return -EINVAL;
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/*
1048c2ecf20Sopenharmony_ci * This function is called by button_sequence_finished to search through the
1058c2ecf20Sopenharmony_ci * list of callback functions, and call any of them whose count argument
1068c2ecf20Sopenharmony_ci * matches the current count of button presses. It starts at the beginning
1078c2ecf20Sopenharmony_ci * of the list and works up to the end. It will refuse to follow a null
1088c2ecf20Sopenharmony_ci * pointer (which should never happen anyway).
1098c2ecf20Sopenharmony_ci */
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic void button_consume_callbacks (int bpcount)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	int lp = 0;
1148c2ecf20Sopenharmony_ci	for (; lp <= 31; lp++) {
1158c2ecf20Sopenharmony_ci		if ((button_callback_list [lp].count) == bpcount) {
1168c2ecf20Sopenharmony_ci			if (button_callback_list [lp].callback) {
1178c2ecf20Sopenharmony_ci				button_callback_list[lp].callback();
1188c2ecf20Sopenharmony_ci			}
1198c2ecf20Sopenharmony_ci		}
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci/*
1248c2ecf20Sopenharmony_ci * This function is called when the button_timer times out.
1258c2ecf20Sopenharmony_ci * ie. When you don't press the button for bdelay jiffies, this is taken to
1268c2ecf20Sopenharmony_ci * mean you have ended the sequence of key presses, and this function is
1278c2ecf20Sopenharmony_ci * called to wind things up (write the press_count out to /dev/button, call
1288c2ecf20Sopenharmony_ci * any matching registered function callbacks, initiate reboot, etc.).
1298c2ecf20Sopenharmony_ci */
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_cistatic void button_sequence_finished(struct timer_list *unused)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_NWBUTTON_REBOOT) &&
1348c2ecf20Sopenharmony_ci	    button_press_count == reboot_count)
1358c2ecf20Sopenharmony_ci		kill_cad_pid(SIGINT, 1);	/* Ask init to reboot us */
1368c2ecf20Sopenharmony_ci	button_consume_callbacks (button_press_count);
1378c2ecf20Sopenharmony_ci	bcount = sprintf (button_output_buffer, "%d\n", button_press_count);
1388c2ecf20Sopenharmony_ci	button_press_count = 0;		/* Reset the button press counter */
1398c2ecf20Sopenharmony_ci	wake_up_interruptible (&button_wait_queue);
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/*
1438c2ecf20Sopenharmony_ci *  This handler is called when the orange button is pressed (GPIO 10 of the
1448c2ecf20Sopenharmony_ci *  SuperIO chip, which maps to logical IRQ 26). If the press_count is 0,
1458c2ecf20Sopenharmony_ci *  this is the first press, so it starts a timer and increments the counter.
1468c2ecf20Sopenharmony_ci *  If it is higher than 0, it deletes the old timer, starts a new one, and
1478c2ecf20Sopenharmony_ci *  increments the counter.
1488c2ecf20Sopenharmony_ci */
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic irqreturn_t button_handler (int irq, void *dev_id)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	button_press_count++;
1538c2ecf20Sopenharmony_ci	mod_timer(&button_timer, jiffies + bdelay);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci/*
1598c2ecf20Sopenharmony_ci * This function is called when a user space program attempts to read
1608c2ecf20Sopenharmony_ci * /dev/nwbutton. It puts the device to sleep on the wait queue until
1618c2ecf20Sopenharmony_ci * button_sequence_finished writes some data to the buffer and flushes
1628c2ecf20Sopenharmony_ci * the queue, at which point it writes the data out to the device and
1638c2ecf20Sopenharmony_ci * returns the number of characters it has written. This function is
1648c2ecf20Sopenharmony_ci * reentrant, so that many processes can be attempting to read from the
1658c2ecf20Sopenharmony_ci * device at any one time.
1668c2ecf20Sopenharmony_ci */
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic int button_read (struct file *filp, char __user *buffer,
1698c2ecf20Sopenharmony_ci			size_t count, loff_t *ppos)
1708c2ecf20Sopenharmony_ci{
1718c2ecf20Sopenharmony_ci	DEFINE_WAIT(wait);
1728c2ecf20Sopenharmony_ci	prepare_to_wait(&button_wait_queue, &wait, TASK_INTERRUPTIBLE);
1738c2ecf20Sopenharmony_ci	schedule();
1748c2ecf20Sopenharmony_ci	finish_wait(&button_wait_queue, &wait);
1758c2ecf20Sopenharmony_ci	return (copy_to_user (buffer, &button_output_buffer, bcount))
1768c2ecf20Sopenharmony_ci		 ? -EFAULT : bcount;
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci/*
1808c2ecf20Sopenharmony_ci * This structure is the file operations structure, which specifies what
1818c2ecf20Sopenharmony_ci * callbacks functions the kernel should call when a user mode process
1828c2ecf20Sopenharmony_ci * attempts to perform these operations on the device.
1838c2ecf20Sopenharmony_ci */
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic const struct file_operations button_fops = {
1868c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
1878c2ecf20Sopenharmony_ci	.read		= button_read,
1888c2ecf20Sopenharmony_ci	.llseek		= noop_llseek,
1898c2ecf20Sopenharmony_ci};
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci/*
1928c2ecf20Sopenharmony_ci * This structure is the misc device structure, which specifies the minor
1938c2ecf20Sopenharmony_ci * device number (158 in this case), the name of the device (for /proc/misc),
1948c2ecf20Sopenharmony_ci * and the address of the above file operations structure.
1958c2ecf20Sopenharmony_ci */
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic struct miscdevice button_misc_device = {
1988c2ecf20Sopenharmony_ci	BUTTON_MINOR,
1998c2ecf20Sopenharmony_ci	"nwbutton",
2008c2ecf20Sopenharmony_ci	&button_fops,
2018c2ecf20Sopenharmony_ci};
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci/*
2048c2ecf20Sopenharmony_ci * This function is called to initialise the driver, either from misc.c at
2058c2ecf20Sopenharmony_ci * bootup if the driver is compiled into the kernel, or from init_module
2068c2ecf20Sopenharmony_ci * below at module insert time. It attempts to register the device node
2078c2ecf20Sopenharmony_ci * and the IRQ and fails with a warning message if either fails, though
2088c2ecf20Sopenharmony_ci * neither ever should because the device number and IRQ are unique to
2098c2ecf20Sopenharmony_ci * this driver.
2108c2ecf20Sopenharmony_ci */
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic int __init nwbutton_init(void)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	if (!machine_is_netwinder())
2158c2ecf20Sopenharmony_ci		return -ENODEV;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	printk (KERN_INFO "NetWinder Button Driver Version %s (C) Alex Holden "
2188c2ecf20Sopenharmony_ci			"<alex@linuxhacker.org> 1998.\n", VERSION);
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	if (misc_register (&button_misc_device)) {
2218c2ecf20Sopenharmony_ci		printk (KERN_WARNING "nwbutton: Couldn't register device 10, "
2228c2ecf20Sopenharmony_ci				"%d.\n", BUTTON_MINOR);
2238c2ecf20Sopenharmony_ci		return -EBUSY;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	if (request_irq (IRQ_NETWINDER_BUTTON, button_handler, 0,
2278c2ecf20Sopenharmony_ci			"nwbutton", NULL)) {
2288c2ecf20Sopenharmony_ci		printk (KERN_WARNING "nwbutton: IRQ %d is not free.\n",
2298c2ecf20Sopenharmony_ci				IRQ_NETWINDER_BUTTON);
2308c2ecf20Sopenharmony_ci		misc_deregister (&button_misc_device);
2318c2ecf20Sopenharmony_ci		return -EIO;
2328c2ecf20Sopenharmony_ci	}
2338c2ecf20Sopenharmony_ci	return 0;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cistatic void __exit nwbutton_exit (void)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	free_irq (IRQ_NETWINDER_BUTTON, NULL);
2398c2ecf20Sopenharmony_ci	misc_deregister (&button_misc_device);
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alex Holden");
2448c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_cimodule_init(nwbutton_init);
2478c2ecf20Sopenharmony_cimodule_exit(nwbutton_exit);
248