text: /* Copyright (c) 2019-2021 Arm Limited
   * SPDX-License-Identifier: Apache-2.0
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #if !DEVICE_PWMOUT
  #error [NOT_SUPPORTED] PWM not supported for this target
  #elif !COMPONENT_FPGA_CI_TEST_SHIELD
  #error [NOT_SUPPORTED] FPGA CI Test Shield is needed to run this test
  #elif !(defined(TARGET_FF_ARDUINO) || defined(TARGET_FF_ARDUINO_UNO)) && !defined(MBED_CONF_TARGET_DEFAULT_FORM_FACTOR)
  #error [NOT_SUPPORTED] Test not supported for this form factor
  #else
  
  #include "utest/utest.h"
  #include "unity/unity.h"
  #include "greentea-client/test_env.h"
  #include "mbed.h"
  #include "MbedTester.h"
  #include "pinmap.h"
  #include "hal/static_pinmap.h"
  #include "test_utils.h"
  #include "pwm_fpga_test.h"
  
  using namespace utest::v1;
  
  #define pwm_debug_printf(...)
  
  typedef enum {
      PERIOD_WRITE,
      PERIOD_MS_WRITE,
      PERIOD_US_WRITE,
      PERIOD_PULSEWIDTH,
      PERIOD_PULSEWIDTH_MS,
      PERIOD_PULSEWIDTH_US
  } pwm_api_test_t;
  
  #define NUM_OF_PERIODS                  10
  #define US_PER_SEC                      1000000
  #define US_PER_MS                       1000
  #define MS_PER_SEC                      1000
  
  #define DELTA_FACTOR                    20 // 5% delta
  
  #define PERIOD_US(PERIOD_MS) (((PERIOD_MS) * US_PER_MS))
  #define PERIOD_FLOAT(PERIOD_MS) (((float)(PERIOD_MS) / US_PER_MS))
  #define FILL_FLOAT(PRC) ((float)(PRC) / 100)
  #define PULSE_HIGH_US(PERIOD_US, PRC) ((uint32_t)((PERIOD_US) * FILL_FLOAT(PRC)))
  #define PULSE_LOW_US(PERIOD_US, PRC) ((uint32_t)((PERIOD_US) * (1.0f - FILL_FLOAT(PRC))))
  
  
  MbedTester tester(DefaultFormFactor::pins(), DefaultFormFactor::restricted_pins());
  
  
  void fpga_pwm_init_free(PinName pin)
  {
      pwmout_t pwm_out;
  
      pwm_debug_printf("PWM init/free test on pin=%s (%i)\r\n", pinmap_ff_default_pin_to_string(pin), pin);
  
      pwmout_init(&pwm_out, pin);
      pwmout_free(&pwm_out);
  }
  
  
  void fpga_pwm_period_fill_test(PinName pin, uint32_t period_ms, uint32_t fill_prc, pwm_api_test_t api_test, bool init_direct)
  {
      pwm_debug_printf("PWM test on pin = %s (%i)\r\n", pinmap_ff_default_pin_to_string(pin), pin);
      pwm_debug_printf("Testing period = %lu ms, duty-cycle = %lu %%\r\n", period_ms, fill_prc);
      pwm_debug_printf("Testing APIs = %d\r\n", (int)api_test);
  
      tester.reset();
      MbedTester::LogicalPin logical_pin = (MbedTester::LogicalPin)(MbedTester::LogicalPinIOMetrics0);
      tester.pin_map_set(pin, logical_pin);
  
      pwmout_t pwm_out;
  
      if (init_direct) {
          const PinMap pinmap = get_pwm_pinmap(pin);
          pwmout_init_direct(&pwm_out, &pinmap);
      } else {
          pwmout_init(&pwm_out, pin);
      }
  
      core_util_critical_section_enter();
  
      switch (api_test) {
          case PERIOD_WRITE:
              pwmout_period(&pwm_out, PERIOD_FLOAT(period_ms));
              pwmout_write(&pwm_out, FILL_FLOAT(fill_prc));
              break;
  
          case PERIOD_MS_WRITE:
              pwmout_period_ms(&pwm_out, (int)period_ms);
              pwmout_write(&pwm_out, FILL_FLOAT(fill_prc));
              break;
  
          case PERIOD_US_WRITE:
              pwmout_period_us(&pwm_out, PERIOD_US(period_ms));
              pwmout_write(&pwm_out, FILL_FLOAT(fill_prc));
              break;
  
          case PERIOD_PULSEWIDTH:
              pwmout_period(&pwm_out, PERIOD_FLOAT(period_ms));
              pwmout_pulsewidth(&pwm_out, (float)PULSE_HIGH_US(PERIOD_US(period_ms), fill_prc) / US_PER_SEC);
              break;
  
          case PERIOD_PULSEWIDTH_MS:
              pwmout_period(&pwm_out, PERIOD_FLOAT(period_ms));
              pwmout_pulsewidth_ms(&pwm_out, (int)PULSE_HIGH_US(PERIOD_US(period_ms), fill_prc) / MS_PER_SEC);
              break;
  
          case PERIOD_PULSEWIDTH_US:
              pwmout_period(&pwm_out, PERIOD_FLOAT(period_ms));
              pwmout_pulsewidth_us(&pwm_out, (int)PULSE_HIGH_US(PERIOD_US(period_ms), fill_prc));
              break;
      }
  
      // wait_us is safe to call as this test disable the IRQs on execution.
      wait_us(PERIOD_US(period_ms));
  
      tester.io_metrics_start();
  
      wait_us(NUM_OF_PERIODS * PERIOD_US(period_ms));
  
      tester.io_metrics_stop();
      core_util_critical_section_exit();
  
      const uint32_t expected_low_pulse_us = PULSE_LOW_US(PERIOD_US(period_ms), fill_prc);
      const uint32_t expected_high_pulse_us = PULSE_HIGH_US(PERIOD_US(period_ms), fill_prc);
      const uint32_t delta_low_pulse = (expected_low_pulse_us / DELTA_FACTOR);
      const uint32_t delta_high_pulse = (expected_high_pulse_us / DELTA_FACTOR);
  
      pwm_debug_printf("Minimum pulse low %lu us\r\n", tester.io_metrics_min_pulse_low(logical_pin) / 100);
      pwm_debug_printf("Minimum pulse high %lu us\r\n", tester.io_metrics_min_pulse_high(logical_pin) / 100);
      pwm_debug_printf("Maximum pulse low %lu us\r\n", tester.io_metrics_max_pulse_low(logical_pin) / 100);
      pwm_debug_printf("Maximum pulse high %lu us\r\n", tester.io_metrics_max_pulse_high(logical_pin) / 100);
      pwm_debug_printf("Rising edges %lu\r\n", tester.io_metrics_rising_edges(logical_pin));
      pwm_debug_printf("Falling edges %lu\r\n", tester.io_metrics_falling_edges(logical_pin));
  
      TEST_ASSERT_FLOAT_WITHIN(FILL_FLOAT(fill_prc) / DELTA_FACTOR, FILL_FLOAT(fill_prc), pwmout_read(&pwm_out));
  
      TEST_ASSERT_UINT32_WITHIN(delta_low_pulse, expected_low_pulse_us, tester.io_metrics_min_pulse_low(logical_pin) / 100);
      TEST_ASSERT_UINT32_WITHIN(delta_low_pulse, expected_low_pulse_us, tester.io_metrics_max_pulse_low(logical_pin) / 100);
      TEST_ASSERT_UINT32_WITHIN(delta_high_pulse, expected_high_pulse_us, tester.io_metrics_min_pulse_high(logical_pin) / 100);
      TEST_ASSERT_UINT32_WITHIN(delta_high_pulse, expected_high_pulse_us, tester.io_metrics_max_pulse_high(logical_pin) / 100);
  
      TEST_ASSERT_UINT32_WITHIN(1, NUM_OF_PERIODS, tester.io_metrics_rising_edges(logical_pin));
      TEST_ASSERT_UINT32_WITHIN(1, NUM_OF_PERIODS, tester.io_metrics_falling_edges(logical_pin));
  
      pwmout_free(&pwm_out);
  }
  
  template<uint32_t period_ms, uint32_t fill_prc, pwm_api_test_t api_test, bool init_direct>
  void fpga_pwm_period_fill_test(PinName pin)
  {
      fpga_pwm_period_fill_test(pin, period_ms, fill_prc, api_test, init_direct);
  }
  
  
  Case cases[] = {
      // This will be run for all pins
      Case("PWM - init/free test", all_ports<PWMPort, DefaultFormFactor, fpga_pwm_init_free>),
  
      // This will be run for single pin
      Case("PWM - period: 10 ms, fill: 10%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_WRITE, false> >),
      Case("PWM (direct init) - period: 10 ms, fill: 10%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_WRITE, true> >),
  
      Case("PWM - period: 10 ms, fill: 10%, api: period_ms/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_MS_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 10%, api: period_us/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_US_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 10%, api: period/pulse_width", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_PULSEWIDTH, false> >),
      Case("PWM - period: 10 ms, fill: 10%, api: period/pulse_width_ms", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_PULSEWIDTH_MS, false> >),
      Case("PWM - period: 10 ms, fill: 10%, api: period/pulse_width_us", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 10, PERIOD_PULSEWIDTH_US, false> >),
  
      Case("PWM - period: 10 ms, fill: 50%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 50, PERIOD_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 50%, api: period_ms/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 50, PERIOD_MS_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 50%, api: period_us/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 50, PERIOD_US_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 50%, api: period/pulse_width", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 50, PERIOD_PULSEWIDTH, false> >),
      Case("PWM - period: 10 ms, fill: 50%, api: period/pulse_width_ms", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 50, PERIOD_PULSEWIDTH_MS, false> >),
      Case("PWM - period: 10 ms, fill: 50%, api: period/pulse_width_us", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 50, PERIOD_PULSEWIDTH_US, false> >),
  
      Case("PWM - period: 10 ms, fill: 90%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 90, PERIOD_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 90%, api: period_ms/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 90, PERIOD_MS_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 90%, api: period_us/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 90, PERIOD_US_WRITE, false> >),
      Case("PWM - period: 10 ms, fill: 90%, api: period/pulse_width", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 90, PERIOD_PULSEWIDTH, false> >),
      Case("PWM - period: 10 ms, fill: 90%, api: period/pulse_width_ms", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 90, PERIOD_PULSEWIDTH_MS, false> >),
      Case("PWM - period: 10 ms, fill: 90%, api: period/pulse_width_us", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<10, 90, PERIOD_PULSEWIDTH_US, false> >),
  
      Case("PWM - period: 30 ms, fill: 10%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 10, PERIOD_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 10%, api: period_ms/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 10, PERIOD_MS_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 10%, api: period_us/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 10, PERIOD_US_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 10%, api: period/pulse_width", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 10, PERIOD_PULSEWIDTH, false> >),
      Case("PWM - period: 30 ms, fill: 10%, api: period/pulse_width_ms", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 10, PERIOD_PULSEWIDTH_MS, false> >),
      Case("PWM - period: 30 ms, fill: 10%, api: period/pulse_width_us", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 10, PERIOD_PULSEWIDTH_US, false> >),
  
      Case("PWM - period: 30 ms, fill: 50%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 50, PERIOD_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 50%, api: period_ms/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 50, PERIOD_MS_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 50%, api: period_us/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 50, PERIOD_US_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 50%, api: period/pulse_width", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 50, PERIOD_PULSEWIDTH, false> >),
      Case("PWM - period: 30 ms, fill: 50%, api: period/pulse_width_ms", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 50, PERIOD_PULSEWIDTH_MS, false> >),
      Case("PWM - period: 30 ms, fill: 50%, api: period/pulse_width_us", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 50, PERIOD_PULSEWIDTH_US, false> >),
  
      Case("PWM - period: 30 ms, fill: 90%, api: period/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 90, PERIOD_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 90%, api: period_ms/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 90, PERIOD_MS_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 90%, api: period_us/write", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 90, PERIOD_US_WRITE, false> >),
      Case("PWM - period: 30 ms, fill: 90%, api: period/pulse_width", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 90, PERIOD_PULSEWIDTH, false> >),
      Case("PWM - period: 30 ms, fill: 90%, api: period/pulse_width_ms", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 90, PERIOD_PULSEWIDTH_MS, false> >),
      Case("PWM - period: 30 ms, fill: 90%, api: period/pulse_width_us", one_peripheral<PWMPort, DefaultFormFactor, fpga_pwm_period_fill_test<30, 90, PERIOD_PULSEWIDTH_US, false> >)
  };
  
  utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
  {
  #ifdef FPGA_FORCE_ALL_PORTS
      GREENTEA_SETUP(300, "default_auto");
  #else
      GREENTEA_SETUP(120, "default_auto");
  #endif
      return greentea_test_setup_handler(number_of_cases);
  }
  
  Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
  
  int main()
  {
      Harness::run(specification);
  }
  
  #endif /* !DEVICE_PWMOUT */
  