blob: 3470710350052f023737acdacde95ce26d30bbf9 [file] [log] [blame]
/* Copyright (c) 2011-2013, 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 <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)
#define UINT32_MAX (0xFFFFFFFFU)
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(&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) ||
(param.num >= (UINT32_MAX / sizeof(unsigned int))))
return -EINVAL;
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) ||
(param.num >= (UINT32_MAX /
sizeof(struct dfe_command_entry))))
return -EINVAL;
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);