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(&param, 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(&param, 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(&param, 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(&param, 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);
+