// Copyright 2020-2021 Beken
//
// 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.
//
#pragma once
#include <common/bk_include.h>
#include <driver/pwm_types.h>

#ifdef __cplusplus
extern "C" {
#endif

/* @brief Overview about this API header
 *
 */

/**
 * @brief PWM API
 * @defgroup bk_api_pwm PWM API group
 * @{
 */

/**
 * @brief     Init the PWM driver
 *
 * This API init the resoure common to all PWM channels:
 *   - Init PWM driver control memory
 *   - Configure PWM common clock to 26M
 *
 * This API should be called before any other PWM APIs.
 *
 * @return
 *    - BK_OK: succeed
 *    - others: other errors.
 */
bk_err_t bk_pwm_driver_init(void);

/**
 * @brief     Deinit the PWM driver
 *
 * This API free all resource related to PWM and power down all PWM channels.
 *
 * @return
 *    - BK_OK: succeed
 *    - others: other errors.
 */
bk_err_t bk_pwm_driver_deinit(void);

/**
 * @brief     Init the PWM channel
 *
 * This API init the PWM channel:
 *  - Power up the PWM channel
 *  - Configure the PWM channel clock to 26M
 *  - Map the PWM channel to dedicated GPIO port
 *  - Set the period and duty cycle.
 *
 * For duty/cycle relationship, refer to bk_pwm_set_period_duty()
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_NULL_PARAM: config is NULL
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - BK_ERR_PWM_PERIOD_DUTY: PWM duty/period relationship is invalid
 *    - others: other errors.
 */
bk_err_t bk_pwm_init(pwm_chan_t chan, const pwm_init_config_t *config);

/**
 * @brief     Deinit a PWM channel
 *
 * This API deinit the PWM channel:
 *   - Stop the PWM channel
 *   - Reset all configuration of PWM channel to default value
 *   - Power down the PWM channel
 *
 * @return
 *    - BK_OK: succeed
 *    - others: other errors.
 */
bk_err_t bk_pwm_deinit(pwm_chan_t chan);

/**
 * @brief     Stop a PWM channel
 *
 * This API init the resoure common to all PWM channels:
 *   - Init PWM driver control memory
 *   - Configure PWM common clock to 26M
 *
 * @return
 *    - BK_OK: succeed
 *    - others: other errors.
 */
bk_err_t bk_pwm_start(pwm_chan_t chan);

/**
 * @brief     Stop a PWM channel
 *
 * This API init the resoure common to all PWM channels:
 *   - Init PWM driver control memory
 *   - Configure PWM common clock to 26M
 *
 * @return
 *    - BK_OK: succeed
 *    - others: other errors.
 */
bk_err_t bk_pwm_stop(pwm_chan_t chan);

/**
 * @brief     Configure the PWM period and duty cycle.
 *
 * This API is used to configure the period and duty time. The unit is cycle of PWM channel clock,
 * since the PWM channel clock is default to 26M, the unit is 1/26M (1/26 000 000) seconds.
 *
 * The beken chip supports up to 3 duties, the exact duty numbers depends on the target type.
 *
 * EXample1 - the simplest case::
 *
 *     Clock cycle = 1/26M
 *     Initial signal is High
 *     duty_cycle = D1 = 3
 *     duty2_cycle = 0
 *     duty3_cycle = 0
 *     period_cycle = 3 + 2 = 5
 *
 *                       |<-C->|
 *                        __    __    __    __    __    __    __    __    __    __    __    __
 *     Clock Signal:   __|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__
 *                       __________________             _________________             ___________
 *     PWM Signal:    __|                  |___________|                 |___________|
 *
 *     Duty1:           |<------- D1 ----->|
 *
 *     Period:          |<---------- Period1 --------->|<------ Period2 ------------>|
 *
 *
 * Example2 - 3 duty with initial signal high::
 *
 *     Clock cycle = 1/26M
 *     Duty_cycle = D1 = 1
 *     Duty2_cycle = D2 = 2
 *     Duty3_cycle = D3 = 3
 *     period_cycle = D1 + D2 + D3 = 6
 *
 *                       |<-C->|
 *                        __    __    __    __    __    __    __    __    __    __    __    __    __
 *     Clock Signal:   __|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|
 *                       ______             _________________ _____             _________________
 *     PWM Signal:    __|      |___________|                 |     |___________|                 |__
 *
 *     Duty1:           |<-D1->|<----D2--->|<------D3------->|
 *
 *     Period:          |<--------------- Period1 ---------->|<------------ Period2 ------------>|
 *
 *
 * Example3 - 3 duty with initial signal low::
 *
 *     Clock cycle = 1/26M
 *     Duty_cycle = D1 = 1
 *     Duty2_cycle = D2 = 2
 *     Duty3_cycle = D3 = 3
 *     period_cycle = D1 + D2 + D3 = 6
 *
 *                       |<-C->|
 *                        __    __    __    __    __    __    __    __    __    __    __    __    __
 *     Clock Signal:   __|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|
 *                     __       ___________                         ___________
 *     PWM Signal:       |_____|           |_________________|_____|           |_________________|__
 *
 *     Duty1:           |<-D1->|<----D2--->|<------D3------->|
 *
 *     Period:          |<--------------- Period1 ---------->|<------------ Period2 -------------|
 *
 *
 * Example4 - 3 duty with initial signal low and period > (D1 + D2 + D3)::
 *
 *     Clock cycle = 1/26M
 *     Duty_cycle = D1 = 1
 *     Duty2_cycle = D2 = 2
 *     Duty3_cycle = D3 = 3
 *     period_cycle = D1 + D2 + D3 + 1 = 7
 *
 *                       |<-C->|
 *                        __    __    __    __    __    __    __    __    __    __    __    __    __
 *     Clock Signal:   __|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|
 *                     __       ___________                   _____       ___________
 *     PWM Signal:       |_____|           |_________________|     |_____|           |______________
 *
 *     Duty1:           |<-D1->|<----D2--->|<------D3------->|
 *
 *     Period:          |<--------------- Period ----------------->|
 *
 * @attention 1. The period should great or equal to (D1 + D2 + D3)
 * @attention 2. If the duty Dx is 0, then Dx+1 to D3 should also be 0. If D1 is 0, it indicates the
 *               duty ratio is 0% and the signal is always low.
 * @attention 3. If D1 equals period, it indicates the duty ratio is 100% and the signal is always high.
 * @attention 4. The signal strengh between two successive duties is opposite, e.g. if signal of D1
 *               is high, then signal of D2 is low.
 * @attention 5. The initial signal is the signal strengh of D1 if D1 is not 0.
 * @attention 6. The duty/period configuration is very flexible, different configurations may have
 *               same result.
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_NULL_PARAM: config is NULL
 *    - BK_ERR_PWM_CHAN_NOT_INIT: PWM channel not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - BK_ERR_PWM_PERIOD_DUTY: PWM duty/period relationship is invalid
 *    - others: other errors.
 */
bk_err_t bk_pwm_set_period_duty(pwm_chan_t chan, pwm_period_duty_config_t *config);

/**
 * @brief     Set the initial signal to low
 *
 * Set the initial signal level of the first duty which is indicated by duty_cycle.
 *
 * @attention The signal will always be high if the duty_cycle equals period cycle
 *            (duty ratio is 100%), regardless whether this API is called or NOT.
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_CHAN_NOT_INIT: PWM channel not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_set_init_signal_low(pwm_chan_t chan);

/**
 * @brief     Set the initial signal to high
 *
 * Set the initial signal level of the first duty which is indicated by duty_cycle.
 *
 * @attention The signal will always be low if the duty_cycle is 0 (duty ratio is 0%),
 *            regardless whether this API is called or NOT.
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_CHAN_NOT_INIT: PWM channel not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_set_init_signal_high(pwm_chan_t chan);

/**
 * @brief     Register the interrupt service routine for PWM channel
 *
 * @attention 1. Both bk_pwm_capture_init() and this API can set the ISR, the one called later
 *            makes sense.
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_register_isr(pwm_chan_t chan, pwm_isr_t isr);

/**
 * @brief     Enable interrupt of PWM channel
 *
 * The PWM channel interrupt is disabled by default when starting the channel by bk_pwm_start() or
 * bk_pwm_group_start(), while it's enabled by default when starting the channel by
 * bk_pwm_capture_start().
 *
 * This API is designed mainly for debug purpose, we can use it to enable the interrupt of any PWM
 * channel, regardless of whether the channel is started by bk_pwm_start() or bk_pwm_group_start() or
 * bk_pwm_capture_start().
 *
 * The bk_pwm_stop() and bk_pwm_group_stop() also disable the interrupt, but the bk_pwm_start()
 * and bk_pwm_group_start will not enable the interrupt. So if the channel interrupt is enabled,
 * and then stop and start the channel, we need to call this API to re-enable the interrupt.
 *
 * @attention 1. The interrupt can be enabled only after PWM channel is started by bk_pwm_start().
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - BK_ERR_PWM_CHAN_NOT_START: PWM channel is not started
 *    - others: other errors.
 */
bk_err_t bk_pwm_enable_interrupt(pwm_chan_t chan);

/**
 * @brief     Disable interrupt of PWM channel
 *
 * Stop the interrupt of any PWM channel, regardless whether the channel is in PWM mode, or
 * PWM capture mode, or PWM group mode.
 *
 * @attention 1. bk_pwm_capture_start() will enable the interrupt anyway, so if we want to disable
 *             the interrupt of PWM capture channel, we need to call this API anytime
 *             bk_pwm_capture_start() is called.
 *
 * @param chan the channel ID
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_disable_interrupt(pwm_chan_t chan);

/**
 * @brief     Init the PWM group
 *
 * This API init the PWM group.
 * The PWM group is a channel pair that has following attributes:
 *  - The period is same
 *  - The initial signal level is opposite
 *  - Start and stop at the same time
 *
 * Below is a picture to demonstrate the relationship between duty and period::
 *
 *                  |<------------------------ period_cycle ---------------------------->|
 *                  |<--chan1_duty_cycle -->|<- D1 --|<--- chan2_duty_cycle --->|<- D2 ->|
 *                   _______________________
 *      channe1 1   |                       |____________________________________________|
 *                                                    __________________________
 *      channel 2   |________________________________|                          |________|
 *
 * The D1/D2 indicates the dead time cycle, it's calculated based on following formula:
 * D1 = D2 = (period - chan1_duty_cycle - chan2_duty_cycle)/2
 *
 * @attention 1. This API doesn't check whether the PWM channel is already used in other mode.
 *            e.g. If the API doesn't returns error if config->chan1 is already used in capture
 *            mode, the application need to make sure the channel configured in the group is not
 *            used for other purpose.
 * @attention 2. The chan2_duty_cycle is actually the 2nd signal wave for channel 2, as depicted
 *            in above picture.
 * @attention 3. This API supports to group any two available channel into a PWM group, however,
 *            we recommend to group 0/1, 2/3, 4/5 into a group, because hardware can support these
 *            group directly and we can start the channels in the group at exactly same time.
 * @attention 4. The chan1_duty_cycle and chan2_duty_cycle should be great than 0.
 *
 * @param config configuration of PWM group
 * @param group store the group ID if the API is succeed.
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_NULL_PARAM: config or group is NULL
 *    - BK_ERR_PWM_CHAN_ID: channel 1 or 2 in the group is invalid
 *    - BK_ERR_PWM_GROUP_SAME_CHAN: channel 1 and 2 are same
 *    - BK_ERR_PWM_GROUP_EXIST: a group with channel 1 and 2 is already exists
 *    - BK_ERR_PWM_GROUP_CHAN_USED: a existing group has a channel equals to channel 1 or 2
 *    - BK_ERR_PWM_GROUP_DUTY: period cycle < (chan1_duty_cycle + chan2_duty_cycle)
 *    - BK_ERR_PWM_PERIOD_DUTY: PWM duty/period relationship is invalid
 *    - others: other errors.
 */
bk_err_t bk_pwm_group_init(const pwm_group_init_config_t *config, pwm_group_t *group);

/**
 * @brief     Deinit a PWM group
 *
 * This API stops and powers down channels in a PWM group.
 *
 * @attention This API returns BK_OK directly if the group doesn't exist.
 *
 * @param group the group ID returned bk bk_pwm_group_init()
 *
 * @return
 *    - BK_OK: succeed
 *    - others: other errors.
 */
bk_err_t bk_pwm_group_deinit(pwm_group_t group);

/**
 * @brief     Configure the PWM group
 *
 * Configure the duty and period of a PWM group, for duty/period relationship refer to
 * bk_pwm_group_init().
 *
 * @attention The configuration can't take effect until the current period is ended
 *
 * @param group the group ID to be configured
 * @param config configuration of PWM group
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_NULL_PARAM: config or group is NULL
 *    - BK_ERR_PWM_GROUP_NOT_EXIST: the group doesn't exist
 *    - BK_ERR_PWM_GROUP_DUTY: period cycle < (chan1_duty_cycle + chan2_duty_cycle)
 *    - BK_ERR_PWM_PERIOD_DUTY: PWM duty/period relationship is invalid
 *    - others: other errors.
 */
bk_err_t bk_pwm_group_set_config(pwm_group_t group, const pwm_group_config_t *config);

/**
 * @brief     Start the PWM group
 *
 * @param group the group ID to be started
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_GROUP_NOT_EXIST: the group doesn't exist
 *    - others: other errors.
 */
bk_err_t bk_pwm_group_start(pwm_group_t group);

/**
 * @brief     Stop the PWM group
 *
 * @param group the group ID to be stopped
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_GROUP_NOT_EXIST: the group doesn't exist
 *    - others: other errors.
 */
bk_err_t bk_pwm_group_stop(pwm_group_t group);

/**
 * @brief     Init the PWM capture
 *
 * The PWM capture can calculate the cycles between two different edges, currently we support
 * following mode:
 * 1. PWM_CAPTURE_POS - calcualte cycles between two pos-edge, the interrupt trigger in post edge
 * 2. PWM_CAPTURE_NEG - calcualte cycles between two neg-edge, the interrupt trigger in neg edge
 * 3. PWM_CAPTURE_EDGE - calculate cycles between two edge (pos or neg), the interrupt trigger
 *                       in post or neg edge.
 *
 * Example 1 - init the capture in PWM_CAPTURE_POS mode::
 *
 *                     |<------ C1 ---->|<------- C2 ------->|
 *                      ______           ___________          ________________            _
 *      PWM input     _|      |_________|           |________|                |__________|
 *
 *      Capture value                   |    C1              |             C2            |
 *
 * @param chan the capture channel
 * @param config the configuration of capture
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_NULL_PARAM: config is NULL
 *    - BK_ERR_PWM_CHAN_ID: invalid channel
 *    - BK_ERR_PWM_CAPTURE_EDGE: capture edge is invalid or not supported.
 *    - others: other errors.
 */
bk_err_t bk_pwm_capture_init(pwm_chan_t chan, const pwm_capture_init_config_t *config);

/**
 * @brief     Deinit the PWM capture
 *
 * @param chan the capture channel
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_PWM_CHAN_ID: invalid channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_capture_deinit(pwm_chan_t chan);

/**
 * @brief     Start the PWM capture
 *
 * @param chan channel to be started
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_capture_start(pwm_chan_t chan);

/**
 * @brief     Stop the PWM capture
 *
 * @param chan channel to be stopped
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_NOT_INIT: PWM driver not init
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_capture_stop(pwm_chan_t chan);

/**
 * @brief     Get the capture value
 *
 * @attention The caller need to make sure the parameter chan is correct!
 *
 * @param chan PWM channel
 *
 * @return capture value
 */
uint32_t bk_pwm_capture_get_value(pwm_chan_t chan);

/**
 * @brief     Set pwm channel as timer mode
 *
 * @attention The caller need to make sure the parameter chan is correct!
 *
 * @param chan PWM channel
 *
 * @return
 *    - BK_OK: succeed
 *    - BK_ERR_PWM_CHAN_ID: invalid PWM channel
 *    - others: other errors.
 */
bk_err_t bk_pwm_set_mode_timer(pwm_chan_t chan);


#if CONFIG_PWM_PHASE_SHIFT

bk_err_t bk_pwm_phase_shift_init(const pwm_phase_shift_config_t *shift_config);
bk_err_t bk_pwm_phase_shift_deinit(void);
bk_err_t bk_pwm_phase_shift_start(void);
bk_err_t bk_pwm_phase_shift_stop(void);
bk_err_t bk_pwm_phase_shift_set_duty_and_update(const pwm_phase_shift_config_t *shift_config);
bk_err_t bk_pwm_phase_shift_set_duty(pwm_chan_t chan, uint32_t duty_cycle);
bk_err_t bk_pwm_phase_shift_update_duty(void);

#endif

/**
 * @}
 */

#ifdef __cplusplus
}
#endif
