| /* |
| * 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 <dspbridge/host_os.h> |
| #include <linux/types.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm.h> |
| |
| #ifdef MODULE |
| #include <linux/module.h> |
| #endif |
| |
| #include <linux/device.h> |
| #include <linux/init.h> |
| #include <linux/moduleparam.h> |
| #include <linux/cdev.h> |
| |
| /* ----------------------------------- DSP/BIOS Bridge */ |
| #include <dspbridge/dbdefs.h> |
| |
| /* ----------------------------------- Trace & Debug */ |
| #include <dspbridge/dbc.h> |
| |
| /* ----------------------------------- OS Adaptation Layer */ |
| #include <dspbridge/services.h> |
| #include <dspbridge/clk.h> |
| #include <dspbridge/sync.h> |
| |
| /* ----------------------------------- Platform Manager */ |
| #include <dspbridge/dspapi-ioctl.h> |
| #include <dspbridge/dspapi.h> |
| #include <dspbridge/dspdrv.h> |
| |
| /* ----------------------------------- Resource Manager */ |
| #include <dspbridge/pwr.h> |
| |
| /* ----------------------------------- This */ |
| #include <drv_interface.h> |
| |
| #include <dspbridge/cfg.h> |
| #include <dspbridge/resourcecleanup.h> |
| #include <dspbridge/chnl.h> |
| #include <dspbridge/proc.h> |
| #include <dspbridge/dev.h> |
| #include <dspbridge/drvdefs.h> |
| #include <dspbridge/drv.h> |
| |
| #ifdef CONFIG_TIDSPBRIDGE_DVFS |
| #include <mach-omap2/omap3-opp.h> |
| #endif |
| |
| #define BRIDGE_NAME "C6410" |
| /* ----------------------------------- 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, |
| }; |
| |
| #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 dspbridge_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 dspbridge_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 dspbridge_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(); |
| services_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(); |
| services_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; |
| void *hdrv_obj = NULL; |
| |
| status = cfg_get_object((u32 *) &hdrv_obj, REG_DRV_OBJECT); |
| if (status) |
| 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(); |
| services_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 = BRIDGE_NAME, |
| }, |
| .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); |