/** @brief forbid CP from going to sleep Wakes up a CP if it can sleep and increases the "ref_cnt" counter in the mem_link_device instance. @param mld the pointer to a mem_link_device instance @remark CAUTION!!! permit_cp_sleep() MUST be invoked after forbid_cp_sleep() success to decrease the "ref_cnt" counter. */ static void forbid_cp_sleep(struct mem_link_device *mld) { struct link_device *ld = &mld->link_dev; int ap_status = gpio_get_value(mld->gpio_ap_status); int cp_wakeup = gpio_get_value(mld->gpio_cp_wakeup); unsigned long flags; spin_lock_irqsave(&mld->pm_lock, flags); atomic_inc(&mld->ref_cnt); gpio_set_value(mld->gpio_ap_status, 1); gpio_set_value(mld->gpio_cp_wakeup, 1); if (work_pending(&mld->cp_sleep_dwork.work)) cancel_delayed_work(&mld->cp_sleep_dwork); spin_unlock_irqrestore(&mld->pm_lock, flags); if (!ap_status || !cp_wakeup) print_pm_status(mld); if (check_link_status(mld) < 0) { print_pm_status(mld); mif_err("%s: ERR! check_link_status fail\n", ld->name); mem_forced_cp_crash(mld); } }
static void stop_pm(struct mem_link_device *mld) { print_pm_status(mld); mif_disable_irq(&mld->irq_ap_wakeup); mif_disable_irq(&mld->irq_cp_status); }
static irqreturn_t cp_status_handler(int irq, void *data) { struct mem_link_device *mld = (struct mem_link_device *)data; struct link_device *ld = &mld->link_dev; struct modem_ctl *mc = ld->mc; int cp_status = gpio_get_value(mld->gpio_cp_status); unsigned long flags; spin_lock_irqsave(&mld->pm_lock, flags); change_irq_type(irq, cp_status); if (!cp_online(mc)) goto exit; if (cp_status) { if (!wake_lock_active(&mld->cp_wlock)) wake_lock(&mld->cp_wlock); } else { gpio_set_value(mld->gpio_ap_status, 0); if (wake_lock_active(&mld->cp_wlock)) wake_unlock(&mld->cp_wlock); } exit: print_pm_status(mld); spin_unlock_irqrestore(&mld->pm_lock, flags); return IRQ_HANDLED; }
static void release_cp_wakeup(struct work_struct *ws) { struct mem_link_device *mld; int i; unsigned long flags; mld = container_of(ws, struct mem_link_device, cp_sleep_dwork.work); if (work_pending(&mld->cp_sleep_dwork.work)) cancel_delayed_work(&mld->cp_sleep_dwork); spin_lock_irqsave(&mld->pm_lock, flags); i = atomic_read(&mld->ref_cnt); spin_unlock_irqrestore(&mld->pm_lock, flags); if (i > 0) goto reschedule; if (gpio_get_value(mld->gpio_ap_wakeup) == 0) { gpio_set_value(mld->gpio_cp_wakeup, 0); gpio_set_value(mld->gpio_ap_status, 0); } #if 1 print_pm_status(mld); #endif return; reschedule: queue_delayed_work(system_nrt_wq, &mld->cp_sleep_dwork, msecs_to_jiffies(sleep_timeout)); }
/** @brief finalize handling the PHONE_START command from CP @param mld the pointer to a mem_link_device instance */ static void finalize_cp_start(struct mem_link_device *mld) { int ap_wakeup = gpio_get_value(mld->gpio_ap_wakeup); int cp_status = gpio_get_value(mld->gpio_cp_status); change_irq_type(mld->irq_ap_wakeup.num, ap_wakeup); change_irq_type(mld->irq_cp_status.num, cp_status); if (ap_wakeup) { if (wake_lock_active(&mld->ap_wlock)) wake_lock(&mld->ap_wlock); } else { if (wake_lock_active(&mld->ap_wlock)) wake_unlock(&mld->ap_wlock); } if (cp_status) { if (!wake_lock_active(&mld->ap_wlock)) wake_lock(&mld->cp_wlock); } else { if (wake_lock_active(&mld->ap_wlock)) wake_unlock(&mld->cp_wlock); } print_pm_status(mld); }
/** @brief interrupt handler for a wakeup interrupt 1) Reads the interrupt value\n 2) Performs interrupt handling\n @param irq the IRQ number @param data the pointer to a data */ static irqreturn_t ap_wakeup_handler(int irq, void *data) { struct mem_link_device *mld = (struct mem_link_device *)data; struct link_device *ld = &mld->link_dev; int ap_wakeup = gpio_get_value(mld->gpio_ap_wakeup); int ap_status = gpio_get_value(mld->gpio_ap_status); s5p_change_irq_type(irq, ap_wakeup); if (!cp_online(ld->mc)) goto exit; if (work_pending(&mld->cp_sleep_dwork.work)) __cancel_delayed_work(&mld->cp_sleep_dwork); print_pm_status(mld); if (ap_wakeup) { if (!wake_lock_active(&mld->ap_wlock)) wake_lock(&mld->ap_wlock); if (!c2c_suspended() && !ap_status) gpio_set_value(mld->gpio_ap_status, 1); } else { if (wake_lock_active(&mld->ap_wlock)) wake_unlock(&mld->ap_wlock); queue_delayed_work(system_nrt_wq, &mld->cp_sleep_dwork, msecs_to_jiffies(CP_WAKEUP_HOLD_TIME)); } exit: return IRQ_HANDLED; }
static inline int check_link_status(struct mem_link_device *mld) { unsigned int magic = get_magic(mld); int cnt; if (gpio_get_value(mld->gpio_cp_status) != 0 && magic == MEM_IPC_MAGIC) return 0; cnt = 0; while (gpio_get_value(mld->gpio_cp_status) == 0) { if (gpio_get_value(mld->gpio_ap_status) == 0) { print_pm_status(mld); gpio_set_value(mld->gpio_ap_status, 1); } cnt++; if (cnt >= 100) { mif_err("ERR! cp_status != 1 (cnt %d)\n", cnt); return -EACCES; } if (in_interrupt()) udelay(100); else usleep_range(100, 200); } cnt = 0; while (1) { magic = get_magic(mld); if (magic == MEM_IPC_MAGIC) break; cnt++; if (cnt >= 100) { mif_err("ERR! magic 0x%X != IPC_MAGIC (cnt %d)\n", magic, cnt); return -EACCES; } if (in_interrupt()) udelay(100); else usleep_range(100, 200); } return 0; }
/** @brief interrupt handler for a MIPI-LLI IPC interrupt 1) Get a free mst buffer\n 2) Reads the RXQ status and saves the status to the mst buffer\n 3) Saves the interrupt value to the mst buffer\n 4) Invokes mem_irq_handler that is common to all memory-type interfaces\n @param data the pointer to a mem_link_device instance @param intr the interrupt value */ static void lli_irq_handler(void *data, u32 intr) { struct mem_link_device *mld = (struct mem_link_device *)data; struct mst_buff *msb; /* Prohibit CP from going to sleep */ if (gpio_get_value(mld->gpio_cp_status) == 0 || gpio_get_value(mld->gpio_ap_status) == 0) print_pm_status(mld); msb = mem_take_snapshot(mld, RX); if (!msb) return; msb->snapshot.int2ap = (u16)intr; mem_irq_handler(mld, msb); }
static void start_pm(struct mem_link_device *mld) { if (pm_enable) { int ap_wakeup = gpio_get_value(mld->gpio_ap_wakeup); int cp_status = gpio_get_value(mld->gpio_cp_status); print_pm_status(mld); change_irq_type(mld->irq_ap_wakeup.num, ap_wakeup); mif_enable_irq(&mld->irq_ap_wakeup); change_irq_type(mld->irq_cp_status.num, cp_status); mif_enable_irq(&mld->irq_cp_status); } else { wake_lock(&mld->ap_wlock); } }
static irqreturn_t cp_status_handler(int irq, void *data) { struct mem_link_device *mld = (struct mem_link_device *)data; struct link_device *ld = &mld->link_dev; int cp_status = gpio_get_value(mld->gpio_cp_status); unsigned long flags; spin_lock_irqsave(&mld->pm_lock, flags); s5p_change_irq_type(irq, cp_status); if (!cp_online(ld->mc)) goto exit; print_pm_status(mld); if (cp_status) { if (!wake_lock_active(&mld->cp_wlock)) wake_lock(&mld->cp_wlock); } else { if (atomic_read(&mld->ref_cnt) > 0) { /* ** This status means that IPC TX is in progress from AP ** to CP. So, CP_WAKEUP must be set to 1. Otherwise, it ** is a critically erroneous status. */ if (gpio_get_value(mld->gpio_cp_wakeup) == 0) { mif_err("%s: ERR! cp_wakeup == 0\n", ld->name); goto exit; } /* CP_STATUS will be reset to 1 soon due to CP_WAKEUP.*/ } else { gpio_set_value(mld->gpio_ap_status, 0); if (wake_lock_active(&mld->cp_wlock)) wake_unlock(&mld->cp_wlock); } } exit: spin_unlock_irqrestore(&mld->pm_lock, flags); return IRQ_HANDLED; }
/** @brief interrupt handler for a wakeup interrupt 1) Reads the interrupt value\n 2) Performs interrupt handling\n @param irq the IRQ number @param data the pointer to a data */ static irqreturn_t ap_wakeup_interrupt(int irq, void *data) { struct mem_link_device *mld = (struct mem_link_device *)data; int ap_wakeup = gpio_get_value(mld->gpio_ap_wakeup); int cp_wakeup = gpio_get_value(mld->gpio_cp_wakeup); int cpu = raw_smp_processor_id(); change_irq_type(irq, ap_wakeup); if (work_pending(&mld->cp_sleep_dwork.work)) cancel_delayed_work(&mld->cp_sleep_dwork); if (ap_wakeup) { mld->last_cp2ap_intr = cpu_clock(cpu); if (!cp_wakeup) gpio_set_value(mld->gpio_cp_wakeup, 1); if (!wake_lock_active(&mld->ap_wlock)) wake_lock(&mld->ap_wlock); if (mipi_lli_get_link_status() == LLI_UNMOUNTED) mipi_lli_set_link_status(LLI_WAITFORMOUNT); if (!mipi_lli_suspended()) gpio_set_value(mld->gpio_ap_status, 1); } else { if (wake_lock_active(&mld->ap_wlock)) wake_unlock(&mld->ap_wlock); if (mipi_lli_get_link_status() & LLI_WAITFORMOUNT) mipi_lli_set_link_status(LLI_UNMOUNTED); queue_delayed_work(system_nrt_wq, &mld->cp_sleep_dwork, msecs_to_jiffies(sleep_timeout)); } print_pm_status(mld); return IRQ_HANDLED; }
/** @brief forbid CP from going to sleep Wakes up a CP if it can sleep and increases the "ref_cnt" counter in the mem_link_device instance. @param mld the pointer to a mem_link_device instance @remark CAUTION!!! permit_cp_sleep() MUST be invoked after forbid_cp_sleep() success to decrease the "ref_cnt" counter. */ static void forbid_cp_sleep(struct mem_link_device *mld) { int ref_cnt; unsigned long flags; int cp_wakeup; spin_lock_irqsave(&mld->pm_lock, flags); if (work_pending(&mld->cp_sleep_dwork.work)) cancel_delayed_work(&mld->cp_sleep_dwork); ref_cnt = atomic_inc_return(&mld->ref_cnt); mif_debug("ref_cnt %d\n", ref_cnt); cp_wakeup = gpio_get_value(mld->gpio_cp_wakeup); gpio_set_value(mld->gpio_cp_wakeup, 1); if (cp_wakeup == 0) print_pm_status(mld); spin_unlock_irqrestore(&mld->pm_lock, flags); }
static void release_cp_wakeup(struct work_struct *ws) { struct mem_link_device *mld; struct link_device *ld; int i; unsigned long flags; mld = container_of(ws, struct mem_link_device, cp_sleep_dwork.work); if (work_pending(&mld->cp_sleep_dwork.work)) cancel_delayed_work(&mld->cp_sleep_dwork); spin_lock_irqsave(&mld->pm_lock, flags); i = atomic_read(&mld->ref_cnt); spin_unlock_irqrestore(&mld->pm_lock, flags); if (i > 0) goto reschedule; /* * If there is any IPC message remained in a TXQ, AP must prevent CP * from going to sleep. */ ld = &mld->link_dev; for (i = 0; i < MAX_SIPC5_DEVICES; i++) { if (ld->skb_txq[i]->qlen > 0) goto reschedule; } if (gpio_get_value(mld->gpio_ap_wakeup)) goto reschedule; gpio_set_value(mld->gpio_cp_wakeup, 0); print_pm_status(mld); return; reschedule: queue_delayed_work(system_nrt_wq, &mld->cp_sleep_dwork, msecs_to_jiffies(CP_WAKEUP_HOLD_TIME)); }
static int init_pm(struct mem_link_device *mld) { int err; unsigned int gpio; unsigned int irq_ap_wakeup; unsigned int irq_cp_status; unsigned long flags; gpio_set_value(mld->gpio_ap_status, 0); /* Retrieve GPIO#, IRQ#, and IRQ flags for PM */ gpio = mld->gpio_ap_wakeup; irq_ap_wakeup = gpio_to_irq(gpio); mif_err("CP2AP_WAKEUP GPIO:%d IRQ:%d\n", gpio, irq_ap_wakeup); gpio = mld->gpio_cp_wakeup; mif_err("AP2CP_WAKEUP GPIO:%d\n", gpio); gpio = mld->gpio_cp_status; irq_cp_status = gpio_to_irq(gpio); mif_err("CP2AP_STATUS GPIO:%d IRQ:%d\n", gpio, irq_cp_status); gpio = mld->gpio_ap_status; mif_err("AP2CP_STATUS GPIO:%d\n", gpio); /* Initialize locks, completions, bottom halves, etc. */ wake_lock_init(&mld->ap_wlock, WAKE_LOCK_SUSPEND, "lli_ap_wlock"); wake_lock_init(&mld->cp_wlock, WAKE_LOCK_SUSPEND, "lli_cp_wlock"); INIT_DELAYED_WORK(&mld->cp_sleep_dwork, release_cp_wakeup); spin_lock_init(&mld->pm_lock); spin_lock_init(&mld->sig_lock); atomic_set(&mld->ref_cnt, 0); /* Enable IRQs for PM */ print_pm_status(mld); flags = (IRQF_TRIGGER_HIGH | IRQF_ONESHOT); mif_init_irq(&mld->irq_ap_wakeup, irq_ap_wakeup, "lli_cp2ap_wakeup", flags); err = mif_request_irq(&mld->irq_ap_wakeup, ap_wakeup_interrupt, mld); if (err) return err; mif_disable_irq(&mld->irq_ap_wakeup); mif_init_irq(&mld->irq_cp_status, irq_cp_status, "lli_cp2ap_status", flags); err = mif_request_irq(&mld->irq_cp_status, cp_status_handler, mld); if (err) return err; mif_disable_irq(&mld->irq_cp_status); return 0; }