void __ubmodem_voice_control_cleanup(struct ubmodem_s *modem)
{
  if (!modem->voice_urcs_registered)
    return;

  modem_voice_disconnected(modem, false);
  ubmodem_audio_cleanup(modem);

  __ubparser_unregister_response_handler(&modem->parser, urc_ATpCRING.name);
  __ubparser_unregister_response_handler(&modem->parser, urc_ATpUCALLSTAT.name);
  __ubparser_unregister_response_handler(&modem->parser, urc_ATpCLIP.name);
  modem->voice_urcs_registered = false;

  memset(&modem->voice, 0, sizeof(modem->voice));
}
void __ubmodem_network_cleanup(struct ubmodem_s *modem)
{
  if (modem->level == UBMODEM_LEVEL_GPRS)
    {
      /* Uninitialize GRPS */

      __ubmodem_gprs_cleanup(modem);
    }

  if (modem->level >= UBMODEM_LEVEL_NETWORK)
    {
      if (modem->creg_timer_id != -1)
        {
          /* Unregister timeout. */

          __ubmodem_remove_timer(modem, modem->creg_timer_id);

          modem->creg_timer_id = -1;

          ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, false);
        }

      if (modem->creg_urc_registered)
        {
          /* Unregister +CREG URC */

          __ubparser_unregister_response_handler(&modem->parser,
                                                 urc_ATpCREG.name);
          modem->creg_urc_registered = false;
        }
    }
}
static void network_registration_successed(struct ubmodem_s *modem,
                                           struct modem_sub_setup_network_s *sub)
{
  /* Switch +CREG URC handler to monitor variant. */

  __ubparser_unregister_response_handler(&modem->parser, urc_ATpCREG.name);
  __ubparser_register_response_handler(&modem->parser, &urc_ATpCREG,
                                       monitor_urc_pCREG_handler, modem, true);
  DEBUGASSERT(modem->creg_urc_registered);

  /* Report successful cellular network connection. */

  sub->keep_creg_urc = true;
  __ubmodem_reached_level(modem, UBMODEM_LEVEL_NETWORK);
}
static void setup_network_cleanup(struct ubmodem_s *modem)
{
  struct modem_sub_setup_network_s *sub = &modem->sub.setup_network;

  if (modem->creg_timer_id != -1)
    {
      /* Unregister timeout. */

      __ubmodem_remove_timer(modem, modem->creg_timer_id);

      modem->creg_timer_id = -1;
    }

  if (modem->creg_urc_registered && !sub->keep_creg_urc)
    {
      /* Unregister +CREG URC */

      __ubparser_unregister_response_handler(&modem->parser,
                                             urc_ATpCREG.name);
      modem->creg_urc_registered = false;
    }
}
static void monitor_urc_pCREG_handler(struct ubmodem_s *modem,
                                      const struct at_cmd_def_s *cmd,
                                      const struct at_resp_info_s *info,
                                      const uint8_t *resp_stream,
                                      size_t stream_len, void *priv)
{
  int8_t val;
  int ret;

  (void)ret;

  /*
   * Response handler for +CREG URC.
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpCREG);

  if (info->status != RESP_STATUS_URC)
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &val))
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  dbg("Network registration URC, +CREG=%d.\n", val);

  if (val >= 0 && val < ARRAY_SIZE(creg_strings))
    {
      dbg("%s\n", creg_strings[val]);
    }
  else
    {
      dbg("Unknown +CREG status: %d\n", val);
    }

  if (modem->creg_timer_id >= 0)
    {
      __ubmodem_remove_timer(modem, modem->creg_timer_id);

      modem->creg_timer_id = -1;

      ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, false);
    }

  switch (val)
    {
      default: /* Unknown? */
      case 1: /* Switched to home network? */
      case 5: /* Switched to roaming? */
        return;

      case 2: /* Looking for connection. */
#ifdef CONFIG_UBMODEM_CREG_WAIT_NETWORK_TO_RESTORE
        /* Start connection search timeout timer. */

        ret = __ubmodem_set_timer(modem, MODEM_CMD_NETWORK_TIMEOUT * 100,
                                  &network_reregister_timer_handler, modem);
        MODEM_DEBUGASSERT(modem, ret != ERROR);

        modem->creg_timer_id = ret;

        ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, true);

        return;
#else
        /* Reset network through SIM reinitialization. Appears to work
         * better in roaming conditions. TODO: Study why, do we understand
         * function of modem in low-signal environments properly? */
