Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/dfe-fsm9xxx.c b/arch/arm/mach-msm/dfe-fsm9xxx.c
new file mode 100644
index 0000000..d372171
--- /dev/null
+++ b/arch/arm/mach-msm/dfe-fsm9xxx.c
@@ -0,0 +1,436 @@
+/* Copyright (c) 2011, Code Aurora Forum. 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <mach/msm_iomap.h>
+
+#include <linux/fsm_dfe_hh.h>
+
+/*
+ * 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_IS_UMTS:
+ return __raw_readl(MSM_TCSR_BASE + 0x0008) & 0x01;
+
+ 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 <rvaswani@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm Hammerhead Digital Front End driver");
+MODULE_VERSION("1.0");
+
+module_init(hh_init);
+module_exit(hh_exit);
+