1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
- *
- */
- #include <linux/delay.h>
- #include <linux/device.h>
- #include <linux/dma-direction.h>
- #include <linux/dma-mapping.h>
- #include <linux/interrupt.h>
- #include <linux/list.h>
- #include <linux/mhi.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/wait.h>
- #include "internal.h"
- /*
- * Not all MHI state transitions are synchronous. Transitions like Linkdown,
- * SYS_ERR, and shutdown can happen anytime asynchronously. This function will
- * transition to a new state only if we're allowed to.
- *
- * Priority increases as we go down. For instance, from any state in L0, the
- * transition can be made to states in L1, L2 and L3. A notable exception to
- * this rule is state DISABLE. From DISABLE state we can only transition to
- * POR state. Also, while in L2 state, user cannot jump back to previous
- * L1 or L0 states.
- *
- * Valid transitions:
- * L0: DISABLE <--> POR
- * POR <--> POR
- * POR -> M0 -> M2 --> M0
- * POR -> FW_DL_ERR
- * FW_DL_ERR <--> FW_DL_ERR
- * M0 <--> M0
- * M0 -> FW_DL_ERR
- * M0 -> M3_ENTER -> M3 -> M3_EXIT --> M0
- * L1: SYS_ERR_DETECT -> SYS_ERR_PROCESS --> POR
- * L2: SHUTDOWN_PROCESS -> LD_ERR_FATAL_DETECT
- * SHUTDOWN_PROCESS -> DISABLE
- * L3: LD_ERR_FATAL_DETECT <--> LD_ERR_FATAL_DETECT
- * LD_ERR_FATAL_DETECT -> DISABLE
- */
- static const struct mhi_pm_transitions dev_state_transitions[] = {
- /* L0 States */
- {
- MHI_PM_DISABLE,
- MHI_PM_POR
- },
- {
- MHI_PM_POR,
- MHI_PM_POR | MHI_PM_DISABLE | MHI_PM_M0 |
- MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_FW_DL_ERR
- },
- {
- MHI_PM_M0,
- MHI_PM_M0 | MHI_PM_M2 | MHI_PM_M3_ENTER |
- MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_FW_DL_ERR
- },
- {
- MHI_PM_M2,
- MHI_PM_M0 | MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT
- },
- {
- MHI_PM_M3_ENTER,
- MHI_PM_M3 | MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT
- },
- {
- MHI_PM_M3,
- MHI_PM_M3_EXIT | MHI_PM_SYS_ERR_DETECT |
- MHI_PM_LD_ERR_FATAL_DETECT
- },
- {
- MHI_PM_M3_EXIT,
- MHI_PM_M0 | MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT
- },
- {
- MHI_PM_FW_DL_ERR,
- MHI_PM_FW_DL_ERR | MHI_PM_SYS_ERR_DETECT |
- MHI_PM_SHUTDOWN_PROCESS | MHI_PM_LD_ERR_FATAL_DETECT
- },
- /* L1 States */
- {
- MHI_PM_SYS_ERR_DETECT,
- MHI_PM_SYS_ERR_PROCESS | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT
- },
- {
- MHI_PM_SYS_ERR_PROCESS,
- MHI_PM_POR | MHI_PM_SHUTDOWN_PROCESS |
- MHI_PM_LD_ERR_FATAL_DETECT
- },
- /* L2 States */
- {
- MHI_PM_SHUTDOWN_PROCESS,
- MHI_PM_DISABLE | MHI_PM_LD_ERR_FATAL_DETECT
- },
- /* L3 States */
- {
- MHI_PM_LD_ERR_FATAL_DETECT,
- MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_DISABLE
- },
- };
- enum mhi_pm_state __must_check mhi_tryset_pm_state(struct mhi_controller *mhi_cntrl,
- enum mhi_pm_state state)
- {
- unsigned long cur_state = mhi_cntrl->pm_state;
- int index = find_last_bit(&cur_state, 32);
- if (unlikely(index >= ARRAY_SIZE(dev_state_transitions)))
- return cur_state;
- if (unlikely(dev_state_transitions[index].from_state != cur_state))
- return cur_state;
- if (unlikely(!(dev_state_transitions[index].to_states & state)))
- return cur_state;
- mhi_cntrl->pm_state = state;
- return mhi_cntrl->pm_state;
- }
- void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl, enum mhi_state state)
- {
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- int ret;
- if (state == MHI_STATE_RESET) {
- ret = mhi_write_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
- MHICTRL_RESET_MASK, 1);
- } else {
- ret = mhi_write_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
- MHICTRL_MHISTATE_MASK, state);
- }
- if (ret)
- MHI_ERR(dev, "Failed to set MHI state to: %s\n",
- mhi_state_str(state));
- }
- /* NOP for backward compatibility, host allowed to ring DB in M2 state */
- static void mhi_toggle_dev_wake_nop(struct mhi_controller *mhi_cntrl)
- {
- }
- static void mhi_toggle_dev_wake(struct mhi_controller *mhi_cntrl)
- {
- mhi_cntrl->wake_get(mhi_cntrl, false);
- mhi_cntrl->wake_put(mhi_cntrl, true);
- }
- /* Add event ring elements and ring er db */
- static void mhi_setup_event_rings(struct mhi_controller *mhi_cntrl, bool add_el)
- {
- struct mhi_event *mhi_event;
- int i;
- bool skip_er_setup;
- mhi_event = mhi_cntrl->mhi_event;
- for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
- struct mhi_ring *ring = &mhi_event->ring;
- if (mhi_event->offload_ev)
- continue;
- /* skip HW event ring setup in ready state */
- if (mhi_cntrl->dev_state == MHI_STATE_READY)
- skip_er_setup = mhi_event->hw_ring;
- else
- skip_er_setup = !mhi_event->hw_ring;
- /* if no er element to add, ring all er dbs */
- if (add_el && skip_er_setup)
- continue;
- if (add_el) {
- ring->wp = ring->base + ring->len - ring->el_size;
- *ring->ctxt_wp =
- ring->iommu_base + ring->len - ring->el_size;
- /* Update all cores */
- smp_wmb();
- }
- /* Ring the event ring db */
- spin_lock_irq(&mhi_event->lock);
- mhi_ring_er_db(mhi_event);
- spin_unlock_irq(&mhi_event->lock);
- }
- }
- /* Handle device ready state transition */
- int mhi_ready_state_transition(struct mhi_controller *mhi_cntrl)
- {
- enum mhi_pm_state cur_state;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- u32 interval_us = 25000; /* poll register field every 25 milliseconds */
- int ret = -EINVAL;
- /* Check if device entered error state */
- if (MHI_PM_IN_FATAL_STATE(mhi_cntrl->pm_state)) {
- MHI_ERR(dev, "Device link is not accessible\n");
- return -EIO;
- }
- /* Wait for RESET to be cleared and READY bit to be set by the device */
- ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
- MHICTRL_RESET_MASK, 0, interval_us);
- if (ret) {
- MHI_ERR(dev, "Device failed to clear MHI Reset\n");
- return ret;
- }
- ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs, MHISTATUS,
- MHISTATUS_READY_MASK, 1, interval_us);
- if (ret) {
- MHI_ERR(dev, "Device failed to enter MHI Ready\n");
- return ret;
- }
- MHI_VERB(dev, "Device in READY State\n");
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_POR);
- mhi_cntrl->dev_state = MHI_STATE_READY;
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (cur_state != MHI_PM_POR) {
- MHI_ERR(dev, "Error moving to state %s from %s\n",
- to_mhi_pm_state_str(MHI_PM_POR),
- to_mhi_pm_state_str(cur_state));
- return -EIO;
- }
- read_lock_bh(&mhi_cntrl->pm_lock);
- if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) {
- MHI_ERR(dev, "Device registers not accessible\n");
- goto error_mmio;
- }
- /* Configure MMIO registers */
- ret = mhi_init_mmio(mhi_cntrl);
- if (ret) {
- MHI_ERR(dev, "Error configuring MMIO registers\n");
- goto error_mmio;
- }
- /* add SW event ring elements and ring SW event ring dbs */
- mhi_setup_event_rings(mhi_cntrl, true);
- /* Set MHI to M0 state */
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M0);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- return 0;
- error_mmio:
- read_unlock_bh(&mhi_cntrl->pm_lock);
- return -EIO;
- }
- int mhi_pm_m0_transition(struct mhi_controller *mhi_cntrl)
- {
- enum mhi_pm_state cur_state;
- struct mhi_chan *mhi_chan;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- int i;
- write_lock_irq(&mhi_cntrl->pm_lock);
- mhi_cntrl->dev_state = MHI_STATE_M0;
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M0);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (unlikely(cur_state != MHI_PM_M0)) {
- MHI_ERR(dev, "Unable to transition to M0 state\n");
- return -EIO;
- }
- mhi_cntrl->M0++;
- /* Wake up the device */
- read_lock_bh(&mhi_cntrl->pm_lock);
- mhi_cntrl->wake_get(mhi_cntrl, true);
- /* Ring all event rings and CMD ring only if we're in mission mode */
- if (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) {
- struct mhi_cmd *mhi_cmd =
- &mhi_cntrl->mhi_cmd[PRIMARY_CMD_RING];
- mhi_setup_event_rings(mhi_cntrl, false);
- /* Only ring primary cmd ring if ring is not empty */
- spin_lock_irq(&mhi_cmd->lock);
- if (mhi_cmd->ring.rp != mhi_cmd->ring.wp)
- mhi_ring_cmd_db(mhi_cntrl, mhi_cmd);
- spin_unlock_irq(&mhi_cmd->lock);
- /* ring misc doorbells for certain controllers */
- mhi_misc_dbs_pending(mhi_cntrl);
- }
- /* Ring channel DB registers */
- mhi_chan = mhi_cntrl->mhi_chan;
- for (i = 0; i < mhi_cntrl->max_chan; i++, mhi_chan++) {
- struct mhi_ring *tre_ring = &mhi_chan->tre_ring;
- if (mhi_chan->db_cfg.reset_req) {
- write_lock_irq(&mhi_chan->lock);
- mhi_chan->db_cfg.db_mode = true;
- write_unlock_irq(&mhi_chan->lock);
- }
- read_lock_irq(&mhi_chan->lock);
- /* Only ring DB if ring is not empty */
- if (tre_ring->base && tre_ring->wp != tre_ring->rp &&
- mhi_chan->ch_state == MHI_CH_STATE_ENABLED)
- mhi_ring_chan_db(mhi_cntrl, mhi_chan);
- read_unlock_irq(&mhi_chan->lock);
- }
- mhi_cntrl->wake_put(mhi_cntrl, false);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- wake_up_all(&mhi_cntrl->state_event);
- return 0;
- }
- /*
- * After receiving the MHI state change event from the device indicating the
- * transition to M1 state, the host can transition the device to M2 state
- * for keeping it in low power state.
- */
- void mhi_pm_m1_transition(struct mhi_controller *mhi_cntrl)
- {
- enum mhi_pm_state state;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- write_lock_irq(&mhi_cntrl->pm_lock);
- state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M2);
- if (state == MHI_PM_M2) {
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M2);
- mhi_cntrl->dev_state = MHI_STATE_M2;
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mhi_cntrl->M2++;
- wake_up_all(&mhi_cntrl->state_event);
- /* If there are any pending resources, exit M2 immediately */
- if (unlikely(atomic_read(&mhi_cntrl->pending_pkts) ||
- atomic_read(&mhi_cntrl->dev_wake))) {
- MHI_VERB(dev,
- "Exiting M2, pending_pkts: %d dev_wake: %d\n",
- atomic_read(&mhi_cntrl->pending_pkts),
- atomic_read(&mhi_cntrl->dev_wake));
- read_lock_bh(&mhi_cntrl->pm_lock);
- mhi_cntrl->wake_get(mhi_cntrl, true);
- mhi_cntrl->wake_put(mhi_cntrl, true);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- } else {
- mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_IDLE);
- }
- } else {
- write_unlock_irq(&mhi_cntrl->pm_lock);
- }
- }
- /* MHI M3 completion handler */
- int mhi_pm_m3_transition(struct mhi_controller *mhi_cntrl)
- {
- enum mhi_pm_state state;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- write_lock_irq(&mhi_cntrl->pm_lock);
- mhi_cntrl->dev_state = MHI_STATE_M3;
- state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (state != MHI_PM_M3) {
- MHI_ERR(dev, "Unable to transition to M3 state\n");
- return -EIO;
- }
- mhi_cntrl->M3++;
- wake_up_all(&mhi_cntrl->state_event);
- return 0;
- }
- /* Handle device Mission Mode transition */
- static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
- {
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- enum mhi_ee_type ee = MHI_EE_MAX, current_ee = mhi_cntrl->ee;
- int ret;
- MHI_VERB(dev, "Processing Mission Mode transition\n");
- write_lock_irq(&mhi_cntrl->pm_lock);
- if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state))
- ee = mhi_get_exec_env(mhi_cntrl);
- if (!MHI_IN_MISSION_MODE(ee)) {
- mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT;
- write_unlock_irq(&mhi_cntrl->pm_lock);
- wake_up_all(&mhi_cntrl->state_event);
- return -EIO;
- }
- mhi_cntrl->ee = ee;
- write_unlock_irq(&mhi_cntrl->pm_lock);
- wake_up_all(&mhi_cntrl->state_event);
- mhi_reset_reg_write_q(mhi_cntrl);
- device_for_each_child(&mhi_cntrl->mhi_dev->dev, ¤t_ee,
- mhi_destroy_device);
- mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_MISSION_MODE);
- /* Force MHI to be in M0 state before continuing */
- ret = __mhi_device_get_sync(mhi_cntrl);
- if (ret)
- return ret;
- read_lock_bh(&mhi_cntrl->pm_lock);
- if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
- ret = -EIO;
- goto error_mission_mode;
- }
- /* Add elements to all HW event rings and ring HW event ring dbs */
- mhi_setup_event_rings(mhi_cntrl, true);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- mhi_process_sleeping_events(mhi_cntrl);
- /*
- * The MHI devices are only created when the client device switches its
- * Execution Environment (EE) to either SBL or AMSS states
- */
- mhi_create_devices(mhi_cntrl);
- mhi_misc_mission_mode(mhi_cntrl);
- read_lock_bh(&mhi_cntrl->pm_lock);
- error_mission_mode:
- mhi_cntrl->wake_put(mhi_cntrl, false);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- return ret;
- }
- /* Handle shutdown transitions */
- static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
- {
- enum mhi_pm_state cur_state;
- struct mhi_event *mhi_event;
- struct mhi_cmd_ctxt *cmd_ctxt;
- struct mhi_cmd *mhi_cmd;
- struct mhi_event_ctxt *er_ctxt;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- int ret, i;
- MHI_VERB(dev, "Processing disable transition with PM state: %s\n",
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
- mhi_reset_reg_write_q(mhi_cntrl);
- mutex_lock(&mhi_cntrl->pm_mutex);
- /* Trigger MHI RESET so that the device will not access host memory. */
- if (!MHI_PM_IN_FATAL_STATE(mhi_cntrl->pm_state)) {
- /* Skip MHI RESET if in RDDM state */
- if (mhi_cntrl->rddm_image && mhi_get_exec_env(mhi_cntrl) == MHI_EE_RDDM)
- goto skip_mhi_reset;
- MHI_VERB(dev, "Triggering MHI Reset in device\n");
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET);
- /* Wait for the reset bit to be cleared by the device */
- ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
- MHICTRL_RESET_MASK, 0, 25000);
- if (ret)
- MHI_ERR(dev, "Device failed to clear MHI Reset\n");
- /*
- * Device will clear BHI_INTVEC as a part of RESET processing,
- * hence re-program it
- */
- mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0);
- if (!MHI_IN_PBL(mhi_get_exec_env(mhi_cntrl))) {
- /* wait for ready to be set */
- ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs,
- MHISTATUS,
- MHISTATUS_READY_MASK, 1, 25000);
- if (ret)
- MHI_ERR(dev, "Device failed to enter READY state\n");
- }
- }
- skip_mhi_reset:
- MHI_VERB(dev,
- "Waiting for all pending event ring processing to complete\n");
- mhi_event = mhi_cntrl->mhi_event;
- for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
- if (mhi_event->offload_ev)
- continue;
- disable_irq(mhi_cntrl->irq[mhi_event->irq]);
- if (mhi_event->priority == MHI_ER_PRIORITY_HI_SLEEP)
- cancel_work_sync(&mhi_event->work);
- else
- tasklet_kill(&mhi_event->task);
- }
- /* Release lock and wait for all pending threads to complete */
- mutex_unlock(&mhi_cntrl->pm_mutex);
- mhi_misc_disable(mhi_cntrl);
- MHI_VERB(dev, "Waiting for all pending threads to complete\n");
- wake_up_all(&mhi_cntrl->state_event);
- MHI_VERB(dev, "Reset all active channels and remove MHI devices\n");
- device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
- mutex_lock(&mhi_cntrl->pm_mutex);
- WARN_ON(atomic_read(&mhi_cntrl->dev_wake));
- WARN_ON(atomic_read(&mhi_cntrl->pending_pkts));
- /* Reset the ev rings and cmd rings */
- MHI_VERB(dev, "Resetting EV CTXT and CMD CTXT\n");
- mhi_cmd = mhi_cntrl->mhi_cmd;
- cmd_ctxt = mhi_cntrl->mhi_ctxt->cmd_ctxt;
- for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++, cmd_ctxt++) {
- struct mhi_ring *ring = &mhi_cmd->ring;
- ring->rp = ring->base;
- ring->wp = ring->base;
- cmd_ctxt->rp = cmd_ctxt->rbase;
- cmd_ctxt->wp = cmd_ctxt->rbase;
- }
- mhi_event = mhi_cntrl->mhi_event;
- er_ctxt = mhi_cntrl->mhi_ctxt->er_ctxt;
- for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++,
- mhi_event++) {
- struct mhi_ring *ring = &mhi_event->ring;
- /* Skip offload events */
- if (mhi_event->offload_ev)
- continue;
- ring->rp = ring->base;
- ring->wp = ring->base;
- er_ctxt->rp = er_ctxt->rbase;
- er_ctxt->wp = er_ctxt->rbase;
- }
- /* Move to disable state */
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_DISABLE);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (unlikely(cur_state != MHI_PM_DISABLE))
- MHI_ERR(dev, "Error moving from PM state: %s to: %s\n",
- to_mhi_pm_state_str(cur_state),
- to_mhi_pm_state_str(MHI_PM_DISABLE));
- MHI_VERB(dev, "Exiting with PM state: %s, MHI state: %s\n",
- to_mhi_pm_state_str(mhi_cntrl->pm_state),
- mhi_state_str(mhi_cntrl->dev_state));
- mutex_unlock(&mhi_cntrl->pm_mutex);
- }
- /* Handle system error transitions */
- static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl)
- {
- enum mhi_pm_state cur_state, prev_state;
- enum dev_st_transition next_state;
- struct mhi_event *mhi_event;
- struct mhi_cmd_ctxt *cmd_ctxt;
- struct mhi_cmd *mhi_cmd;
- struct mhi_event_ctxt *er_ctxt;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- int ret, i;
- MHI_VERB(dev, "Transitioning from PM state: %s to: %s\n",
- to_mhi_pm_state_str(mhi_cntrl->pm_state),
- to_mhi_pm_state_str(MHI_PM_SYS_ERR_PROCESS));
- /* We must notify MHI control driver so it can clean up first */
- mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_SYS_ERROR);
- mutex_lock(&mhi_cntrl->pm_mutex);
- write_lock_irq(&mhi_cntrl->pm_lock);
- prev_state = mhi_cntrl->pm_state;
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_SYS_ERR_PROCESS);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (cur_state != MHI_PM_SYS_ERR_PROCESS) {
- MHI_ERR(dev, "Failed to transition from PM state: %s to: %s\n",
- to_mhi_pm_state_str(cur_state),
- to_mhi_pm_state_str(MHI_PM_SYS_ERR_PROCESS));
- goto exit_sys_error_transition;
- }
- mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION;
- mhi_cntrl->dev_state = MHI_STATE_RESET;
- /* Wake up threads waiting for state transition */
- wake_up_all(&mhi_cntrl->state_event);
- /* Trigger MHI RESET so that the device will not access host memory */
- if (MHI_REG_ACCESS_VALID(prev_state)) {
- u32 in_reset = -1;
- unsigned long timeout = msecs_to_jiffies(mhi_cntrl->timeout_ms);
- MHI_VERB(dev, "Triggering MHI Reset in device\n");
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET);
- /* Wait for the reset bit to be cleared by the device */
- ret = wait_event_timeout(mhi_cntrl->state_event,
- mhi_read_reg_field(mhi_cntrl,
- mhi_cntrl->regs,
- MHICTRL,
- MHICTRL_RESET_MASK,
- &in_reset) ||
- !in_reset, timeout);
- if (!ret || in_reset) {
- MHI_ERR(dev, "Device failed to exit MHI Reset state\n");
- goto exit_sys_error_transition;
- }
- /*
- * Device will clear BHI_INTVEC as a part of RESET processing,
- * hence re-program it
- */
- mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0);
- }
- MHI_VERB(dev,
- "Waiting for all pending event ring processing to complete\n");
- mhi_event = mhi_cntrl->mhi_event;
- for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
- if (mhi_event->offload_ev)
- continue;
- if (mhi_event->priority == MHI_ER_PRIORITY_HI_SLEEP)
- cancel_work_sync(&mhi_event->work);
- else
- tasklet_kill(&mhi_event->task);
- }
- /* Release lock and wait for all pending threads to complete */
- mutex_unlock(&mhi_cntrl->pm_mutex);
- mhi_misc_disable(mhi_cntrl);
- MHI_VERB(dev, "Waiting for all pending threads to complete\n");
- wake_up_all(&mhi_cntrl->state_event);
- MHI_VERB(dev, "Reset all active channels and remove MHI devices\n");
- device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
- mutex_lock(&mhi_cntrl->pm_mutex);
- WARN_ON(atomic_read(&mhi_cntrl->dev_wake));
- WARN_ON(atomic_read(&mhi_cntrl->pending_pkts));
- /* Reset the ev rings and cmd rings */
- MHI_VERB(dev, "Resetting EV CTXT and CMD CTXT\n");
- mhi_cmd = mhi_cntrl->mhi_cmd;
- cmd_ctxt = mhi_cntrl->mhi_ctxt->cmd_ctxt;
- for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++, cmd_ctxt++) {
- struct mhi_ring *ring = &mhi_cmd->ring;
- ring->rp = ring->base;
- ring->wp = ring->base;
- cmd_ctxt->rp = cmd_ctxt->rbase;
- cmd_ctxt->wp = cmd_ctxt->rbase;
- }
- mhi_event = mhi_cntrl->mhi_event;
- er_ctxt = mhi_cntrl->mhi_ctxt->er_ctxt;
- for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++,
- mhi_event++) {
- struct mhi_ring *ring = &mhi_event->ring;
- /* Skip offload events */
- if (mhi_event->offload_ev)
- continue;
- ring->rp = ring->base;
- ring->wp = ring->base;
- er_ctxt->rp = er_ctxt->rbase;
- er_ctxt->wp = er_ctxt->rbase;
- }
- /* Transition to next state */
- if (MHI_IN_PBL(mhi_get_exec_env(mhi_cntrl))) {
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_POR);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (cur_state != MHI_PM_POR) {
- MHI_ERR(dev, "Error moving to state %s from %s\n",
- to_mhi_pm_state_str(MHI_PM_POR),
- to_mhi_pm_state_str(cur_state));
- goto exit_sys_error_transition;
- }
- next_state = DEV_ST_TRANSITION_PBL;
- } else {
- next_state = DEV_ST_TRANSITION_READY;
- }
- mhi_queue_state_transition(mhi_cntrl, next_state);
- exit_sys_error_transition:
- MHI_VERB(dev, "Exiting with PM state: %s, MHI state: %s\n",
- to_mhi_pm_state_str(mhi_cntrl->pm_state),
- mhi_state_str(mhi_cntrl->dev_state));
- mutex_unlock(&mhi_cntrl->pm_mutex);
- }
- /* Queue a new work item and schedule work */
- int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl,
- enum dev_st_transition state)
- {
- struct state_transition *item = kmalloc(sizeof(*item), GFP_ATOMIC);
- unsigned long flags;
- if (!item)
- return -ENOMEM;
- item->state = state;
- spin_lock_irqsave(&mhi_cntrl->transition_lock, flags);
- list_add_tail(&item->node, &mhi_cntrl->transition_list);
- spin_unlock_irqrestore(&mhi_cntrl->transition_lock, flags);
- queue_work(mhi_cntrl->hiprio_wq, &mhi_cntrl->st_worker);
- return 0;
- }
- /* SYS_ERR worker */
- void mhi_pm_sys_err_handler(struct mhi_controller *mhi_cntrl)
- {
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- /* skip if controller supports RDDM */
- if (mhi_cntrl->rddm_image) {
- MHI_VERB(dev, "Controller supports RDDM, skip SYS_ERROR\n");
- return;
- }
- mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_SYS_ERR);
- }
- /* Device State Transition worker */
- void mhi_pm_st_worker(struct work_struct *work)
- {
- struct state_transition *itr, *tmp;
- LIST_HEAD(head);
- struct mhi_controller *mhi_cntrl = container_of(work,
- struct mhi_controller,
- st_worker);
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- spin_lock_irq(&mhi_cntrl->transition_lock);
- list_splice_tail_init(&mhi_cntrl->transition_list, &head);
- spin_unlock_irq(&mhi_cntrl->transition_lock);
- list_for_each_entry_safe(itr, tmp, &head, node) {
- list_del(&itr->node);
- MHI_VERB(dev, "Handling state transition: %s\n",
- TO_DEV_STATE_TRANS_STR(itr->state));
- switch (itr->state) {
- case DEV_ST_TRANSITION_PBL:
- write_lock_irq(&mhi_cntrl->pm_lock);
- if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state))
- mhi_cntrl->ee = mhi_get_exec_env(mhi_cntrl);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mhi_fw_load_handler(mhi_cntrl);
- break;
- case DEV_ST_TRANSITION_SBL:
- write_lock_irq(&mhi_cntrl->pm_lock);
- mhi_cntrl->ee = MHI_EE_SBL;
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mhi_process_sleeping_events(mhi_cntrl);
- /*
- * The MHI devices are only created when the client
- * device switches its Execution Environment (EE) to
- * either SBL or AMSS states
- */
- mhi_create_devices(mhi_cntrl);
- if (mhi_cntrl->fbc_download)
- mhi_download_amss_image(mhi_cntrl);
- break;
- case DEV_ST_TRANSITION_MISSION_MODE:
- mhi_pm_mission_mode_transition(mhi_cntrl);
- break;
- case DEV_ST_TRANSITION_FP:
- write_lock_irq(&mhi_cntrl->pm_lock);
- mhi_cntrl->ee = MHI_EE_FP;
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mhi_create_devices(mhi_cntrl);
- break;
- case DEV_ST_TRANSITION_READY:
- mhi_ready_state_transition(mhi_cntrl);
- break;
- case DEV_ST_TRANSITION_SYS_ERR:
- mhi_pm_sys_error_transition(mhi_cntrl);
- break;
- case DEV_ST_TRANSITION_DISABLE:
- mhi_pm_disable_transition(mhi_cntrl);
- break;
- default:
- break;
- }
- kfree(itr);
- }
- }
- static bool mhi_in_rddm(struct mhi_controller *mhi_cntrl)
- {
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- if (mhi_cntrl->rddm_image && mhi_get_exec_env(mhi_cntrl) == MHI_EE_RDDM
- && mhi_is_active(mhi_cntrl)) {
- mhi_cntrl->ee = MHI_EE_RDDM;
- MHI_ERR(dev, "RDDM event occurred!\n");
- /* notify critical clients with early notifications */
- mhi_report_error(mhi_cntrl);
- mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM);
- wake_up_all(&mhi_cntrl->state_event);
- return true;
- }
- return false;
- }
- int mhi_pm_suspend(struct mhi_controller *mhi_cntrl)
- {
- struct mhi_chan *itr, *tmp;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- enum mhi_pm_state new_state;
- int ret;
- if (mhi_cntrl->pm_state == MHI_PM_DISABLE)
- return -EINVAL;
- if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))
- return -EIO;
- /* Return busy if there are any pending resources */
- if (atomic_read(&mhi_cntrl->dev_wake) ||
- atomic_read(&mhi_cntrl->pending_pkts))
- return -EBUSY;
- /* Take MHI out of M2 state */
- read_lock_bh(&mhi_cntrl->pm_lock);
- mhi_cntrl->wake_get(mhi_cntrl, false);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- /* finish reg writes */
- mhi_force_reg_write(mhi_cntrl);
- ret = wait_event_timeout(mhi_cntrl->state_event,
- mhi_cntrl->dev_state == MHI_STATE_M0 ||
- mhi_cntrl->dev_state == MHI_STATE_M1 ||
- MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
- msecs_to_jiffies(mhi_cntrl->timeout_ms));
- read_lock_bh(&mhi_cntrl->pm_lock);
- mhi_cntrl->wake_put(mhi_cntrl, false);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
- MHI_ERR(dev,
- "Could not enter M0/M1 state");
- return -EIO;
- }
- /* finish any reg writes before setting M3 */
- mhi_force_reg_write(mhi_cntrl);
- write_lock_irq(&mhi_cntrl->pm_lock);
- if (atomic_read(&mhi_cntrl->dev_wake) ||
- atomic_read(&mhi_cntrl->pending_pkts)) {
- write_unlock_irq(&mhi_cntrl->pm_lock);
- return -EBUSY;
- }
- MHI_VERB(dev, "Allowing M3 transition\n");
- new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3_ENTER);
- if (new_state != MHI_PM_M3_ENTER) {
- write_unlock_irq(&mhi_cntrl->pm_lock);
- MHI_ERR(dev,
- "Error setting to PM state: %s from: %s\n",
- to_mhi_pm_state_str(MHI_PM_M3_ENTER),
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
- return -EIO;
- }
- /* Set MHI to M3 and wait for completion */
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M3);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- MHI_VERB(dev, "Waiting for M3 completion\n");
- ret = wait_event_timeout(mhi_cntrl->state_event,
- mhi_cntrl->dev_state == MHI_STATE_M3 ||
- MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
- msecs_to_jiffies(mhi_cntrl->timeout_ms));
- if (!ret) {
- mhi_debug_reg_dump(mhi_cntrl);
- panic("Timedout waiting for M3 ACK");
- return -EIO;
- } else if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
- MHI_ERR(dev,
- "Did not enter M3 state, MHI state: %s, PM state: %s\n",
- mhi_state_str(mhi_cntrl->dev_state),
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
- return -EIO;
- }
- /* Notify clients about entering LPM */
- list_for_each_entry_safe(itr, tmp, &mhi_cntrl->lpm_chans, node) {
- mutex_lock(&itr->mutex);
- if (itr->mhi_dev)
- mhi_notify(itr->mhi_dev, MHI_CB_LPM_ENTER);
- mutex_unlock(&itr->mutex);
- }
- return 0;
- }
- EXPORT_SYMBOL_GPL(mhi_pm_suspend);
- static int __mhi_pm_resume(struct mhi_controller *mhi_cntrl, bool force)
- {
- struct mhi_chan *itr, *tmp;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- enum mhi_pm_state cur_state;
- int ret;
- MHI_VERB(dev, "Entered with PM state: %s, MHI state: %s\n",
- to_mhi_pm_state_str(mhi_cntrl->pm_state),
- mhi_state_str(mhi_cntrl->dev_state));
- if (mhi_cntrl->pm_state == MHI_PM_DISABLE)
- return 0;
- if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))
- return -EIO;
- if (mhi_get_mhi_state(mhi_cntrl) != MHI_STATE_M3) {
- dev_warn(dev, "Resuming from non M3 state (%s)\n",
- mhi_state_str(mhi_get_mhi_state(mhi_cntrl)));
- return -EINVAL;
- }
- if (mhi_in_rddm(mhi_cntrl))
- return 0;
- /* Notify clients about exiting LPM */
- list_for_each_entry_safe(itr, tmp, &mhi_cntrl->lpm_chans, node) {
- mutex_lock(&itr->mutex);
- if (itr->mhi_dev)
- mhi_notify(itr->mhi_dev, MHI_CB_LPM_EXIT);
- mutex_unlock(&itr->mutex);
- }
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3_EXIT);
- if (cur_state != MHI_PM_M3_EXIT) {
- write_unlock_irq(&mhi_cntrl->pm_lock);
- MHI_LOG(dev,
- "Error setting to PM state: %s from: %s\n",
- to_mhi_pm_state_str(MHI_PM_M3_EXIT),
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
- return -EIO;
- }
- /* Set MHI to M0 and wait for completion */
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M0);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- ret = wait_event_timeout(mhi_cntrl->state_event,
- mhi_cntrl->dev_state == MHI_STATE_M0 ||
- mhi_cntrl->dev_state == MHI_STATE_M2 ||
- MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
- msecs_to_jiffies(mhi_cntrl->timeout_ms));
- if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
- if (mhi_in_rddm(mhi_cntrl))
- return 0;
- MHI_ERR(dev,
- "Did not enter M0 state, MHI state: %s, PM state: %s\n",
- mhi_state_str(mhi_cntrl->dev_state),
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
- return -EIO;
- }
- return 0;
- }
- int mhi_pm_resume(struct mhi_controller *mhi_cntrl)
- {
- return __mhi_pm_resume(mhi_cntrl, false);
- }
- EXPORT_SYMBOL_GPL(mhi_pm_resume);
- int mhi_pm_resume_force(struct mhi_controller *mhi_cntrl)
- {
- return __mhi_pm_resume(mhi_cntrl, true);
- }
- EXPORT_SYMBOL_GPL(mhi_pm_resume_force);
- int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl)
- {
- int ret;
- /* Wake up the device */
- read_lock_bh(&mhi_cntrl->pm_lock);
- if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
- read_unlock_bh(&mhi_cntrl->pm_lock);
- return -EIO;
- }
- mhi_cntrl->wake_get(mhi_cntrl, true);
- if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state))
- mhi_trigger_resume(mhi_cntrl);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- mhi_force_reg_write(mhi_cntrl);
- ret = wait_event_timeout(mhi_cntrl->state_event,
- mhi_cntrl->pm_state == MHI_PM_M0 ||
- MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
- msecs_to_jiffies(mhi_cntrl->timeout_ms));
- if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
- read_lock_bh(&mhi_cntrl->pm_lock);
- mhi_cntrl->wake_put(mhi_cntrl, false);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- return -EIO;
- }
- return 0;
- }
- /* Assert device wake db */
- static void mhi_assert_dev_wake(struct mhi_controller *mhi_cntrl, bool force)
- {
- unsigned long flags;
- /*
- * If force flag is set, then increment the wake count value and
- * ring wake db
- */
- if (unlikely(force)) {
- spin_lock_irqsave(&mhi_cntrl->wlock, flags);
- atomic_inc(&mhi_cntrl->dev_wake);
- if (MHI_WAKE_DB_FORCE_SET_VALID(mhi_cntrl->pm_state) &&
- !mhi_cntrl->wake_set) {
- if (mhi_cntrl->db_access & MHI_PM_M2)
- mhi_write_db(mhi_cntrl, mhi_cntrl->wake_db, 1);
- else
- mhi_write_offload_wakedb(mhi_cntrl, 1);
- mhi_cntrl->wake_set = true;
- }
- spin_unlock_irqrestore(&mhi_cntrl->wlock, flags);
- } else {
- /*
- * If resources are already requested, then just increment
- * the wake count value and return
- */
- if (likely(atomic_add_unless(&mhi_cntrl->dev_wake, 1, 0)))
- return;
- spin_lock_irqsave(&mhi_cntrl->wlock, flags);
- if ((atomic_inc_return(&mhi_cntrl->dev_wake) == 1) &&
- MHI_WAKE_DB_SET_VALID(mhi_cntrl->pm_state) &&
- !mhi_cntrl->wake_set) {
- if (mhi_cntrl->db_access & MHI_PM_M2)
- mhi_write_db(mhi_cntrl, mhi_cntrl->wake_db, 1);
- else
- mhi_write_offload_wakedb(mhi_cntrl, 1);
- mhi_cntrl->wake_set = true;
- }
- spin_unlock_irqrestore(&mhi_cntrl->wlock, flags);
- }
- }
- /* De-assert device wake db */
- static void mhi_deassert_dev_wake(struct mhi_controller *mhi_cntrl,
- bool override)
- {
- unsigned long flags;
- /*
- * Only continue if there is a single resource, else just decrement
- * and return
- */
- if (likely(atomic_add_unless(&mhi_cntrl->dev_wake, -1, 1)))
- return;
- spin_lock_irqsave(&mhi_cntrl->wlock, flags);
- if ((atomic_dec_return(&mhi_cntrl->dev_wake) == 0) &&
- MHI_WAKE_DB_CLEAR_VALID(mhi_cntrl->pm_state) && !override &&
- mhi_cntrl->wake_set) {
- if (mhi_cntrl->db_access & MHI_PM_M2)
- mhi_write_db(mhi_cntrl, mhi_cntrl->wake_db, 0);
- else
- mhi_write_offload_wakedb(mhi_cntrl, 0);
- mhi_cntrl->wake_set = false;
- }
- spin_unlock_irqrestore(&mhi_cntrl->wlock, flags);
- }
- int mhi_async_power_up(struct mhi_controller *mhi_cntrl)
- {
- struct mhi_event *mhi_event = mhi_cntrl->mhi_event;
- enum mhi_state state;
- enum mhi_ee_type current_ee;
- enum dev_st_transition next_state;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- u32 interval_us = 25000; /* poll register field every 25 milliseconds */
- int ret, i;
- MHI_LOG(dev, "Requested to power ON\n");
- /* Supply default wake routines if not provided by controller driver */
- if (!mhi_cntrl->wake_get || !mhi_cntrl->wake_put ||
- !mhi_cntrl->wake_toggle) {
- mhi_cntrl->wake_get = mhi_assert_dev_wake;
- mhi_cntrl->wake_put = mhi_deassert_dev_wake;
- mhi_cntrl->wake_toggle = (mhi_cntrl->db_access & MHI_PM_M2) ?
- mhi_toggle_dev_wake_nop : mhi_toggle_dev_wake;
- }
- mutex_lock(&mhi_cntrl->pm_mutex);
- mhi_cntrl->pm_state = MHI_PM_DISABLE;
- /* Setup BHI INTVEC */
- write_lock_irq(&mhi_cntrl->pm_lock);
- mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0);
- mhi_cntrl->pm_state = MHI_PM_POR;
- mhi_cntrl->ee = MHI_EE_MAX;
- current_ee = mhi_get_exec_env(mhi_cntrl);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- /* Confirm that the device is in valid exec env */
- if (!MHI_POWER_UP_CAPABLE(current_ee)) {
- MHI_ERR(dev, "%s is not a valid EE for power on\n",
- TO_MHI_EXEC_STR(current_ee));
- ret = -EIO;
- goto error_exit;
- }
- state = mhi_get_mhi_state(mhi_cntrl);
- MHI_VERB(dev, "Attempting power on with EE: %s, state: %s\n",
- TO_MHI_EXEC_STR(current_ee), mhi_state_str(state));
- if (state == MHI_STATE_SYS_ERR) {
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET);
- ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
- MHICTRL_RESET_MASK, 0, interval_us);
- if (ret) {
- MHI_LOG(dev, "Failed to reset MHI due to syserr state\n");
- goto error_exit;
- }
- /*
- * device cleares INTVEC as part of RESET processing,
- * re-program it
- */
- mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0);
- }
- /* IRQs have been requested during probe, so we just need to enable them. */
- enable_irq(mhi_cntrl->irq[0]);
- for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
- if (mhi_event->offload_ev)
- continue;
- enable_irq(mhi_cntrl->irq[mhi_event->irq]);
- }
- /* Transition to next state */
- next_state = MHI_IN_PBL(current_ee) ?
- DEV_ST_TRANSITION_PBL : DEV_ST_TRANSITION_READY;
- mhi_queue_state_transition(mhi_cntrl, next_state);
- mutex_unlock(&mhi_cntrl->pm_mutex);
- MHI_LOG(dev, "Power on setup success\n");
- return 0;
- error_exit:
- mhi_cntrl->pm_state = MHI_PM_DISABLE;
- mutex_unlock(&mhi_cntrl->pm_mutex);
- return ret;
- }
- EXPORT_SYMBOL_GPL(mhi_async_power_up);
- void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
- {
- enum mhi_pm_state cur_state, transition_state;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- mutex_lock(&mhi_cntrl->pm_mutex);
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_cntrl->pm_state;
- if (cur_state == MHI_PM_DISABLE) {
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mutex_unlock(&mhi_cntrl->pm_mutex);
- return; /* Already powered down */
- }
- /* If it's not a graceful shutdown, force MHI to linkdown state */
- transition_state = (graceful) ? MHI_PM_SHUTDOWN_PROCESS :
- MHI_PM_LD_ERR_FATAL_DETECT;
- cur_state = mhi_tryset_pm_state(mhi_cntrl, transition_state);
- if (cur_state != transition_state) {
- MHI_ERR(dev, "Failed to move to state: %s from: %s\n",
- to_mhi_pm_state_str(transition_state),
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
- /* Force link down or error fatal detected state */
- mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT;
- }
- /* mark device inactive to avoid any further host processing */
- mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION;
- mhi_cntrl->dev_state = MHI_STATE_RESET;
- wake_up_all(&mhi_cntrl->state_event);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mutex_unlock(&mhi_cntrl->pm_mutex);
- mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE);
- /* Wait for shutdown to complete */
- flush_work(&mhi_cntrl->st_worker);
- disable_irq(mhi_cntrl->irq[0]);
- if (mhi_cntrl->fbc_image)
- mhi_free_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image);
- }
- EXPORT_SYMBOL_GPL(mhi_power_down);
- int mhi_sync_power_up(struct mhi_controller *mhi_cntrl)
- {
- int ret = mhi_async_power_up(mhi_cntrl);
- if (ret)
- return ret;
- wait_event_timeout(mhi_cntrl->state_event,
- MHI_IN_MISSION_MODE(mhi_cntrl->ee) ||
- MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
- msecs_to_jiffies(mhi_cntrl->timeout_ms));
- ret = (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) ? 0 : -ETIMEDOUT;
- if (ret && !mhi_cntrl->rddm_image)
- mhi_power_down(mhi_cntrl, false);
- return ret;
- }
- EXPORT_SYMBOL(mhi_sync_power_up);
- int mhi_force_rddm_mode(struct mhi_controller *mhi_cntrl)
- {
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
- int ret;
- /* Check if device is already in RDDM */
- if (mhi_cntrl->ee == MHI_EE_RDDM)
- return 0;
- MHI_VERB(dev, "Triggering SYS_ERR to force RDDM state\n");
- mhi_set_mhi_state(mhi_cntrl, MHI_STATE_SYS_ERR);
- /* Wait for RDDM event */
- ret = wait_event_timeout(mhi_cntrl->state_event,
- mhi_cntrl->ee == MHI_EE_RDDM,
- msecs_to_jiffies(mhi_cntrl->timeout_ms));
- ret = ret ? 0 : -EIO;
- return ret;
- }
- EXPORT_SYMBOL_GPL(mhi_force_rddm_mode);
- void mhi_device_get(struct mhi_device *mhi_dev)
- {
- struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
- mhi_dev->dev_wake++;
- read_lock_bh(&mhi_cntrl->pm_lock);
- if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state))
- mhi_trigger_resume(mhi_cntrl);
- mhi_cntrl->wake_get(mhi_cntrl, true);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- }
- EXPORT_SYMBOL_GPL(mhi_device_get);
- int mhi_device_get_sync(struct mhi_device *mhi_dev)
- {
- struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
- int ret;
- ret = __mhi_device_get_sync(mhi_cntrl);
- if (!ret)
- mhi_dev->dev_wake++;
- return ret;
- }
- EXPORT_SYMBOL_GPL(mhi_device_get_sync);
- void mhi_device_put(struct mhi_device *mhi_dev)
- {
- struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
- mhi_dev->dev_wake--;
- read_lock_bh(&mhi_cntrl->pm_lock);
- if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state))
- mhi_trigger_resume(mhi_cntrl);
- mhi_cntrl->wake_put(mhi_cntrl, false);
- read_unlock_bh(&mhi_cntrl->pm_lock);
- }
- EXPORT_SYMBOL_GPL(mhi_device_put);
|