#endif

      case 3: /* Registration failed? */
      case 4: /* Unknown registration error? */
      case 0: /* MT not searching for connection. */

        /* Unregister +CREG URC */

        __ubparser_unregister_response_handler(&modem->parser,
                                               urc_ATpCREG.name);
        modem->creg_urc_registered = false;

        /* Issue new task. We cannot issue new state machine work from
         * URC 'context' as other work might be active in main state
         * machine. Task will be run with main state machine in proper
         * state. */

        __ubmodem_add_task(modem, retry_network_through_sim, NULL);

        return;
    }
}
static void urc_pCREG_handler(struct ubmodem_s *modem,
                              const struct at_cmd_def_s *cmd,
                              const struct at_resp_info_s *info,
                              const uint8_t *resp_stream,
                              size_t stream_len, void *priv)
{
  int8_t val;
  int err;
  int ret;

  /*
   * Response handler for +CREG URC.
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpCREG);

  if (info->status != RESP_STATUS_URC)
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &val))
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  dbg("Network registration URC, +CREG=%d.\n", val);

  if (val >= 0 && val < ARRAY_SIZE(creg_strings))
    {
      dbg("%s\n", creg_strings[val]);
    }
  else
    {
      dbg("Unknown +CREG status: %d\n", val);
    }

  if (modem->level < UBMODEM_LEVEL_NETWORK)
    {
      struct modem_sub_setup_network_s *sub = &modem->sub.setup_network;

      DEBUGASSERT(modem->state == MODEM_STATE_IN_SUBSTATE);

      if (sub->network_state < NETWORK_SETUP_WAITING_NETWORK_REGISTRATION)
        {
          /* Not ready yet, spurious +CREG URC? */

          ubdbg("Ignored spurious +CREG URC.\n");

          return;
        }

      switch (val)
      {
        case 1:
        case 5:
          /*
           * If 'val' is 1, registered: home network.
           * If 'val' is 5, registered: roamed.
           */

          if (sub->network_state == NETWORK_SETUP_RETRYING_NETWORK_REGISTRATION)
            {
              /* Cannot do state transition to UBMODEM_LEVEL_NETWORK right now
               * as still retrying AT+COPS=0. Defer handling to
               * retry_ATpCOPS_handler. */

              sub->received_creg_while_retrying = val;
            }
          else
            {
              /* Report successful cellular network connection. */

              sub->keep_creg_urc = true;
              __ubmodem_reached_level(modem, UBMODEM_LEVEL_NETWORK);
            }

          return;

        case 0:
          /*
           * MT not searching for connection.
           */

          if (sub->network_state == NETWORK_SETUP_WAITING_NETWORK_REGISTRATION)
            {
              /* Keep retrying automatic network registration until timeout. */

              sub->network_state = NETWORK_SETUP_RETRYING_NETWORK_REGISTRATION;
              sub->received_creg_while_retrying = -1;

              err = __ubmodem_send_cmd(modem, &cmd_ATpCOPS,
                                       retry_ATpCOPS_handler, sub, "%s", "=0");
              MODEM_DEBUGASSERT(modem, err == OK);
            }

          return;

        case 4:
          /*
           * If 'val' is 4, unknown registration error.
           */

          /* no break */

        case 3:
          /*
           * If 'val' is 3, registration was denied.
           */

          /* Perform fail-over code from timeout handler. */

          network_register_timer_handler(modem, -1, sub);

          return;

        case 2:
          /*
           * If 'val' is 2, modem is trying to register.
           */

          return;

        default:
          /*
           * Unknown.
           */

          return;
      }
    }
  else
    {
      /* Current level is UBMODEM_LEVEL_NETWORK or higher. */

      switch (val)
        {
          default: /* Unknown? */
          case 1: /* Switched to home network? */
          case 5: /* Switched to roaming? */

            if (modem->creg_timer_id >= 0)
              {
                __ubmodem_remove_timer(modem, modem->creg_timer_id);
                modem->creg_timer_id = -1;
              }

            return;

          case 2: /* Looking for connection. */

            /* Start connection search timeout timer. */

            ret = __ubmodem_set_timer(modem, MODEM_CMD_NETWORK_TIMEOUT * 100,
                                      &network_reregister_timer_handler, modem);
            if (ret == ERROR)
              {
                /* Error here? Add assert? Or just try bailout? */

                MODEM_DEBUGASSERT(modem, false);

                (void)network_reregister_timer_handler(modem, -1, modem);
                return;
              }

            modem->creg_timer_id = ret;

            return;

          case 3: /* Registration failed? */
          case 4: /* Unknown registration error? */
          case 0: /* MT not searching for connection. */

            /* Unregister +CREG URC */

            __ubparser_unregister_response_handler(&modem->parser,
                                                   urc_ATpCREG.name);
            modem->creg_urc_registered = false;

            /* Issue new task. We cannot issue new state machine work from
             * URC 'context' as other work might be active in main state
             * machine. Task will be run with main state machine in proper
             * state. */

            __ubmodem_add_task(modem, retry_network_through_sim, NULL);

            return;
        }
    }
}