/* * Copyright 2015 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: AMD * */ #include "dm_services_types.h" #include "dc.h" #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" /** * DOC: overview * * DM provides another layer of IRQ management on top of what the base driver * already provides. This is something that could be cleaned up, and is a * future TODO item. * * The base driver provides IRQ source registration with DRM, handler * registration into the base driver's IRQ table, and a handler callback * amdgpu_irq_handler(), with which DRM calls on interrupts. This generic * handler looks up the IRQ table, and calls the respective * &amdgpu_irq_src_funcs.process hookups. * * What DM provides on top are two IRQ tables specifically for top-half and * bottom-half IRQ handling, with the bottom-half implementing workqueues: * * - &amdgpu_display_manager.irq_handler_list_high_tab * - &amdgpu_display_manager.irq_handler_list_low_tab * * They override the base driver's IRQ table, and the effect can be seen * in the hooks that DM provides for &amdgpu_irq_src_funcs.process. They * are all set to the DM generic handler amdgpu_dm_irq_handler(), which looks up * DM's IRQ tables. However, in order for base driver to recognize this hook, DM * still needs to register the IRQ with the base driver. See * dce110_register_irq_handlers() and dcn10_register_irq_handlers(). * * To expose DC's hardware interrupt toggle to the base driver, DM implements * &amdgpu_irq_src_funcs.set hooks. Base driver calls it through * amdgpu_irq_update() to enable or disable the interrupt. */ /****************************************************************************** * Private declarations. *****************************************************************************/ /** * struct amdgpu_dm_irq_handler_data - Data for DM interrupt handlers. * * @list: Linked list entry referencing the next/previous handler * @handler: Handler function * @handler_arg: Argument passed to the handler when triggered * @dm: DM which this handler belongs to * @irq_source: DC interrupt source that this handler is registered for * @work: work struct */ struct amdgpu_dm_irq_handler_data { … }; #define DM_IRQ_TABLE_LOCK(adev, flags) … #define DM_IRQ_TABLE_UNLOCK(adev, flags) … /****************************************************************************** * Private functions. *****************************************************************************/ static void init_handler_common_data(struct amdgpu_dm_irq_handler_data *hcd, void (*ih)(void *), void *args, struct amdgpu_display_manager *dm) { … } /** * dm_irq_work_func() - Handle an IRQ outside of the interrupt handler proper. * * @work: work struct */ static void dm_irq_work_func(struct work_struct *work) { … } /* * Remove a handler and return a pointer to handler list from which the * handler was removed. */ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, void *ih, const struct dc_interrupt_params *int_params) { … } /** * unregister_all_irq_handlers() - Cleans up handlers from the DM IRQ table * @adev: The base driver device containing the DM device * * Go through low and high context IRQ tables and deallocate handlers. */ static void unregister_all_irq_handlers(struct amdgpu_device *adev) { … } static bool validate_irq_registration_params(struct dc_interrupt_params *int_params, void (*ih)(void *)) { … } static bool validate_irq_unregistration_params(enum dc_irq_source irq_source, irq_handler_idx handler_idx) { … } /****************************************************************************** * Public functions. * * Note: caller is responsible for input validation. *****************************************************************************/ /** * amdgpu_dm_irq_register_interrupt() - Register a handler within DM. * @adev: The base driver device containing the DM device. * @int_params: Interrupt parameters containing the source, and handler context * @ih: Function pointer to the interrupt handler to register * @handler_args: Arguments passed to the handler when the interrupt occurs * * Register an interrupt handler for the given IRQ source, under the given * context. The context can either be high or low. High context handlers are * executed directly within ISR context, while low context is executed within a * workqueue, thereby allowing operations that sleep. * * Registered handlers are called in a FIFO manner, i.e. the most recently * registered handler will be called first. * * Return: Handler data &struct amdgpu_dm_irq_handler_data containing the IRQ * source, handler function, and args */ void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev, struct dc_interrupt_params *int_params, void (*ih)(void *), void *handler_args) { … } /** * amdgpu_dm_irq_unregister_interrupt() - Remove a handler from the DM IRQ table * @adev: The base driver device containing the DM device * @irq_source: IRQ source to remove the given handler from * @ih: Function pointer to the interrupt handler to unregister * * Go through both low and high context IRQ tables, and find the given handler * for the given irq source. If found, remove it. Otherwise, do nothing. */ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, enum dc_irq_source irq_source, void *ih) { … } /** * amdgpu_dm_irq_init() - Initialize DM IRQ management * @adev: The base driver device containing the DM device * * Initialize DM's high and low context IRQ tables. * * The N by M table contains N IRQ sources, with M * &struct amdgpu_dm_irq_handler_data hooked together in a linked list. The * list_heads are initialized here. When an interrupt n is triggered, all m * handlers are called in sequence, FIFO according to registration order. * * The low context table requires special steps to initialize, since handlers * will be deferred to a workqueue. See &struct irq_list_head. */ int amdgpu_dm_irq_init(struct amdgpu_device *adev) { … } /** * amdgpu_dm_irq_fini() - Tear down DM IRQ management * @adev: The base driver device containing the DM device * * Flush all work within the low context IRQ table. */ void amdgpu_dm_irq_fini(struct amdgpu_device *adev) { … } int amdgpu_dm_irq_suspend(struct amdgpu_device *adev) { … } int amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) { … } int amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) { … } /* * amdgpu_dm_irq_schedule_work - schedule all work items registered for the * "irq_source". */ static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { … } /* * amdgpu_dm_irq_immediate_work * Callback high irq work immediately, don't send to work queue */ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { … } /** * amdgpu_dm_irq_handler - Generic DM IRQ handler * @adev: amdgpu base driver device containing the DM device * @source: Unused * @entry: Data about the triggered interrupt * * Calls all registered high irq work immediately, and schedules work for low * irq. The DM IRQ table is used to find the corresponding handlers. */ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry) { … } static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) { … } static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) { … } static inline int dm_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state, const enum irq_type dal_irq_type, const char *func) { … } static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) { … } static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) { … } static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) { … } static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) { … } static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) { … } static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) { … } static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = …; static const struct amdgpu_irq_src_funcs dm_vline0_irq_funcs = …; static const struct amdgpu_irq_src_funcs dm_dmub_outbox_irq_funcs = …; static const struct amdgpu_irq_src_funcs dm_vupdate_irq_funcs = …; static const struct amdgpu_irq_src_funcs dm_dmub_trace_irq_funcs = …; static const struct amdgpu_irq_src_funcs dm_pageflip_irq_funcs = …; static const struct amdgpu_irq_src_funcs dm_hpd_irq_funcs = …; void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) { … } void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { … } /** * amdgpu_dm_hpd_init - hpd setup callback. * * @adev: amdgpu_device pointer * * Setup the hpd pins used by the card (evergreen+). * Enable the pin, set the polarity, and enable the hpd interrupts. */ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) { … } /** * amdgpu_dm_hpd_fini - hpd tear down callback. * * @adev: amdgpu_device pointer * * Tear down the hpd pins used by the card (evergreen+). * Disable the hpd interrupts. */ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) { … }