blob: 696dca5e675fe4f01892a66ee17a23b88b196266 [file] [log] [blame]
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <asm/uaccess.h>
#include <mach/gpio.h>
#include "kgpio.h"
#define GALOIS_GPIO_NAME "gpio"
#undef ENABLE_GPIO_DEBUG
#ifdef ENABLE_GPIO_DEBUG
#define DEBUG_PRINT printk
#else
#define DEBUG_PRINT(...)
#endif
struct gpio_priv {
int apb_gpio0_irq;
int apb_gpio1_irq;
int apb_gpio2_irq;
int apb_gpio3_irq;
int sm_gpio0_irq;
int sm_gpio1_irq;
};
struct gpio_priv priv;
static spinlock_t gpio_irq_lock;
static DEFINE_MUTEX(irq_init_mutex);
static int irq_init = 0; /* ioctl IRQ_INIT count by multiple processes */
static unsigned int gpio_dev_id = 0x12345678;
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait_queue);
static unsigned int gpio_irq_mode = 0;
static unsigned int gpio_irq_status = 0;
static int gpio_mode[GPIO_PORT_NUM];
static unsigned int gpio_mode_status = 0;
static unsigned int sm_gpio_irq_mode = 0;
static unsigned int sm_gpio_irq_status = 0;
static int sm_gpio_mode[SM_GPIO_PORT_NUM];
static unsigned int sm_gpio_mode_status = 0;
static irqreturn_t gpio_interrupt_handle(int irq, void *dev_id)
{
int i;
spin_lock(&gpio_irq_lock);
for (i = 0; i < GPIO_PORT_NUM; i++) {
if ((gpio_irq_mode & (1 << i)) && (GPIO_PortHasInterrupt(i) == 1)) {
DEBUG_PRINT("[K]irq:%d, port:%d\n", irq, i);
GPIO_PortClearInterrupt(i);
DEBUG_PRINT("[port:%d, %d]\n", i, GPIO_PortHasInterrupt(i));
gpio_irq_status |= (1 << i);
}
}
for (i = 0; i < SM_GPIO_PORT_NUM; i++) {
if ((sm_gpio_irq_mode & (1 << i)) && (SM_GPIO_PortHasInterrupt(i) == 1)) {
DEBUG_PRINT("[K]irq:%d, smport:%d\n", irq, i);
SM_GPIO_PortClearInterrupt(i);
DEBUG_PRINT("[port:%d, %d]\n", i, SM_GPIO_PortHasInterrupt(i));
sm_gpio_irq_status |= (1 << i);
}
}
if (gpio_irq_status || sm_gpio_irq_status)
wake_up_interruptible(&gpio_wait_queue);
spin_unlock(&gpio_irq_lock);
return IRQ_HANDLED;
}
static int galois_gpio_open(struct inode *inode, struct file *file)
{
return 0;
}
static int galois_gpio_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t galois_gpio_read(struct file *file, char __user * buf,
size_t count, loff_t *ppos)
{
printk("error: please use ioctl.\n");
return -EFAULT;
}
static ssize_t galois_gpio_write(struct file *file, const char __user * buf,
size_t count, loff_t *ppos)
{
printk("error: please use ioctl.\n");
return -EFAULT;
}
/*
* gpio_set_mode: set SoC GPIO port to specific mode
* @port: port number
* @mode: mode
*/
static int gpio_set_mode(int port, int mode)
{
switch (mode) {
case GPIO_MODE_DATA_IN:
GPIO_PortSetInOut(port, 1);
break;
case GPIO_MODE_DATA_OUT:
GPIO_PortSetInOut(port, 0);
break;
case GPIO_MODE_IRQ_LOWLEVEL:
GPIO_PortSetInOut(port, 1);
GPIO_PortInitIRQ(port, 0, 0);
break;
case GPIO_MODE_IRQ_HIGHLEVEL:
GPIO_PortSetInOut(port, 1);
GPIO_PortInitIRQ(port, 0, 1);
break;
case GPIO_MODE_IRQ_RISEEDGE:
GPIO_PortSetInOut(port, 1);
GPIO_PortInitIRQ(port, 1, 1);
break;
case GPIO_MODE_IRQ_FALLEDGE:
GPIO_PortSetInOut(port, 1);
GPIO_PortInitIRQ(port, 1, 0);
break;
default:
printk("Unknown GPIO mode.\n");
return -1;
}
gpio_mode_status |= (1<<port);
gpio_mode[port] = mode;
return 0;
}
/*
* sm_gpio_set_mode: set SM GPIO port to specific mode
* @port: port number
* @mode: mode
*/
static int sm_gpio_set_mode(int port, int mode)
{
switch (mode) {
case GPIO_MODE_DATA_IN:
SM_GPIO_PortSetInOut(port, 1);
break;
case GPIO_MODE_DATA_OUT:
SM_GPIO_PortSetInOut(port, 0);
break;
case GPIO_MODE_IRQ_LOWLEVEL:
SM_GPIO_PortSetInOut(port, 1);
SM_GPIO_PortInitIRQ(port, 0, 0);
break;
case GPIO_MODE_IRQ_HIGHLEVEL:
SM_GPIO_PortSetInOut(port, 1);
SM_GPIO_PortInitIRQ(port, 0, 1);
break;
case GPIO_MODE_IRQ_RISEEDGE:
SM_GPIO_PortSetInOut(port, 1);
SM_GPIO_PortInitIRQ(port, 1, 1);
break;
case GPIO_MODE_IRQ_FALLEDGE:
SM_GPIO_PortSetInOut(port, 1);
SM_GPIO_PortInitIRQ(port, 1, 0);
break;
default:
printk("Unknown SM GPIO mode.\n");
return -1;
}
sm_gpio_mode_status |= (1<<port);
sm_gpio_mode[port] = mode;
return 0;
}
static int gpio_get_info(galois_gpio_reg_t *gpio_reg)
{
int i, inout, data;
for (i = 0; i < GPIO_PORT_NUM; i++) {
GPIO_PortGetInOut(i, &inout);
gpio_reg->port_ddr |= (inout << i);
GPIO_PortGetData(i, &data);
gpio_reg->port_dr |= (data << i);
}
for (i = 0; i < GPIO_PORT_NUM; i++) {
gpio_reg->gpio_mode[i] = gpio_mode[i];
}
return 0;
}
static int sm_gpio_get_info(galois_gpio_reg_t *gpio_reg)
{
int i, inout, data;
for (i = 0; i < SM_GPIO_PORT_NUM; i++) {
SM_GPIO_PortGetInOut(i, &inout);
gpio_reg->port_ddr |= (inout << i);
SM_GPIO_PortGetData(i, &data);
gpio_reg->port_dr |= (data << i);
}
for (i = 0; i < SM_GPIO_PORT_NUM; i++) {
gpio_reg->gpio_mode[i] = sm_gpio_mode[i];
}
return 0;
}
static long galois_gpio_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
galois_gpio_data_t *arg_data = (galois_gpio_data_t *)arg;
galois_gpio_reg_t *kreg;
galois_gpio_data_t gpio_data;
unsigned int __gpio_irq_status, __sm_gpio_irq_status;
unsigned long flag;
int ret = 0;
switch(cmd) {
case GPIO_IOCTL_LOCK:
GPIO_PortLock(arg);
break;
case GPIO_IOCTL_UNLOCK:
GPIO_PortUnlock(arg);
break;
case SM_GPIO_IOCTL_LOCK:
SM_GPIO_PortLock(arg);
break;
case SM_GPIO_IOCTL_UNLOCK:
SM_GPIO_PortUnlock(arg);
break;
case GPIO_IOCTL_SET:
case SM_GPIO_IOCTL_SET:
if (copy_from_user(&gpio_data, (void __user *)arg, sizeof(galois_gpio_data_t)))
return -EFAULT;
if (cmd == GPIO_IOCTL_SET)
gpio_set_mode(gpio_data.port, gpio_data.mode);
else
sm_gpio_set_mode(gpio_data.port, gpio_data.mode);
break;
case GPIO_IOCTL_GET:
case SM_GPIO_IOCTL_GET:
kreg = kzalloc(sizeof(galois_gpio_reg_t), GFP_KERNEL);
if (kreg == NULL) {
printk("error: failed to allocate memory.\n");
return -ENOMEM;
}
if (cmd == GPIO_IOCTL_GET)
gpio_get_info(kreg);
else
sm_gpio_get_info(kreg);
if (copy_to_user((void __user *)arg, kreg, sizeof(galois_gpio_reg_t))) {
kfree(kreg);
return -EFAULT;
}
kfree(kreg);
break;
case GPIO_IOCTL_READ:
case SM_GPIO_IOCTL_READ:
if (copy_from_user(&gpio_data, (void __user *)arg, sizeof(galois_gpio_data_t)))
return -EFAULT;
if (cmd == GPIO_IOCTL_READ) {
if (gpio_mode[gpio_data.port] != GPIO_MODE_DATA_IN) {
printk("GPIO port %d isn't set to data in.\n", gpio_data.port);
return -ENODEV;
}
if (GPIO_PortRead(gpio_data.port, &gpio_data.data)) {
printk("GPIO_PortRead error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
} else {
if (sm_gpio_mode[gpio_data.port] != GPIO_MODE_DATA_IN) {
printk("SM GPIO port %d isn't set to data in.\n", gpio_data.port);
return -ENODEV;
}
if (SM_GPIO_PortRead(gpio_data.port, &gpio_data.data)) {
printk("SM_GPIO_PortRead error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
}
if (put_user(gpio_data.data, &arg_data->data))
return -EFAULT;
break;
case GPIO_IOCTL_WRITE:
case SM_GPIO_IOCTL_WRITE:
if (copy_from_user(&gpio_data, (void __user *)arg, sizeof(galois_gpio_data_t)))
return -EFAULT;
if (cmd == GPIO_IOCTL_WRITE) {
if (gpio_mode[gpio_data.port] != GPIO_MODE_DATA_OUT) {
printk("GPIO port %d isn't set to data out.\n", gpio_data.port);
return -ENODEV;
}
if (GPIO_PortWrite(gpio_data.port, gpio_data.data)) {
printk("GPIO_PortRead error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
} else {
if (sm_gpio_mode[gpio_data.port] != GPIO_MODE_DATA_OUT) {
printk("SM GPIO port %d isn't set to data out.\n", gpio_data.port);
return -ENODEV;
}
if (SM_GPIO_PortWrite(gpio_data.port, gpio_data.data)) {
printk("SM_GPIO_PortRead error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
}
break;
case GPIO_IOCTL_ENABLE_IRQ:
case SM_GPIO_IOCTL_ENABLE_IRQ:
if (copy_from_user(&gpio_data, (void __user *)arg, sizeof(galois_gpio_data_t)))
return -EFAULT;
if (cmd == GPIO_IOCTL_ENABLE_IRQ) {
if (GPIO_PortEnableIRQ(gpio_data.port)) {
printk("GPIO_PortEnableIRQ error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
spin_lock_irqsave(&gpio_irq_lock, flag);
gpio_irq_mode |= (1 << gpio_data.port);
spin_unlock_irqrestore(&gpio_irq_lock, flag);
} else {
if (SM_GPIO_PortEnableIRQ(gpio_data.port)) {
printk("SM_GPIO_PortEnableIRQ error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
spin_lock_irqsave(&gpio_irq_lock, flag);
sm_gpio_irq_mode |= (1 << gpio_data.port);
spin_unlock_irqrestore(&gpio_irq_lock, flag);
}
break;
case GPIO_IOCTL_DISABLE_IRQ:
case SM_GPIO_IOCTL_DISABLE_IRQ:
if (copy_from_user(&gpio_data, (void __user *)arg, sizeof(galois_gpio_data_t)))
return -EFAULT;
if (cmd == GPIO_IOCTL_DISABLE_IRQ) {
if (GPIO_PortDisableIRQ(gpio_data.port)) {
printk("GPIO_PortDisableIRQ error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
spin_lock_irqsave(&gpio_irq_lock, flag);
gpio_irq_mode &= ~(1 << gpio_data.port);
spin_unlock_irqrestore(&gpio_irq_lock, flag);
} else {
if (SM_GPIO_PortDisableIRQ(gpio_data.port)) {
printk("SM_GPIO_PortDisableIRQ error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
spin_lock_irqsave(&gpio_irq_lock, flag);
sm_gpio_irq_mode &= ~(1 << gpio_data.port);
spin_unlock_irqrestore(&gpio_irq_lock, flag);
}
break;
case GPIO_IOCTL_CLEAR_IRQ:
case SM_GPIO_IOCTL_CLEAR_IRQ:
if (copy_from_user(&gpio_data, (void __user *)arg, sizeof(galois_gpio_data_t)))
return -EFAULT;
if (cmd == GPIO_IOCTL_CLEAR_IRQ) {
/* GPIO_PortClearInterrupt isn't necessary */
if (GPIO_PortClearInterrupt(gpio_data.port)) {
printk("GPIO_PortClearIRQ error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
spin_lock_irqsave(&gpio_irq_lock, flag);
gpio_irq_status &= ~(1 << gpio_data.port);
spin_unlock_irqrestore(&gpio_irq_lock, flag);
} else {
/* GPIO_PortClearInterrupt isn't necessary */
if (SM_GPIO_PortClearInterrupt(gpio_data.port)) {
printk("SM_GPIO_PortClearIRQ error (port=%d)\n", gpio_data.port);
return -ENODEV;
}
spin_lock_irqsave(&gpio_irq_lock, flag);
sm_gpio_irq_status &= ~(1 << gpio_data.port);
spin_unlock_irqrestore(&gpio_irq_lock, flag);
}
break;
case GPIO_IOCTL_READ_IRQ:
case SM_GPIO_IOCTL_READ_IRQ:
spin_lock_irqsave(&gpio_irq_lock, flag);
__gpio_irq_status = gpio_irq_status;
__sm_gpio_irq_status = sm_gpio_irq_status;
spin_unlock_irqrestore(&gpio_irq_lock, flag);
if (cmd == GPIO_IOCTL_READ_IRQ) {
if (put_user(__gpio_irq_status, (unsigned int *)arg))
return -EFAULT;
} else {
if (put_user(__sm_gpio_irq_status, (unsigned int *)arg))
return -EFAULT;
}
break;
case GPIO_IOCTL_INIT_IRQ:
mutex_lock(&irq_init_mutex);
if (irq_init > 0) {
irq_init++;
printk("GPIO irq init++ %d\n", irq_init);
mutex_unlock(&irq_init_mutex);
return 0;
}
/* reigster GPIO inst0 interrupt */
ret = request_irq(priv.apb_gpio0_irq, gpio_interrupt_handle,
IRQF_SHARED, GALOIS_GPIO_NAME, &gpio_dev_id);
if (ret) {
printk("failed to request_irq %d.\n", priv.apb_gpio0_irq);
goto init_irq_exit;
}
/* reigster GPIO inst1 interrupt */
ret = request_irq(priv.apb_gpio1_irq, gpio_interrupt_handle,
IRQF_SHARED, GALOIS_GPIO_NAME, &gpio_dev_id);
if (ret) {
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
printk("failed to request_irq %d.\n", priv.apb_gpio1_irq);
goto init_irq_exit;
}
/* reigster GPIO inst2 interrupt */
ret = request_irq(priv.apb_gpio2_irq, gpio_interrupt_handle,
IRQF_SHARED, GALOIS_GPIO_NAME, &gpio_dev_id);
if (ret) {
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
free_irq(priv.apb_gpio1_irq, &gpio_dev_id);
printk("failed to request_irq %d.\n", priv.apb_gpio2_irq);
goto init_irq_exit;
}
/* reigster GPIO inst3 interrupt */
ret = request_irq(priv.apb_gpio3_irq, gpio_interrupt_handle,
IRQF_SHARED, GALOIS_GPIO_NAME, &gpio_dev_id);
if (ret) {
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
free_irq(priv.apb_gpio1_irq, &gpio_dev_id);
free_irq(priv.apb_gpio2_irq, &gpio_dev_id);
printk("failed to request_irq %d.\n", priv.apb_gpio3_irq);
goto init_irq_exit;
}
/* reigster SM GPIO inst1 interrupt */
ret = request_irq(priv.sm_gpio1_irq, gpio_interrupt_handle,
IRQF_SHARED, GALOIS_GPIO_NAME, &gpio_dev_id);
if (ret) {
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
free_irq(priv.apb_gpio1_irq, &gpio_dev_id);
free_irq(priv.apb_gpio2_irq, &gpio_dev_id);
free_irq(priv.apb_gpio3_irq, &gpio_dev_id);
printk("failed to request_irq %d.\n", priv.sm_gpio1_irq);
goto init_irq_exit;
}
/* reigster SM GPIO inst0 interrupt */
ret = request_irq(priv.sm_gpio0_irq, gpio_interrupt_handle, IRQF_SHARED,
GALOIS_GPIO_NAME, &gpio_dev_id);
if (ret) {
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
free_irq(priv.apb_gpio1_irq, &gpio_dev_id);
free_irq(priv.apb_gpio2_irq, &gpio_dev_id);
free_irq(priv.apb_gpio3_irq, &gpio_dev_id);
free_irq(priv.sm_gpio1_irq, &gpio_dev_id);
printk("error: failed to request_irq %d.\n", priv.sm_gpio0_irq);
goto init_irq_exit;
}
irq_init++;
printk("GPIO irq init++ %d\n", irq_init);
init_irq_exit:
mutex_unlock(&irq_init_mutex);
return ret;
//break;
case GPIO_IOCTL_EXIT_IRQ:
mutex_lock(&irq_init_mutex);
if (irq_init > 0) {
irq_init--;
printk("GPIO irq init-- %d", irq_init);
}
if (irq_init > 0) {
mutex_unlock(&irq_init_mutex);
return 0;
}
/* can it disable the irq when last handle is freed? */
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
free_irq(priv.apb_gpio1_irq, &gpio_dev_id);
free_irq(priv.apb_gpio2_irq, &gpio_dev_id);
free_irq(priv.apb_gpio3_irq, &gpio_dev_id);
free_irq(priv.sm_gpio0_irq, &gpio_dev_id);
free_irq(priv.sm_gpio1_irq, &gpio_dev_id);
mutex_unlock(&irq_init_mutex);
break;
default:
return -EPERM;
}
return 0;
}
static unsigned int galois_gpio_poll(struct file *file, poll_table *wait)
{
unsigned int __gpio_irq_status, __sm_gpio_irq_status;
unsigned long flag;
poll_wait(file, &gpio_wait_queue, wait);
spin_lock_irqsave(&gpio_irq_lock, flag);
__gpio_irq_status = gpio_irq_status;
__sm_gpio_irq_status = sm_gpio_irq_status;
spin_unlock_irqrestore(&gpio_irq_lock, flag);
if (__gpio_irq_status || __sm_gpio_irq_status)
return (POLLIN | POLLRDNORM);
return 0;
}
static struct file_operations galois_gpio_fops = {
.owner = THIS_MODULE,
.open = galois_gpio_open,
.release = galois_gpio_release,
.write = galois_gpio_write,
.read = galois_gpio_read,
.unlocked_ioctl = galois_gpio_ioctl,
.poll = galois_gpio_poll,
};
static struct miscdevice gpio_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = GALOIS_GPIO_NAME,
.fops = &galois_gpio_fops,
};
static int __init galois_gpio_init(void)
{
int i;
struct device_node *np;
struct resource r;
np = of_find_compatible_node(NULL, NULL, "berlin,apb-gpio");
if (!np)
return -ENODEV;
of_irq_to_resource(np, 0, &r);
priv.apb_gpio0_irq = r.start;
of_irq_to_resource(np, 1, &r);
priv.apb_gpio1_irq = r.start;
of_irq_to_resource(np, 2, &r);
priv.apb_gpio2_irq = r.start;
of_irq_to_resource(np, 3, &r);
priv.apb_gpio3_irq = r.start;
of_node_put(np);
np = of_find_compatible_node(NULL, NULL, "berlin,sm-gpio");
if (!np)
return -ENODEV;
of_irq_to_resource(np, 0, &r);
priv.sm_gpio0_irq = r.start;
of_irq_to_resource(np, 1, &r);
priv.sm_gpio1_irq = r.start;
of_node_put(np);
spin_lock_init(&gpio_irq_lock);
for (i = 0; i < GPIO_PORT_NUM; i++)
gpio_mode[i] = 0;
for (i = 0; i < SM_GPIO_PORT_NUM; i++)
sm_gpio_mode[i] = 0;
return misc_register(&gpio_dev);
}
static void __exit galois_gpio_exit(void)
{
int i;
/* disable all GPIO interrupts */
for (i = 0; i < GPIO_PORT_NUM; i++) {
if (gpio_irq_mode & (1 << i))
GPIO_PortDisableIRQ(i);
}
for (i = 0; i < SM_GPIO_PORT_NUM; i++) {
if (sm_gpio_irq_mode & (1 << i))
SM_GPIO_PortDisableIRQ(i);
}
/* un-registeer handler registered by GPIO */
free_irq(priv.apb_gpio0_irq, &gpio_dev_id);
free_irq(priv.apb_gpio1_irq, &gpio_dev_id);
free_irq(priv.apb_gpio2_irq, &gpio_dev_id);
free_irq(priv.apb_gpio3_irq, &gpio_dev_id);
free_irq(priv.sm_gpio0_irq, &gpio_dev_id);
free_irq(priv.sm_gpio1_irq, &gpio_dev_id);
misc_deregister(&gpio_dev);
}
module_init(galois_gpio_init);
module_exit(galois_gpio_exit);
MODULE_AUTHOR("Marvell-Galois");
MODULE_DESCRIPTION("Galois GPIO Driver");
MODULE_LICENSE("GPL");