/* * drv_interface.c * * DSP-BIOS Bridge driver support functions for TI OMAP processors. * * DSP/BIOS Bridge driver interface. * * Copyright (C) 2005-2006 Texas Instruments, Inc. * * This package is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* ----------------------------------- Host OS */ #include #include #include #include #include #ifdef MODULE #include #endif #include #include #include #include /* ----------------------------------- DSP/BIOS Bridge */ #include /* ----------------------------------- Trace & Debug */ #include /* ----------------------------------- OS Adaptation Layer */ #include #include /* ----------------------------------- Platform Manager */ #include #include #include /* ----------------------------------- Resource Manager */ #include /* ----------------------------------- This */ #include #include #include #include #include #include #ifdef CONFIG_TIDSPBRIDGE_DVFS #include #endif /* ----------------------------------- Globals */ #define DRIVER_NAME "DspBridge" #define DSPBRIDGE_VERSION "0.3" s32 dsp_debug; struct platform_device *omap_dspbridge_dev; struct device *bridge; /* This is a test variable used by Bridge to test different sleep states */ s32 dsp_test_sleepstate; static struct cdev bridge_cdev; static struct class *bridge_class; static u32 driver_context; static s32 driver_major; static char *base_img; char *iva_img; static s32 shm_size = 0x500000; /* 5 MB */ static int tc_wordswapon; /* Default value is always false */ #ifdef CONFIG_TIDSPBRIDGE_RECOVERY #define REC_TIMEOUT 5000 /*recovery timeout in msecs */ static atomic_t bridge_cref; /* number of bridge open handles */ static struct workqueue_struct *bridge_rec_queue; static struct work_struct bridge_recovery_work; static DECLARE_COMPLETION(bridge_comp); static DECLARE_COMPLETION(bridge_open_comp); static bool recover; #endif #ifdef CONFIG_PM struct omap34_xx_bridge_suspend_data { int suspended; wait_queue_head_t suspend_wq; }; static struct omap34_xx_bridge_suspend_data bridge_suspend_data; static int omap34_xxbridge_suspend_lockout(struct omap34_xx_bridge_suspend_data *s, struct file *f) { if ((s)->suspended) { if ((f)->f_flags & O_NONBLOCK) return -EPERM; wait_event_interruptible((s)->suspend_wq, (s)->suspended == 0); } return 0; } #endif module_param(dsp_debug, int, 0); MODULE_PARM_DESC(dsp_debug, "Wait after loading DSP image. default = false"); module_param(dsp_test_sleepstate, int, 0); MODULE_PARM_DESC(dsp_test_sleepstate, "DSP Sleep state = 0"); module_param(base_img, charp, 0); MODULE_PARM_DESC(base_img, "DSP base image, default = NULL"); module_param(shm_size, int, 0); MODULE_PARM_DESC(shm_size, "shm size, default = 4 MB, minimum = 64 KB"); module_param(tc_wordswapon, int, 0); MODULE_PARM_DESC(tc_wordswapon, "TC Word Swap Option. default = 0"); MODULE_AUTHOR("Texas Instruments"); MODULE_LICENSE("GPL"); MODULE_VERSION(DSPBRIDGE_VERSION); static char *driver_name = DRIVER_NAME; static const struct file_operations bridge_fops = { .open = bridge_open, .release = bridge_release, .unlocked_ioctl = bridge_ioctl, .mmap = bridge_mmap, .llseek = noop_llseek, }; #ifdef CONFIG_PM static u32 time_out = 1000; #ifdef CONFIG_TIDSPBRIDGE_DVFS s32 dsp_max_opps = VDD1_OPP5; #endif /* Maximum Opps that can be requested by IVA */ /*vdd1 rate table */ #ifdef CONFIG_TIDSPBRIDGE_DVFS const struct omap_opp vdd1_rate_table_bridge[] = { {0, 0, 0}, /*OPP1 */ {S125M, VDD1_OPP1, 0}, /*OPP2 */ {S250M, VDD1_OPP2, 0}, /*OPP3 */ {S500M, VDD1_OPP3, 0}, /*OPP4 */ {S550M, VDD1_OPP4, 0}, /*OPP5 */ {S600M, VDD1_OPP5, 0}, }; #endif #endif struct omap_dsp_platform_data *omap_dspbridge_pdata; u32 vdd1_dsp_freq[6][4] = { {0, 0, 0, 0}, /*OPP1 */ {0, 90000, 0, 86000}, /*OPP2 */ {0, 180000, 80000, 170000}, /*OPP3 */ {0, 360000, 160000, 340000}, /*OPP4 */ {0, 396000, 325000, 376000}, /*OPP5 */ {0, 430000, 355000, 430000}, }; #ifdef CONFIG_TIDSPBRIDGE_RECOVERY static void bridge_recover(struct work_struct *work) { struct dev_object *dev; struct cfg_devnode *dev_node; if (atomic_read(&bridge_cref)) { INIT_COMPLETION(bridge_comp); while (!wait_for_completion_timeout(&bridge_comp, msecs_to_jiffies(REC_TIMEOUT))) pr_info("%s:%d handle(s) still opened\n", __func__, atomic_read(&bridge_cref)); } dev = dev_get_first(); dev_get_dev_node(dev, &dev_node); if (!dev_node || proc_auto_start(dev_node, dev)) pr_err("DSP could not be restarted\n"); recover = false; complete_all(&bridge_open_comp); } void bridge_recover_schedule(void) { INIT_COMPLETION(bridge_open_comp); recover = true; queue_work(bridge_rec_queue, &bridge_recovery_work); } #endif #ifdef CONFIG_TIDSPBRIDGE_DVFS static int dspbridge_scale_notification(struct notifier_block *op, unsigned long val, void *ptr) { struct omap_dsp_platform_data *pdata = omap_dspbridge_dev->dev.platform_data; if (CPUFREQ_POSTCHANGE == val && pdata->dsp_get_opp) pwr_pm_post_scale(PRCM_VDD1, pdata->dsp_get_opp()); return 0; } static struct notifier_block iva_clk_notifier = { .notifier_call = dspbridge_scale_notification, NULL, }; #endif /** * omap3_bridge_startup() - perform low lever initializations * @pdev: pointer to platform device * * Initializes recovery, PM and DVFS required data, before calling * clk and memory init routines. */ static int omap3_bridge_startup(struct platform_device *pdev) { struct omap_dsp_platform_data *pdata = pdev->dev.platform_data; struct drv_data *drv_datap = NULL; u32 phys_membase, phys_memsize; int err; #ifdef CONFIG_TIDSPBRIDGE_RECOVERY bridge_rec_queue = create_workqueue("bridge_rec_queue"); INIT_WORK(&bridge_recovery_work, bridge_recover); INIT_COMPLETION(bridge_comp); #endif #ifdef CONFIG_PM /* Initialize the wait queue */ bridge_suspend_data.suspended = 0; init_waitqueue_head(&bridge_suspend_data.suspend_wq); #ifdef CONFIG_TIDSPBRIDGE_DVFS for (i = 0; i < 6; i++) pdata->mpu_speed[i] = vdd1_rate_table_bridge[i].rate; err = cpufreq_register_notifier(&iva_clk_notifier, CPUFREQ_TRANSITION_NOTIFIER); if (err) pr_err("%s: clk_notifier_register failed for iva2_ck\n", __func__); #endif #endif dsp_clk_init(); drv_datap = kzalloc(sizeof(struct drv_data), GFP_KERNEL); if (!drv_datap) { err = -ENOMEM; goto err1; } drv_datap->shm_size = shm_size; drv_datap->tc_wordswapon = tc_wordswapon; if (base_img) { drv_datap->base_img = kmalloc(strlen(base_img) + 1, GFP_KERNEL); if (!drv_datap->base_img) { err = -ENOMEM; goto err2; } strncpy(drv_datap->base_img, base_img, strlen(base_img) + 1); } dev_set_drvdata(bridge, drv_datap); if (shm_size < 0x10000) { /* 64 KB */ err = -EINVAL; pr_err("%s: shm size must be at least 64 KB\n", __func__); goto err3; } dev_dbg(bridge, "%s: requested shm_size = 0x%x\n", __func__, shm_size); phys_membase = pdata->phys_mempool_base; phys_memsize = pdata->phys_mempool_size; if (phys_membase > 0 && phys_memsize > 0) mem_ext_phys_pool_init(phys_membase, phys_memsize); if (tc_wordswapon) dev_dbg(bridge, "%s: TC Word Swap is enabled\n", __func__); driver_context = dsp_init(&err); if (err) { pr_err("DSP Bridge driver initialization failed\n"); goto err4; } return 0; err4: mem_ext_phys_pool_release(); err3: kfree(drv_datap->base_img); err2: kfree(drv_datap); err1: #ifdef CONFIG_TIDSPBRIDGE_DVFS cpufreq_unregister_notifier(&iva_clk_notifier, CPUFREQ_TRANSITION_NOTIFIER); #endif dsp_clk_exit(); return err; } static int __devinit omap34_xx_bridge_probe(struct platform_device *pdev) { int err; dev_t dev = 0; #ifdef CONFIG_TIDSPBRIDGE_DVFS int i = 0; #endif omap_dspbridge_dev = pdev; /* Global bridge device */ bridge = &omap_dspbridge_dev->dev; /* Bridge low level initializations */ err = omap3_bridge_startup(pdev); if (err) goto err1; /* use 2.6 device model */ err = alloc_chrdev_region(&dev, 0, 1, driver_name); if (err) { pr_err("%s: Can't get major %d\n", __func__, driver_major); goto err1; } cdev_init(&bridge_cdev, &bridge_fops); bridge_cdev.owner = THIS_MODULE; err = cdev_add(&bridge_cdev, dev, 1); if (err) { pr_err("%s: Failed to add bridge device\n", __func__); goto err2; } /* udev support */ bridge_class = class_create(THIS_MODULE, "ti_bridge"); if (IS_ERR(bridge_class)) { pr_err("%s: Error creating bridge class\n", __func__); goto err3; } driver_major = MAJOR(dev); device_create(bridge_class, NULL, MKDEV(driver_major, 0), NULL, "DspBridge"); pr_info("DSP Bridge driver loaded\n"); return 0; err3: cdev_del(&bridge_cdev); err2: unregister_chrdev_region(dev, 1); err1: return err; } static int __devexit omap34_xx_bridge_remove(struct platform_device *pdev) { dev_t devno; bool ret; int status = 0; struct drv_data *drv_datap = dev_get_drvdata(bridge); /* Retrieve the Object handle from the driver data */ if (!drv_datap || !drv_datap->drv_object) { status = -ENODATA; pr_err("%s: Failed to retrieve the object handle\n", __func__); goto func_cont; } #ifdef CONFIG_TIDSPBRIDGE_DVFS if (cpufreq_unregister_notifier(&iva_clk_notifier, CPUFREQ_TRANSITION_NOTIFIER)) pr_err("%s: cpufreq_unregister_notifier failed for iva2_ck\n", __func__); #endif /* #ifdef CONFIG_TIDSPBRIDGE_DVFS */ if (driver_context) { /* Put the DSP in reset state */ ret = dsp_deinit(driver_context); driver_context = 0; DBC_ASSERT(ret == true); } func_cont: mem_ext_phys_pool_release(); dsp_clk_exit(); devno = MKDEV(driver_major, 0); cdev_del(&bridge_cdev); unregister_chrdev_region(devno, 1); if (bridge_class) { /* remove the device from sysfs */ device_destroy(bridge_class, MKDEV(driver_major, 0)); class_destroy(bridge_class); } return 0; } #ifdef CONFIG_PM static int BRIDGE_SUSPEND(struct platform_device *pdev, pm_message_t state) { u32 status; u32 command = PWR_EMERGENCYDEEPSLEEP; status = pwr_sleep_dsp(command, time_out); if (status) return -1; bridge_suspend_data.suspended = 1; return 0; } static int BRIDGE_RESUME(struct platform_device *pdev) { u32 status; status = pwr_wake_dsp(time_out); if (status) return -1; bridge_suspend_data.suspended = 0; wake_up(&bridge_suspend_data.suspend_wq); return 0; } #else #define BRIDGE_SUSPEND NULL #define BRIDGE_RESUME NULL #endif static struct platform_driver bridge_driver = { .driver = { .name = "omap-dsp", }, .probe = omap34_xx_bridge_probe, .remove = __devexit_p(omap34_xx_bridge_remove), .suspend = BRIDGE_SUSPEND, .resume = BRIDGE_RESUME, }; static int __init bridge_init(void) { return platform_driver_register(&bridge_driver); } static void __exit bridge_exit(void) { platform_driver_unregister(&bridge_driver); } /* * This function is called when an application opens handle to the * bridge driver. */ static int bridge_open(struct inode *ip, struct file *filp) { int status = 0; struct process_context *pr_ctxt = NULL; /* * Allocate a new process context and insert it into global * process context list. */ #ifdef CONFIG_TIDSPBRIDGE_RECOVERY if (recover) { if (filp->f_flags & O_NONBLOCK || wait_for_completion_interruptible(&bridge_open_comp)) return -EBUSY; } #endif pr_ctxt = kzalloc(sizeof(struct process_context), GFP_KERNEL); if (pr_ctxt) { pr_ctxt->res_state = PROC_RES_ALLOCATED; spin_lock_init(&pr_ctxt->dmm_map_lock); INIT_LIST_HEAD(&pr_ctxt->dmm_map_list); spin_lock_init(&pr_ctxt->dmm_rsv_lock); INIT_LIST_HEAD(&pr_ctxt->dmm_rsv_list); pr_ctxt->node_id = kzalloc(sizeof(struct idr), GFP_KERNEL); if (pr_ctxt->node_id) { idr_init(pr_ctxt->node_id); } else { status = -ENOMEM; goto err; } pr_ctxt->stream_id = kzalloc(sizeof(struct idr), GFP_KERNEL); if (pr_ctxt->stream_id) idr_init(pr_ctxt->stream_id); else status = -ENOMEM; } else { status = -ENOMEM; } err: filp->private_data = pr_ctxt; #ifdef CONFIG_TIDSPBRIDGE_RECOVERY if (!status) atomic_inc(&bridge_cref); #endif return status; } /* * This function is called when an application closes handle to the bridge * driver. */ static int bridge_release(struct inode *ip, struct file *filp) { int status = 0; struct process_context *pr_ctxt; if (!filp->private_data) { status = -EIO; goto err; } pr_ctxt = filp->private_data; flush_signals(current); drv_remove_all_resources(pr_ctxt); proc_detach(pr_ctxt); kfree(pr_ctxt); filp->private_data = NULL; err: #ifdef CONFIG_TIDSPBRIDGE_RECOVERY if (!atomic_dec_return(&bridge_cref)) complete(&bridge_comp); #endif return status; } /* This function provides IO interface to the bridge driver. */ static long bridge_ioctl(struct file *filp, unsigned int code, unsigned long args) { int status; u32 retval = 0; union trapped_args buf_in; DBC_REQUIRE(filp != NULL); #ifdef CONFIG_TIDSPBRIDGE_RECOVERY if (recover) { status = -EIO; goto err; } #endif #ifdef CONFIG_PM status = omap34_xxbridge_suspend_lockout(&bridge_suspend_data, filp); if (status != 0) return status; #endif if (!filp->private_data) { status = -EIO; goto err; } status = copy_from_user(&buf_in, (union trapped_args *)args, sizeof(union trapped_args)); if (!status) { status = api_call_dev_ioctl(code, &buf_in, &retval, filp->private_data); if (!status) { status = retval; } else { dev_dbg(bridge, "%s: IOCTL Failed, code: 0x%x " "status 0x%x\n", __func__, code, status); status = -1; } } err: return status; } /* This function maps kernel space memory to user space memory. */ static int bridge_mmap(struct file *filp, struct vm_area_struct *vma) { u32 offset = vma->vm_pgoff << PAGE_SHIFT; u32 status; DBC_ASSERT(vma->vm_start < vma->vm_end); vma->vm_flags |= VM_RESERVED | VM_IO; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); dev_dbg(bridge, "%s: vm filp %p offset %x start %lx end %lx page_prot " "%lx flags %lx\n", __func__, filp, offset, vma->vm_start, vma->vm_end, vma->vm_page_prot, vma->vm_flags); status = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot); if (status != 0) status = -EAGAIN; return status; } /* To remove all process resources before removing the process from the * process context list */ int drv_remove_all_resources(void *process_ctxt) { int status = 0; struct process_context *ctxt = (struct process_context *)process_ctxt; drv_remove_all_strm_res_elements(ctxt); drv_remove_all_node_res_elements(ctxt); drv_remove_all_dmm_res_elements(ctxt); ctxt->res_state = PROC_RES_FREED; return status; } /* Bridge driver initialization and de-initialization functions */ module_init(bridge_init); module_exit(bridge_exit);