blob: 9c08be1ffe8395c5e067bee1f9fa2e29c69b3662 [file] [log] [blame]
/*******************************************************************************
Copyright 2010 Broadcom Corporation. All rights reserved.
Unless you and Broadcom execute a separate written software license agreement
governing use of this software, this software is licensed to you under the
terms of the GNU General Public License version 2, available at
http://www.gnu.org/copyleft/gpl.html (the "GPL").
Notwithstanding the above, under no circumstances may you combine this software
in any way with any other Broadcom software provided under a license other than
the GPL, without Broadcom's express prior written consent.
*******************************************************************************/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/semaphore.h>
#include <linux/mutex.h>
#include <mach/irqs.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/bootmem.h>
#include <linux/spinlock_types.h>
#include <linux/broadcom/isp.h>
#include <mach/rdb/brcm_rdb_sysmap.h>
#include <mach/rdb/brcm_rdb_pwrmgr.h>
#include <mach/rdb/brcm_rdb_mm_rst_mgr_reg.h>
#include <mach/rdb/brcm_rdb_isp.h>
#include <plat/clock.h>
#include <plat/pi_mgr.h>
#include <plat/scu.h>
#include <linux/delay.h>
/* TODO - define the major device ID */
#define ISP_DEV_MAJOR 0
#define RHEA_ISP_BASE_PERIPHERAL_ADDRESS ISP_BASE_ADDR
#define RHEA_MM_CLK_BASE_ADDRESS MM_CLK_BASE_ADDR
#define IRQ_ISP (153+32)
/* #define ISP_DEBUG */
#ifdef ISP_DEBUG
#define dbg_print(fmt, arg...) \
printk(KERN_ALERT "%s():" fmt, __func__, ##arg)
#else
#define dbg_print(fmt, arg...) do { } while (0)
#endif
#define err_print(fmt, arg...) \
printk(KERN_ERR "%s():" fmt, __func__, ##arg)
static int isp_major = ISP_DEV_MAJOR;
int brcm_global_isp_in_use = 0;
DEFINE_MUTEX(brcm_global_isp_lock);
static struct class *isp_class;
static void __iomem *isp_base;
static void __iomem *mmclk_base;
static struct clk *isp_clk;
static int interrupt_irq;
static struct pi_mgr_dfs_node isp_dfs_node;
static struct pi_mgr_qos_node isp_qos_node;
struct isp_status_t {
unsigned int status;
};
struct isp_t {
struct completion irq_sem;
spinlock_t lock;
struct isp_status_t isp_status;
};
static int enable_isp_clock(void);
static void disable_isp_clock(void);
static inline unsigned int reg_read(void __iomem *, unsigned int reg);
static inline void reg_write(void __iomem *, unsigned int reg,
unsigned int value);
static irqreturn_t isp_isr(int irq, void *dev_id)
{
struct isp_t *dev;
unsigned long flags;
dev = (struct isp_t *) dev_id;
spin_lock_irqsave(&dev->lock, flags);
dev->isp_status.status = reg_read(isp_base, ISP_STATUS_OFFSET);
spin_unlock_irqrestore(&dev->lock, flags);
reg_write(isp_base, ISP_STATUS_OFFSET, dev->isp_status.status);
complete(&dev->irq_sem);
return IRQ_RETVAL(1);
}
static int isp_open(struct inode *inode, struct file *filp)
{
int ret = 0;
struct isp_t *dev = kmalloc(sizeof(struct isp_t), GFP_KERNEL);
if (!dev)
return -ENOMEM;
filp->private_data = dev;
spin_lock_init(&dev->lock);
mutex_lock(&brcm_global_isp_lock);
if (brcm_global_isp_in_use == 0) {
brcm_global_isp_in_use++;
} else {
mutex_unlock(&brcm_global_isp_lock);
kfree(dev);
pr_err("ISP already in use");
return -EBUSY;
}
mutex_unlock(&brcm_global_isp_lock);
dev->isp_status.status = 0;
init_completion(&dev->irq_sem);
ret =
pi_mgr_dfs_add_request(&isp_dfs_node, "isp", PI_MGR_PI_ID_MM,
PI_MGR_DFS_MIN_VALUE);
if (ret) {
printk(KERN_ERR "%s: failed to register PI DFS request\n",
__func__);
goto err;
}
ret = pi_mgr_qos_add_request(&isp_qos_node, "isp",
PI_MGR_PI_ID_ARM_CORE,
PI_MGR_QOS_DEFAULT_VALUE);
if (ret) {
printk(KERN_ERR "%s: failed to register PI QOS request\n",
__func__);
ret = -EIO;
goto qos_request_fail;
}
enable_isp_clock();
pi_mgr_qos_request_update(&isp_qos_node, 0);
#ifndef CONFIG_ARCH_JAVA
scu_standby(0);
#endif
ret =
request_irq(IRQ_ISP, isp_isr, IRQF_DISABLED | IRQF_SHARED,
ISP_DEV_NAME, dev);
if (ret) {
err_print("request_irq failed ret = %d\n", ret);
goto irq_request_fail;
}
/* Ensure that only one CORE handles interrupt for the MM block. */
irq_set_affinity(IRQ_ISP, cpumask_of(0));
disable_irq(IRQ_ISP);
return 0;
irq_request_fail:
pi_mgr_qos_request_remove(&isp_qos_node);
qos_request_fail:
pi_mgr_dfs_request_remove(&isp_dfs_node);
err:
kfree(dev);
return ret;
}
static int isp_release(struct inode *inode, struct file *filp)
{
struct isp_t *dev = (struct isp_t *) filp->private_data;
free_irq(IRQ_ISP, dev);
pi_mgr_qos_request_update(&isp_qos_node, PI_MGR_QOS_DEFAULT_VALUE);
#ifndef CONFIG_ARCH_JAVA
scu_standby(1);
#endif
disable_isp_clock();
if (pi_mgr_dfs_request_update(&isp_dfs_node, PI_MGR_DFS_MIN_VALUE)) {
printk(KERN_ERR "%s: failed to update dfs request for isp\n",
__func__);
}
pi_mgr_dfs_request_remove(&isp_dfs_node);
isp_dfs_node.name = NULL;
pi_mgr_qos_request_remove(&isp_qos_node);
isp_qos_node.name = NULL;
mutex_lock(&brcm_global_isp_lock);
brcm_global_isp_in_use--;
mutex_unlock(&brcm_global_isp_lock);
kfree(dev);
return 0;
}
static int isp_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long vma_size = vma->vm_end - vma->vm_start;
if (vma_size & (~PAGE_MASK)) {
pr_err(KERN_ERR
"isp_mmap: mmaps must be aligned to " \
"a multiple of pages_size.\n");
return -EINVAL;
}
if (!vma->vm_pgoff)
vma->vm_pgoff = RHEA_ISP_BASE_PERIPHERAL_ADDRESS >> PAGE_SHIFT;
else
return -EINVAL;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */
if (remap_pfn_range(vma,
vma->vm_start,
vma->vm_pgoff, vma_size, vma->vm_page_prot)) {
pr_err("%s(): remap_pfn_range() failed\n", __func__);
return -EINVAL;
}
return 0;
}
static long isp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct isp_t *dev;
int ret = 0;
if (_IOC_TYPE(cmd) != BCM_ISP_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > ISP_CMD_LAST)
return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ)
ret = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
if (_IOC_DIR(cmd) & _IOC_WRITE)
ret |= !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
if (ret)
return -EFAULT;
dev = (struct isp_t *) (filp->private_data);
switch (cmd) {
case ISP_IOCTL_WAIT_IRQ:
{
interrupt_irq = 0;
enable_irq(IRQ_ISP);
dbg_print("Waiting for interrupt\n");
if (wait_for_completion_interruptible(&dev->irq_sem)) {
disable_irq(IRQ_ISP);
return -ERESTARTSYS;
}
if (copy_to_user
((u32 *) arg, &dev->isp_status,
sizeof(dev->isp_status))) {
err_print("ISP_IOCTL_WAIT_IRQ " \
"copy_to_user failed\n");
ret = -EFAULT;
}
dbg_print("Disabling ISP interrupt\n");
disable_irq(IRQ_ISP);
if (interrupt_irq)
return -EIO;
}
break;
case ISP_IOCTL_RELEASE_IRQ:
{
interrupt_irq = 1;
dbg_print("Interrupting irq ioctl\n");
complete(&dev->irq_sem);
}
break;
case ISP_IOCTL_CLK_RESET:
{
struct clk *clk;
clk = clk_get(NULL, "isp_axi_clk");
if (!IS_ERR_OR_NULL(clk)) {
dbg_print("reset ISP clock\n");
clk_reset(clk);
/* sleep for 1ms */
usleep_range(1000, 2000);
} else {
err_print("%s: error get clock\n", __func__);
ret = -EIO;
}
}
default:
break;
}
return ret;
}
static const struct file_operations isp_fops = {
.open = isp_open,
.release = isp_release,
.mmap = isp_mmap,
.unlocked_ioctl = isp_ioctl
};
static int enable_isp_clock(void)
{
unsigned long rate;
int ret;
isp_clk = clk_get(NULL, "isp_axi_clk");
if (IS_ERR_OR_NULL(isp_clk)) {
err_print("%s: error get clock\n", __func__);
return -EIO;
}
#if defined(CONFIG_PI_MGR_MM_STURBO_ENABLE)
if (pi_mgr_dfs_request_update(&isp_dfs_node, PI_OPP_SUPER_TURBO)) {
printk(KERN_ERR "%s:failed to update dfs request for isp\n",
__func__);
return -1;
}
#else
if (pi_mgr_dfs_request_update(&isp_dfs_node, PI_OPP_TURBO)) {
printk(KERN_ERR "%s:failed to update dfs request for isp\n",
__func__);
return -1;
}
#endif
ret = clk_enable(isp_clk);
if (ret) {
err_print("%s: error enable ISP clock\n", __func__);
return -EIO;
}
/*
ret = clk_set_rate(isp_clk, 249600000);
if (ret) {
err_print("%s: error changing clock rate\n", __func__);
return -EIO;
}
*/
rate = clk_get_rate(isp_clk);
dbg_print("isp_clk_clk rate %lu\n", rate);
dbg_print("mmclk policy status 08:%08x 0c:%08x 10:%08x 14:%08x " \
"18:%08x 1c:%08x ec0:%08x\n",
reg_read(mmclk_base, 0x08), reg_read(mmclk_base, 0x0c),
reg_read(mmclk_base, 0x10), reg_read(mmclk_base, 0x14),
reg_read(mmclk_base, 0x18), reg_read(mmclk_base, 0x1c),
reg_read(mmclk_base, 0xec0));
return ret;
}
static void disable_isp_clock(void)
{
isp_clk = clk_get(NULL, "isp_axi_clk");
if (IS_ERR_OR_NULL(isp_clk))
return;
clk_disable(isp_clk);
}
static inline unsigned int reg_read(void __iomem *base_addr, unsigned int reg)
{
unsigned int flags;
flags = ioread32(base_addr + reg);
return flags;
}
static inline void reg_write(void __iomem *base_addr, unsigned int reg,
unsigned int value)
{
iowrite32(value, base_addr + reg);
}
int __init isp_init(void)
{
int ret;
struct device *isp_dev;
dbg_print("ISP driver Init\n");
ret = register_chrdev(0, ISP_DEV_NAME, &isp_fops);
if (ret < 0)
return -EINVAL;
else
isp_major = ret;
isp_class = class_create(THIS_MODULE, ISP_DEV_NAME);
if (IS_ERR(isp_class)) {
err_print("Failed to create ISP class\n");
unregister_chrdev(isp_major, ISP_DEV_NAME);
return PTR_ERR(isp_class);
}
isp_dev = device_create(isp_class, NULL, MKDEV(isp_major, 0),
NULL, ISP_DEV_NAME);
if (IS_ERR(isp_dev)) {
err_print("Failed to create ISP device\n");
goto err;
}
/* Map the ISP registers */
isp_base =
(void __iomem *)ioremap_nocache(RHEA_ISP_BASE_PERIPHERAL_ADDRESS,
SZ_512K);
if (isp_base == NULL)
goto err2;
/* Map the MM CLK registers */
mmclk_base =
(void __iomem *)ioremap_nocache(RHEA_MM_CLK_BASE_ADDRESS, SZ_4K);
if (mmclk_base == NULL)
goto err3;
return 0;
err3:
iounmap(isp_base);
err2:
err_print("Failed to MAP the ISP IO space\n");
device_destroy(isp_class, MKDEV(isp_major, 0));
err:
class_destroy(isp_class);
unregister_chrdev(isp_major, ISP_DEV_NAME);
return ret;
}
void __exit isp_exit(void)
{
dbg_print("ISP driver Exit\n");
if (isp_base)
iounmap(isp_base);
if (mmclk_base)
iounmap(mmclk_base);
device_destroy(isp_class, MKDEV(isp_major, 0));
class_destroy(isp_class);
unregister_chrdev(isp_major, ISP_DEV_NAME);
}
module_init(isp_init);
module_exit(isp_exit);
MODULE_AUTHOR("Broadcom Corporation");
MODULE_DESCRIPTION("ISP device driver");
MODULE_LICENSE("GPL");