1/*
2 * Linux Broadcom BCM47xx GPIO char driver
3 *
4 * Copyright (C) 2013, Broadcom Corporation. All Rights Reserved.
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 * $Id: linux_gpio.c 401759 2013-05-13 16:08:08Z $
18 *
19 */
20#include <linux/module.h>
21#include <linux/init.h>
22#include <linux/fs.h>
23#include <linux/miscdevice.h>
24#include <asm/uaccess.h>
25
26#include <typedefs.h>
27#include <bcmutils.h>
28#include <siutils.h>
29#include <bcmdevs.h>
30
31#include <linux_gpio.h>
32
33/* handle to the sb */
34static si_t *gpio_sih;
35
36/* major number assigned to the device and device handles */
37static int gpio_major;
38#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
39static struct class *gpiodev_class = NULL;
40#else
41devfs_handle_t gpiodev_handle;
42#endif
43
44static int
45gpio_open(struct inode *inode, struct file * file)
46{
47	MOD_INC_USE_COUNT;
48	return 0;
49}
50
51static int
52gpio_release(struct inode *inode, struct file * file)
53{
54	MOD_DEC_USE_COUNT;
55	return 0;
56}
57
58#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
59static long
60gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
61{
62#else
63static int
64gpio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
65{
66#endif /* linux-2.6.22 */
67	struct gpio_ioctl gpioioc;
68
69	if (copy_from_user(&gpioioc, (struct gpio_ioctl *)arg, sizeof(struct gpio_ioctl)))
70		return -EFAULT;
71
72	switch (cmd) {
73		case GPIO_IOC_RESERVE:
74			gpioioc.val = si_gpioreserve(gpio_sih, gpioioc.mask, GPIO_APP_PRIORITY);
75			break;
76		case GPIO_IOC_RELEASE:
77			/*
78			 * releasing the gpio doesn't change the current
79			 * value on the GPIO last write value
80			 * persists till some one overwrites it
81			 */
82			gpioioc.val = si_gpiorelease(gpio_sih, gpioioc.mask, GPIO_APP_PRIORITY);
83			break;
84		case GPIO_IOC_OUT:
85			gpioioc.val = si_gpioout(gpio_sih, gpioioc.mask, gpioioc.val,
86			                         GPIO_APP_PRIORITY);
87			break;
88		case GPIO_IOC_OUTEN:
89			gpioioc.val = si_gpioouten(gpio_sih, gpioioc.mask, gpioioc.val,
90			                           GPIO_APP_PRIORITY);
91			break;
92		case GPIO_IOC_IN:
93			gpioioc.val = si_gpioin(gpio_sih);
94			break;
95		default:
96			break;
97	}
98	if (copy_to_user((struct gpio_ioctl *)arg, &gpioioc, sizeof(struct gpio_ioctl)))
99		return -EFAULT;
100
101	return 0;
102
103}
104static struct file_operations gpio_fops = {
105	owner:		THIS_MODULE,
106	open:		gpio_open,
107	release:	gpio_release,
108#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
109	unlocked_ioctl:  gpio_ioctl
110#else
111	ioctl:		gpio_ioctl
112#endif
113};
114
115static int __init
116gpio_init(void)
117{
118	if (!(gpio_sih = si_kattach(SI_OSH)))
119		return -ENODEV;
120
121#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
122	if ((gpio_major = register_chrdev(0, "gpio", &gpio_fops)) < 0)
123#else
124	if ((gpio_major = devfs_register_chrdev(0, "gpio", &gpio_fops)) < 0)
125#endif
126		return gpio_major;
127#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
128	gpiodev_class = class_create(THIS_MODULE, "gpio");
129	if (IS_ERR(gpiodev_class)) {
130		printk("Error creating gpio class\n");
131		return -1;
132	}
133
134	/* Add the device gpio0 */
135#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
136	device_create(gpiodev_class, NULL, MKDEV(gpio_major, 0), NULL, "gpio", 0);
137#else
138	class_device_create(gpiodev_class, NULL, MKDEV(gpio_major, 0), NULL, "gpio");
139#endif /* linux-2.6.36 */
140#else
141	gpiodev_handle = devfs_register(NULL, "gpio", DEVFS_FL_DEFAULT,
142	                                gpio_major, 0, S_IFCHR | S_IRUGO | S_IWUGO,
143	                                &gpio_fops, NULL);
144#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) */
145
146	return 0;
147}
148
149static void __exit
150gpio_exit(void)
151{
152#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
153	if (gpiodev_class != NULL) {
154#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
155		device_destroy(gpiodev_class, MKDEV(gpio_major, 0));
156#else
157		class_device_destroy(gpiodev_class, MKDEV(gpio_major, 0));
158#endif
159		class_destroy(gpiodev_class);
160	}
161
162	gpiodev_class = NULL;
163	if (gpio_major >= 0)
164		unregister_chrdev(gpio_major, "gpio");
165#else
166	if (gpiodev_handle != NULL)
167		devfs_unregister(gpiodev_handle);
168	gpiodev_handle = NULL;
169	devfs_unregister_chrdev(gpio_major, "gpio");
170#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) */
171	si_detach(gpio_sih);
172}
173
174module_init(gpio_init);
175module_exit(gpio_exit);
176