text: // Copyright 2018 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <zircon/device/audio.h>
  
  #include "intel-dsp-topology.h"
  #include "intel-dsp.h"
  
  namespace audio {
  namespace intel_hda {
  
  namespace {
  // Module config parameters extracted from kbl_i2s_chrome.conf
  
  // Set up 2 pipelines for system playback:
  // 1. copier[host DMA]->mixin
  // 2. mixout->copier[I2S DMA]
  // 2 Pipelines are needed because only one instance of a module can exist in a pipeline.
  constexpr uint8_t PIPELINE0_ID = 0;
  constexpr uint8_t PIPELINE1_ID = 1;
  
  // Set up 2 pipelines for system capture:
  // 2. copier[I2S DMA]->mixin
  // 3. mixout->copier[host DMA]
  // 2 Pipelines are needed because only one instance of a module can exist in a pipeline.
  constexpr uint8_t PIPELINE2_ID = 2;
  constexpr uint8_t PIPELINE3_ID = 3;
  
  // Module instance IDs.
  constexpr uint8_t HOST_OUT_COPIER_ID = 0;
  constexpr uint8_t I2S0_OUT_COPIER_ID = 1;
  constexpr uint8_t I2S0_IN_COPIER_ID  = 2;
  constexpr uint8_t HOST_IN_COPIER_ID  = 3;
  
  constexpr uint8_t HOST_OUT_MIXIN_ID  = 0;
  constexpr uint8_t I2S0_IN_MIXIN_ID   = 1;
  
  constexpr uint8_t I2S0_OUT_MIXOUT_ID = 0;
  constexpr uint8_t HOST_IN_MIXOUT_ID  = 1;
  
  const struct PipelineConfig {
      uint8_t id;
      uint8_t priority;
      uint8_t mem_pages;
      bool lp;
  } PIPELINE_CFG[] = {
  {
      .id = PIPELINE0_ID,
      .priority = 0,
      .mem_pages = 2,
      .lp = true, // false in config, keep running in low power mode for dev
  },
  {
      .id = PIPELINE1_ID,
      .priority = 0,
      .mem_pages = 4,
      .lp = true,
  },
  {
      .id = PIPELINE2_ID,
      .priority = 0,
      .mem_pages = 2,
      .lp = true,
  },
  {
      .id = PIPELINE3_ID,
      .priority = 0,
      .mem_pages = 2,
      .lp = true,
  },
  };
  
  // Use 48khz 16-bit stereo throughout
  const AudioDataFormat FMT_HOST = {
      .sampling_frequency = SamplingFrequency::FS_48000HZ,
      .bit_depth = BitDepth::DEPTH_16BIT,
      .channel_map = 0xFFFFFF10,
      .channel_config = ChannelConfig::CONFIG_STEREO,
      .interleaving_style = InterleavingStyle::PER_CHANNEL,
      .number_of_channels = 2,
      .valid_bit_depth = 16,
      .sample_type = SampleType::INT_MSB,
      .reserved = 0,
  };
  
  const AudioDataFormat FMT_I2S = {
      .sampling_frequency = SamplingFrequency::FS_48000HZ,
      .bit_depth = BitDepth::DEPTH_32BIT,
      .channel_map = 0xFFFFFF10,
      .channel_config = ChannelConfig::CONFIG_STEREO,
      .interleaving_style = InterleavingStyle::PER_CHANNEL,
      .number_of_channels = 2,
      .valid_bit_depth = 16,
      .sample_type = SampleType::INT_MSB,
      .reserved = 0,
  };
  
  // Mixer modules only operate on 32-bits
  const AudioDataFormat FMT_MIXER = {
      .sampling_frequency = SamplingFrequency::FS_48000HZ,
      .bit_depth = BitDepth::DEPTH_32BIT,
      .channel_map = 0xFFFFFF10,
      .channel_config = ChannelConfig::CONFIG_STEREO,
      .interleaving_style = InterleavingStyle::PER_CHANNEL,
      .number_of_channels = 2,
      .valid_bit_depth = 32,
      .sample_type = SampleType::INT_MSB,
      .reserved = 0,
  };
  
  const CopierCfg HOST_OUT_COPIER_CFG = {
      .base_cfg = {
          .cpc = 100000,
          .ibs = 384,
          .obs = 384,
          .is_pages = 0,
          .audio_fmt = {
              .sampling_frequency = FMT_HOST.sampling_frequency,
              .bit_depth = FMT_HOST.bit_depth,
              .channel_map = FMT_HOST.channel_map,
              .channel_config = FMT_HOST.channel_config,
              .interleaving_style = FMT_HOST.interleaving_style,
              .number_of_channels = FMT_HOST.number_of_channels,
              .valid_bit_depth = FMT_HOST.valid_bit_depth,
              .sample_type = FMT_HOST.sample_type,
              .reserved = 0,
          },
      },
      .out_fmt = {
          .sampling_frequency = FMT_MIXER.sampling_frequency,
          .bit_depth = FMT_MIXER.bit_depth,
          .channel_map = FMT_MIXER.channel_map,
          .channel_config = FMT_MIXER.channel_config,
          .interleaving_style = FMT_MIXER.interleaving_style,
          .number_of_channels = FMT_MIXER.number_of_channels,
          .valid_bit_depth = FMT_MIXER.valid_bit_depth,
          .sample_type = FMT_MIXER.sample_type,
          .reserved = 0,
      },
      .copier_feature_mask = 0,
      .gtw_cfg = {
          .node_id = HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_OUTPUT, 0),
          .dma_buffer_size = 2 * 384,
          .config_length = 0,
      },
  };
  
  const CopierCfg HOST_IN_COPIER_CFG = {
      .base_cfg = {
          .cpc = 100000,
          .ibs = 384,
          .obs = 384,
          .is_pages = 0,
          .audio_fmt = {
              .sampling_frequency = FMT_MIXER.sampling_frequency,
              .bit_depth = FMT_MIXER.bit_depth,
              .channel_map = FMT_MIXER.channel_map,
              .channel_config = FMT_MIXER.channel_config,
              .interleaving_style = FMT_MIXER.interleaving_style,
              .number_of_channels = FMT_MIXER.number_of_channels,
              .valid_bit_depth = FMT_MIXER.valid_bit_depth,
              .sample_type = FMT_MIXER.sample_type,
              .reserved = 0,
          },
      },
      .out_fmt = {
          .sampling_frequency = FMT_HOST.sampling_frequency,
          .bit_depth = FMT_HOST.bit_depth,
          .channel_map = FMT_HOST.channel_map,
          .channel_config = FMT_HOST.channel_config,
          .interleaving_style = FMT_HOST.interleaving_style,
          .number_of_channels = FMT_HOST.number_of_channels,
          .valid_bit_depth = FMT_HOST.valid_bit_depth,
          .sample_type = FMT_HOST.sample_type,
          .reserved = 0,
      },
      .copier_feature_mask = 0,
      .gtw_cfg = {
          .node_id = HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_INPUT, 0),
          .dma_buffer_size = 2 * 384,
          .config_length = 0,
      },
  };
  
  constexpr uint8_t I2S_OUT_INSTANCE_ID = 0;
  
  const CopierCfg I2S_OUT_COPIER_CFG = {
      .base_cfg = {
          .cpc = 100000,
          .ibs = 384,
          .obs = 384,
          .is_pages = 0,
          .audio_fmt = {
              .sampling_frequency = FMT_MIXER.sampling_frequency,
              .bit_depth = FMT_MIXER.bit_depth,
              .channel_map = FMT_MIXER.channel_map,
              .channel_config = FMT_MIXER.channel_config,
              .interleaving_style = FMT_MIXER.interleaving_style,
              .number_of_channels = FMT_MIXER.number_of_channels,
              .valid_bit_depth = FMT_MIXER.valid_bit_depth,
              .sample_type = FMT_MIXER.sample_type,
              .reserved = 0,
          },
      },
      .out_fmt = {
          .sampling_frequency = FMT_I2S.sampling_frequency,
          .bit_depth = FMT_I2S.bit_depth,
          .channel_map = FMT_I2S.channel_map,
          .channel_config = FMT_I2S.channel_config,
          .interleaving_style = FMT_I2S.interleaving_style,
          .number_of_channels = FMT_I2S.number_of_channels,
          .valid_bit_depth = FMT_I2S.valid_bit_depth,
          .sample_type = FMT_I2S.sample_type,
          .reserved = 0,
      },
      .copier_feature_mask = 0,
      .gtw_cfg = {
          .node_id = I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_OUTPUT, I2S_OUT_INSTANCE_ID, 0),
          .dma_buffer_size = 2 * 384,
          .config_length = 0,
      },
  };
  
  constexpr uint8_t I2S_IN_INSTANCE_ID = 0;
  
  const CopierCfg I2S_IN_COPIER_CFG = {
      .base_cfg = {
          .cpc = 100000,
          .ibs = 384,
          .obs = 384,
          .is_pages = 0,
          .audio_fmt = {
              .sampling_frequency = FMT_I2S.sampling_frequency,
              .bit_depth = FMT_I2S.bit_depth,
              .channel_map = FMT_I2S.channel_map,
              .channel_config = FMT_I2S.channel_config,
              .interleaving_style = FMT_I2S.interleaving_style,
              .number_of_channels = FMT_I2S.number_of_channels,
              .valid_bit_depth = FMT_I2S.valid_bit_depth,
              .sample_type = FMT_I2S.sample_type,
              .reserved = 0,
          },
      },
      .out_fmt = {
          .sampling_frequency = FMT_MIXER.sampling_frequency,
          .bit_depth = FMT_MIXER.bit_depth,
          .channel_map = FMT_MIXER.channel_map,
          .channel_config = FMT_MIXER.channel_config,
          .interleaving_style = FMT_MIXER.interleaving_style,
          .number_of_channels = FMT_MIXER.number_of_channels,
          .valid_bit_depth = FMT_MIXER.valid_bit_depth,
          .sample_type = FMT_MIXER.sample_type,
          .reserved = 0,
      },
      .copier_feature_mask = 0,
      .gtw_cfg = {
          .node_id = I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_INPUT, I2S_IN_INSTANCE_ID, 0),
          .dma_buffer_size = 2 * 384,
          .config_length = 0,
      },
  };
  
  const BaseModuleCfg MIXER_CFG = {
      .cpc = 100000,
      .ibs = 384,
      .obs = 384,
      .is_pages = 0,
      .audio_fmt = {
          .sampling_frequency = FMT_MIXER.sampling_frequency,
          .bit_depth = FMT_MIXER.bit_depth,
          .channel_map = FMT_MIXER.channel_map,
          .channel_config = FMT_MIXER.channel_config,
          .interleaving_style = FMT_MIXER.interleaving_style,
          .number_of_channels = FMT_MIXER.number_of_channels,
          .valid_bit_depth = FMT_MIXER.valid_bit_depth,
          .sample_type = FMT_MIXER.sample_type,
          .reserved = 0,
      },
  };
  
  }  // anon namespace
  
  zx_status_t IntelDsp::GetI2SBlob(uint8_t bus_id, uint8_t direction,
                                   const AudioDataFormat& format,
                                   const void** out_blob, size_t* out_size) {
      zx_status_t st = ZX_ERR_NOT_FOUND;
      for (const auto& cfg : i2s_configs_) {
          if (!cfg.valid) {
              break;
          }
          if ((cfg.bus_id != bus_id) || (cfg.direction != direction)) {
              continue;
          }
          // TODO better matching here
          const formats_config_t* formats = cfg.formats;
          const format_config_t* f = &formats->format_configs[0];
          for (size_t j = 0; j < formats->format_config_count; j++) {
              if (format.valid_bit_depth != f->valid_bits_per_sample) {
                  f = reinterpret_cast<const format_config_t*>(
                          reinterpret_cast<const uint8_t*>(f) +
                          sizeof(*f) +
                          f->config.capabilities_size
                      );
                  continue;
              }
              *out_blob = reinterpret_cast<const void*>(f->config.capabilities);
              *out_size = static_cast<size_t>(f->config.capabilities_size);
              st = ZX_OK;
              break;
          }
      }
      return st;
  }
  
  zx_status_t IntelDsp::CreateHostDmaModule(uint8_t instance_id, uint8_t pipeline_id,
                                            const CopierCfg& cfg) {
      return ipc_.InitInstance(module_ids_[Module::COPIER],
                               instance_id,
                               ProcDomain::LOW_LATENCY,
                               0,
                               pipeline_id,
                               sizeof(cfg),
                               &cfg);
  }
  
  zx_status_t IntelDsp::CreateI2SModule(uint8_t instance_id, uint8_t pipeline_id,
                                        uint8_t i2s_instance_id, uint8_t direction,
                                        const CopierCfg& cfg) {
      const void* blob;
      size_t blob_size;
      zx_status_t st = GetI2SBlob(i2s_instance_id, direction,
                                  (direction == NHLT_DIRECTION_RENDER) ? cfg.out_fmt :
                                                                         cfg.base_cfg.audio_fmt,
                                  &blob, &blob_size);
      if (st != ZX_OK) {
          LOG(ERROR, "I2S config (instance %u direction %u) not found\n",
                     i2s_instance_id, direction);
          return st;
      }
  
      // Copy the I2S config blob
      size_t cfg_size = sizeof(cfg) + blob_size;
      ZX_DEBUG_ASSERT(cfg_size <= UINT16_MAX);
  
      fbl::AllocChecker ac;
      fbl::unique_ptr<uint8_t[]> cfg_buf(new (&ac) uint8_t[cfg_size]);
      if (!ac.check()) {
          LOG(ERROR, "out of memory while attempting to allocate copier config buffer\n");
          return ZX_ERR_NO_MEMORY;
      }
      memcpy(cfg_buf.get() + sizeof(cfg), blob, blob_size);
  
      // Copy the copier config
      memcpy(cfg_buf.get(), &cfg, sizeof(cfg));
      auto copier_cfg = reinterpret_cast<CopierCfg*>(cfg_buf.get());
      copier_cfg->gtw_cfg.config_length = static_cast<uint32_t>(blob_size);
  
      return ipc_.InitInstance(module_ids_[Module::COPIER],
                               instance_id,
                               ProcDomain::LOW_LATENCY,
                               0,
                               pipeline_id,
                               static_cast<uint16_t>(cfg_size),
                               cfg_buf.get());
  }
  
  zx_status_t IntelDsp::CreateMixinModule(uint8_t instance_id, uint8_t pipeline_id,
                                          const BaseModuleCfg& cfg) {
      return ipc_.InitInstance(module_ids_[Module::MIXIN],
                               instance_id,
                               ProcDomain::LOW_LATENCY,
                               0,
                               pipeline_id,
                               sizeof(cfg),
                               &cfg);
  }
  
  zx_status_t IntelDsp::CreateMixoutModule(uint8_t instance_id, uint8_t pipeline_id,
                                           const BaseModuleCfg& cfg) {
      return ipc_.InitInstance(module_ids_[Module::MIXOUT],
                               instance_id,
                               ProcDomain::LOW_LATENCY,
                               0,
                               pipeline_id,
                               sizeof(cfg),
                               &cfg);
  }
  
  zx_status_t IntelDsp::SetupPipelines() {
      ZX_DEBUG_ASSERT(module_ids_[Module::COPIER] != 0);
      ZX_DEBUG_ASSERT(module_ids_[Module::MIXIN] != 0);
      ZX_DEBUG_ASSERT(module_ids_[Module::MIXOUT] != 0);
  
      zx_status_t st = ZX_OK;
  
      // Create pipelines
      for (const auto& cfg : PIPELINE_CFG) {
          st = ipc_.CreatePipeline(cfg.id, cfg.priority, cfg.mem_pages, cfg.lp);
          if (st != ZX_OK) {
              return st;
          }
      }
  
      // Create pipeline 0 modules. Host DMA -> mixin
      // Modules must be created in order of source -> sink
      st = CreateHostDmaModule(HOST_OUT_COPIER_ID, PIPELINE0_ID, HOST_OUT_COPIER_CFG);
      if (st != ZX_OK) {
          return st;
      }
      st = CreateMixinModule(HOST_OUT_MIXIN_ID, PIPELINE0_ID, MIXER_CFG);
      if (st != ZX_OK) {
          return st;
      }
  
      // Bind pipeline 0
      st = ipc_.Bind(module_ids_[Module::COPIER], HOST_OUT_COPIER_ID, 0,
                     module_ids_[Module::MIXIN], HOST_OUT_MIXIN_ID, 0);
      if (st != ZX_OK) {
          return st;
      }
  
      // Create pipeline 1 modules. mixout -> I2S DMA
      st = CreateMixoutModule(I2S0_OUT_MIXOUT_ID, PIPELINE1_ID, MIXER_CFG);
      if (st != ZX_OK) {
          return st;
      }
  
      st = CreateI2SModule(I2S0_OUT_COPIER_ID, PIPELINE1_ID,
                           I2S_OUT_INSTANCE_ID, NHLT_DIRECTION_RENDER,
                           I2S_OUT_COPIER_CFG);
      if (st != ZX_OK) {
          return st;
      }
  
      // Bind pipeline 1
      st = ipc_.Bind(module_ids_[Module::MIXOUT], I2S0_OUT_MIXOUT_ID, 0,
                     module_ids_[Module::COPIER], I2S0_OUT_COPIER_ID, 0);
      if (st != ZX_OK) {
          return st;
      }
  
      // Create pipeline 2 modules. I2S DMA -> mixin
      st = CreateI2SModule(I2S0_IN_COPIER_ID, PIPELINE2_ID,
                           I2S_IN_INSTANCE_ID, NHLT_DIRECTION_CAPTURE,
                           I2S_IN_COPIER_CFG);
      if (st != ZX_OK) {
          return st;
      }
      st = CreateMixinModule(I2S0_IN_MIXIN_ID, PIPELINE2_ID, MIXER_CFG);
      if (st != ZX_OK) {
          return st;
      }
  
      // Bind pipeline 2
      st = ipc_.Bind(module_ids_[Module::COPIER], I2S0_IN_COPIER_ID, 0,
                     module_ids_[Module::MIXIN], I2S0_IN_MIXIN_ID, 0);
      if (st != ZX_OK) {
          return st;
      }
  
      // Create pipeline 3 modules. mixout -> Host DMA
      st = CreateMixoutModule(HOST_IN_MIXOUT_ID, PIPELINE3_ID, MIXER_CFG);
      if (st != ZX_OK) {
          return st;
      }
      st = CreateHostDmaModule(HOST_IN_COPIER_ID, PIPELINE3_ID, HOST_IN_COPIER_CFG);
      if (st != ZX_OK) {
          return st;
      }
  
      // Bind pipeline 2
      st = ipc_.Bind(module_ids_[Module::MIXOUT], HOST_IN_MIXOUT_ID, 0,
                     module_ids_[Module::COPIER], HOST_IN_COPIER_ID, 0);
      if (st != ZX_OK) {
          return st;
      }
  
      // Bind playback pipeline
      st = ipc_.Bind(module_ids_[Module::MIXIN], HOST_OUT_MIXIN_ID, 0,
                     module_ids_[Module::MIXOUT], I2S0_OUT_MIXOUT_ID, 0);
      if (st != ZX_OK) {
          return st;
      }
  
      // Bind capture pipeline
      st = ipc_.Bind(module_ids_[Module::MIXIN], I2S0_IN_MIXIN_ID, 0,
                     module_ids_[Module::MIXOUT], HOST_IN_MIXOUT_ID, 0);
      if (st != ZX_OK) {
          return st;
      }
  
      return ZX_OK;
  }
  
  zx_status_t IntelDsp::StartPipeline(const DspPipeline& pipeline) {
      // Sink first and then source
      zx_status_t st = RunPipeline(pipeline.pl_sink);
      if (st != ZX_OK) {
          return st;
      }
      return RunPipeline(pipeline.pl_source);
      // TODO Error recovery
  }
  
  zx_status_t IntelDsp::PausePipeline(const DspPipeline& pipeline) {
      zx_status_t st = ipc_.SetPipelineState(pipeline.pl_source, PipelineState::PAUSED, true);
      if (st != ZX_OK) {
          return st;
      }
      st = ipc_.SetPipelineState(pipeline.pl_sink, PipelineState::PAUSED, true);
      if (st != ZX_OK) {
          return st;
      }
      // Reset DSP DMA
      st = ipc_.SetPipelineState(pipeline.pl_source, PipelineState::RESET, true);
      if (st != ZX_OK) {
          return st;
      }
      return ipc_.SetPipelineState(pipeline.pl_sink, PipelineState::RESET, true);
      // TODO Error recovery
  }
  
  zx_status_t IntelDsp::CreateAndStartStreams() {
      zx_status_t res = ZX_OK;
  
      // Create and publish the streams we will use.
      static struct {
          uint32_t stream_id;
          bool is_input;
          struct DspPipeline pipeline;
          audio_stream_unique_id_t uid;
      } STREAMS[] = {
          // Speakers
          {
              .stream_id = 1,
              .is_input = false,
              .pipeline = {
                  .pl_source = PIPELINE0_ID,
                  .pl_sink = PIPELINE1_ID,
              },
              .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS,
          },
          // DMIC
          {
              .stream_id = 2,
              .is_input = true,
              .pipeline = {
                  .pl_source = PIPELINE3_ID,
                  .pl_sink = PIPELINE2_ID,
              },
              .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE,
          },
      };
  
      for (const auto& stream_def : STREAMS) {
          auto stream = fbl::AdoptRef(new IntelDspStream(stream_def.stream_id,
                                                         stream_def.is_input,
                                                         stream_def.pipeline,
                                                         &stream_def.uid));
  
          res = ActivateStream(stream);
          if (res != ZX_OK) {
              LOG(ERROR, "Failed to activate %s stream id #%u (res %d)!",
                         stream_def.is_input ? "input" : "output", stream_def.stream_id, res);
              return res;
          }
      }
  
      return ZX_OK;
  }
  
  
  }  // namespace intel_hda
  }  // namespace audio
  