Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648// SPDX-License-Identifier: MIT/* * 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 "dc/dc_dmub_srv.h"#include "dc/dc_stat.h"
#include "amdgpu.h"#include "amdgpu_display.h"#include "amdgpu_dm.h"#include "amdgpu_dm_irq.h"#include "dm_helpers.h"#include "amdgpu_dm_crtc.h"#include "amdgpu_dm_hdcp.h"#include "amdgpu_dm_mst_types.h"#include "amdgpu_dm_dmub.h"#include "amdgpu_dm_trace.h"#include "link/protocols/link_dpcd.h"#include "link_service_types.h"#include "ivsrcid/ivsrcid_vislands30.h"#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h"#include "modules/inc/mod_freesync.h"#include <drm/drm_vblank.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 * amdgpu_dm_dce110_register_irq_handlers() and * amdgpu_dm_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 { struct list_head list; interrupt_handler handler; void *handler_arg;
struct amdgpu_display_manager *dm; /* DAL irq source which registered for this interrupt. */ enum dc_irq_source irq_source; struct work_struct work;};
#define DM_IRQ_TABLE_LOCK(adev, flags) \ spin_lock_irqsave(&adev->dm.irq_handler_list_table_lock, flags)
#define DM_IRQ_TABLE_UNLOCK(adev, flags) \ spin_unlock_irqrestore(&adev->dm.irq_handler_list_table_lock, 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){ hcd->handler = ih; hcd->handler_arg = args; hcd->dm = 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){ struct amdgpu_dm_irq_handler_data *handler_data = container_of(work, struct amdgpu_dm_irq_handler_data, work);
handler_data->handler(handler_data->handler_arg);
/* Call a DAL subcomponent which registered for interrupt notification * at INTERRUPT_LOW_IRQ_CONTEXT. * (The most common use is HPD interrupt) */}
/* * 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){ struct list_head *hnd_list; struct list_head *entry, *tmp; struct amdgpu_dm_irq_handler_data *handler; unsigned long irq_table_flags; bool handler_removed = false; enum dc_irq_source irq_source;
DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
irq_source = int_params->irq_source;
switch (int_params->int_context) { case INTERRUPT_HIGH_IRQ_CONTEXT: hnd_list = &adev->dm.irq_handler_list_high_tab[irq_source]; break; case INTERRUPT_LOW_IRQ_CONTEXT: default: hnd_list = &adev->dm.irq_handler_list_low_tab[irq_source]; break; }
list_for_each_safe(entry, tmp, hnd_list) {
handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, list);
if (handler == NULL) continue;
if (ih == handler->handler) { /* Found our handler. Remove it from the list. */ list_del(&handler->list); handler_removed = true; break; } }
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
if (!handler_removed) { /* Not necessarily an error - caller may not * know the context. */ return NULL; }
if (int_params->int_context == INTERRUPT_LOW_IRQ_CONTEXT) cancel_work_sync(&handler->work);
kfree(handler);
DRM_DEBUG_KMS( "DM_IRQ: removed irq handler: %p for: dal_src=%d, irq context=%d\n", ih, int_params->irq_source, int_params->int_context);
return hnd_list;}
static boolvalidate_irq_registration_params(struct dc_interrupt_params *int_params, void (*ih)(void *)){ if (NULL == int_params || NULL == ih) { DRM_ERROR("DM_IRQ: invalid input!\n"); return false; }
if (int_params->int_context >= INTERRUPT_CONTEXT_NUMBER) { DRM_ERROR("DM_IRQ: invalid context: %d!\n", int_params->int_context); return false; }
if (!DAL_VALID_IRQ_SRC_NUM(int_params->irq_source)) { DRM_ERROR("DM_IRQ: invalid irq_source: %d!\n", int_params->irq_source); return false; }
return true;}
static bool validate_irq_unregistration_params(enum dc_irq_source irq_source, irq_handler_idx handler_idx){ if (handler_idx == DAL_INVALID_IRQ_HANDLER_IDX) { DRM_ERROR("DM_IRQ: invalid handler_idx==NULL!\n"); return false; }
if (!DAL_VALID_IRQ_SRC_NUM(irq_source)) { DRM_ERROR("DM_IRQ: invalid irq_source:%d!\n", irq_source); return false; }
return true;}/****************************************************************************** * 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){ struct list_head *hnd_list; struct amdgpu_dm_irq_handler_data *handler_data; unsigned long irq_table_flags; enum dc_irq_source irq_source;
if (!validate_irq_registration_params(int_params, ih)) return DAL_INVALID_IRQ_HANDLER_IDX;
handler_data = kzalloc_obj(*handler_data); if (!handler_data) { DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); return DAL_INVALID_IRQ_HANDLER_IDX; }
init_handler_common_data(handler_data, ih, handler_args, &adev->dm);
irq_source = int_params->irq_source;
handler_data->irq_source = irq_source;
/* Lock the list, add the handler. */ DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
switch (int_params->int_context) { case INTERRUPT_HIGH_IRQ_CONTEXT: hnd_list = &adev->dm.irq_handler_list_high_tab[irq_source]; break; case INTERRUPT_LOW_IRQ_CONTEXT: default: hnd_list = &adev->dm.irq_handler_list_low_tab[irq_source]; INIT_WORK(&handler_data->work, dm_irq_work_func); break; }
list_add_tail(&handler_data->list, hnd_list);
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
/* This pointer will be stored by code which requested interrupt * registration. * The same pointer will be needed in order to unregister the * interrupt. */
DRM_DEBUG_KMS( "DM_IRQ: added irq handler: %p for: dal_src=%d, irq context=%d\n", handler_data, irq_source, int_params->int_context);
return handler_data;}EXPORT_IF_KUNIT(amdgpu_dm_irq_register_interrupt);
/** * 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){ struct list_head *handler_list; struct dc_interrupt_params int_params; int i;
if (!validate_irq_unregistration_params(irq_source, ih)) return;
memset(&int_params, 0, sizeof(int_params));
int_params.irq_source = irq_source;
for (i = 0; i < INTERRUPT_CONTEXT_NUMBER; i++) {
int_params.int_context = i;
handler_list = remove_irq_handler(adev, ih, &int_params);
if (handler_list != NULL) break; }
if (handler_list == NULL) { /* If we got here, it means we searched all irq contexts * for this irq source, but the handler was not found. */ DRM_ERROR( "DM_IRQ: failed to find irq handler:%p for irq_source:%d!\n", ih, irq_source); }}EXPORT_IF_KUNIT(amdgpu_dm_irq_unregister_interrupt);
/** * 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){ int src; struct list_head *lh;
DRM_DEBUG_KMS("DM_IRQ\n");
spin_lock_init(&adev->dm.irq_handler_list_table_lock); spin_lock_init(&adev->dm.irq_reg_lock);
adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", WQ_UNBOUND | WQ_HIGHPRI, 0);
if (!adev->dm.irq_wq) return -ENOMEM;
adev->dm.vmin_vmax_wq = alloc_workqueue("amdgpu_dm_vmin_vmax", WQ_UNBOUND, 0);
if (!adev->dm.vmin_vmax_wq) { destroy_workqueue(adev->dm.irq_wq); adev->dm.irq_wq = NULL; return -ENOMEM; }
for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { /* low context handler list init */ lh = &adev->dm.irq_handler_list_low_tab[src]; INIT_LIST_HEAD(lh); /* high context handler init */ INIT_LIST_HEAD(&adev->dm.irq_handler_list_high_tab[src]); }
return 0;}EXPORT_IF_KUNIT(amdgpu_dm_irq_init);
/** * amdgpu_dm_irq_fini() - Tear down DM IRQ management * @adev: The base driver device containing the DM device * * Removes all handlers from the IRQ tables under the spinlock, cancels * pending work items, and deallocates all handler data. */void amdgpu_dm_irq_fini(struct amdgpu_device *adev){ int src; LIST_HEAD(low_handlers); LIST_HEAD(high_handlers); struct list_head *entry, *tmp; struct amdgpu_dm_irq_handler_data *handler; unsigned long irq_table_flags;
DRM_DEBUG_KMS("DM_IRQ: releasing resources.\n");
for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
/* * Move all handlers from the low and high context tables to * temporary lists under the lock. This prevents the ISR from * finding them while we process them outside the lock. */ list_splice_init(&adev->dm.irq_handler_list_low_tab[src], &low_handlers); list_splice_init(&adev->dm.irq_handler_list_high_tab[src], &high_handlers);
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
/* * Cancel all pending work for the low-context handlers * outside the lock. cancel_work_sync() may sleep and waits * until any running work completes, preventing UAF. */ list_for_each_safe(entry, tmp, &low_handlers) { handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, list); cancel_work_sync(&handler->work); }
/* * High-context handlers are executed synchronously within ISR * context (see amdgpu_dm_irq_immediate_work()) and have no * work_struct, so there is no pending work to cancel here. * They will be freed along with low_handlers after the loop. */ }
/* Deallocate all handlers. */ list_for_each_safe(entry, tmp, &low_handlers) { handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, list); list_del(&handler->list); kfree(handler); }
list_for_each_safe(entry, tmp, &high_handlers) { handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, list); list_del(&handler->list); kfree(handler); }
if (adev->dm.vmin_vmax_wq) { destroy_workqueue(adev->dm.vmin_vmax_wq); adev->dm.vmin_vmax_wq = NULL; }
if (adev->dm.irq_wq) { destroy_workqueue(adev->dm.irq_wq); adev->dm.irq_wq = NULL; }}EXPORT_IF_KUNIT(amdgpu_dm_irq_fini);
void amdgpu_dm_irq_suspend(struct amdgpu_device *adev){ struct drm_device *dev = adev_to_drm(adev); int src; struct list_head *hnd_list_l; unsigned long irq_table_flags; struct list_head *entry, *tmp; struct amdgpu_dm_irq_handler_data *handler;
DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
DRM_DEBUG_KMS("DM_IRQ: suspend\n");
/** * Disable HW interrupt for HPD and HPDRX only since FLIP and VBLANK * will be disabled from manage_dm_interrupts on disable CRTC. * * Disable the HW interrupt first, then flush any pending work. Since * the HW interrupt is disabled under the lock, no new IRQ can be * generated after the disable completes. Any work already queued by an * in-flight ISR will be flushed below. */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; amdgpu_dm_irq_set(adev, src, false);
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
if (!list_empty(hnd_list_l)) { list_for_each_safe(entry, tmp, hnd_list_l) { handler = list_entry( entry, struct amdgpu_dm_irq_handler_data, list); flush_work(&handler->work); } } DM_IRQ_TABLE_LOCK(adev, irq_table_flags); }
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
if (dev->mode_config.poll_enabled) drm_kms_helper_poll_disable(dev);}EXPORT_IF_KUNIT(amdgpu_dm_irq_suspend);
void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev){ int src; struct list_head *hnd_list_h, *hnd_list_l; unsigned long irq_table_flags;
DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
drm_dbg(adev_to_drm(adev), "DM_IRQ: early resume\n");
/* re-enable short pulse interrupts HW interrupt */ for (src = DC_IRQ_SOURCE_HPD1RX; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) amdgpu_dm_irq_set(adev, src, true); }
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);}EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_early);
void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev){ struct drm_device *dev = adev_to_drm(adev); int src; struct list_head *hnd_list_h, *hnd_list_l; unsigned long irq_table_flags;
DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
drm_dbg(adev_to_drm(adev), "DM_IRQ: resume\n");
/** * Renable HW interrupt for HPD and only since FLIP and VBLANK * will be enabled from manage_dm_interrupts on enable CRTC. */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) amdgpu_dm_irq_set(adev, src, true); }
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
if (dev->mode_config.poll_enabled) drm_kms_helper_poll_enable(dev);}EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_late);
/* * amdgpu_dm_irq_schedule_work - schedule all work items registered for the * "irq_source". */STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, enum dc_irq_source irq_source){ struct list_head *handler_list = &adev->dm.irq_handler_list_low_tab[irq_source]; struct amdgpu_dm_irq_handler_data *handler_data; bool work_queued = false; unsigned long irq_table_flags;
DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
if (!adev->dm.irq_wq) goto out_unlock;
if (list_empty(handler_list)) goto out_unlock;
list_for_each_entry(handler_data, handler_list, list) { if (queue_work(adev->dm.irq_wq, &handler_data->work)) { work_queued = true; break; } }
if (!work_queued) { struct amdgpu_dm_irq_handler_data *handler_data_add; /*get the amdgpu_dm_irq_handler_data of first item pointed by handler_list*/ handler_data = container_of(handler_list->next, struct amdgpu_dm_irq_handler_data, list);
/*allocate a new amdgpu_dm_irq_handler_data*/ handler_data_add = kzalloc_obj(*handler_data, GFP_ATOMIC); if (!handler_data_add) { DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); goto out_unlock; }
/*copy new amdgpu_dm_irq_handler_data members from handler_data*/ handler_data_add->handler = handler_data->handler; handler_data_add->handler_arg = handler_data->handler_arg; handler_data_add->dm = handler_data->dm; handler_data_add->irq_source = irq_source;
list_add_tail(&handler_data_add->list, handler_list);
INIT_WORK(&handler_data_add->work, dm_irq_work_func);
if (queue_work(adev->dm.irq_wq, &handler_data_add->work)) DRM_DEBUG("Queued work for handling interrupt from " "display for IRQ source %d\n", irq_source); else DRM_ERROR("Failed to queue work for handling interrupt " "from display for IRQ source %d\n", irq_source); }
out_unlock: DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);}EXPORT_IF_KUNIT(amdgpu_dm_irq_schedule_work);
/* * amdgpu_dm_irq_immediate_work * Callback high irq work immediately, don't send to work queue */STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, enum dc_irq_source irq_source){ struct amdgpu_dm_irq_handler_data *handler_data; unsigned long irq_table_flags;
DM_IRQ_TABLE_LOCK(adev, irq_table_flags);
list_for_each_entry(handler_data, &adev->dm.irq_handler_list_high_tab[irq_source], list) { /* Call a subcomponent which registered for immediate * interrupt notification */ handler_data->handler(handler_data->handler_arg); }
DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);}EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work);
bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, bool enable){ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock);
return dc_interrupt_set(adev->dm.dc, src, enable);}EXPORT_IF_KUNIT(amdgpu_dm_irq_set);
void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src){ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock);
dc_interrupt_ack(adev->dm.dc, src);}EXPORT_IF_KUNIT(amdgpu_dm_irq_ack);
/** * 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_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry){
enum dc_irq_source src = dc_interrupt_to_irq_source( adev->dm.dc, entry->src_id, entry->src_data[0]);
if (!DAL_VALID_IRQ_SRC_NUM(src)) { DRM_ERROR("DM_IRQ: invalid irq_source:%d!\n", src); return 0; }
amdgpu_dm_irq_ack(adev, src);
/* Call high irq work immediately */ amdgpu_dm_irq_immediate_work(adev, src); /*Schedule low_irq work */ amdgpu_dm_irq_schedule_work(adev, src);
return 0;}EXPORT_IF_KUNIT(amdgpu_dm_irq_handler);
STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type){ switch (type) { case AMDGPU_HPD_1: return DC_IRQ_SOURCE_HPD1; case AMDGPU_HPD_2: return DC_IRQ_SOURCE_HPD2; case AMDGPU_HPD_3: return DC_IRQ_SOURCE_HPD3; case AMDGPU_HPD_4: return DC_IRQ_SOURCE_HPD4; case AMDGPU_HPD_5: return DC_IRQ_SOURCE_HPD5; case AMDGPU_HPD_6: return DC_IRQ_SOURCE_HPD6; default: return DC_IRQ_SOURCE_INVALID; }}EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source);
STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state){ enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); bool st = (state == AMDGPU_IRQ_STATE_ENABLE);
amdgpu_dm_irq_set(adev, src, st); return 0;}EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_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){ bool st; enum dc_irq_source irq_source; struct dc *dc = adev->dm.dc; struct amdgpu_crtc *acrtc = adev->mode_info.crtcs[crtc_id];
if (!acrtc) { DRM_ERROR( "%s: crtc is NULL at id :%d\n", func, crtc_id); return 0; }
if (acrtc->otg_inst == -1) return 0;
irq_source = dal_irq_type + acrtc->otg_inst;
st = (state == AMDGPU_IRQ_STATE_ENABLE);
if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) dc_allow_idle_optimizations(dc, false);
amdgpu_dm_irq_set(adev, irq_source, st); return 0;}
STATIC_IFN_KUNIT 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){ return dm_irq_state( adev, source, crtc_id, state, IRQ_TYPE_PFLIP, __func__);}EXPORT_IF_KUNIT(amdgpu_dm_set_pflip_irq_state);
STATIC_IFN_KUNIT 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){ return dm_irq_state( adev, source, crtc_id, state, IRQ_TYPE_VBLANK, __func__);}EXPORT_IF_KUNIT(amdgpu_dm_set_crtc_irq_state);
STATIC_IFN_KUNIT 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){ return dm_irq_state( adev, source, crtc_id, state, IRQ_TYPE_VLINE0, __func__);}EXPORT_IF_KUNIT(amdgpu_dm_set_vline0_irq_state);
STATIC_IFN_KUNIT 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){ enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; bool st = (state == AMDGPU_IRQ_STATE_ENABLE);
amdgpu_dm_irq_set(adev, irq_source, st); return 0;}EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state);
STATIC_IFN_KUNIT 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){ return dm_irq_state( adev, source, crtc_id, state, IRQ_TYPE_VUPDATE, __func__);}EXPORT_IF_KUNIT(amdgpu_dm_set_vupdate_irq_state);
STATIC_IFN_KUNIT 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){ enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; bool st = (state == AMDGPU_IRQ_STATE_ENABLE);
amdgpu_dm_irq_set(adev, irq_source, st); return 0;}EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state);
static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = { .set = amdgpu_dm_set_crtc_irq_state, .process = amdgpu_dm_irq_handler,};
static const struct amdgpu_irq_src_funcs dm_vline0_irq_funcs = { .set = amdgpu_dm_set_vline0_irq_state, .process = amdgpu_dm_irq_handler,};
static const struct amdgpu_irq_src_funcs dm_dmub_outbox_irq_funcs = { .set = amdgpu_dm_set_dmub_outbox_irq_state, .process = amdgpu_dm_irq_handler,};
static const struct amdgpu_irq_src_funcs dm_vupdate_irq_funcs = { .set = amdgpu_dm_set_vupdate_irq_state, .process = amdgpu_dm_irq_handler,};
static const struct amdgpu_irq_src_funcs dm_dmub_trace_irq_funcs = { .set = amdgpu_dm_set_dmub_trace_irq_state, .process = amdgpu_dm_irq_handler,};
static const struct amdgpu_irq_src_funcs dm_pageflip_irq_funcs = { .set = amdgpu_dm_set_pflip_irq_state, .process = amdgpu_dm_irq_handler,};
static const struct amdgpu_irq_src_funcs dm_hpd_irq_funcs = { .set = amdgpu_dm_set_hpd_irq_state, .process = amdgpu_dm_irq_handler,};
void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev){ adev->crtc_irq.num_types = adev->mode_info.num_crtc; adev->crtc_irq.funcs = &dm_crtc_irq_funcs;
adev->vline0_irq.num_types = adev->mode_info.num_crtc; adev->vline0_irq.funcs = &dm_vline0_irq_funcs;
adev->dmub_outbox_irq.num_types = 1; adev->dmub_outbox_irq.funcs = &dm_dmub_outbox_irq_funcs;
adev->vupdate_irq.num_types = adev->mode_info.num_crtc; adev->vupdate_irq.funcs = &dm_vupdate_irq_funcs;
adev->dmub_trace_irq.num_types = 1; adev->dmub_trace_irq.funcs = &dm_dmub_trace_irq_funcs;
adev->pageflip_irq.num_types = adev->mode_info.num_crtc; adev->pageflip_irq.funcs = &dm_pageflip_irq_funcs;
adev->hpd_irq.num_types = adev->mode_info.num_hpd; adev->hpd_irq.funcs = &dm_hpd_irq_funcs;}EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs);
void amdgpu_dm_outbox_init(struct amdgpu_device *adev){ amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true);}EXPORT_IF_KUNIT(amdgpu_dm_outbox_init);
/** * 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){ struct drm_device *dev = adev_to_drm(adev); struct drm_connector *connector; struct drm_connector_list_iter iter; int irq_type; int i; bool use_polling = false;
/* * First, clear all hpd and hpdrx interrupts, over the links this ASIC * has rather than over the whole enum: the unused slots of the DC * interrupt table hold dummy handlers that assert when touched. */ for (i = 0; i < adev->dm.dc->link_count; i++) { const struct dc_link *link = adev->dm.dc->links[i];
if (link->irq_source_hpd != DC_IRQ_SOURCE_INVALID && !amdgpu_dm_irq_set(adev, link->irq_source_hpd, false)) drm_err(dev, "Failed to clear hpd source=%d on init\n", link->irq_source_hpd);
if (link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID && !amdgpu_dm_irq_set(adev, link->irq_source_hpd_rx, false)) drm_err(dev, "Failed to clear hpdrx source=%d on init\n", link->irq_source_hpd_rx); }
drm_connector_list_iter_begin(dev, &iter); drm_for_each_connector_iter(connector, &iter) { struct amdgpu_dm_connector *amdgpu_dm_connector; const struct dc_link *dc_link;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
amdgpu_dm_connector = to_amdgpu_dm_connector(connector); dc_link = amdgpu_dm_connector->dc_link; if (!dc_link) continue;
/* * Analog connectors may be hot-plugged unlike other connector * types that don't support HPD. Only poll analog connectors. */ use_polling |= dc_connector_supports_analog(dc_link->link_id.id);
/* * Get a base driver irq reference for hpd ints for the lifetime * of dm. Note that only hpd interrupt types are registered with * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on * hpd_rx isn't available. DM currently controls hpd_rx * explicitly with amdgpu_dm_irq_set() */ if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; /* * TODO: There's a mismatch between mode_info.num_hpd * and what bios reports as the # of connectors with hpd * sources. Since the # of hpd source types registered * with base driver == mode_info.num_hpd, we have to * fallback to amdgpu_dm_irq_set for the remaining types. */ if (irq_type < adev->mode_info.num_hpd) { if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", dc_link->irq_source_hpd); } else { amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, true); } }
if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, true); } } drm_connector_list_iter_end(&iter);
if (use_polling) drm_kms_helper_poll_init(dev);}EXPORT_IF_KUNIT(amdgpu_dm_hpd_init);
/** * 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){ struct drm_device *dev = adev_to_drm(adev); struct drm_connector *connector; struct drm_connector_list_iter iter; int irq_type;
drm_connector_list_iter_begin(dev, &iter); drm_for_each_connector_iter(connector, &iter) { struct amdgpu_dm_connector *amdgpu_dm_connector; const struct dc_link *dc_link;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
amdgpu_dm_connector = to_amdgpu_dm_connector(connector); dc_link = amdgpu_dm_connector->dc_link;
if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1;
/* TODO: See same TODO in amdgpu_dm_hpd_init() */ if (irq_type < adev->mode_info.num_hpd) { if (amdgpu_irq_put(adev, &adev->hpd_irq, irq_type)) drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", dc_link->irq_source_hpd); } else { amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, false); } }
if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, false); } } drm_connector_list_iter_end(&iter);
if (dev->mode_config.poll_enabled) drm_kms_helper_poll_fini(dev);}EXPORT_IF_KUNIT(amdgpu_dm_hpd_fini);
/* ========== HPD handling ========== */static void force_connector_state( struct amdgpu_dm_connector *aconnector, enum drm_connector_force force_state){ struct drm_connector *connector = &aconnector->base;
mutex_lock(&connector->dev->mode_config.mutex); aconnector->base.force = force_state; mutex_unlock(&connector->dev->mode_config.mutex);
mutex_lock(&aconnector->hpd_lock); drm_kms_helper_connector_hotplug_event(connector); mutex_unlock(&aconnector->hpd_lock);}
STATIC_IFN_KUNIT void dm_handle_hpd_rx_offload_work(struct work_struct *work){ struct hpd_rx_irq_offload_work *offload_work; struct amdgpu_dm_connector *aconnector; struct dc_link *dc_link; struct amdgpu_device *adev; enum dc_connection_type new_connection_type = dc_connection_none; unsigned long flags; union test_response test_response;
memset(&test_response, 0, sizeof(test_response));
offload_work = container_of(work, struct hpd_rx_irq_offload_work, work); aconnector = offload_work->offload_wq->aconnector; adev = offload_work->adev;
if (!aconnector) { drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work"); goto skip; }
dc_link = aconnector->dc_link;
mutex_lock(&aconnector->hpd_lock); if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); mutex_unlock(&aconnector->hpd_lock);
if (new_connection_type == dc_connection_none) goto skip;
if (amdgpu_in_reset(adev)) goto skip;
if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT); spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); offload_work->offload_wq->is_handling_mst_msg_rdy_event = false; spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); goto skip; }
mutex_lock(&adev->dm.dc_lock); if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) { dc_link_dp_handle_automated_test(dc_link);
if (aconnector->timing_changed) { /* force connector disconnect and reconnect */ force_connector_state(aconnector, DRM_FORCE_OFF); msleep(100); force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED); }
test_response.bits.ACK = 1;
core_link_write_dpcd( dc_link, DP_TEST_RESPONSE, &test_response.raw, sizeof(test_response)); } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) && dc_link_check_link_loss_status(dc_link, &offload_work->data) && dc_link_dp_allow_hpd_rx_irq(dc_link)) { /* offload_work->data is from handle_hpd_rx_irq-> * schedule_hpd_rx_offload_work.this is defer handle * for hpd short pulse. upon here, link status may be * changed, need get latest link status from dpcd * registers. if link status is good, skip run link * training again. */ union hpd_irq_data irq_data;
memset(&irq_data, 0, sizeof(irq_data));
/* before dc_link_dp_handle_link_loss, allow new link lost handle * request be added to work queue if link lost at end of dc_link_ * dp_handle_link_loss */ spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); offload_work->offload_wq->is_handling_link_loss = false; spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags);
if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) && dc_link_check_link_loss_status(dc_link, &irq_data)) dc_link_dp_handle_link_loss(dc_link); } mutex_unlock(&adev->dm.dc_lock);
skip: kfree(offload_work);
}EXPORT_IF_KUNIT(dm_handle_hpd_rx_offload_work);
struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev){ struct dc *dc = adev->dm.dc; int max_caps = dc->caps.max_links; int i = 0; struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL;
hpd_rx_offload_wq = kzalloc_objs(*hpd_rx_offload_wq, max_caps);
if (!hpd_rx_offload_wq) return NULL;
for (i = 0; i < max_caps; i++) { hpd_rx_offload_wq[i].wq = create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq");
if (hpd_rx_offload_wq[i].wq == NULL) { drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!"); goto out_err; }
spin_lock_init(&hpd_rx_offload_wq[i].offload_lock); }
return hpd_rx_offload_wq;
out_err: for (i = 0; i < max_caps; i++) { if (hpd_rx_offload_wq[i].wq) destroy_workqueue(hpd_rx_offload_wq[i].wq); } kfree(hpd_rx_offload_wq); return NULL;}EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_create_workqueue);
void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm){ int i;
if (dm->hpd_rx_offload_wq) { for (i = 0; i < dm->dc->caps.max_links; i++) flush_workqueue(dm->hpd_rx_offload_wq[i].wq); }}EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_work_suspend);
STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2){ if (!sink1 || !sink2) return false; if (sink1->sink_signal != sink2->sink_signal) return false;
if (sink1->dc_edid.length != sink2->dc_edid.length) return false;
if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid, sink1->dc_edid.length) != 0) return false; return true;}EXPORT_IF_KUNIT(are_sinks_equal);
/** * DOC: amdgpu_dm_hdmi_hpd_debounce_work * * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD * (such as during power save transitions), this delay determines how long to * wait before processing the HPD event. This allows distinguishing between a * physical unplug (>hdmi_hpd_debounce_delay) * and a spontaneous RX HPD toggle (<hdmi_hpd_debounce_delay). * * If the toggle is less than this delay, the driver compares sink capabilities * and permits a hotplug event if they changed. * * The default value of 1500ms was chosen based on experimental testing with * various monitors that exhibit spontaneous HPD toggling behavior. */void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work){ struct amdgpu_dm_connector *aconnector = container_of(to_delayed_work(work), struct amdgpu_dm_connector, hdmi_hpd_debounce_work); struct drm_connector *connector = &aconnector->base; struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); struct dc *dc = aconnector->dc_link->ctx->dc; bool fake_reconnect = false; bool reallow_idle = false; bool ret = false;
guard(mutex)(&aconnector->hpd_lock);
/* Re-detect the display */ scoped_guard(mutex, &adev->dm.dc_lock) { if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) { dc_allow_idle_optimizations(dc, false); reallow_idle = true; } ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); }
if (ret) { /* Apply workaround delay for certain panels */ amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); /* Compare sinks to determine if this was a spontaneous HPD toggle */ if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) { /* * Sinks match - this was a spontaneous HDMI HPD toggle. */ drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n"); fake_reconnect = true; }
/* Update connector state */ amdgpu_dm_update_connector_after_detect(aconnector);
drm_modeset_lock_all(dev); dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev);
/* Only notify OS if sink actually changed */ if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED) drm_kms_helper_hotplug_event(dev); }
/* Release the cached sink reference */ if (aconnector->hdmi_prev_sink) { dc_sink_release(aconnector->hdmi_prev_sink); aconnector->hdmi_prev_sink = NULL; }
scoped_guard(mutex, &adev->dm.dc_lock) { if (reallow_idle && dc->caps.ips_support) dc_allow_idle_optimizations(dc, true); }}EXPORT_IF_KUNIT(amdgpu_dm_hdmi_hpd_debounce_work);
STATIC_IFN_KUNIT void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, enum dc_detect_reason reason){ struct drm_connector *connector = &aconnector->base; struct drm_device *dev = connector->dev; enum dc_connection_type new_connection_type = dc_connection_none; struct amdgpu_device *adev = drm_to_adev(dev); struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); struct dc *dc = aconnector->dc_link->ctx->dc; bool ret = false; bool debounce_required = false;
if (adev->dm.disable_hpd_irq) return;
/* * In case of failure or MST no need to update connector status or notify the OS * since (for MST case) MST does this in its own context. */ guard(mutex)(&aconnector->hpd_lock);
if (adev->dm.hdcp_workqueue) { hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index); dm_con_state->update_hdcp = true; } if (aconnector->fake_enable) aconnector->fake_enable = false;
aconnector->timing_changed = false;
if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type)) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
/* * Check for HDMI disconnect with debounce enabled. */ debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 && dc_is_hdmi_signal(aconnector->dc_link->connector_signal) && new_connection_type == dc_connection_none && aconnector->dc_link->local_sink != NULL);
if (aconnector->base.force && new_connection_type == dc_connection_none) { amdgpu_dm_emulated_link_detect(aconnector->dc_link);
drm_modeset_lock_all(dev); dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev);
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || reason == DETECT_REASON_HPDRX) drm_kms_helper_connector_hotplug_event(connector); } else if (debounce_required) { /* * HDMI disconnect detected - schedule delayed work instead of * processing immediately. This allows us to coalesce spurious * HDMI signals from physical unplugs. */ drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n", aconnector->hdmi_hpd_debounce_delay_ms);
/* Cache the current sink for later comparison */ if (aconnector->hdmi_prev_sink) dc_sink_release(aconnector->hdmi_prev_sink); aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink; if (aconnector->hdmi_prev_sink) dc_sink_retain(aconnector->hdmi_prev_sink);
/* Schedule delayed detection. */ if (mod_delayed_work(system_percpu_wq, &aconnector->hdmi_hpd_debounce_work, msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms))) drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n");
} else {
/* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */ if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work)) return;
scoped_guard(mutex, &adev->dm.dc_lock) { dc_exit_ips_for_hw_access(dc); ret = dc_link_detect(aconnector->dc_link, reason); } if (ret) { /* w/a delay for certain panels */ amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); amdgpu_dm_update_connector_after_detect(aconnector);
drm_modeset_lock_all(dev); dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev);
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || reason == DETECT_REASON_HPDRX) drm_kms_helper_connector_hotplug_event(connector); } }}EXPORT_IF_KUNIT(handle_hpd_irq_helper);
STATIC_IFN_KUNIT void handle_hpd_irq(void *param){ struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param;
handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD);
}EXPORT_IF_KUNIT(handle_hpd_irq);
STATIC_IFN_KUNIT void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, union hpd_irq_data hpd_irq_data){ struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work);
if (!offload_work) { drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n"); return; }
INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); offload_work->data = hpd_irq_data; offload_work->offload_wq = offload_wq; offload_work->adev = adev;
queue_work(offload_wq->wq, &offload_work->work); drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work");}EXPORT_IF_KUNIT(schedule_hpd_rx_offload_work);
STATIC_IFN_KUNIT void handle_hpd_rx_irq(void *param){ struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; struct drm_connector *connector = &aconnector->base; struct drm_device *dev = connector->dev; struct dc_link *dc_link = aconnector->dc_link; bool is_mst_root_connector = aconnector->mst_mgr.mst_state; bool result = false; enum dc_connection_type new_connection_type = dc_connection_none; struct amdgpu_device *adev = drm_to_adev(dev); union hpd_irq_data hpd_irq_data; bool link_loss = false; bool has_left_work = false; int idx = dc_link->link_index; struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; struct dc *dc = aconnector->dc_link->ctx->dc;
memset(&hpd_irq_data, 0, sizeof(hpd_irq_data));
if (adev->dm.disable_hpd_irq) return;
/* * TODO:Temporary add mutex to protect hpd interrupt not have a gpio * conflict, after implement i2c helper, this mutex should be * retired. */ mutex_lock(&aconnector->hpd_lock);
result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data, &link_loss, true, &has_left_work);
if (!has_left_work) goto out;
if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); goto out; }
if (dc_link_dp_allow_hpd_rx_irq(dc_link)) { if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { bool skip = false;
/* * DOWN_REP_MSG_RDY is also handled by polling method * mgr->cbs->poll_hpd_irq() */ spin_lock(&offload_wq->offload_lock); skip = offload_wq->is_handling_mst_msg_rdy_event;
if (!skip) offload_wq->is_handling_mst_msg_rdy_event = true;
spin_unlock(&offload_wq->offload_lock);
if (!skip) schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
goto out; }
if (link_loss) { bool skip = false;
spin_lock(&offload_wq->offload_lock); skip = offload_wq->is_handling_link_loss;
if (!skip) offload_wq->is_handling_link_loss = true;
spin_unlock(&offload_wq->offload_lock);
if (!skip) schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
goto out; } }
out: if (result && !is_mst_root_connector) { /* Downstream Port status changed. */ if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) { amdgpu_dm_emulated_link_detect(dc_link);
if (aconnector->fake_enable) aconnector->fake_enable = false;
amdgpu_dm_update_connector_after_detect(aconnector);
drm_modeset_lock_all(dev); dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev);
drm_kms_helper_connector_hotplug_event(connector); } else { bool ret = false;
mutex_lock(&adev->dm.dc_lock); dc_exit_ips_for_hw_access(dc); ret = dc_link_detect(dc_link, DETECT_REASON_HPDRX); mutex_unlock(&adev->dm.dc_lock);
if (ret) { if (aconnector->fake_enable) aconnector->fake_enable = false;
amdgpu_dm_update_connector_after_detect(aconnector);
drm_modeset_lock_all(dev); dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev);
drm_kms_helper_connector_hotplug_event(connector); } } } if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { if (adev->dm.hdcp_workqueue) hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index); }
if (dc_link->type != dc_connection_mst_branch) drm_dp_cec_irq(&aconnector->dm_dp_aux.aux);
mutex_unlock(&aconnector->hpd_lock);}EXPORT_IF_KUNIT(handle_hpd_rx_irq);
/** * dmub_hpd_callback - DMUB HPD interrupt processing callback. * @adev: amdgpu_device pointer * @notify: dmub notification structure * * Dmub Hpd interrupt processing callback. Gets displayindex through the * ink index and calls helper to do the processing. */STATIC_IFN_KUNIT void dmub_hpd_callback(struct amdgpu_device *adev, struct dmub_notification *notify){ struct amdgpu_dm_connector *aconnector; struct amdgpu_dm_connector *hpd_aconnector = NULL; struct drm_connector *connector; struct drm_connector_list_iter iter; struct dc_link *link; u8 link_index = 0; struct drm_device *dev;
if (adev == NULL) return;
if (notify == NULL) { drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL"); return; }
if (notify->link_index > adev->dm.dc->link_count) { drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index); return; }
/* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */ if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) { drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n"); return; }
link_index = notify->link_index; link = adev->dm.dc->links[link_index]; dev = adev->dm.ddev;
drm_connector_list_iter_begin(dev, &iter); drm_for_each_connector_iter(connector, &iter) {
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
aconnector = to_amdgpu_dm_connector(connector); if (link && aconnector->dc_link == link) { if (notify->type == DMUB_NOTIFICATION_HPD) drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index); else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index); else drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n", notify->type, link_index);
hpd_aconnector = aconnector; break; } } drm_connector_list_iter_end(&iter);
if (hpd_aconnector) { if (notify->type == DMUB_NOTIFICATION_HPD) { if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { handle_hpd_rx_irq(hpd_aconnector); } }}EXPORT_IF_KUNIT(dmub_hpd_callback);
/** * dmub_hpd_sense_callback - DMUB HPD sense processing callback. * @adev: amdgpu_device pointer * @notify: dmub notification structure * * HPD sense changes can occur during low power states and need to be * notified from firmware to driver. */STATIC_IFN_KUNIT void dmub_hpd_sense_callback(struct amdgpu_device *adev, struct dmub_notification *notify){ drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n");}EXPORT_IF_KUNIT(dmub_hpd_sense_callback);
int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev){ struct drm_device *dev = adev_to_drm(adev); struct drm_connector *connector; struct amdgpu_dm_connector *aconnector; const struct dc_link *dc_link; struct dc_interrupt_params int_params = {0};
int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
if (dc_is_dmub_outbox_supported(adev->dm.dc)) { if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, dmub_hpd_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); return -EINVAL; }
if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, dmub_hpd_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); return -EINVAL; }
if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, dmub_hpd_sense_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); return -EINVAL; } }
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
aconnector = to_amdgpu_dm_connector(connector); dc_link = aconnector->dc_link;
if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; int_params.irq_source = dc_link->irq_source_hpd;
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_HPD1 || int_params.irq_source > DC_IRQ_SOURCE_HPD6) { drm_err(adev_to_drm(adev), "Failed to register hpd irq %u for %s!\n", int_params.irq_source, connector->name); return -EINVAL; }
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, handle_hpd_irq, (void *) aconnector)) return -ENOMEM; }
if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) {
/* Also register for DP short pulse (hpd_rx). */ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; int_params.irq_source = dc_link->irq_source_hpd_rx;
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { drm_err(adev_to_drm(adev), "Failed to register hpd rx irq %u for %s!\n", int_params.irq_source, connector->name); return -EINVAL; }
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, handle_hpd_rx_irq, (void *) aconnector)) return -ENOMEM; } } return 0;}EXPORT_IF_KUNIT(amdgpu_dm_register_hpd_handlers);
/* ========== IRQ handlers ========== */struct amdgpu_crtc *amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, int otg_inst){ struct drm_device *dev = adev_to_drm(adev); struct drm_crtc *crtc; struct amdgpu_crtc *amdgpu_crtc;
if (WARN_ON(otg_inst == -1)) return adev->mode_info.crtcs[0];
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { amdgpu_crtc = to_amdgpu_crtc(crtc);
if (amdgpu_crtc->otg_inst == otg_inst) return amdgpu_crtc; }
return NULL;}EXPORT_IF_KUNIT(amdgpu_dm_get_crtc_by_otg_inst);
/** * dm_pflip_high_irq() - Handle pageflip interrupt * @interrupt_params: ignored * * Handles the pageflip interrupt by notifying all interested parties * that the pageflip has been completed. */STATIC_IFN_KUNIT void dm_pflip_high_irq(void *interrupt_params){ struct amdgpu_crtc *amdgpu_crtc; struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; struct drm_device *dev = adev_to_drm(adev); unsigned long flags; struct drm_pending_vblank_event *e; u32 vpos, hpos, v_blank_start, v_blank_end; bool vrr_active;
amdgpu_crtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP);
/* IRQ could occur when in initial stage */ /* TODO work and BO cleanup */ if (amdgpu_crtc == NULL) { drm_dbg_state(dev, "CRTC is null, returning.\n"); return; }
spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) { drm_dbg_state(dev, "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n", amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED, amdgpu_crtc->crtc_id, amdgpu_crtc); spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); return; }
/* page flip completed. */ e = amdgpu_crtc->event; amdgpu_crtc->event = NULL;
WARN_ON(!e);
vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc);
/* Fixed refresh rate, or VRR scanout position outside front-porch? */ if (!vrr_active || !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start, &v_blank_end, &hpos, &vpos) || (vpos < v_blank_start)) { /* Update to correct count and vblank timestamp if racing with * vblank irq. This also updates to the correct vblank timestamp * even in VRR mode, as scanout is past the front-porch atm. */ drm_crtc_accurate_vblank_count(&amdgpu_crtc->base);
/* Wake up userspace by sending the pageflip event with proper * count and timestamp of vblank of flip completion. */ if (e) { drm_crtc_send_vblank_event(&amdgpu_crtc->base, e);
/* Event sent, so done with vblank for this flip */ drm_crtc_vblank_put(&amdgpu_crtc->base); } } else if (e) { /* VRR active and inside front-porch: vblank count and * timestamp for pageflip event will only be up to date after * drm_crtc_handle_vblank() has been executed from late vblank * irq handler after start of back-porch (vline 0). We queue the * pageflip event for send-out by drm_crtc_handle_vblank() with * updated timestamp and count, once it runs after us. * * We need to open-code this instead of using the helper * drm_crtc_arm_vblank_event(), as that helper would * call drm_crtc_accurate_vblank_count(), which we must * not call in VRR mode while we are in front-porch! */
/* sequence will be replaced by real count during send-out. */ e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base); e->pipe = amdgpu_crtc->crtc_id;
list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list); e = NULL; }
/* Keep track of vblank of this flip for flip throttling. We use the * cooked hw counter, as that one incremented at start of this vblank * of pageflip completion, so last_flip_vblank is the forbidden count * for queueing new pageflips if vsync + VRR is enabled. */ amdgpu_crtc->dm_irq_params.last_flip_vblank = amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base);
amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
drm_dbg_state(dev, "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e);}EXPORT_IF_KUNIT(dm_pflip_high_irq);
STATIC_IFN_KUNIT void dm_handle_vmin_vmax_update(struct work_struct *offload_work){ struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); struct amdgpu_device *adev = work->adev; struct dc_stream_state *stream = work->stream; struct dc_crtc_timing_adjust *adjust = work->adjust;
mutex_lock(&adev->dm.dc_lock); dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust); mutex_unlock(&adev->dm.dc_lock);
dc_stream_release(stream); kfree(work->adjust); kfree(work);}EXPORT_IF_KUNIT(dm_handle_vmin_vmax_update);
static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, struct dc_stream_state *stream, struct dc_crtc_timing_adjust *adjust){ struct vupdate_offload_work *offload_work = kzalloc_obj(*offload_work, GFP_NOWAIT); if (!offload_work) { drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n"); return; }
struct dc_crtc_timing_adjust *adjust_copy = kzalloc_obj(*adjust_copy, GFP_NOWAIT); if (!adjust_copy) { drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n"); kfree(offload_work); return; }
dc_stream_retain(stream); memcpy(adjust_copy, adjust, sizeof(*adjust_copy));
INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update); offload_work->adev = adev; offload_work->stream = stream; offload_work->adjust = adjust_copy;
queue_work(adev->dm.vmin_vmax_wq, &offload_work->work);}
/** * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling * @adev: amdgpu device * @acrtc: the CRTC to service * Performs writeback completion, vblank event handling, CRC processing, VRR BTR * updates and pageflip completion delivery. * * On DCN this is driven by VUPDATE_NO_LOCK (the register latch point) from * dm_vupdate_high_irq(); on DCE it is driven by VLINE0 at the start of vblank * from dm_crtc_high_irq(). */static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, struct amdgpu_crtc *acrtc){ unsigned long flags; int vrr_active; bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0;
if (acrtc->wb_conn && acrtc->wb_pending) { if (acrtc->wb_frame_done) { /* * Second vblank: the DMA for the captured frame has * had a full frame period to flush to memory. Signal * the out fence now. */ amdgpu_dm_crtc_complete_writeback(acrtc); } else { /* * First vblank after arming: the frame has been * scanned out and the DMA is finishing. Disable * writeback immediately to prevent the hardware from * starting a new capture that would overwrite the * buffer. Signal completion on the next vblank to * ensure the DMA is fully flushed to memory. */ dc_stream_fc_disable_writeback(adev->dm.dc, acrtc->dm_irq_params.stream, 0); acrtc->wb_frame_done = true; } }
vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
drm_dbg_vbl(adev_to_drm(adev), "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id, vrr_active, acrtc->dm_irq_params.active_planes);
/** * Core vblank handling. * * On DCN this handler runs at VUPDATE_NO_LOCK, the register latch * point, which is the correct place to timestamp both VRR and non-VRR * vblanks. * * On DCE this handler runs at the start of front-porch, where only * non-VRR timestamping is valid; VRR vblank is deferred to * dm_vupdate_high_irq() after end of front-porch. */ if (is_dcn || !vrr_active) amdgpu_dm_crtc_handle_vblank(acrtc);
/** * Following stuff must happen at start of vblank, for crc * computation and below-the-range btr support in vrr mode. */ amdgpu_dm_crtc_handle_crc_irq(&acrtc->base);
/* BTR updates need to happen before VUPDATE on Vega and above. */ if (adev->family < AMDGPU_FAMILY_AI) return;
spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
if (acrtc->dm_irq_params.stream && acrtc->dm_irq_params.vrr_params.supported) { bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE;
mod_freesync_handle_v_update(adev->dm.freesync_module, acrtc->dm_irq_params.stream, &acrtc->dm_irq_params.vrr_params);
/* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */ if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream, &acrtc->dm_irq_params.vrr_params.adjust); } }
/* * Deliver pageflip completion events (DCN only). * * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch * point. Deliver any pending pageflip completion event from here, * once HW has consumed the new address (the OTG no longer reports a * pending flip). * * Also handle the case here where there aren't any active planes and * DCN HUBP may be clock-gated, so the flip-pending status may be * undefined. */ if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && acrtc->event) {
if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; drm_crtc_vblank_put(&acrtc->base); acrtc->pflip_status = AMDGPU_FLIP_NONE; } /* * If the flip is still pending, leave it armed and * retry on the next vupdate. */ } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && acrtc->dm_irq_params.active_planes == 0) {
if (acrtc->event) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; drm_crtc_vblank_put(&acrtc->base); } acrtc->pflip_status = AMDGPU_FLIP_NONE; }
spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);}
STATIC_IFN_KUNIT void dm_vupdate_high_irq(void *interrupt_params){ struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; struct amdgpu_crtc *acrtc; struct drm_device *drm_dev; struct drm_vblank_crtc *vblank; ktime_t frame_duration_ns, previous_timestamp; unsigned long flags; int vrr_active;
acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); if (!acrtc) return;
vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); drm_dev = acrtc->base.dev; vblank = drm_crtc_vblank_crtc(&acrtc->base); previous_timestamp = atomic64_read(&irq_params->previous_timestamp); frame_duration_ns = vblank->time - previous_timestamp;
if (frame_duration_ns > 0) { trace_amdgpu_refresh_rate_track(acrtc->base.index, frame_duration_ns, ktime_divns(NSEC_PER_SEC, frame_duration_ns)); atomic64_set(&irq_params->previous_timestamp, vblank->time); }
drm_dbg_vbl(drm_dev, "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, vrr_active);
/* * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver * vblank and pageflip completion events; VSTARTUP and GRPH_PFLIP are * not used. Run the full handler here. */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { dm_crtc_high_irq_handler(adev, acrtc); return; }
/* DCE only below. */
/* Core vblank handling is done here after end of front-porch in * vrr mode, as vblank timestamping will give valid results * while now done after front-porch. This will also deliver * page-flip completion events that have been queued to us * if a pageflip happened inside front-porch. */ if (vrr_active && acrtc->dm_irq_params.stream) { bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE;
amdgpu_dm_crtc_handle_vblank(acrtc);
/* BTR processing for pre-DCE12 ASICs */ if (adev->family < AMDGPU_FAMILY_AI) { spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); mod_freesync_handle_v_update( adev->dm.freesync_module, acrtc->dm_irq_params.stream, &acrtc->dm_irq_params.vrr_params);
if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream, &acrtc->dm_irq_params.vrr_params.adjust); } spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); } }}EXPORT_IF_KUNIT(dm_vupdate_high_irq);
/** * dm_crtc_high_irq() - Handles CRTC interrupt * @interrupt_params: used for determining the CRTC instance * * Handles the CRTC/VSYNC interrupt by notifying DRM's VBLANK event handler. * * Used on DCE (VLINE0, set to vblank start). On DCN the equivalent handling is * driven by VUPDATE_NO_LOCK in dm_vupdate_high_irq(). */STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params){ struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; struct amdgpu_crtc *acrtc;
acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); if (!acrtc) return;
dm_crtc_high_irq_handler(adev, acrtc);}EXPORT_IF_KUNIT(dm_crtc_high_irq);
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)/** * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for * DCN generation ASICs * @interrupt_params: interrupt parameters * * Used to set crc window/read out crc value at vertical line 0 position */static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params){ struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; struct amdgpu_crtc *acrtc;
acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0);
if (!acrtc) return;
amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base);}#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */
STATIC_IFN_KUNIT void dm_handle_hpd_work(struct work_struct *work){ struct dmub_hpd_work *dmub_hpd_wrk;
dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work);
if (!dmub_hpd_wrk->dmub_notify) { drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL"); return; }
if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) { dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev, dmub_hpd_wrk->dmub_notify); }
kfree(dmub_hpd_wrk->dmub_notify); kfree(dmub_hpd_wrk);
}EXPORT_IF_KUNIT(dm_handle_hpd_work);
STATIC_IFN_KUNIT const char *dmub_notification_type_str(enum dmub_notification_type e){ switch (e) { case DMUB_NOTIFICATION_NO_DATA: return "NO_DATA"; case DMUB_NOTIFICATION_AUX_REPLY: return "AUX_REPLY"; case DMUB_NOTIFICATION_HPD: return "HPD"; case DMUB_NOTIFICATION_HPD_IRQ: return "HPD_IRQ"; case DMUB_NOTIFICATION_SET_CONFIG_REPLY: return "SET_CONFIG_REPLY"; case DMUB_NOTIFICATION_DPIA_NOTIFICATION: return "DPIA_NOTIFICATION"; case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY: return "HPD_SENSE_NOTIFY"; case DMUB_NOTIFICATION_FUSED_IO: return "FUSED_IO"; default: return "<unknown>"; }}EXPORT_IF_KUNIT(dmub_notification_type_str);
#define DMUB_TRACE_MAX_READ 64/** * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt * @interrupt_params: used for determining the Outbox instance * * Handles the Outbox Interrupt * event handler. */STATIC_IFN_KUNIT void dm_dmub_outbox1_low_irq(void *interrupt_params){ struct dmub_notification notify = {0}; struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; struct amdgpu_display_manager *dm = &adev->dm; struct dmcub_trace_buf_entry entry = { 0 }; u32 count = 0; struct dmub_hpd_work *dmub_hpd_wrk;
do { if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) { trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count, entry.param0, entry.param1);
drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n", entry.trace_code, entry.tick_count, entry.param0, entry.param1); } else break;
count++;
} while (count <= DMUB_TRACE_MAX_READ);
if (count > DMUB_TRACE_MAX_READ) drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ");
if (dc_enable_dmub_notifications(adev->dm.dc) && irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) {
do { dc_stat_get_dmub_notification(adev->dm.dc, ¬ify); if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) { drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type); continue; } if (!dm->dmub_callback[notify.type]) { drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n", dmub_notification_type_str(notify.type)); continue; } if (dm->dmub_thread_offload[notify.type]) { dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, GFP_ATOMIC); if (!dmub_hpd_wrk) { drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk"); return; } dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification), GFP_ATOMIC); if (!dmub_hpd_wrk->dmub_notify) { kfree(dmub_hpd_wrk); drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify"); return; } INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work); dmub_hpd_wrk->adev = adev; queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work); } else { dm->dmub_callback[notify.type](adev, ¬ify); } } while (notify.pending_notification); }}EXPORT_IF_KUNIT(dm_dmub_outbox1_low_irq);
/* Register IRQ sources and initialize IRQ callbacks */int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev){ struct dc *dc = adev->dm.dc; struct common_irq_params *c_irq_params; struct dc_interrupt_params int_params = {0}; int r; int i; unsigned int src_id; unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY; /* Use different interrupts for VBLANK on DCE 6 vs. newer. */ const unsigned int vblank_d1 = adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0 ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1;
if (adev->family >= AMDGPU_FAMILY_AI) client_id = SOC15_IH_CLIENTID_DCE;
int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
/* * Actions of amdgpu_irq_add_id(): * 1. Register a set() function with base driver. * Base driver will call set() function to enable/disable an * interrupt in DC hardware. * 2. Register amdgpu_dm_irq_handler(). * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts * coming from DC hardware. * amdgpu_dm_irq_handler() will re-direct the interrupt to DC * for acknowledging and handling. */
/* Use VBLANK interrupt */ for (i = 0; i < adev->mode_info.num_crtc; i++) { src_id = vblank_d1 + i; r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq); if (r) { drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); return r; }
int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; int_params.irq_source = dc_interrupt_to_irq_source(dc, src_id, 0);
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); return -EINVAL; }
c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
c_irq_params->adev = adev; c_irq_params->irq_src = int_params.irq_source;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, dm_crtc_high_irq, c_irq_params)) return -ENOMEM; }
if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) { /* Use VUPDATE interrupt */ for (i = 0; i < adev->mode_info.num_crtc; i++) { src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2; r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq); if (r) { drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); return r; }
int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; int_params.irq_source = dc_interrupt_to_irq_source(dc, src_id, 0);
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); return -EINVAL; }
c_irq_params = &adev->dm.vupdate_params[ int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; c_irq_params->adev = adev; c_irq_params->irq_src = int_params.irq_source;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, dm_vupdate_high_irq, c_irq_params)) return -ENOMEM; } }
/* Use GRPH_PFLIP interrupt */ for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq); if (r) { drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); return r; }
int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; int_params.irq_source = dc_interrupt_to_irq_source(dc, i, 0);
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); return -EINVAL; }
c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
c_irq_params->adev = adev; c_irq_params->irq_src = int_params.irq_source;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, dm_pflip_high_irq, c_irq_params)) return -ENOMEM; }
/* HPD */ r = amdgpu_irq_add_id(adev, client_id, VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq); if (r) { drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); return r; }
r = amdgpu_dm_register_hpd_handlers(adev);
return r;}EXPORT_IF_KUNIT(amdgpu_dm_dce110_register_irq_handlers);
/* Register IRQ sources and initialize IRQ callbacks */int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev){ struct dc *dc = adev->dm.dc; struct common_irq_params *c_irq_params; struct dc_interrupt_params int_params = {0}; int r; int i;#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) static const unsigned int vrtl_int_srcid[] = { DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL, DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL, DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL, DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL, DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL, DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL };#endif
int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
/* * Actions of amdgpu_irq_add_id(): * 1. Register a set() function with base driver. * Base driver will call set() function to enable/disable an * interrupt in DC hardware. * 2. Register amdgpu_dm_irq_handler(). * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts * coming from DC hardware. * amdgpu_dm_irq_handler() will re-direct the interrupt to DC * for acknowledging and handling. */
/* Use otg vertical line interrupt */#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, vrtl_int_srcid[i], &adev->vline0_irq);
if (r) { drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n"); return r; }
int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; int_params.irq_source = dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0);
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 || int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) { drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n"); return -EINVAL; }
c_irq_params = &adev->dm.vline0_params[int_params.irq_source - DC_IRQ_SOURCE_DC1_VLINE0];
c_irq_params->adev = adev; c_irq_params->irq_src = int_params.irq_source;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, dm_dcn_vertical_interrupt0_high_irq, c_irq_params)) return -ENOMEM; }#endif
/* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx * to trigger at end of each vblank, regardless of state of the lock, * matching DCE behaviour. */ for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT; i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1; i++) { r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq);
if (r) { drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); return r; }
int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; int_params.irq_source = dc_interrupt_to_irq_source(dc, i, 0);
if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); return -EINVAL; }
c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1];
c_irq_params->adev = adev; c_irq_params->irq_src = int_params.irq_source;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, dm_vupdate_high_irq, c_irq_params)) return -ENOMEM; }
/* HPD */ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, &adev->hpd_irq); if (r) { drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); return r; }
r = amdgpu_dm_register_hpd_handlers(adev);
return r;}EXPORT_IF_KUNIT(amdgpu_dm_dcn10_register_irq_handlers);
/* Register Outbox IRQ sources and initialize IRQ callbacks */int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev){ struct dc *dc = adev->dm.dc; struct common_irq_params *c_irq_params; struct dc_interrupt_params int_params = {0}; int r, i;
int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT, &adev->dmub_outbox_irq); if (r) { drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n"); return r; }
if (dc->ctx->dmub_srv) { i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT; int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; int_params.irq_source = dc_interrupt_to_irq_source(dc, i, 0);
c_irq_params = &adev->dm.dmub_outbox_params[0];
c_irq_params->adev = adev; c_irq_params->irq_src = int_params.irq_source;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, dm_dmub_outbox1_low_irq, c_irq_params)) return -ENOMEM; }
return 0;}EXPORT_IF_KUNIT(amdgpu_dm_register_outbox_irq_handlers);