123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144 |
- From c9df32c057e43e38c8113199e64f7a64f8d341df Mon Sep 17 00:00:00 2001
- From: Robert Marko <[email protected]>
- Date: Mon, 11 Apr 2022 14:35:36 +0200
- Subject: [PATCH] regulator: add Qualcomm CPR regulators
- Allow building Qualcomm CPR regulators.
- Signed-off-by: Robert Marko <[email protected]>
- ---
- drivers/regulator/Kconfig | 33 +
- drivers/regulator/Makefile | 3 +
- drivers/regulator/cpr3-npu-regulator.c | 695 +++
- drivers/regulator/cpr3-regulator.c | 5111 +++++++++++++++++++++++
- drivers/regulator/cpr3-regulator.h | 1211 ++++++
- drivers/regulator/cpr3-util.c | 2750 ++++++++++++
- drivers/regulator/cpr4-apss-regulator.c | 1819 ++++++++
- include/soc/qcom/socinfo.h | 463 ++
- 8 files changed, 12085 insertions(+)
- create mode 100644 drivers/regulator/cpr3-npu-regulator.c
- create mode 100644 drivers/regulator/cpr3-regulator.c
- create mode 100644 drivers/regulator/cpr3-regulator.h
- create mode 100644 drivers/regulator/cpr3-util.c
- create mode 100644 drivers/regulator/cpr4-apss-regulator.c
- create mode 100644 include/soc/qcom/socinfo.h
- --- a/drivers/regulator/Kconfig
- +++ b/drivers/regulator/Kconfig
- @@ -1663,4 +1663,37 @@ config REGULATOR_QCOM_LABIBB
- boost regulator and IBB can be used as a negative boost regulator
- for LCD display panel.
-
- +config REGULATOR_CPR3
- + bool "QCOM CPR3 regulator core support"
- + help
- + This driver supports Core Power Reduction (CPR) version 3 controllers
- + which are used by some Qualcomm Technologies, Inc. SoCs to
- + manage important voltage regulators. CPR3 controllers are capable of
- + monitoring several ring oscillator sensing loops simultaneously. The
- + CPR3 controller informs software when the silicon conditions require
- + the supply voltage to be increased or decreased. On certain supply
- + rails, the CPR3 controller is able to propagate the voltage increase
- + or decrease requests all the way to the PMIC without software
- + involvement.
- +
- +config REGULATOR_CPR3_NPU
- + bool "QCOM CPR3 regulator for NPU"
- + depends on OF && REGULATOR_CPR3
- + help
- + This driver supports Qualcomm Technologies, Inc. NPU CPR3
- + regulator Which will always operate in open loop.
- +
- +config REGULATOR_CPR4_APSS
- + bool "QCOM CPR4 regulator for APSS"
- + depends on OF && REGULATOR_CPR3
- + help
- + This driver supports Qualcomm Technologies, Inc. APSS application
- + processor specific features including memory array power mux (APM)
- + switching, one CPR4 thread which monitor the two APSS clusters that
- + are both powered by a shared supply, hardware closed-loop auto
- + voltage stepping, voltage adjustments based on online core count,
- + voltage adjustments based on temperature readings, and voltage
- + adjustments for performance boost mode. This driver reads both initial
- + voltage and CPR target quotient values out of hardware fuses.
- +
- endif
- --- a/drivers/regulator/Makefile
- +++ b/drivers/regulator/Makefile
- @@ -116,6 +116,9 @@ obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qco
- obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
- obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
- obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) += qcom_usb_vbus-regulator.o
- +obj-$(CONFIG_REGULATOR_CPR3) += cpr3-regulator.o cpr3-util.o
- +obj-$(CONFIG_REGULATOR_CPR3_NPU) += cpr3-npu-regulator.o
- +obj-$(CONFIG_REGULATOR_CPR4_APSS) += cpr4-apss-regulator.o
- obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
- obj-$(CONFIG_REGULATOR_PCA9450) += pca9450-regulator.o
- obj-$(CONFIG_REGULATOR_PF8X00) += pf8x00-regulator.o
- --- /dev/null
- +++ b/drivers/regulator/cpr3-npu-regulator.c
- @@ -0,0 +1,695 @@
- +/*
- + * Copyright (c) 2017, The Linux Foundation. All rights reserved.
- + *
- + * Permission to use, copy, modify, and/or distribute this software for any
- + * purpose with or without fee is hereby granted, provided that the above
- + * copyright notice and this permission notice appear in all copies.
- + *
- + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- + */
- +
- +#include <linux/err.h>
- +#include <linux/platform_device.h>
- +#include <linux/module.h>
- +#include <linux/of.h>
- +#include <linux/of_device.h>
- +#include <linux/slab.h>
- +#include <linux/thermal.h>
- +
- +#include "cpr3-regulator.h"
- +
- +#define IPQ807x_NPU_FUSE_CORNERS 2
- +#define IPQ817x_NPU_FUSE_CORNERS 1
- +#define IPQ807x_NPU_FUSE_STEP_VOLT 8000
- +#define IPQ807x_NPU_VOLTAGE_FUSE_SIZE 6
- +#define IPQ807x_NPU_CPR_CLOCK_RATE 19200000
- +
- +#define IPQ807x_NPU_CPR_TCSR_START 6
- +#define IPQ807x_NPU_CPR_TCSR_END 7
- +
- +#define NPU_TSENS 5
- +
- +u32 g_valid_npu_fuse_count = IPQ807x_NPU_FUSE_CORNERS;
- +/**
- + * struct cpr3_ipq807x_npu_fuses - NPU specific fuse data for IPQ807x
- + * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
- + * for each fuse corner (raw, not converted to a voltage)
- + * This struct holds the values for all of the fuses read from memory.
- + */
- +struct cpr3_ipq807x_npu_fuses {
- + u64 init_voltage[IPQ807x_NPU_FUSE_CORNERS];
- +};
- +
- +/*
- + * Constants which define the name of each fuse corner.
- + */
- +enum cpr3_ipq807x_npu_fuse_corner {
- + CPR3_IPQ807x_NPU_FUSE_CORNER_NOM = 0,
- + CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO = 1,
- +};
- +
- +static const char * const cpr3_ipq807x_npu_fuse_corner_name[] = {
- + [CPR3_IPQ807x_NPU_FUSE_CORNER_NOM] = "NOM",
- + [CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO] = "TURBO",
- +};
- +
- +/*
- + * IPQ807x NPU fuse parameter locations:
- + *
- + * Structs are organized with the following dimensions:
- + * Outer: 0 to 1 for fuse corners from lowest to highest corner
- + * Inner: large enough to hold the longest set of parameter segments which
- + * fully defines a fuse parameter, +1 (for NULL termination).
- + * Each segment corresponds to a contiguous group of bits from a
- + * single fuse row. These segments are concatentated together in
- + * order to form the full fuse parameter value. The segments for
- + * a given parameter may correspond to different fuse rows.
- + */
- +static struct cpr3_fuse_param
- +ipq807x_npu_init_voltage_param[IPQ807x_NPU_FUSE_CORNERS][2] = {
- + {{73, 22, 27}, {} },
- + {{73, 16, 21}, {} },
- +};
- +
- +/*
- + * Open loop voltage fuse reference voltages in microvolts for IPQ807x
- + */
- +static int
- +ipq807x_npu_fuse_ref_volt [IPQ807x_NPU_FUSE_CORNERS] = {
- + 912000,
- + 992000,
- +};
- +
- +/*
- + * IPQ9574 (Few parameters are changed, remaining are same as IPQ807x)
- + */
- +#define IPQ9574_NPU_FUSE_CORNERS 2
- +#define IPQ9574_NPU_FUSE_STEP_VOLT 10000
- +#define IPQ9574_NPU_CPR_CLOCK_RATE 24000000
- +
- +/*
- + * fues parameters for IPQ9574
- + */
- +static struct cpr3_fuse_param
- +ipq9574_npu_init_voltage_param[IPQ9574_NPU_FUSE_CORNERS][2] = {
- + {{105, 12, 17}, {} },
- + {{105, 6, 11}, {} },
- +};
- +
- +/*
- + * Open loop voltage fuse reference voltages in microvolts for IPQ9574
- + */
- +static int
- +ipq9574_npu_fuse_ref_volt [IPQ9574_NPU_FUSE_CORNERS] = {
- + 862500,
- + 987500,
- +};
- +
- +struct cpr3_controller *g_ctrl;
- +
- +void cpr3_npu_temp_notify(int sensor, int temp, int low_notif)
- +{
- + u32 prev_sensor_state;
- +
- + if (sensor != NPU_TSENS)
- + return;
- +
- + prev_sensor_state = g_ctrl->cur_sensor_state;
- + if (low_notif)
- + g_ctrl->cur_sensor_state |= BIT(sensor);
- + else
- + g_ctrl->cur_sensor_state &= ~BIT(sensor);
- +
- + if (!prev_sensor_state && g_ctrl->cur_sensor_state)
- + cpr3_handle_temp_open_loop_adjustment(g_ctrl, true);
- + else if (prev_sensor_state && !g_ctrl->cur_sensor_state)
- + cpr3_handle_temp_open_loop_adjustment(g_ctrl, false);
- +}
- +
- +/**
- + * cpr3_ipq807x_npu_read_fuse_data() - load NPU specific fuse parameter values
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function allocates a cpr3_ipq807x_npu_fuses struct, fills it with
- + * values read out of hardware fuses, and finally copies common fuse values
- + * into the CPR3 regulator struct.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_ipq807x_npu_read_fuse_data(struct cpr3_regulator *vreg)
- +{
- + void __iomem *base = vreg->thread->ctrl->fuse_base;
- + struct cpr3_ipq807x_npu_fuses *fuse;
- + int i, rc;
- +
- + fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
- + if (!fuse)
- + return -ENOMEM;
- +
- + for (i = 0; i < g_valid_npu_fuse_count; i++) {
- + rc = cpr3_read_fuse_param(base,
- + vreg->cpr3_regulator_data->init_voltage_param[i],
- + &fuse->init_voltage[i]);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
- + i, rc);
- + return rc;
- + }
- + }
- +
- + vreg->fuse_corner_count = g_valid_npu_fuse_count;
- + vreg->platform_fuses = fuse;
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_npu_parse_corner_data() - parse NPU corner data from device tree
- + * properties of the CPR3 regulator's device node
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_npu_parse_corner_data(struct cpr3_regulator *vreg)
- +{
- + int rc;
- +
- + rc = cpr3_parse_common_corner_data(vreg);
- + if (rc) {
- + cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
- + return rc;
- + }
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_ipq807x_npu_calculate_open_loop_voltages() - calculate the open-loop
- + * voltage for each corner of a CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + * @temp_correction: Temperature based correction
- + *
- + * If open-loop voltage interpolation is allowed in device tree, then
- + * this function calculates the open-loop voltage for a given corner using
- + * linear interpolation. This interpolation is performed using the processor
- + * frequencies of the lower and higher Fmax corners along with their fused
- + * open-loop voltages.
- + *
- + * If open-loop voltage interpolation is not allowed, then this function uses
- + * the Fmax fused open-loop voltage for all of the corners associated with a
- + * given fuse corner.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_ipq807x_npu_calculate_open_loop_voltages(
- + struct cpr3_regulator *vreg, bool temp_correction)
- +{
- + struct cpr3_ipq807x_npu_fuses *fuse = vreg->platform_fuses;
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + int i, j, rc = 0;
- + u64 freq_low, volt_low, freq_high, volt_high;
- + int *fuse_volt;
- + int *fmax_corner;
- +
- + fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
- + GFP_KERNEL);
- + fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
- + GFP_KERNEL);
- + if (!fuse_volt || !fmax_corner) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->fuse_corner_count; i++) {
- + if (ctrl->cpr_global_setting == CPR_DISABLED)
- + fuse_volt[i] = vreg->cpr3_regulator_data->fuse_ref_volt[i];
- + else
- + fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
- + vreg->cpr3_regulator_data->fuse_ref_volt[i],
- + vreg->cpr3_regulator_data->fuse_step_volt,
- + fuse->init_voltage[i],
- + IPQ807x_NPU_VOLTAGE_FUSE_SIZE);
- +
- + /* Log fused open-loop voltage values for debugging purposes. */
- + cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
- + cpr3_ipq807x_npu_fuse_corner_name[i],
- + fuse_volt[i]);
- + }
- +
- + rc = cpr3_determine_part_type(vreg,
- + fuse_volt[CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO]);
- + if (rc) {
- + cpr3_err(vreg,
- + "fused part type detection failed failed, rc=%d\n", rc);
- + goto done;
- + }
- +
- + rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
- + if (rc) {
- + cpr3_err(vreg,
- + "fused open-loop voltage adjustment failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + if (temp_correction) {
- + rc = cpr3_determine_temp_base_open_loop_correction(vreg,
- + fuse_volt);
- + if (rc) {
- + cpr3_err(vreg,
- + "temp open-loop voltage adj. failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + for (i = 1; i < vreg->fuse_corner_count; i++) {
- + if (fuse_volt[i] < fuse_volt[i - 1]) {
- + cpr3_info(vreg,
- + "fuse corner %d voltage=%d uV < fuse corner %d \
- + voltage=%d uV; overriding: fuse corner %d \
- + voltage=%d\n",
- + i, fuse_volt[i], i - 1, fuse_volt[i - 1],
- + i, fuse_volt[i - 1]);
- + fuse_volt[i] = fuse_volt[i - 1];
- + }
- + }
- +
- + /* Determine highest corner mapped to each fuse corner */
- + j = vreg->fuse_corner_count - 1;
- + for (i = vreg->corner_count - 1; i >= 0; i--) {
- + if (vreg->corner[i].cpr_fuse_corner == j) {
- + fmax_corner[j] = i;
- + j--;
- + }
- + }
- +
- + if (j >= 0) {
- + cpr3_err(vreg, "invalid fuse corner mapping\n");
- + rc = -EINVAL;
- + goto done;
- + }
- +
- + /*
- + * Interpolation is not possible for corners mapped to the lowest fuse
- + * corner so use the fuse corner value directly.
- + */
- + for (i = 0; i <= fmax_corner[0]; i++)
- + vreg->corner[i].open_loop_volt = fuse_volt[0];
- +
- + /* Interpolate voltages for the higher fuse corners. */
- + for (i = 1; i < vreg->fuse_corner_count; i++) {
- + freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
- + volt_low = fuse_volt[i - 1];
- + freq_high = vreg->corner[fmax_corner[i]].proc_freq;
- + volt_high = fuse_volt[i];
- +
- + for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
- + vreg->corner[j].open_loop_volt = cpr3_interpolate(
- + freq_low, volt_low, freq_high, volt_high,
- + vreg->corner[j].proc_freq);
- + }
- +
- +done:
- + if (rc == 0) {
- + cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
- + for (i = 0; i < vreg->corner_count; i++)
- + cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
- + vreg->corner[i].open_loop_volt);
- +
- + rc = cpr3_adjust_open_loop_voltages(vreg);
- + if (rc)
- + cpr3_err(vreg,
- + "open-loop voltage adjustment failed, rc=%d\n",
- + rc);
- + }
- +
- + kfree(fuse_volt);
- + kfree(fmax_corner);
- + return rc;
- +}
- +
- +/**
- + * cpr3_npu_print_settings() - print out NPU CPR configuration settings into
- + * the kernel log for debugging purposes
- + * @vreg: Pointer to the CPR3 regulator
- + */
- +static void cpr3_npu_print_settings(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_corner *corner;
- + int i;
- +
- + cpr3_debug(vreg,
- + "Corner: Frequency (Hz), Fuse Corner, Floor (uV), \
- + Open-Loop (uV), Ceiling (uV)\n");
- + for (i = 0; i < vreg->corner_count; i++) {
- + corner = &vreg->corner[i];
- + cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
- + i, corner->proc_freq, corner->cpr_fuse_corner,
- + corner->floor_volt, corner->open_loop_volt,
- + corner->ceiling_volt);
- + }
- +
- + if (vreg->thread->ctrl->apm)
- + cpr3_debug(vreg, "APM threshold = %d uV, APM adjust = %d uV\n",
- + vreg->thread->ctrl->apm_threshold_volt,
- + vreg->thread->ctrl->apm_adj_volt);
- +}
- +
- +/**
- + * cpr3_ipq807x_npu_calc_temp_based_ol_voltages() - Calculate the open loop
- + * voltages based on temperature based correction margins
- + * @vreg: Pointer to the CPR3 regulator
- + */
- +
- +static int
- +cpr3_ipq807x_npu_calc_temp_based_ol_voltages(struct cpr3_regulator *vreg,
- + bool temp_correction)
- +{
- + int rc, i;
- +
- + rc = cpr3_ipq807x_npu_calculate_open_loop_voltages(vreg,
- + temp_correction);
- + if (rc) {
- + cpr3_err(vreg,
- + "unable to calculate open-loop voltages, rc=%d\n", rc);
- + return rc;
- + }
- +
- + rc = cpr3_limit_open_loop_voltages(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + cpr3_open_loop_voltage_as_ceiling(vreg);
- +
- + rc = cpr3_limit_floor_voltages(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
- + return rc;
- + }
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + if (temp_correction)
- + vreg->corner[i].cold_temp_open_loop_volt =
- + vreg->corner[i].open_loop_volt;
- + else
- + vreg->corner[i].normal_temp_open_loop_volt =
- + vreg->corner[i].open_loop_volt;
- + }
- +
- + cpr3_npu_print_settings(vreg);
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_npu_init_thread() - perform steps necessary to initialize the
- + * configuration data for a CPR3 thread
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_npu_init_thread(struct cpr3_thread *thread)
- +{
- + int rc;
- +
- + rc = cpr3_parse_common_thread_data(thread);
- + if (rc) {
- + cpr3_err(thread->ctrl,
- + "thread %u CPR thread data from DT- failed, rc=%d\n",
- + thread->thread_id, rc);
- + return rc;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_npu_init_regulator() - perform all steps necessary to initialize the
- + * configuration data for a CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_npu_init_regulator(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_ipq807x_npu_fuses *fuse;
- + int rc, cold_temp = 0;
- + bool can_adj_cold_temp = cpr3_can_adjust_cold_temp(vreg);
- +
- + rc = cpr3_ipq807x_npu_read_fuse_data(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
- + return rc;
- + }
- +
- + fuse = vreg->platform_fuses;
- +
- + rc = cpr3_npu_parse_corner_data(vreg);
- + if (rc) {
- + cpr3_err(vreg,
- + "Cannot read CPR corner data from DT, rc=%d\n", rc);
- + return rc;
- + }
- +
- + rc = cpr3_mem_acc_init(vreg);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(vreg,
- + "Cannot initialize mem-acc regulator settings, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (can_adj_cold_temp) {
- + rc = cpr3_ipq807x_npu_calc_temp_based_ol_voltages(vreg, true);
- + if (rc) {
- + cpr3_err(vreg,
- + "unable to calculate open-loop voltages, rc=%d\n", rc);
- + return rc;
- + }
- + }
- +
- + rc = cpr3_ipq807x_npu_calc_temp_based_ol_voltages(vreg, false);
- + if (rc) {
- + cpr3_err(vreg,
- + "unable to calculate open-loop voltages, rc=%d\n", rc);
- + return rc;
- + }
- +
- + if (can_adj_cold_temp) {
- + cpr3_info(vreg,
- + "Normal and Cold condition init done. Default to normal.\n");
- +
- + rc = cpr3_get_cold_temp_threshold(vreg, &cold_temp);
- + if (rc) {
- + cpr3_err(vreg,
- + "Get cold temperature threshold failed, rc=%d\n", rc);
- + return rc;
- + }
- + register_low_temp_notif(NPU_TSENS, cold_temp,
- + cpr3_npu_temp_notify);
- + }
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_npu_init_controller() - perform NPU CPR3 controller specific
- + * initializations
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_npu_init_controller(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + rc = cpr3_parse_open_loop_common_ctrl_data(ctrl);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + ctrl->ctrl_type = CPR_CTRL_TYPE_CPR3;
- + ctrl->supports_hw_closed_loop = false;
- +
- + return 0;
- +}
- +
- +static const struct cpr3_reg_data ipq807x_cpr_npu = {
- + .cpr_valid_fuse_count = IPQ807x_NPU_FUSE_CORNERS,
- + .init_voltage_param = ipq807x_npu_init_voltage_param,
- + .fuse_ref_volt = ipq807x_npu_fuse_ref_volt,
- + .fuse_step_volt = IPQ807x_NPU_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ807x_NPU_CPR_CLOCK_RATE,
- +};
- +
- +static const struct cpr3_reg_data ipq817x_cpr_npu = {
- + .cpr_valid_fuse_count = IPQ817x_NPU_FUSE_CORNERS,
- + .init_voltage_param = ipq807x_npu_init_voltage_param,
- + .fuse_ref_volt = ipq807x_npu_fuse_ref_volt,
- + .fuse_step_volt = IPQ807x_NPU_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ807x_NPU_CPR_CLOCK_RATE,
- +};
- +
- +static const struct cpr3_reg_data ipq9574_cpr_npu = {
- + .cpr_valid_fuse_count = IPQ9574_NPU_FUSE_CORNERS,
- + .init_voltage_param = ipq9574_npu_init_voltage_param,
- + .fuse_ref_volt = ipq9574_npu_fuse_ref_volt,
- + .fuse_step_volt = IPQ9574_NPU_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ9574_NPU_CPR_CLOCK_RATE,
- +};
- +
- +static struct of_device_id cpr3_regulator_match_table[] = {
- + {
- + .compatible = "qcom,cpr3-ipq807x-npu-regulator",
- + .data = &ipq807x_cpr_npu
- + },
- + {
- + .compatible = "qcom,cpr3-ipq817x-npu-regulator",
- + .data = &ipq817x_cpr_npu
- + },
- + {
- + .compatible = "qcom,cpr3-ipq9574-npu-regulator",
- + .data = &ipq9574_cpr_npu
- + },
- + {}
- +};
- +
- +static int cpr3_npu_regulator_probe(struct platform_device *pdev)
- +{
- + struct device *dev = &pdev->dev;
- + struct cpr3_controller *ctrl;
- + int i, rc;
- + const struct of_device_id *match;
- + struct cpr3_reg_data *cpr_data;
- +
- + if (!dev->of_node) {
- + dev_err(dev, "Device tree node is missing\n");
- + return -EINVAL;
- + }
- +
- + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
- + if (!ctrl)
- + return -ENOMEM;
- + g_ctrl = ctrl;
- +
- + match = of_match_device(cpr3_regulator_match_table, &pdev->dev);
- + if (!match)
- + return -ENODEV;
- +
- + cpr_data = (struct cpr3_reg_data *)match->data;
- + g_valid_npu_fuse_count = cpr_data->cpr_valid_fuse_count;
- + dev_info(dev, "NPU CPR valid fuse count: %d\n", g_valid_npu_fuse_count);
- + ctrl->cpr_clock_rate = cpr_data->cpr_clk_rate;
- +
- + ctrl->dev = dev;
- + /* Set to false later if anything precludes CPR operation. */
- + ctrl->cpr_allowed_hw = true;
- +
- + rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
- + &ctrl->name);
- + if (rc) {
- + cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr3_map_fuse_base(ctrl, pdev);
- + if (rc) {
- + cpr3_err(ctrl, "could not map fuse base address\n");
- + return rc;
- + }
- +
- + rc = cpr3_read_tcsr_setting(ctrl, pdev, IPQ807x_NPU_CPR_TCSR_START,
- + IPQ807x_NPU_CPR_TCSR_END);
- + if (rc) {
- + cpr3_err(ctrl, "could not read CPR tcsr rsetting\n");
- + return rc;
- + }
- +
- + rc = cpr3_allocate_threads(ctrl, 0, 0);
- + if (rc) {
- + cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (ctrl->thread_count != 1) {
- + cpr3_err(ctrl, "expected 1 thread but found %d\n",
- + ctrl->thread_count);
- + return -EINVAL;
- + }
- +
- + rc = cpr3_npu_init_controller(ctrl);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr3_npu_init_thread(&ctrl->thread[0]);
- + if (rc) {
- + cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + for (i = 0; i < ctrl->thread[0].vreg_count; i++) {
- + ctrl->thread[0].vreg[i].cpr3_regulator_data = cpr_data;
- + rc = cpr3_npu_init_regulator(&ctrl->thread[0].vreg[i]);
- + if (rc) {
- + cpr3_err(&ctrl->thread[0].vreg[i], "regulator initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + platform_set_drvdata(pdev, ctrl);
- +
- + return cpr3_open_loop_regulator_register(pdev, ctrl);
- +}
- +
- +static int cpr3_npu_regulator_remove(struct platform_device *pdev)
- +{
- + struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
- +
- + return cpr3_open_loop_regulator_unregister(ctrl);
- +}
- +
- +static struct platform_driver cpr3_npu_regulator_driver = {
- + .driver = {
- + .name = "qcom,cpr3-npu-regulator",
- + .of_match_table = cpr3_regulator_match_table,
- + .owner = THIS_MODULE,
- + },
- + .probe = cpr3_npu_regulator_probe,
- + .remove = cpr3_npu_regulator_remove,
- +};
- +
- +static int cpr3_regulator_init(void)
- +{
- + return platform_driver_register(&cpr3_npu_regulator_driver);
- +}
- +arch_initcall(cpr3_regulator_init);
- +
- +static void cpr3_regulator_exit(void)
- +{
- + platform_driver_unregister(&cpr3_npu_regulator_driver);
- +}
- +module_exit(cpr3_regulator_exit);
- +
- +MODULE_DESCRIPTION("QCOM CPR3 NPU regulator driver");
- +MODULE_LICENSE("Dual BSD/GPLv2");
- +MODULE_ALIAS("platform:npu-ipq807x");
- --- /dev/null
- +++ b/drivers/regulator/cpr3-regulator.c
- @@ -0,0 +1,5111 @@
- +/*
- + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 as published by the Free Software Foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + */
- +
- +#define pr_fmt(fmt) "%s: " fmt, __func__
- +
- +#include <linux/bitops.h>
- +#include <linux/debugfs.h>
- +#include <linux/delay.h>
- +#include <linux/err.h>
- +#include <linux/init.h>
- +#include <linux/interrupt.h>
- +#include <linux/io.h>
- +#include <linux/kernel.h>
- +#include <linux/ktime.h>
- +#include <linux/list.h>
- +#include <linux/module.h>
- +#include <linux/of.h>
- +#include <linux/of_device.h>
- +#include <linux/platform_device.h>
- +#include <linux/pm_opp.h>
- +#include <linux/slab.h>
- +#include <linux/sort.h>
- +#include <linux/string.h>
- +#include <linux/uaccess.h>
- +#include <linux/regulator/driver.h>
- +#include <linux/regulator/machine.h>
- +#include <linux/regulator/of_regulator.h>
- +#include <linux/panic_notifier.h>
- +
- +#include "cpr3-regulator.h"
- +
- +#define CPR3_REGULATOR_CORNER_INVALID (-1)
- +#define CPR3_RO_MASK GENMASK(CPR3_RO_COUNT - 1, 0)
- +
- +/* CPR3 registers */
- +#define CPR3_REG_CPR_CTL 0x4
- +#define CPR3_CPR_CTL_LOOP_EN_MASK BIT(0)
- +#define CPR3_CPR_CTL_LOOP_ENABLE BIT(0)
- +#define CPR3_CPR_CTL_LOOP_DISABLE 0
- +#define CPR3_CPR_CTL_IDLE_CLOCKS_MASK GENMASK(5, 1)
- +#define CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT 1
- +#define CPR3_CPR_CTL_COUNT_MODE_MASK GENMASK(7, 6)
- +#define CPR3_CPR_CTL_COUNT_MODE_SHIFT 6
- +#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN 0
- +#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MAX 1
- +#define CPR3_CPR_CTL_COUNT_MODE_STAGGERED 2
- +#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE 3
- +#define CPR3_CPR_CTL_COUNT_REPEAT_MASK GENMASK(31, 9)
- +#define CPR3_CPR_CTL_COUNT_REPEAT_SHIFT 9
- +
- +#define CPR3_REG_CPR_STATUS 0x8
- +#define CPR3_CPR_STATUS_BUSY_MASK BIT(0)
- +#define CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK BIT(1)
- +
- +/*
- + * This register is not present on controllers that support HW closed-loop
- + * except CPR4 APSS controller.
- + */
- +#define CPR3_REG_CPR_TIMER_AUTO_CONT 0xC
- +
- +#define CPR3_REG_CPR_STEP_QUOT 0x14
- +#define CPR3_CPR_STEP_QUOT_MIN_MASK GENMASK(5, 0)
- +#define CPR3_CPR_STEP_QUOT_MIN_SHIFT 0
- +#define CPR3_CPR_STEP_QUOT_MAX_MASK GENMASK(11, 6)
- +#define CPR3_CPR_STEP_QUOT_MAX_SHIFT 6
- +
- +#define CPR3_REG_GCNT(ro) (0xA0 + 0x4 * (ro))
- +
- +#define CPR3_REG_SENSOR_BYPASS_WRITE(sensor) (0xE0 + 0x4 * ((sensor) / 32))
- +#define CPR3_REG_SENSOR_BYPASS_WRITE_BANK(bank) (0xE0 + 0x4 * (bank))
- +
- +#define CPR3_REG_SENSOR_MASK_WRITE(sensor) (0x120 + 0x4 * ((sensor) / 32))
- +#define CPR3_REG_SENSOR_MASK_WRITE_BANK(bank) (0x120 + 0x4 * (bank))
- +#define CPR3_REG_SENSOR_MASK_READ(sensor) (0x140 + 0x4 * ((sensor) / 32))
- +
- +#define CPR3_REG_SENSOR_OWNER(sensor) (0x200 + 0x4 * (sensor))
- +
- +#define CPR3_REG_CONT_CMD 0x800
- +#define CPR3_CONT_CMD_ACK 0x1
- +#define CPR3_CONT_CMD_NACK 0x0
- +
- +#define CPR3_REG_THRESH(thread) (0x808 + 0x440 * (thread))
- +#define CPR3_THRESH_CONS_DOWN_MASK GENMASK(3, 0)
- +#define CPR3_THRESH_CONS_DOWN_SHIFT 0
- +#define CPR3_THRESH_CONS_UP_MASK GENMASK(7, 4)
- +#define CPR3_THRESH_CONS_UP_SHIFT 4
- +#define CPR3_THRESH_DOWN_THRESH_MASK GENMASK(12, 8)
- +#define CPR3_THRESH_DOWN_THRESH_SHIFT 8
- +#define CPR3_THRESH_UP_THRESH_MASK GENMASK(17, 13)
- +#define CPR3_THRESH_UP_THRESH_SHIFT 13
- +
- +#define CPR3_REG_RO_MASK(thread) (0x80C + 0x440 * (thread))
- +
- +#define CPR3_REG_RESULT0(thread) (0x810 + 0x440 * (thread))
- +#define CPR3_RESULT0_BUSY_MASK BIT(0)
- +#define CPR3_RESULT0_STEP_DN_MASK BIT(1)
- +#define CPR3_RESULT0_STEP_UP_MASK BIT(2)
- +#define CPR3_RESULT0_ERROR_STEPS_MASK GENMASK(7, 3)
- +#define CPR3_RESULT0_ERROR_STEPS_SHIFT 3
- +#define CPR3_RESULT0_ERROR_MASK GENMASK(19, 8)
- +#define CPR3_RESULT0_ERROR_SHIFT 8
- +#define CPR3_RESULT0_NEGATIVE_MASK BIT(20)
- +
- +#define CPR3_REG_RESULT1(thread) (0x814 + 0x440 * (thread))
- +#define CPR3_RESULT1_QUOT_MIN_MASK GENMASK(11, 0)
- +#define CPR3_RESULT1_QUOT_MIN_SHIFT 0
- +#define CPR3_RESULT1_QUOT_MAX_MASK GENMASK(23, 12)
- +#define CPR3_RESULT1_QUOT_MAX_SHIFT 12
- +#define CPR3_RESULT1_RO_MIN_MASK GENMASK(27, 24)
- +#define CPR3_RESULT1_RO_MIN_SHIFT 24
- +#define CPR3_RESULT1_RO_MAX_MASK GENMASK(31, 28)
- +#define CPR3_RESULT1_RO_MAX_SHIFT 28
- +
- +#define CPR3_REG_RESULT2(thread) (0x818 + 0x440 * (thread))
- +#define CPR3_RESULT2_STEP_QUOT_MIN_MASK GENMASK(5, 0)
- +#define CPR3_RESULT2_STEP_QUOT_MIN_SHIFT 0
- +#define CPR3_RESULT2_STEP_QUOT_MAX_MASK GENMASK(11, 6)
- +#define CPR3_RESULT2_STEP_QUOT_MAX_SHIFT 6
- +#define CPR3_RESULT2_SENSOR_MIN_MASK GENMASK(23, 16)
- +#define CPR3_RESULT2_SENSOR_MIN_SHIFT 16
- +#define CPR3_RESULT2_SENSOR_MAX_MASK GENMASK(31, 24)
- +#define CPR3_RESULT2_SENSOR_MAX_SHIFT 24
- +
- +#define CPR3_REG_IRQ_EN 0x81C
- +#define CPR3_REG_IRQ_CLEAR 0x820
- +#define CPR3_REG_IRQ_STATUS 0x824
- +#define CPR3_IRQ_UP BIT(3)
- +#define CPR3_IRQ_MID BIT(2)
- +#define CPR3_IRQ_DOWN BIT(1)
- +
- +#define CPR3_REG_TARGET_QUOT(thread, ro) \
- + (0x840 + 0x440 * (thread) + 0x4 * (ro))
- +
- +/* Registers found only on controllers that support HW closed-loop. */
- +#define CPR3_REG_PD_THROTTLE 0xE8
- +#define CPR3_PD_THROTTLE_DISABLE 0x0
- +
- +#define CPR3_REG_HW_CLOSED_LOOP 0x3000
- +#define CPR3_HW_CLOSED_LOOP_ENABLE 0x0
- +#define CPR3_HW_CLOSED_LOOP_DISABLE 0x1
- +
- +#define CPR3_REG_CPR_TIMER_MID_CONT 0x3004
- +#define CPR3_REG_CPR_TIMER_UP_DN_CONT 0x3008
- +
- +#define CPR3_REG_LAST_MEASUREMENT 0x7F8
- +#define CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT 0
- +#define CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT 4
- +#define CPR3_LAST_MEASUREMENT_THREAD_DN(thread) \
- + (BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT)
- +#define CPR3_LAST_MEASUREMENT_THREAD_UP(thread) \
- + (BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT)
- +#define CPR3_LAST_MEASUREMENT_AGGR_DN BIT(8)
- +#define CPR3_LAST_MEASUREMENT_AGGR_MID BIT(9)
- +#define CPR3_LAST_MEASUREMENT_AGGR_UP BIT(10)
- +#define CPR3_LAST_MEASUREMENT_VALID BIT(11)
- +#define CPR3_LAST_MEASUREMENT_SAW_ERROR BIT(12)
- +#define CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK GENMASK(23, 16)
- +#define CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT 16
- +
- +/* CPR4 controller specific registers and bit definitions */
- +#define CPR4_REG_CPR_TIMER_CLAMP 0x10
- +#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN BIT(27)
- +
- +#define CPR4_REG_MISC 0x700
- +#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK GENMASK(23, 20)
- +#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT 20
- +#define CPR4_MISC_TEMP_SENSOR_ID_START_MASK GENMASK(27, 24)
- +#define CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT 24
- +#define CPR4_MISC_TEMP_SENSOR_ID_END_MASK GENMASK(31, 28)
- +#define CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT 28
- +
- +#define CPR4_REG_SAW_ERROR_STEP_LIMIT 0x7A4
- +#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK GENMASK(4, 0)
- +#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT 0
- +#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK GENMASK(9, 5)
- +#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT 5
- +
- +#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS 0x7A8
- +#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK GENMASK(28, 18)
- +#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT 18
- +
- +#define CPR4_REG_MARGIN_TEMP_CORE(core) (0x7AC + 0x4 * (core))
- +#define CPR4_MARGIN_TEMP_CORE_ADJ_MASK GENMASK(7, 0)
- +#define CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT 8
- +
- +#define CPR4_REG_MARGIN_TEMP_POINT0N1 0x7F0
- +#define CPR4_MARGIN_TEMP_POINT0_MASK GENMASK(11, 0)
- +#define CPR4_MARGIN_TEMP_POINT0_SHIFT 0
- +#define CPR4_MARGIN_TEMP_POINT1_MASK GENMASK(23, 12)
- +#define CPR4_MARGIN_TEMP_POINT1_SHIFT 12
- +#define CPR4_REG_MARGIN_TEMP_POINT2 0x7F4
- +#define CPR4_MARGIN_TEMP_POINT2_MASK GENMASK(11, 0)
- +#define CPR4_MARGIN_TEMP_POINT2_SHIFT 0
- +
- +#define CPR4_REG_MARGIN_ADJ_CTL 0x7F8
- +#define CPR4_MARGIN_ADJ_CTL_BOOST_EN BIT(0)
- +#define CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN BIT(1)
- +#define CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN BIT(2)
- +#define CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN BIT(3)
- +#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK BIT(4)
- +#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE BIT(4)
- +#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE 0
- +#define CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN BIT(7)
- +#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN BIT(8)
- +#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK GENMASK(16, 12)
- +#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT 12
- +#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK GENMASK(21, 19)
- +#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT 19
- +#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK GENMASK(25, 22)
- +#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT 22
- +#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK GENMASK(31, 26)
- +#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT 26
- +
- +#define CPR4_REG_CPR_MASK_THREAD(thread) (0x80C + 0x440 * (thread))
- +#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD BIT(31)
- +#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK GENMASK(15, 0)
- +
- +/*
- + * The amount of time to wait for the CPR controller to become idle when
- + * performing an aging measurement.
- + */
- +#define CPR3_AGING_MEASUREMENT_TIMEOUT_NS 5000000
- +
- +/*
- + * The number of individual aging measurements to perform which are then
- + * averaged together in order to determine the final aging adjustment value.
- + */
- +#define CPR3_AGING_MEASUREMENT_ITERATIONS 16
- +
- +/*
- + * Aging measurements for the aged and unaged ring oscillators take place a few
- + * microseconds apart. If the vdd-supply voltage fluctuates between the two
- + * measurements, then the difference between them will be incorrect. The
- + * difference could end up too high or too low. This constant defines the
- + * number of lowest and highest measurements to ignore when averaging.
- + */
- +#define CPR3_AGING_MEASUREMENT_FILTER 3
- +
- +/*
- + * The number of times to attempt the full aging measurement sequence before
- + * declaring a measurement failure.
- + */
- +#define CPR3_AGING_RETRY_COUNT 5
- +
- +/*
- + * The maximum time to wait in microseconds for a CPR register write to
- + * complete.
- + */
- +#define CPR3_REGISTER_WRITE_DELAY_US 200
- +
- +static DEFINE_MUTEX(cpr3_controller_list_mutex);
- +static LIST_HEAD(cpr3_controller_list);
- +static struct dentry *cpr3_debugfs_base;
- +
- +/**
- + * cpr3_read() - read four bytes from the memory address specified
- + * @ctrl: Pointer to the CPR3 controller
- + * @offset: Offset in bytes from the CPR3 controller's base address
- + *
- + * Return: memory address value
- + */
- +static inline u32 cpr3_read(struct cpr3_controller *ctrl, u32 offset)
- +{
- + if (!ctrl->cpr_enabled) {
- + cpr3_err(ctrl, "CPR register reads are not possible when CPR clocks are disabled\n");
- + return 0;
- + }
- +
- + return readl_relaxed(ctrl->cpr_ctrl_base + offset);
- +}
- +
- +/**
- + * cpr3_write() - write four bytes to the memory address specified
- + * @ctrl: Pointer to the CPR3 controller
- + * @offset: Offset in bytes from the CPR3 controller's base address
- + * @value: Value to write to the memory address
- + *
- + * Return: none
- + */
- +static inline void cpr3_write(struct cpr3_controller *ctrl, u32 offset,
- + u32 value)
- +{
- + if (!ctrl->cpr_enabled) {
- + cpr3_err(ctrl, "CPR register writes are not possible when CPR clocks are disabled\n");
- + return;
- + }
- +
- + writel_relaxed(value, ctrl->cpr_ctrl_base + offset);
- +}
- +
- +/**
- + * cpr3_masked_write() - perform a read-modify-write sequence so that only
- + * masked bits are modified
- + * @ctrl: Pointer to the CPR3 controller
- + * @offset: Offset in bytes from the CPR3 controller's base address
- + * @mask: Mask identifying the bits that should be modified
- + * @value: Value to write to the memory address
- + *
- + * Return: none
- + */
- +static inline void cpr3_masked_write(struct cpr3_controller *ctrl, u32 offset,
- + u32 mask, u32 value)
- +{
- + u32 reg_val, orig_val;
- +
- + if (!ctrl->cpr_enabled) {
- + cpr3_err(ctrl, "CPR register writes are not possible when CPR clocks are disabled\n");
- + return;
- + }
- +
- + reg_val = orig_val = readl_relaxed(ctrl->cpr_ctrl_base + offset);
- + reg_val &= ~mask;
- + reg_val |= value & mask;
- +
- + if (reg_val != orig_val)
- + writel_relaxed(reg_val, ctrl->cpr_ctrl_base + offset);
- +}
- +
- +/**
- + * cpr3_ctrl_loop_enable() - enable the CPR sensing loop for a given controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: none
- + */
- +static inline void cpr3_ctrl_loop_enable(struct cpr3_controller *ctrl)
- +{
- + if (ctrl->cpr_enabled && !(ctrl->aggr_corner.sdelta
- + && ctrl->aggr_corner.sdelta->allow_boost))
- + cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
- + CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_ENABLE);
- +}
- +
- +/**
- + * cpr3_ctrl_loop_disable() - disable the CPR sensing loop for a given
- + * controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: none
- + */
- +static inline void cpr3_ctrl_loop_disable(struct cpr3_controller *ctrl)
- +{
- + if (ctrl->cpr_enabled)
- + cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
- + CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_DISABLE);
- +}
- +
- +/**
- + * cpr3_clock_enable() - prepare and enable all clocks used by this CPR3
- + * controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_clock_enable(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + rc = clk_prepare_enable(ctrl->bus_clk);
- + if (rc) {
- + cpr3_err(ctrl, "failed to enable bus clock, rc=%d\n", rc);
- + return rc;
- + }
- +
- + rc = clk_prepare_enable(ctrl->iface_clk);
- + if (rc) {
- + cpr3_err(ctrl, "failed to enable interface clock, rc=%d\n", rc);
- + clk_disable_unprepare(ctrl->bus_clk);
- + return rc;
- + }
- +
- + rc = clk_prepare_enable(ctrl->core_clk);
- + if (rc) {
- + cpr3_err(ctrl, "failed to enable core clock, rc=%d\n", rc);
- + clk_disable_unprepare(ctrl->iface_clk);
- + clk_disable_unprepare(ctrl->bus_clk);
- + return rc;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_clock_disable() - disable and unprepare all clocks used by this CPR3
- + * controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: none
- + */
- +static void cpr3_clock_disable(struct cpr3_controller *ctrl)
- +{
- + clk_disable_unprepare(ctrl->core_clk);
- + clk_disable_unprepare(ctrl->iface_clk);
- + clk_disable_unprepare(ctrl->bus_clk);
- +}
- +
- +/**
- + * cpr3_ctrl_clear_cpr4_config() - clear the CPR4 register configuration
- + * programmed for current aggregated corner of a given controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static inline int cpr3_ctrl_clear_cpr4_config(struct cpr3_controller *ctrl)
- +{
- + struct cpr4_sdelta *aggr_sdelta = ctrl->aggr_corner.sdelta;
- + bool cpr_enabled = ctrl->cpr_enabled;
- + int i, rc = 0;
- +
- + if (!aggr_sdelta || !(aggr_sdelta->allow_core_count_adj
- + || aggr_sdelta->allow_temp_adj || aggr_sdelta->allow_boost))
- + /* cpr4 features are not enabled */
- + return 0;
- +
- + /* Ensure that CPR clocks are enabled before writing to registers. */
- + if (!cpr_enabled) {
- + rc = cpr3_clock_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
- + return rc;
- + }
- + ctrl->cpr_enabled = true;
- + }
- +
- + /*
- + * Clear feature enable configuration made for current
- + * aggregated corner.
- + */
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
- + | CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
- + | CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
- + | CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
- + | CPR4_MARGIN_ADJ_CTL_BOOST_EN
- + | CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK, 0);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MISC,
- + CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
- + 0 << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
- +
- + for (i = 0; i <= aggr_sdelta->max_core_count; i++) {
- + /* Clear voltage margin adjustments programmed in TEMP_COREi */
- + cpr3_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE(i), 0);
- + }
- +
- + /* Turn off CPR clocks if they were off before this function call. */
- + if (!cpr_enabled) {
- + cpr3_clock_disable(ctrl);
- + ctrl->cpr_enabled = false;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_closed_loop_enable() - enable logical CPR closed-loop operation
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_closed_loop_enable(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + if (!ctrl->cpr_allowed_hw || !ctrl->cpr_allowed_sw) {
- + cpr3_err(ctrl, "cannot enable closed-loop CPR operation because it is disallowed\n");
- + return -EPERM;
- + } else if (ctrl->cpr_enabled) {
- + /* Already enabled */
- + return 0;
- + } else if (ctrl->cpr_suspended) {
- + /*
- + * CPR must remain disabled as the system is entering suspend.
- + */
- + return 0;
- + }
- +
- + rc = cpr3_clock_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "unable to enable CPR clocks, rc=%d\n", rc);
- + return rc;
- + }
- +
- + ctrl->cpr_enabled = true;
- + cpr3_debug(ctrl, "CPR closed-loop operation enabled\n");
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_closed_loop_disable() - disable logical CPR closed-loop operation
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static inline int cpr3_closed_loop_disable(struct cpr3_controller *ctrl)
- +{
- + if (!ctrl->cpr_enabled) {
- + /* Already disabled */
- + return 0;
- + }
- +
- + cpr3_clock_disable(ctrl);
- + ctrl->cpr_enabled = false;
- + cpr3_debug(ctrl, "CPR closed-loop operation disabled\n");
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_get_gcnt() - returns the GCNT register value corresponding
- + * to the clock rate and sensor time of the CPR3 controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: GCNT value
- + */
- +static u32 cpr3_regulator_get_gcnt(struct cpr3_controller *ctrl)
- +{
- + u64 temp;
- + unsigned int remainder;
- + u32 gcnt;
- +
- + temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->sensor_time;
- + remainder = do_div(temp, 1000000000);
- + if (remainder)
- + temp++;
- + /*
- + * GCNT == 0 corresponds to a single ref clock measurement interval so
- + * offset GCNT values by 1.
- + */
- + gcnt = temp - 1;
- +
- + return gcnt;
- +}
- +
- +/**
- + * cpr3_regulator_init_thread() - performs hardware initialization of CPR
- + * thread registers
- + * @thread: Pointer to the CPR3 thread
- + *
- + * CPR interface/bus clocks must be enabled before calling this function.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_init_thread(struct cpr3_thread *thread)
- +{
- + u32 reg;
- +
- + reg = (thread->consecutive_up << CPR3_THRESH_CONS_UP_SHIFT)
- + & CPR3_THRESH_CONS_UP_MASK;
- + reg |= (thread->consecutive_down << CPR3_THRESH_CONS_DOWN_SHIFT)
- + & CPR3_THRESH_CONS_DOWN_MASK;
- + reg |= (thread->up_threshold << CPR3_THRESH_UP_THRESH_SHIFT)
- + & CPR3_THRESH_UP_THRESH_MASK;
- + reg |= (thread->down_threshold << CPR3_THRESH_DOWN_THRESH_SHIFT)
- + & CPR3_THRESH_DOWN_THRESH_MASK;
- +
- + cpr3_write(thread->ctrl, CPR3_REG_THRESH(thread->thread_id), reg);
- +
- + /*
- + * Mask all RO's initially so that unused thread doesn't contribute
- + * to closed-loop voltage.
- + */
- + cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
- + CPR3_RO_MASK);
- +
- + return 0;
- +}
- +
- +/**
- + * cpr4_regulator_init_temp_points() - performs hardware initialization of CPR4
- + * registers to track tsen temperature data and also specify the
- + * temperature band range values to apply different voltage margins
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * CPR interface/bus clocks must be enabled before calling this function.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_regulator_init_temp_points(struct cpr3_controller *ctrl)
- +{
- + if (!ctrl->allow_temp_adj)
- + return 0;
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MISC,
- + CPR4_MISC_TEMP_SENSOR_ID_START_MASK,
- + ctrl->temp_sensor_id_start
- + << CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MISC,
- + CPR4_MISC_TEMP_SENSOR_ID_END_MASK,
- + ctrl->temp_sensor_id_end
- + << CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT2,
- + CPR4_MARGIN_TEMP_POINT2_MASK,
- + (ctrl->temp_band_count == 4 ? ctrl->temp_points[2] : 0x7FF)
- + << CPR4_MARGIN_TEMP_POINT2_SHIFT);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
- + CPR4_MARGIN_TEMP_POINT1_MASK,
- + (ctrl->temp_band_count >= 3 ? ctrl->temp_points[1] : 0x7FF)
- + << CPR4_MARGIN_TEMP_POINT1_SHIFT);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
- + CPR4_MARGIN_TEMP_POINT0_MASK,
- + (ctrl->temp_band_count >= 2 ? ctrl->temp_points[0] : 0x7FF)
- + << CPR4_MARGIN_TEMP_POINT0_SHIFT);
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_init_cpr4() - performs hardware initialization at the
- + * controller and thread level required for CPR4 operation.
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * CPR interface/bus clocks must be enabled before calling this function.
- + * This function allocates sdelta structures and sdelta tables for aggregated
- + * corners of the controller and its threads.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
- +{
- + struct cpr3_thread *thread;
- + struct cpr3_regulator *vreg;
- + struct cpr4_sdelta *sdelta;
- + int i, j, ctrl_max_core_count, thread_max_core_count, rc = 0;
- + bool ctrl_valid_sdelta, thread_valid_sdelta;
- + u32 pmic_step_size = 1;
- + int thread_id = 0;
- + u64 temp;
- +
- + if (ctrl->supports_hw_closed_loop) {
- + if (ctrl->saw_use_unit_mV)
- + pmic_step_size = ctrl->step_volt / 1000;
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK,
- + (pmic_step_size
- + << CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT));
- +
- + cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
- + CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
- + (ctrl->down_error_step_limit
- + << CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT));
- +
- + cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
- + CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
- + (ctrl->up_error_step_limit
- + << CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));
- +
- + /*
- + * Enable thread aggregation regardless of which threads are
- + * enabled or disabled.
- + */
- + cpr3_masked_write(ctrl, CPR4_REG_CPR_TIMER_CLAMP,
- + CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
- + CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN);
- +
- + switch (ctrl->thread_count) {
- + case 0:
- + /* Disable both threads */
- + cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(0),
- + CPR4_CPR_MASK_THREAD_DISABLE_THREAD
- + | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
- + CPR4_CPR_MASK_THREAD_DISABLE_THREAD
- + | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(1),
- + CPR4_CPR_MASK_THREAD_DISABLE_THREAD
- + | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
- + CPR4_CPR_MASK_THREAD_DISABLE_THREAD
- + | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
- + break;
- + case 1:
- + /* Disable unused thread */
- + thread_id = ctrl->thread[0].thread_id ? 0 : 1;
- + cpr3_masked_write(ctrl,
- + CPR4_REG_CPR_MASK_THREAD(thread_id),
- + CPR4_CPR_MASK_THREAD_DISABLE_THREAD
- + | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
- + CPR4_CPR_MASK_THREAD_DISABLE_THREAD
- + | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
- + break;
- + }
- + }
- +
- + if (!ctrl->allow_core_count_adj && !ctrl->allow_temp_adj
- + && !ctrl->allow_boost) {
- + /*
- + * Skip below configuration as none of the features
- + * are enabled.
- + */
- + return rc;
- + }
- +
- + if (ctrl->supports_hw_closed_loop)
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN,
- + CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK,
- + ctrl->step_quot_fixed
- + << CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT);
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN,
- + (ctrl->use_dynamic_step_quot
- + ? CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN : 0));
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK,
- + ctrl->initial_temp_band
- + << CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT);
- +
- + rc = cpr4_regulator_init_temp_points(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "initialize temp points failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + if (ctrl->voltage_settling_time) {
- + /*
- + * Configure the settling timer used to account for
- + * one VDD supply step.
- + */
- + temp = (u64)ctrl->cpr_clock_rate
- + * (u64)ctrl->voltage_settling_time;
- + do_div(temp, 1000000000);
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
- + CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK,
- + temp
- + << CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT);
- + }
- +
- + /*
- + * Allocate memory for cpr4_sdelta structure and sdelta table for
- + * controller aggregated corner by finding the maximum core count
- + * used by any cpr3 regulators.
- + */
- + ctrl_max_core_count = 1;
- + ctrl_valid_sdelta = false;
- + for (i = 0; i < ctrl->thread_count; i++) {
- + thread = &ctrl->thread[i];
- +
- + /*
- + * Allocate memory for cpr4_sdelta structure and sdelta table
- + * for thread aggregated corner by finding the maximum core
- + * count used by any cpr3 regulators of the thread.
- + */
- + thread_max_core_count = 1;
- + thread_valid_sdelta = false;
- + for (j = 0; j < thread->vreg_count; j++) {
- + vreg = &thread->vreg[j];
- + thread_max_core_count = max(thread_max_core_count,
- + vreg->max_core_count);
- + thread_valid_sdelta |= (vreg->allow_core_count_adj
- + | vreg->allow_temp_adj
- + | vreg->allow_boost);
- + }
- + if (thread_valid_sdelta) {
- + sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta),
- + GFP_KERNEL);
- + if (!sdelta)
- + return -ENOMEM;
- +
- + sdelta->table = devm_kcalloc(ctrl->dev,
- + thread_max_core_count
- + * ctrl->temp_band_count,
- + sizeof(*sdelta->table),
- + GFP_KERNEL);
- + if (!sdelta->table)
- + return -ENOMEM;
- +
- + sdelta->boost_table = devm_kcalloc(ctrl->dev,
- + ctrl->temp_band_count,
- + sizeof(*sdelta->boost_table),
- + GFP_KERNEL);
- + if (!sdelta->boost_table)
- + return -ENOMEM;
- +
- + thread->aggr_corner.sdelta = sdelta;
- + }
- +
- + ctrl_valid_sdelta |= thread_valid_sdelta;
- + ctrl_max_core_count = max(ctrl_max_core_count,
- + thread_max_core_count);
- + }
- +
- + if (ctrl_valid_sdelta) {
- + sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta), GFP_KERNEL);
- + if (!sdelta)
- + return -ENOMEM;
- +
- + sdelta->table = devm_kcalloc(ctrl->dev, ctrl_max_core_count
- + * ctrl->temp_band_count,
- + sizeof(*sdelta->table), GFP_KERNEL);
- + if (!sdelta->table)
- + return -ENOMEM;
- +
- + sdelta->boost_table = devm_kcalloc(ctrl->dev,
- + ctrl->temp_band_count,
- + sizeof(*sdelta->boost_table),
- + GFP_KERNEL);
- + if (!sdelta->boost_table)
- + return -ENOMEM;
- +
- + ctrl->aggr_corner.sdelta = sdelta;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_write_temp_core_margin() - programs hardware SDELTA registers with
- + * the voltage margin adjustments that need to be applied for
- + * different online core-count and temperature bands.
- + * @ctrl: Pointer to the CPR3 controller
- + * @addr: SDELTA register address
- + * @temp_core_adj: Array of voltage margin values for different temperature
- + * bands.
- + *
- + * CPR interface/bus clocks must be enabled before calling this function.
- + *
- + * Return: none
- + */
- +static void cpr3_write_temp_core_margin(struct cpr3_controller *ctrl,
- + int addr, int *temp_core_adj)
- +{
- + int i, margin_steps;
- + u32 reg = 0;
- +
- + for (i = 0; i < ctrl->temp_band_count; i++) {
- + margin_steps = max(min(temp_core_adj[i], 127), -128);
- + reg |= (margin_steps & CPR4_MARGIN_TEMP_CORE_ADJ_MASK) <<
- + (i * CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT);
- + }
- +
- + cpr3_write(ctrl, addr, reg);
- + cpr3_debug(ctrl, "sdelta offset=0x%08x, val=0x%08x\n", addr, reg);
- +}
- +
- +/**
- + * cpr3_controller_program_sdelta() - programs hardware SDELTA registers with
- + * the voltage margin adjustments that need to be applied at
- + * different online core-count and temperature bands. Also,
- + * programs hardware register configuration for per-online-core
- + * and per-temperature based adjustments.
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * CPR interface/bus clocks must be enabled before calling this function.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_controller_program_sdelta(struct cpr3_controller *ctrl)
- +{
- + struct cpr3_corner *corner = &ctrl->aggr_corner;
- + struct cpr4_sdelta *sdelta = corner->sdelta;
- + int i, index, max_core_count, rc = 0;
- + bool cpr_enabled = ctrl->cpr_enabled;
- +
- + if (!sdelta)
- + /* cpr4_sdelta not defined for current aggregated corner */
- + return 0;
- +
- + if (ctrl->supports_hw_closed_loop && ctrl->cpr_enabled) {
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
- + (ctrl->use_hw_closed_loop && !sdelta->allow_boost)
- + ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0);
- + }
- +
- + if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj
- + && !sdelta->allow_boost) {
- + /*
- + * Per-online-core, per-temperature and voltage boost
- + * adjustments are disabled for this aggregation corner.
- + */
- + return 0;
- + }
- +
- + /* Ensure that CPR clocks are enabled before writing to registers. */
- + if (!cpr_enabled) {
- + rc = cpr3_clock_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
- + return rc;
- + }
- + ctrl->cpr_enabled = true;
- + }
- +
- + max_core_count = sdelta->max_core_count;
- +
- + if (sdelta->allow_core_count_adj || sdelta->allow_temp_adj) {
- + if (sdelta->allow_core_count_adj) {
- + /* Program TEMP_CORE0 to same margins as TEMP_CORE1 */
- + cpr3_write_temp_core_margin(ctrl,
- + CPR4_REG_MARGIN_TEMP_CORE(0),
- + &sdelta->table[0]);
- + }
- +
- + for (i = 0; i < max_core_count; i++) {
- + index = i * sdelta->temp_band_count;
- + /*
- + * Program TEMP_COREi with voltage margin adjustments
- + * that need to be applied when the number of cores
- + * becomes i.
- + */
- + cpr3_write_temp_core_margin(ctrl,
- + CPR4_REG_MARGIN_TEMP_CORE(
- + sdelta->allow_core_count_adj
- + ? i + 1 : max_core_count),
- + &sdelta->table[index]);
- + }
- + }
- +
- + if (sdelta->allow_boost) {
- + /* Program only boost_num_cores row of SDELTA */
- + cpr3_write_temp_core_margin(ctrl,
- + CPR4_REG_MARGIN_TEMP_CORE(sdelta->boost_num_cores),
- + &sdelta->boost_table[0]);
- + }
- +
- + if (!sdelta->allow_core_count_adj && !sdelta->allow_boost) {
- + cpr3_masked_write(ctrl, CPR4_REG_MISC,
- + CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
- + max_core_count
- + << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
- + }
- +
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
- + | CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
- + | CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
- + | CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
- + | CPR4_MARGIN_ADJ_CTL_BOOST_EN,
- + max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
- + | ((sdelta->allow_core_count_adj || sdelta->allow_boost)
- + ? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
- + | ((sdelta->allow_temp_adj && ctrl->supports_hw_closed_loop)
- + ? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
- + | (((ctrl->use_hw_closed_loop && !sdelta->allow_boost)
- + || !ctrl->supports_hw_closed_loop)
- + ? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
- + | (sdelta->allow_boost
- + ? CPR4_MARGIN_ADJ_CTL_BOOST_EN : 0));
- +
- + /*
- + * Ensure that all previous CPR register writes have completed before
- + * continuing.
- + */
- + mb();
- +
- + /* Turn off CPR clocks if they were off before this function call. */
- + if (!cpr_enabled) {
- + cpr3_clock_disable(ctrl);
- + ctrl->cpr_enabled = false;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_init_ctrl() - performs hardware initialization of CPR
- + * controller registers
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
- +{
- + int i, j, k, m, rc;
- + u32 ro_used = 0;
- + u32 gcnt, cont_dly, up_down_dly, val;
- + u64 temp;
- + char *mode;
- +
- + if (ctrl->core_clk) {
- + rc = clk_set_rate(ctrl->core_clk, ctrl->cpr_clock_rate);
- + if (rc) {
- + cpr3_err(ctrl, "clk_set_rate(core_clk, %u) failed, rc=%d\n",
- + ctrl->cpr_clock_rate, rc);
- + return rc;
- + }
- + }
- +
- + rc = cpr3_clock_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
- + return rc;
- + }
- + ctrl->cpr_enabled = true;
- +
- + /* Find all RO's used by any corner of any regulator. */
- + for (i = 0; i < ctrl->thread_count; i++)
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++)
- + for (k = 0; k < ctrl->thread[i].vreg[j].corner_count;
- + k++)
- + for (m = 0; m < CPR3_RO_COUNT; m++)
- + if (ctrl->thread[i].vreg[j].corner[k].
- + target_quot[m])
- + ro_used |= BIT(m);
- +
- + /* Configure the GCNT of the RO's that will be used */
- + gcnt = cpr3_regulator_get_gcnt(ctrl);
- + for (i = 0; i < CPR3_RO_COUNT; i++)
- + if (ro_used & BIT(i))
- + cpr3_write(ctrl, CPR3_REG_GCNT(i), gcnt);
- +
- + /* Configure the loop delay time */
- + temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->loop_time;
- + do_div(temp, 1000000000);
- + cont_dly = temp;
- + if (ctrl->supports_hw_closed_loop
- + && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, cont_dly);
- + else
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT, cont_dly);
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + temp = (u64)ctrl->cpr_clock_rate *
- + (u64)ctrl->up_down_delay_time;
- + do_div(temp, 1000000000);
- + up_down_dly = temp;
- + if (ctrl->supports_hw_closed_loop)
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT,
- + up_down_dly);
- + cpr3_debug(ctrl, "up_down_dly=%u, up_down_delay_time=%u ns\n",
- + up_down_dly, ctrl->up_down_delay_time);
- + }
- +
- + cpr3_debug(ctrl, "cpr_clock_rate=%u HZ, sensor_time=%u ns, loop_time=%u ns, gcnt=%u, cont_dly=%u\n",
- + ctrl->cpr_clock_rate, ctrl->sensor_time, ctrl->loop_time,
- + gcnt, cont_dly);
- +
- + /* Configure CPR sensor operation */
- + val = (ctrl->idle_clocks << CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT)
- + & CPR3_CPR_CTL_IDLE_CLOCKS_MASK;
- + val |= (ctrl->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT)
- + & CPR3_CPR_CTL_COUNT_MODE_MASK;
- + val |= (ctrl->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT)
- + & CPR3_CPR_CTL_COUNT_REPEAT_MASK;
- + cpr3_write(ctrl, CPR3_REG_CPR_CTL, val);
- +
- + cpr3_debug(ctrl, "idle_clocks=%u, count_mode=%u, count_repeat=%u; CPR_CTL=0x%08X\n",
- + ctrl->idle_clocks, ctrl->count_mode, ctrl->count_repeat, val);
- +
- + /* Configure CPR default step quotients */
- + val = (ctrl->step_quot_init_min << CPR3_CPR_STEP_QUOT_MIN_SHIFT)
- + & CPR3_CPR_STEP_QUOT_MIN_MASK;
- + val |= (ctrl->step_quot_init_max << CPR3_CPR_STEP_QUOT_MAX_SHIFT)
- + & CPR3_CPR_STEP_QUOT_MAX_MASK;
- + cpr3_write(ctrl, CPR3_REG_CPR_STEP_QUOT, val);
- +
- + cpr3_debug(ctrl, "step_quot_min=%u, step_quot_max=%u; STEP_QUOT=0x%08X\n",
- + ctrl->step_quot_init_min, ctrl->step_quot_init_max, val);
- +
- + /* Configure the CPR sensor ownership */
- + for (i = 0; i < ctrl->sensor_count; i++)
- + cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(i),
- + ctrl->sensor_owner[i]);
- +
- + /* Configure per-thread registers */
- + for (i = 0; i < ctrl->thread_count; i++) {
- + rc = cpr3_regulator_init_thread(&ctrl->thread[i]);
- + if (rc) {
- + cpr3_err(ctrl, "CPR thread register initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + if (ctrl->supports_hw_closed_loop) {
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
- + ctrl->use_hw_closed_loop
- + ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
- + : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
- + } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
- + ctrl->use_hw_closed_loop
- + ? CPR3_HW_CLOSED_LOOP_ENABLE
- + : CPR3_HW_CLOSED_LOOP_DISABLE);
- +
- + cpr3_debug(ctrl, "PD_THROTTLE=0x%08X\n",
- + ctrl->proc_clock_throttle);
- + }
- +
- + if ((ctrl->use_hw_closed_loop ||
- + ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) &&
- + ctrl->vdd_limit_regulator) {
- + rc = regulator_enable(ctrl->vdd_limit_regulator);
- + if (rc) {
- + cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- + }
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_regulator_init_cpr4(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "CPR4-specific controller initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + /* Ensure that all register writes complete before disabling clocks. */
- + wmb();
- +
- + cpr3_clock_disable(ctrl);
- + ctrl->cpr_enabled = false;
- +
- + if (!ctrl->cpr_allowed_sw || !ctrl->cpr_allowed_hw)
- + mode = "open-loop";
- + else if (ctrl->supports_hw_closed_loop)
- + mode = ctrl->use_hw_closed_loop
- + ? "HW closed-loop" : "SW closed-loop";
- + else
- + mode = "closed-loop";
- +
- + cpr3_info(ctrl, "Default CPR mode = %s", mode);
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_set_target_quot() - configure the target quotient for each
- + * RO of the CPR3 thread and set the RO mask
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_set_target_quot(struct cpr3_thread *thread)
- +{
- + u32 new_quot, last_quot;
- + int i;
- +
- + if (thread->aggr_corner.ro_mask == CPR3_RO_MASK
- + && thread->last_closed_loop_aggr_corner.ro_mask == CPR3_RO_MASK) {
- + /* Avoid writing target quotients since all RO's are masked. */
- + return;
- + } else if (thread->aggr_corner.ro_mask == CPR3_RO_MASK) {
- + cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
- + CPR3_RO_MASK);
- + thread->last_closed_loop_aggr_corner.ro_mask = CPR3_RO_MASK;
- + /*
- + * Only the RO_MASK register needs to be written since all
- + * RO's are masked.
- + */
- + return;
- + } else if (thread->aggr_corner.ro_mask
- + != thread->last_closed_loop_aggr_corner.ro_mask) {
- + cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
- + thread->aggr_corner.ro_mask);
- + }
- +
- + for (i = 0; i < CPR3_RO_COUNT; i++) {
- + new_quot = thread->aggr_corner.target_quot[i];
- + last_quot = thread->last_closed_loop_aggr_corner.target_quot[i];
- + if (new_quot != last_quot)
- + cpr3_write(thread->ctrl,
- + CPR3_REG_TARGET_QUOT(thread->thread_id, i),
- + new_quot);
- + }
- +
- + thread->last_closed_loop_aggr_corner = thread->aggr_corner;
- +
- + return;
- +}
- +
- +/**
- + * cpr3_update_vreg_closed_loop_volt() - update the last known settled
- + * closed loop voltage for a CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + * @vdd_volt: Last known settled voltage in microvolts for the
- + * VDD supply
- + * @reg_last_measurement: Value read from the LAST_MEASUREMENT register
- + *
- + * Return: none
- + */
- +static void cpr3_update_vreg_closed_loop_volt(struct cpr3_regulator *vreg,
- + int vdd_volt, u32 reg_last_measurement)
- +{
- + bool step_dn, step_up, aggr_step_up, aggr_step_dn, aggr_step_mid;
- + bool valid, pd_valid, saw_error;
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + struct cpr3_corner *corner;
- + u32 id;
- +
- + if (vreg->last_closed_loop_corner == CPR3_REGULATOR_CORNER_INVALID)
- + return;
- + else
- + corner = &vreg->corner[vreg->last_closed_loop_corner];
- +
- + if (vreg->thread->last_closed_loop_aggr_corner.ro_mask
- + == CPR3_RO_MASK || !vreg->aggregated) {
- + return;
- + } else if (!ctrl->cpr_enabled || !ctrl->last_corner_was_closed_loop) {
- + return;
- + } else if (ctrl->thread_count == 1
- + && vdd_volt >= corner->floor_volt
- + && vdd_volt <= corner->ceiling_volt) {
- + corner->last_volt = vdd_volt;
- + cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d\n",
- + vreg->last_closed_loop_corner, corner->last_volt,
- + vreg->last_closed_loop_corner,
- + corner->ceiling_volt,
- + vreg->last_closed_loop_corner,
- + corner->floor_volt);
- + return;
- + } else if (!ctrl->supports_hw_closed_loop) {
- + return;
- + } else if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPR3) {
- + corner->last_volt = vdd_volt;
- + cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d\n",
- + vreg->last_closed_loop_corner, corner->last_volt,
- + vreg->last_closed_loop_corner,
- + corner->ceiling_volt,
- + vreg->last_closed_loop_corner,
- + corner->floor_volt);
- + return;
- + }
- +
- + /* CPR clocks are on and HW closed loop is supported */
- + valid = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_VALID);
- + if (!valid) {
- + cpr3_debug(vreg, "CPR_LAST_VALID_MEASUREMENT=0x%X valid bit not set\n",
- + reg_last_measurement);
- + return;
- + }
- +
- + id = vreg->thread->thread_id;
- +
- + step_dn
- + = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_THREAD_DN(id));
- + step_up
- + = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_THREAD_UP(id));
- + aggr_step_dn = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_DN);
- + aggr_step_mid
- + = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_MID);
- + aggr_step_up = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_UP);
- + saw_error = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_SAW_ERROR);
- + pd_valid
- + = !((((reg_last_measurement & CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK)
- + >> CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT)
- + & vreg->pd_bypass_mask) == vreg->pd_bypass_mask);
- +
- + if (!pd_valid) {
- + cpr3_debug(vreg, "CPR_LAST_VALID_MEASUREMENT=0x%X, all power domains bypassed\n",
- + reg_last_measurement);
- + return;
- + } else if (step_dn && step_up) {
- + cpr3_err(vreg, "both up and down status bits set, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
- + reg_last_measurement);
- + return;
- + } else if (aggr_step_dn && step_dn && vdd_volt < corner->last_volt
- + && vdd_volt >= corner->floor_volt) {
- + corner->last_volt = vdd_volt;
- + } else if (aggr_step_up && step_up && vdd_volt > corner->last_volt
- + && vdd_volt <= corner->ceiling_volt) {
- + corner->last_volt = vdd_volt;
- + } else if (aggr_step_mid
- + && vdd_volt >= corner->floor_volt
- + && vdd_volt <= corner->ceiling_volt) {
- + corner->last_volt = vdd_volt;
- + } else if (saw_error && (vdd_volt == corner->ceiling_volt
- + || vdd_volt == corner->floor_volt)) {
- + corner->last_volt = vdd_volt;
- + } else {
- + cpr3_debug(vreg, "last_volt not updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d, vdd_volt=%d, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
- + vreg->last_closed_loop_corner, corner->last_volt,
- + vreg->last_closed_loop_corner,
- + corner->ceiling_volt,
- + vreg->last_closed_loop_corner, corner->floor_volt,
- + vdd_volt, reg_last_measurement);
- + return;
- + }
- +
- + cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
- + vreg->last_closed_loop_corner, corner->last_volt,
- + vreg->last_closed_loop_corner, corner->ceiling_volt,
- + vreg->last_closed_loop_corner, corner->floor_volt,
- + reg_last_measurement);
- +}
- +
- +/**
- + * cpr3_regulator_mem_acc_bhs_used() - determines if mem-acc regulators powered
- + * through a BHS are associated with the CPR3 controller or any of
- + * the CPR3 regulators it controls.
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * This function determines if the CPR3 controller or any of its CPR3 regulators
- + * need to manage mem-acc regulators that are currently powered through a BHS
- + * and whose corner selection is based upon a particular voltage threshold.
- + *
- + * Return: true or false
- + */
- +static bool cpr3_regulator_mem_acc_bhs_used(struct cpr3_controller *ctrl)
- +{
- + struct cpr3_regulator *vreg;
- + int i, j;
- +
- + if (!ctrl->mem_acc_threshold_volt)
- + return false;
- +
- + if (ctrl->mem_acc_regulator)
- + return true;
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- +
- + if (vreg->mem_acc_regulator)
- + return true;
- + }
- + }
- +
- + return false;
- +}
- +
- +/**
- + * cpr3_regulator_config_bhs_mem_acc() - configure the mem-acc regulator
- + * settings for hardware blocks currently powered through the BHS.
- + * @ctrl: Pointer to the CPR3 controller
- + * @new_volt: New voltage in microvolts that VDD supply needs to
- + * end up at
- + * @last_volt: Pointer to the last known voltage in microvolts for the
- + * VDD supply
- + * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
- + * corner aggregated from all CPR3 threads managed by the
- + * CPR3 controller
- + *
- + * This function programs the mem-acc regulator corners for CPR3 regulators
- + * whose LDO regulators are in bypassed state. The function also handles
- + * CPR3 controllers which utilize mem-acc regulators that operate independently
- + * from the LDO hardware and that must be programmed when the VDD supply
- + * crosses a particular voltage threshold.
- + *
- + * Return: 0 on success, errno on failure. If the VDD supply voltage is
- + * modified, last_volt is updated to reflect the new voltage setpoint.
- + */
- +static int cpr3_regulator_config_bhs_mem_acc(struct cpr3_controller *ctrl,
- + int new_volt, int *last_volt,
- + struct cpr3_corner *aggr_corner)
- +{
- + struct cpr3_regulator *vreg;
- + int i, j, rc, mem_acc_corn, safe_volt;
- + int mem_acc_volt = ctrl->mem_acc_threshold_volt;
- + int ref_volt;
- +
- + if (!cpr3_regulator_mem_acc_bhs_used(ctrl))
- + return 0;
- +
- + ref_volt = ctrl->use_hw_closed_loop ? aggr_corner->floor_volt :
- + new_volt;
- +
- + if (((*last_volt < mem_acc_volt && mem_acc_volt <= ref_volt) ||
- + (*last_volt >= mem_acc_volt && mem_acc_volt > ref_volt))) {
- + if (ref_volt < *last_volt)
- + safe_volt = max(mem_acc_volt, aggr_corner->last_volt);
- + else
- + safe_volt = max(mem_acc_volt, *last_volt);
- +
- + rc = regulator_set_voltage(ctrl->vdd_regulator, safe_volt,
- + new_volt < *last_volt ?
- + ctrl->aggr_corner.ceiling_volt :
- + new_volt);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
- + safe_volt, rc);
- + return rc;
- + }
- +
- + *last_volt = safe_volt;
- +
- + mem_acc_corn = ref_volt < mem_acc_volt ?
- + ctrl->mem_acc_corner_map[CPR3_MEM_ACC_LOW_CORNER] :
- + ctrl->mem_acc_corner_map[CPR3_MEM_ACC_HIGH_CORNER];
- +
- + if (ctrl->mem_acc_regulator) {
- + rc = regulator_set_voltage(ctrl->mem_acc_regulator,
- + mem_acc_corn, mem_acc_corn);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
- + mem_acc_corn, rc);
- + return rc;
- + }
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- +
- + if (!vreg->mem_acc_regulator)
- + continue;
- +
- + rc = regulator_set_voltage(
- + vreg->mem_acc_regulator, mem_acc_corn,
- + mem_acc_corn);
- + if (rc) {
- + cpr3_err(vreg, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
- + mem_acc_corn, rc);
- + return rc;
- + }
- + }
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_switch_apm_mode() - switch the mode of the APM controller
- + * associated with a given CPR3 controller
- + * @ctrl: Pointer to the CPR3 controller
- + * @new_volt: New voltage in microvolts that VDD supply needs to
- + * end up at
- + * @last_volt: Pointer to the last known voltage in microvolts for the
- + * VDD supply
- + * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
- + * corner aggregated from all CPR3 threads managed by the
- + * CPR3 controller
- + *
- + * This function requests a switch of the APM mode while guaranteeing
- + * any LDO regulator hardware requirements are satisfied. The function must
- + * be called once it is known a new VDD supply setpoint crosses the APM
- + * voltage threshold.
- + *
- + * Return: 0 on success, errno on failure. If the VDD supply voltage is
- + * modified, last_volt is updated to reflect the new voltage setpoint.
- + */
- +static int cpr3_regulator_switch_apm_mode(struct cpr3_controller *ctrl,
- + int new_volt, int *last_volt,
- + struct cpr3_corner *aggr_corner)
- +{
- + struct regulator *vdd = ctrl->vdd_regulator;
- + int apm_volt = ctrl->apm_threshold_volt;
- + int orig_last_volt = *last_volt;
- + int rc;
- +
- + rc = regulator_set_voltage(vdd, apm_volt, apm_volt);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
- + apm_volt, rc);
- + return rc;
- + }
- +
- + *last_volt = apm_volt;
- +
- + rc = msm_apm_set_supply(ctrl->apm, new_volt >= apm_volt
- + ? ctrl->apm_high_supply : ctrl->apm_low_supply);
- + if (rc) {
- + cpr3_err(ctrl, "APM switch failed, rc=%d\n", rc);
- + /* Roll back the voltage. */
- + regulator_set_voltage(vdd, orig_last_volt, INT_MAX);
- + *last_volt = orig_last_volt;
- + return rc;
- + }
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_config_voltage_crossings() - configure APM and mem-acc
- + * settings depending upon a new VDD supply setpoint
- + *
- + * @ctrl: Pointer to the CPR3 controller
- + * @new_volt: New voltage in microvolts that VDD supply needs to
- + * end up at
- + * @last_volt: Pointer to the last known voltage in microvolts for the
- + * VDD supply
- + * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
- + * corner aggregated from all CPR3 threads managed by the
- + * CPR3 controller
- + *
- + * This function handles the APM and mem-acc regulator reconfiguration if
- + * the new VDD supply voltage will result in crossing their respective voltage
- + * thresholds.
- + *
- + * Return: 0 on success, errno on failure. If the VDD supply voltage is
- + * modified, last_volt is updated to reflect the new voltage setpoint.
- + */
- +static int cpr3_regulator_config_voltage_crossings(struct cpr3_controller *ctrl,
- + int new_volt, int *last_volt,
- + struct cpr3_corner *aggr_corner)
- +{
- + bool apm_crossing = false, mem_acc_crossing = false;
- + bool mem_acc_bhs_used;
- + int apm_volt = ctrl->apm_threshold_volt;
- + int mem_acc_volt = ctrl->mem_acc_threshold_volt;
- + int ref_volt, rc;
- +
- + if (ctrl->apm && apm_volt > 0
- + && ((*last_volt < apm_volt && apm_volt <= new_volt)
- + || (*last_volt >= apm_volt && apm_volt > new_volt)))
- + apm_crossing = true;
- +
- + mem_acc_bhs_used = cpr3_regulator_mem_acc_bhs_used(ctrl);
- +
- + ref_volt = ctrl->use_hw_closed_loop ? aggr_corner->floor_volt :
- + new_volt;
- +
- + if (mem_acc_bhs_used &&
- + (((*last_volt < mem_acc_volt && mem_acc_volt <= ref_volt) ||
- + (*last_volt >= mem_acc_volt && mem_acc_volt > ref_volt))))
- + mem_acc_crossing = true;
- +
- + if (apm_crossing && mem_acc_crossing) {
- + if ((new_volt < *last_volt && apm_volt >= mem_acc_volt) ||
- + (new_volt >= *last_volt && apm_volt < mem_acc_volt)) {
- + rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt,
- + last_volt,
- + aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to switch APM mode\n");
- + return rc;
- + }
- +
- + rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
- + last_volt, aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
- + return rc;
- + }
- + } else {
- + rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
- + last_volt, aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
- + return rc;
- + }
- +
- + rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt,
- + last_volt,
- + aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to switch APM mode\n");
- + return rc;
- + }
- + }
- + } else if (apm_crossing) {
- + rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt, last_volt,
- + aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to switch APM mode\n");
- + return rc;
- + }
- + } else if (mem_acc_crossing) {
- + rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
- + last_volt, aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
- + return rc;
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_config_mem_acc() - configure the corner of the mem-acc
- + * regulator associated with the CPR3 controller
- + * @ctrl: Pointer to the CPR3 controller
- + * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
- + * corner aggregated from all CPR3 threads managed by the
- + * CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_config_mem_acc(struct cpr3_controller *ctrl,
- + struct cpr3_corner *aggr_corner)
- +{
- + int rc;
- +
- + if (ctrl->mem_acc_regulator && aggr_corner->mem_acc_volt) {
- + rc = regulator_set_voltage(ctrl->mem_acc_regulator,
- + aggr_corner->mem_acc_volt,
- + aggr_corner->mem_acc_volt);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
- + aggr_corner->mem_acc_volt, rc);
- + return rc;
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_scale_vdd_voltage() - scale the CPR controlled VDD supply
- + * voltage to the new level while satisfying any other hardware
- + * requirements
- + * @ctrl: Pointer to the CPR3 controller
- + * @new_volt: New voltage in microvolts that VDD supply needs to end
- + * up at
- + * @last_volt: Last known voltage in microvolts for the VDD supply
- + * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
- + * corner aggregated from all CPR3 threads managed by the
- + * CPR3 controller
- + *
- + * This function scales the CPR controlled VDD supply voltage from its
- + * current level to the new voltage that is specified. If the supply is
- + * configured to use the APM and the APM threshold is crossed as a result of
- + * the voltage scaling, then this function also stops at the APM threshold,
- + * switches the APM source, and finally sets the final new voltage.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_scale_vdd_voltage(struct cpr3_controller *ctrl,
- + int new_volt, int last_volt,
- + struct cpr3_corner *aggr_corner)
- +{
- + struct regulator *vdd = ctrl->vdd_regulator;
- + int rc;
- +
- + if (new_volt < last_volt) {
- + rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
- + if (rc)
- + return rc;
- + } else {
- + /* Increasing VDD voltage */
- + if (ctrl->system_regulator) {
- + rc = regulator_set_voltage(ctrl->system_regulator,
- + aggr_corner->system_volt, INT_MAX);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(system) == %d failed, rc=%d\n",
- + aggr_corner->system_volt, rc);
- + return rc;
- + }
- + }
- + }
- +
- + rc = cpr3_regulator_config_voltage_crossings(ctrl, new_volt, &last_volt,
- + aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "unable to handle voltage threshold crossing configurations, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + /*
- + * Subtract a small amount from the min_uV parameter so that the
- + * set voltage request is not dropped by the framework due to being
- + * duplicate. This is needed in order to switch from hardware
- + * closed-loop to open-loop successfully.
- + */
- + rc = regulator_set_voltage(vdd, new_volt - (ctrl->cpr_enabled ? 0 : 1),
- + aggr_corner->ceiling_volt);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
- + new_volt, rc);
- + return rc;
- + }
- +
- + if (new_volt == last_volt && ctrl->supports_hw_closed_loop
- + && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + /*
- + * CPR4 features enforce voltage reprogramming when the last
- + * set voltage and new set voltage are same. This way, we can
- + * ensure that SAW PMIC STATUS register is updated with newly
- + * programmed voltage.
- + */
- + rc = regulator_sync_voltage(vdd);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_sync_voltage(vdd) == %d failed, rc=%d\n",
- + new_volt, rc);
- + return rc;
- + }
- + }
- +
- + if (new_volt >= last_volt) {
- + rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
- + if (rc)
- + return rc;
- + } else {
- + /* Decreasing VDD voltage */
- + if (ctrl->system_regulator) {
- + rc = regulator_set_voltage(ctrl->system_regulator,
- + aggr_corner->system_volt, INT_MAX);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_set_voltage(system) == %d failed, rc=%d\n",
- + aggr_corner->system_volt, rc);
- + return rc;
- + }
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_get_dynamic_floor_volt() - returns the current dynamic floor
- + * voltage based upon static configurations and the state of all
- + * power domains during the last CPR measurement
- + * @ctrl: Pointer to the CPR3 controller
- + * @reg_last_measurement: Value read from the LAST_MEASUREMENT register
- + *
- + * When using HW closed-loop, the dynamic floor voltage is always returned
- + * regardless of the current state of the power domains.
- + *
- + * Return: dynamic floor voltage in microvolts or 0 if dynamic floor is not
- + * currently required
- + */
- +static int cpr3_regulator_get_dynamic_floor_volt(struct cpr3_controller *ctrl,
- + u32 reg_last_measurement)
- +{
- + int dynamic_floor_volt = 0;
- + struct cpr3_regulator *vreg;
- + bool valid, pd_valid;
- + u32 bypass_bits;
- + int i, j;
- +
- + if (!ctrl->supports_hw_closed_loop)
- + return 0;
- +
- + if (likely(!ctrl->use_hw_closed_loop)) {
- + valid = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_VALID);
- + bypass_bits
- + = (reg_last_measurement & CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK)
- + >> CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT;
- + } else {
- + /*
- + * Ensure that the dynamic floor voltage is always used for
- + * HW closed-loop since the conditions below cannot be evaluated
- + * after each CPR measurement.
- + */
- + valid = false;
- + bypass_bits = 0;
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- +
- + if (!vreg->uses_dynamic_floor)
- + continue;
- +
- + pd_valid = !((bypass_bits & vreg->pd_bypass_mask)
- + == vreg->pd_bypass_mask);
- +
- + if (!valid || !pd_valid)
- + dynamic_floor_volt = max(dynamic_floor_volt,
- + vreg->corner[
- + vreg->dynamic_floor_corner].last_volt);
- + }
- + }
- +
- + return dynamic_floor_volt;
- +}
- +
- +/**
- + * cpr3_regulator_max_sdelta_diff() - returns the maximum voltage difference in
- + * microvolts that can result from different operating conditions
- + * for the specified sdelta struct
- + * @sdelta: Pointer to the sdelta structure
- + * @step_volt: Step size in microvolts between available set
- + * points of the VDD supply.
- + *
- + * Return: voltage difference between the highest and lowest adjustments if
- + * sdelta and sdelta->table are valid, else 0.
- + */
- +static int cpr3_regulator_max_sdelta_diff(const struct cpr4_sdelta *sdelta,
- + int step_volt)
- +{
- + int i, j, index, sdelta_min = INT_MAX, sdelta_max = INT_MIN;
- +
- + if (!sdelta || !sdelta->table)
- + return 0;
- +
- + for (i = 0; i < sdelta->max_core_count; i++) {
- + for (j = 0; j < sdelta->temp_band_count; j++) {
- + index = i * sdelta->temp_band_count + j;
- + sdelta_min = min(sdelta_min, sdelta->table[index]);
- + sdelta_max = max(sdelta_max, sdelta->table[index]);
- + }
- + }
- +
- + return (sdelta_max - sdelta_min) * step_volt;
- +}
- +
- +/**
- + * cpr3_regulator_aggregate_sdelta() - check open-loop voltages of current
- + * aggregated corner and current corner of a given regulator
- + * and adjust the sdelta strucuture data of aggregate corner.
- + * @aggr_corner: Pointer to accumulated aggregated corner which
- + * is both an input and an output
- + * @corner: Pointer to the corner to be aggregated with
- + * aggr_corner
- + * @step_volt: Step size in microvolts between available set
- + * points of the VDD supply.
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_aggregate_sdelta(
- + struct cpr3_corner *aggr_corner,
- + const struct cpr3_corner *corner, int step_volt)
- +{
- + struct cpr4_sdelta *aggr_sdelta, *sdelta;
- + int aggr_core_count, core_count, temp_band_count;
- + u32 aggr_index, index;
- + int i, j, sdelta_size, cap_steps, adjust_sdelta;
- +
- + aggr_sdelta = aggr_corner->sdelta;
- + sdelta = corner->sdelta;
- +
- + if (aggr_corner->open_loop_volt < corner->open_loop_volt) {
- + /*
- + * Found the new dominant regulator as its open-loop requirement
- + * is higher than previous dominant regulator. Calculate cap
- + * voltage to limit the SDELTA values to make sure the runtime
- + * (Core-count/temp) adjustments do not violate other
- + * regulators' voltage requirements. Use cpr4_sdelta values of
- + * new dominant regulator.
- + */
- + aggr_sdelta->cap_volt = min(aggr_sdelta->cap_volt,
- + (corner->open_loop_volt -
- + aggr_corner->open_loop_volt));
- +
- + /* Clear old data in the sdelta table */
- + sdelta_size = aggr_sdelta->max_core_count
- + * aggr_sdelta->temp_band_count;
- +
- + if (aggr_sdelta->allow_core_count_adj
- + || aggr_sdelta->allow_temp_adj)
- + memset(aggr_sdelta->table, 0, sdelta_size
- + * sizeof(*aggr_sdelta->table));
- +
- + if (sdelta->allow_temp_adj || sdelta->allow_core_count_adj) {
- + /* Copy new data in sdelta table */
- + sdelta_size = sdelta->max_core_count
- + * sdelta->temp_band_count;
- + if (sdelta->table)
- + memcpy(aggr_sdelta->table, sdelta->table,
- + sdelta_size * sizeof(*sdelta->table));
- + }
- +
- + if (sdelta->allow_boost) {
- + memcpy(aggr_sdelta->boost_table, sdelta->boost_table,
- + sdelta->temp_band_count
- + * sizeof(*sdelta->boost_table));
- + aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
- + } else if (aggr_sdelta->allow_boost) {
- + for (i = 0; i < aggr_sdelta->temp_band_count; i++) {
- + adjust_sdelta = (corner->open_loop_volt
- + - aggr_corner->open_loop_volt)
- + / step_volt;
- + aggr_sdelta->boost_table[i] += adjust_sdelta;
- + aggr_sdelta->boost_table[i]
- + = min(aggr_sdelta->boost_table[i], 0);
- + }
- + }
- +
- + aggr_corner->open_loop_volt = corner->open_loop_volt;
- + aggr_sdelta->allow_temp_adj = sdelta->allow_temp_adj;
- + aggr_sdelta->allow_core_count_adj
- + = sdelta->allow_core_count_adj;
- + aggr_sdelta->max_core_count = sdelta->max_core_count;
- + aggr_sdelta->temp_band_count = sdelta->temp_band_count;
- + } else if (aggr_corner->open_loop_volt > corner->open_loop_volt) {
- + /*
- + * Adjust the cap voltage if the open-loop requirement of new
- + * regulator is the next highest.
- + */
- + aggr_sdelta->cap_volt = min(aggr_sdelta->cap_volt,
- + (aggr_corner->open_loop_volt
- + - corner->open_loop_volt));
- +
- + if (sdelta->allow_boost) {
- + for (i = 0; i < aggr_sdelta->temp_band_count; i++) {
- + adjust_sdelta = (aggr_corner->open_loop_volt
- + - corner->open_loop_volt)
- + / step_volt;
- + aggr_sdelta->boost_table[i] =
- + sdelta->boost_table[i] + adjust_sdelta;
- + aggr_sdelta->boost_table[i]
- + = min(aggr_sdelta->boost_table[i], 0);
- + }
- + aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
- + }
- + } else {
- + /*
- + * Found another dominant regulator with same open-loop
- + * requirement. Make cap voltage to '0'. Disable core-count
- + * adjustments as we couldn't support for both regulators.
- + * Keep enable temp based adjustments if enabled for both
- + * regulators and choose mininum margin adjustment values
- + * between them.
- + */
- + aggr_sdelta->cap_volt = 0;
- + aggr_sdelta->allow_core_count_adj = false;
- +
- + if (aggr_sdelta->allow_temp_adj
- + && sdelta->allow_temp_adj) {
- + aggr_core_count = aggr_sdelta->max_core_count - 1;
- + core_count = sdelta->max_core_count - 1;
- + temp_band_count = sdelta->temp_band_count;
- + for (j = 0; j < temp_band_count; j++) {
- + aggr_index = aggr_core_count * temp_band_count
- + + j;
- + index = core_count * temp_band_count + j;
- + aggr_sdelta->table[aggr_index] =
- + min(aggr_sdelta->table[aggr_index],
- + sdelta->table[index]);
- + }
- + } else {
- + aggr_sdelta->allow_temp_adj = false;
- + }
- +
- + if (sdelta->allow_boost) {
- + memcpy(aggr_sdelta->boost_table, sdelta->boost_table,
- + sdelta->temp_band_count
- + * sizeof(*sdelta->boost_table));
- + aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
- + }
- + }
- +
- + /* Keep non-dominant clients boost enable state */
- + aggr_sdelta->allow_boost |= sdelta->allow_boost;
- + if (aggr_sdelta->allow_boost)
- + aggr_sdelta->allow_core_count_adj = false;
- +
- + if (aggr_sdelta->cap_volt && !(aggr_sdelta->cap_volt == INT_MAX)) {
- + core_count = aggr_sdelta->max_core_count;
- + temp_band_count = aggr_sdelta->temp_band_count;
- + /*
- + * Convert cap voltage from uV to PMIC steps and use to limit
- + * sdelta margin adjustments.
- + */
- + cap_steps = aggr_sdelta->cap_volt / step_volt;
- + for (i = 0; i < core_count; i++)
- + for (j = 0; j < temp_band_count; j++) {
- + index = i * temp_band_count + j;
- + aggr_sdelta->table[index] =
- + min(aggr_sdelta->table[index],
- + cap_steps);
- + }
- + }
- +}
- +
- +/**
- + * cpr3_regulator_aggregate_corners() - aggregate two corners together
- + * @aggr_corner: Pointer to accumulated aggregated corner which
- + * is both an input and an output
- + * @corner: Pointer to the corner to be aggregated with
- + * aggr_corner
- + * @aggr_quot: Flag indicating that target quotients should be
- + * aggregated as well.
- + * @step_volt: Step size in microvolts between available set
- + * points of the VDD supply.
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_aggregate_corners(struct cpr3_corner *aggr_corner,
- + const struct cpr3_corner *corner, bool aggr_quot,
- + int step_volt)
- +{
- + int i;
- +
- + aggr_corner->ceiling_volt
- + = max(aggr_corner->ceiling_volt, corner->ceiling_volt);
- + aggr_corner->floor_volt
- + = max(aggr_corner->floor_volt, corner->floor_volt);
- + aggr_corner->last_volt
- + = max(aggr_corner->last_volt, corner->last_volt);
- + aggr_corner->system_volt
- + = max(aggr_corner->system_volt, corner->system_volt);
- + aggr_corner->mem_acc_volt
- + = max(aggr_corner->mem_acc_volt, corner->mem_acc_volt);
- + aggr_corner->irq_en |= corner->irq_en;
- + aggr_corner->use_open_loop |= corner->use_open_loop;
- +
- + if (aggr_quot) {
- + aggr_corner->ro_mask &= corner->ro_mask;
- +
- + for (i = 0; i < CPR3_RO_COUNT; i++)
- + aggr_corner->target_quot[i]
- + = max(aggr_corner->target_quot[i],
- + corner->target_quot[i]);
- + }
- +
- + if (aggr_corner->sdelta && corner->sdelta
- + && (aggr_corner->sdelta->table
- + || aggr_corner->sdelta->boost_table)) {
- + cpr3_regulator_aggregate_sdelta(aggr_corner, corner, step_volt);
- + } else {
- + aggr_corner->open_loop_volt
- + = max(aggr_corner->open_loop_volt,
- + corner->open_loop_volt);
- + }
- +}
- +
- +/**
- + * cpr3_regulator_update_ctrl_state() - update the state of the CPR controller
- + * to reflect the corners used by all CPR3 regulators as well as
- + * the CPR operating mode
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * This function aggregates the CPR parameters for all CPR3 regulators
- + * associated with the VDD supply. Upon success, it sets the aggregated last
- + * known good voltage.
- + *
- + * The VDD supply voltage will not be physically configured unless this
- + * condition is met by at least one of the regulators of the controller:
- + * regulator->vreg_enabled == true &&
- + * regulator->current_corner != CPR3_REGULATOR_CORNER_INVALID
- + *
- + * CPR registers for the controller and each thread are updated as long as
- + * ctrl->cpr_enabled == true.
- + *
- + * Note, CPR3 controller lock must be held by the caller.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int _cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
- +{
- + struct cpr3_corner aggr_corner = {};
- + struct cpr3_thread *thread;
- + struct cpr3_regulator *vreg;
- + struct cpr4_sdelta *sdelta;
- + bool valid = false;
- + bool thread_valid;
- + int i, j, rc, new_volt, vdd_volt, dynamic_floor_volt, last_corner_volt;
- + u32 reg_last_measurement = 0, sdelta_size;
- + int *sdelta_table, *boost_table;
- +
- + last_corner_volt = 0;
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + vdd_volt = regulator_get_voltage(ctrl->vdd_regulator);
- + if (vdd_volt < 0) {
- + cpr3_err(ctrl, "regulator_get_voltage(vdd) failed, rc=%d\n",
- + vdd_volt);
- + return vdd_volt;
- + }
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + /*
- + * Save aggregated corner open-loop voltage which was programmed
- + * during last corner switch which is used when programming new
- + * aggregated corner open-loop voltage.
- + */
- + last_corner_volt = ctrl->aggr_corner.open_loop_volt;
- + }
- +
- + if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop &&
- + ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
- + reg_last_measurement
- + = cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
- +
- + aggr_corner.sdelta = ctrl->aggr_corner.sdelta;
- + if (aggr_corner.sdelta) {
- + sdelta = aggr_corner.sdelta;
- + sdelta_table = sdelta->table;
- + if (sdelta_table) {
- + sdelta_size = sdelta->max_core_count *
- + sdelta->temp_band_count;
- + memset(sdelta_table, 0, sdelta_size
- + * sizeof(*sdelta_table));
- + }
- +
- + boost_table = sdelta->boost_table;
- + if (boost_table)
- + memset(boost_table, 0, sdelta->temp_band_count
- + * sizeof(*boost_table));
- +
- + memset(sdelta, 0, sizeof(*sdelta));
- + sdelta->table = sdelta_table;
- + sdelta->cap_volt = INT_MAX;
- + sdelta->boost_table = boost_table;
- + }
- +
- + /* Aggregate the requests of all threads */
- + for (i = 0; i < ctrl->thread_count; i++) {
- + thread = &ctrl->thread[i];
- + thread_valid = false;
- +
- + sdelta = thread->aggr_corner.sdelta;
- + if (sdelta) {
- + sdelta_table = sdelta->table;
- + if (sdelta_table) {
- + sdelta_size = sdelta->max_core_count *
- + sdelta->temp_band_count;
- + memset(sdelta_table, 0, sdelta_size
- + * sizeof(*sdelta_table));
- + }
- +
- + boost_table = sdelta->boost_table;
- + if (boost_table)
- + memset(boost_table, 0, sdelta->temp_band_count
- + * sizeof(*boost_table));
- +
- + memset(sdelta, 0, sizeof(*sdelta));
- + sdelta->table = sdelta_table;
- + sdelta->cap_volt = INT_MAX;
- + sdelta->boost_table = boost_table;
- + }
- +
- + memset(&thread->aggr_corner, 0, sizeof(thread->aggr_corner));
- + thread->aggr_corner.sdelta = sdelta;
- + thread->aggr_corner.ro_mask = CPR3_RO_MASK;
- +
- + for (j = 0; j < thread->vreg_count; j++) {
- + vreg = &thread->vreg[j];
- +
- + if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop)
- + cpr3_update_vreg_closed_loop_volt(vreg,
- + vdd_volt, reg_last_measurement);
- +
- + if (!vreg->vreg_enabled
- + || vreg->current_corner
- + == CPR3_REGULATOR_CORNER_INVALID) {
- + /* Cannot participate in aggregation. */
- + vreg->aggregated = false;
- + continue;
- + } else {
- + vreg->aggregated = true;
- + thread_valid = true;
- + }
- +
- + cpr3_regulator_aggregate_corners(&thread->aggr_corner,
- + &vreg->corner[vreg->current_corner],
- + true, ctrl->step_volt);
- + }
- +
- + valid |= thread_valid;
- +
- + if (thread_valid)
- + cpr3_regulator_aggregate_corners(&aggr_corner,
- + &thread->aggr_corner,
- + false, ctrl->step_volt);
- + }
- +
- + if (valid && ctrl->cpr_allowed_hw && ctrl->cpr_allowed_sw) {
- + rc = cpr3_closed_loop_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
- + return rc;
- + }
- + } else {
- + rc = cpr3_closed_loop_disable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
- + return rc;
- + }
- + }
- +
- + /* No threads are enabled with a valid corner so exit. */
- + if (!valid)
- + return 0;
- +
- + /*
- + * When using CPR hardware closed-loop, the voltage may vary anywhere
- + * between the floor and ceiling voltage without software notification.
- + * Therefore, it is required that the floor to ceiling range for the
- + * aggregated corner not intersect the APM threshold voltage. Adjust
- + * the floor to ceiling range if this requirement is violated.
- + *
- + * The following algorithm is applied in the case that
- + * floor < threshold <= ceiling:
- + * if open_loop >= threshold - adj, then floor = threshold
- + * else ceiling = threshold - step
- + * where adj = an adjustment factor to ensure sufficient voltage margin
- + * and step = VDD output step size
- + *
- + * The open-loop and last known voltages are also bounded by the new
- + * floor or ceiling value as needed.
- + */
- + if (ctrl->use_hw_closed_loop
- + && aggr_corner.ceiling_volt >= ctrl->apm_threshold_volt
- + && aggr_corner.floor_volt < ctrl->apm_threshold_volt) {
- +
- + if (aggr_corner.open_loop_volt
- + >= ctrl->apm_threshold_volt - ctrl->apm_adj_volt)
- + aggr_corner.floor_volt = ctrl->apm_threshold_volt;
- + else
- + aggr_corner.ceiling_volt
- + = ctrl->apm_threshold_volt - ctrl->step_volt;
- +
- + aggr_corner.last_volt
- + = max(aggr_corner.last_volt, aggr_corner.floor_volt);
- + aggr_corner.last_volt
- + = min(aggr_corner.last_volt, aggr_corner.ceiling_volt);
- + aggr_corner.open_loop_volt
- + = max(aggr_corner.open_loop_volt, aggr_corner.floor_volt);
- + aggr_corner.open_loop_volt
- + = min(aggr_corner.open_loop_volt, aggr_corner.ceiling_volt);
- + }
- +
- + if (ctrl->use_hw_closed_loop
- + && aggr_corner.ceiling_volt >= ctrl->mem_acc_threshold_volt
- + && aggr_corner.floor_volt < ctrl->mem_acc_threshold_volt) {
- + aggr_corner.floor_volt = ctrl->mem_acc_threshold_volt;
- + aggr_corner.last_volt = max(aggr_corner.last_volt,
- + aggr_corner.floor_volt);
- + aggr_corner.open_loop_volt = max(aggr_corner.open_loop_volt,
- + aggr_corner.floor_volt);
- + }
- +
- + if (ctrl->use_hw_closed_loop) {
- + dynamic_floor_volt
- + = cpr3_regulator_get_dynamic_floor_volt(ctrl,
- + reg_last_measurement);
- + if (aggr_corner.floor_volt < dynamic_floor_volt) {
- + aggr_corner.floor_volt = dynamic_floor_volt;
- + aggr_corner.last_volt = max(aggr_corner.last_volt,
- + aggr_corner.floor_volt);
- + aggr_corner.open_loop_volt
- + = max(aggr_corner.open_loop_volt,
- + aggr_corner.floor_volt);
- + aggr_corner.ceiling_volt = max(aggr_corner.ceiling_volt,
- + aggr_corner.floor_volt);
- + }
- + }
- +
- + if (ctrl->cpr_enabled && ctrl->last_corner_was_closed_loop) {
- + /*
- + * Always program open-loop voltage for CPR4 controllers which
- + * support hardware closed-loop. Storing the last closed loop
- + * voltage in corner structure can still help with debugging.
- + */
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
- + new_volt = aggr_corner.last_volt;
- + else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
- + && ctrl->supports_hw_closed_loop)
- + new_volt = aggr_corner.open_loop_volt;
- + else
- + new_volt = min(aggr_corner.last_volt +
- + cpr3_regulator_max_sdelta_diff(aggr_corner.sdelta,
- + ctrl->step_volt),
- + aggr_corner.ceiling_volt);
- +
- + aggr_corner.last_volt = new_volt;
- + } else {
- + new_volt = aggr_corner.open_loop_volt;
- + aggr_corner.last_volt = aggr_corner.open_loop_volt;
- + }
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
- + && ctrl->supports_hw_closed_loop) {
- + /*
- + * Store last aggregated corner open-loop voltage in vdd_volt
- + * which is used when programming current aggregated corner
- + * required voltage.
- + */
- + vdd_volt = last_corner_volt;
- + }
- +
- + cpr3_debug(ctrl, "setting new voltage=%d uV\n", new_volt);
- + rc = cpr3_regulator_scale_vdd_voltage(ctrl, new_volt,
- + vdd_volt, &aggr_corner);
- + if (rc) {
- + cpr3_err(ctrl, "vdd voltage scaling failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + /* Only update registers if CPR is enabled. */
- + if (ctrl->cpr_enabled) {
- + if (ctrl->use_hw_closed_loop) {
- + /* Hardware closed-loop */
- +
- + /* Set ceiling and floor limits in hardware */
- + rc = regulator_set_voltage(ctrl->vdd_limit_regulator,
- + aggr_corner.floor_volt,
- + aggr_corner.ceiling_volt);
- + if (rc) {
- + cpr3_err(ctrl, "could not configure HW closed-loop voltage limits, rc=%d\n",
- + rc);
- + return rc;
- + }
- + } else {
- + /* Software closed-loop */
- +
- + /*
- + * Disable UP or DOWN interrupts when at ceiling or
- + * floor respectively.
- + */
- + if (new_volt == aggr_corner.floor_volt)
- + aggr_corner.irq_en &= ~CPR3_IRQ_DOWN;
- + if (new_volt == aggr_corner.ceiling_volt)
- + aggr_corner.irq_en &= ~CPR3_IRQ_UP;
- +
- + cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
- + CPR3_IRQ_UP | CPR3_IRQ_DOWN);
- + cpr3_write(ctrl, CPR3_REG_IRQ_EN, aggr_corner.irq_en);
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + cpr3_regulator_set_target_quot(&ctrl->thread[i]);
- +
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- +
- + if (vreg->vreg_enabled)
- + vreg->last_closed_loop_corner
- + = vreg->current_corner;
- + }
- + }
- +
- + if (ctrl->proc_clock_throttle) {
- + if (aggr_corner.ceiling_volt > aggr_corner.floor_volt
- + && (ctrl->use_hw_closed_loop
- + || new_volt < aggr_corner.ceiling_volt))
- + cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
- + ctrl->proc_clock_throttle);
- + else
- + cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
- + CPR3_PD_THROTTLE_DISABLE);
- + }
- +
- + /*
- + * Ensure that all CPR register writes complete before
- + * re-enabling CPR loop operation.
- + */
- + wmb();
- + } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
- + && ctrl->vdd_limit_regulator) {
- + /* Set ceiling and floor limits in hardware */
- + rc = regulator_set_voltage(ctrl->vdd_limit_regulator,
- + aggr_corner.floor_volt,
- + aggr_corner.ceiling_volt);
- + if (rc) {
- + cpr3_err(ctrl, "could not configure HW closed-loop voltage limits, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + ctrl->aggr_corner = aggr_corner;
- +
- + if (ctrl->allow_core_count_adj || ctrl->allow_temp_adj
- + || ctrl->allow_boost) {
- + rc = cpr3_controller_program_sdelta(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "failed to program sdelta, rc=%d\n", rc);
- + return rc;
- + }
- + }
- +
- + /*
- + * Only enable the CPR controller if it is possible to set more than
- + * one vdd-supply voltage.
- + */
- + if (aggr_corner.ceiling_volt > aggr_corner.floor_volt &&
- + !aggr_corner.use_open_loop)
- + cpr3_ctrl_loop_enable(ctrl);
- +
- + ctrl->last_corner_was_closed_loop = ctrl->cpr_enabled;
- + cpr3_debug(ctrl, "CPR configuration updated\n");
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_wait_for_idle() - wait for the CPR controller to no longer be
- + * busy
- + * @ctrl: Pointer to the CPR3 controller
- + * @max_wait_ns: Max wait time in nanoseconds
- + *
- + * Return: 0 on success or -ETIMEDOUT if the controller was still busy after
- + * the maximum delay time
- + */
- +static int cpr3_regulator_wait_for_idle(struct cpr3_controller *ctrl,
- + s64 max_wait_ns)
- +{
- + ktime_t start, end;
- + s64 time_ns;
- + u32 reg;
- +
- + /*
- + * Ensure that all previous CPR register writes have completed before
- + * checking the status register.
- + */
- + mb();
- +
- + start = ktime_get();
- + do {
- + end = ktime_get();
- + time_ns = ktime_to_ns(ktime_sub(end, start));
- + if (time_ns > max_wait_ns) {
- + cpr3_err(ctrl, "CPR controller still busy after %lld us\n",
- + div_s64(time_ns, 1000));
- + return -ETIMEDOUT;
- + }
- + usleep_range(50, 100);
- + reg = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
- + } while (reg & CPR3_CPR_STATUS_BUSY_MASK);
- +
- + return 0;
- +}
- +
- +/**
- + * cmp_int() - int comparison function to be passed into the sort() function
- + * which leads to ascending sorting
- + * @a: First int value
- + * @b: Second int value
- + *
- + * Return: >0 if a > b, 0 if a == b, <0 if a < b
- + */
- +static int cmp_int(const void *a, const void *b)
- +{
- + return *(int *)a - *(int *)b;
- +}
- +
- +/**
- + * cpr3_regulator_measure_aging() - measure the quotient difference for the
- + * specified CPR aging sensor
- + * @ctrl: Pointer to the CPR3 controller
- + * @aging_sensor: Aging sensor to measure
- + *
- + * Note that vdd-supply must be configured to the aging reference voltage before
- + * calling this function.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_measure_aging(struct cpr3_controller *ctrl,
- + struct cpr3_aging_sensor_info *aging_sensor)
- +{
- + u32 mask, reg, result, quot_min, quot_max, sel_min, sel_max;
- + u32 quot_min_scaled, quot_max_scaled;
- + u32 gcnt, gcnt_ref, gcnt0_restore, gcnt1_restore, irq_restore;
- + u32 ro_mask_restore, cont_dly_restore, up_down_dly_restore = 0;
- + int quot_delta, quot_delta_scaled, quot_delta_scaled_sum;
- + int *quot_delta_results;
- + int rc, rc2, i, aging_measurement_count, filtered_count;
- + bool is_aging_measurement;
- +
- + quot_delta_results = kcalloc(CPR3_AGING_MEASUREMENT_ITERATIONS,
- + sizeof(*quot_delta_results), GFP_KERNEL);
- + if (!quot_delta_results)
- + return -ENOMEM;
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc);
- + kfree(quot_delta_results);
- + return rc;
- + }
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + /* Enable up, down, and mid CPR interrupts */
- + irq_restore = cpr3_read(ctrl, CPR3_REG_IRQ_EN);
- + cpr3_write(ctrl, CPR3_REG_IRQ_EN,
- + CPR3_IRQ_UP | CPR3_IRQ_DOWN | CPR3_IRQ_MID);
- +
- + /* Ensure that the aging sensor is assigned to CPR thread 0 */
- + cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(aging_sensor->sensor_id), 0);
- +
- + /* Switch from HW to SW closed-loop if necessary */
- + if (ctrl->supports_hw_closed_loop) {
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
- + CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
- + } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
- + CPR3_HW_CLOSED_LOOP_DISABLE);
- + }
- + }
- +
- + /* Configure the GCNT for RO0 and RO1 that are used for aging */
- + gcnt0_restore = cpr3_read(ctrl, CPR3_REG_GCNT(0));
- + gcnt1_restore = cpr3_read(ctrl, CPR3_REG_GCNT(1));
- + gcnt_ref = cpr3_regulator_get_gcnt(ctrl);
- + gcnt = gcnt_ref * 3 / 2;
- + cpr3_write(ctrl, CPR3_REG_GCNT(0), gcnt);
- + cpr3_write(ctrl, CPR3_REG_GCNT(1), gcnt);
- +
- + /* Unmask all RO's */
- + ro_mask_restore = cpr3_read(ctrl, CPR3_REG_RO_MASK(0));
- + cpr3_write(ctrl, CPR3_REG_RO_MASK(0), 0);
- +
- + /*
- + * Mask all sensors except for the one to measure and bypass all
- + * sensors in collapsible domains.
- + */
- + for (i = 0; i <= ctrl->sensor_count / 32; i++) {
- + mask = GENMASK(min(31, ctrl->sensor_count - i * 32), 0);
- + if (aging_sensor->sensor_id / 32 >= i
- + && aging_sensor->sensor_id / 32 < (i + 1))
- + mask &= ~BIT(aging_sensor->sensor_id % 32);
- + cpr3_write(ctrl, CPR3_REG_SENSOR_MASK_WRITE_BANK(i), mask);
- + cpr3_write(ctrl, CPR3_REG_SENSOR_BYPASS_WRITE_BANK(i),
- + aging_sensor->bypass_mask[i]);
- + }
- +
- + /* Set CPR loop delays to 0 us */
- + if (ctrl->supports_hw_closed_loop
- + && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + cont_dly_restore = cpr3_read(ctrl, CPR3_REG_CPR_TIMER_MID_CONT);
- + up_down_dly_restore = cpr3_read(ctrl,
- + CPR3_REG_CPR_TIMER_UP_DN_CONT);
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, 0);
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT, 0);
- + } else {
- + cont_dly_restore = cpr3_read(ctrl,
- + CPR3_REG_CPR_TIMER_AUTO_CONT);
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT, 0);
- + }
- +
- + /* Set count mode to all-at-once min with no repeat */
- + cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
- + CPR3_CPR_CTL_COUNT_MODE_MASK | CPR3_CPR_CTL_COUNT_REPEAT_MASK,
- + CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN
- + << CPR3_CPR_CTL_COUNT_MODE_SHIFT);
- +
- + cpr3_ctrl_loop_enable(ctrl);
- +
- + rc = cpr3_regulator_wait_for_idle(ctrl,
- + CPR3_AGING_MEASUREMENT_TIMEOUT_NS);
- + if (rc)
- + goto cleanup;
- +
- + /* Set count mode to all-at-once aging */
- + cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL, CPR3_CPR_CTL_COUNT_MODE_MASK,
- + CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE
- + << CPR3_CPR_CTL_COUNT_MODE_SHIFT);
- +
- + aging_measurement_count = 0;
- + for (i = 0; i < CPR3_AGING_MEASUREMENT_ITERATIONS; i++) {
- + /* Send CONT_NACK */
- + cpr3_write(ctrl, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_NACK);
- +
- + rc = cpr3_regulator_wait_for_idle(ctrl,
- + CPR3_AGING_MEASUREMENT_TIMEOUT_NS);
- + if (rc)
- + goto cleanup;
- +
- + /* Check for PAGE_IS_AGE flag in status register */
- + reg = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
- + is_aging_measurement
- + = reg & CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK;
- +
- + /* Read CPR measurement results */
- + result = cpr3_read(ctrl, CPR3_REG_RESULT1(0));
- + quot_min = (result & CPR3_RESULT1_QUOT_MIN_MASK)
- + >> CPR3_RESULT1_QUOT_MIN_SHIFT;
- + quot_max = (result & CPR3_RESULT1_QUOT_MAX_MASK)
- + >> CPR3_RESULT1_QUOT_MAX_SHIFT;
- + sel_min = (result & CPR3_RESULT1_RO_MIN_MASK)
- + >> CPR3_RESULT1_RO_MIN_SHIFT;
- + sel_max = (result & CPR3_RESULT1_RO_MAX_MASK)
- + >> CPR3_RESULT1_RO_MAX_SHIFT;
- +
- + /*
- + * Scale the quotients so that they are equivalent to the fused
- + * values. This accounts for the difference in measurement
- + * interval times.
- + */
- + quot_min_scaled = quot_min * (gcnt_ref + 1) / (gcnt + 1);
- + quot_max_scaled = quot_max * (gcnt_ref + 1) / (gcnt + 1);
- +
- + if (sel_max == 1) {
- + quot_delta = quot_max - quot_min;
- + quot_delta_scaled = quot_max_scaled - quot_min_scaled;
- + } else {
- + quot_delta = quot_min - quot_max;
- + quot_delta_scaled = quot_min_scaled - quot_max_scaled;
- + }
- +
- + if (is_aging_measurement)
- + quot_delta_results[aging_measurement_count++]
- + = quot_delta_scaled;
- +
- + cpr3_debug(ctrl, "aging results: page_is_age=%u, sel_min=%u, sel_max=%u, quot_min=%u, quot_max=%u, quot_delta=%d, quot_min_scaled=%u, quot_max_scaled=%u, quot_delta_scaled=%d\n",
- + is_aging_measurement, sel_min, sel_max, quot_min,
- + quot_max, quot_delta, quot_min_scaled, quot_max_scaled,
- + quot_delta_scaled);
- + }
- +
- + filtered_count
- + = aging_measurement_count - CPR3_AGING_MEASUREMENT_FILTER * 2;
- + if (filtered_count > 0) {
- + sort(quot_delta_results, aging_measurement_count,
- + sizeof(*quot_delta_results), cmp_int, NULL);
- +
- + quot_delta_scaled_sum = 0;
- + for (i = 0; i < filtered_count; i++)
- + quot_delta_scaled_sum
- + += quot_delta_results[i
- + + CPR3_AGING_MEASUREMENT_FILTER];
- +
- + aging_sensor->measured_quot_diff
- + = quot_delta_scaled_sum / filtered_count;
- + cpr3_info(ctrl, "average quotient delta=%d (count=%d)\n",
- + aging_sensor->measured_quot_diff,
- + filtered_count);
- + } else {
- + cpr3_err(ctrl, "%d aging measurements completed after %d iterations\n",
- + aging_measurement_count,
- + CPR3_AGING_MEASUREMENT_ITERATIONS);
- + rc = -EBUSY;
- + }
- +
- +cleanup:
- + kfree(quot_delta_results);
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc2 = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc2) {
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc2);
- + rc = rc2;
- + }
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_restore);
- +
- + cpr3_write(ctrl, CPR3_REG_RO_MASK(0), ro_mask_restore);
- +
- + cpr3_write(ctrl, CPR3_REG_GCNT(0), gcnt0_restore);
- + cpr3_write(ctrl, CPR3_REG_GCNT(1), gcnt1_restore);
- +
- + if (ctrl->supports_hw_closed_loop
- + && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, cont_dly_restore);
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT,
- + up_down_dly_restore);
- + } else {
- + cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT,
- + cont_dly_restore);
- + }
- +
- + for (i = 0; i <= ctrl->sensor_count / 32; i++) {
- + cpr3_write(ctrl, CPR3_REG_SENSOR_MASK_WRITE_BANK(i), 0);
- + cpr3_write(ctrl, CPR3_REG_SENSOR_BYPASS_WRITE_BANK(i), 0);
- + }
- +
- + cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
- + CPR3_CPR_CTL_COUNT_MODE_MASK | CPR3_CPR_CTL_COUNT_REPEAT_MASK,
- + (ctrl->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT)
- + | (ctrl->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT));
- +
- + cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(aging_sensor->sensor_id),
- + ctrl->sensor_owner[aging_sensor->sensor_id]);
- +
- + cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
- + CPR3_IRQ_UP | CPR3_IRQ_DOWN | CPR3_IRQ_MID);
- +
- + if (ctrl->supports_hw_closed_loop) {
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
- + ctrl->use_hw_closed_loop
- + ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
- + : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
- + } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
- + ctrl->use_hw_closed_loop
- + ? CPR3_HW_CLOSED_LOOP_ENABLE
- + : CPR3_HW_CLOSED_LOOP_DISABLE);
- + }
- + }
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_regulator_readjust_volt_and_quot() - readjust the target quotients as
- + * well as the floor, ceiling, and open-loop voltages for the
- + * regulator by removing the old adjustment and adding the new one
- + * @vreg: Pointer to the CPR3 regulator
- + * @old_adjust_volt: Old aging adjustment voltage in microvolts
- + * @new_adjust_volt: New aging adjustment voltage in microvolts
- + *
- + * Also reset the cached closed loop voltage (last_volt) to equal the open-loop
- + * voltage for each corner.
- + *
- + * Return: None
- + */
- +static void cpr3_regulator_readjust_volt_and_quot(struct cpr3_regulator *vreg,
- + int old_adjust_volt, int new_adjust_volt)
- +{
- + unsigned long long temp;
- + int i, j, old_volt, new_volt, rounded_volt;
- +
- + if (!vreg->aging_allowed)
- + return;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + temp = (unsigned long long)old_adjust_volt
- + * (unsigned long long)vreg->corner[i].aging_derate;
- + do_div(temp, 1000);
- + old_volt = temp;
- +
- + temp = (unsigned long long)new_adjust_volt
- + * (unsigned long long)vreg->corner[i].aging_derate;
- + do_div(temp, 1000);
- + new_volt = temp;
- +
- + old_volt = min(vreg->aging_max_adjust_volt, old_volt);
- + new_volt = min(vreg->aging_max_adjust_volt, new_volt);
- +
- + for (j = 0; j < CPR3_RO_COUNT; j++) {
- + if (vreg->corner[i].target_quot[j] != 0) {
- + vreg->corner[i].target_quot[j]
- + += cpr3_quot_adjustment(
- + vreg->corner[i].ro_scale[j],
- + new_volt)
- + - cpr3_quot_adjustment(
- + vreg->corner[i].ro_scale[j],
- + old_volt);
- + }
- + }
- +
- + rounded_volt = CPR3_ROUND(new_volt,
- + vreg->thread->ctrl->step_volt);
- +
- + if (!vreg->aging_allow_open_loop_adj)
- + rounded_volt = 0;
- +
- + vreg->corner[i].ceiling_volt
- + = vreg->corner[i].unaged_ceiling_volt + rounded_volt;
- + vreg->corner[i].ceiling_volt = min(vreg->corner[i].ceiling_volt,
- + vreg->corner[i].abs_ceiling_volt);
- + vreg->corner[i].floor_volt
- + = vreg->corner[i].unaged_floor_volt + rounded_volt;
- + vreg->corner[i].floor_volt = min(vreg->corner[i].floor_volt,
- + vreg->corner[i].ceiling_volt);
- + vreg->corner[i].open_loop_volt
- + = vreg->corner[i].unaged_open_loop_volt + rounded_volt;
- + vreg->corner[i].open_loop_volt
- + = min(vreg->corner[i].open_loop_volt,
- + vreg->corner[i].ceiling_volt);
- +
- + vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
- +
- + cpr3_debug(vreg, "corner %d: applying %d uV closed-loop and %d uV open-loop voltage margin adjustment\n",
- + i, new_volt, rounded_volt);
- + }
- +}
- +
- +/**
- + * cpr3_regulator_set_aging_ref_adjustment() - adjust target quotients for the
- + * regulators managed by this CPR controller to account for aging
- + * @ctrl: Pointer to the CPR3 controller
- + * @ref_adjust_volt: New aging reference adjustment voltage in microvolts to
- + * apply to all regulators managed by this CPR controller
- + *
- + * The existing aging adjustment as defined by ctrl->aging_ref_adjust_volt is
- + * first removed and then the adjustment is applied. Lastly, the value of
- + * ctrl->aging_ref_adjust_volt is updated to ref_adjust_volt.
- + */
- +static void cpr3_regulator_set_aging_ref_adjustment(
- + struct cpr3_controller *ctrl, int ref_adjust_volt)
- +{
- + struct cpr3_regulator *vreg;
- + int i, j;
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + cpr3_regulator_readjust_volt_and_quot(vreg,
- + ctrl->aging_ref_adjust_volt, ref_adjust_volt);
- + }
- + }
- +
- + ctrl->aging_ref_adjust_volt = ref_adjust_volt;
- +}
- +
- +/**
- + * cpr3_regulator_aging_adjust() - adjust the target quotients for regulators
- + * based on the output of CPR aging sensors
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_aging_adjust(struct cpr3_controller *ctrl)
- +{
- + struct cpr3_regulator *vreg;
- + struct cpr3_corner restore_aging_corner;
- + struct cpr3_corner *corner;
- + int *restore_current_corner;
- + bool *restore_vreg_enabled;
- + int i, j, id, rc, rc2, vreg_count, aging_volt, max_aging_volt = 0;
- + u32 reg;
- +
- + if (!ctrl->aging_required || !ctrl->cpr_enabled
- + || ctrl->aggr_corner.ceiling_volt == 0
- + || ctrl->aggr_corner.ceiling_volt > ctrl->aging_ref_volt)
- + return 0;
- +
- + for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + vreg_count++;
- +
- + if (vreg->aging_allowed && vreg->vreg_enabled
- + && vreg->current_corner > vreg->aging_corner)
- + return 0;
- + }
- + }
- +
- + /* Verify that none of the aging sensors are currently masked. */
- + for (i = 0; i < ctrl->aging_sensor_count; i++) {
- + id = ctrl->aging_sensor[i].sensor_id;
- + reg = cpr3_read(ctrl, CPR3_REG_SENSOR_MASK_READ(id));
- + if (reg & BIT(id % 32))
- + return 0;
- + }
- +
- + /*
- + * Verify that the aging possible register (if specified) has an
- + * acceptable value.
- + */
- + if (ctrl->aging_possible_reg) {
- + reg = readl_relaxed(ctrl->aging_possible_reg);
- + reg &= ctrl->aging_possible_mask;
- + if (reg != ctrl->aging_possible_val)
- + return 0;
- + }
- +
- + restore_current_corner = kcalloc(vreg_count,
- + sizeof(*restore_current_corner), GFP_KERNEL);
- + restore_vreg_enabled = kcalloc(vreg_count,
- + sizeof(*restore_vreg_enabled), GFP_KERNEL);
- + if (!restore_current_corner || !restore_vreg_enabled) {
- + kfree(restore_current_corner);
- + kfree(restore_vreg_enabled);
- + return -ENOMEM;
- + }
- +
- + /* Force all regulators to the aging corner */
- + for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++, vreg_count++) {
- + vreg = &ctrl->thread[i].vreg[j];
- +
- + restore_current_corner[vreg_count]
- + = vreg->current_corner;
- + restore_vreg_enabled[vreg_count]
- + = vreg->vreg_enabled;
- +
- + vreg->current_corner = vreg->aging_corner;
- + vreg->vreg_enabled = true;
- + }
- + }
- +
- + /* Force one of the regulators to require the aging reference voltage */
- + vreg = &ctrl->thread[0].vreg[0];
- + corner = &vreg->corner[vreg->current_corner];
- + restore_aging_corner = *corner;
- + corner->ceiling_volt = ctrl->aging_ref_volt;
- + corner->floor_volt = ctrl->aging_ref_volt;
- + corner->open_loop_volt = ctrl->aging_ref_volt;
- + corner->last_volt = ctrl->aging_ref_volt;
- +
- + /* Skip last_volt caching */
- + ctrl->last_corner_was_closed_loop = false;
- +
- + /* Set the vdd supply voltage to the aging reference voltage */
- + rc = _cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "unable to force vdd-supply to the aging reference voltage=%d uV, rc=%d\n",
- + ctrl->aging_ref_volt, rc);
- + goto cleanup;
- + }
- +
- + if (ctrl->aging_vdd_mode) {
- + rc = regulator_set_mode(ctrl->vdd_regulator,
- + ctrl->aging_vdd_mode);
- + if (rc) {
- + cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
- + ctrl->aging_vdd_mode, rc);
- + goto cleanup;
- + }
- + }
- +
- + /* Perform aging measurement on all aging sensors */
- + for (i = 0; i < ctrl->aging_sensor_count; i++) {
- + for (j = 0; j < CPR3_AGING_RETRY_COUNT; j++) {
- + rc = cpr3_regulator_measure_aging(ctrl,
- + &ctrl->aging_sensor[i]);
- + if (!rc)
- + break;
- + }
- +
- + if (!rc) {
- + aging_volt =
- + cpr3_voltage_adjustment(
- + ctrl->aging_sensor[i].ro_scale,
- + ctrl->aging_sensor[i].measured_quot_diff
- + - ctrl->aging_sensor[i].init_quot_diff);
- + max_aging_volt = max(max_aging_volt, aging_volt);
- + } else {
- + cpr3_err(ctrl, "CPR aging measurement failed after %d tries, rc=%d\n",
- + j, rc);
- + ctrl->aging_failed = true;
- + ctrl->aging_required = false;
- + goto cleanup;
- + }
- + }
- +
- +cleanup:
- + vreg = &ctrl->thread[0].vreg[0];
- + vreg->corner[vreg->current_corner] = restore_aging_corner;
- +
- + for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++, vreg_count++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + vreg->current_corner
- + = restore_current_corner[vreg_count];
- + vreg->vreg_enabled = restore_vreg_enabled[vreg_count];
- + }
- + }
- +
- + kfree(restore_current_corner);
- + kfree(restore_vreg_enabled);
- +
- + /* Adjust the CPR target quotients according to the aging measurement */
- + if (!rc) {
- + cpr3_regulator_set_aging_ref_adjustment(ctrl, max_aging_volt);
- +
- + cpr3_info(ctrl, "aging measurement successful; aging reference adjustment voltage=%d uV\n",
- + ctrl->aging_ref_adjust_volt);
- + ctrl->aging_succeeded = true;
- + ctrl->aging_required = false;
- + }
- +
- + if (ctrl->aging_complete_vdd_mode) {
- + rc = regulator_set_mode(ctrl->vdd_regulator,
- + ctrl->aging_complete_vdd_mode);
- + if (rc)
- + cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
- + ctrl->aging_complete_vdd_mode, rc);
- + }
- +
- + /* Skip last_volt caching */
- + ctrl->last_corner_was_closed_loop = false;
- +
- + /*
- + * Restore vdd-supply to the voltage before the aging measurement and
- + * restore the CPR3 controller hardware state.
- + */
- + rc2 = _cpr3_regulator_update_ctrl_state(ctrl);
- +
- + /* Stop last_volt caching on for the next request */
- + ctrl->last_corner_was_closed_loop = false;
- +
- + return rc ? rc : rc2;
- +}
- +
- +/**
- + * cpr3_regulator_update_ctrl_state() - update the state of the CPR controller
- + * to reflect the corners used by all CPR3 regulators as well as
- + * the CPR operating mode and perform aging adjustments if needed
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Note, CPR3 controller lock must be held by the caller.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + rc = _cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc)
- + return rc;
- +
- + return cpr3_regulator_aging_adjust(ctrl);
- +}
- +
- +/**
- + * cpr3_regulator_set_voltage() - set the voltage corner for the CPR3 regulator
- + * associated with the regulator device
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + * @corner: New voltage corner to set (offset by CPR3_CORNER_OFFSET)
- + * @corner_max: Maximum voltage corner allowed (offset by
- + * CPR3_CORNER_OFFSET)
- + * @selector: Pointer which is filled with the selector value for the
- + * corner
- + *
- + * This function is passed as a callback function into the regulator ops that
- + * are registered for each cpr3-regulator device. The VDD voltage will not be
- + * physically configured until both this function and cpr3_regulator_enable()
- + * are called.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_set_voltage(struct regulator_dev *rdev,
- + int corner, int corner_max, unsigned *selector)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + int rc = 0;
- + int last_corner;
- +
- + corner -= CPR3_CORNER_OFFSET;
- + corner_max -= CPR3_CORNER_OFFSET;
- + *selector = corner;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (!vreg->vreg_enabled) {
- + vreg->current_corner = corner;
- + cpr3_debug(vreg, "stored corner=%d\n", corner);
- + goto done;
- + } else if (vreg->current_corner == corner) {
- + goto done;
- + }
- +
- + last_corner = vreg->current_corner;
- + vreg->current_corner = corner;
- +
- + if (vreg->cpr4_regulator_data != NULL)
- + if (vreg->cpr4_regulator_data->mem_acc_funcs != NULL)
- + vreg->cpr4_regulator_data->mem_acc_funcs->set_mem_acc(rdev);
- +
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
- + vreg->current_corner = last_corner;
- + }
- +
- + if (vreg->cpr4_regulator_data != NULL)
- + if (vreg->cpr4_regulator_data->mem_acc_funcs != NULL)
- + vreg->cpr4_regulator_data->mem_acc_funcs->clear_mem_acc(rdev);
- +
- + cpr3_debug(vreg, "set corner=%d\n", corner);
- +done:
- + mutex_unlock(&ctrl->lock);
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_handle_temp_open_loop_adjustment() - voltage based cold temperature
- + *
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + * @is_cold: Flag to denote enter/exit cold condition
- + *
- + * This function is adjusts voltage margin based on cold condition
- + *
- + * Return: 0 = success
- + */
- +
- +int cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
- + bool is_cold)
- +{
- + int i ,j, k, rc;
- + struct cpr3_regulator *vreg;
- +
- + mutex_lock(&ctrl->lock);
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + for (k = 0; k < vreg->corner_count; k++) {
- + vreg->corner[k].open_loop_volt = is_cold ?
- + vreg->corner[k].cold_temp_open_loop_volt :
- + vreg->corner[k].normal_temp_open_loop_volt;
- + }
- + }
- + }
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + mutex_unlock(&ctrl->lock);
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_regulator_get_voltage() - get the voltage corner for the CPR3 regulator
- + * associated with the regulator device
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + *
- + * This function is passed as a callback function into the regulator ops that
- + * are registered for each cpr3-regulator device.
- + *
- + * Return: voltage corner value offset by CPR3_CORNER_OFFSET
- + */
- +static int cpr3_regulator_get_voltage(struct regulator_dev *rdev)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- +
- + if (vreg->current_corner == CPR3_REGULATOR_CORNER_INVALID)
- + return CPR3_CORNER_OFFSET;
- + else
- + return vreg->current_corner + CPR3_CORNER_OFFSET;
- +}
- +
- +/**
- + * cpr3_regulator_list_voltage() - return the voltage corner mapped to the
- + * specified selector
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + * @selector: Regulator selector
- + *
- + * This function is passed as a callback function into the regulator ops that
- + * are registered for each cpr3-regulator device.
- + *
- + * Return: voltage corner value offset by CPR3_CORNER_OFFSET
- + */
- +static int cpr3_regulator_list_voltage(struct regulator_dev *rdev,
- + unsigned selector)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- +
- + if (selector < vreg->corner_count)
- + return selector + CPR3_CORNER_OFFSET;
- + else
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_is_enabled() - return the enable state of the CPR3 regulator
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + *
- + * This function is passed as a callback function into the regulator ops that
- + * are registered for each cpr3-regulator device.
- + *
- + * Return: true if regulator is enabled, false if regulator is disabled
- + */
- +static int cpr3_regulator_is_enabled(struct regulator_dev *rdev)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- +
- + return vreg->vreg_enabled;
- +}
- +
- +/**
- + * cpr3_regulator_enable() - enable the CPR3 regulator
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + *
- + * This function is passed as a callback function into the regulator ops that
- + * are registered for each cpr3-regulator device.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_enable(struct regulator_dev *rdev)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + int rc = 0;
- +
- + if (vreg->vreg_enabled == true)
- + return 0;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (ctrl->system_regulator) {
- + rc = regulator_enable(ctrl->system_regulator);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_enable(system) failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + rc = regulator_enable(ctrl->vdd_regulator);
- + if (rc) {
- + cpr3_err(vreg, "regulator_enable(vdd) failed, rc=%d\n", rc);
- + goto done;
- + }
- +
- + vreg->vreg_enabled = true;
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
- + regulator_disable(ctrl->vdd_regulator);
- + vreg->vreg_enabled = false;
- + goto done;
- + }
- +
- + cpr3_debug(vreg, "Enabled\n");
- +done:
- + mutex_unlock(&ctrl->lock);
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_regulator_disable() - disable the CPR3 regulator
- + * @rdev: Regulator device pointer for the cpr3-regulator
- + *
- + * This function is passed as a callback function into the regulator ops that
- + * are registered for each cpr3-regulator device.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_disable(struct regulator_dev *rdev)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + int rc, rc2;
- +
- + if (vreg->vreg_enabled == false)
- + return 0;
- +
- + mutex_lock(&ctrl->lock);
- + rc = regulator_disable(ctrl->vdd_regulator);
- + if (rc) {
- + cpr3_err(vreg, "regulator_disable(vdd) failed, rc=%d\n", rc);
- + goto done;
- + }
- +
- + vreg->vreg_enabled = false;
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
- + rc2 = regulator_enable(ctrl->vdd_regulator);
- + vreg->vreg_enabled = true;
- + goto done;
- + }
- +
- + if (ctrl->system_regulator) {
- + rc = regulator_disable(ctrl->system_regulator);
- + if (rc) {
- + cpr3_err(ctrl, "regulator_disable(system) failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + cpr3_debug(vreg, "Disabled\n");
- +done:
- + mutex_unlock(&ctrl->lock);
- +
- + return rc;
- +}
- +
- +static struct regulator_ops cpr3_regulator_ops = {
- + .enable = cpr3_regulator_enable,
- + .disable = cpr3_regulator_disable,
- + .is_enabled = cpr3_regulator_is_enabled,
- + .set_voltage = cpr3_regulator_set_voltage,
- + .get_voltage = cpr3_regulator_get_voltage,
- + .list_voltage = cpr3_regulator_list_voltage,
- +};
- +
- +/**
- + * cpr3_print_result() - print CPR measurement results to the kernel log for
- + * debugging purposes
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: None
- + */
- +static void cpr3_print_result(struct cpr3_thread *thread)
- +{
- + struct cpr3_controller *ctrl = thread->ctrl;
- + u32 result[3], busy, step_dn, step_up, error_steps, error, negative;
- + u32 quot_min, quot_max, ro_min, ro_max, step_quot_min, step_quot_max;
- + u32 sensor_min, sensor_max;
- + char *sign;
- +
- + result[0] = cpr3_read(ctrl, CPR3_REG_RESULT0(thread->thread_id));
- + result[1] = cpr3_read(ctrl, CPR3_REG_RESULT1(thread->thread_id));
- + result[2] = cpr3_read(ctrl, CPR3_REG_RESULT2(thread->thread_id));
- +
- + busy = !!(result[0] & CPR3_RESULT0_BUSY_MASK);
- + step_dn = !!(result[0] & CPR3_RESULT0_STEP_DN_MASK);
- + step_up = !!(result[0] & CPR3_RESULT0_STEP_UP_MASK);
- + error_steps = (result[0] & CPR3_RESULT0_ERROR_STEPS_MASK)
- + >> CPR3_RESULT0_ERROR_STEPS_SHIFT;
- + error = (result[0] & CPR3_RESULT0_ERROR_MASK)
- + >> CPR3_RESULT0_ERROR_SHIFT;
- + negative = !!(result[0] & CPR3_RESULT0_NEGATIVE_MASK);
- +
- + quot_min = (result[1] & CPR3_RESULT1_QUOT_MIN_MASK)
- + >> CPR3_RESULT1_QUOT_MIN_SHIFT;
- + quot_max = (result[1] & CPR3_RESULT1_QUOT_MAX_MASK)
- + >> CPR3_RESULT1_QUOT_MAX_SHIFT;
- + ro_min = (result[1] & CPR3_RESULT1_RO_MIN_MASK)
- + >> CPR3_RESULT1_RO_MIN_SHIFT;
- + ro_max = (result[1] & CPR3_RESULT1_RO_MAX_MASK)
- + >> CPR3_RESULT1_RO_MAX_SHIFT;
- +
- + step_quot_min = (result[2] & CPR3_RESULT2_STEP_QUOT_MIN_MASK)
- + >> CPR3_RESULT2_STEP_QUOT_MIN_SHIFT;
- + step_quot_max = (result[2] & CPR3_RESULT2_STEP_QUOT_MAX_MASK)
- + >> CPR3_RESULT2_STEP_QUOT_MAX_SHIFT;
- + sensor_min = (result[2] & CPR3_RESULT2_SENSOR_MIN_MASK)
- + >> CPR3_RESULT2_SENSOR_MIN_SHIFT;
- + sensor_max = (result[2] & CPR3_RESULT2_SENSOR_MAX_MASK)
- + >> CPR3_RESULT2_SENSOR_MAX_SHIFT;
- +
- + sign = negative ? "-" : "";
- + cpr3_debug(ctrl, "thread %u: busy=%u, step_dn=%u, step_up=%u, error_steps=%s%u, error=%s%u\n",
- + thread->thread_id, busy, step_dn, step_up, sign, error_steps,
- + sign, error);
- + cpr3_debug(ctrl, "thread %u: quot_min=%u, quot_max=%u, ro_min=%u, ro_max=%u\n",
- + thread->thread_id, quot_min, quot_max, ro_min, ro_max);
- + cpr3_debug(ctrl, "thread %u: step_quot_min=%u, step_quot_max=%u, sensor_min=%u, sensor_max=%u\n",
- + thread->thread_id, step_quot_min, step_quot_max, sensor_min,
- + sensor_max);
- +}
- +
- +/**
- + * cpr3_thread_busy() - returns if the specified CPR3 thread is busy taking
- + * a measurement
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: CPR3 busy status
- + */
- +static bool cpr3_thread_busy(struct cpr3_thread *thread)
- +{
- + u32 result;
- +
- + result = cpr3_read(thread->ctrl, CPR3_REG_RESULT0(thread->thread_id));
- +
- + return !!(result & CPR3_RESULT0_BUSY_MASK);
- +}
- +
- +/**
- + * cpr3_irq_handler() - CPR interrupt handler callback function used for
- + * software closed-loop operation
- + * @irq: CPR interrupt number
- + * @data: Private data corresponding to the CPR3 controller
- + * pointer
- + *
- + * This function increases or decreases the vdd supply voltage based upon the
- + * CPR controller recommendation.
- + *
- + * Return: IRQ_HANDLED
- + */
- +static irqreturn_t cpr3_irq_handler(int irq, void *data)
- +{
- + struct cpr3_controller *ctrl = data;
- + struct cpr3_corner *aggr = &ctrl->aggr_corner;
- + u32 cont = CPR3_CONT_CMD_NACK;
- + u32 reg_last_measurement = 0;
- + struct cpr3_regulator *vreg;
- + struct cpr3_corner *corner;
- + unsigned long flags;
- + int i, j, new_volt, last_volt, dynamic_floor_volt, rc;
- + u32 irq_en, status, cpr_status, ctl;
- + bool up, down;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (!ctrl->cpr_enabled) {
- + cpr3_debug(ctrl, "CPR interrupt received but CPR is disabled\n");
- + mutex_unlock(&ctrl->lock);
- + return IRQ_HANDLED;
- + } else if (ctrl->use_hw_closed_loop) {
- + cpr3_debug(ctrl, "CPR interrupt received but CPR is using HW closed-loop\n");
- + goto done;
- + }
- +
- + /*
- + * CPR IRQ status checking and CPR controller disabling must happen
- + * atomically and without invening delay in order to avoid an interrupt
- + * storm caused by the handler racing with the CPR controller.
- + */
- + local_irq_save(flags);
- + preempt_disable();
- +
- + status = cpr3_read(ctrl, CPR3_REG_IRQ_STATUS);
- + up = status & CPR3_IRQ_UP;
- + down = status & CPR3_IRQ_DOWN;
- +
- + if (!up && !down) {
- + /*
- + * Toggle the CPR controller off and then back on since the
- + * hardware and software states are out of sync. This condition
- + * occurs after an aging measurement completes as the CPR IRQ
- + * physically triggers during the aging measurement but the
- + * handler is stuck waiting on the mutex lock.
- + */
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + local_irq_restore(flags);
- + preempt_enable();
- +
- + /* Wait for the loop disable write to complete */
- + mb();
- +
- + /* Wait for BUSY=1 and LOOP_EN=0 in CPR controller registers. */
- + for (i = 0; i < CPR3_REGISTER_WRITE_DELAY_US / 10; i++) {
- + cpr_status = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
- + ctl = cpr3_read(ctrl, CPR3_REG_CPR_CTL);
- + if (cpr_status & CPR3_CPR_STATUS_BUSY_MASK
- + && (ctl & CPR3_CPR_CTL_LOOP_EN_MASK)
- + == CPR3_CPR_CTL_LOOP_DISABLE)
- + break;
- + udelay(10);
- + }
- + if (i == CPR3_REGISTER_WRITE_DELAY_US / 10)
- + cpr3_debug(ctrl, "CPR controller not disabled after %d us\n",
- + CPR3_REGISTER_WRITE_DELAY_US);
- +
- + /* Clear interrupt status */
- + cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
- + CPR3_IRQ_UP | CPR3_IRQ_DOWN);
- +
- + /* Wait for the interrupt clearing write to complete */
- + mb();
- +
- + /* Wait for IRQ_STATUS register to be cleared. */
- + for (i = 0; i < CPR3_REGISTER_WRITE_DELAY_US / 10; i++) {
- + status = cpr3_read(ctrl, CPR3_REG_IRQ_STATUS);
- + if (!(status & (CPR3_IRQ_UP | CPR3_IRQ_DOWN)))
- + break;
- + udelay(10);
- + }
- + if (i == CPR3_REGISTER_WRITE_DELAY_US / 10)
- + cpr3_debug(ctrl, "CPR interrupts not cleared after %d us\n",
- + CPR3_REGISTER_WRITE_DELAY_US);
- +
- + cpr3_ctrl_loop_enable(ctrl);
- +
- + cpr3_debug(ctrl, "CPR interrupt received but no up or down status bit is set\n");
- +
- + mutex_unlock(&ctrl->lock);
- + return IRQ_HANDLED;
- + } else if (up && down) {
- + cpr3_debug(ctrl, "both up and down status bits set\n");
- + /* The up flag takes precedence over the down flag. */
- + down = false;
- + }
- +
- + if (ctrl->supports_hw_closed_loop)
- + reg_last_measurement
- + = cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
- + dynamic_floor_volt = cpr3_regulator_get_dynamic_floor_volt(ctrl,
- + reg_last_measurement);
- +
- + local_irq_restore(flags);
- + preempt_enable();
- +
- + irq_en = aggr->irq_en;
- + last_volt = aggr->last_volt;
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + if (cpr3_thread_busy(&ctrl->thread[i])) {
- + cpr3_debug(ctrl, "CPR thread %u busy when it should be waiting for SW cont\n",
- + ctrl->thread[i].thread_id);
- + goto done;
- + }
- + }
- +
- + new_volt = up ? last_volt + ctrl->step_volt
- + : last_volt - ctrl->step_volt;
- +
- + /* Re-enable UP/DOWN interrupt when its opposite is received. */
- + irq_en |= up ? CPR3_IRQ_DOWN : CPR3_IRQ_UP;
- +
- + if (new_volt > aggr->ceiling_volt) {
- + new_volt = aggr->ceiling_volt;
- + irq_en &= ~CPR3_IRQ_UP;
- + cpr3_debug(ctrl, "limiting to ceiling=%d uV\n",
- + aggr->ceiling_volt);
- + } else if (new_volt < aggr->floor_volt) {
- + new_volt = aggr->floor_volt;
- + irq_en &= ~CPR3_IRQ_DOWN;
- + cpr3_debug(ctrl, "limiting to floor=%d uV\n", aggr->floor_volt);
- + }
- +
- + if (down && new_volt < dynamic_floor_volt) {
- + /*
- + * The vdd-supply voltage should not be decreased below the
- + * dynamic floor voltage. However, it is not necessary (and
- + * counter productive) to force the voltage up to this level
- + * if it happened to be below it since the closed-loop voltage
- + * must have gotten there in a safe manner while the power
- + * domains for the CPR3 regulator imposing the dynamic floor
- + * were not bypassed.
- + */
- + new_volt = last_volt;
- + irq_en &= ~CPR3_IRQ_DOWN;
- + cpr3_debug(ctrl, "limiting to dynamic floor=%d uV\n",
- + dynamic_floor_volt);
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++)
- + cpr3_print_result(&ctrl->thread[i]);
- +
- + cpr3_debug(ctrl, "%s: new_volt=%d uV, last_volt=%d uV\n",
- + up ? "UP" : "DN", new_volt, last_volt);
- +
- + if (ctrl->proc_clock_throttle && last_volt == aggr->ceiling_volt
- + && new_volt < last_volt)
- + cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
- + ctrl->proc_clock_throttle);
- +
- + if (new_volt != last_volt) {
- + rc = cpr3_regulator_scale_vdd_voltage(ctrl, new_volt,
- + last_volt,
- + aggr);
- + if (rc) {
- + cpr3_err(ctrl, "scale_vdd() failed to set vdd=%d uV, rc=%d\n",
- + new_volt, rc);
- + goto done;
- + }
- + cont = CPR3_CONT_CMD_ACK;
- +
- + /*
- + * Update the closed-loop voltage for all regulators managed
- + * by this CPR controller.
- + */
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + cpr3_update_vreg_closed_loop_volt(vreg,
- + new_volt, reg_last_measurement);
- + }
- + }
- + }
- +
- + if (ctrl->proc_clock_throttle && new_volt == aggr->ceiling_volt)
- + cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
- + CPR3_PD_THROTTLE_DISABLE);
- +
- + corner = &ctrl->thread[0].vreg[0].corner[
- + ctrl->thread[0].vreg[0].current_corner];
- +
- + if (irq_en != aggr->irq_en) {
- + aggr->irq_en = irq_en;
- + cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_en);
- + }
- +
- + aggr->last_volt = new_volt;
- +
- +done:
- + /* Clear interrupt status */
- + cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR, CPR3_IRQ_UP | CPR3_IRQ_DOWN);
- +
- + /* ACK or NACK the CPR controller */
- + cpr3_write(ctrl, CPR3_REG_CONT_CMD, cont);
- +
- + mutex_unlock(&ctrl->lock);
- + return IRQ_HANDLED;
- +}
- +
- +/**
- + * cpr3_ceiling_irq_handler() - CPR ceiling reached interrupt handler callback
- + * function used for hardware closed-loop operation
- + * @irq: CPR ceiling interrupt number
- + * @data: Private data corresponding to the CPR3 controller
- + * pointer
- + *
- + * This function disables processor clock throttling and closed-loop operation
- + * when the ceiling voltage is reached.
- + *
- + * Return: IRQ_HANDLED
- + */
- +static irqreturn_t cpr3_ceiling_irq_handler(int irq, void *data)
- +{
- + struct cpr3_controller *ctrl = data;
- + int volt;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (!ctrl->cpr_enabled) {
- + cpr3_debug(ctrl, "CPR ceiling interrupt received but CPR is disabled\n");
- + goto done;
- + } else if (!ctrl->use_hw_closed_loop) {
- + cpr3_debug(ctrl, "CPR ceiling interrupt received but CPR is using SW closed-loop\n");
- + goto done;
- + }
- +
- + volt = regulator_get_voltage(ctrl->vdd_regulator);
- + if (volt < 0) {
- + cpr3_err(ctrl, "could not get vdd voltage, rc=%d\n", volt);
- + goto done;
- + } else if (volt != ctrl->aggr_corner.ceiling_volt) {
- + cpr3_debug(ctrl, "CPR ceiling interrupt received but vdd voltage: %d uV != ceiling voltage: %d uV\n",
- + volt, ctrl->aggr_corner.ceiling_volt);
- + goto done;
- + }
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + /*
- + * Since the ceiling voltage has been reached, disable processor
- + * clock throttling as well as CPR closed-loop operation.
- + */
- + cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
- + CPR3_PD_THROTTLE_DISABLE);
- + cpr3_ctrl_loop_disable(ctrl);
- + cpr3_debug(ctrl, "CPR closed-loop and throttling disabled\n");
- + }
- +
- +done:
- + mutex_unlock(&ctrl->lock);
- + return IRQ_HANDLED;
- +}
- +
- +/**
- + * cpr3_regulator_vreg_register() - register a regulator device for a CPR3
- + * regulator
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function initializes all regulator framework related structures and then
- + * calls regulator_register() for the CPR3 regulator.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_vreg_register(struct cpr3_regulator *vreg)
- +{
- + struct regulator_config config = {};
- + struct regulator_desc *rdesc;
- + struct regulator_init_data *init_data;
- + int rc;
- +
- + init_data = of_get_regulator_init_data(vreg->thread->ctrl->dev,
- + vreg->of_node, &vreg->rdesc);
- + if (!init_data) {
- + cpr3_err(vreg, "regulator init data is missing\n");
- + return -EINVAL;
- + }
- +
- + init_data->constraints.input_uV = init_data->constraints.max_uV;
- + rdesc = &vreg->rdesc;
- + init_data->constraints.valid_ops_mask |=
- + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
- + rdesc->ops = &cpr3_regulator_ops;
- +
- + rdesc->n_voltages = vreg->corner_count;
- + rdesc->name = init_data->constraints.name;
- + rdesc->owner = THIS_MODULE;
- + rdesc->type = REGULATOR_VOLTAGE;
- +
- + config.dev = vreg->thread->ctrl->dev;
- + config.driver_data = vreg;
- + config.init_data = init_data;
- + config.of_node = vreg->of_node;
- +
- + vreg->rdev = regulator_register(vreg->thread->ctrl->dev, rdesc, &config);
- + if (IS_ERR(vreg->rdev)) {
- + rc = PTR_ERR(vreg->rdev);
- + cpr3_err(vreg, "regulator_register failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + return 0;
- +}
- +
- +static int debugfs_int_set(void *data, u64 val)
- +{
- + *(int *)data = val;
- + return 0;
- +}
- +
- +static int debugfs_int_get(void *data, u64 *val)
- +{
- + *val = *(int *)data;
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(fops_int, debugfs_int_get, debugfs_int_set, "%lld\n");
- +DEFINE_SIMPLE_ATTRIBUTE(fops_int_ro, debugfs_int_get, NULL, "%lld\n");
- +DEFINE_SIMPLE_ATTRIBUTE(fops_int_wo, NULL, debugfs_int_set, "%lld\n");
- +
- +/**
- + * debugfs_create_int - create a debugfs file that is used to read and write a
- + * signed int value
- + * @name: Pointer to a string containing the name of the file to
- + * create
- + * @mode: The permissions that the file should have
- + * @parent: Pointer to the parent dentry for this file. This should
- + * be a directory dentry if set. If this parameter is
- + * %NULL, then the file will be created in the root of the
- + * debugfs filesystem.
- + * @value: Pointer to the variable that the file should read to and
- + * write from
- + *
- + * This function creates a file in debugfs with the given name that
- + * contains the value of the variable @value. If the @mode variable is so
- + * set, it can be read from, and written to.
- + *
- + * This function will return a pointer to a dentry if it succeeds. This
- + * pointer must be passed to the debugfs_remove() function when the file is
- + * to be removed. If an error occurs, %NULL will be returned.
- + */
- +static struct dentry *debugfs_create_int(const char *name, umode_t mode,
- + struct dentry *parent, int *value)
- +{
- + /* if there are no write bits set, make read only */
- + if (!(mode & S_IWUGO))
- + return debugfs_create_file(name, mode, parent, value,
- + &fops_int_ro);
- + /* if there are no read bits set, make write only */
- + if (!(mode & S_IRUGO))
- + return debugfs_create_file(name, mode, parent, value,
- + &fops_int_wo);
- +
- + return debugfs_create_file(name, mode, parent, value, &fops_int);
- +}
- +
- +static int debugfs_bool_get(void *data, u64 *val)
- +{
- + *val = *(bool *)data;
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(fops_bool_ro, debugfs_bool_get, NULL, "%lld\n");
- +
- +/**
- + * struct cpr3_debug_corner_info - data structure used by the
- + * cpr3_debugfs_create_corner_int function
- + * @vreg: Pointer to the CPR3 regulator
- + * @index: Pointer to the corner array index
- + * @member_offset: Offset in bytes from the beginning of struct cpr3_corner
- + * to the beginning of the value to be read from
- + * @corner: Pointer to the CPR3 corner array
- + */
- +struct cpr3_debug_corner_info {
- + struct cpr3_regulator *vreg;
- + int *index;
- + size_t member_offset;
- + struct cpr3_corner *corner;
- +};
- +
- +static int cpr3_debug_corner_int_get(void *data, u64 *val)
- +{
- + struct cpr3_debug_corner_info *info = data;
- + struct cpr3_controller *ctrl = info->vreg->thread->ctrl;
- + int i;
- +
- + mutex_lock(&ctrl->lock);
- +
- + i = *info->index;
- + if (i < 0)
- + i = 0;
- +
- + *val = *(int *)((char *)&info->vreg->corner[i] + info->member_offset);
- +
- + mutex_unlock(&ctrl->lock);
- +
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_corner_int_fops, cpr3_debug_corner_int_get,
- + NULL, "%lld\n");
- +
- +/**
- + * cpr3_debugfs_create_corner_int - create a debugfs file that is used to read
- + * a signed int value out of a CPR3 regulator's corner array
- + * @vreg: Pointer to the CPR3 regulator
- + * @name: Pointer to a string containing the name of the file to
- + * create
- + * @mode: The permissions that the file should have
- + * @parent: Pointer to the parent dentry for this file. This should
- + * be a directory dentry if set. If this parameter is
- + * %NULL, then the file will be created in the root of the
- + * debugfs filesystem.
- + * @index: Pointer to the corner array index
- + * @member_offset: Offset in bytes from the beginning of struct cpr3_corner
- + * to the beginning of the value to be read from
- + *
- + * This function creates a file in debugfs with the given name that
- + * contains the value of the int type variable vreg->corner[index].member
- + * where member_offset == offsetof(struct cpr3_corner, member).
- + */
- +static struct dentry *cpr3_debugfs_create_corner_int(
- + struct cpr3_regulator *vreg, const char *name, umode_t mode,
- + struct dentry *parent, int *index, size_t member_offset)
- +{
- + struct cpr3_debug_corner_info *info;
- +
- + info = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
- + if (!info)
- + return NULL;
- +
- + info->vreg = vreg;
- + info->index = index;
- + info->member_offset = member_offset;
- +
- + return debugfs_create_file(name, mode, parent, info,
- + &cpr3_debug_corner_int_fops);
- +}
- +
- +static int cpr3_debug_quot_open(struct inode *inode, struct file *file)
- +{
- + struct cpr3_debug_corner_info *info = inode->i_private;
- + struct cpr3_thread *thread = info->vreg->thread;
- + int size, i, pos;
- + u32 *quot;
- + char *buf;
- +
- + /*
- + * Max size:
- + * - 10 digits + ' ' or '\n' = 11 bytes per number
- + * - terminating '\0'
- + */
- + size = CPR3_RO_COUNT * 11;
- + buf = kzalloc(size + 1, GFP_KERNEL);
- + if (!buf)
- + return -ENOMEM;
- +
- + file->private_data = buf;
- +
- + mutex_lock(&thread->ctrl->lock);
- +
- + quot = info->corner[*info->index].target_quot;
- +
- + for (i = 0, pos = 0; i < CPR3_RO_COUNT; i++)
- + pos += scnprintf(buf + pos, size - pos, "%u%c",
- + quot[i], i < CPR3_RO_COUNT - 1 ? ' ' : '\n');
- +
- + mutex_unlock(&thread->ctrl->lock);
- +
- + return nonseekable_open(inode, file);
- +}
- +
- +static ssize_t cpr3_debug_quot_read(struct file *file, char __user *buf,
- + size_t len, loff_t *ppos)
- +{
- + return simple_read_from_buffer(buf, len, ppos, file->private_data,
- + strlen(file->private_data));
- +}
- +
- +static int cpr3_debug_quot_release(struct inode *inode, struct file *file)
- +{
- + kfree(file->private_data);
- +
- + return 0;
- +}
- +
- +static const struct file_operations cpr3_debug_quot_fops = {
- + .owner = THIS_MODULE,
- + .open = cpr3_debug_quot_open,
- + .release = cpr3_debug_quot_release,
- + .read = cpr3_debug_quot_read,
- +};
- +
- +/**
- + * cpr3_regulator_debugfs_corner_add() - add debugfs files to expose
- + * configuration data for the CPR corner
- + * @vreg: Pointer to the CPR3 regulator
- + * @corner_dir: Pointer to the parent corner dentry for the new files
- + * @index: Pointer to the corner array index
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_debugfs_corner_add(struct cpr3_regulator *vreg,
- + struct dentry *corner_dir, int *index)
- +{
- + struct cpr3_debug_corner_info *info;
- + struct dentry *temp;
- +
- + temp = cpr3_debugfs_create_corner_int(vreg, "floor_volt", S_IRUGO,
- + corner_dir, index, offsetof(struct cpr3_corner, floor_volt));
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "floor_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = cpr3_debugfs_create_corner_int(vreg, "ceiling_volt", S_IRUGO,
- + corner_dir, index, offsetof(struct cpr3_corner, ceiling_volt));
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "ceiling_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = cpr3_debugfs_create_corner_int(vreg, "open_loop_volt", S_IRUGO,
- + corner_dir, index,
- + offsetof(struct cpr3_corner, open_loop_volt));
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "open_loop_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = cpr3_debugfs_create_corner_int(vreg, "last_volt", S_IRUGO,
- + corner_dir, index, offsetof(struct cpr3_corner, last_volt));
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "last_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + info = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
- + if (!info)
- + return;
- +
- + info->vreg = vreg;
- + info->index = index;
- + info->corner = vreg->corner;
- +
- + temp = debugfs_create_file("target_quots", S_IRUGO, corner_dir,
- + info, &cpr3_debug_quot_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "target_quots debugfs file creation failed\n");
- + return;
- + }
- +}
- +
- +/**
- + * cpr3_debug_corner_index_set() - debugfs callback used to change the
- + * value of the CPR3 regulator debug_corner index
- + * @data: Pointer to private data which is equal to the CPR3
- + * regulator pointer
- + * @val: New value for debug_corner
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_corner_index_set(void *data, u64 val)
- +{
- + struct cpr3_regulator *vreg = data;
- +
- + if (val < CPR3_CORNER_OFFSET || val > vreg->corner_count) {
- + cpr3_err(vreg, "invalid corner index %llu; allowed values: %d-%d\n",
- + val, CPR3_CORNER_OFFSET, vreg->corner_count);
- + return -EINVAL;
- + }
- +
- + mutex_lock(&vreg->thread->ctrl->lock);
- + vreg->debug_corner = val - CPR3_CORNER_OFFSET;
- + mutex_unlock(&vreg->thread->ctrl->lock);
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_debug_corner_index_get() - debugfs callback used to retrieve
- + * the value of the CPR3 regulator debug_corner index
- + * @data: Pointer to private data which is equal to the CPR3
- + * regulator pointer
- + * @val: Output parameter written with the value of
- + * debug_corner
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_corner_index_get(void *data, u64 *val)
- +{
- + struct cpr3_regulator *vreg = data;
- +
- + *val = vreg->debug_corner + CPR3_CORNER_OFFSET;
- +
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_corner_index_fops,
- + cpr3_debug_corner_index_get,
- + cpr3_debug_corner_index_set,
- + "%llu\n");
- +
- +/**
- + * cpr3_debug_current_corner_index_get() - debugfs callback used to retrieve
- + * the value of the CPR3 regulator current_corner index
- + * @data: Pointer to private data which is equal to the CPR3
- + * regulator pointer
- + * @val: Output parameter written with the value of
- + * current_corner
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_current_corner_index_get(void *data, u64 *val)
- +{
- + struct cpr3_regulator *vreg = data;
- +
- + *val = vreg->current_corner + CPR3_CORNER_OFFSET;
- +
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_current_corner_index_fops,
- + cpr3_debug_current_corner_index_get,
- + NULL, "%llu\n");
- +
- +/**
- + * cpr3_regulator_debugfs_vreg_add() - add debugfs files to expose configuration
- + * data for the CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + * @thread_dir CPR3 thread debugfs directory handle
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_debugfs_vreg_add(struct cpr3_regulator *vreg,
- + struct dentry *thread_dir)
- +{
- + struct dentry *temp, *corner_dir, *vreg_dir;
- +
- + vreg_dir = debugfs_create_dir(vreg->name, thread_dir);
- + if (IS_ERR_OR_NULL(vreg_dir)) {
- + cpr3_err(vreg, "%s debugfs directory creation failed\n",
- + vreg->name);
- + return;
- + }
- +
- + temp = debugfs_create_int("speed_bin_fuse", S_IRUGO, vreg_dir,
- + &vreg->speed_bin_fuse);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "speed_bin_fuse debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("cpr_rev_fuse", S_IRUGO, vreg_dir,
- + &vreg->cpr_rev_fuse);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "cpr_rev_fuse debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("fuse_combo", S_IRUGO, vreg_dir,
- + &vreg->fuse_combo);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "fuse_combo debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("corner_count", S_IRUGO, vreg_dir,
- + &vreg->corner_count);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "corner_count debugfs file creation failed\n");
- + return;
- + }
- +
- + corner_dir = debugfs_create_dir("corner", vreg_dir);
- + if (IS_ERR_OR_NULL(corner_dir)) {
- + cpr3_err(vreg, "corner debugfs directory creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_file("index", S_IRUGO | S_IWUSR, corner_dir,
- + vreg, &cpr3_debug_corner_index_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "index debugfs file creation failed\n");
- + return;
- + }
- +
- + cpr3_regulator_debugfs_corner_add(vreg, corner_dir,
- + &vreg->debug_corner);
- +
- + corner_dir = debugfs_create_dir("current_corner", vreg_dir);
- + if (IS_ERR_OR_NULL(corner_dir)) {
- + cpr3_err(vreg, "current_corner debugfs directory creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_file("index", S_IRUGO, corner_dir,
- + vreg, &cpr3_debug_current_corner_index_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(vreg, "index debugfs file creation failed\n");
- + return;
- + }
- +
- + cpr3_regulator_debugfs_corner_add(vreg, corner_dir,
- + &vreg->current_corner);
- +}
- +
- +/**
- + * cpr3_regulator_debugfs_thread_add() - add debugfs files to expose
- + * configuration data for the CPR thread
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_debugfs_thread_add(struct cpr3_thread *thread)
- +{
- + struct cpr3_controller *ctrl = thread->ctrl;
- + struct dentry *aggr_dir, *temp, *thread_dir;
- + struct cpr3_debug_corner_info *info;
- + char buf[20];
- + int *index;
- + int i;
- +
- + scnprintf(buf, sizeof(buf), "thread%u", thread->thread_id);
- + thread_dir = debugfs_create_dir(buf, thread->ctrl->debugfs);
- + if (IS_ERR_OR_NULL(thread_dir)) {
- + cpr3_err(ctrl, "thread %u %s debugfs directory creation failed\n",
- + thread->thread_id, buf);
- + return;
- + }
- +
- + aggr_dir = debugfs_create_dir("max_aggregated_params", thread_dir);
- + if (IS_ERR_OR_NULL(aggr_dir)) {
- + cpr3_err(ctrl, "thread %u max_aggregated_params debugfs directory creation failed\n",
- + thread->thread_id);
- + return;
- + }
- +
- + temp = debugfs_create_int("floor_volt", S_IRUGO, aggr_dir,
- + &thread->aggr_corner.floor_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "thread %u aggr floor_volt debugfs file creation failed\n",
- + thread->thread_id);
- + return;
- + }
- +
- + temp = debugfs_create_int("ceiling_volt", S_IRUGO, aggr_dir,
- + &thread->aggr_corner.ceiling_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "thread %u aggr ceiling_volt debugfs file creation failed\n",
- + thread->thread_id);
- + return;
- + }
- +
- + temp = debugfs_create_int("open_loop_volt", S_IRUGO, aggr_dir,
- + &thread->aggr_corner.open_loop_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "thread %u aggr open_loop_volt debugfs file creation failed\n",
- + thread->thread_id);
- + return;
- + }
- +
- + temp = debugfs_create_int("last_volt", S_IRUGO, aggr_dir,
- + &thread->aggr_corner.last_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "thread %u aggr last_volt debugfs file creation failed\n",
- + thread->thread_id);
- + return;
- + }
- +
- + info = devm_kzalloc(thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
- + index = devm_kzalloc(thread->ctrl->dev, sizeof(*index), GFP_KERNEL);
- + if (!info || !index)
- + return;
- + *index = 0;
- + info->vreg = &thread->vreg[0];
- + info->index = index;
- + info->corner = &thread->aggr_corner;
- +
- + temp = debugfs_create_file("target_quots", S_IRUGO, aggr_dir,
- + info, &cpr3_debug_quot_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "thread %u target_quots debugfs file creation failed\n",
- + thread->thread_id);
- + return;
- + }
- +
- + for (i = 0; i < thread->vreg_count; i++)
- + cpr3_regulator_debugfs_vreg_add(&thread->vreg[i], thread_dir);
- +}
- +
- +/**
- + * cpr3_debug_closed_loop_enable_set() - debugfs callback used to change the
- + * value of the CPR controller cpr_allowed_sw flag which enables or
- + * disables closed-loop operation
- + * @data: Pointer to private data which is equal to the CPR
- + * controller pointer
- + * @val: New value for cpr_allowed_sw
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_closed_loop_enable_set(void *data, u64 val)
- +{
- + struct cpr3_controller *ctrl = data;
- + bool enable = !!val;
- + int rc;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (ctrl->cpr_allowed_sw == enable)
- + goto done;
- +
- + if (enable && !ctrl->cpr_allowed_hw) {
- + cpr3_err(ctrl, "CPR closed-loop operation is not allowed\n");
- + goto done;
- + }
- +
- + ctrl->cpr_allowed_sw = enable;
- +
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not change CPR enable state=%u, rc=%d\n",
- + enable, rc);
- + goto done;
- + }
- +
- + if (ctrl->proc_clock_throttle && !ctrl->cpr_enabled) {
- + rc = cpr3_clock_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "clock enable failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + ctrl->cpr_enabled = true;
- +
- + cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
- + CPR3_PD_THROTTLE_DISABLE);
- +
- + cpr3_clock_disable(ctrl);
- + ctrl->cpr_enabled = false;
- + }
- +
- + cpr3_debug(ctrl, "closed-loop=%s\n", enable ? "enabled" : "disabled");
- +done:
- + mutex_unlock(&ctrl->lock);
- + return 0;
- +}
- +
- +/**
- + * cpr3_debug_closed_loop_enable_get() - debugfs callback used to retrieve
- + * the value of the CPR controller cpr_allowed_sw flag which
- + * indicates if closed-loop operation is enabled
- + * @data: Pointer to private data which is equal to the CPR
- + * controller pointer
- + * @val: Output parameter written with the value of
- + * cpr_allowed_sw
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_closed_loop_enable_get(void *data, u64 *val)
- +{
- + struct cpr3_controller *ctrl = data;
- +
- + *val = ctrl->cpr_allowed_sw;
- +
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_closed_loop_enable_fops,
- + cpr3_debug_closed_loop_enable_get,
- + cpr3_debug_closed_loop_enable_set,
- + "%llu\n");
- +
- +/**
- + * cpr3_debug_hw_closed_loop_enable_set() - debugfs callback used to change the
- + * value of the CPR controller use_hw_closed_loop flag which
- + * switches between software closed-loop and hardware closed-loop
- + * operation for CPR3 and CPR4 controllers and between open-loop
- + * and full hardware closed-loop operation for CPRh controllers.
- + * @data: Pointer to private data which is equal to the CPR
- + * controller pointer
- + * @val: New value for use_hw_closed_loop
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_hw_closed_loop_enable_set(void *data, u64 val)
- +{
- + struct cpr3_controller *ctrl = data;
- + bool use_hw_closed_loop = !!val;
- + struct cpr3_regulator *vreg;
- + bool cpr_enabled;
- + int i, j, k, rc;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (ctrl->use_hw_closed_loop == use_hw_closed_loop)
- + goto done;
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + ctrl->use_hw_closed_loop = use_hw_closed_loop;
- +
- + cpr_enabled = ctrl->cpr_enabled;
- +
- + /* Ensure that CPR clocks are enabled before writing to registers. */
- + if (!cpr_enabled) {
- + rc = cpr3_clock_enable(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
- + goto done;
- + }
- + ctrl->cpr_enabled = true;
- + }
- +
- + if (ctrl->use_hw_closed_loop)
- + cpr3_write(ctrl, CPR3_REG_IRQ_EN, 0);
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
- + CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
- + ctrl->use_hw_closed_loop
- + ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
- + : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
- + } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
- + ctrl->use_hw_closed_loop
- + ? CPR3_HW_CLOSED_LOOP_ENABLE
- + : CPR3_HW_CLOSED_LOOP_DISABLE);
- + }
- +
- + /* Turn off CPR clocks if they were off before this function call. */
- + if (!cpr_enabled) {
- + cpr3_clock_disable(ctrl);
- + ctrl->cpr_enabled = false;
- + }
- +
- + if (ctrl->use_hw_closed_loop && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + rc = regulator_enable(ctrl->vdd_limit_regulator);
- + if (rc) {
- + cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + } else if (!ctrl->use_hw_closed_loop
- + && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + rc = regulator_disable(ctrl->vdd_limit_regulator);
- + if (rc) {
- + cpr3_err(ctrl, "CPR limit regulator disable failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + /*
- + * Due to APM and mem-acc floor restriction constraints,
- + * the closed-loop voltage may be different when using
- + * software closed-loop vs hardware closed-loop. Therefore,
- + * reset the cached closed-loop voltage for all corners to the
- + * corresponding open-loop voltage when switching between
- + * SW and HW closed-loop mode.
- + */
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + for (k = 0; k < vreg->corner_count; k++)
- + vreg->corner[k].last_volt
- + = vreg->corner[k].open_loop_volt;
- + }
- + }
- +
- + /* Skip last_volt caching */
- + ctrl->last_corner_was_closed_loop = false;
- +
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not change CPR HW closed-loop enable state=%u, rc=%d\n",
- + use_hw_closed_loop, rc);
- + goto done;
- + }
- +
- + cpr3_debug(ctrl, "CPR mode=%s\n",
- + use_hw_closed_loop ?
- + "HW closed-loop" : "SW closed-loop");
- +done:
- + mutex_unlock(&ctrl->lock);
- + return 0;
- +}
- +
- +/**
- + * cpr3_debug_hw_closed_loop_enable_get() - debugfs callback used to retrieve
- + * the value of the CPR controller use_hw_closed_loop flag which
- + * indicates if hardware closed-loop operation is being used in
- + * place of software closed-loop operation
- + * @data: Pointer to private data which is equal to the CPR
- + * controller pointer
- + * @val: Output parameter written with the value of
- + * use_hw_closed_loop
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_hw_closed_loop_enable_get(void *data, u64 *val)
- +{
- + struct cpr3_controller *ctrl = data;
- +
- + *val = ctrl->use_hw_closed_loop;
- +
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_hw_closed_loop_enable_fops,
- + cpr3_debug_hw_closed_loop_enable_get,
- + cpr3_debug_hw_closed_loop_enable_set,
- + "%llu\n");
- +
- +/**
- + * cpr3_debug_trigger_aging_measurement_set() - debugfs callback used to trigger
- + * another CPR measurement
- + * @data: Pointer to private data which is equal to the CPR
- + * controller pointer
- + * @val: Unused
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_debug_trigger_aging_measurement_set(void *data, u64 val)
- +{
- + struct cpr3_controller *ctrl = data;
- + int rc;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + cpr3_regulator_set_aging_ref_adjustment(ctrl, INT_MAX);
- + ctrl->aging_required = true;
- + ctrl->aging_succeeded = false;
- + ctrl->aging_failed = false;
- +
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not update the CPR controller state, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- +done:
- + mutex_unlock(&ctrl->lock);
- + return 0;
- +}
- +DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_trigger_aging_measurement_fops,
- + NULL,
- + cpr3_debug_trigger_aging_measurement_set,
- + "%llu\n");
- +
- +/**
- + * cpr3_regulator_debugfs_ctrl_add() - add debugfs files to expose configuration
- + * data for the CPR controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_debugfs_ctrl_add(struct cpr3_controller *ctrl)
- +{
- + struct dentry *temp, *aggr_dir;
- + int i;
- +
- + /* Add cpr3-regulator base directory if it isn't present already. */
- + if (cpr3_debugfs_base == NULL) {
- + cpr3_debugfs_base = debugfs_create_dir("cpr3-regulator", NULL);
- + if (IS_ERR_OR_NULL(cpr3_debugfs_base)) {
- + cpr3_err(ctrl, "cpr3-regulator debugfs base directory creation failed\n");
- + cpr3_debugfs_base = NULL;
- + return;
- + }
- + }
- +
- + ctrl->debugfs = debugfs_create_dir(ctrl->name, cpr3_debugfs_base);
- + if (IS_ERR_OR_NULL(ctrl->debugfs)) {
- + cpr3_err(ctrl, "cpr3-regulator controller debugfs directory creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_file("cpr_closed_loop_enable", S_IRUGO | S_IWUSR,
- + ctrl->debugfs, ctrl,
- + &cpr3_debug_closed_loop_enable_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "cpr_closed_loop_enable debugfs file creation failed\n");
- + return;
- + }
- +
- + if (ctrl->supports_hw_closed_loop) {
- + temp = debugfs_create_file("use_hw_closed_loop",
- + S_IRUGO | S_IWUSR, ctrl->debugfs, ctrl,
- + &cpr3_debug_hw_closed_loop_enable_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "use_hw_closed_loop debugfs file creation failed\n");
- + return;
- + }
- + }
- +
- + temp = debugfs_create_int("thread_count", S_IRUGO, ctrl->debugfs,
- + &ctrl->thread_count);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "thread_count debugfs file creation failed\n");
- + return;
- + }
- +
- + if (ctrl->apm) {
- + temp = debugfs_create_int("apm_threshold_volt", S_IRUGO,
- + ctrl->debugfs, &ctrl->apm_threshold_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "apm_threshold_volt debugfs file creation failed\n");
- + return;
- + }
- + }
- +
- + if (ctrl->aging_required || ctrl->aging_succeeded
- + || ctrl->aging_failed) {
- + temp = debugfs_create_int("aging_adj_volt", S_IRUGO,
- + ctrl->debugfs, &ctrl->aging_ref_adjust_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aging_adj_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_file("aging_succeeded", S_IRUGO,
- + ctrl->debugfs, &ctrl->aging_succeeded, &fops_bool_ro);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aging_succeeded debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_file("aging_failed", S_IRUGO,
- + ctrl->debugfs, &ctrl->aging_failed, &fops_bool_ro);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aging_failed debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_file("aging_trigger", S_IWUSR,
- + ctrl->debugfs, ctrl,
- + &cpr3_debug_trigger_aging_measurement_fops);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aging_trigger debugfs file creation failed\n");
- + return;
- + }
- + }
- +
- + aggr_dir = debugfs_create_dir("max_aggregated_voltages", ctrl->debugfs);
- + if (IS_ERR_OR_NULL(aggr_dir)) {
- + cpr3_err(ctrl, "max_aggregated_voltages debugfs directory creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("floor_volt", S_IRUGO, aggr_dir,
- + &ctrl->aggr_corner.floor_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aggr floor_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("ceiling_volt", S_IRUGO, aggr_dir,
- + &ctrl->aggr_corner.ceiling_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aggr ceiling_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("open_loop_volt", S_IRUGO, aggr_dir,
- + &ctrl->aggr_corner.open_loop_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aggr open_loop_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + temp = debugfs_create_int("last_volt", S_IRUGO, aggr_dir,
- + &ctrl->aggr_corner.last_volt);
- + if (IS_ERR_OR_NULL(temp)) {
- + cpr3_err(ctrl, "aggr last_volt debugfs file creation failed\n");
- + return;
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++)
- + cpr3_regulator_debugfs_thread_add(&ctrl->thread[i]);
- +}
- +
- +/**
- + * cpr3_regulator_debugfs_ctrl_remove() - remove debugfs files for the CPR
- + * controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Note, this function must be called after the controller has been removed from
- + * cpr3_controller_list and while the cpr3_controller_list_mutex lock is held.
- + *
- + * Return: none
- + */
- +static void cpr3_regulator_debugfs_ctrl_remove(struct cpr3_controller *ctrl)
- +{
- + if (list_empty(&cpr3_controller_list)) {
- + debugfs_remove_recursive(cpr3_debugfs_base);
- + cpr3_debugfs_base = NULL;
- + } else {
- + debugfs_remove_recursive(ctrl->debugfs);
- + }
- +}
- +
- +/**
- + * cpr3_regulator_init_ctrl_data() - performs initialization of CPR controller
- + * elements
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_init_ctrl_data(struct cpr3_controller *ctrl)
- +{
- + /* Read the initial vdd voltage from hardware. */
- + ctrl->aggr_corner.last_volt
- + = regulator_get_voltage(ctrl->vdd_regulator);
- + if (ctrl->aggr_corner.last_volt < 0) {
- + cpr3_err(ctrl, "regulator_get_voltage(vdd) failed, rc=%d\n",
- + ctrl->aggr_corner.last_volt);
- + return ctrl->aggr_corner.last_volt;
- + }
- + ctrl->aggr_corner.open_loop_volt = ctrl->aggr_corner.last_volt;
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_init_vreg_data() - performs initialization of common CPR3
- + * regulator elements and validate aging configurations
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_init_vreg_data(struct cpr3_regulator *vreg)
- +{
- + int i, j;
- + bool init_aging;
- +
- + vreg->current_corner = CPR3_REGULATOR_CORNER_INVALID;
- + vreg->last_closed_loop_corner = CPR3_REGULATOR_CORNER_INVALID;
- +
- + init_aging = vreg->aging_allowed && vreg->thread->ctrl->aging_required;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
- + vreg->corner[i].irq_en = CPR3_IRQ_UP | CPR3_IRQ_DOWN;
- +
- + vreg->corner[i].ro_mask = 0;
- + for (j = 0; j < CPR3_RO_COUNT; j++) {
- + if (vreg->corner[i].target_quot[j] == 0)
- + vreg->corner[i].ro_mask |= BIT(j);
- + }
- +
- + if (init_aging) {
- + vreg->corner[i].unaged_floor_volt
- + = vreg->corner[i].floor_volt;
- + vreg->corner[i].unaged_ceiling_volt
- + = vreg->corner[i].ceiling_volt;
- + vreg->corner[i].unaged_open_loop_volt
- + = vreg->corner[i].open_loop_volt;
- + }
- +
- + if (vreg->aging_allowed) {
- + if (vreg->corner[i].unaged_floor_volt <= 0) {
- + cpr3_err(vreg, "invalid unaged_floor_volt[%d] = %d\n",
- + i, vreg->corner[i].unaged_floor_volt);
- + return -EINVAL;
- + }
- + if (vreg->corner[i].unaged_ceiling_volt <= 0) {
- + cpr3_err(vreg, "invalid unaged_ceiling_volt[%d] = %d\n",
- + i, vreg->corner[i].unaged_ceiling_volt);
- + return -EINVAL;
- + }
- + if (vreg->corner[i].unaged_open_loop_volt <= 0) {
- + cpr3_err(vreg, "invalid unaged_open_loop_volt[%d] = %d\n",
- + i, vreg->corner[i].unaged_open_loop_volt);
- + return -EINVAL;
- + }
- + }
- + }
- +
- + if (vreg->aging_allowed && vreg->corner[vreg->aging_corner].ceiling_volt
- + > vreg->thread->ctrl->aging_ref_volt) {
- + cpr3_err(vreg, "aging corner %d ceiling voltage = %d > aging ref voltage = %d uV\n",
- + vreg->aging_corner,
- + vreg->corner[vreg->aging_corner].ceiling_volt,
- + vreg->thread->ctrl->aging_ref_volt);
- + return -EINVAL;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_suspend() - perform common required CPR3 power down steps
- + * before the system enters suspend
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + mutex_lock(&ctrl->lock);
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc);
- + mutex_unlock(&ctrl->lock);
- + return rc;
- + }
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + rc = cpr3_closed_loop_disable(ctrl);
- + if (rc)
- + cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
- +
- + ctrl->cpr_suspended = true;
- +
- + mutex_unlock(&ctrl->lock);
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_resume() - perform common required CPR3 power up steps after
- + * the system resumes from suspend
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_regulator_resume(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + mutex_lock(&ctrl->lock);
- +
- + ctrl->cpr_suspended = false;
- + rc = cpr3_regulator_update_ctrl_state(ctrl);
- + if (rc)
- + cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
- +
- + mutex_unlock(&ctrl->lock);
- + return 0;
- +}
- +
- +/**
- + * cpr3_regulator_validate_controller() - verify the data passed in via the
- + * cpr3_controller data structure
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_regulator_validate_controller(struct cpr3_controller *ctrl)
- +{
- + struct cpr3_thread *thread;
- + struct cpr3_regulator *vreg;
- + int i, j, allow_boost_vreg_count = 0;
- +
- + if (!ctrl->vdd_regulator) {
- + cpr3_err(ctrl, "vdd regulator missing\n");
- + return -EINVAL;
- + } else if (ctrl->sensor_count <= 0
- + || ctrl->sensor_count > CPR3_MAX_SENSOR_COUNT) {
- + cpr3_err(ctrl, "invalid CPR sensor count=%d\n",
- + ctrl->sensor_count);
- + return -EINVAL;
- + } else if (!ctrl->sensor_owner) {
- + cpr3_err(ctrl, "CPR sensor ownership table missing\n");
- + return -EINVAL;
- + }
- +
- + if (ctrl->aging_required) {
- + for (i = 0; i < ctrl->aging_sensor_count; i++) {
- + if (ctrl->aging_sensor[i].sensor_id
- + >= ctrl->sensor_count) {
- + cpr3_err(ctrl, "aging_sensor[%d] id=%u is not in the value range 0-%d",
- + i, ctrl->aging_sensor[i].sensor_id,
- + ctrl->sensor_count - 1);
- + return -EINVAL;
- + }
- + }
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + thread = &ctrl->thread[i];
- + for (j = 0; j < thread->vreg_count; j++) {
- + vreg = &thread->vreg[j];
- + if (vreg->allow_boost)
- + allow_boost_vreg_count++;
- + }
- + }
- +
- + if (allow_boost_vreg_count > 1) {
- + /*
- + * Boost feature is not allowed to be used for more
- + * than one CPR3 regulator of a CPR3 controller.
- + */
- + cpr3_err(ctrl, "Boost feature is enabled for more than one regulator\n");
- + return -EINVAL;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_panic_callback() - panic notification callback function. This function
- + * is invoked when a kernel panic occurs.
- + * @nfb: Notifier block pointer of CPR3 controller
- + * @event: Value passed unmodified to notifier function
- + * @data: Pointer passed unmodified to notifier function
- + *
- + * Return: NOTIFY_OK
- + */
- +static int cpr3_panic_callback(struct notifier_block *nfb,
- + unsigned long event, void *data)
- +{
- + struct cpr3_controller *ctrl = container_of(nfb,
- + struct cpr3_controller, panic_notifier);
- + struct cpr3_panic_regs_info *regs_info = ctrl->panic_regs_info;
- + struct cpr3_reg_info *reg;
- + int i = 0;
- +
- + for (i = 0; i < regs_info->reg_count; i++) {
- + reg = &(regs_info->regs[i]);
- + reg->value = readl_relaxed(reg->virt_addr);
- + pr_err("%s[0x%08x] = 0x%08x\n", reg->name, reg->addr,
- + reg->value);
- + }
- + /*
- + * Barrier to ensure that the information has been updated in the
- + * structure.
- + */
- + mb();
- +
- + return NOTIFY_OK;
- +}
- +
- +/**
- + * cpr3_regulator_register() - register the regulators for a CPR3 controller and
- + * perform CPR hardware initialization
- + * @pdev: Platform device pointer for the CPR3 controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_regulator_register(struct platform_device *pdev,
- + struct cpr3_controller *ctrl)
- +{
- + struct device *dev = &pdev->dev;
- + struct resource *res;
- + int i, j, rc;
- +
- + if (!dev->of_node) {
- + dev_err(dev, "%s: Device tree node is missing\n", __func__);
- + return -EINVAL;
- + }
- +
- + if (!ctrl || !ctrl->name) {
- + dev_err(dev, "%s: CPR controller data is missing\n", __func__);
- + return -EINVAL;
- + }
- +
- + rc = cpr3_regulator_validate_controller(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "controller validation failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + mutex_init(&ctrl->lock);
- +
- + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cpr_ctrl");
- + if (!res || !res->start) {
- + cpr3_err(ctrl, "CPR controller address is missing\n");
- + return -ENXIO;
- + }
- + ctrl->cpr_ctrl_base = devm_ioremap(dev, res->start, resource_size(res));
- +
- + if (ctrl->aging_possible_mask) {
- + /*
- + * Aging possible register address is required if an aging
- + * possible mask has been specified.
- + */
- + res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- + "aging_allowed");
- + if (!res || !res->start) {
- + cpr3_err(ctrl, "CPR aging allowed address is missing\n");
- + return -ENXIO;
- + }
- + ctrl->aging_possible_reg = devm_ioremap(dev, res->start,
- + resource_size(res));
- + }
- +
- + ctrl->irq = platform_get_irq_byname(pdev, "cpr");
- + if (ctrl->irq < 0) {
- + cpr3_err(ctrl, "missing CPR interrupt\n");
- + return ctrl->irq;
- + }
- +
- + if (ctrl->supports_hw_closed_loop) {
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + ctrl->ceiling_irq = platform_get_irq_byname(pdev,
- + "ceiling");
- + if (ctrl->ceiling_irq < 0) {
- + cpr3_err(ctrl, "missing ceiling interrupt\n");
- + return ctrl->ceiling_irq;
- + }
- + }
- + }
- +
- + rc = cpr3_regulator_init_ctrl_data(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + rc = cpr3_regulator_init_vreg_data(
- + &ctrl->thread[i].vreg[j]);
- + if (rc)
- + return rc;
- + cpr3_print_quots(&ctrl->thread[i].vreg[j]);
- + }
- + }
- +
- + /*
- + * Add the maximum possible aging voltage margin until it is possible
- + * to perform an aging measurement.
- + */
- + if (ctrl->aging_required)
- + cpr3_regulator_set_aging_ref_adjustment(ctrl, INT_MAX);
- +
- + rc = cpr3_regulator_init_ctrl(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "CPR controller initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + /* Register regulator devices for all threads. */
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + rc = cpr3_regulator_vreg_register(
- + &ctrl->thread[i].vreg[j]);
- + if (rc) {
- + cpr3_err(&ctrl->thread[i].vreg[j], "failed to register regulator, rc=%d\n",
- + rc);
- + goto free_regulators;
- + }
- + }
- + }
- +
- + rc = devm_request_threaded_irq(dev, ctrl->irq, NULL,
- + cpr3_irq_handler,
- + IRQF_ONESHOT |
- + IRQF_TRIGGER_RISING,
- + "cpr3", ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not request IRQ %d, rc=%d\n",
- + ctrl->irq, rc);
- + goto free_regulators;
- + }
- +
- + if (ctrl->supports_hw_closed_loop &&
- + ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
- + rc = devm_request_threaded_irq(dev, ctrl->ceiling_irq, NULL,
- + cpr3_ceiling_irq_handler,
- + IRQF_ONESHOT | IRQF_TRIGGER_RISING,
- + "cpr3_ceiling", ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "could not request ceiling IRQ %d, rc=%d\n",
- + ctrl->ceiling_irq, rc);
- + goto free_regulators;
- + }
- + }
- +
- + mutex_lock(&cpr3_controller_list_mutex);
- + cpr3_regulator_debugfs_ctrl_add(ctrl);
- + list_add(&ctrl->list, &cpr3_controller_list);
- + mutex_unlock(&cpr3_controller_list_mutex);
- +
- + if (ctrl->panic_regs_info) {
- + /* Register panic notification call back */
- + ctrl->panic_notifier.notifier_call = cpr3_panic_callback;
- + atomic_notifier_chain_register(&panic_notifier_list,
- + &ctrl->panic_notifier);
- + }
- +
- + return 0;
- +
- +free_regulators:
- + for (i = 0; i < ctrl->thread_count; i++)
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++)
- + if (!IS_ERR_OR_NULL(ctrl->thread[i].vreg[j].rdev))
- + regulator_unregister(
- + ctrl->thread[i].vreg[j].rdev);
- + return rc;
- +}
- +
- +/**
- + * cpr3_open_loop_regulator_register() - register the regulators for a CPR3
- + * controller which will always work in Open loop and
- + * won't support close loop.
- + * @pdev: Platform device pointer for the CPR3 controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_open_loop_regulator_register(struct platform_device *pdev,
- + struct cpr3_controller *ctrl)
- +{
- + struct device *dev = &pdev->dev;
- + struct cpr3_regulator *vreg;
- + int i, j, rc;
- +
- + if (!dev->of_node) {
- + dev_err(dev, "%s: Device tree node is missing\n", __func__);
- + return -EINVAL;
- + }
- +
- + if (!ctrl || !ctrl->name) {
- + dev_err(dev, "%s: CPR controller data is missing\n", __func__);
- + return -EINVAL;
- + }
- +
- + if (!ctrl->vdd_regulator) {
- + cpr3_err(ctrl, "vdd regulator missing\n");
- + return -EINVAL;
- + }
- +
- + mutex_init(&ctrl->lock);
- +
- + rc = cpr3_regulator_init_ctrl_data(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + vreg = &ctrl->thread[i].vreg[j];
- + vreg->corner[i].last_volt =
- + vreg->corner[i].open_loop_volt;
- + }
- + }
- +
- + /* Register regulator devices for all threads. */
- + for (i = 0; i < ctrl->thread_count; i++) {
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
- + rc = cpr3_regulator_vreg_register(
- + &ctrl->thread[i].vreg[j]);
- + if (rc) {
- + cpr3_err(&ctrl->thread[i].vreg[j], "failed to register regulator, rc=%d\n",
- + rc);
- + goto free_regulators;
- + }
- + }
- + }
- +
- + mutex_lock(&cpr3_controller_list_mutex);
- + list_add(&ctrl->list, &cpr3_controller_list);
- + mutex_unlock(&cpr3_controller_list_mutex);
- +
- + return 0;
- +
- +free_regulators:
- + for (i = 0; i < ctrl->thread_count; i++)
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++)
- + if (!IS_ERR_OR_NULL(ctrl->thread[i].vreg[j].rdev))
- + regulator_unregister(
- + ctrl->thread[i].vreg[j].rdev);
- + return rc;
- +}
- +
- +/**
- + * cpr3_regulator_unregister() - unregister the regulators for a CPR3 controller
- + * and perform CPR hardware shutdown
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_regulator_unregister(struct cpr3_controller *ctrl)
- +{
- + int i, j, rc = 0;
- +
- + mutex_lock(&cpr3_controller_list_mutex);
- + list_del(&ctrl->list);
- + cpr3_regulator_debugfs_ctrl_remove(ctrl);
- + mutex_unlock(&cpr3_controller_list_mutex);
- +
- + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
- + rc = cpr3_ctrl_clear_cpr4_config(ctrl);
- + if (rc)
- + cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
- + rc);
- + }
- +
- + cpr3_ctrl_loop_disable(ctrl);
- +
- + cpr3_closed_loop_disable(ctrl);
- +
- + if (ctrl->vdd_limit_regulator) {
- + regulator_disable(ctrl->vdd_limit_regulator);
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++)
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++)
- + regulator_unregister(ctrl->thread[i].vreg[j].rdev);
- +
- + if (ctrl->panic_notifier.notifier_call)
- + atomic_notifier_chain_unregister(&panic_notifier_list,
- + &ctrl->panic_notifier);
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_open_loop_regulator_unregister() - unregister the regulators for a CPR3
- + * open loop controller and perform CPR hardware shutdown
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl)
- +{
- + int i, j;
- +
- + mutex_lock(&cpr3_controller_list_mutex);
- + list_del(&ctrl->list);
- + mutex_unlock(&cpr3_controller_list_mutex);
- +
- + if (ctrl->vdd_limit_regulator) {
- + regulator_disable(ctrl->vdd_limit_regulator);
- + }
- +
- + for (i = 0; i < ctrl->thread_count; i++)
- + for (j = 0; j < ctrl->thread[i].vreg_count; j++)
- + regulator_unregister(ctrl->thread[i].vreg[j].rdev);
- +
- + if (ctrl->panic_notifier.notifier_call)
- + atomic_notifier_chain_unregister(&panic_notifier_list,
- + &ctrl->panic_notifier);
- +
- + return 0;
- +}
- --- /dev/null
- +++ b/drivers/regulator/cpr3-regulator.h
- @@ -0,0 +1,1211 @@
- +/*
- + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 as published by the Free Software Foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + */
- +
- +#ifndef __REGULATOR_CPR3_REGULATOR_H__
- +#define __REGULATOR_CPR3_REGULATOR_H__
- +
- +#include <linux/clk.h>
- +#include <linux/mutex.h>
- +#include <linux/of.h>
- +#include <linux/platform_device.h>
- +#include <linux/types.h>
- +#include <linux/power/qcom/apm.h>
- +#include <linux/regulator/driver.h>
- +
- +struct cpr3_controller;
- +struct cpr3_thread;
- +
- +/**
- + * struct cpr3_fuse_param - defines one contiguous segment of a fuse parameter
- + * that is contained within a given row.
- + * @row: Fuse row number
- + * @bit_start: The first bit within the row of the fuse parameter segment
- + * @bit_end: The last bit within the row of the fuse parameter segment
- + *
- + * Each fuse row is 64 bits in length. bit_start and bit_end may take values
- + * from 0 to 63. bit_start must be less than or equal to bit_end.
- + */
- +struct cpr3_fuse_param {
- + unsigned row;
- + unsigned bit_start;
- + unsigned bit_end;
- +};
- +
- +/* Each CPR3 sensor has 16 ring oscillators */
- +#define CPR3_RO_COUNT 16
- +
- +/* The maximum number of sensors that can be present on a single CPR loop. */
- +#define CPR3_MAX_SENSOR_COUNT 256
- +
- +/* This constant is used when allocating array printing buffers. */
- +#define MAX_CHARS_PER_INT 10
- +
- +/**
- + * struct cpr4_sdelta - CPR4 controller specific data structure for the sdelta
- + * adjustment table which is used to adjust the VDD supply
- + * voltage automatically based upon the temperature and/or
- + * the number of online CPU cores.
- + * @allow_core_count_adj: Core count adjustments are allowed.
- + * @allow_temp_adj: Temperature based adjustments are allowed.
- + * @max_core_count: Maximum number of cores considered for core count
- + * adjustment logic.
- + * @temp_band_count: Number of temperature bands considered for temperature
- + * based adjustment logic.
- + * @cap_volt: CAP in uV to apply to SDELTA margins with multiple
- + * cpr3-regulators defined for single controller.
- + * @table: SDELTA table with per-online-core and temperature based
- + * adjustments of size (max_core_count * temp_band_count)
- + * Outer: core count
- + * Inner: temperature band
- + * Each element has units of VDD supply steps. Positive
- + * values correspond to a reduction in voltage and negative
- + * value correspond to an increase (this follows the SDELTA
- + * register semantics).
- + * @allow_boost: Voltage boost allowed.
- + * @boost_num_cores: The number of online cores at which the boost voltage
- + * adjustments will be applied
- + * @boost_table: SDELTA table with boost voltage adjustments of size
- + * temp_band_count. Each element has units of VDD supply
- + * steps. Positive values correspond to a reduction in
- + * voltage and negative value correspond to an increase
- + * (this follows the SDELTA register semantics).
- + */
- +struct cpr4_sdelta {
- + bool allow_core_count_adj;
- + bool allow_temp_adj;
- + int max_core_count;
- + int temp_band_count;
- + int cap_volt;
- + int *table;
- + bool allow_boost;
- + int boost_num_cores;
- + int *boost_table;
- +};
- +
- +/**
- + * struct cpr3_corner - CPR3 virtual voltage corner data structure
- + * @floor_volt: CPR closed-loop floor voltage in microvolts
- + * @ceiling_volt: CPR closed-loop ceiling voltage in microvolts
- + * @open_loop_volt: CPR open-loop voltage (i.e. initial voltage) in
- + * microvolts
- + * @last_volt: Last known settled CPR closed-loop voltage which is used
- + * when switching to a new corner
- + * @abs_ceiling_volt: The absolute CPR closed-loop ceiling voltage in
- + * microvolts. This is used to limit the ceiling_volt
- + * value when it is increased as a result of aging
- + * adjustment.
- + * @unaged_floor_volt: The CPR closed-loop floor voltage in microvolts before
- + * any aging adjustment is performed
- + * @unaged_ceiling_volt: The CPR closed-loop ceiling voltage in microvolts
- + * before any aging adjustment is performed
- + * @unaged_open_loop_volt: The CPR open-loop voltage (i.e. initial voltage) in
- + * microvolts before any aging adjusment is performed
- + * @system_volt: The system-supply voltage in microvolts or corners or
- + * levels
- + * @mem_acc_volt: The mem-acc-supply voltage in corners
- + * @proc_freq: Processor frequency in Hertz. For CPR rev. 3 and 4
- + * conrollers, this field is only used by platform specific
- + * CPR3 driver for interpolation. For CPRh-compliant
- + * controllers, this frequency is also utilized by the
- + * clock driver to determine the corner to CPU clock
- + * frequency mappings.
- + * @cpr_fuse_corner: Fused corner index associated with this virtual corner
- + * (only used by platform specific CPR3 driver for
- + * mapping purposes)
- + * @target_quot: Array of target quotient values to use for each ring
- + * oscillator (RO) for this corner. A value of 0 should be
- + * specified as the target quotient for each RO that is
- + * unused by this corner.
- + * @ro_scale: Array of CPR ring oscillator (RO) scaling factors. The
- + * scaling factor for each RO is defined from RO0 to RO15
- + * with units of QUOT/V. A value of 0 may be specified for
- + * an RO that is unused.
- + * @ro_mask: Bitmap where each of the 16 LSBs indicate if the
- + * corresponding ROs should be masked for this corner
- + * @irq_en: Bitmap of the CPR interrupts to enable for this corner
- + * @aging_derate: The amount to derate the aging voltage adjustment
- + * determined for the reference corner in units of uV/mV.
- + * E.g. a value of 900 would imply that the adjustment for
- + * this corner should be 90% (900/1000) of that for the
- + * reference corner.
- + * @use_open_loop: Boolean indicating that open-loop (i.e CPR disabled) as
- + * opposed to closed-loop operation must be used for this
- + * corner on CPRh controllers.
- + * @sdelta: The CPR4 controller specific data for this corner. This
- + * field is applicable for CPR4 controllers.
- + *
- + * The value of last_volt is initialized inside of the cpr3_regulator_register()
- + * call with the open_loop_volt value. It can later be updated to the settled
- + * VDD supply voltage. The values for unaged_floor_volt, unaged_ceiling_volt,
- + * and unaged_open_loop_volt are initialized inside of cpr3_regulator_register()
- + * if ctrl->aging_required == true. These three values must be pre-initialized
- + * if cpr3_regulator_register() is called with ctrl->aging_required == false and
- + * ctrl->aging_succeeded == true.
- + *
- + * The values of ro_mask and irq_en are initialized inside of the
- + * cpr3_regulator_register() call.
- + */
- +struct cpr3_corner {
- + int floor_volt;
- + int ceiling_volt;
- + int cold_temp_open_loop_volt;
- + int normal_temp_open_loop_volt;
- + int open_loop_volt;
- + int last_volt;
- + int abs_ceiling_volt;
- + int unaged_floor_volt;
- + int unaged_ceiling_volt;
- + int unaged_open_loop_volt;
- + int system_volt;
- + int mem_acc_volt;
- + u32 proc_freq;
- + int cpr_fuse_corner;
- + u32 target_quot[CPR3_RO_COUNT];
- + u32 ro_scale[CPR3_RO_COUNT];
- + u32 ro_mask;
- + u32 irq_en;
- + int aging_derate;
- + bool use_open_loop;
- + struct cpr4_sdelta *sdelta;
- +};
- +
- +/**
- + * struct cprh_corner_band - CPRh controller specific data structure which
- + * encapsulates the range of corners and the SDELTA
- + * adjustment table to be applied to the corners within
- + * the min and max bounds of the corner band.
- + * @corner: Corner number which defines the corner band boundary
- + * @sdelta: The SDELTA adjustment table which contains core-count
- + * and temp based margin adjustments that are applicable
- + * to the corner band.
- + */
- +struct cprh_corner_band {
- + int corner;
- + struct cpr4_sdelta *sdelta;
- +};
- +
- +/**
- + * struct cpr3_fuse_parameters - CPR4 fuse specific data structure which has
- + * the required fuse parameters need for Close Loop CPR
- + * @(*apss_ro_sel_param)[2]: Pointer to RO select fuse details
- + * @(*apss_init_voltage_param)[2]: Pointer to Target voltage fuse details
- + * @(*apss_target_quot_param)[2]: Pointer to Target quot fuse details
- + * @(*apss_quot_offset_param)[2]: Pointer to quot offset fuse details
- + * @cpr_fusing_rev_param: Pointer to CPR revision fuse details
- + * @apss_speed_bin_param: Pointer to Speed bin fuse details
- + * @cpr_boost_fuse_cfg_param: Pointer to Boost fuse cfg details
- + * @apss_boost_fuse_volt_param: Pointer to Boost fuse volt details
- + * @misc_fuse_volt_adj_param: Pointer to Misc fuse volt fuse details
- + */
- +struct cpr3_fuse_parameters {
- + struct cpr3_fuse_param (*apss_ro_sel_param)[2];
- + struct cpr3_fuse_param (*apss_init_voltage_param)[2];
- + struct cpr3_fuse_param (*apss_target_quot_param)[2];
- + struct cpr3_fuse_param (*apss_quot_offset_param)[2];
- + struct cpr3_fuse_param *cpr_fusing_rev_param;
- + struct cpr3_fuse_param *apss_speed_bin_param;
- + struct cpr3_fuse_param *cpr_boost_fuse_cfg_param;
- + struct cpr3_fuse_param *apss_boost_fuse_volt_param;
- + struct cpr3_fuse_param *misc_fuse_volt_adj_param;
- +};
- +
- +struct cpr4_mem_acc_func {
- + void (*set_mem_acc)(struct regulator_dev *);
- + void (*clear_mem_acc)(struct regulator_dev *);
- +};
- +
- +/**
- + * struct cpr4_reg_data - CPR4 regulator specific data structure which is
- + * target specific
- + * @cpr_valid_fuse_count: Number of valid fuse corners
- + * @fuse_ref_volt: Pointer to fuse reference voltage
- + * @fuse_step_volt: CPR step voltage available in fuse
- + * @cpr_clk_rate: CPR clock rate
- + * @boost_fuse_ref_volt: Boost fuse reference voltage
- + * @boost_ceiling_volt: Boost ceiling voltage
- + * @boost_floor_volt: Boost floor voltage
- + * @cpr3_fuse_params: Pointer to CPR fuse parameters
- + * @mem_acc_funcs: Pointer to MEM ACC set/clear functions
- + **/
- +struct cpr4_reg_data {
- + u32 cpr_valid_fuse_count;
- + int *fuse_ref_volt;
- + u32 fuse_step_volt;
- + u32 cpr_clk_rate;
- + int boost_fuse_ref_volt;
- + int boost_ceiling_volt;
- + int boost_floor_volt;
- + struct cpr3_fuse_parameters *cpr3_fuse_params;
- + struct cpr4_mem_acc_func *mem_acc_funcs;
- +};
- +/**
- + * struct cpr3_reg_data - CPR3 regulator specific data structure which is
- + * target specific
- + * @cpr_valid_fuse_count: Number of valid fuse corners
- + * @(*init_voltage_param)[2]: Pointer to Target voltage fuse details
- + * @fuse_ref_volt: Pointer to fuse reference voltage
- + * @fuse_step_volt: CPR step voltage available in fuse
- + * @cpr_clk_rate: CPR clock rate
- + * @cpr3_fuse_params: Pointer to CPR fuse parameters
- + **/
- +struct cpr3_reg_data {
- + u32 cpr_valid_fuse_count;
- + struct cpr3_fuse_param (*init_voltage_param)[2];
- + int *fuse_ref_volt;
- + u32 fuse_step_volt;
- + u32 cpr_clk_rate;
- +};
- +
- +/**
- + * struct cpr3_regulator - CPR3 logical regulator instance associated with a
- + * given CPR3 hardware thread
- + * @of_node: Device node associated with the device tree child node
- + * of this CPR3 regulator
- + * @thread: Pointer to the CPR3 thread which manages this CPR3
- + * regulator
- + * @name: Unique name for this CPR3 regulator which is filled
- + * using the device tree regulator-name property
- + * @rdesc: Regulator description for this CPR3 regulator
- + * @rdev: Regulator device pointer for the regulator registered
- + * for this CPR3 regulator
- + * @mem_acc_regulator: Pointer to the optional mem-acc supply regulator used
- + * to manage memory circuitry settings based upon CPR3
- + * regulator output voltage.
- + * @corner: Array of all corners supported by this CPR3 regulator
- + * @corner_count: The number of elements in the corner array
- + * @corner_band: Array of all corner bands supported by CPRh compatible
- + * controllers
- + * @cpr4_regulator_data Target specific cpr4 regulator data
- + * @cpr3_regulator_data Target specific cpr3 regulator data
- + * @corner_band_count: The number of elements in the corner band array
- + * @platform_fuses: Pointer to platform specific CPR fuse data (only used by
- + * platform specific CPR3 driver)
- + * @speed_bin_fuse: Value read from the speed bin fuse parameter
- + * @speed_bins_supported: The number of speed bins supported by the device tree
- + * configuration for this CPR3 regulator
- + * @cpr_rev_fuse: Value read from the CPR fusing revision fuse parameter
- + * @fuse_combo: Platform specific enum value identifying the specific
- + * combination of fuse values found on a given chip
- + * @fuse_combos_supported: The number of fuse combinations supported by the
- + * device tree configuration for this CPR3 regulator
- + * @fuse_corner_count: Number of corners defined by fuse parameters
- + * @fuse_corner_map: Array of length fuse_corner_count which specifies the
- + * highest corner associated with each fuse corner. Note
- + * that each element must correspond to a valid corner
- + * and that element values must be strictly increasing.
- + * Also, it is acceptable for the lowest fuse corner to map
- + * to a corner other than the lowest. Likewise, it is
- + * acceptable for the highest fuse corner to map to a
- + * corner other than the highest.
- + * @fuse_combo_corner_sum: The sum of the corner counts across all fuse combos
- + * @fuse_combo_offset: The device tree property array offset for the selected
- + * fuse combo
- + * @speed_bin_corner_sum: The sum of the corner counts across all speed bins
- + * This may be specified as 0 if per speed bin parsing
- + * support is not required.
- + * @speed_bin_offset: The device tree property array offset for the selected
- + * speed bin
- + * @fuse_combo_corner_band_sum: The sum of the corner band counts across all
- + * fuse combos
- + * @fuse_combo_corner_band_offset: The device tree property array offset for
- + * the corner band count corresponding to the selected
- + * fuse combo
- + * @speed_bin_corner_band_sum: The sum of the corner band counts across all
- + * speed bins. This may be specified as 0 if per speed bin
- + * parsing support is not required
- + * @speed_bin_corner_band_offset: The device tree property array offset for the
- + * corner band count corresponding to the selected speed
- + * bin
- + * @pd_bypass_mask: Bit mask of power domains associated with this CPR3
- + * regulator
- + * @dynamic_floor_corner: Index identifying the voltage corner for the CPR3
- + * regulator whose last_volt value should be used as the
- + * global CPR floor voltage if all of the power domains
- + * associated with this CPR3 regulator are bypassed
- + * @uses_dynamic_floor: Boolean flag indicating that dynamic_floor_corner should
- + * be utilized for the CPR3 regulator
- + * @current_corner: Index identifying the currently selected voltage corner
- + * for the CPR3 regulator or less than 0 if no corner has
- + * been requested
- + * @last_closed_loop_corner: Index identifying the last voltage corner for the
- + * CPR3 regulator which was configured when operating in
- + * CPR closed-loop mode or less than 0 if no corner has
- + * been requested. CPR registers are only written to when
- + * using closed-loop mode.
- + * @aggregated: Boolean flag indicating that this CPR3 regulator
- + * participated in the last aggregation event
- + * @debug_corner: Index identifying voltage corner used for displaying
- + * corner configuration values in debugfs
- + * @vreg_enabled: Boolean defining the enable state of the CPR3
- + * regulator's regulator within the regulator framework.
- + * @aging_allowed: Boolean defining if CPR aging adjustments are allowed
- + * for this CPR3 regulator given the fuse combo of the
- + * device
- + * @aging_allow_open_loop_adj: Boolean defining if the open-loop voltage of each
- + * corner of this regulator should be adjusted as a result
- + * of an aging measurement. This flag can be set to false
- + * when the open-loop voltage adjustments have been
- + * specified such that they include the maximum possible
- + * aging adjustment. This flag is only used if
- + * aging_allowed == true.
- + * @aging_corner: The corner that should be configured for this regulator
- + * when an aging measurement is performed.
- + * @aging_max_adjust_volt: The maximum aging voltage margin in microvolts that
- + * may be added to the target quotients of this regulator.
- + * A value of 0 may be specified if this regulator does not
- + * require any aging adjustment.
- + * @allow_core_count_adj: Core count adjustments are allowed for this regulator.
- + * @allow_temp_adj: Temperature based adjustments are allowed for this
- + * regulator.
- + * @max_core_count: Maximum number of cores considered for core count
- + * adjustment logic.
- + * @allow_boost: Voltage boost allowed for this regulator.
- + *
- + * This structure contains both configuration and runtime state data. The
- + * elements current_corner, last_closed_loop_corner, aggregated, debug_corner,
- + * and vreg_enabled are state variables.
- + */
- +struct cpr3_regulator {
- + struct device_node *of_node;
- + struct cpr3_thread *thread;
- + const char *name;
- + struct regulator_desc rdesc;
- + struct regulator_dev *rdev;
- + struct regulator *mem_acc_regulator;
- + struct cpr3_corner *corner;
- + int corner_count;
- + struct cprh_corner_band *corner_band;
- + struct cpr4_reg_data *cpr4_regulator_data;
- + struct cpr3_reg_data *cpr3_regulator_data;
- + u32 corner_band_count;
- +
- + void *platform_fuses;
- + int speed_bin_fuse;
- + int speed_bins_supported;
- + int cpr_rev_fuse;
- + int part_type;
- + int part_type_supported;
- + int fuse_combo;
- + int fuse_combos_supported;
- + int fuse_corner_count;
- + int *fuse_corner_map;
- + int fuse_combo_corner_sum;
- + int fuse_combo_offset;
- + int speed_bin_corner_sum;
- + int speed_bin_offset;
- + int fuse_combo_corner_band_sum;
- + int fuse_combo_corner_band_offset;
- + int speed_bin_corner_band_sum;
- + int speed_bin_corner_band_offset;
- + u32 pd_bypass_mask;
- + int dynamic_floor_corner;
- + bool uses_dynamic_floor;
- +
- + int current_corner;
- + int last_closed_loop_corner;
- + bool aggregated;
- + int debug_corner;
- + bool vreg_enabled;
- +
- + bool aging_allowed;
- + bool aging_allow_open_loop_adj;
- + int aging_corner;
- + int aging_max_adjust_volt;
- +
- + bool allow_core_count_adj;
- + bool allow_temp_adj;
- + int max_core_count;
- + bool allow_boost;
- +};
- +
- +/**
- + * struct cpr3_thread - CPR3 hardware thread data structure
- + * @thread_id: Hardware thread ID
- + * @of_node: Device node associated with the device tree child node
- + * of this CPR3 thread
- + * @ctrl: Pointer to the CPR3 controller which manages this thread
- + * @vreg: Array of CPR3 regulators handled by the CPR3 thread
- + * @vreg_count: Number of elements in the vreg array
- + * @aggr_corner: CPR corner containing the in process aggregated voltage
- + * and target quotient configurations which will be applied
- + * @last_closed_loop_aggr_corner: CPR corner containing the most recent
- + * configurations which were written into hardware
- + * registers when operating in closed loop mode (i.e. with
- + * CPR enabled)
- + * @consecutive_up: The number of consecutive CPR step up events needed to
- + * to trigger an up interrupt
- + * @consecutive_down: The number of consecutive CPR step down events needed to
- + * to trigger a down interrupt
- + * @up_threshold: The number CPR error steps required to generate an up
- + * event
- + * @down_threshold: The number CPR error steps required to generate a down
- + * event
- + *
- + * This structure contains both configuration and runtime state data. The
- + * elements aggr_corner and last_closed_loop_aggr_corner are state variables.
- + */
- +struct cpr3_thread {
- + u32 thread_id;
- + struct device_node *of_node;
- + struct cpr3_controller *ctrl;
- + struct cpr3_regulator *vreg;
- + int vreg_count;
- + struct cpr3_corner aggr_corner;
- + struct cpr3_corner last_closed_loop_aggr_corner;
- +
- + u32 consecutive_up;
- + u32 consecutive_down;
- + u32 up_threshold;
- + u32 down_threshold;
- +};
- +
- +/* Per CPR controller data */
- +/**
- + * enum cpr3_mem_acc_corners - Constants which define the number of mem-acc
- + * regulator corners available in the mem-acc corner map array.
- + * %CPR3_MEM_ACC_LOW_CORNER: Index in mem-acc corner map array mapping to the
- + * mem-acc regulator corner
- + * to be used for low voltage vdd supply
- + * %CPR3_MEM_ACC_HIGH_CORNER: Index in mem-acc corner map array mapping to the
- + * mem-acc regulator corner to be used for high
- + * voltage vdd supply
- + * %CPR3_MEM_ACC_CORNERS: Number of elements in the mem-acc corner map
- + * array
- + */
- +enum cpr3_mem_acc_corners {
- + CPR3_MEM_ACC_LOW_CORNER = 0,
- + CPR3_MEM_ACC_HIGH_CORNER = 1,
- + CPR3_MEM_ACC_CORNERS = 2,
- +};
- +
- +/**
- + * enum cpr3_count_mode - CPR3 controller count mode which defines the
- + * method that CPR sensor data is acquired
- + * %CPR3_COUNT_MODE_ALL_AT_ONCE_MIN: Capture all CPR sensor readings
- + * simultaneously and report the minimum
- + * value seen in successive measurements
- + * %CPR3_COUNT_MODE_ALL_AT_ONCE_MAX: Capture all CPR sensor readings
- + * simultaneously and report the maximum
- + * value seen in successive measurements
- + * %CPR3_COUNT_MODE_STAGGERED: Read one sensor at a time in a
- + * sequential fashion
- + * %CPR3_COUNT_MODE_ALL_AT_ONCE_AGE: Capture all CPR aging sensor readings
- + * simultaneously.
- + */
- +enum cpr3_count_mode {
- + CPR3_COUNT_MODE_ALL_AT_ONCE_MIN = 0,
- + CPR3_COUNT_MODE_ALL_AT_ONCE_MAX = 1,
- + CPR3_COUNT_MODE_STAGGERED = 2,
- + CPR3_COUNT_MODE_ALL_AT_ONCE_AGE = 3,
- +};
- +
- +/**
- + * enum cpr_controller_type - supported CPR controller hardware types
- + * %CPR_CTRL_TYPE_CPR3: HW has CPR3 controller
- + * %CPR_CTRL_TYPE_CPR4: HW has CPR4 controller
- + */
- +enum cpr_controller_type {
- + CPR_CTRL_TYPE_CPR3,
- + CPR_CTRL_TYPE_CPR4,
- +};
- +
- +/**
- + * cpr_setting - supported CPR global settings
- + * %CPR_DEFAULT: default mode from dts will be used
- + * %CPR_DISABLED: ceiling voltage will be used for all the corners
- + * %CPR_OPEN_LOOP_EN: CPR will work in OL
- + * %CPR_CLOSED_LOOP_EN: CPR will work in CL, if supported
- + */
- +enum cpr_setting {
- + CPR_DEFAULT = 0,
- + CPR_DISABLED = 1,
- + CPR_OPEN_LOOP_EN = 2,
- + CPR_CLOSED_LOOP_EN = 3,
- +};
- +
- +/**
- + * struct cpr3_aging_sensor_info - CPR3 aging sensor information
- + * @sensor_id The index of the CPR3 sensor to be used in the aging
- + * measurement.
- + * @ro_scale The CPR ring oscillator (RO) scaling factor for the
- + * aging sensor with units of QUOT/V.
- + * @init_quot_diff: The fused quotient difference between aged and un-aged
- + * paths that was measured at manufacturing time.
- + * @measured_quot_diff: The quotient difference measured at runtime.
- + * @bypass_mask: Bit mask of the CPR sensors that must be bypassed during
- + * the aging measurement for this sensor
- + *
- + * This structure contains both configuration and runtime state data. The
- + * element measured_quot_diff is a state variable.
- + */
- +struct cpr3_aging_sensor_info {
- + u32 sensor_id;
- + u32 ro_scale;
- + int init_quot_diff;
- + int measured_quot_diff;
- + u32 bypass_mask[CPR3_MAX_SENSOR_COUNT / 32];
- +};
- +
- +/**
- + * struct cpr3_reg_info - Register information data structure
- + * @name: Register name
- + * @addr: Register physical address
- + * @value: Register content
- + * @virt_addr: Register virtual address
- + *
- + * This data structure is used to dump some critical register contents
- + * when the device crashes due to a kernel panic.
- + */
- +struct cpr3_reg_info {
- + const char *name;
- + u32 addr;
- + u32 value;
- + void __iomem *virt_addr;
- +};
- +
- +/**
- + * struct cpr3_panic_regs_info - Data structure to dump critical register
- + * contents.
- + * @reg_count: Number of elements in the regs array
- + * @regs: Array of critical registers information
- + *
- + * This data structure is used to dump critical register contents when
- + * the device crashes due to a kernel panic.
- + */
- +struct cpr3_panic_regs_info {
- + int reg_count;
- + struct cpr3_reg_info *regs;
- +};
- +
- +/**
- + * struct cpr3_controller - CPR3 controller data structure
- + * @dev: Device pointer for the CPR3 controller device
- + * @name: Unique name for the CPR3 controller
- + * @ctrl_id: Controller ID corresponding to the VDD supply number
- + * that this CPR3 controller manages.
- + * @cpr_ctrl_base: Virtual address of the CPR3 controller base register
- + * @fuse_base: Virtual address of fuse row 0
- + * @aging_possible_reg: Virtual address of an optional platform-specific
- + * register that must be ready to determine if it is
- + * possible to perform an aging measurement.
- + * @list: list head used in a global cpr3-regulator list so that
- + * cpr3-regulator structs can be found easily in RAM dumps
- + * @thread: Array of CPR3 threads managed by the CPR3 controller
- + * @thread_count: Number of elements in the thread array
- + * @sensor_owner: Array of thread IDs indicating which thread owns a given
- + * CPR sensor
- + * @sensor_count: The number of CPR sensors found on the CPR loop managed
- + * by this CPR controller. Must be equal to the number of
- + * elements in the sensor_owner array
- + * @soc_revision: Revision number of the SoC. This may be unused by
- + * platforms that do not have different behavior for
- + * different SoC revisions.
- + * @lock: Mutex lock used to ensure mutual exclusion between
- + * all of the threads associated with the controller
- + * @vdd_regulator: Pointer to the VDD supply regulator which this CPR3
- + * controller manages
- + * @system_regulator: Pointer to the optional system-supply regulator upon
- + * which the VDD supply regulator depends.
- + * @mem_acc_regulator: Pointer to the optional mem-acc supply regulator used
- + * to manage memory circuitry settings based upon the
- + * VDD supply output voltage.
- + * @vdd_limit_regulator: Pointer to the VDD supply limit regulator which is used
- + * for hardware closed-loop in order specify ceiling and
- + * floor voltage limits (platform specific)
- + * @system_supply_max_volt: Voltage in microvolts which corresponds to the
- + * absolute ceiling voltage of the system-supply
- + * @mem_acc_threshold_volt: mem-acc threshold voltage in microvolts
- + * @mem_acc_corner_map: mem-acc regulator corners mapping to low and high
- + * voltage mem-acc settings for the memories powered by
- + * this CPR3 controller and its associated CPR3 regulators
- + * @mem_acc_crossover_volt: Voltage in microvolts corresponding to the voltage
- + * that the VDD supply must be set to while a MEM ACC
- + * switch is in progress. This element must be initialized
- + * for CPRh controllers when a MEM ACC threshold voltage is
- + * defined.
- + * @core_clk: Pointer to the CPR3 controller core clock
- + * @iface_clk: Pointer to the CPR3 interface clock (platform specific)
- + * @bus_clk: Pointer to the CPR3 bus clock (platform specific)
- + * @irq: CPR interrupt number
- + * @irq_affinity_mask: The cpumask for the CPUs which the CPR interrupt should
- + * have affinity for
- + * @cpu_hotplug_notifier: CPU hotplug notifier used to reset IRQ affinity when a
- + * CPU is brought back online
- + * @ceiling_irq: Interrupt number for the interrupt that is triggered
- + * when hardware closed-loop attempts to exceed the ceiling
- + * voltage
- + * @apm: Handle to the array power mux (APM)
- + * @apm_threshold_volt: Voltage in microvolts which defines the threshold
- + * voltage to determine the APM supply selection for
- + * each corner
- + * @apm_crossover_volt: Voltage in microvolts corresponding to the voltage that
- + * the VDD supply must be set to while an APM switch is in
- + * progress. This element must be initialized for CPRh
- + * controllers when an APM threshold voltage is defined
- + * @apm_adj_volt: Minimum difference between APM threshold voltage and
- + * open-loop voltage which allows the APM threshold voltage
- + * to be used as a ceiling
- + * @apm_high_supply: APM supply to configure if VDD voltage is greater than
- + * or equal to the APM threshold voltage
- + * @apm_low_supply: APM supply to configure if the VDD voltage is less than
- + * the APM threshold voltage
- + * @base_volt: Minimum voltage in microvolts supported by the VDD
- + * supply managed by this CPR controller
- + * @corner_switch_delay_time: The delay time in nanoseconds used by the CPR
- + * controller to wait for voltage settling before
- + * acknowledging the OSM block after corner changes
- + * @cpr_clock_rate: CPR reference clock frequency in Hz.
- + * @sensor_time: The time in nanoseconds that each sensor takes to
- + * perform a measurement.
- + * @loop_time: The time in nanoseconds between consecutive CPR
- + * measurements.
- + * @up_down_delay_time: The time to delay in nanoseconds between consecutive CPR
- + * measurements when the last measurement recommended
- + * increasing or decreasing the vdd-supply voltage.
- + * (platform specific)
- + * @idle_clocks: Number of CPR reference clock ticks that the CPR
- + * controller waits in transitional states.
- + * @step_quot_init_min: The default minimum CPR step quotient value. The step
- + * quotient is the number of additional ring oscillator
- + * ticks observed when increasing one step in vdd-supply
- + * output voltage.
- + * @step_quot_init_max: The default maximum CPR step quotient value.
- + * @step_volt: Step size in microvolts between available set points
- + * of the VDD supply
- + * @down_error_step_limit: CPR4 hardware closed-loop down error step limit which
- + * defines the maximum number of VDD supply regulator steps
- + * that the voltage may be reduced as the result of a
- + * single CPR measurement.
- + * @up_error_step_limit: CPR4 hardware closed-loop up error step limit which
- + * defines the maximum number of VDD supply regulator steps
- + * that the voltage may be increased as the result of a
- + * single CPR measurement.
- + * @count_mode: CPR controller count mode
- + * @count_repeat: Number of times to perform consecutive sensor
- + * measurements when using all-at-once count modes.
- + * @proc_clock_throttle: Defines the processor clock frequency throttling
- + * register value to use. This can be used to reduce the
- + * clock frequency when a power domain exits a low power
- + * mode until CPR settles at a new voltage.
- + * (platform specific)
- + * @cpr_allowed_hw: Boolean which indicates if closed-loop CPR operation is
- + * permitted for a given chip based upon hardware fuse
- + * values
- + * @cpr_allowed_sw: Boolean which indicates if closed-loop CPR operation is
- + * permitted based upon software policies
- + * @supports_hw_closed_loop: Boolean which indicates if this CPR3/4 controller
- + * physically supports hardware closed-loop CPR operation
- + * @use_hw_closed_loop: Boolean which indicates that this controller will be
- + * using hardware closed-loop operation in place of
- + * software closed-loop operation.
- + * @ctrl_type: CPR controller type
- + * @saw_use_unit_mV: Boolean which indicates the unit used in SAW PVC
- + * interface is mV.
- + * @aggr_corner: CPR corner containing the most recently aggregated
- + * voltage configurations which are being used currently
- + * @cpr_enabled: Boolean which indicates that the CPR controller is
- + * enabled and operating in closed-loop mode. CPR clocks
- + * have been prepared and enabled whenever this flag is
- + * true.
- + * @last_corner_was_closed_loop: Boolean indicating if the last known corners
- + * were updated during closed loop operation.
- + * @cpr_suspended: Boolean which indicates that CPR has been temporarily
- + * disabled while enterring system suspend.
- + * @debugfs: Pointer to the debugfs directory of this CPR3 controller
- + * @aging_ref_volt: Reference voltage in microvolts to configure when
- + * performing CPR aging measurements.
- + * @aging_vdd_mode: vdd-supply regulator mode to configure before performing
- + * a CPR aging measurement. It should be one of
- + * REGULATOR_MODE_*.
- + * @aging_complete_vdd_mode: vdd-supply regulator mode to configure after
- + * performing a CPR aging measurement. It should be one of
- + * REGULATOR_MODE_*.
- + * @aging_ref_adjust_volt: The reference aging voltage margin in microvolts that
- + * should be added to the target quotients of the
- + * regulators managed by this controller after derating.
- + * @aging_required: Flag which indicates that a CPR aging measurement still
- + * needs to be performed for this CPR3 controller.
- + * @aging_succeeded: Flag which indicates that a CPR aging measurement has
- + * completed successfully.
- + * @aging_failed: Flag which indicates that a CPR aging measurement has
- + * failed to complete successfully.
- + * @aging_sensor: Array of CPR3 aging sensors which are used to perform
- + * aging measurements at a runtime.
- + * @aging_sensor_count: Number of elements in the aging_sensor array
- + * @aging_possible_mask: Optional bitmask used to mask off the
- + * aging_possible_reg register.
- + * @aging_possible_val: Optional value that the masked aging_possible_reg
- + * register must have in order for a CPR aging measurement
- + * to be possible.
- + * @step_quot_fixed: Fixed step quotient value used for target quotient
- + * adjustment if use_dynamic_step_quot is not set.
- + * This parameter is only relevant for CPR4 controllers
- + * when using the per-online-core or per-temperature
- + * adjustments.
- + * @initial_temp_band: Temperature band used for calculation of base-line
- + * target quotients (fused).
- + * @use_dynamic_step_quot: Boolean value which indicates that margin adjustment
- + * of target quotient will be based on the step quotient
- + * calculated dynamically in hardware for each RO.
- + * @allow_core_count_adj: Core count adjustments are allowed for this controller
- + * @allow_temp_adj: Temperature based adjustments are allowed for
- + * this controller
- + * @allow_boost: Voltage boost allowed for this controller.
- + * @temp_band_count: Number of temperature bands used for temperature based
- + * adjustment logic
- + * @temp_points: Array of temperature points in decidegrees Celsius used
- + * to specify the ranges for selected temperature bands.
- + * The array must have (temp_band_count - 1) elements
- + * allocated.
- + * @temp_sensor_id_start: Start ID of temperature sensors used for temperature
- + * based adjustments.
- + * @temp_sensor_id_end: End ID of temperature sensors used for temperature
- + * based adjustments.
- + * @voltage_settling_time: The time in nanoseconds that it takes for the
- + * VDD supply voltage to settle after being increased or
- + * decreased by step_volt microvolts which is used when
- + * SDELTA voltage margin adjustments are applied.
- + * @cpr_global_setting: Global setting for this CPR controller
- + * @panic_regs_info: Array of panic registers information which provides the
- + * list of registers to dump when the device crashes.
- + * @panic_notifier: Notifier block registered to global panic notifier list.
- + *
- + * This structure contains both configuration and runtime state data. The
- + * elements cpr_allowed_sw, use_hw_closed_loop, aggr_corner, cpr_enabled,
- + * last_corner_was_closed_loop, cpr_suspended, aging_ref_adjust_volt,
- + * aging_required, aging_succeeded, and aging_failed are state variables.
- + *
- + * The apm* elements do not need to be initialized if the VDD supply managed by
- + * the CPR3 controller does not utilize an APM.
- + *
- + * The elements step_quot_fixed, initial_temp_band, allow_core_count_adj,
- + * allow_temp_adj and temp* need to be initialized for CPR4 controllers which
- + * are using per-online-core or per-temperature adjustments.
- + */
- +struct cpr3_controller {
- + struct device *dev;
- + const char *name;
- + int ctrl_id;
- + void __iomem *cpr_ctrl_base;
- + void __iomem *fuse_base;
- + void __iomem *aging_possible_reg;
- + struct list_head list;
- + struct cpr3_thread *thread;
- + int thread_count;
- + u8 *sensor_owner;
- + int sensor_count;
- + int soc_revision;
- + struct mutex lock;
- + struct regulator *vdd_regulator;
- + struct regulator *system_regulator;
- + struct regulator *mem_acc_regulator;
- + struct regulator *vdd_limit_regulator;
- + int system_supply_max_volt;
- + int mem_acc_threshold_volt;
- + int mem_acc_corner_map[CPR3_MEM_ACC_CORNERS];
- + int mem_acc_crossover_volt;
- + struct clk *core_clk;
- + struct clk *iface_clk;
- + struct clk *bus_clk;
- + int irq;
- + struct cpumask irq_affinity_mask;
- + struct notifier_block cpu_hotplug_notifier;
- + int ceiling_irq;
- + struct msm_apm_ctrl_dev *apm;
- + int apm_threshold_volt;
- + int apm_crossover_volt;
- + int apm_adj_volt;
- + enum msm_apm_supply apm_high_supply;
- + enum msm_apm_supply apm_low_supply;
- + int base_volt;
- + u32 corner_switch_delay_time;
- + u32 cpr_clock_rate;
- + u32 sensor_time;
- + u32 loop_time;
- + u32 up_down_delay_time;
- + u32 idle_clocks;
- + u32 step_quot_init_min;
- + u32 step_quot_init_max;
- + int step_volt;
- + u32 down_error_step_limit;
- + u32 up_error_step_limit;
- + enum cpr3_count_mode count_mode;
- + u32 count_repeat;
- + u32 proc_clock_throttle;
- + bool cpr_allowed_hw;
- + bool cpr_allowed_sw;
- + bool supports_hw_closed_loop;
- + bool use_hw_closed_loop;
- + enum cpr_controller_type ctrl_type;
- + bool saw_use_unit_mV;
- + struct cpr3_corner aggr_corner;
- + bool cpr_enabled;
- + bool last_corner_was_closed_loop;
- + bool cpr_suspended;
- + struct dentry *debugfs;
- +
- + int aging_ref_volt;
- + unsigned int aging_vdd_mode;
- + unsigned int aging_complete_vdd_mode;
- + int aging_ref_adjust_volt;
- + bool aging_required;
- + bool aging_succeeded;
- + bool aging_failed;
- + struct cpr3_aging_sensor_info *aging_sensor;
- + int aging_sensor_count;
- + u32 cur_sensor_state;
- + u32 aging_possible_mask;
- + u32 aging_possible_val;
- +
- + u32 step_quot_fixed;
- + u32 initial_temp_band;
- + bool use_dynamic_step_quot;
- + bool allow_core_count_adj;
- + bool allow_temp_adj;
- + bool allow_boost;
- + int temp_band_count;
- + int *temp_points;
- + u32 temp_sensor_id_start;
- + u32 temp_sensor_id_end;
- + u32 voltage_settling_time;
- + enum cpr_setting cpr_global_setting;
- + struct cpr3_panic_regs_info *panic_regs_info;
- + struct notifier_block panic_notifier;
- +};
- +
- +/* Used for rounding voltages to the closest physically available set point. */
- +#define CPR3_ROUND(n, d) (DIV_ROUND_UP(n, d) * (d))
- +
- +#define cpr3_err(cpr3_thread, message, ...) \
- + pr_err("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
- +#define cpr3_info(cpr3_thread, message, ...) \
- + pr_info("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
- +#define cpr3_debug(cpr3_thread, message, ...) \
- + pr_debug("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
- +
- +/*
- + * Offset subtracted from voltage corner values passed in from the regulator
- + * framework in order to get internal voltage corner values. This is needed
- + * since the regulator framework treats 0 as an error value at regulator
- + * registration time.
- + */
- +#define CPR3_CORNER_OFFSET 1
- +
- +#ifdef CONFIG_REGULATOR_CPR3
- +
- +int cpr3_regulator_register(struct platform_device *pdev,
- + struct cpr3_controller *ctrl);
- +int cpr3_open_loop_regulator_register(struct platform_device *pdev,
- + struct cpr3_controller *ctrl);
- +int cpr3_regulator_unregister(struct cpr3_controller *ctrl);
- +int cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl);
- +int cpr3_regulator_suspend(struct cpr3_controller *ctrl);
- +int cpr3_regulator_resume(struct cpr3_controller *ctrl);
- +
- +int cpr3_allocate_threads(struct cpr3_controller *ctrl, u32 min_thread_id,
- + u32 max_thread_id);
- +int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
- + struct platform_device *pdev);
- +int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
- + struct platform_device *pdev, u8 start, u8 end);
- +int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
- + const struct cpr3_fuse_param *param, u64 *param_value);
- +int cpr3_convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse,
- + int fuse_len);
- +u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x);
- +int cpr3_parse_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out);
- +int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out);
- +int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out);
- +int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg);
- +int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
- + u32 *out_value, u32 value_min, u32 value_max);
- +int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl, const char *propname,
- + u32 *out_value, u32 value_min, u32 value_max);
- +int cpr3_parse_common_thread_data(struct cpr3_thread *thread);
- +int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl);
- +int cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl);
- +int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg);
- +void cpr3_open_loop_voltage_as_ceiling(struct cpr3_regulator *vreg);
- +int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg);
- +void cpr3_print_quots(struct cpr3_regulator *vreg);
- +int cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt);
- +int cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
- + int *fuse_volt);
- +int cpr3_adjust_fused_open_loop_voltages(struct cpr3_regulator *vreg,
- + int *fuse_volt);
- +int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg);
- +int cpr3_quot_adjustment(int ro_scale, int volt_adjust);
- +int cpr3_voltage_adjustment(int ro_scale, int quot_adjust);
- +int cpr3_parse_closed_loop_voltage_adjustments(struct cpr3_regulator *vreg,
- + u64 *ro_sel, int *volt_adjust,
- + int *volt_adjust_fuse, int *ro_scale);
- +int cpr4_parse_core_count_temp_voltage_adj(struct cpr3_regulator *vreg,
- + bool use_corner_band);
- +int cpr3_apm_init(struct cpr3_controller *ctrl);
- +int cpr3_mem_acc_init(struct cpr3_regulator *vreg);
- +void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg);
- +void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg);
- +int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
- + int *fuse_volt_adjust);
- +int cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
- + bool is_cold);
- +int cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp);
- +bool cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg);
- +
- +#else
- +
- +static inline int cpr3_regulator_register(struct platform_device *pdev,
- + struct cpr3_controller *ctrl)
- +{
- + return -ENXIO;
- +}
- +
- +static inline int
- +cpr3_open_loop_regulator_register(struct platform_device *pdev,
- + struct cpr3_controller *ctrl);
- +{
- + return -ENXIO;
- +}
- +
- +static inline int cpr3_regulator_unregister(struct cpr3_controller *ctrl)
- +{
- + return -ENXIO;
- +}
- +
- +static inline int
- +cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl)
- +{
- + return -ENXIO;
- +}
- +
- +static inline int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
- +{
- + return -ENXIO;
- +}
- +
- +static inline int cpr3_regulator_resume(struct cpr3_controller *ctrl)
- +{
- + return -ENXIO;
- +}
- +
- +static inline int cpr3_get_thread_name(struct cpr3_thread *thread,
- + struct device_node *thread_node)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_allocate_threads(struct cpr3_controller *ctrl,
- + u32 min_thread_id, u32 max_thread_id)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
- + struct platform_device *pdev)
- +{
- + return -ENXIO;
- +}
- +
- +static inline int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
- + struct platform_device *pdev, u8 start, u8 end)
- +{
- + return 0;
- +}
- +
- +static inline int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
- + const struct cpr3_fuse_param *param, u64 *param_value)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_convert_open_loop_voltage_fuse(int ref_volt,
- + int step_volt, u32 fuse, int fuse_len)
- +{
- + return -EPERM;
- +}
- +
- +static inline u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x)
- +{
- + return 0;
- +}
- +
- +static inline int cpr3_parse_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_corner_band_array_property(
- + struct cpr3_regulator *vreg, const char *prop_name,
- + int tuple_size, u32 *out)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_thread_u32(struct cpr3_thread *thread,
- + const char *propname, u32 *out_value, u32 value_min,
- + u32 value_max)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl,
- + const char *propname, u32 *out_value, u32 value_min,
- + u32 value_max)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_common_thread_data(struct cpr3_thread *thread)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
- +{
- + return -EPERM;
- +}
- +
- +static inline int
- +cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg)
- +{
- + return -EPERM;
- +}
- +
- +static inline void cpr3_open_loop_voltage_as_ceiling(
- + struct cpr3_regulator *vreg)
- +{
- + return;
- +}
- +
- +static inline int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
- +{
- + return -EPERM;
- +}
- +
- +static inline void cpr3_print_quots(struct cpr3_regulator *vreg)
- +{
- + return;
- +}
- +
- +static inline int
- +cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt)
- +{
- + return -EPERM;
- +}
- +
- +static inline int
- +cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
- + int *fuse_volt)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_adjust_fused_open_loop_voltages(
- + struct cpr3_regulator *vreg, int *fuse_volt)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg)
- +{
- + return -EPERM;
- +}
- +
- +static inline int cpr3_quot_adjustment(int ro_scale, int volt_adjust)
- +{
- + return 0;
- +}
- +
- +static inline int cpr3_voltage_adjustment(int ro_scale, int quot_adjust)
- +{
- + return 0;
- +}
- +
- +static inline int cpr3_parse_closed_loop_voltage_adjustments(
- + struct cpr3_regulator *vreg, u64 *ro_sel,
- + int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
- +{
- + return 0;
- +}
- +
- +static inline int cpr4_parse_core_count_temp_voltage_adj(
- + struct cpr3_regulator *vreg, bool use_corner_band)
- +{
- + return 0;
- +}
- +
- +static inline int cpr3_apm_init(struct cpr3_controller *ctrl)
- +{
- + return 0;
- +}
- +
- +static inline int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
- +{
- + return 0;
- +}
- +
- +static inline void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
- +{
- +}
- +
- +static inline void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg)
- +{
- +}
- +
- +static inline int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
- + int *fuse_volt_adjust)
- +{
- + return 0;
- +}
- +
- +static inline int
- +cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
- + bool is_cold)
- +{
- + return 0;
- +}
- +
- +static inline bool
- +cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg)
- +{
- + return false;
- +}
- +
- +static inline int
- +cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp)
- +{
- + return 0;
- +}
- +#endif /* CONFIG_REGULATOR_CPR3 */
- +
- +#endif /* __REGULATOR_CPR_REGULATOR_H__ */
- --- /dev/null
- +++ b/drivers/regulator/cpr3-util.c
- @@ -0,0 +1,2750 @@
- +/*
- + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 as published by the Free Software Foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + */
- +
- +/*
- + * This file contains utility functions to be used by platform specific CPR3
- + * regulator drivers.
- + */
- +
- +#define pr_fmt(fmt) "%s: " fmt, __func__
- +
- +#include <linux/cpumask.h>
- +#include <linux/device.h>
- +#include <linux/io.h>
- +#include <linux/kernel.h>
- +#include <linux/of.h>
- +#include <linux/platform_device.h>
- +#include <linux/slab.h>
- +#include <linux/types.h>
- +
- +#include <soc/qcom/socinfo.h>
- +
- +#include "cpr3-regulator.h"
- +
- +#define BYTES_PER_FUSE_ROW 8
- +#define MAX_FUSE_ROW_BIT 63
- +
- +#define CPR3_CONSECUTIVE_UP_DOWN_MIN 0
- +#define CPR3_CONSECUTIVE_UP_DOWN_MAX 15
- +#define CPR3_UP_DOWN_THRESHOLD_MIN 0
- +#define CPR3_UP_DOWN_THRESHOLD_MAX 31
- +#define CPR3_STEP_QUOT_MIN 0
- +#define CPR3_STEP_QUOT_MAX 63
- +#define CPR3_IDLE_CLOCKS_MIN 0
- +#define CPR3_IDLE_CLOCKS_MAX 31
- +
- +/* This constant has units of uV/mV so 1000 corresponds to 100%. */
- +#define CPR3_AGING_DERATE_UNITY 1000
- +
- +/**
- + * cpr3_allocate_regulators() - allocate and initialize CPR3 regulators for a
- + * given thread based upon device tree data
- + * @thread: Pointer to the CPR3 thread
- + *
- + * This function allocates the thread->vreg array based upon the number of
- + * device tree regulator subnodes. It also initializes generic elements of each
- + * regulator struct such as name, of_node, and thread.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_allocate_regulators(struct cpr3_thread *thread)
- +{
- + struct device_node *node;
- + int i, rc;
- +
- + thread->vreg_count = 0;
- +
- + for_each_available_child_of_node(thread->of_node, node) {
- + thread->vreg_count++;
- + }
- +
- + thread->vreg = devm_kcalloc(thread->ctrl->dev, thread->vreg_count,
- + sizeof(*thread->vreg), GFP_KERNEL);
- + if (!thread->vreg)
- + return -ENOMEM;
- +
- + i = 0;
- + for_each_available_child_of_node(thread->of_node, node) {
- + thread->vreg[i].of_node = node;
- + thread->vreg[i].thread = thread;
- +
- + rc = of_property_read_string(node, "regulator-name",
- + &thread->vreg[i].name);
- + if (rc) {
- + dev_err(thread->ctrl->dev, "could not find regulator name, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + i++;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_allocate_threads() - allocate and initialize CPR3 threads for a given
- + * controller based upon device tree data
- + * @ctrl: Pointer to the CPR3 controller
- + * @min_thread_id: Minimum allowed hardware thread ID for this controller
- + * @max_thread_id: Maximum allowed hardware thread ID for this controller
- + *
- + * This function allocates the ctrl->thread array based upon the number of
- + * device tree thread subnodes. It also initializes generic elements of each
- + * thread struct such as thread_id, of_node, ctrl, and vreg array.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_allocate_threads(struct cpr3_controller *ctrl, u32 min_thread_id,
- + u32 max_thread_id)
- +{
- + struct device *dev = ctrl->dev;
- + struct device_node *thread_node;
- + int i, j, rc;
- +
- + ctrl->thread_count = 0;
- +
- + for_each_available_child_of_node(dev->of_node, thread_node) {
- + ctrl->thread_count++;
- + }
- +
- + ctrl->thread = devm_kcalloc(dev, ctrl->thread_count,
- + sizeof(*ctrl->thread), GFP_KERNEL);
- + if (!ctrl->thread)
- + return -ENOMEM;
- +
- + i = 0;
- + for_each_available_child_of_node(dev->of_node, thread_node) {
- + ctrl->thread[i].of_node = thread_node;
- + ctrl->thread[i].ctrl = ctrl;
- +
- + rc = of_property_read_u32(thread_node, "qcom,cpr-thread-id",
- + &ctrl->thread[i].thread_id);
- + if (rc) {
- + dev_err(dev, "could not read DT property qcom,cpr-thread-id, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (ctrl->thread[i].thread_id < min_thread_id ||
- + ctrl->thread[i].thread_id > max_thread_id) {
- + dev_err(dev, "invalid thread id = %u; not within [%u, %u]\n",
- + ctrl->thread[i].thread_id, min_thread_id,
- + max_thread_id);
- + return -EINVAL;
- + }
- +
- + /* Verify that the thread ID is unique for all child nodes. */
- + for (j = 0; j < i; j++) {
- + if (ctrl->thread[j].thread_id
- + == ctrl->thread[i].thread_id) {
- + dev_err(dev, "duplicate thread id = %u found\n",
- + ctrl->thread[i].thread_id);
- + return -EINVAL;
- + }
- + }
- +
- + rc = cpr3_allocate_regulators(&ctrl->thread[i]);
- + if (rc)
- + return rc;
- +
- + i++;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_map_fuse_base() - ioremap the base address of the fuse region
- + * @ctrl: Pointer to the CPR3 controller
- + * @pdev: Platform device pointer for the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
- + struct platform_device *pdev)
- +{
- + struct resource *res;
- +
- + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fuse_base");
- + if (!res || !res->start) {
- + dev_err(&pdev->dev, "fuse base address is missing\n");
- + return -ENXIO;
- + }
- +
- + ctrl->fuse_base = devm_ioremap(&pdev->dev, res->start,
- + resource_size(res));
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_read_tcsr_setting - reads the CPR setting bits from TCSR register
- + * @ctrl: Pointer to the CPR3 controller
- + * @pdev: Platform device pointer for the CPR3 controller
- + * @start: start bit in TCSR register
- + * @end: end bit in TCSR register
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
- + struct platform_device *pdev, u8 start, u8 end)
- +{
- + struct resource *res;
- + void __iomem *tcsr_reg;
- + u32 val;
- +
- + res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- + "cpr_tcsr_reg");
- + if (!res || !res->start)
- + return 0;
- +
- + tcsr_reg = ioremap(res->start, resource_size(res));
- + if (!tcsr_reg) {
- + dev_err(&pdev->dev, "tcsr ioremap failed\n");
- + return 0;
- + }
- +
- + val = readl_relaxed(tcsr_reg);
- + val &= GENMASK(end, start);
- + val >>= start;
- +
- + switch (val) {
- + case 1:
- + ctrl->cpr_global_setting = CPR_DISABLED;
- + break;
- + case 2:
- + ctrl->cpr_global_setting = CPR_OPEN_LOOP_EN;
- + break;
- + case 3:
- + ctrl->cpr_global_setting = CPR_CLOSED_LOOP_EN;
- + break;
- + default:
- + ctrl->cpr_global_setting = CPR_DEFAULT;
- + }
- +
- + iounmap(tcsr_reg);
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_read_fuse_param() - reads a CPR3 fuse parameter out of eFuses
- + * @fuse_base_addr: Virtual memory address of the eFuse base address
- + * @param: Null terminated array of fuse param segments to read
- + * from
- + * @param_value: Output with value read from the eFuses
- + *
- + * This function reads from each of the parameter segments listed in the param
- + * array and concatenates their values together. Reading stops when an element
- + * is reached which has all 0 struct values. The total number of bits specified
- + * for the fuse parameter across all segments must be less than or equal to 64.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
- + const struct cpr3_fuse_param *param, u64 *param_value)
- +{
- + u64 fuse_val, val;
- + int bits;
- + int bits_total = 0;
- +
- + *param_value = 0;
- +
- + while (param->row || param->bit_start || param->bit_end) {
- + if (param->bit_start > param->bit_end
- + || param->bit_end > MAX_FUSE_ROW_BIT) {
- + pr_err("Invalid fuse parameter segment: row=%u, start=%u, end=%u\n",
- + param->row, param->bit_start, param->bit_end);
- + return -EINVAL;
- + }
- +
- + bits = param->bit_end - param->bit_start + 1;
- + if (bits_total + bits > 64) {
- + pr_err("Invalid fuse parameter segments; total bits = %d\n",
- + bits_total + bits);
- + return -EINVAL;
- + }
- +
- + fuse_val = readq_relaxed(fuse_base_addr
- + + param->row * BYTES_PER_FUSE_ROW);
- + val = (fuse_val >> param->bit_start) & ((1ULL << bits) - 1);
- + *param_value |= val << bits_total;
- + bits_total += bits;
- +
- + param++;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_convert_open_loop_voltage_fuse() - converts an open loop voltage fuse
- + * value into an absolute voltage with units of microvolts
- + * @ref_volt: Reference voltage in microvolts
- + * @step_volt: The step size in microvolts of the fuse LSB
- + * @fuse: Open loop voltage fuse value
- + * @fuse_len: The bit length of the fuse value
- + *
- + * The MSB of the fuse parameter corresponds to a sign bit. If it is set, then
- + * the lower bits correspond to the number of steps to go down from the
- + * reference voltage. If it is not set, then the lower bits correspond to the
- + * number of steps to go up from the reference voltage.
- + */
- +int cpr3_convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse,
- + int fuse_len)
- +{
- + int sign, steps;
- +
- + sign = (fuse & (1 << (fuse_len - 1))) ? -1 : 1;
- + steps = fuse & ((1 << (fuse_len - 1)) - 1);
- +
- + return ref_volt + sign * steps * step_volt;
- +}
- +
- +/**
- + * cpr3_interpolate() - performs linear interpolation
- + * @x1 Lower known x value
- + * @y1 Lower known y value
- + * @x2 Upper known x value
- + * @y2 Upper known y value
- + * @x Intermediate x value
- + *
- + * Returns y where (x, y) falls on the line between (x1, y1) and (x2, y2).
- + * It is required that x1 < x2, y1 <= y2, and x1 <= x <= x2. If these
- + * conditions are not met, then y2 will be returned.
- + */
- +u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x)
- +{
- + u64 temp;
- +
- + if (x1 >= x2 || y1 > y2 || x1 > x || x > x2)
- + return y2;
- +
- + temp = (x2 - x) * (y2 - y1);
- + do_div(temp, (u32)(x2 - x1));
- +
- + return y2 - temp;
- +}
- +
- +/**
- + * cpr3_parse_array_property() - fill an array from a portion of the values
- + * specified for a device tree property
- + * @vreg: Pointer to the CPR3 regulator
- + * @prop_name: The name of the device tree property to read from
- + * @tuple_size: The number of elements in each tuple
- + * @out: Output data array which must be of size tuple_size
- + *
- + * cpr3_parse_common_corner_data() must be called for vreg before this function
- + * is called so that fuse combo and speed bin size elements are initialized.
- + *
- + * Three formats are supported for the device tree property:
- + * 1. Length == tuple_size
- + * (reading begins at index 0)
- + * 2. Length == tuple_size * vreg->fuse_combos_supported
- + * (reading begins at index tuple_size * vreg->fuse_combo)
- + * 3. Length == tuple_size * vreg->speed_bins_supported
- + * (reading begins at index tuple_size * vreg->speed_bin_fuse)
- + *
- + * All other property lengths are treated as errors.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out)
- +{
- + struct device_node *node = vreg->of_node;
- + int len = 0;
- + int i, offset, rc;
- +
- + if (!of_find_property(node, prop_name, &len)) {
- + cpr3_err(vreg, "property %s is missing\n", prop_name);
- + return -EINVAL;
- + }
- +
- + if (len == tuple_size * sizeof(u32)) {
- + offset = 0;
- + } else if (len == tuple_size * vreg->fuse_combos_supported
- + * sizeof(u32)) {
- + offset = tuple_size * vreg->fuse_combo;
- + } else if (vreg->speed_bins_supported > 0 &&
- + len == tuple_size * vreg->speed_bins_supported * sizeof(u32)) {
- + offset = tuple_size * vreg->speed_bin_fuse;
- + } else {
- + if (vreg->speed_bins_supported > 0)
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
- + prop_name, len,
- + tuple_size * sizeof(u32),
- + tuple_size * vreg->speed_bins_supported
- + * sizeof(u32),
- + tuple_size * vreg->fuse_combos_supported
- + * sizeof(u32));
- + else
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
- + prop_name, len,
- + tuple_size * sizeof(u32),
- + tuple_size * vreg->fuse_combos_supported
- + * sizeof(u32));
- + return -EINVAL;
- + }
- +
- + for (i = 0; i < tuple_size; i++) {
- + rc = of_property_read_u32_index(node, prop_name, offset + i,
- + &out[i]);
- + if (rc) {
- + cpr3_err(vreg, "error reading property %s, rc=%d\n",
- + prop_name, rc);
- + return rc;
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_parse_corner_array_property() - fill a per-corner array from a portion
- + * of the values specified for a device tree property
- + * @vreg: Pointer to the CPR3 regulator
- + * @prop_name: The name of the device tree property to read from
- + * @tuple_size: The number of elements in each per-corner tuple
- + * @out: Output data array which must be of size:
- + * tuple_size * vreg->corner_count
- + *
- + * cpr3_parse_common_corner_data() must be called for vreg before this function
- + * is called so that fuse combo and speed bin size elements are initialized.
- + *
- + * Three formats are supported for the device tree property:
- + * 1. Length == tuple_size * vreg->corner_count
- + * (reading begins at index 0)
- + * 2. Length == tuple_size * vreg->fuse_combo_corner_sum
- + * (reading begins at index tuple_size * vreg->fuse_combo_offset)
- + * 3. Length == tuple_size * vreg->speed_bin_corner_sum
- + * (reading begins at index tuple_size * vreg->speed_bin_offset)
- + *
- + * All other property lengths are treated as errors.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out)
- +{
- + struct device_node *node = vreg->of_node;
- + int len = 0;
- + int i, offset, rc;
- +
- + if (!of_find_property(node, prop_name, &len)) {
- + cpr3_err(vreg, "property %s is missing\n", prop_name);
- + return -EINVAL;
- + }
- +
- + if (len == tuple_size * vreg->corner_count * sizeof(u32)) {
- + offset = 0;
- + } else if (len == tuple_size * vreg->fuse_combo_corner_sum
- + * sizeof(u32)) {
- + offset = tuple_size * vreg->fuse_combo_offset;
- + } else if (vreg->speed_bin_corner_sum > 0 &&
- + len == tuple_size * vreg->speed_bin_corner_sum * sizeof(u32)) {
- + offset = tuple_size * vreg->speed_bin_offset;
- + } else {
- + if (vreg->speed_bin_corner_sum > 0)
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
- + prop_name, len,
- + tuple_size * vreg->corner_count * sizeof(u32),
- + tuple_size * vreg->speed_bin_corner_sum
- + * sizeof(u32),
- + tuple_size * vreg->fuse_combo_corner_sum
- + * sizeof(u32));
- + else
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
- + prop_name, len,
- + tuple_size * vreg->corner_count * sizeof(u32),
- + tuple_size * vreg->fuse_combo_corner_sum
- + * sizeof(u32));
- + return -EINVAL;
- + }
- +
- + for (i = 0; i < tuple_size * vreg->corner_count; i++) {
- + rc = of_property_read_u32_index(node, prop_name, offset + i,
- + &out[i]);
- + if (rc) {
- + cpr3_err(vreg, "error reading property %s, rc=%d\n",
- + prop_name, rc);
- + return rc;
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_parse_corner_band_array_property() - fill a per-corner band array
- + * from a portion of the values specified for a device tree
- + * property
- + * @vreg: Pointer to the CPR3 regulator
- + * @prop_name: The name of the device tree property to read from
- + * @tuple_size: The number of elements in each per-corner band tuple
- + * @out: Output data array which must be of size:
- + * tuple_size * vreg->corner_band_count
- + *
- + * cpr3_parse_common_corner_data() must be called for vreg before this function
- + * is called so that fuse combo and speed bin size elements are initialized.
- + * In addition, corner band fuse combo and speed bin sum and offset elements
- + * must be initialized prior to executing this function.
- + *
- + * Three formats are supported for the device tree property:
- + * 1. Length == tuple_size * vreg->corner_band_count
- + * (reading begins at index 0)
- + * 2. Length == tuple_size * vreg->fuse_combo_corner_band_sum
- + * (reading begins at index tuple_size *
- + * vreg->fuse_combo_corner_band_offset)
- + * 3. Length == tuple_size * vreg->speed_bin_corner_band_sum
- + * (reading begins at index tuple_size *
- + * vreg->speed_bin_corner_band_offset)
- + *
- + * All other property lengths are treated as errors.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
- + const char *prop_name, int tuple_size, u32 *out)
- +{
- + struct device_node *node = vreg->of_node;
- + int len = 0;
- + int i, offset, rc;
- +
- + if (!of_find_property(node, prop_name, &len)) {
- + cpr3_err(vreg, "property %s is missing\n", prop_name);
- + return -EINVAL;
- + }
- +
- + if (len == tuple_size * vreg->corner_band_count * sizeof(u32)) {
- + offset = 0;
- + } else if (len == tuple_size * vreg->fuse_combo_corner_band_sum
- + * sizeof(u32)) {
- + offset = tuple_size * vreg->fuse_combo_corner_band_offset;
- + } else if (vreg->speed_bin_corner_band_sum > 0 &&
- + len == tuple_size * vreg->speed_bin_corner_band_sum *
- + sizeof(u32)) {
- + offset = tuple_size * vreg->speed_bin_corner_band_offset;
- + } else {
- + if (vreg->speed_bin_corner_band_sum > 0)
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
- + prop_name, len,
- + tuple_size * vreg->corner_band_count *
- + sizeof(u32),
- + tuple_size * vreg->speed_bin_corner_band_sum
- + * sizeof(u32),
- + tuple_size * vreg->fuse_combo_corner_band_sum
- + * sizeof(u32));
- + else
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
- + prop_name, len,
- + tuple_size * vreg->corner_band_count *
- + sizeof(u32),
- + tuple_size * vreg->fuse_combo_corner_band_sum
- + * sizeof(u32));
- + return -EINVAL;
- + }
- +
- + for (i = 0; i < tuple_size * vreg->corner_band_count; i++) {
- + rc = of_property_read_u32_index(node, prop_name, offset + i,
- + &out[i]);
- + if (rc) {
- + cpr3_err(vreg, "error reading property %s, rc=%d\n",
- + prop_name, rc);
- + return rc;
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_parse_common_corner_data() - parse common CPR3 properties relating to
- + * the corners supported by a CPR3 regulator from device tree
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function reads, validates, and utilizes the following device tree
- + * properties: qcom,cpr-fuse-corners, qcom,cpr-fuse-combos, qcom,cpr-speed-bins,
- + * qcom,cpr-speed-bin-corners, qcom,cpr-corners, qcom,cpr-voltage-ceiling,
- + * qcom,cpr-voltage-floor, qcom,corner-frequencies,
- + * and qcom,cpr-corner-fmax-map.
- + *
- + * It initializes these CPR3 regulator elements: corner, corner_count,
- + * fuse_combos_supported, fuse_corner_map, and speed_bins_supported. It
- + * initializes these elements for each corner: ceiling_volt, floor_volt,
- + * proc_freq, and cpr_fuse_corner.
- + *
- + * It requires that the following CPR3 regulator elements be initialized before
- + * being called: fuse_corner_count, fuse_combo, and speed_bin_fuse.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
- +{
- + struct device_node *node = vreg->of_node;
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + u32 max_fuse_combos, fuse_corners, aging_allowed = 0;
- + u32 max_speed_bins = 0;
- + u32 *combo_corners;
- + u32 *speed_bin_corners;
- + u32 *temp;
- + int i, j, rc;
- +
- + rc = of_property_read_u32(node, "qcom,cpr-fuse-corners", &fuse_corners);
- + if (rc) {
- + cpr3_err(vreg, "error reading property qcom,cpr-fuse-corners, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (vreg->fuse_corner_count != fuse_corners) {
- + cpr3_err(vreg, "device tree config supports %d fuse corners but the hardware has %d fuse corners\n",
- + fuse_corners, vreg->fuse_corner_count);
- + return -EINVAL;
- + }
- +
- + rc = of_property_read_u32(node, "qcom,cpr-fuse-combos",
- + &max_fuse_combos);
- + if (rc) {
- + cpr3_err(vreg, "error reading property qcom,cpr-fuse-combos, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + /*
- + * Sanity check against arbitrarily large value to avoid excessive
- + * memory allocation.
- + */
- + if (max_fuse_combos > 100 || max_fuse_combos == 0) {
- + cpr3_err(vreg, "qcom,cpr-fuse-combos is invalid: %u\n",
- + max_fuse_combos);
- + return -EINVAL;
- + }
- +
- + if (vreg->fuse_combo >= max_fuse_combos) {
- + cpr3_err(vreg, "device tree config supports fuse combos 0-%u but the hardware has combo %d\n",
- + max_fuse_combos - 1, vreg->fuse_combo);
- + BUG_ON(1);
- + return -EINVAL;
- + }
- +
- + vreg->fuse_combos_supported = max_fuse_combos;
- +
- + of_property_read_u32(node, "qcom,cpr-speed-bins", &max_speed_bins);
- +
- + /*
- + * Sanity check against arbitrarily large value to avoid excessive
- + * memory allocation.
- + */
- + if (max_speed_bins > 100) {
- + cpr3_err(vreg, "qcom,cpr-speed-bins is invalid: %u\n",
- + max_speed_bins);
- + return -EINVAL;
- + }
- +
- + if (max_speed_bins && vreg->speed_bin_fuse >= max_speed_bins) {
- + cpr3_err(vreg, "device tree config supports speed bins 0-%u but the hardware has speed bin %d\n",
- + max_speed_bins - 1, vreg->speed_bin_fuse);
- + BUG();
- + return -EINVAL;
- + }
- +
- + vreg->speed_bins_supported = max_speed_bins;
- +
- + combo_corners = kcalloc(vreg->fuse_combos_supported,
- + sizeof(*combo_corners), GFP_KERNEL);
- + if (!combo_corners)
- + return -ENOMEM;
- +
- + rc = of_property_read_u32_array(node, "qcom,cpr-corners", combo_corners,
- + vreg->fuse_combos_supported);
- + if (rc == -EOVERFLOW) {
- + /* Single value case */
- + rc = of_property_read_u32(node, "qcom,cpr-corners",
- + combo_corners);
- + for (i = 1; i < vreg->fuse_combos_supported; i++)
- + combo_corners[i] = combo_corners[0];
- + }
- + if (rc) {
- + cpr3_err(vreg, "error reading property qcom,cpr-corners, rc=%d\n",
- + rc);
- + kfree(combo_corners);
- + return rc;
- + }
- +
- + vreg->fuse_combo_offset = 0;
- + vreg->fuse_combo_corner_sum = 0;
- + for (i = 0; i < vreg->fuse_combos_supported; i++) {
- + vreg->fuse_combo_corner_sum += combo_corners[i];
- + if (i < vreg->fuse_combo)
- + vreg->fuse_combo_offset += combo_corners[i];
- + }
- +
- + vreg->corner_count = combo_corners[vreg->fuse_combo];
- +
- + kfree(combo_corners);
- +
- + vreg->speed_bin_offset = 0;
- + vreg->speed_bin_corner_sum = 0;
- + if (vreg->speed_bins_supported > 0) {
- + speed_bin_corners = kcalloc(vreg->speed_bins_supported,
- + sizeof(*speed_bin_corners), GFP_KERNEL);
- + if (!speed_bin_corners)
- + return -ENOMEM;
- +
- + rc = of_property_read_u32_array(node,
- + "qcom,cpr-speed-bin-corners", speed_bin_corners,
- + vreg->speed_bins_supported);
- + if (rc) {
- + cpr3_err(vreg, "error reading property qcom,cpr-speed-bin-corners, rc=%d\n",
- + rc);
- + kfree(speed_bin_corners);
- + return rc;
- + }
- +
- + for (i = 0; i < vreg->speed_bins_supported; i++) {
- + vreg->speed_bin_corner_sum += speed_bin_corners[i];
- + if (i < vreg->speed_bin_fuse)
- + vreg->speed_bin_offset += speed_bin_corners[i];
- + }
- +
- + if (speed_bin_corners[vreg->speed_bin_fuse]
- + != vreg->corner_count) {
- + cpr3_err(vreg, "qcom,cpr-corners and qcom,cpr-speed-bin-corners conflict on number of corners: %d vs %u\n",
- + vreg->corner_count,
- + speed_bin_corners[vreg->speed_bin_fuse]);
- + kfree(speed_bin_corners);
- + return -EINVAL;
- + }
- +
- + kfree(speed_bin_corners);
- + }
- +
- + vreg->corner = devm_kcalloc(ctrl->dev, vreg->corner_count,
- + sizeof(*vreg->corner), GFP_KERNEL);
- + temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
- + if (!vreg->corner || !temp)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-voltage-ceiling",
- + 1, temp);
- + if (rc)
- + goto free_temp;
- + for (i = 0; i < vreg->corner_count; i++) {
- + vreg->corner[i].ceiling_volt
- + = CPR3_ROUND(temp[i], ctrl->step_volt);
- + vreg->corner[i].abs_ceiling_volt = vreg->corner[i].ceiling_volt;
- + }
- +
- + rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-voltage-floor",
- + 1, temp);
- + if (rc)
- + goto free_temp;
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].floor_volt
- + = CPR3_ROUND(temp[i], ctrl->step_volt);
- +
- + /* Validate ceiling and floor values */
- + for (i = 0; i < vreg->corner_count; i++) {
- + if (vreg->corner[i].floor_volt
- + > vreg->corner[i].ceiling_volt) {
- + cpr3_err(vreg, "CPR floor[%d]=%d > ceiling[%d]=%d uV\n",
- + i, vreg->corner[i].floor_volt,
- + i, vreg->corner[i].ceiling_volt);
- + rc = -EINVAL;
- + goto free_temp;
- + }
- + }
- +
- + /* Load optional system-supply voltages */
- + if (of_find_property(vreg->of_node, "qcom,system-voltage", NULL)) {
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,system-voltage", 1, temp);
- + if (rc)
- + goto free_temp;
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].system_volt = temp[i];
- + }
- +
- + rc = cpr3_parse_corner_array_property(vreg, "qcom,corner-frequencies",
- + 1, temp);
- + if (rc)
- + goto free_temp;
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].proc_freq = temp[i];
- +
- + /* Validate frequencies */
- + for (i = 1; i < vreg->corner_count; i++) {
- + if (vreg->corner[i].proc_freq
- + < vreg->corner[i - 1].proc_freq) {
- + cpr3_err(vreg, "invalid frequency: freq[%d]=%u < freq[%d]=%u\n",
- + i, vreg->corner[i].proc_freq, i - 1,
- + vreg->corner[i - 1].proc_freq);
- + rc = -EINVAL;
- + goto free_temp;
- + }
- + }
- +
- + vreg->fuse_corner_map = devm_kcalloc(ctrl->dev, vreg->fuse_corner_count,
- + sizeof(*vreg->fuse_corner_map), GFP_KERNEL);
- + if (!vreg->fuse_corner_map) {
- + rc = -ENOMEM;
- + goto free_temp;
- + }
- +
- + rc = cpr3_parse_array_property(vreg, "qcom,cpr-corner-fmax-map",
- + vreg->fuse_corner_count, temp);
- + if (rc)
- + goto free_temp;
- + for (i = 0; i < vreg->fuse_corner_count; i++) {
- + vreg->fuse_corner_map[i] = temp[i] - CPR3_CORNER_OFFSET;
- + if (temp[i] < CPR3_CORNER_OFFSET
- + || temp[i] > vreg->corner_count + CPR3_CORNER_OFFSET) {
- + cpr3_err(vreg, "invalid corner value specified in qcom,cpr-corner-fmax-map: %u\n",
- + temp[i]);
- + rc = -EINVAL;
- + goto free_temp;
- + } else if (i > 0 && temp[i - 1] >= temp[i]) {
- + cpr3_err(vreg, "invalid corner %u less than or equal to previous corner %u\n",
- + temp[i], temp[i - 1]);
- + rc = -EINVAL;
- + goto free_temp;
- + }
- + }
- + if (temp[vreg->fuse_corner_count - 1] != vreg->corner_count)
- + cpr3_debug(vreg, "Note: highest Fmax corner %u in qcom,cpr-corner-fmax-map does not match highest supported corner %d\n",
- + temp[vreg->fuse_corner_count - 1],
- + vreg->corner_count);
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + for (j = 0; j < vreg->fuse_corner_count; j++) {
- + if (i + CPR3_CORNER_OFFSET <= temp[j]) {
- + vreg->corner[i].cpr_fuse_corner = j;
- + break;
- + }
- + }
- + if (j == vreg->fuse_corner_count) {
- + /*
- + * Handle the case where the highest fuse corner maps
- + * to a corner below the highest corner.
- + */
- + vreg->corner[i].cpr_fuse_corner
- + = vreg->fuse_corner_count - 1;
- + }
- + }
- +
- + if (of_find_property(vreg->of_node,
- + "qcom,allow-aging-voltage-adjustment", NULL)) {
- + rc = cpr3_parse_array_property(vreg,
- + "qcom,allow-aging-voltage-adjustment",
- + 1, &aging_allowed);
- + if (rc)
- + goto free_temp;
- +
- + vreg->aging_allowed = aging_allowed;
- + }
- +
- + if (of_find_property(vreg->of_node,
- + "qcom,allow-aging-open-loop-voltage-adjustment", NULL)) {
- + rc = cpr3_parse_array_property(vreg,
- + "qcom,allow-aging-open-loop-voltage-adjustment",
- + 1, &aging_allowed);
- + if (rc)
- + goto free_temp;
- +
- + vreg->aging_allow_open_loop_adj = aging_allowed;
- + }
- +
- + if (vreg->aging_allowed) {
- + if (ctrl->aging_ref_volt <= 0) {
- + cpr3_err(ctrl, "qcom,cpr-aging-ref-voltage must be specified\n");
- + rc = -EINVAL;
- + goto free_temp;
- + }
- +
- + rc = cpr3_parse_array_property(vreg,
- + "qcom,cpr-aging-max-voltage-adjustment",
- + 1, &vreg->aging_max_adjust_volt);
- + if (rc)
- + goto free_temp;
- +
- + rc = cpr3_parse_array_property(vreg,
- + "qcom,cpr-aging-ref-corner", 1, &vreg->aging_corner);
- + if (rc) {
- + goto free_temp;
- + } else if (vreg->aging_corner < CPR3_CORNER_OFFSET
- + || vreg->aging_corner > vreg->corner_count - 1
- + + CPR3_CORNER_OFFSET) {
- + cpr3_err(vreg, "aging reference corner=%d not in range [%d, %d]\n",
- + vreg->aging_corner, CPR3_CORNER_OFFSET,
- + vreg->corner_count - 1 + CPR3_CORNER_OFFSET);
- + rc = -EINVAL;
- + goto free_temp;
- + }
- + vreg->aging_corner -= CPR3_CORNER_OFFSET;
- +
- + if (of_find_property(vreg->of_node, "qcom,cpr-aging-derate",
- + NULL)) {
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,cpr-aging-derate", 1, temp);
- + if (rc)
- + goto free_temp;
- +
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].aging_derate = temp[i];
- + } else {
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].aging_derate
- + = CPR3_AGING_DERATE_UNITY;
- + }
- + }
- +
- +free_temp:
- + kfree(temp);
- + return rc;
- +}
- +
- +/**
- + * cpr3_parse_thread_u32() - parse the specified property from the CPR3 thread's
- + * device tree node and verify that it is within the allowed limits
- + * @thread: Pointer to the CPR3 thread
- + * @propname: The name of the device tree property to read
- + * @out_value: The output pointer to fill with the value read
- + * @value_min: The minimum allowed property value
- + * @value_max: The maximum allowed property value
- + *
- + * This function prints a verbose error message if the property is missing or
- + * has a value which is not within the specified range.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
- + u32 *out_value, u32 value_min, u32 value_max)
- +{
- + int rc;
- +
- + rc = of_property_read_u32(thread->of_node, propname, out_value);
- + if (rc) {
- + cpr3_err(thread->ctrl, "thread %u error reading property %s, rc=%d\n",
- + thread->thread_id, propname, rc);
- + return rc;
- + }
- +
- + if (*out_value < value_min || *out_value > value_max) {
- + cpr3_err(thread->ctrl, "thread %u %s=%u is invalid; allowed range: [%u, %u]\n",
- + thread->thread_id, propname, *out_value, value_min,
- + value_max);
- + return -EINVAL;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_parse_ctrl_u32() - parse the specified property from the CPR3
- + * controller's device tree node and verify that it is within the
- + * allowed limits
- + * @ctrl: Pointer to the CPR3 controller
- + * @propname: The name of the device tree property to read
- + * @out_value: The output pointer to fill with the value read
- + * @value_min: The minimum allowed property value
- + * @value_max: The maximum allowed property value
- + *
- + * This function prints a verbose error message if the property is missing or
- + * has a value which is not within the specified range.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl, const char *propname,
- + u32 *out_value, u32 value_min, u32 value_max)
- +{
- + int rc;
- +
- + rc = of_property_read_u32(ctrl->dev->of_node, propname, out_value);
- + if (rc) {
- + cpr3_err(ctrl, "error reading property %s, rc=%d\n",
- + propname, rc);
- + return rc;
- + }
- +
- + if (*out_value < value_min || *out_value > value_max) {
- + cpr3_err(ctrl, "%s=%u is invalid; allowed range: [%u, %u]\n",
- + propname, *out_value, value_min, value_max);
- + return -EINVAL;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_parse_common_thread_data() - parse common CPR3 thread properties from
- + * device tree
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_common_thread_data(struct cpr3_thread *thread)
- +{
- + int rc;
- +
- + rc = cpr3_parse_thread_u32(thread, "qcom,cpr-consecutive-up",
- + &thread->consecutive_up, CPR3_CONSECUTIVE_UP_DOWN_MIN,
- + CPR3_CONSECUTIVE_UP_DOWN_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_thread_u32(thread, "qcom,cpr-consecutive-down",
- + &thread->consecutive_down, CPR3_CONSECUTIVE_UP_DOWN_MIN,
- + CPR3_CONSECUTIVE_UP_DOWN_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_thread_u32(thread, "qcom,cpr-up-threshold",
- + &thread->up_threshold, CPR3_UP_DOWN_THRESHOLD_MIN,
- + CPR3_UP_DOWN_THRESHOLD_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_thread_u32(thread, "qcom,cpr-down-threshold",
- + &thread->down_threshold, CPR3_UP_DOWN_THRESHOLD_MIN,
- + CPR3_UP_DOWN_THRESHOLD_MAX);
- + if (rc)
- + return rc;
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_parse_irq_affinity() - parse CPR IRQ affinity information
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_parse_irq_affinity(struct cpr3_controller *ctrl)
- +{
- + struct device_node *cpu_node;
- + int i, cpu;
- + int len = 0;
- +
- + if (!of_find_property(ctrl->dev->of_node, "qcom,cpr-interrupt-affinity",
- + &len)) {
- + /* No IRQ affinity required */
- + return 0;
- + }
- +
- + len /= sizeof(u32);
- +
- + for (i = 0; i < len; i++) {
- + cpu_node = of_parse_phandle(ctrl->dev->of_node,
- + "qcom,cpr-interrupt-affinity", i);
- + if (!cpu_node) {
- + cpr3_err(ctrl, "could not find CPU node %d\n", i);
- + return -EINVAL;
- + }
- +
- + for_each_possible_cpu(cpu) {
- + if (of_get_cpu_node(cpu, NULL) == cpu_node) {
- + cpumask_set_cpu(cpu, &ctrl->irq_affinity_mask);
- + break;
- + }
- + }
- + of_node_put(cpu_node);
- + }
- +
- + return 0;
- +}
- +
- +static int cpr3_panic_notifier_init(struct cpr3_controller *ctrl)
- +{
- + struct device_node *node = ctrl->dev->of_node;
- + struct cpr3_panic_regs_info *panic_regs_info;
- + struct cpr3_reg_info *regs;
- + int i, reg_count, len, rc = 0;
- +
- + if (!of_find_property(node, "qcom,cpr-panic-reg-addr-list", &len)) {
- + /* panic register address list not specified */
- + return rc;
- + }
- +
- + reg_count = len / sizeof(u32);
- + if (!reg_count) {
- + cpr3_err(ctrl, "qcom,cpr-panic-reg-addr-list has invalid len = %d\n",
- + len);
- + return -EINVAL;
- + }
- +
- + if (!of_find_property(node, "qcom,cpr-panic-reg-name-list", NULL)) {
- + cpr3_err(ctrl, "property qcom,cpr-panic-reg-name-list not specified\n");
- + return -EINVAL;
- + }
- +
- + len = of_property_count_strings(node, "qcom,cpr-panic-reg-name-list");
- + if (reg_count != len) {
- + cpr3_err(ctrl, "qcom,cpr-panic-reg-name-list should have %d strings\n",
- + reg_count);
- + return -EINVAL;
- + }
- +
- + panic_regs_info = devm_kzalloc(ctrl->dev, sizeof(*panic_regs_info),
- + GFP_KERNEL);
- + if (!panic_regs_info)
- + return -ENOMEM;
- +
- + regs = devm_kcalloc(ctrl->dev, reg_count, sizeof(*regs), GFP_KERNEL);
- + if (!regs)
- + return -ENOMEM;
- +
- + for (i = 0; i < reg_count; i++) {
- + rc = of_property_read_string_index(node,
- + "qcom,cpr-panic-reg-name-list", i,
- + &(regs[i].name));
- + if (rc) {
- + cpr3_err(ctrl, "error reading property qcom,cpr-panic-reg-name-list, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = of_property_read_u32_index(node,
- + "qcom,cpr-panic-reg-addr-list", i,
- + &(regs[i].addr));
- + if (rc) {
- + cpr3_err(ctrl, "error reading property qcom,cpr-panic-reg-addr-list, rc=%d\n",
- + rc);
- + return rc;
- + }
- + regs[i].virt_addr = devm_ioremap(ctrl->dev, regs[i].addr, 0x4);
- + if (!regs[i].virt_addr) {
- + pr_err("Unable to map panic register addr 0x%08x\n",
- + regs[i].addr);
- + return -EINVAL;
- + }
- + regs[i].value = 0xFFFFFFFF;
- + }
- +
- + panic_regs_info->reg_count = reg_count;
- + panic_regs_info->regs = regs;
- + ctrl->panic_regs_info = panic_regs_info;
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_parse_common_ctrl_data() - parse common CPR3 controller properties from
- + * device tree
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-sensor-time",
- + &ctrl->sensor_time, 0, UINT_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-loop-time",
- + &ctrl->loop_time, 0, UINT_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-idle-cycles",
- + &ctrl->idle_clocks, CPR3_IDLE_CLOCKS_MIN,
- + CPR3_IDLE_CLOCKS_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-step-quot-init-min",
- + &ctrl->step_quot_init_min, CPR3_STEP_QUOT_MIN,
- + CPR3_STEP_QUOT_MAX);
- + if (rc)
- + return rc;
- +
- + rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-step-quot-init-max",
- + &ctrl->step_quot_init_max, CPR3_STEP_QUOT_MIN,
- + CPR3_STEP_QUOT_MAX);
- + if (rc)
- + return rc;
- +
- + rc = of_property_read_u32(ctrl->dev->of_node, "qcom,voltage-step",
- + &ctrl->step_volt);
- + if (rc) {
- + cpr3_err(ctrl, "error reading property qcom,voltage-step, rc=%d\n",
- + rc);
- + return rc;
- + }
- + if (ctrl->step_volt <= 0) {
- + cpr3_err(ctrl, "qcom,voltage-step=%d is invalid\n",
- + ctrl->step_volt);
- + return -EINVAL;
- + }
- +
- + rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-count-mode",
- + &ctrl->count_mode, CPR3_COUNT_MODE_ALL_AT_ONCE_MIN,
- + CPR3_COUNT_MODE_STAGGERED);
- + if (rc)
- + return rc;
- +
- + /* Count repeat is optional */
- + ctrl->count_repeat = 0;
- + of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-count-repeat",
- + &ctrl->count_repeat);
- +
- + ctrl->cpr_allowed_sw =
- + of_property_read_bool(ctrl->dev->of_node, "qcom,cpr-enable") ||
- + ctrl->cpr_global_setting == CPR_CLOSED_LOOP_EN;
- +
- + rc = cpr3_parse_irq_affinity(ctrl);
- + if (rc)
- + return rc;
- +
- + /* Aging reference voltage is optional */
- + ctrl->aging_ref_volt = 0;
- + of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-aging-ref-voltage",
- + &ctrl->aging_ref_volt);
- +
- + /* Aging possible bitmask is optional */
- + ctrl->aging_possible_mask = 0;
- + of_property_read_u32(ctrl->dev->of_node,
- + "qcom,cpr-aging-allowed-reg-mask",
- + &ctrl->aging_possible_mask);
- +
- + if (ctrl->aging_possible_mask) {
- + /*
- + * Aging possible register value required if bitmask is
- + * specified
- + */
- + rc = cpr3_parse_ctrl_u32(ctrl,
- + "qcom,cpr-aging-allowed-reg-value",
- + &ctrl->aging_possible_val, 0, UINT_MAX);
- + if (rc)
- + return rc;
- + }
- +
- + if (of_find_property(ctrl->dev->of_node, "clock-names", NULL)) {
- + ctrl->core_clk = devm_clk_get(ctrl->dev, "core_clk");
- + if (IS_ERR(ctrl->core_clk)) {
- + rc = PTR_ERR(ctrl->core_clk);
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable request core clock, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + rc = cpr3_panic_notifier_init(ctrl);
- + if (rc)
- + return rc;
- +
- + if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) {
- + ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
- + if (IS_ERR(ctrl->vdd_regulator)) {
- + rc = PTR_ERR(ctrl->vdd_regulator);
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
- + rc);
- + return rc;
- + }
- + } else {
- + cpr3_err(ctrl, "vdd supply is not defined\n");
- + return -ENODEV;
- + }
- +
- + ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
- + "system");
- + if (IS_ERR(ctrl->system_regulator)) {
- + rc = PTR_ERR(ctrl->system_regulator);
- + if (rc != -EPROBE_DEFER) {
- + rc = 0;
- + ctrl->system_regulator = NULL;
- + } else {
- + return rc;
- + }
- + }
- +
- + ctrl->mem_acc_regulator = devm_regulator_get_optional(ctrl->dev,
- + "mem-acc");
- + if (IS_ERR(ctrl->mem_acc_regulator)) {
- + rc = PTR_ERR(ctrl->mem_acc_regulator);
- + if (rc != -EPROBE_DEFER) {
- + rc = 0;
- + ctrl->mem_acc_regulator = NULL;
- + } else {
- + return rc;
- + }
- + }
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_parse_open_loop_common_ctrl_data() - parse common open loop CPR3
- + * controller properties from device tree
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + rc = of_property_read_u32(ctrl->dev->of_node, "qcom,voltage-step",
- + &ctrl->step_volt);
- + if (rc) {
- + cpr3_err(ctrl, "error reading property qcom,voltage-step, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (ctrl->step_volt <= 0) {
- + cpr3_err(ctrl, "qcom,voltage-step=%d is invalid\n",
- + ctrl->step_volt);
- + return -EINVAL;
- + }
- +
- + if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) {
- + ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
- + if (IS_ERR(ctrl->vdd_regulator)) {
- + rc = PTR_ERR(ctrl->vdd_regulator);
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
- + rc);
- + return rc;
- + }
- + } else {
- + cpr3_err(ctrl, "vdd supply is not defined\n");
- + return -ENODEV;
- + }
- +
- + ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
- + "system");
- + if (IS_ERR(ctrl->system_regulator)) {
- + rc = PTR_ERR(ctrl->system_regulator);
- + if (rc != -EPROBE_DEFER) {
- + rc = 0;
- + ctrl->system_regulator = NULL;
- + } else {
- + return rc;
- + }
- + } else {
- + rc = regulator_enable(ctrl->system_regulator);
- + }
- +
- + ctrl->mem_acc_regulator = devm_regulator_get_optional(ctrl->dev,
- + "mem-acc");
- + if (IS_ERR(ctrl->mem_acc_regulator)) {
- + rc = PTR_ERR(ctrl->mem_acc_regulator);
- + if (rc != -EPROBE_DEFER) {
- + rc = 0;
- + ctrl->mem_acc_regulator = NULL;
- + } else {
- + return rc;
- + }
- + }
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_limit_open_loop_voltages() - modify the open-loop voltage of each corner
- + * so that it fits within the floor to ceiling
- + * voltage range of the corner
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function clips the open-loop voltage for each corner so that it is
- + * limited to the floor to ceiling range. It also rounds each open-loop voltage
- + * so that it corresponds to a set point available to the underlying regulator.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg)
- +{
- + int i, volt;
- +
- + cpr3_debug(vreg, "open-loop voltages after trimming and rounding:\n");
- + for (i = 0; i < vreg->corner_count; i++) {
- + volt = CPR3_ROUND(vreg->corner[i].open_loop_volt,
- + vreg->thread->ctrl->step_volt);
- + if (volt < vreg->corner[i].floor_volt)
- + volt = vreg->corner[i].floor_volt;
- + else if (volt > vreg->corner[i].ceiling_volt)
- + volt = vreg->corner[i].ceiling_volt;
- + vreg->corner[i].open_loop_volt = volt;
- + cpr3_debug(vreg, "corner[%2d]: open-loop=%d uV\n", i, volt);
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_open_loop_voltage_as_ceiling() - configures the ceiling voltage for each
- + * corner to equal the open-loop voltage if the relevant device
- + * tree property is found for the CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function assumes that the the open-loop voltage for each corner has
- + * already been rounded to the nearest allowed set point and that it falls
- + * within the floor to ceiling range.
- + *
- + * Return: none
- + */
- +void cpr3_open_loop_voltage_as_ceiling(struct cpr3_regulator *vreg)
- +{
- + int i;
- +
- + if (!of_property_read_bool(vreg->of_node,
- + "qcom,cpr-scaled-open-loop-voltage-as-ceiling"))
- + return;
- +
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].ceiling_volt
- + = vreg->corner[i].open_loop_volt;
- +}
- +
- +/**
- + * cpr3_limit_floor_voltages() - raise the floor voltage of each corner so that
- + * the optional maximum floor to ceiling voltage range specified in
- + * device tree is satisfied
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function also ensures that the open-loop voltage for each corner falls
- + * within the final floor to ceiling voltage range and that floor voltages
- + * increase monotonically.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
- +{
- + char *prop = "qcom,cpr-floor-to-ceiling-max-range";
- + int i, floor_new;
- + u32 *floor_range;
- + int rc = 0;
- +
- + if (!of_find_property(vreg->of_node, prop, NULL))
- + goto enforce_monotonicity;
- +
- + floor_range = kcalloc(vreg->corner_count, sizeof(*floor_range),
- + GFP_KERNEL);
- + if (!floor_range)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_corner_array_property(vreg, prop, 1, floor_range);
- + if (rc)
- + goto free_floor_adjust;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + if ((s32)floor_range[i] >= 0) {
- + floor_new = CPR3_ROUND(vreg->corner[i].ceiling_volt
- + - floor_range[i],
- + vreg->thread->ctrl->step_volt);
- +
- + vreg->corner[i].floor_volt = max(floor_new,
- + vreg->corner[i].floor_volt);
- + if (vreg->corner[i].open_loop_volt
- + < vreg->corner[i].floor_volt)
- + vreg->corner[i].open_loop_volt
- + = vreg->corner[i].floor_volt;
- + }
- + }
- +
- +free_floor_adjust:
- + kfree(floor_range);
- +
- +enforce_monotonicity:
- + /* Ensure that floor voltages increase monotonically. */
- + for (i = 1; i < vreg->corner_count; i++) {
- + if (vreg->corner[i].floor_volt
- + < vreg->corner[i - 1].floor_volt) {
- + cpr3_debug(vreg, "corner %d floor voltage=%d uV < corner %d voltage=%d uV; overriding: corner %d voltage=%d\n",
- + i, vreg->corner[i].floor_volt,
- + i - 1, vreg->corner[i - 1].floor_volt,
- + i, vreg->corner[i - 1].floor_volt);
- + vreg->corner[i].floor_volt
- + = vreg->corner[i - 1].floor_volt;
- +
- + if (vreg->corner[i].open_loop_volt
- + < vreg->corner[i].floor_volt)
- + vreg->corner[i].open_loop_volt
- + = vreg->corner[i].floor_volt;
- + if (vreg->corner[i].ceiling_volt
- + < vreg->corner[i].floor_volt)
- + vreg->corner[i].ceiling_volt
- + = vreg->corner[i].floor_volt;
- + }
- + }
- +
- + return rc;
- +}
- +
- +/**
- + * cpr3_print_quots() - print CPR target quotients into the kernel log for
- + * debugging purposes
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: none
- + */
- +void cpr3_print_quots(struct cpr3_regulator *vreg)
- +{
- + int i, j, pos;
- + size_t buflen;
- + char *buf;
- +
- + buflen = sizeof(*buf) * CPR3_RO_COUNT * (MAX_CHARS_PER_INT + 2);
- + buf = kzalloc(buflen, GFP_KERNEL);
- + if (!buf)
- + return;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + for (j = 0, pos = 0; j < CPR3_RO_COUNT; j++)
- + pos += scnprintf(buf + pos, buflen - pos, " %u",
- + vreg->corner[i].target_quot[j]);
- + cpr3_debug(vreg, "target quots[%2d]:%s\n", i, buf);
- + }
- +
- + kfree(buf);
- +}
- +
- +/**
- + * cpr3_determine_part_type() - determine the part type (SS/TT/FF).
- + *
- + * qcom,cpr-part-types prop tells the number of part types for which correction
- + * voltages are different. Another prop qcom,cpr-parts-voltage will contain the
- + * open loop fuse voltage which will be compared with this part voltage
- + * and accordingly part type will de determined.
- + *
- + * if qcom,cpr-part-types has value n, then qcom,cpr-parts-voltage will be
- + * array of n - 1 elements which will contain the voltage in increasing order.
- + * This function compares the fused volatge with all these voltage and returns
- + * the first index for which the fused volatge is greater.
- + *
- + * @vreg: Pointer to the CPR3 regulator
- + * @fuse_volt: fused open loop voltage which will be compared with
- + * qcom,cpr-parts-voltage array
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt)
- +{
- + int i, rc, len;
- + u32 volt;
- + int soc_version_major;
- + char prop_name[100];
- + const char prop_name_def[] = "qcom,cpr-parts-voltage";
- + const char prop_name_v2[] = "qcom,cpr-parts-voltage-v2";
- +
- + soc_version_major = read_ipq_soc_version_major();
- + BUG_ON(soc_version_major <= 0);
- +
- + if (of_property_read_u32(vreg->of_node, "qcom,cpr-part-types",
- + &vreg->part_type_supported))
- + return 0;
- +
- + if (soc_version_major > 1)
- + strlcpy(prop_name, prop_name_v2, sizeof(prop_name_v2));
- + else
- + strlcpy(prop_name, prop_name_def, sizeof(prop_name_def));
- +
- + if (!of_find_property(vreg->of_node, prop_name, &len)) {
- + cpr3_err(vreg, "property %s is missing\n", prop_name);
- + return -EINVAL;
- + }
- +
- + if (len != (vreg->part_type_supported - 1) * sizeof(u32)) {
- + cpr3_err(vreg, "wrong len in qcom,cpr-parts-voltage\n");
- + return -EINVAL;
- + }
- +
- + for (i = 0; i < vreg->part_type_supported - 1; i++) {
- + rc = of_property_read_u32_index(vreg->of_node,
- + prop_name, i, &volt);
- + if (rc) {
- + cpr3_err(vreg, "error reading property %s, rc=%d\n",
- + prop_name, rc);
- + return rc;
- + }
- +
- + if (fuse_volt < volt)
- + break;
- + }
- +
- + vreg->part_type = i;
- + return 0;
- +}
- +
- +int cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
- + int *fuse_volt)
- +{
- + int i, rc, prev_volt;
- + int *volt_adjust;
- + char prop_str[75];
- + int soc_version_major = read_ipq_soc_version_major();
- +
- + BUG_ON(soc_version_major <= 0);
- +
- + if (vreg->part_type_supported) {
- + if (soc_version_major > 1)
- + snprintf(prop_str, sizeof(prop_str),
- + "qcom,cpr-cold-temp-voltage-adjustment-v2-%d",
- + vreg->part_type);
- + else
- + snprintf(prop_str, sizeof(prop_str),
- + "qcom,cpr-cold-temp-voltage-adjustment-%d",
- + vreg->part_type);
- + } else {
- + strlcpy(prop_str, "qcom,cpr-cold-temp-voltage-adjustment",
- + sizeof(prop_str));
- + }
- +
- + if (!of_find_property(vreg->of_node, prop_str, NULL)) {
- + /* No adjustment required. */
- + cpr3_info(vreg, "No cold temperature adjustment required.\n");
- + return 0;
- + }
- +
- + volt_adjust = kcalloc(vreg->fuse_corner_count, sizeof(*volt_adjust),
- + GFP_KERNEL);
- + if (!volt_adjust)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_array_property(vreg, prop_str,
- + vreg->fuse_corner_count, volt_adjust);
- + if (rc) {
- + cpr3_err(vreg, "could not load cold temp voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->fuse_corner_count; i++) {
- + if (volt_adjust[i]) {
- + prev_volt = fuse_volt[i];
- + fuse_volt[i] += volt_adjust[i];
- + cpr3_debug(vreg,
- + "adjusted fuse corner %d open-loop voltage: %d -> %d uV\n",
- + i, prev_volt, fuse_volt[i]);
- + }
- + }
- +
- +done:
- + kfree(volt_adjust);
- + return rc;
- +}
- +
- +/**
- + * cpr3_can_adjust_cold_temp() - Is cold temperature adjustment available
- + *
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function checks the cold temperature threshold is available
- + *
- + * Return: true on cold temperature threshold is available, else false
- + */
- +bool cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg)
- +{
- + char prop_str[75];
- + int soc_version_major = read_ipq_soc_version_major();
- +
- + BUG_ON(soc_version_major <= 0);
- +
- + if (soc_version_major > 1)
- + strlcpy(prop_str, "qcom,cpr-cold-temp-threshold-v2",
- + sizeof(prop_str));
- + else
- + strlcpy(prop_str, "qcom,cpr-cold-temp-threshold",
- + sizeof(prop_str));
- +
- + if (!of_find_property(vreg->of_node, prop_str, NULL)) {
- + /* No adjustment required. */
- + return false;
- + } else
- + return true;
- +}
- +
- +/**
- + * cpr3_get_cold_temp_threshold() - get cold temperature threshold
- + *
- + * @vreg: Pointer to the CPR3 regulator
- + * @cold_temp: cold temperature read.
- + *
- + * This function reads the cold temperature threshold below which
- + * cold temperature adjustment margins will be applied.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp)
- +{
- + int rc;
- + u32 temp;
- + char req_prop_str[75], prop_str[75];
- + int soc_version_major = read_ipq_soc_version_major();
- +
- + BUG_ON(soc_version_major <= 0);
- +
- + if (vreg->part_type_supported) {
- + if (soc_version_major > 1)
- + snprintf(req_prop_str, sizeof(req_prop_str),
- + "qcom,cpr-cold-temp-voltage-adjustment-v2-%d",
- + vreg->part_type);
- + else
- + snprintf(req_prop_str, sizeof(req_prop_str),
- + "qcom,cpr-cold-temp-voltage-adjustment-%d",
- + vreg->part_type);
- + } else {
- + strlcpy(req_prop_str, "qcom,cpr-cold-temp-voltage-adjustment",
- + sizeof(req_prop_str));
- + }
- +
- + if (soc_version_major > 1)
- + strlcpy(prop_str, "qcom,cpr-cold-temp-threshold-v2",
- + sizeof(prop_str));
- + else
- + strlcpy(prop_str, "qcom,cpr-cold-temp-threshold",
- + sizeof(prop_str));
- +
- + if (!of_find_property(vreg->of_node, req_prop_str, NULL)) {
- + /* No adjustment required. */
- + cpr3_info(vreg, "Cold temperature adjustment not required.\n");
- + return 0;
- + }
- +
- + if (!of_find_property(vreg->of_node, prop_str, NULL)) {
- + /* No adjustment required. */
- + cpr3_err(vreg, "Missing %s required for %s\n",
- + prop_str, req_prop_str);
- + return -EINVAL;
- + }
- +
- + rc = of_property_read_u32(vreg->of_node, prop_str, &temp);
- + if (rc) {
- + cpr3_err(vreg, "error reading property %s, rc=%d\n",
- + prop_str, rc);
- + return rc;
- + }
- +
- + *cold_temp = temp;
- + return 0;
- +}
- +
- +/**
- + * cpr3_adjust_fused_open_loop_voltages() - adjust the fused open-loop voltages
- + * for each fuse corner according to device tree values
- + * @vreg: Pointer to the CPR3 regulator
- + * @fuse_volt: Pointer to an array of the fused open-loop voltage
- + * values
- + *
- + * Voltage values in fuse_volt are modified in place.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_adjust_fused_open_loop_voltages(struct cpr3_regulator *vreg,
- + int *fuse_volt)
- +{
- + int i, rc, prev_volt;
- + int *volt_adjust;
- + char prop_str[75];
- + int soc_version_major = read_ipq_soc_version_major();
- +
- + BUG_ON(soc_version_major <= 0);
- +
- + if (vreg->part_type_supported) {
- + if (soc_version_major > 1)
- + snprintf(prop_str, sizeof(prop_str),
- + "qcom,cpr-open-loop-voltage-fuse-adjustment-v2-%d",
- + vreg->part_type);
- + else
- + snprintf(prop_str, sizeof(prop_str),
- + "qcom,cpr-open-loop-voltage-fuse-adjustment-%d",
- + vreg->part_type);
- + } else {
- + strlcpy(prop_str, "qcom,cpr-open-loop-voltage-fuse-adjustment",
- + sizeof(prop_str));
- + }
- +
- + if (!of_find_property(vreg->of_node, prop_str, NULL)) {
- + /* No adjustment required. */
- + return 0;
- + }
- +
- + volt_adjust = kcalloc(vreg->fuse_corner_count, sizeof(*volt_adjust),
- + GFP_KERNEL);
- + if (!volt_adjust)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_array_property(vreg,
- + prop_str, vreg->fuse_corner_count, volt_adjust);
- + if (rc) {
- + cpr3_err(vreg, "could not load open-loop fused voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->fuse_corner_count; i++) {
- + if (volt_adjust[i]) {
- + prev_volt = fuse_volt[i];
- + fuse_volt[i] += volt_adjust[i];
- + cpr3_debug(vreg, "adjusted fuse corner %d open-loop voltage: %d --> %d uV\n",
- + i, prev_volt, fuse_volt[i]);
- + }
- + }
- +
- +done:
- + kfree(volt_adjust);
- + return rc;
- +}
- +
- +/**
- + * cpr3_adjust_open_loop_voltages() - adjust the open-loop voltages for each
- + * corner according to device tree values
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg)
- +{
- + int i, rc, prev_volt, min_volt;
- + int *volt_adjust, *volt_diff;
- +
- + if (!of_find_property(vreg->of_node,
- + "qcom,cpr-open-loop-voltage-adjustment", NULL)) {
- + /* No adjustment required. */
- + return 0;
- + }
- +
- + volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
- + GFP_KERNEL);
- + volt_diff = kcalloc(vreg->corner_count, sizeof(*volt_diff), GFP_KERNEL);
- + if (!volt_adjust || !volt_diff) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,cpr-open-loop-voltage-adjustment", 1, volt_adjust);
- + if (rc) {
- + cpr3_err(vreg, "could not load open-loop voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + if (volt_adjust[i]) {
- + prev_volt = vreg->corner[i].open_loop_volt;
- + vreg->corner[i].open_loop_volt += volt_adjust[i];
- + cpr3_debug(vreg, "adjusted corner %d open-loop voltage: %d --> %d uV\n",
- + i, prev_volt, vreg->corner[i].open_loop_volt);
- + }
- + }
- +
- + if (of_find_property(vreg->of_node,
- + "qcom,cpr-open-loop-voltage-min-diff", NULL)) {
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,cpr-open-loop-voltage-min-diff", 1, volt_diff);
- + if (rc) {
- + cpr3_err(vreg, "could not load minimum open-loop voltage differences, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + /*
- + * Ensure that open-loop voltages increase monotonically with respect
- + * to configurable minimum allowed differences.
- + */
- + for (i = 1; i < vreg->corner_count; i++) {
- + min_volt = vreg->corner[i - 1].open_loop_volt + volt_diff[i];
- + if (vreg->corner[i].open_loop_volt < min_volt) {
- + cpr3_debug(vreg, "adjusted corner %d open-loop voltage=%d uV < corner %d voltage=%d uV + min diff=%d uV; overriding: corner %d voltage=%d\n",
- + i, vreg->corner[i].open_loop_volt,
- + i - 1, vreg->corner[i - 1].open_loop_volt,
- + volt_diff[i], i, min_volt);
- + vreg->corner[i].open_loop_volt = min_volt;
- + }
- + }
- +
- +done:
- + kfree(volt_diff);
- + kfree(volt_adjust);
- + return rc;
- +}
- +
- +/**
- + * cpr3_quot_adjustment() - returns the quotient adjustment value resulting from
- + * the specified voltage adjustment and RO scaling factor
- + * @ro_scale: The CPR ring oscillator (RO) scaling factor with units
- + * of QUOT/V
- + * @volt_adjust: The amount to adjust the voltage by in units of
- + * microvolts. This value may be positive or negative.
- + */
- +int cpr3_quot_adjustment(int ro_scale, int volt_adjust)
- +{
- + unsigned long long temp;
- + int quot_adjust;
- + int sign = 1;
- +
- + if (ro_scale < 0) {
- + sign = -sign;
- + ro_scale = -ro_scale;
- + }
- +
- + if (volt_adjust < 0) {
- + sign = -sign;
- + volt_adjust = -volt_adjust;
- + }
- +
- + temp = (unsigned long long)ro_scale * (unsigned long long)volt_adjust;
- + do_div(temp, 1000000);
- +
- + quot_adjust = temp;
- + quot_adjust *= sign;
- +
- + return quot_adjust;
- +}
- +
- +/**
- + * cpr3_voltage_adjustment() - returns the voltage adjustment value resulting
- + * from the specified quotient adjustment and RO scaling factor
- + * @ro_scale: The CPR ring oscillator (RO) scaling factor with units
- + * of QUOT/V
- + * @quot_adjust: The amount to adjust the quotient by in units of
- + * QUOT. This value may be positive or negative.
- + */
- +int cpr3_voltage_adjustment(int ro_scale, int quot_adjust)
- +{
- + unsigned long long temp;
- + int volt_adjust;
- + int sign = 1;
- +
- + if (ro_scale < 0) {
- + sign = -sign;
- + ro_scale = -ro_scale;
- + }
- +
- + if (quot_adjust < 0) {
- + sign = -sign;
- + quot_adjust = -quot_adjust;
- + }
- +
- + if (ro_scale == 0)
- + return 0;
- +
- + temp = (unsigned long long)quot_adjust * 1000000;
- + do_div(temp, ro_scale);
- +
- + volt_adjust = temp;
- + volt_adjust *= sign;
- +
- + return volt_adjust;
- +}
- +
- +/**
- + * cpr3_parse_closed_loop_voltage_adjustments() - load per-fuse-corner and
- + * per-corner closed-loop adjustment values from device tree
- + * @vreg: Pointer to the CPR3 regulator
- + * @ro_sel: Array of ring oscillator values selected for each
- + * fuse corner
- + * @volt_adjust: Pointer to array which will be filled with the
- + * per-corner closed-loop adjustment voltages
- + * @volt_adjust_fuse: Pointer to array which will be filled with the
- + * per-fuse-corner closed-loop adjustment voltages
- + * @ro_scale: Pointer to array which will be filled with the
- + * per-fuse-corner RO scaling factor values with units of
- + * QUOT/V
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_parse_closed_loop_voltage_adjustments(
- + struct cpr3_regulator *vreg, u64 *ro_sel,
- + int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
- +{
- + int i, rc;
- + u32 *ro_all_scale;
- +
- + char volt_adj[] = "qcom,cpr-closed-loop-voltage-adjustment";
- + char volt_fuse_adj[] = "qcom,cpr-closed-loop-voltage-fuse-adjustment";
- + char ro_scaling[] = "qcom,cpr-ro-scaling-factor";
- +
- + if (!of_find_property(vreg->of_node, volt_adj, NULL)
- + && !of_find_property(vreg->of_node, volt_fuse_adj, NULL)
- + && !vreg->aging_allowed) {
- + /* No adjustment required. */
- + return 0;
- + } else if (!of_find_property(vreg->of_node, ro_scaling, NULL)) {
- + cpr3_err(vreg, "Missing %s required for closed-loop voltage adjustment.\n",
- + ro_scaling);
- + return -EINVAL;
- + }
- +
- + ro_all_scale = kcalloc(vreg->fuse_corner_count * CPR3_RO_COUNT,
- + sizeof(*ro_all_scale), GFP_KERNEL);
- + if (!ro_all_scale)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_array_property(vreg, ro_scaling,
- + vreg->fuse_corner_count * CPR3_RO_COUNT, ro_all_scale);
- + if (rc) {
- + cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->fuse_corner_count; i++)
- + ro_scale[i] = ro_all_scale[i * CPR3_RO_COUNT + ro_sel[i]];
- +
- + for (i = 0; i < vreg->corner_count; i++)
- + memcpy(vreg->corner[i].ro_scale,
- + &ro_all_scale[vreg->corner[i].cpr_fuse_corner * CPR3_RO_COUNT],
- + sizeof(*ro_all_scale) * CPR3_RO_COUNT);
- +
- + if (of_find_property(vreg->of_node, volt_fuse_adj, NULL)) {
- + rc = cpr3_parse_array_property(vreg, volt_fuse_adj,
- + vreg->fuse_corner_count, volt_adjust_fuse);
- + if (rc) {
- + cpr3_err(vreg, "could not load closed-loop fused voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + if (of_find_property(vreg->of_node, volt_adj, NULL)) {
- + rc = cpr3_parse_corner_array_property(vreg, volt_adj,
- + 1, volt_adjust);
- + if (rc) {
- + cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- +done:
- + kfree(ro_all_scale);
- + return rc;
- +}
- +
- +/**
- + * cpr3_apm_init() - initialize APM data for a CPR3 controller
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * This function loads memory array power mux (APM) data from device tree
- + * if it is present and requests a handle to the appropriate APM controller
- + * device.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_apm_init(struct cpr3_controller *ctrl)
- +{
- + struct device_node *node = ctrl->dev->of_node;
- + int rc;
- +
- + if (!of_find_property(node, "qcom,apm-ctrl", NULL)) {
- + /* No APM used */
- + return 0;
- + }
- +
- + ctrl->apm = msm_apm_ctrl_dev_get(ctrl->dev);
- + if (IS_ERR(ctrl->apm)) {
- + rc = PTR_ERR(ctrl->apm);
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "APM get failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + rc = of_property_read_u32(node, "qcom,apm-threshold-voltage",
- + &ctrl->apm_threshold_volt);
- + if (rc) {
- + cpr3_err(ctrl, "error reading qcom,apm-threshold-voltage, rc=%d\n",
- + rc);
- + return rc;
- + }
- + ctrl->apm_threshold_volt
- + = CPR3_ROUND(ctrl->apm_threshold_volt, ctrl->step_volt);
- +
- + /* No error check since this is an optional property. */
- + of_property_read_u32(node, "qcom,apm-hysteresis-voltage",
- + &ctrl->apm_adj_volt);
- + ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
- +
- + ctrl->apm_high_supply = MSM_APM_SUPPLY_APCC;
- + ctrl->apm_low_supply = MSM_APM_SUPPLY_MX;
- +
- + return 0;
- +}
- +
- +/**
- + * cpr3_mem_acc_init() - initialize mem-acc regulator data for
- + * a CPR3 regulator
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + u32 *temp;
- + int i, rc;
- +
- + if (!ctrl->mem_acc_regulator) {
- + cpr3_info(ctrl, "not using memory accelerator regulator\n");
- + return 0;
- + }
- +
- + temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
- + if (!temp)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_corner_array_property(vreg, "qcom,mem-acc-voltage",
- + 1, temp);
- + if (rc) {
- + cpr3_err(ctrl, "could not load mem-acc corners, rc=%d\n", rc);
- + } else {
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].mem_acc_volt = temp[i];
- + }
- +
- + kfree(temp);
- + return rc;
- +}
- +
- +/**
- + * cpr4_load_core_and_temp_adj() - parse amount of voltage adjustment for
- + * per-online-core and per-temperature voltage adjustment for a
- + * given corner or corner band from device tree.
- + * @vreg: Pointer to the CPR3 regulator
- + * @num: Corner number or corner band number
- + * @use_corner_band: Boolean indicating if the CPR3 regulator supports
- + * adjustments per corner band
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_load_core_and_temp_adj(struct cpr3_regulator *vreg,
- + int num, bool use_corner_band)
- +{
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + struct cpr4_sdelta *sdelta;
- + int sdelta_size, i, j, pos, rc = 0;
- + char str[75];
- + size_t buflen;
- + char *buf;
- +
- + sdelta = use_corner_band ? vreg->corner_band[num].sdelta :
- + vreg->corner[num].sdelta;
- +
- + if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
- + /* corner doesn't need sdelta table */
- + sdelta->max_core_count = 0;
- + sdelta->temp_band_count = 0;
- + return rc;
- + }
- +
- + sdelta_size = sdelta->max_core_count * sdelta->temp_band_count;
- + if (use_corner_band)
- + snprintf(str, sizeof(str),
- + "corner_band=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
- + num, sdelta->max_core_count,
- + sdelta->temp_band_count, sdelta_size);
- + else
- + snprintf(str, sizeof(str),
- + "corner=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
- + num, sdelta->max_core_count,
- + sdelta->temp_band_count, sdelta_size);
- +
- + cpr3_debug(vreg, "%s", str);
- +
- + sdelta->table = devm_kcalloc(ctrl->dev, sdelta_size,
- + sizeof(*sdelta->table), GFP_KERNEL);
- + if (!sdelta->table)
- + return -ENOMEM;
- +
- + if (use_corner_band)
- + snprintf(str, sizeof(str),
- + "qcom,cpr-corner-band%d-temp-core-voltage-adjustment",
- + num + CPR3_CORNER_OFFSET);
- + else
- + snprintf(str, sizeof(str),
- + "qcom,cpr-corner%d-temp-core-voltage-adjustment",
- + num + CPR3_CORNER_OFFSET);
- +
- + rc = cpr3_parse_array_property(vreg, str, sdelta_size,
- + sdelta->table);
- + if (rc) {
- + cpr3_err(vreg, "could not load %s, rc=%d\n", str, rc);
- + return rc;
- + }
- +
- + /*
- + * Convert sdelta margins from uV to PMIC steps and apply negation to
- + * follow the SDELTA register semantics.
- + */
- + for (i = 0; i < sdelta_size; i++)
- + sdelta->table[i] = -(sdelta->table[i] / ctrl->step_volt);
- +
- + buflen = sizeof(*buf) * sdelta_size * (MAX_CHARS_PER_INT + 2);
- + buf = kzalloc(buflen, GFP_KERNEL);
- + if (!buf)
- + return rc;
- +
- + for (i = 0; i < sdelta->max_core_count; i++) {
- + for (j = 0, pos = 0; j < sdelta->temp_band_count; j++)
- + pos += scnprintf(buf + pos, buflen - pos, " %u",
- + sdelta->table[i * sdelta->temp_band_count + j]);
- + cpr3_debug(vreg, "sdelta[%d]:%s\n", i, buf);
- + }
- +
- + kfree(buf);
- + return rc;
- +}
- +
- +/**
- + * cpr4_parse_core_count_temp_voltage_adj() - parse configuration data for
- + * per-online-core and per-temperature voltage adjustment for
- + * a CPR3 regulator from device tree.
- + * @vreg: Pointer to the CPR3 regulator
- + * @use_corner_band: Boolean indicating if the CPR3 regulator supports
- + * adjustments per corner band
- + *
- + * This function supports parsing of per-online-core and per-temperature
- + * adjustments per corner or per corner band. CPR controllers which support
- + * corner bands apply the same adjustments to all corners within a corner band.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr4_parse_core_count_temp_voltage_adj(
- + struct cpr3_regulator *vreg, bool use_corner_band)
- +{
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + struct device_node *node = vreg->of_node;
- + struct cpr3_corner *corner;
- + struct cpr4_sdelta *sdelta;
- + int i, sdelta_table_count, rc = 0;
- + int *allow_core_count_adj = NULL, *allow_temp_adj = NULL;
- + char prop_str[75];
- +
- + if (of_find_property(node, use_corner_band ?
- + "qcom,corner-band-allow-temp-adjustment"
- + : "qcom,corner-allow-temp-adjustment", NULL)) {
- + if (!ctrl->allow_temp_adj) {
- + cpr3_err(ctrl, "Temperature adjustment configurations missing\n");
- + return -EINVAL;
- + }
- +
- + vreg->allow_temp_adj = true;
- + }
- +
- + if (of_find_property(node, use_corner_band ?
- + "qcom,corner-band-allow-core-count-adjustment"
- + : "qcom,corner-allow-core-count-adjustment",
- + NULL)) {
- + rc = of_property_read_u32(node, "qcom,max-core-count",
- + &vreg->max_core_count);
- + if (rc) {
- + cpr3_err(vreg, "error reading qcom,max-core-count, rc=%d\n",
- + rc);
- + return -EINVAL;
- + }
- +
- + vreg->allow_core_count_adj = true;
- + ctrl->allow_core_count_adj = true;
- + }
- +
- + if (!vreg->allow_temp_adj && !vreg->allow_core_count_adj) {
- + /*
- + * Both per-online-core and temperature based adjustments are
- + * disabled for this regulator.
- + */
- + return 0;
- + } else if (!vreg->allow_core_count_adj) {
- + /*
- + * Only per-temperature voltage adjusments are allowed.
- + * Keep max core count value as 1 to allocate SDELTA.
- + */
- + vreg->max_core_count = 1;
- + }
- +
- + if (vreg->allow_core_count_adj) {
- + allow_core_count_adj = kcalloc(vreg->corner_count,
- + sizeof(*allow_core_count_adj),
- + GFP_KERNEL);
- + if (!allow_core_count_adj)
- + return -ENOMEM;
- +
- + snprintf(prop_str, sizeof(prop_str), "%s", use_corner_band ?
- + "qcom,corner-band-allow-core-count-adjustment" :
- + "qcom,corner-allow-core-count-adjustment");
- +
- + rc = use_corner_band ?
- + cpr3_parse_corner_band_array_property(vreg, prop_str,
- + 1, allow_core_count_adj) :
- + cpr3_parse_corner_array_property(vreg, prop_str,
- + 1, allow_core_count_adj);
- + if (rc) {
- + cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
- + rc);
- + goto done;
- + }
- + }
- +
- + if (vreg->allow_temp_adj) {
- + allow_temp_adj = kcalloc(vreg->corner_count,
- + sizeof(*allow_temp_adj), GFP_KERNEL);
- + if (!allow_temp_adj) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + snprintf(prop_str, sizeof(prop_str), "%s", use_corner_band ?
- + "qcom,corner-band-allow-temp-adjustment" :
- + "qcom,corner-allow-temp-adjustment");
- +
- + rc = use_corner_band ?
- + cpr3_parse_corner_band_array_property(vreg, prop_str,
- + 1, allow_temp_adj) :
- + cpr3_parse_corner_array_property(vreg, prop_str,
- + 1, allow_temp_adj);
- + if (rc) {
- + cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
- + rc);
- + goto done;
- + }
- + }
- +
- + sdelta_table_count = use_corner_band ? vreg->corner_band_count :
- + vreg->corner_count;
- +
- + for (i = 0; i < sdelta_table_count; i++) {
- + sdelta = devm_kzalloc(ctrl->dev, sizeof(*corner->sdelta),
- + GFP_KERNEL);
- + if (!sdelta) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + if (allow_core_count_adj)
- + sdelta->allow_core_count_adj = allow_core_count_adj[i];
- + if (allow_temp_adj)
- + sdelta->allow_temp_adj = allow_temp_adj[i];
- + sdelta->max_core_count = vreg->max_core_count;
- + sdelta->temp_band_count = ctrl->temp_band_count;
- +
- + if (use_corner_band)
- + vreg->corner_band[i].sdelta = sdelta;
- + else
- + vreg->corner[i].sdelta = sdelta;
- +
- + rc = cpr4_load_core_and_temp_adj(vreg, i, use_corner_band);
- + if (rc) {
- + cpr3_err(vreg, "corner/band %d core and temp adjustment loading failed, rc=%d\n",
- + i, rc);
- + goto done;
- + }
- + }
- +
- +done:
- + kfree(allow_core_count_adj);
- + kfree(allow_temp_adj);
- +
- + return rc;
- +}
- +
- +/**
- + * cprh_adjust_voltages_for_apm() - adjust per-corner floor and ceiling voltages
- + * so that they do not overlap the APM threshold voltage.
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * The memory array power mux (APM) must be configured for a specific supply
- + * based upon where the VDD voltage lies with respect to the APM threshold
- + * voltage. When using CPR hardware closed-loop, the voltage may vary anywhere
- + * between the floor and ceiling voltage without software notification.
- + * Therefore, it is required that the floor to ceiling range for every corner
- + * not intersect the APM threshold voltage. This function adjusts the floor to
- + * ceiling range for each corner which violates this requirement.
- + *
- + * The following algorithm is applied:
- + * if floor < threshold <= ceiling:
- + * if open_loop >= threshold, then floor = threshold - adj
- + * else ceiling = threshold - step
- + * where:
- + * adj = APM hysteresis voltage established to minimize the number of
- + * corners with artificially increased floor voltages
- + * step = voltage in microvolts of a single step of the VDD supply
- + *
- + * The open-loop voltage is also bounded by the new floor or ceiling value as
- + * needed.
- + *
- + * Return: none
- + */
- +void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + struct cpr3_corner *corner;
- + int i, adj, threshold, prev_ceiling, prev_floor, prev_open_loop;
- +
- + if (!ctrl->apm_threshold_volt) {
- + /* APM not being used. */
- + return;
- + }
- +
- + ctrl->apm_threshold_volt = CPR3_ROUND(ctrl->apm_threshold_volt,
- + ctrl->step_volt);
- + ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
- +
- + threshold = ctrl->apm_threshold_volt;
- + adj = ctrl->apm_adj_volt;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + corner = &vreg->corner[i];
- +
- + if (threshold <= corner->floor_volt
- + || threshold > corner->ceiling_volt)
- + continue;
- +
- + prev_floor = corner->floor_volt;
- + prev_ceiling = corner->ceiling_volt;
- + prev_open_loop = corner->open_loop_volt;
- +
- + if (corner->open_loop_volt >= threshold) {
- + corner->floor_volt = max(corner->floor_volt,
- + threshold - adj);
- + if (corner->open_loop_volt < corner->floor_volt)
- + corner->open_loop_volt = corner->floor_volt;
- + } else {
- + corner->ceiling_volt = threshold - ctrl->step_volt;
- + }
- +
- + if (corner->floor_volt != prev_floor
- + || corner->ceiling_volt != prev_ceiling
- + || corner->open_loop_volt != prev_open_loop)
- + cpr3_debug(vreg, "APM threshold=%d, APM adj=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
- + threshold, adj, i, prev_floor, prev_ceiling,
- + prev_open_loop, corner->floor_volt,
- + corner->ceiling_volt, corner->open_loop_volt);
- + }
- +}
- +
- +/**
- + * cprh_adjust_voltages_for_mem_acc() - adjust per-corner floor and ceiling
- + * voltages so that they do not intersect the MEM ACC threshold
- + * voltage
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * The following algorithm is applied:
- + * if floor < threshold <= ceiling:
- + * if open_loop >= threshold, then floor = threshold
- + * else ceiling = threshold - step
- + * where:
- + * step = voltage in microvolts of a single step of the VDD supply
- + *
- + * The open-loop voltage is also bounded by the new floor or ceiling value as
- + * needed.
- + *
- + * Return: none
- + */
- +void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + struct cpr3_corner *corner;
- + int i, threshold, prev_ceiling, prev_floor, prev_open_loop;
- +
- + if (!ctrl->mem_acc_threshold_volt) {
- + /* MEM ACC not being used. */
- + return;
- + }
- +
- + ctrl->mem_acc_threshold_volt = CPR3_ROUND(ctrl->mem_acc_threshold_volt,
- + ctrl->step_volt);
- +
- + threshold = ctrl->mem_acc_threshold_volt;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + corner = &vreg->corner[i];
- +
- + if (threshold <= corner->floor_volt
- + || threshold > corner->ceiling_volt)
- + continue;
- +
- + prev_floor = corner->floor_volt;
- + prev_ceiling = corner->ceiling_volt;
- + prev_open_loop = corner->open_loop_volt;
- +
- + if (corner->open_loop_volt >= threshold) {
- + corner->floor_volt = max(corner->floor_volt, threshold);
- + if (corner->open_loop_volt < corner->floor_volt)
- + corner->open_loop_volt = corner->floor_volt;
- + } else {
- + corner->ceiling_volt = threshold - ctrl->step_volt;
- + }
- +
- + if (corner->floor_volt != prev_floor
- + || corner->ceiling_volt != prev_ceiling
- + || corner->open_loop_volt != prev_open_loop)
- + cpr3_debug(vreg, "MEM ACC threshold=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
- + threshold, i, prev_floor, prev_ceiling,
- + prev_open_loop, corner->floor_volt,
- + corner->ceiling_volt, corner->open_loop_volt);
- + }
- +}
- +
- +/**
- + * cpr3_apply_closed_loop_offset_voltages() - modify the closed-loop voltage
- + * adjustments by the amounts that are needed for this
- + * fuse combo
- + * @vreg: Pointer to the CPR3 regulator
- + * @volt_adjust: Array of closed-loop voltage adjustment values of length
- + * vreg->corner_count which is further adjusted based upon
- + * offset voltage fuse values.
- + * @fuse_volt_adjust: Fused closed-loop voltage adjustment values of length
- + * vreg->fuse_corner_count.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr3_apply_closed_loop_offset_voltages(struct cpr3_regulator *vreg,
- + int *volt_adjust, int *fuse_volt_adjust)
- +{
- + u32 *corner_map;
- + int rc = 0, i;
- +
- + if (!of_find_property(vreg->of_node,
- + "qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL)) {
- + /* No closed-loop offset required. */
- + return 0;
- + }
- +
- + corner_map = kcalloc(vreg->corner_count, sizeof(*corner_map),
- + GFP_KERNEL);
- + if (!corner_map)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,cpr-fused-closed-loop-voltage-adjustment-map",
- + 1, corner_map);
- + if (rc)
- + goto done;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + if (corner_map[i] == 0) {
- + continue;
- + } else if (corner_map[i] > vreg->fuse_corner_count) {
- + cpr3_err(vreg, "corner %d mapped to invalid fuse corner: %u\n",
- + i, corner_map[i]);
- + rc = -EINVAL;
- + goto done;
- + }
- +
- + volt_adjust[i] += fuse_volt_adjust[corner_map[i] - 1];
- + }
- +
- +done:
- + kfree(corner_map);
- + return rc;
- +}
- +
- +/**
- + * cpr3_enforce_inc_quotient_monotonicity() - Ensure that target quotients
- + * increase monotonically from lower to higher corners
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static void cpr3_enforce_inc_quotient_monotonicity(struct cpr3_regulator *vreg)
- +{
- + int i, j;
- +
- + for (i = 1; i < vreg->corner_count; i++) {
- + for (j = 0; j < CPR3_RO_COUNT; j++) {
- + if (vreg->corner[i].target_quot[j]
- + && vreg->corner[i].target_quot[j]
- + < vreg->corner[i - 1].target_quot[j]) {
- + cpr3_debug(vreg, "corner %d RO%u target quot=%u < corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
- + i, j,
- + vreg->corner[i].target_quot[j],
- + i - 1, j,
- + vreg->corner[i - 1].target_quot[j],
- + i, j,
- + vreg->corner[i - 1].target_quot[j]);
- + vreg->corner[i].target_quot[j]
- + = vreg->corner[i - 1].target_quot[j];
- + }
- + }
- + }
- +}
- +
- +/**
- + * cpr3_enforce_dec_quotient_monotonicity() - Ensure that target quotients
- + * decrease monotonically from higher to lower corners
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static void cpr3_enforce_dec_quotient_monotonicity(struct cpr3_regulator *vreg)
- +{
- + int i, j;
- +
- + for (i = vreg->corner_count - 2; i >= 0; i--) {
- + for (j = 0; j < CPR3_RO_COUNT; j++) {
- + if (vreg->corner[i + 1].target_quot[j]
- + && vreg->corner[i].target_quot[j]
- + > vreg->corner[i + 1].target_quot[j]) {
- + cpr3_debug(vreg, "corner %d RO%u target quot=%u > corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
- + i, j,
- + vreg->corner[i].target_quot[j],
- + i + 1, j,
- + vreg->corner[i + 1].target_quot[j],
- + i, j,
- + vreg->corner[i + 1].target_quot[j]);
- + vreg->corner[i].target_quot[j]
- + = vreg->corner[i + 1].target_quot[j];
- + }
- + }
- + }
- +}
- +
- +/**
- + * _cpr3_adjust_target_quotients() - adjust the target quotients for each
- + * corner of the regulator according to input adjustment and
- + * scaling arrays
- + * @vreg: Pointer to the CPR3 regulator
- + * @volt_adjust: Pointer to an array of closed-loop voltage adjustments
- + * with units of microvolts. The array must have
- + * vreg->corner_count number of elements.
- + * @ro_scale: Pointer to a flattened 2D array of RO scaling factors.
- + * The array must have an inner dimension of CPR3_RO_COUNT
- + * and an outer dimension of vreg->corner_count
- + * @label: Null terminated string providing a label for the type
- + * of adjustment.
- + *
- + * Return: true if any corners received a positive voltage adjustment (> 0),
- + * else false
- + */
- +static bool _cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
- + const int *volt_adjust, const int *ro_scale, const char *label)
- +{
- + int i, j, quot_adjust;
- + bool is_increasing = false;
- + u32 prev_quot;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + for (j = 0; j < CPR3_RO_COUNT; j++) {
- + if (vreg->corner[i].target_quot[j]) {
- + quot_adjust = cpr3_quot_adjustment(
- + ro_scale[i * CPR3_RO_COUNT + j],
- + volt_adjust[i]);
- + if (quot_adjust) {
- + prev_quot = vreg->corner[i].
- + target_quot[j];
- + vreg->corner[i].target_quot[j]
- + += quot_adjust;
- + cpr3_debug(vreg, "adjusted corner %d RO%d target quot %s: %u --> %u (%d uV)\n",
- + i, j, label, prev_quot,
- + vreg->corner[i].target_quot[j],
- + volt_adjust[i]);
- + }
- + }
- + }
- + if (volt_adjust[i] > 0)
- + is_increasing = true;
- + }
- +
- + return is_increasing;
- +}
- +
- +/**
- + * cpr3_adjust_target_quotients() - adjust the target quotients for each
- + * corner according to device tree values and fuse values
- + * @vreg: Pointer to the CPR3 regulator
- + * @fuse_volt_adjust: Fused closed-loop voltage adjustment values of length
- + * vreg->fuse_corner_count. This parameter could be null
- + * pointer when no fused adjustments are needed.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
- + int *fuse_volt_adjust)
- +{
- + int i, rc;
- + int *volt_adjust, *ro_scale;
- + bool explicit_adjustment, fused_adjustment, is_increasing;
- +
- + explicit_adjustment = of_find_property(vreg->of_node,
- + "qcom,cpr-closed-loop-voltage-adjustment", NULL);
- + fused_adjustment = of_find_property(vreg->of_node,
- + "qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL);
- +
- + if (!explicit_adjustment && !fused_adjustment && !vreg->aging_allowed) {
- + /* No adjustment required. */
- + return 0;
- + } else if (!of_find_property(vreg->of_node,
- + "qcom,cpr-ro-scaling-factor", NULL)) {
- + cpr3_err(vreg, "qcom,cpr-ro-scaling-factor is required for closed-loop voltage adjustment, but is missing\n");
- + return -EINVAL;
- + }
- +
- + volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
- + GFP_KERNEL);
- + ro_scale = kcalloc(vreg->corner_count * CPR3_RO_COUNT,
- + sizeof(*ro_scale), GFP_KERNEL);
- + if (!volt_adjust || !ro_scale) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,cpr-ro-scaling-factor", CPR3_RO_COUNT, ro_scale);
- + if (rc) {
- + cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->corner_count; i++)
- + memcpy(vreg->corner[i].ro_scale, &ro_scale[i * CPR3_RO_COUNT],
- + sizeof(*ro_scale) * CPR3_RO_COUNT);
- +
- + if (explicit_adjustment) {
- + rc = cpr3_parse_corner_array_property(vreg,
- + "qcom,cpr-closed-loop-voltage-adjustment",
- + 1, volt_adjust);
- + if (rc) {
- + cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + _cpr3_adjust_target_quotients(vreg, volt_adjust, ro_scale,
- + "from DT");
- + cpr3_enforce_inc_quotient_monotonicity(vreg);
- + }
- +
- + if (fused_adjustment && fuse_volt_adjust) {
- + memset(volt_adjust, 0,
- + sizeof(*volt_adjust) * vreg->corner_count);
- +
- + rc = cpr3_apply_closed_loop_offset_voltages(vreg, volt_adjust,
- + fuse_volt_adjust);
- + if (rc) {
- + cpr3_err(vreg, "could not apply fused closed-loop voltage reductions, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + is_increasing = _cpr3_adjust_target_quotients(vreg, volt_adjust,
- + ro_scale, "from fuse");
- + if (is_increasing)
- + cpr3_enforce_inc_quotient_monotonicity(vreg);
- + else
- + cpr3_enforce_dec_quotient_monotonicity(vreg);
- + }
- +
- +done:
- + kfree(volt_adjust);
- + kfree(ro_scale);
- + return rc;
- +}
- --- /dev/null
- +++ b/drivers/regulator/cpr4-apss-regulator.c
- @@ -0,0 +1,1819 @@
- +/*
- + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 as published by the Free Software Foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + */
- +
- +#define pr_fmt(fmt) "%s: " fmt, __func__
- +
- +#include <linux/bitops.h>
- +#include <linux/debugfs.h>
- +#include <linux/err.h>
- +#include <linux/init.h>
- +#include <linux/interrupt.h>
- +#include <linux/io.h>
- +#include <linux/kernel.h>
- +#include <linux/list.h>
- +#include <linux/module.h>
- +#include <linux/of.h>
- +#include <linux/of_device.h>
- +#include <linux/platform_device.h>
- +#include <linux/pm_opp.h>
- +#include <linux/slab.h>
- +#include <linux/string.h>
- +#include <linux/uaccess.h>
- +#include <linux/regulator/driver.h>
- +#include <linux/regulator/machine.h>
- +#include <linux/regulator/of_regulator.h>
- +
- +#include "cpr3-regulator.h"
- +
- +#define IPQ807x_APSS_FUSE_CORNERS 4
- +#define IPQ817x_APPS_FUSE_CORNERS 2
- +#define IPQ6018_APSS_FUSE_CORNERS 4
- +#define IPQ9574_APSS_FUSE_CORNERS 4
- +
- +u32 g_valid_fuse_count = IPQ807x_APSS_FUSE_CORNERS;
- +
- +/**
- + * struct cpr4_ipq807x_apss_fuses - APSS specific fuse data for IPQ807x
- + * @ro_sel: Ring oscillator select fuse parameter value for each
- + * fuse corner
- + * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
- + * for each fuse corner (raw, not converted to a voltage)
- + * @target_quot: CPR target quotient fuse parameter value for each fuse
- + * corner
- + * @quot_offset: CPR target quotient offset fuse parameter value for each
- + * fuse corner (raw, not unpacked) used for target quotient
- + * interpolation
- + * @speed_bin: Application processor speed bin fuse parameter value for
- + * the given chip
- + * @cpr_fusing_rev: CPR fusing revision fuse parameter value
- + * @boost_cfg: CPR boost configuration fuse parameter value
- + * @boost_voltage: CPR boost voltage fuse parameter value (raw, not
- + * converted to a voltage)
- + *
- + * This struct holds the values for all of the fuses read from memory.
- + */
- +struct cpr4_ipq807x_apss_fuses {
- + u64 ro_sel[IPQ807x_APSS_FUSE_CORNERS];
- + u64 init_voltage[IPQ807x_APSS_FUSE_CORNERS];
- + u64 target_quot[IPQ807x_APSS_FUSE_CORNERS];
- + u64 quot_offset[IPQ807x_APSS_FUSE_CORNERS];
- + u64 speed_bin;
- + u64 cpr_fusing_rev;
- + u64 boost_cfg;
- + u64 boost_voltage;
- + u64 misc;
- +};
- +
- +/*
- + * fuse combo = fusing revision + 8 * (speed bin)
- + * where: fusing revision = 0 - 7 and speed bin = 0 - 7
- + */
- +#define CPR4_IPQ807x_APSS_FUSE_COMBO_COUNT 64
- +
- +/*
- + * Constants which define the name of each fuse corner.
- + */
- +enum cpr4_ipq807x_apss_fuse_corner {
- + CPR4_IPQ807x_APSS_FUSE_CORNER_SVS = 0,
- + CPR4_IPQ807x_APSS_FUSE_CORNER_NOM = 1,
- + CPR4_IPQ807x_APSS_FUSE_CORNER_TURBO = 2,
- + CPR4_IPQ807x_APSS_FUSE_CORNER_STURBO = 3,
- +};
- +
- +static const char * const cpr4_ipq807x_apss_fuse_corner_name[] = {
- + [CPR4_IPQ807x_APSS_FUSE_CORNER_SVS] = "SVS",
- + [CPR4_IPQ807x_APSS_FUSE_CORNER_NOM] = "NOM",
- + [CPR4_IPQ807x_APSS_FUSE_CORNER_TURBO] = "TURBO",
- + [CPR4_IPQ807x_APSS_FUSE_CORNER_STURBO] = "STURBO",
- +};
- +
- +/*
- + * IPQ807x APSS fuse parameter locations:
- + *
- + * Structs are organized with the following dimensions:
- + * Outer: 0 to 3 for fuse corners from lowest to highest corner
- + * Inner: large enough to hold the longest set of parameter segments which
- + * fully defines a fuse parameter, +1 (for NULL termination).
- + * Each segment corresponds to a contiguous group of bits from a
- + * single fuse row. These segments are concatentated together in
- + * order to form the full fuse parameter value. The segments for
- + * a given parameter may correspond to different fuse rows.
- + */
- +static struct cpr3_fuse_param
- +ipq807x_apss_ro_sel_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
- + {{73, 8, 11}, {} },
- + {{73, 4, 7}, {} },
- + {{73, 0, 3}, {} },
- + {{73, 12, 15}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq807x_apss_init_voltage_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
- + {{71, 18, 23}, {} },
- + {{71, 12, 17}, {} },
- + {{71, 6, 11}, {} },
- + {{71, 0, 5}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq807x_apss_target_quot_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
- + {{72, 32, 43}, {} },
- + {{72, 20, 31}, {} },
- + {{72, 8, 19}, {} },
- + {{72, 44, 55}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq807x_apss_quot_offset_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
- + {{} },
- + {{71, 46, 52}, {} },
- + {{71, 39, 45}, {} },
- + {{71, 32, 38}, {} },
- +};
- +
- +static struct cpr3_fuse_param ipq807x_cpr_fusing_rev_param[] = {
- + {71, 53, 55},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq807x_apss_speed_bin_param[] = {
- + {36, 40, 42},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq807x_cpr_boost_fuse_cfg_param[] = {
- + {36, 43, 45},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq807x_apss_boost_fuse_volt_param[] = {
- + {71, 0, 5},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq807x_misc_fuse_volt_adj_param[] = {
- + {36, 54, 54},
- + {},
- +};
- +
- +static struct cpr3_fuse_parameters ipq807x_fuse_params = {
- + .apss_ro_sel_param = ipq807x_apss_ro_sel_param,
- + .apss_init_voltage_param = ipq807x_apss_init_voltage_param,
- + .apss_target_quot_param = ipq807x_apss_target_quot_param,
- + .apss_quot_offset_param = ipq807x_apss_quot_offset_param,
- + .cpr_fusing_rev_param = ipq807x_cpr_fusing_rev_param,
- + .apss_speed_bin_param = ipq807x_apss_speed_bin_param,
- + .cpr_boost_fuse_cfg_param = ipq807x_cpr_boost_fuse_cfg_param,
- + .apss_boost_fuse_volt_param = ipq807x_apss_boost_fuse_volt_param,
- + .misc_fuse_volt_adj_param = ipq807x_misc_fuse_volt_adj_param
- +};
- +
- +/*
- + * The number of possible values for misc fuse is
- + * 2^(#bits defined for misc fuse)
- + */
- +#define IPQ807x_MISC_FUSE_VAL_COUNT BIT(1)
- +
- +/*
- + * Open loop voltage fuse reference voltages in microvolts for IPQ807x
- + */
- +static int ipq807x_apss_fuse_ref_volt
- + [IPQ807x_APSS_FUSE_CORNERS] = {
- + 720000,
- + 864000,
- + 992000,
- + 1064000,
- +};
- +
- +#define IPQ807x_APSS_FUSE_STEP_VOLT 8000
- +#define IPQ807x_APSS_VOLTAGE_FUSE_SIZE 6
- +#define IPQ807x_APSS_QUOT_OFFSET_SCALE 5
- +
- +#define IPQ807x_APSS_CPR_SENSOR_COUNT 6
- +
- +#define IPQ807x_APSS_CPR_CLOCK_RATE 19200000
- +
- +#define IPQ807x_APSS_MAX_TEMP_POINTS 3
- +#define IPQ807x_APSS_TEMP_SENSOR_ID_START 4
- +#define IPQ807x_APSS_TEMP_SENSOR_ID_END 13
- +/*
- + * Boost voltage fuse reference and ceiling voltages in microvolts for
- + * IPQ807x.
- + */
- +#define IPQ807x_APSS_BOOST_FUSE_REF_VOLT 1140000
- +#define IPQ807x_APSS_BOOST_CEILING_VOLT 1140000
- +#define IPQ807x_APSS_BOOST_FLOOR_VOLT 900000
- +#define MAX_BOOST_CONFIG_FUSE_VALUE 8
- +
- +#define IPQ807x_APSS_CPR_SDELTA_CORE_COUNT 15
- +
- +#define IPQ807x_APSS_CPR_TCSR_START 8
- +#define IPQ807x_APSS_CPR_TCSR_END 9
- +
- +/*
- + * Array of integer values mapped to each of the boost config fuse values to
- + * indicate boost enable/disable status.
- + */
- +static bool boost_fuse[MAX_BOOST_CONFIG_FUSE_VALUE] = {0, 1, 1, 1, 1, 1, 1, 1};
- +
- +/*
- + * IPQ6018 (Few parameters are changed, remaining are same as IPQ807x)
- + */
- +#define IPQ6018_APSS_FUSE_STEP_VOLT 12500
- +#define IPQ6018_APSS_CPR_CLOCK_RATE 24000000
- +
- +static struct cpr3_fuse_param
- +ipq6018_apss_ro_sel_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
- + {{75, 8, 11}, {} },
- + {{75, 4, 7}, {} },
- + {{75, 0, 3}, {} },
- + {{75, 12, 15}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq6018_apss_init_voltage_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
- + {{73, 18, 23}, {} },
- + {{73, 12, 17}, {} },
- + {{73, 6, 11}, {} },
- + {{73, 0, 5}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq6018_apss_target_quot_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
- + {{74, 32, 43}, {} },
- + {{74, 20, 31}, {} },
- + {{74, 8, 19}, {} },
- + {{74, 44, 55}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq6018_apss_quot_offset_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
- + {{} },
- + {{73, 48, 55}, {} },
- + {{73, 40, 47}, {} },
- + {{73, 32, 39}, {} },
- +};
- +
- +static struct cpr3_fuse_param ipq6018_cpr_fusing_rev_param[] = {
- + {75, 16, 18},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq6018_apss_speed_bin_param[] = {
- + {36, 40, 42},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq6018_cpr_boost_fuse_cfg_param[] = {
- + {36, 43, 45},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq6018_apss_boost_fuse_volt_param[] = {
- + {73, 0, 5},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq6018_misc_fuse_volt_adj_param[] = {
- + {36, 54, 54},
- + {},
- +};
- +
- +static struct cpr3_fuse_parameters ipq6018_fuse_params = {
- + .apss_ro_sel_param = ipq6018_apss_ro_sel_param,
- + .apss_init_voltage_param = ipq6018_apss_init_voltage_param,
- + .apss_target_quot_param = ipq6018_apss_target_quot_param,
- + .apss_quot_offset_param = ipq6018_apss_quot_offset_param,
- + .cpr_fusing_rev_param = ipq6018_cpr_fusing_rev_param,
- + .apss_speed_bin_param = ipq6018_apss_speed_bin_param,
- + .cpr_boost_fuse_cfg_param = ipq6018_cpr_boost_fuse_cfg_param,
- + .apss_boost_fuse_volt_param = ipq6018_apss_boost_fuse_volt_param,
- + .misc_fuse_volt_adj_param = ipq6018_misc_fuse_volt_adj_param
- +};
- +
- +
- +/*
- + * Boost voltage fuse reference and ceiling voltages in microvolts for
- + * IPQ6018.
- + */
- +#define IPQ6018_APSS_BOOST_FUSE_REF_VOLT 1140000
- +#define IPQ6018_APSS_BOOST_CEILING_VOLT 1140000
- +#define IPQ6018_APSS_BOOST_FLOOR_VOLT 900000
- +
- +/*
- + * Open loop voltage fuse reference voltages in microvolts for IPQ807x
- + */
- +static int ipq6018_apss_fuse_ref_volt
- + [IPQ6018_APSS_FUSE_CORNERS] = {
- + 725000,
- + 862500,
- + 987500,
- + 1062500,
- +};
- +
- +/*
- + * IPQ6018 Memory ACC settings on TCSR
- + *
- + * Turbo_L1: write TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x10
- + * write TCSR_CUSTOM_VDDAPC0_ACC_1 0x1
- + * Other modes: write TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x0
- + * write TCSR_CUSTOM_VDDAPC0_ACC_1 0x0
- + *
- + */
- +#define IPQ6018_APSS_MEM_ACC_TCSR_COUNT 2
- +#define TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x1946178
- +#define TCSR_CUSTOM_VDDAPC0_ACC_1 0x1946124
- +
- +struct mem_acc_tcsr {
- + u32 phy_addr;
- + void __iomem *ioremap_addr;
- + u32 value;
- +};
- +
- +static struct mem_acc_tcsr ipq6018_mem_acc_tcsr[IPQ6018_APSS_MEM_ACC_TCSR_COUNT] = {
- + {TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0, NULL, 0x10},
- + {TCSR_CUSTOM_VDDAPC0_ACC_1, NULL, 0x1},
- +};
- +
- +/*
- + * IPQ9574 (Few parameters are changed, remaining are same as IPQ6018)
- + */
- +#define IPQ9574_APSS_FUSE_STEP_VOLT 10000
- +
- +static struct cpr3_fuse_param
- +ipq9574_apss_ro_sel_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
- + {{107, 4, 7}, {} },
- + {{107, 0, 3}, {} },
- + {{106, 4, 7}, {} },
- + {{106, 0, 3}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq9574_apss_init_voltage_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
- + {{104, 24, 29}, {} },
- + {{104, 18, 23}, {} },
- + {{104, 12, 17}, {} },
- + {{104, 6, 11}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq9574_apss_target_quot_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
- + {{106, 32, 43}, {} },
- + {{106, 20, 31}, {} },
- + {{106, 8, 19}, {} },
- + {{106, 44, 55}, {} },
- +};
- +
- +static struct cpr3_fuse_param
- +ipq9574_apss_quot_offset_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
- + {{} },
- + {{105, 48, 55}, {} },
- + {{105, 40, 47}, {} },
- + {{105, 32, 39}, {} },
- +};
- +
- +static struct cpr3_fuse_param ipq9574_cpr_fusing_rev_param[] = {
- + {107, 8, 10},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq9574_apss_speed_bin_param[] = {
- + {0, 40, 42},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq9574_cpr_boost_fuse_cfg_param[] = {
- + {0, 43, 45},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq9574_apss_boost_fuse_volt_param[] = {
- + {104, 0, 5},
- + {},
- +};
- +
- +static struct cpr3_fuse_param ipq9574_misc_fuse_volt_adj_param[] = {
- + {0, 54, 54},
- + {},
- +};
- +
- +static struct cpr3_fuse_parameters ipq9574_fuse_params = {
- + .apss_ro_sel_param = ipq9574_apss_ro_sel_param,
- + .apss_init_voltage_param = ipq9574_apss_init_voltage_param,
- + .apss_target_quot_param = ipq9574_apss_target_quot_param,
- + .apss_quot_offset_param = ipq9574_apss_quot_offset_param,
- + .cpr_fusing_rev_param = ipq9574_cpr_fusing_rev_param,
- + .apss_speed_bin_param = ipq9574_apss_speed_bin_param,
- + .cpr_boost_fuse_cfg_param = ipq9574_cpr_boost_fuse_cfg_param,
- + .apss_boost_fuse_volt_param = ipq9574_apss_boost_fuse_volt_param,
- + .misc_fuse_volt_adj_param = ipq9574_misc_fuse_volt_adj_param
- +};
- +
- +/*
- + * Open loop voltage fuse reference voltages in microvolts for IPQ9574
- + */
- +static int ipq9574_apss_fuse_ref_volt
- + [IPQ9574_APSS_FUSE_CORNERS] = {
- + 725000,
- + 862500,
- + 987500,
- + 1062500,
- +};
- +
- +/**
- + * cpr4_ipq807x_apss_read_fuse_data() - load APSS specific fuse parameter values
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * This function allocates a cpr4_ipq807x_apss_fuses struct, fills it with
- + * values read out of hardware fuses, and finally copies common fuse values
- + * into the CPR3 regulator struct.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_ipq807x_apss_read_fuse_data(struct cpr3_regulator *vreg)
- +{
- + void __iomem *base = vreg->thread->ctrl->fuse_base;
- + struct cpr4_ipq807x_apss_fuses *fuse;
- + int i, rc;
- +
- + fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
- + if (!fuse)
- + return -ENOMEM;
- +
- + rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->apss_speed_bin_param,
- + &fuse->speed_bin);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read speed bin fuse, rc=%d\n", rc);
- + return rc;
- + }
- + cpr3_info(vreg, "speed bin = %llu\n", fuse->speed_bin);
- +
- + rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->cpr_fusing_rev_param,
- + &fuse->cpr_fusing_rev);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read CPR fusing revision fuse, rc=%d\n",
- + rc);
- + return rc;
- + }
- + cpr3_info(vreg, "CPR fusing revision = %llu\n", fuse->cpr_fusing_rev);
- +
- + rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->misc_fuse_volt_adj_param,
- + &fuse->misc);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read misc voltage adjustment fuse, rc=%d\n",
- + rc);
- + return rc;
- + }
- + cpr3_info(vreg, "CPR misc fuse value = %llu\n", fuse->misc);
- + if (fuse->misc >= IPQ807x_MISC_FUSE_VAL_COUNT) {
- + cpr3_err(vreg, "CPR misc fuse value = %llu, should be < %lu\n",
- + fuse->misc, IPQ807x_MISC_FUSE_VAL_COUNT);
- + return -EINVAL;
- + }
- +
- + for (i = 0; i < g_valid_fuse_count; i++) {
- + rc = cpr3_read_fuse_param(base,
- + vreg->cpr4_regulator_data->cpr3_fuse_params->apss_init_voltage_param[i],
- + &fuse->init_voltage[i]);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
- + i, rc);
- + return rc;
- + }
- +
- + rc = cpr3_read_fuse_param(base,
- + vreg->cpr4_regulator_data->cpr3_fuse_params->apss_target_quot_param[i],
- + &fuse->target_quot[i]);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read fuse-corner %d target quotient fuse, rc=%d\n",
- + i, rc);
- + return rc;
- + }
- +
- + rc = cpr3_read_fuse_param(base,
- + vreg->cpr4_regulator_data->cpr3_fuse_params->apss_ro_sel_param[i],
- + &fuse->ro_sel[i]);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read fuse-corner %d RO select fuse, rc=%d\n",
- + i, rc);
- + return rc;
- + }
- +
- + rc = cpr3_read_fuse_param(base,
- + vreg->cpr4_regulator_data->cpr3_fuse_params->apss_quot_offset_param[i],
- + &fuse->quot_offset[i]);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read fuse-corner %d quotient offset fuse, rc=%d\n",
- + i, rc);
- + return rc;
- + }
- + }
- +
- + rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->cpr_boost_fuse_cfg_param,
- + &fuse->boost_cfg);
- + if (rc) {
- + cpr3_err(vreg, "Unable to read CPR boost config fuse, rc=%d\n",
- + rc);
- + return rc;
- + }
- + cpr3_info(vreg, "Voltage boost fuse config = %llu boost = %s\n",
- + fuse->boost_cfg, boost_fuse[fuse->boost_cfg]
- + ? "enable" : "disable");
- +
- + rc = cpr3_read_fuse_param(base,
- + vreg->cpr4_regulator_data->cpr3_fuse_params->apss_boost_fuse_volt_param,
- + &fuse->boost_voltage);
- + if (rc) {
- + cpr3_err(vreg, "failed to read boost fuse voltage, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
- + if (vreg->fuse_combo >= CPR4_IPQ807x_APSS_FUSE_COMBO_COUNT) {
- + cpr3_err(vreg, "invalid CPR fuse combo = %d found\n",
- + vreg->fuse_combo);
- + return -EINVAL;
- + }
- +
- + vreg->speed_bin_fuse = fuse->speed_bin;
- + vreg->cpr_rev_fuse = fuse->cpr_fusing_rev;
- + vreg->fuse_corner_count = g_valid_fuse_count;
- + vreg->platform_fuses = fuse;
- +
- + return 0;
- +}
- +
- +/**
- + * cpr4_apss_parse_corner_data() - parse APSS corner data from device tree
- + * properties of the CPR3 regulator's device node
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_parse_corner_data(struct cpr3_regulator *vreg)
- +{
- + struct device_node *node = vreg->of_node;
- + struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
- + u32 *temp = NULL;
- + int i, rc;
- +
- + rc = cpr3_parse_common_corner_data(vreg);
- + if (rc) {
- + cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
- + return rc;
- + }
- +
- + /* If fuse has incorrect RO Select values and dtsi has "qcom,cpr-ro-sel"
- + * entry with RO select values other than zero, then dtsi values will
- + * be used.
- + */
- + if (of_find_property(node, "qcom,cpr-ro-sel", NULL)) {
- + temp = kcalloc(vreg->fuse_corner_count, sizeof(*temp),
- + GFP_KERNEL);
- + if (!temp)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_array_property(vreg, "qcom,cpr-ro-sel",
- + vreg->fuse_corner_count, temp);
- + if (rc)
- + goto done;
- +
- + for (i = 0; i < vreg->fuse_corner_count; i++) {
- + if (temp[i] != 0)
- + fuse->ro_sel[i] = temp[i];
- + }
- + }
- +done:
- + kfree(temp);
- + return rc;
- +}
- +
- +/**
- + * cpr4_apss_parse_misc_fuse_voltage_adjustments() - fill an array from a
- + * portion of the voltage adjustments specified based on
- + * miscellaneous fuse bits.
- + * @vreg: Pointer to the CPR3 regulator
- + * @volt_adjust: Voltage adjustment output data array which must be
- + * of size vreg->corner_count
- + *
- + * cpr3_parse_common_corner_data() must be called for vreg before this function
- + * is called so that speed bin size elements are initialized.
- + *
- + * Two formats are supported for the device tree property:
- + * 1. Length == tuple_list_size * vreg->corner_count
- + * (reading begins at index 0)
- + * 2. Length == tuple_list_size * vreg->speed_bin_corner_sum
- + * (reading begins at index tuple_list_size * vreg->speed_bin_offset)
- + *
- + * Here, tuple_list_size is the number of possible values for misc fuse.
- + * All other property lengths are treated as errors.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_parse_misc_fuse_voltage_adjustments(
- + struct cpr3_regulator *vreg, u32 *volt_adjust)
- +{
- + struct device_node *node = vreg->of_node;
- + struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
- + int tuple_list_size = IPQ807x_MISC_FUSE_VAL_COUNT;
- + int i, offset, rc, len = 0;
- + const char *prop_name = "qcom,cpr-misc-fuse-voltage-adjustment";
- +
- + if (!of_find_property(node, prop_name, &len)) {
- + cpr3_err(vreg, "property %s is missing\n", prop_name);
- + return -EINVAL;
- + }
- +
- + if (len == tuple_list_size * vreg->corner_count * sizeof(u32)) {
- + offset = 0;
- + } else if (vreg->speed_bin_corner_sum > 0 &&
- + len == tuple_list_size * vreg->speed_bin_corner_sum
- + * sizeof(u32)) {
- + offset = tuple_list_size * vreg->speed_bin_offset
- + + fuse->misc * vreg->corner_count;
- + } else {
- + if (vreg->speed_bin_corner_sum > 0)
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
- + prop_name, len,
- + tuple_list_size * vreg->corner_count
- + * sizeof(u32),
- + tuple_list_size * vreg->speed_bin_corner_sum
- + * sizeof(u32));
- + else
- + cpr3_err(vreg, "property %s has invalid length=%d, should be %zu\n",
- + prop_name, len,
- + tuple_list_size * vreg->corner_count
- + * sizeof(u32));
- + return -EINVAL;
- + }
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + rc = of_property_read_u32_index(node, prop_name, offset + i,
- + &volt_adjust[i]);
- + if (rc) {
- + cpr3_err(vreg, "error reading property %s, rc=%d\n",
- + prop_name, rc);
- + return rc;
- + }
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr4_ipq807x_apss_calculate_open_loop_voltages() - calculate the open-loop
- + * voltage for each corner of a CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * If open-loop voltage interpolation is allowed in device tree, then
- + * this function calculates the open-loop voltage for a given corner using
- + * linear interpolation. This interpolation is performed using the processor
- + * frequencies of the lower and higher Fmax corners along with their fused
- + * open-loop voltages.
- + *
- + * If open-loop voltage interpolation is not allowed, then this function uses
- + * the Fmax fused open-loop voltage for all of the corners associated with a
- + * given fuse corner.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_ipq807x_apss_calculate_open_loop_voltages(
- + struct cpr3_regulator *vreg)
- +{
- + struct device_node *node = vreg->of_node;
- + struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + int i, j, rc = 0;
- + bool allow_interpolation;
- + u64 freq_low, volt_low, freq_high, volt_high;
- + int *fuse_volt, *misc_adj_volt;
- + int *fmax_corner;
- +
- + fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
- + GFP_KERNEL);
- + fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
- + GFP_KERNEL);
- + if (!fuse_volt || !fmax_corner) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->fuse_corner_count; i++) {
- + if (ctrl->cpr_global_setting == CPR_DISABLED)
- + fuse_volt[i] = vreg->cpr4_regulator_data->fuse_ref_volt[i];
- + else
- + fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
- + vreg->cpr4_regulator_data->fuse_ref_volt[i],
- + vreg->cpr4_regulator_data->fuse_step_volt,
- + fuse->init_voltage[i],
- + IPQ807x_APSS_VOLTAGE_FUSE_SIZE);
- +
- + /* Log fused open-loop voltage values for debugging purposes. */
- + cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
- + cpr4_ipq807x_apss_fuse_corner_name[i],
- + fuse_volt[i]);
- + }
- +
- + rc = cpr3_determine_part_type(vreg,
- + fuse_volt[vreg->fuse_corner_count - 1]);
- + if (rc) {
- + cpr3_err(vreg, "fused part type detection failed failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
- + if (rc) {
- + cpr3_err(vreg, "fused open-loop voltage adjustment failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + allow_interpolation = of_property_read_bool(node,
- + "qcom,allow-voltage-interpolation");
- +
- + for (i = 1; i < vreg->fuse_corner_count; i++) {
- + if (fuse_volt[i] < fuse_volt[i - 1]) {
- + cpr3_info(vreg, "fuse corner %d voltage=%d uV < fuse corner %d voltage=%d uV; overriding: fuse corner %d voltage=%d\n",
- + i, fuse_volt[i], i - 1, fuse_volt[i - 1],
- + i, fuse_volt[i - 1]);
- + fuse_volt[i] = fuse_volt[i - 1];
- + }
- + }
- +
- + if (!allow_interpolation) {
- + /* Use fused open-loop voltage for lower frequencies. */
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].open_loop_volt
- + = fuse_volt[vreg->corner[i].cpr_fuse_corner];
- + goto done;
- + }
- +
- + /* Determine highest corner mapped to each fuse corner */
- + j = vreg->fuse_corner_count - 1;
- + for (i = vreg->corner_count - 1; i >= 0; i--) {
- + if (vreg->corner[i].cpr_fuse_corner == j) {
- + fmax_corner[j] = i;
- + j--;
- + }
- + }
- + if (j >= 0) {
- + cpr3_err(vreg, "invalid fuse corner mapping\n");
- + rc = -EINVAL;
- + goto done;
- + }
- +
- + /*
- + * Interpolation is not possible for corners mapped to the lowest fuse
- + * corner so use the fuse corner value directly.
- + */
- + for (i = 0; i <= fmax_corner[0]; i++)
- + vreg->corner[i].open_loop_volt = fuse_volt[0];
- +
- + /* Interpolate voltages for the higher fuse corners. */
- + for (i = 1; i < vreg->fuse_corner_count; i++) {
- + freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
- + volt_low = fuse_volt[i - 1];
- + freq_high = vreg->corner[fmax_corner[i]].proc_freq;
- + volt_high = fuse_volt[i];
- +
- + for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
- + vreg->corner[j].open_loop_volt = cpr3_interpolate(
- + freq_low, volt_low, freq_high, volt_high,
- + vreg->corner[j].proc_freq);
- + }
- +
- +done:
- + if (rc == 0) {
- + cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
- + for (i = 0; i < vreg->corner_count; i++)
- + cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
- + vreg->corner[i].open_loop_volt);
- +
- + rc = cpr3_adjust_open_loop_voltages(vreg);
- + if (rc)
- + cpr3_err(vreg, "open-loop voltage adjustment failed, rc=%d\n",
- + rc);
- +
- + if (of_find_property(node,
- + "qcom,cpr-misc-fuse-voltage-adjustment",
- + NULL)) {
- + misc_adj_volt = kcalloc(vreg->corner_count,
- + sizeof(*misc_adj_volt), GFP_KERNEL);
- + if (!misc_adj_volt) {
- + rc = -ENOMEM;
- + goto _exit;
- + }
- +
- + rc = cpr4_apss_parse_misc_fuse_voltage_adjustments(vreg,
- + misc_adj_volt);
- + if (rc) {
- + cpr3_err(vreg, "qcom,cpr-misc-fuse-voltage-adjustment reading failed, rc=%d\n",
- + rc);
- + kfree(misc_adj_volt);
- + goto _exit;
- + }
- +
- + for (i = 0; i < vreg->corner_count; i++)
- + vreg->corner[i].open_loop_volt
- + += misc_adj_volt[i];
- + kfree(misc_adj_volt);
- + }
- + }
- +
- +_exit:
- + kfree(fuse_volt);
- + kfree(fmax_corner);
- + return rc;
- +}
- +
- +/**
- + * cpr4_ipq807x_apss_set_no_interpolation_quotients() - use the fused target
- + * quotient values for lower frequencies.
- + * @vreg: Pointer to the CPR3 regulator
- + * @volt_adjust: Pointer to array of per-corner closed-loop adjustment
- + * voltages
- + * @volt_adjust_fuse: Pointer to array of per-fuse-corner closed-loop
- + * adjustment voltages
- + * @ro_scale: Pointer to array of per-fuse-corner RO scaling factor
- + * values with units of QUOT/V
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_ipq807x_apss_set_no_interpolation_quotients(
- + struct cpr3_regulator *vreg, int *volt_adjust,
- + int *volt_adjust_fuse, int *ro_scale)
- +{
- + struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
- + u32 quot, ro;
- + int quot_adjust;
- + int i, fuse_corner;
- +
- + for (i = 0; i < vreg->corner_count; i++) {
- + fuse_corner = vreg->corner[i].cpr_fuse_corner;
- + quot = fuse->target_quot[fuse_corner];
- + quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
- + volt_adjust_fuse[fuse_corner] +
- + volt_adjust[i]);
- + ro = fuse->ro_sel[fuse_corner];
- + vreg->corner[i].target_quot[ro] = quot + quot_adjust;
- + cpr3_debug(vreg, "corner=%d RO=%u target quot=%u\n",
- + i, ro, quot);
- +
- + if (quot_adjust)
- + cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %u --> %u (%d uV)\n",
- + i, ro, quot, vreg->corner[i].target_quot[ro],
- + volt_adjust_fuse[fuse_corner] +
- + volt_adjust[i]);
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr4_ipq807x_apss_calculate_target_quotients() - calculate the CPR target
- + * quotient for each corner of a CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * If target quotient interpolation is allowed in device tree, then this
- + * function calculates the target quotient for a given corner using linear
- + * interpolation. This interpolation is performed using the processor
- + * frequencies of the lower and higher Fmax corners along with the fused
- + * target quotient and quotient offset of the higher Fmax corner.
- + *
- + * If target quotient interpolation is not allowed, then this function uses
- + * the Fmax fused target quotient for all of the corners associated with a
- + * given fuse corner.
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_ipq807x_apss_calculate_target_quotients(
- + struct cpr3_regulator *vreg)
- +{
- + struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
- + int rc;
- + bool allow_interpolation;
- + u64 freq_low, freq_high, prev_quot;
- + u64 *quot_low;
- + u64 *quot_high;
- + u32 quot, ro;
- + int i, j, fuse_corner, quot_adjust;
- + int *fmax_corner;
- + int *volt_adjust, *volt_adjust_fuse, *ro_scale;
- + int *voltage_adj_misc;
- +
- + /* Log fused quotient values for debugging purposes. */
- + for (i = CPR4_IPQ807x_APSS_FUSE_CORNER_SVS;
- + i < vreg->fuse_corner_count; i++)
- + cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
- + cpr4_ipq807x_apss_fuse_corner_name[i],
- + fuse->ro_sel[i], fuse->target_quot[i],
- + fuse->ro_sel[i], fuse->quot_offset[i] *
- + IPQ807x_APSS_QUOT_OFFSET_SCALE);
- +
- + allow_interpolation = of_property_read_bool(vreg->of_node,
- + "qcom,allow-quotient-interpolation");
- +
- + volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
- + GFP_KERNEL);
- + volt_adjust_fuse = kcalloc(vreg->fuse_corner_count,
- + sizeof(*volt_adjust_fuse), GFP_KERNEL);
- + ro_scale = kcalloc(vreg->fuse_corner_count, sizeof(*ro_scale),
- + GFP_KERNEL);
- + fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
- + GFP_KERNEL);
- + quot_low = kcalloc(vreg->fuse_corner_count, sizeof(*quot_low),
- + GFP_KERNEL);
- + quot_high = kcalloc(vreg->fuse_corner_count, sizeof(*quot_high),
- + GFP_KERNEL);
- + if (!volt_adjust || !volt_adjust_fuse || !ro_scale ||
- + !fmax_corner || !quot_low || !quot_high) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + rc = cpr3_parse_closed_loop_voltage_adjustments(vreg, &fuse->ro_sel[0],
- + volt_adjust, volt_adjust_fuse, ro_scale);
- + if (rc) {
- + cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
- + rc);
- + goto done;
- + }
- +
- + if (of_find_property(vreg->of_node,
- + "qcom,cpr-misc-fuse-voltage-adjustment", NULL)) {
- + voltage_adj_misc = kcalloc(vreg->corner_count,
- + sizeof(*voltage_adj_misc), GFP_KERNEL);
- + if (!voltage_adj_misc) {
- + rc = -ENOMEM;
- + goto done;
- + }
- +
- + rc = cpr4_apss_parse_misc_fuse_voltage_adjustments(vreg,
- + voltage_adj_misc);
- + if (rc) {
- + cpr3_err(vreg, "qcom,cpr-misc-fuse-voltage-adjustment reading failed, rc=%d\n",
- + rc);
- + kfree(voltage_adj_misc);
- + goto done;
- + }
- +
- + for (i = 0; i < vreg->corner_count; i++)
- + volt_adjust[i] += voltage_adj_misc[i];
- +
- + kfree(voltage_adj_misc);
- + }
- +
- + if (!allow_interpolation) {
- + /* Use fused target quotients for lower frequencies. */
- + return cpr4_ipq807x_apss_set_no_interpolation_quotients(
- + vreg, volt_adjust, volt_adjust_fuse, ro_scale);
- + }
- +
- + /* Determine highest corner mapped to each fuse corner */
- + j = vreg->fuse_corner_count - 1;
- + for (i = vreg->corner_count - 1; i >= 0; i--) {
- + if (vreg->corner[i].cpr_fuse_corner == j) {
- + fmax_corner[j] = i;
- + j--;
- + }
- + }
- + if (j >= 0) {
- + cpr3_err(vreg, "invalid fuse corner mapping\n");
- + rc = -EINVAL;
- + goto done;
- + }
- +
- + /*
- + * Interpolation is not possible for corners mapped to the lowest fuse
- + * corner so use the fuse corner value directly.
- + */
- + i = CPR4_IPQ807x_APSS_FUSE_CORNER_SVS;
- + quot_adjust = cpr3_quot_adjustment(ro_scale[i], volt_adjust_fuse[i]);
- + quot = fuse->target_quot[i] + quot_adjust;
- + quot_high[i] = quot_low[i] = quot;
- + ro = fuse->ro_sel[i];
- + if (quot_adjust)
- + cpr3_debug(vreg, "adjusted fuse corner %d RO%u target quot: %llu --> %u (%d uV)\n",
- + i, ro, fuse->target_quot[i], quot, volt_adjust_fuse[i]);
- +
- + for (i = 0; i <= fmax_corner[CPR4_IPQ807x_APSS_FUSE_CORNER_SVS];
- + i++)
- + vreg->corner[i].target_quot[ro] = quot;
- +
- + for (i = CPR4_IPQ807x_APSS_FUSE_CORNER_NOM;
- + i < vreg->fuse_corner_count; i++) {
- + quot_high[i] = fuse->target_quot[i];
- + if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
- + quot_low[i] = quot_high[i - 1];
- + else
- + quot_low[i] = quot_high[i]
- + - fuse->quot_offset[i]
- + * IPQ807x_APSS_QUOT_OFFSET_SCALE;
- + if (quot_high[i] < quot_low[i]) {
- + cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu; overriding: quot_high[%d]=%llu\n",
- + i, quot_high[i], i, quot_low[i],
- + i, quot_low[i]);
- + quot_high[i] = quot_low[i];
- + }
- + }
- +
- + /* Perform per-fuse-corner target quotient adjustment */
- + for (i = 1; i < vreg->fuse_corner_count; i++) {
- + quot_adjust = cpr3_quot_adjustment(ro_scale[i],
- + volt_adjust_fuse[i]);
- + if (quot_adjust) {
- + prev_quot = quot_high[i];
- + quot_high[i] += quot_adjust;
- + cpr3_debug(vreg, "adjusted fuse corner %d RO%llu target quot: %llu --> %llu (%d uV)\n",
- + i, fuse->ro_sel[i], prev_quot, quot_high[i],
- + volt_adjust_fuse[i]);
- + }
- +
- + if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
- + quot_low[i] = quot_high[i - 1];
- + else
- + quot_low[i] += cpr3_quot_adjustment(ro_scale[i],
- + volt_adjust_fuse[i - 1]);
- +
- + if (quot_high[i] < quot_low[i]) {
- + cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu after adjustment; overriding: quot_high[%d]=%llu\n",
- + i, quot_high[i], i, quot_low[i],
- + i, quot_low[i]);
- + quot_high[i] = quot_low[i];
- + }
- + }
- +
- + /* Interpolate voltages for the higher fuse corners. */
- + for (i = 1; i < vreg->fuse_corner_count; i++) {
- + freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
- + freq_high = vreg->corner[fmax_corner[i]].proc_freq;
- +
- + ro = fuse->ro_sel[i];
- + for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
- + vreg->corner[j].target_quot[ro] = cpr3_interpolate(
- + freq_low, quot_low[i], freq_high, quot_high[i],
- + vreg->corner[j].proc_freq);
- + }
- +
- + /* Perform per-corner target quotient adjustment */
- + for (i = 0; i < vreg->corner_count; i++) {
- + fuse_corner = vreg->corner[i].cpr_fuse_corner;
- + ro = fuse->ro_sel[fuse_corner];
- + quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
- + volt_adjust[i]);
- + if (quot_adjust) {
- + prev_quot = vreg->corner[i].target_quot[ro];
- + vreg->corner[i].target_quot[ro] += quot_adjust;
- + cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %llu --> %u (%d uV)\n",
- + i, ro, prev_quot,
- + vreg->corner[i].target_quot[ro],
- + volt_adjust[i]);
- + }
- + }
- +
- + /* Ensure that target quotients increase monotonically */
- + for (i = 1; i < vreg->corner_count; i++) {
- + ro = fuse->ro_sel[vreg->corner[i].cpr_fuse_corner];
- + if (fuse->ro_sel[vreg->corner[i - 1].cpr_fuse_corner] == ro
- + && vreg->corner[i].target_quot[ro]
- + < vreg->corner[i - 1].target_quot[ro]) {
- + cpr3_debug(vreg, "adjusted corner %d RO%u target quot=%u < adjusted corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
- + i, ro, vreg->corner[i].target_quot[ro],
- + i - 1, ro, vreg->corner[i - 1].target_quot[ro],
- + i, ro, vreg->corner[i - 1].target_quot[ro]);
- + vreg->corner[i].target_quot[ro]
- + = vreg->corner[i - 1].target_quot[ro];
- + }
- + }
- +
- +done:
- + kfree(volt_adjust);
- + kfree(volt_adjust_fuse);
- + kfree(ro_scale);
- + kfree(fmax_corner);
- + kfree(quot_low);
- + kfree(quot_high);
- + return rc;
- +}
- +
- +/**
- + * cpr4_apss_print_settings() - print out APSS CPR configuration settings into
- + * the kernel log for debugging purposes
- + * @vreg: Pointer to the CPR3 regulator
- + */
- +static void cpr4_apss_print_settings(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_corner *corner;
- + int i;
- +
- + cpr3_debug(vreg, "Corner: Frequency (Hz), Fuse Corner, Floor (uV), Open-Loop (uV), Ceiling (uV)\n");
- + for (i = 0; i < vreg->corner_count; i++) {
- + corner = &vreg->corner[i];
- + cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
- + i, corner->proc_freq, corner->cpr_fuse_corner,
- + corner->floor_volt, corner->open_loop_volt,
- + corner->ceiling_volt);
- + }
- +
- + if (vreg->thread->ctrl->apm)
- + cpr3_debug(vreg, "APM threshold = %d uV, APM adjust = %d uV\n",
- + vreg->thread->ctrl->apm_threshold_volt,
- + vreg->thread->ctrl->apm_adj_volt);
- +}
- +
- +/**
- + * cpr4_apss_init_thread() - perform steps necessary to initialize the
- + * configuration data for a CPR3 thread
- + * @thread: Pointer to the CPR3 thread
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_init_thread(struct cpr3_thread *thread)
- +{
- + int rc;
- +
- + rc = cpr3_parse_common_thread_data(thread);
- + if (rc) {
- + cpr3_err(thread->ctrl, "thread %u unable to read CPR thread data from device tree, rc=%d\n",
- + thread->thread_id, rc);
- + return rc;
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * cpr4_apss_parse_temp_adj_properties() - parse temperature based
- + * adjustment properties from device tree.
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_parse_temp_adj_properties(struct cpr3_controller *ctrl)
- +{
- + struct device_node *of_node = ctrl->dev->of_node;
- + int rc, i, len, temp_point_count;
- +
- + if (!of_find_property(of_node, "qcom,cpr-temp-point-map", &len)) {
- + /*
- + * Temperature based adjustments are not defined. Single
- + * temperature band is still valid for per-online-core
- + * adjustments.
- + */
- + ctrl->temp_band_count = 1;
- + return 0;
- + }
- +
- + temp_point_count = len / sizeof(u32);
- + if (temp_point_count <= 0 ||
- + temp_point_count > IPQ807x_APSS_MAX_TEMP_POINTS) {
- + cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
- + temp_point_count, IPQ807x_APSS_MAX_TEMP_POINTS);
- + return -EINVAL;
- + }
- +
- + ctrl->temp_points = devm_kcalloc(ctrl->dev, temp_point_count,
- + sizeof(*ctrl->temp_points), GFP_KERNEL);
- + if (!ctrl->temp_points)
- + return -ENOMEM;
- +
- + rc = of_property_read_u32_array(of_node, "qcom,cpr-temp-point-map",
- + ctrl->temp_points, temp_point_count);
- + if (rc) {
- + cpr3_err(ctrl, "error reading property qcom,cpr-temp-point-map, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + for (i = 0; i < temp_point_count; i++)
- + cpr3_debug(ctrl, "Temperature Point %d=%d\n", i,
- + ctrl->temp_points[i]);
- +
- + /*
- + * If t1, t2, and t3 are the temperature points, then the temperature
- + * bands are: (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).
- + */
- + ctrl->temp_band_count = temp_point_count + 1;
- + cpr3_debug(ctrl, "Number of temp bands =%d\n", ctrl->temp_band_count);
- +
- + rc = of_property_read_u32(of_node, "qcom,cpr-initial-temp-band",
- + &ctrl->initial_temp_band);
- + if (rc) {
- + cpr3_err(ctrl, "error reading qcom,cpr-initial-temp-band, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (ctrl->initial_temp_band >= ctrl->temp_band_count) {
- + cpr3_err(ctrl, "Initial temperature band value %d should be in range [0 - %d]\n",
- + ctrl->initial_temp_band, ctrl->temp_band_count - 1);
- + return -EINVAL;
- + }
- +
- + ctrl->temp_sensor_id_start = IPQ807x_APSS_TEMP_SENSOR_ID_START;
- + ctrl->temp_sensor_id_end = IPQ807x_APSS_TEMP_SENSOR_ID_END;
- + ctrl->allow_temp_adj = true;
- + return rc;
- +}
- +
- +/**
- + * cpr4_apss_parse_boost_properties() - parse configuration data for boost
- + * voltage adjustment for CPR3 regulator from device tree.
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_parse_boost_properties(struct cpr3_regulator *vreg)
- +{
- + struct cpr3_controller *ctrl = vreg->thread->ctrl;
- + struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
- + struct cpr3_corner *corner;
- + int i, boost_voltage, final_boost_volt, rc = 0;
- + int *boost_table = NULL, *boost_temp_adj = NULL;
- + int boost_voltage_adjust = 0, boost_num_cores = 0;
- + u32 boost_allowed = 0;
- +
- + if (!boost_fuse[fuse->boost_cfg])
- + /* Voltage boost is disabled in fuse */
- + return 0;
- +
- + if (of_find_property(vreg->of_node, "qcom,allow-boost", NULL)) {
- + rc = cpr3_parse_array_property(vreg, "qcom,allow-boost", 1,
- + &boost_allowed);
- + if (rc)
- + return rc;
- + }
- +
- + if (!boost_allowed) {
- + /* Voltage boost is not enabled for this regulator */
- + return 0;
- + }
- +
- + boost_voltage = cpr3_convert_open_loop_voltage_fuse(
- + vreg->cpr4_regulator_data->boost_fuse_ref_volt,
- + vreg->cpr4_regulator_data->fuse_step_volt,
- + fuse->boost_voltage,
- + IPQ807x_APSS_VOLTAGE_FUSE_SIZE);
- +
- + /* Log boost voltage value for debugging purposes. */
- + cpr3_info(vreg, "Boost open-loop=%7d uV\n", boost_voltage);
- +
- + if (of_find_property(vreg->of_node,
- + "qcom,cpr-boost-voltage-fuse-adjustment", NULL)) {
- + rc = cpr3_parse_array_property(vreg,
- + "qcom,cpr-boost-voltage-fuse-adjustment",
- + 1, &boost_voltage_adjust);
- + if (rc) {
- + cpr3_err(vreg, "qcom,cpr-boost-voltage-fuse-adjustment reading failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + boost_voltage += boost_voltage_adjust;
- + /* Log boost voltage value for debugging purposes. */
- + cpr3_info(vreg, "Adjusted boost open-loop=%7d uV\n",
- + boost_voltage);
- + }
- +
- + /* Limit boost voltage value between ceiling and floor voltage limits */
- + boost_voltage = min(boost_voltage, vreg->cpr4_regulator_data->boost_ceiling_volt);
- + boost_voltage = max(boost_voltage, vreg->cpr4_regulator_data->boost_floor_volt);
- +
- + /*
- + * The boost feature can only be used for the highest voltage corner.
- + * Also, keep core-count adjustments disabled when the boost feature
- + * is enabled.
- + */
- + corner = &vreg->corner[vreg->corner_count - 1];
- + if (!corner->sdelta) {
- + /*
- + * If core-count/temp adjustments are not defined, the cpr4
- + * sdelta for this corner will not be allocated. Allocate it
- + * here for boost configuration.
- + */
- + corner->sdelta = devm_kzalloc(ctrl->dev,
- + sizeof(*corner->sdelta), GFP_KERNEL);
- + if (!corner->sdelta)
- + return -ENOMEM;
- + }
- + corner->sdelta->temp_band_count = ctrl->temp_band_count;
- +
- + rc = of_property_read_u32(vreg->of_node, "qcom,cpr-num-boost-cores",
- + &boost_num_cores);
- + if (rc) {
- + cpr3_err(vreg, "qcom,cpr-num-boost-cores reading failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (boost_num_cores <= 0 ||
- + boost_num_cores > IPQ807x_APSS_CPR_SDELTA_CORE_COUNT) {
- + cpr3_err(vreg, "Invalid boost number of cores = %d\n",
- + boost_num_cores);
- + return -EINVAL;
- + }
- + corner->sdelta->boost_num_cores = boost_num_cores;
- +
- + boost_table = devm_kcalloc(ctrl->dev, corner->sdelta->temp_band_count,
- + sizeof(*boost_table), GFP_KERNEL);
- + if (!boost_table)
- + return -ENOMEM;
- +
- + if (of_find_property(vreg->of_node,
- + "qcom,cpr-boost-temp-adjustment", NULL)) {
- + boost_temp_adj = kcalloc(corner->sdelta->temp_band_count,
- + sizeof(*boost_temp_adj), GFP_KERNEL);
- + if (!boost_temp_adj)
- + return -ENOMEM;
- +
- + rc = cpr3_parse_array_property(vreg,
- + "qcom,cpr-boost-temp-adjustment",
- + corner->sdelta->temp_band_count,
- + boost_temp_adj);
- + if (rc) {
- + cpr3_err(vreg, "qcom,cpr-boost-temp-adjustment reading failed, rc=%d\n",
- + rc);
- + goto done;
- + }
- + }
- +
- + for (i = 0; i < corner->sdelta->temp_band_count; i++) {
- + /* Apply static adjustments to boost voltage */
- + final_boost_volt = boost_voltage + (boost_temp_adj == NULL
- + ? 0 : boost_temp_adj[i]);
- + /*
- + * Limit final adjusted boost voltage value between ceiling
- + * and floor voltage limits
- + */
- + final_boost_volt = min(final_boost_volt,
- + vreg->cpr4_regulator_data->boost_ceiling_volt);
- + final_boost_volt = max(final_boost_volt,
- + vreg->cpr4_regulator_data->boost_floor_volt);
- +
- + boost_table[i] = (corner->open_loop_volt - final_boost_volt)
- + / ctrl->step_volt;
- + cpr3_debug(vreg, "Adjusted boost voltage margin for temp band %d = %d steps\n",
- + i, boost_table[i]);
- + }
- +
- + corner->ceiling_volt = vreg->cpr4_regulator_data->boost_ceiling_volt;
- + corner->sdelta->boost_table = boost_table;
- + corner->sdelta->allow_boost = true;
- + corner->sdelta->allow_core_count_adj = false;
- + vreg->allow_boost = true;
- + ctrl->allow_boost = true;
- +done:
- + kfree(boost_temp_adj);
- + return rc;
- +}
- +
- +/**
- + * cpr4_apss_init_regulator() - perform all steps necessary to initialize the
- + * configuration data for a CPR3 regulator
- + * @vreg: Pointer to the CPR3 regulator
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_init_regulator(struct cpr3_regulator *vreg)
- +{
- + struct cpr4_ipq807x_apss_fuses *fuse;
- + int rc;
- +
- + rc = cpr4_ipq807x_apss_read_fuse_data(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
- + return rc;
- + }
- +
- + fuse = vreg->platform_fuses;
- +
- + rc = cpr4_apss_parse_corner_data(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to read CPR corner data from device tree, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr3_mem_acc_init(vreg);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(vreg, "unable to initialize mem-acc regulator settings, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr4_ipq807x_apss_calculate_open_loop_voltages(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to calculate open-loop voltages, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr3_limit_open_loop_voltages(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + cpr3_open_loop_voltage_as_ceiling(vreg);
- +
- + rc = cpr3_limit_floor_voltages(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
- + return rc;
- + }
- +
- + rc = cpr4_ipq807x_apss_calculate_target_quotients(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to calculate target quotients, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr4_parse_core_count_temp_voltage_adj(vreg, false);
- + if (rc) {
- + cpr3_err(vreg, "unable to parse temperature and core count voltage adjustments, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (vreg->allow_core_count_adj && (vreg->max_core_count <= 0
- + || vreg->max_core_count >
- + IPQ807x_APSS_CPR_SDELTA_CORE_COUNT)) {
- + cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
- + vreg->max_core_count);
- + return -EINVAL;
- + }
- +
- + rc = cpr4_apss_parse_boost_properties(vreg);
- + if (rc) {
- + cpr3_err(vreg, "unable to parse boost adjustments, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + cpr4_apss_print_settings(vreg);
- +
- + return rc;
- +}
- +
- +/**
- + * cpr4_apss_init_controller() - perform APSS CPR4 controller specific
- + * initializations
- + * @ctrl: Pointer to the CPR3 controller
- + *
- + * Return: 0 on success, errno on failure
- + */
- +static int cpr4_apss_init_controller(struct cpr3_controller *ctrl)
- +{
- + int rc;
- +
- + rc = cpr3_parse_common_ctrl_data(ctrl);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = of_property_read_u32(ctrl->dev->of_node,
- + "qcom,cpr-down-error-step-limit",
- + &ctrl->down_error_step_limit);
- + if (rc) {
- + cpr3_err(ctrl, "error reading qcom,cpr-down-error-step-limit, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = of_property_read_u32(ctrl->dev->of_node,
- + "qcom,cpr-up-error-step-limit",
- + &ctrl->up_error_step_limit);
- + if (rc) {
- + cpr3_err(ctrl, "error reading qcom,cpr-up-error-step-limit, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + /*
- + * Use fixed step quotient if specified otherwise use dynamic
- + * calculated per RO step quotient
- + */
- + of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-step-quot-fixed",
- + &ctrl->step_quot_fixed);
- + ctrl->use_dynamic_step_quot = ctrl->step_quot_fixed ? false : true;
- +
- + ctrl->saw_use_unit_mV = of_property_read_bool(ctrl->dev->of_node,
- + "qcom,cpr-saw-use-unit-mV");
- +
- + of_property_read_u32(ctrl->dev->of_node,
- + "qcom,cpr-voltage-settling-time",
- + &ctrl->voltage_settling_time);
- +
- + if (of_find_property(ctrl->dev->of_node, "vdd-limit-supply", NULL)) {
- + ctrl->vdd_limit_regulator =
- + devm_regulator_get(ctrl->dev, "vdd-limit");
- + if (IS_ERR(ctrl->vdd_limit_regulator)) {
- + rc = PTR_ERR(ctrl->vdd_limit_regulator);
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable to request vdd-limit regulator, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + rc = cpr3_apm_init(ctrl);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "unable to initialize APM settings, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr4_apss_parse_temp_adj_properties(ctrl);
- + if (rc) {
- + cpr3_err(ctrl, "unable to parse temperature adjustment properties, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + ctrl->sensor_count = IPQ807x_APSS_CPR_SENSOR_COUNT;
- +
- + /*
- + * APSS only has one thread (0) per controller so the zeroed
- + * array does not need further modification.
- + */
- + ctrl->sensor_owner = devm_kcalloc(ctrl->dev, ctrl->sensor_count,
- + sizeof(*ctrl->sensor_owner), GFP_KERNEL);
- + if (!ctrl->sensor_owner)
- + return -ENOMEM;
- +
- + ctrl->ctrl_type = CPR_CTRL_TYPE_CPR4;
- + ctrl->supports_hw_closed_loop = false;
- + ctrl->use_hw_closed_loop = of_property_read_bool(ctrl->dev->of_node,
- + "qcom,cpr-hw-closed-loop");
- + return 0;
- +}
- +
- +static int cpr4_apss_regulator_suspend(struct platform_device *pdev,
- + pm_message_t state)
- +{
- + struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
- +
- + return cpr3_regulator_suspend(ctrl);
- +}
- +
- +static int cpr4_apss_regulator_resume(struct platform_device *pdev)
- +{
- + struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
- +
- + return cpr3_regulator_resume(ctrl);
- +}
- +
- +static void ipq6018_set_mem_acc(struct regulator_dev *rdev)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- +
- + ipq6018_mem_acc_tcsr[0].ioremap_addr =
- + ioremap(ipq6018_mem_acc_tcsr[0].phy_addr, 0x4);
- + ipq6018_mem_acc_tcsr[1].ioremap_addr =
- + ioremap(ipq6018_mem_acc_tcsr[1].phy_addr, 0x4);
- +
- + if ((ipq6018_mem_acc_tcsr[0].ioremap_addr != NULL) &&
- + (ipq6018_mem_acc_tcsr[1].ioremap_addr != NULL) &&
- + (vreg->current_corner == (vreg->corner_count - CPR3_CORNER_OFFSET))) {
- +
- + writel_relaxed(ipq6018_mem_acc_tcsr[0].value,
- + ipq6018_mem_acc_tcsr[0].ioremap_addr);
- + writel_relaxed(ipq6018_mem_acc_tcsr[1].value,
- + ipq6018_mem_acc_tcsr[1].ioremap_addr);
- + }
- +}
- +
- +static void ipq6018_clr_mem_acc(struct regulator_dev *rdev)
- +{
- + struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
- +
- + if ((ipq6018_mem_acc_tcsr[0].ioremap_addr != NULL) &&
- + (ipq6018_mem_acc_tcsr[1].ioremap_addr != NULL) &&
- + (vreg->current_corner != vreg->corner_count - CPR3_CORNER_OFFSET)) {
- + writel_relaxed(0x0, ipq6018_mem_acc_tcsr[0].ioremap_addr);
- + writel_relaxed(0x0, ipq6018_mem_acc_tcsr[1].ioremap_addr);
- + }
- +
- + iounmap(ipq6018_mem_acc_tcsr[0].ioremap_addr);
- + iounmap(ipq6018_mem_acc_tcsr[1].ioremap_addr);
- +}
- +
- +static struct cpr4_mem_acc_func ipq6018_mem_acc_funcs = {
- + .set_mem_acc = ipq6018_set_mem_acc,
- + .clear_mem_acc = ipq6018_clr_mem_acc
- +};
- +
- +static const struct cpr4_reg_data ipq807x_cpr_apss = {
- + .cpr_valid_fuse_count = IPQ807x_APSS_FUSE_CORNERS,
- + .fuse_ref_volt = ipq807x_apss_fuse_ref_volt,
- + .fuse_step_volt = IPQ807x_APSS_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ807x_APSS_CPR_CLOCK_RATE,
- + .boost_fuse_ref_volt= IPQ807x_APSS_BOOST_FUSE_REF_VOLT,
- + .boost_ceiling_volt= IPQ807x_APSS_BOOST_CEILING_VOLT,
- + .boost_floor_volt= IPQ807x_APSS_BOOST_FLOOR_VOLT,
- + .cpr3_fuse_params = &ipq807x_fuse_params,
- + .mem_acc_funcs = NULL,
- +};
- +
- +static const struct cpr4_reg_data ipq817x_cpr_apss = {
- + .cpr_valid_fuse_count = IPQ817x_APPS_FUSE_CORNERS,
- + .fuse_ref_volt = ipq807x_apss_fuse_ref_volt,
- + .fuse_step_volt = IPQ807x_APSS_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ807x_APSS_CPR_CLOCK_RATE,
- + .boost_fuse_ref_volt= IPQ807x_APSS_BOOST_FUSE_REF_VOLT,
- + .boost_ceiling_volt= IPQ807x_APSS_BOOST_CEILING_VOLT,
- + .boost_floor_volt= IPQ807x_APSS_BOOST_FLOOR_VOLT,
- + .cpr3_fuse_params = &ipq807x_fuse_params,
- + .mem_acc_funcs = NULL,
- +};
- +
- +static const struct cpr4_reg_data ipq6018_cpr_apss = {
- + .cpr_valid_fuse_count = IPQ6018_APSS_FUSE_CORNERS,
- + .fuse_ref_volt = ipq6018_apss_fuse_ref_volt,
- + .fuse_step_volt = IPQ6018_APSS_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ6018_APSS_CPR_CLOCK_RATE,
- + .boost_fuse_ref_volt = IPQ6018_APSS_BOOST_FUSE_REF_VOLT,
- + .boost_ceiling_volt = IPQ6018_APSS_BOOST_CEILING_VOLT,
- + .boost_floor_volt = IPQ6018_APSS_BOOST_FLOOR_VOLT,
- + .cpr3_fuse_params = &ipq6018_fuse_params,
- + .mem_acc_funcs = &ipq6018_mem_acc_funcs,
- +};
- +
- +static const struct cpr4_reg_data ipq9574_cpr_apss = {
- + .cpr_valid_fuse_count = IPQ9574_APSS_FUSE_CORNERS,
- + .fuse_ref_volt = ipq9574_apss_fuse_ref_volt,
- + .fuse_step_volt = IPQ9574_APSS_FUSE_STEP_VOLT,
- + .cpr_clk_rate = IPQ6018_APSS_CPR_CLOCK_RATE,
- + .boost_fuse_ref_volt = IPQ6018_APSS_BOOST_FUSE_REF_VOLT,
- + .boost_ceiling_volt = IPQ6018_APSS_BOOST_CEILING_VOLT,
- + .boost_floor_volt = IPQ6018_APSS_BOOST_FLOOR_VOLT,
- + .cpr3_fuse_params = &ipq9574_fuse_params,
- + .mem_acc_funcs = NULL,
- +};
- +
- +static struct of_device_id cpr4_regulator_match_table[] = {
- + {
- + .compatible = "qcom,cpr4-ipq807x-apss-regulator",
- + .data = &ipq807x_cpr_apss
- + },
- + {
- + .compatible = "qcom,cpr4-ipq817x-apss-regulator",
- + .data = &ipq817x_cpr_apss
- + },
- + {
- + .compatible = "qcom,cpr4-ipq6018-apss-regulator",
- + .data = &ipq6018_cpr_apss
- + },
- + {
- + .compatible = "qcom,cpr4-ipq9574-apss-regulator",
- + .data = &ipq9574_cpr_apss
- + },
- + {}
- +};
- +
- +static int cpr4_apss_regulator_probe(struct platform_device *pdev)
- +{
- + struct device *dev = &pdev->dev;
- + struct cpr3_controller *ctrl;
- + const struct of_device_id *match;
- + struct cpr4_reg_data *cpr_data;
- + int i, rc;
- +
- + if (!dev->of_node) {
- + dev_err(dev, "Device tree node is missing\n");
- + return -EINVAL;
- + }
- +
- + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
- + if (!ctrl)
- + return -ENOMEM;
- +
- + match = of_match_device(cpr4_regulator_match_table, &pdev->dev);
- + if (!match)
- + return -ENODEV;
- +
- + cpr_data = (struct cpr4_reg_data *)match->data;
- + g_valid_fuse_count = cpr_data->cpr_valid_fuse_count;
- + dev_info(dev, "CPR valid fuse count: %d\n", g_valid_fuse_count);
- + ctrl->cpr_clock_rate = cpr_data->cpr_clk_rate;
- +
- + ctrl->dev = dev;
- + /* Set to false later if anything precludes CPR operation. */
- + ctrl->cpr_allowed_hw = true;
- +
- + rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
- + &ctrl->name);
- + if (rc) {
- + cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr3_map_fuse_base(ctrl, pdev);
- + if (rc) {
- + cpr3_err(ctrl, "could not map fuse base address\n");
- + return rc;
- + }
- +
- + rc = cpr3_read_tcsr_setting(ctrl, pdev, IPQ807x_APSS_CPR_TCSR_START,
- + IPQ807x_APSS_CPR_TCSR_END);
- + if (rc) {
- + cpr3_err(ctrl, "could not read CPR tcsr setting\n");
- + return rc;
- + }
- +
- + rc = cpr3_allocate_threads(ctrl, 0, 0);
- + if (rc) {
- + cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + if (ctrl->thread_count != 1) {
- + cpr3_err(ctrl, "expected 1 thread but found %d\n",
- + ctrl->thread_count);
- + return -EINVAL;
- + }
- +
- + rc = cpr4_apss_init_controller(ctrl);
- + if (rc) {
- + if (rc != -EPROBE_DEFER)
- + cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
- + rc);
- + return rc;
- + }
- +
- + rc = cpr4_apss_init_thread(&ctrl->thread[0]);
- + if (rc) {
- + cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
- + return rc;
- + }
- +
- + for (i = 0; i < ctrl->thread[0].vreg_count; i++) {
- + ctrl->thread[0].vreg[i].cpr4_regulator_data = cpr_data;
- + rc = cpr4_apss_init_regulator(&ctrl->thread[0].vreg[i]);
- + if (rc) {
- + cpr3_err(&ctrl->thread[0].vreg[i], "regulator initialization failed, rc=%d\n",
- + rc);
- + return rc;
- + }
- + }
- +
- + platform_set_drvdata(pdev, ctrl);
- +
- + return cpr3_regulator_register(pdev, ctrl);
- +}
- +
- +static int cpr4_apss_regulator_remove(struct platform_device *pdev)
- +{
- + struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
- +
- + return cpr3_regulator_unregister(ctrl);
- +}
- +
- +static struct platform_driver cpr4_apss_regulator_driver = {
- + .driver = {
- + .name = "qcom,cpr4-apss-regulator",
- + .of_match_table = cpr4_regulator_match_table,
- + .owner = THIS_MODULE,
- + },
- + .probe = cpr4_apss_regulator_probe,
- + .remove = cpr4_apss_regulator_remove,
- + .suspend = cpr4_apss_regulator_suspend,
- + .resume = cpr4_apss_regulator_resume,
- +};
- +
- +static int cpr4_regulator_init(void)
- +{
- + return platform_driver_register(&cpr4_apss_regulator_driver);
- +}
- +
- +static void cpr4_regulator_exit(void)
- +{
- + platform_driver_unregister(&cpr4_apss_regulator_driver);
- +}
- +
- +MODULE_DESCRIPTION("CPR4 APSS regulator driver");
- +MODULE_LICENSE("GPL v2");
- +
- +arch_initcall(cpr4_regulator_init);
- +module_exit(cpr4_regulator_exit);
- --- /dev/null
- +++ b/include/soc/qcom/socinfo.h
- @@ -0,0 +1,463 @@
- +/* Copyright (c) 2009-2014, 2016, 2020, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 as published by the Free Software Foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + *
- + */
- +
- +#ifndef _ARCH_ARM_MACH_MSM_SOCINFO_H_
- +#define _ARCH_ARM_MACH_MSM_SOCINFO_H_
- +
- +#include <linux/of.h>
- +
- +#define CPU_IPQ8074 323
- +#define CPU_IPQ8072 342
- +#define CPU_IPQ8076 343
- +#define CPU_IPQ8078 344
- +#define CPU_IPQ8070 375
- +#define CPU_IPQ8071 376
- +
- +#define CPU_IPQ8072A 389
- +#define CPU_IPQ8074A 390
- +#define CPU_IPQ8076A 391
- +#define CPU_IPQ8078A 392
- +#define CPU_IPQ8070A 395
- +#define CPU_IPQ8071A 396
- +
- +#define CPU_IPQ8172 397
- +#define CPU_IPQ8173 398
- +#define CPU_IPQ8174 399
- +
- +#define CPU_IPQ6018 402
- +#define CPU_IPQ6028 403
- +#define CPU_IPQ6000 421
- +#define CPU_IPQ6010 422
- +#define CPU_IPQ6005 453
- +
- +#define CPU_IPQ5010 446
- +#define CPU_IPQ5018 447
- +#define CPU_IPQ5028 448
- +#define CPU_IPQ5000 503
- +#define CPU_IPQ0509 504
- +#define CPU_IPQ0518 505
- +
- +#define CPU_IPQ9514 510
- +#define CPU_IPQ9554 512
- +#define CPU_IPQ9570 513
- +#define CPU_IPQ9574 514
- +#define CPU_IPQ9550 511
- +#define CPU_IPQ9510 521
- +
- +static inline int read_ipq_soc_version_major(void)
- +{
- + const int *prop;
- + prop = of_get_property(of_find_node_by_path("/"), "soc_version_major",
- + NULL);
- +
- + if (!prop)
- + return -EINVAL;
- +
- + return le32_to_cpu(*prop);
- +}
- +
- +static inline int read_ipq_cpu_type(void)
- +{
- + const int *prop;
- + prop = of_get_property(of_find_node_by_path("/"), "cpu_type", NULL);
- + /*
- + * Return Default CPU type if "cpu_type" property is not found in DTSI
- + */
- + if (!prop)
- + return CPU_IPQ8074;
- +
- + return le32_to_cpu(*prop);
- +}
- +
- +static inline int cpu_is_ipq8070(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8070;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8071(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8071;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8072(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8072;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8074(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8074;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8076(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8076;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8078(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8078;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8072a(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8072A;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8074a(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8074A;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8076a(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8076A;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8078a(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8078A;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8070a(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8070A;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8071a(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8071A;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8172(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8172;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8173(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8173;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq8174(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ8174;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq6018(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ6018;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq6028(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ6028;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq6000(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ6000;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq6010(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ6010;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq6005(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ6005;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq5010(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ5010;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq5018(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ5018;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq5028(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ5028;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq5000(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ5000;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq0509(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ0509;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq0518(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ0518;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq9514(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ9514;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq9554(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ9554;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq9570(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ9570;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq9574(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ9574;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq9550(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ9550;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq9510(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return read_ipq_cpu_type() == CPU_IPQ9510;
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq807x(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq8072() || cpu_is_ipq8074() ||
- + cpu_is_ipq8076() || cpu_is_ipq8078() ||
- + cpu_is_ipq8070() || cpu_is_ipq8071() ||
- + cpu_is_ipq8072a() || cpu_is_ipq8074a() ||
- + cpu_is_ipq8076a() || cpu_is_ipq8078a() ||
- + cpu_is_ipq8070a() || cpu_is_ipq8071a() ||
- + cpu_is_ipq8172() || cpu_is_ipq8173() ||
- + cpu_is_ipq8174();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq60xx(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq6018() || cpu_is_ipq6028() ||
- + cpu_is_ipq6000() || cpu_is_ipq6010() ||
- + cpu_is_ipq6005();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq50xx(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq5010() || cpu_is_ipq5018() ||
- + cpu_is_ipq5028() || cpu_is_ipq5000() ||
- + cpu_is_ipq0509() || cpu_is_ipq0518();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_ipq95xx(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq9514() || cpu_is_ipq9554() ||
- + cpu_is_ipq9570() || cpu_is_ipq9574() ||
- + cpu_is_ipq9550() || cpu_is_ipq9510();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_nss_crypto_enabled(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq807x() || cpu_is_ipq60xx() ||
- + cpu_is_ipq50xx() || cpu_is_ipq9570() ||
- + cpu_is_ipq9550() || cpu_is_ipq9574() ||
- + cpu_is_ipq9554();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_internal_wifi_enabled(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq807x() || cpu_is_ipq60xx() ||
- + cpu_is_ipq50xx() || cpu_is_ipq9514() ||
- + cpu_is_ipq9554() || cpu_is_ipq9574();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_uniphy1_enabled(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq807x() || cpu_is_ipq60xx() ||
- + cpu_is_ipq9554() || cpu_is_ipq9570() ||
- + cpu_is_ipq9574() || cpu_is_ipq9550();
- +#else
- + return 0;
- +#endif
- +}
- +
- +static inline int cpu_is_uniphy2_enabled(void)
- +{
- +#ifdef CONFIG_ARCH_QCOM
- + return cpu_is_ipq807x() || cpu_is_ipq9570() ||
- + cpu_is_ipq9574();
- +#else
- + return 0;
- +#endif
- +}
- +
- +#endif /* _ARCH_ARM_MACH_MSM_SOCINFO_H_ */
|