/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * DFE of FSM9XXX */ #define HH_ADDR_MASK 0x000ffffc #define HH_OFFSET_VALID(offset) (((offset) & ~HH_ADDR_MASK) == 0) #define HH_REG_IOADDR(offset) ((uint8_t *) MSM_HH_BASE + (offset)) #define HH_MAKE_OFFSET(blk, adr) (((blk)&0x1F)<<15|((adr)&0x1FFF)<<2) #define HH_REG_SCPN_IREQ_MASK HH_REG_IOADDR(HH_MAKE_OFFSET(5, 0x12)) #define HH_REG_SCPN_IREQ_FLAG HH_REG_IOADDR(HH_MAKE_OFFSET(5, 0x13)) /* * Device private information per device node */ #define HH_IRQ_FIFO_SIZE 64 #define HH_IRQ_FIFO_EMPTY(pdev) ((pdev)->irq_fifo_head == \ (pdev)->irq_fifo_tail) #define HH_IRQ_FIFO_FULL(pdev) ((((pdev)->irq_fifo_tail + 1) % \ HH_IRQ_FIFO_SIZE) == \ (pdev)->irq_fifo_head) static struct hh_dev_node_info { spinlock_t hh_lock; char irq_fifo[HH_IRQ_FIFO_SIZE]; unsigned int irq_fifo_head, irq_fifo_tail; wait_queue_head_t wq; } hh_dev_info; /* * Device private information per file */ struct hh_dev_file_info { /* Buffer */ unsigned int *parray; unsigned int array_num; struct dfe_command_entry *pcmd; unsigned int cmd_num; }; /* * File interface */ static int hh_open(struct inode *inode, struct file *file) { struct hh_dev_file_info *pdfi; /* private data allocation */ pdfi = kmalloc(sizeof(*pdfi), GFP_KERNEL); if (pdfi == NULL) return -ENOMEM; file->private_data = pdfi; /* buffer initialization */ pdfi->parray = NULL; pdfi->array_num = 0; pdfi->pcmd = NULL; pdfi->cmd_num = 0; return 0; } static int hh_release(struct inode *inode, struct file *file) { struct hh_dev_file_info *pdfi; pdfi = (struct hh_dev_file_info *) file->private_data; kfree(pdfi->parray); pdfi->parray = NULL; pdfi->array_num = 0; kfree(pdfi->pcmd); pdfi->pcmd = NULL; pdfi->cmd_num = 0; kfree(file->private_data); file->private_data = NULL; return 0; } static ssize_t hh_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { signed char irq = -1; unsigned long irq_flags; do { spin_lock_irqsave(&hh_dev_info.hh_lock, irq_flags); if (!HH_IRQ_FIFO_EMPTY(&hh_dev_info)) { irq = hh_dev_info.irq_fifo[hh_dev_info.irq_fifo_head]; if (++hh_dev_info.irq_fifo_head == HH_IRQ_FIFO_SIZE) hh_dev_info.irq_fifo_head = 0; } spin_unlock_irqrestore(&hh_dev_info.hh_lock, irq_flags); if (irq < 0) if (wait_event_interruptible(hh_dev_info.wq, !HH_IRQ_FIFO_EMPTY(&hh_dev_info)) < 0) break; } while (irq < 0); if (irq < 0) { /* No pending interrupt */ return 0; } else { put_user(irq, buf); return 1; } return 0; } static ssize_t hh_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { return 0; } static long hh_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned int __user *argp = (unsigned int __user *) arg; struct hh_dev_file_info *pdfi = (struct hh_dev_file_info *) file->private_data; switch (cmd) { case DFE_IOCTL_READ_REGISTER: { unsigned int offset, value; if (get_user(offset, argp)) return -EFAULT; if (!HH_OFFSET_VALID(offset)) return -EINVAL; value = __raw_readl(HH_REG_IOADDR(offset)); if (put_user(value, argp)) return -EFAULT; } break; case DFE_IOCTL_WRITE_REGISTER: { struct dfe_write_register_param param; if (copy_from_user(¶m, argp, sizeof param)) return -EFAULT; if (!HH_OFFSET_VALID(param.offset)) return -EINVAL; __raw_writel(param.value, HH_REG_IOADDR(param.offset)); } break; case DFE_IOCTL_WRITE_REGISTER_WITH_MASK: { struct dfe_write_register_mask_param param; unsigned int value; unsigned long irq_flags; if (copy_from_user(¶m, argp, sizeof param)) return -EFAULT; if (!HH_OFFSET_VALID(param.offset)) return -EINVAL; spin_lock_irqsave(&hh_dev_info.hh_lock, irq_flags); value = __raw_readl(HH_REG_IOADDR(param.offset)); value &= ~param.mask; value |= param.value & param.mask; __raw_writel(value, HH_REG_IOADDR(param.offset)); spin_unlock_irqrestore(&hh_dev_info.hh_lock, irq_flags); } break; case DFE_IOCTL_READ_REGISTER_ARRAY: case DFE_IOCTL_WRITE_REGISTER_ARRAY: { struct dfe_read_write_array_param param; unsigned int req_sz; unsigned long irq_flags; unsigned int i; void *addr; if (copy_from_user(¶m, argp, sizeof param)) return -EFAULT; if (!HH_OFFSET_VALID(param.offset)) return -EINVAL; if (param.num == 0) break; req_sz = sizeof(unsigned int) * param.num; if (pdfi->array_num < param.num) { void *pmem; pmem = kmalloc(req_sz, GFP_KERNEL); if (pmem == NULL) return -ENOMEM; pdfi->parray = (unsigned int *) pmem; pdfi->array_num = param.num; } if (cmd == DFE_IOCTL_WRITE_REGISTER_ARRAY) if (copy_from_user(pdfi->parray, param.pArray, req_sz)) return -EFAULT; addr = HH_REG_IOADDR(param.offset); spin_lock_irqsave(&hh_dev_info.hh_lock, irq_flags); for (i = 0; i < param.num; ++i, addr += 4) { if (cmd == DFE_IOCTL_READ_REGISTER_ARRAY) pdfi->parray[i] = __raw_readl(addr); else __raw_writel(pdfi->parray[i], addr); } spin_unlock_irqrestore(&hh_dev_info.hh_lock, irq_flags); if (cmd == DFE_IOCTL_READ_REGISTER_ARRAY) if (copy_to_user(pdfi->parray, param.pArray, req_sz)) return -EFAULT; } break; case DFE_IOCTL_COMMAND: { struct dfe_command_param param; unsigned int req_sz; unsigned long irq_flags; unsigned int i, value; struct dfe_command_entry *pcmd; void *addr; if (copy_from_user(¶m, argp, sizeof param)) return -EFAULT; if (param.num == 0) break; req_sz = sizeof(struct dfe_command_entry) * param.num; if (pdfi->cmd_num < param.num) { void *pmem; pmem = kmalloc(req_sz, GFP_KERNEL); if (pmem == NULL) return -ENOMEM; pdfi->pcmd = (struct dfe_command_entry *) pmem; pdfi->cmd_num = param.num; } if (copy_from_user(pdfi->pcmd, param.pEntry, req_sz)) return -EFAULT; pcmd = pdfi->pcmd; spin_lock_irqsave(&hh_dev_info.hh_lock, irq_flags); for (i = 0; i < param.num; ++i, ++pcmd) { if (!HH_OFFSET_VALID(pcmd->offset)) return -EINVAL; addr = HH_REG_IOADDR(pcmd->offset); switch (pcmd->code) { case DFE_IOCTL_COMMAND_CODE_WRITE: __raw_writel(pcmd->value, addr); break; case DFE_IOCTL_COMMAND_CODE_WRITE_WITH_MASK: value = __raw_readl(addr); value &= ~pcmd->mask; value |= pcmd->value & pcmd->mask; __raw_writel(value, addr); break; } } spin_unlock_irqrestore(&hh_dev_info.hh_lock, irq_flags); } break; default: return -EINVAL; } return 0; } static unsigned int hh_poll(struct file *filp, struct poll_table_struct *wait) { unsigned mask = 0; if (!HH_IRQ_FIFO_EMPTY(&hh_dev_info)) mask |= POLLIN; if (mask == 0) { poll_wait(filp, &hh_dev_info.wq, wait); if (!HH_IRQ_FIFO_EMPTY(&hh_dev_info)) mask |= POLLIN; } return mask; } static const struct file_operations hh_fops = { .owner = THIS_MODULE, .open = hh_open, .release = hh_release, .read = hh_read, .write = hh_write, .unlocked_ioctl = hh_ioctl, .poll = hh_poll, }; /* * Interrupt handling */ static irqreturn_t hh_irq_handler(int irq, void *data) { unsigned int irq_enable, irq_flag, irq_mask; int i; irq_enable = __raw_readl(HH_REG_SCPN_IREQ_MASK); irq_flag = __raw_readl(HH_REG_SCPN_IREQ_FLAG); irq_flag &= irq_enable; /* Disables interrupts */ irq_enable &= ~irq_flag; __raw_writel(irq_enable, HH_REG_SCPN_IREQ_MASK); /* Adds the pending interrupts to irq_fifo */ spin_lock(&hh_dev_info.hh_lock); for (i = 0, irq_mask = 1; i < 32; ++i, irq_mask <<= 1) { if (HH_IRQ_FIFO_FULL(&hh_dev_info)) break; if (irq_flag & irq_mask) { hh_dev_info.irq_fifo[hh_dev_info.irq_fifo_tail] = \ (char) i; if (++hh_dev_info.irq_fifo_tail == HH_IRQ_FIFO_SIZE) hh_dev_info.irq_fifo_tail = 0; } } spin_unlock(&hh_dev_info.hh_lock); /* Wakes up pending processes */ wake_up_interruptible(&hh_dev_info.wq); return IRQ_HANDLED; } /* * Driver initialization & cleanup */ static struct miscdevice hh_misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = DFE_HH_DEVICE_NAME, .fops = &hh_fops, }; static int __init hh_init(void) { int ret; /* lock initialization */ spin_lock_init(&hh_dev_info.hh_lock); /* interrupt handler */ hh_dev_info.irq_fifo_head = 0; hh_dev_info.irq_fifo_tail = 0; ret = request_irq(INT_HH_SUPSS_IRQ, hh_irq_handler, IRQF_TRIGGER_RISING, "hh_dev", 0); if (ret < 0) { pr_err("Cannot register HH interrupt handler.\n"); return ret; } /* wait queue */ init_waitqueue_head(&hh_dev_info.wq); return misc_register(&hh_misc_dev); } static void __exit hh_exit(void) { misc_deregister(&hh_misc_dev); free_irq(INT_HH_SUPSS_IRQ, 0); } MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Rohit Vaswani "); MODULE_DESCRIPTION("Qualcomm Hammerhead Digital Front End driver"); MODULE_VERSION("1.0"); module_init(hh_init); module_exit(hh_exit);