/* * File Name: aep_core.c */ /* This file is provided under a dual BSD/GPLv2 license. When using or redistributing this file, you may do so under either license. GPL LICENSE SUMMARY Copyright(c) 2007-2012 Intel Corporation. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. The full GNU General Public License is included in this distribution in the file called LICENSE.GPL. Contact Information: Intel Corporation 2200 Mission College Blvd. Santa Clara, CA 97052 */ #include #include "aep_core.h" #include "aep_host_ipc.h" #include "aep_host_ipc_defs.h" static aep_result_t aep_set_power_state(power_state_t requested_state); static bool aep_set_hardware_power_state(power_state_t requested_state); /** container for variables global to the driver. */ aep_globals_t aep_globals; /** TODO: document global variable aep_init_flag */ bool aep_init_flag; /** TODO: document global variable aep_global_log_level */ int AEP_global_log_level = AEP_LOG_ERROR; /* * Get AEP F/W Status. * * bool aep_is_active(void) * * Description: * This API is used to check if the AEP F/W is exist and running. * On A0 or B0 parts there is no AEP IP. The API will return false * On C0 part and above the AEP can be active or not active, * depend on the product design. * * Precondition: * This API can be call with out any precondition. * * Parameters: * None. * * Return: * false on Not Active * true on Active. */ bool aep_is_active(void) { return aep_init_flag; } EXPORT_SYMBOL(aep_is_active); /* * Determine if the hardware device is currently suspended. This function is * for use by generic APIs. */ bool aep_hw_device_suspended(void) { bool result = false; if (aep_globals.power.state != D0) result = true; return result; } /* * Work Queue worker function. * * void aep_workq_func(struct work_struct *work) * * Description: * This is a work queue worker function, called when ever there is new AEP IPC message to proccess. * * Precondition: * This function called in Work Queue Context * * Parameters: * work_struct - The calling WQ structure. * * Return: * none. */ void aep_workq_func(struct work_struct *work) { char msg[AEP_ATM_MAILBOX_SIZE]; aep_ipc_sizes_t io_sizes; aep_work_t *workq = (aep_work_t *)work; int cmd_id = 0; memset(&io_sizes, 0x0, sizeof(aep_ipc_sizes_t)); if (workq->ipc_cmd != 0) { io_sizes.ipl_size = sizeof(ipl_gpio_read_write_t); if (AEP_IPC_RET_SUCCESS != hostIPC_SendCmd(workq->hipc, workq->ipc_cmd, io_sizes, (ipl_t *)workq->ipl, NULL)) { printk(KERN_ERR "AEP: Error - aep_send_command(cmd_id = %d) failed\n", workq->ipc_cmd); kfree((void *)workq->ipl); kfree((void *)work); return; } kfree((void *)workq->ipl); kfree((void *)work); } else { /* Get Command ID */ /* Note: we need to store command_id locally, becasue after we set the 'Done' Bit, we can get new interrupt */ cmd_id = readl(workq->aep_io_addr + REG_HOST_DBL); /* Read the payload from the mailbox, and set 'Done' bit */ if (AEP_SUCCESS != aep_host_read_ipc_msg(msg, 0, AEP_ATM_MAILBOX_SIZE)) { printk(KERN_ERR "AEP: Error - aep_recv_command failed\n"); return; } io_sizes.ipl_size = AEP_ATM_MAILBOX_SIZE; /* Write payload to mailbox and send 'Complete' response */ if (AEP_SUCCESS != aep_host_send_response( AEP_IPC_CMD_ID_COMPLETE_FLAG|cmd_id, io_sizes, (ipl_t *)msg)) { printk(KERN_ERR "AEP: Error - aep_send_command(cmd_id = %d) failed\n", AEP_IPC_CMD_ID_COMPLETE_FLAG|cmd_id); return; } } return; } static irqreturn_t aep_intr_func(int irq, void *dev_id) { irqreturn_t result; aep_globals_t *aep_host = dev_id; /* Check if AEP has sent a message*/ if (readl(aep_globals.io_addr + CONFIG_HOST_IPC_REGISTER_STATUS_OFFSET) == 0) { aep_host_cmd_t cmd_type; /** Read and clear interrupt type */ cmd_type = readl(aep_globals.io_addr + REG_HOST_DBL); /* Check if FW has sent Cmd Done Signal*/ if (cmd_type & AEP_IPC_CMD_ID_COMPLETE_FLAG) { /** Wake up sync message event */ tasklet_schedule(&aep_host->finish_tasklet); } else if (AEP_FW_NP_REQUEST == cmd_type) { /*Todo */ } result = IRQ_HANDLED; goto out; } result = IRQ_NONE; out: return result; } /* function to disable AEP IPC interrupts */ aep_result_t aep_disable_ipc_interrupts(void) { unsigned int interrupt_enable; AEP_FUNC_ENTER(); /* read and disable the AEP host interrupt */ interrupt_enable = readl(aep_globals.io_addr + PV_TO_HOST_INTERRUPT_ENABLE); writel( interrupt_enable & ~AEP_HOST_INTERRUPT_ENABLE_HOST_Doorbell_Event, aep_globals.io_addr + PV_TO_HOST_INTERRUPT_ENABLE ); AEP_FUNC_EXIT(); return AEP_SUCCESS; } /* function to enable AEP IPC interrupts */ aep_result_t aep_enable_ipc_interrupts(int irq_num) { aep_result_t ret_val = AEP_SUCCESS; unsigned int interrupt_enable; int ret; AEP_FUNC_ENTER(); if (AEP_SUCCESS != aep_disable_ipc_interrupts()) LOG_ERROR("could not disable int\n"); /* register interrupt */ ret = request_irq(irq_num, aep_intr_func, IRQF_SHARED, "AEP_ISR", &aep_globals); if (ret) { LOG_ERROR("could not map IRQ\n"); ret_val = AEP_ERROR_NO_RESOURCES; } interrupt_enable = readl( aep_globals.io_addr + PV_TO_HOST_INTERRUPT_ENABLE ); writel(interrupt_enable | AEP_HOST_INTERRUPT_ENABLE_HOST_Doorbell_Event, aep_globals.io_addr + PV_TO_HOST_INTERRUPT_ENABLE); AEP_FUNC_EXIT(); return ret_val; } aep_result_t aep_unload_fw_dependent_components(void) { aep_result_t result; AEP_FUNC_ENTER(); LOG_DEBUG("disabling ipc interrupts\n"); result = aep_disable_ipc_interrupts(); if (result != AEP_SUCCESS) LOG_ERROR("%d received from disable interrupts\n", result); AEP_FUNC_EXIT(); return AEP_SUCCESS; } /** @brief starts communication with FW @retval AEP_SUCCESS : The communication was successfully started. @retval AEP_ERROR_NO_RESOURCES: There are no system resources available. */ aep_result_t aep_init_fw_comm(void) { aep_result_t rc = AEP_ERROR_NO_RESOURCES; rc = aep_host_ipc_initialize( &aep_globals.finish_tasklet, aep_globals.io_addr ); if (AEP_SUCCESS != rc) goto exit; rc = aep_enable_ipc_interrupts(aep_globals.irq); if (rc != AEP_SUCCESS) { LOG_ERROR("initializing AEP interrupts\n"); goto exit; } rc = AEP_SUCCESS; exit: AEP_FUNC_EXIT(); return rc; } static void aep_exit(void) { LOG_DEBUG("Unlocad fw dependent components"); aep_unload_fw_dependent_components(); aep_host_ipc_deinitialize(); } static int __devinit intelce_aep_probe( struct pci_dev *pdev, const struct pci_device_id *pci_id ) { aep_result_t result = AEP_ERROR_OPERATION_FAILED; int rc = -1; unsigned int id, rev; uint32_t fw_ver; int ret; aep_init_flag = false; intelce_get_soc_info(&id, &rev); if ((id != CE2600_SOC_DEVICE_ID) || (rev < C0_REV_ID)) return -ENODEV; ret = pci_enable_device(pdev); if (ret) { dev_err(&pdev->dev, "can't enable device.\n"); goto cleanup_return; } aep_globals.io_addr = pci_ioremap_bar(pdev, INTELCE_AEP_BAR); if (!aep_globals.io_addr) { dev_err(&pdev->dev, "failed to remap registers\n"); ret = -ENOMEM; goto cleanup_return; } if (!(readl(aep_globals.io_addr + AEP_PV_CONTROL_REG) & 0x1)) goto cleanup_return; aep_globals.irq = pdev->irq; result = aep_init_fw_comm(); if (AEP_SUCCESS != result) { LOG_ERROR("Initializing FW Communication %x", result); goto cleanup_return; } if (result != AEP_SUCCESS) { LOG_ERROR("initializing FW Dependent Components\n"); AEP_FUNC_EXIT(); return result; } /* Create work queue for interrupt handler*/ aep_globals.aep_work_queue = create_workqueue("AEP_Workqueue"); if (aep_globals.aep_work_queue == NULL) { printk(KERN_ERR "AEP: Failed to create workqueue\n"); return -1; } aep_globals.aep_req_work.aep_io_addr = aep_globals.io_addr; /* indicate that the device has been initialized */ aep_init_flag = true; aep_get_fw(&fw_ver); rc = 0; cleanup_return: if (rc != 0) { pci_release_region(pdev, INTELCE_AEP_BAR); pci_disable_device(pdev); aep_exit(); } return rc; } static void intelce_aep_remove(struct pci_dev *pdev) { } /* * This is the suspend function we register with the Linux device model. * It gets called whenever the kernel requests a change to the suspended * state. */ int aep_suspend(struct device *dev) { int result = 0; if (AEP_SUCCESS != aep_set_power_state(D3)) result = -EBUSY; return result; } /* * This is the resume function we register with the Linux device model. * It gets called whenever the kernel requests a change to the resumed state. */ int aep_resume(struct device *dev) { return (AEP_SUCCESS == aep_set_power_state(D0)) ? 0 : -EBUSY; } /* * This is the implementation of both suspend and resume into a single * set_power_state function for simplicity. */ static aep_result_t aep_set_power_state(power_state_t requested_state) { aep_result_t result = AEP_ERROR_FEATURE_NOT_IMPLEMENTED; AEP_FUNC_ENTER(); /* If we're already in the requested state, there's nothing to do. */ if (aep_globals.power.state == requested_state) { result = AEP_SUCCESS; } else { /* Success, save the new power state, return success. */ /* Attempt to change to the new power state. */ if (D3 == requested_state) { /** Prepares the aep driver for power save mode. SendsSets flags to prevent API functions from interacting with the firmware. */ if (!aep_set_hardware_power_state(requested_state)) { result = AEP_ERROR_OPERATION_FAILED; } else { aep_globals.power.state = requested_state; result = AEP_SUCCESS; } } else if (D0 == requested_state) { /** Prepares the vidpproc driver for normal running mode. effectively loads all code that might try to interact with the firmware, and sets flags to allow API functions to interact with the firmware */ aep_globals.power.state = requested_state; result = AEP_SUCCESS; if (!aep_set_hardware_power_state(requested_state)) { result = AEP_ERROR_OPERATION_FAILED; } else { /*unsupported state*/ result = AEP_ERROR_INVALID_PARAMETER; } } } AEP_FUNC_EXIT(); return result; } /* Modify the power state of the hardware unit(s) controlled by this module. */ static bool aep_set_hardware_power_state(power_state_t requested_state) { /* * Send IPC to AEP that Atom is going into suspend mode. */ /* * If we are suspending, do whatever is needed to prevent software from * accessing hardware registers. This will include any I/O threads, and * any top- or bottom- half interrupt handlers, and any other threads that * call module APIs. The simplest way to accomplish this might be to * acquire a device lock, check the power state, and refuse to access the * hardware when the state is not D0. It is likely that hardware * device interrupts will need to be disabled/enabled here, too. */ bool result = false; aep_result_t aep_result; AEP_FUNC_ENTER(); if (D3 == requested_state) { /** Prepares the aep driver for power save mode. */ aep_result = aep_host_send_suspend_to_fw(); if (AEP_SUCCESS != aep_result) { LOG_ERROR("Send suspend IPC to AEP\n"); goto exit; } aep_disable_ipc_interrupts(); aep_host_ipc_deinitialize(); result = true; } else if (D0 == requested_state) { /** Restores the aep driver from power save mode. */ aep_result = aep_init_fw_comm(); if (AEP_SUCCESS != aep_result) { LOG_ERROR("Initializing FW Communication"); goto exit; } result = true; } else { LOG_ERROR("Unsupported Power state\n"); result = false; } exit: AEP_FUNC_EXIT(); return result; } static const struct dev_pm_ops intelce_aep_pm_ops = { .suspend = aep_suspend, .resume = aep_resume, }; static struct pci_device_id intelce_aep_pci_ids[] __devinitdata = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_INTELCE_AEP_DEVICE_ID) }, { 0, }, }; static struct pci_driver intelce_aep_driver = { .name = INTELCE_AEP_DRV_NAME, .id_table = intelce_aep_pci_ids, .probe = intelce_aep_probe, .remove = intelce_aep_remove, #ifdef CONFIG_PM .driver.pm = &intelce_aep_pm_ops, #endif }; static int __init intelce_aep_init(void) { return pci_register_driver(&intelce_aep_driver); } void __exit intelce_aep_exit(void) { pci_unregister_driver(&intelce_aep_driver); } MODULE_AUTHOR("Intel Corporation, (C) 2013 - All Rights Reserved"); MODULE_LICENSE("GPL v2"); module_init(intelce_aep_init); module_exit(intelce_aep_exit);