blob: cbf144fe252df887b83409682bad54738f46aa58 [file] [log] [blame]
/****************************************************************************
* arch/risc-v/src/bl602/bl602_pwm_lowerhalf.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <arch/board/board.h>
#include "riscv_internal.h"
#include "bl602_gpio.h"
#include "bl602_pwm_lowerhalf.h"
#include "hardware/bl602_pwm.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define BL_PWM_FREQ_DEFAULT (1000)
#define BL_PWM_STOP_TIMEOUT_COUNT (160*1000)
#define BL_PWM_XTAL_CLK (40000000)
#define BL_PWM_BUS_BCLK (80000000)
#ifdef CONFIG_PWM_NCHANNELS
# if (CONFIG_PWM_NCHANNELS > 5)
# error "The maximum number of BL602 PWM channels is 5!"
# endif
#endif
#ifdef CONFIG_BL602_PWM0_BUS_BCLK_SRC
#define BL_PWM_CLK BL_PWM_BUS_BCLK
#else
#define BL_PWM_CLK BL_PWM_XTAL_CLK
#endif
/****************************************************************************
* Private Types
****************************************************************************/
struct bl602_pwm_ch_cfgs
{
int ch; /* PWM channel */
int clk; /* PWM Clock */
int stop_mode; /* PWM Stop Mode */
int pol; /* PWM mode type */
uint16_t clk_div; /* PWM clkDiv num */
uint16_t period; /* PWM period set */
uint16_t threshold1; /* PWM threshold1 num */
uint16_t threshold2; /* PWM threshold2 num */
uint16_t int_pulse_cnt; /* PWM interrupt pulse count */
};
struct bl602_pwm_s
{
const struct pwm_ops_s *ops; /* PWM operations */
uint32_t chan_pin[5]; /* Channel pin */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* PWM driver methods */
static int bl602_pwm_setup(struct pwm_lowerhalf_s *dev);
static int bl602_pwm_shutdown(struct pwm_lowerhalf_s *dev);
static int bl602_pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info);
static int bl602_pwm_stop(struct pwm_lowerhalf_s *dev);
static int bl602_pwm_ioctl(struct pwm_lowerhalf_s *dev,
int cmd, unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
/* This is the list of lower half PWM driver methods used by the upper half
* driver.
*/
static const struct pwm_ops_s g_bl602_pwmops =
{
.setup = bl602_pwm_setup,
.shutdown = bl602_pwm_shutdown,
.start = bl602_pwm_start,
.stop = bl602_pwm_stop,
.ioctl = bl602_pwm_ioctl,
};
struct bl602_pwm_s g_bl602_pwm0 =
{
.ops = &g_bl602_pwmops,
#ifdef BOARD_PWM_CH0_PIN
.chan_pin[0] = BOARD_PWM_CH0_PIN,
#endif
#ifdef BOARD_PWM_CH1_PIN
.chan_pin[1] = BOARD_PWM_CH1_PIN,
#endif
#ifdef BOARD_PWM_CH2_PIN
.chan_pin[2] = BOARD_PWM_CH2_PIN,
#endif
#ifdef BOARD_PWM_CH3_PIN
.chan_pin[3] = BOARD_PWM_CH3_PIN,
#endif
#ifdef BOARD_PWM_CH4_PIN
.chan_pin[4] = BOARD_PWM_CH4_PIN,
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
static void pwm_channel_disable(uint8_t ch)
{
modifyreg32(BL602_PWM_N_CONFIG(ch), 0, CONFIG_PWM_STOP_EN);
modifyreg32(BL602_PWM_N_CONFIG(ch), INTERRUPT_PWM_INT_ENABLE, 0);
}
static void pwm_channel_enable(uint8_t ch)
{
modifyreg32(BL602_PWM_N_CONFIG(ch), CONFIG_PWM_STOP_EN, 0);
}
static void pwm_channel_init(uint8_t ch, struct bl602_pwm_ch_cfgs *ch_cfg)
{
uint32_t timeout_cnt = BL_PWM_STOP_TIMEOUT_COUNT;
/* Config pwm clock and polarity */
modifyreg32(BL602_PWM_N_CONFIG(ch), 0, CONFIG_PWM_STOP_EN);
while (!(getreg32(BL602_PWM_N_CONFIG(ch)) & CONFIG_PWM_STS_TOP))
{
timeout_cnt--;
if (timeout_cnt == 0)
{
return;
}
}
modifyreg32(BL602_PWM_N_CONFIG(ch),
CONFIG_REG_CLK_SEL_MASK & (~ch_cfg->clk),
CONFIG_REG_CLK_SEL_MASK & ch_cfg->clk);
modifyreg32(BL602_PWM_N_CONFIG(ch),
CONFIG_PWM_OUT_INV,
ch_cfg->pol ? CONFIG_PWM_OUT_INV : 0);
modifyreg32(BL602_PWM_N_CONFIG(ch),
CONFIG_PWM_STOP_MODE,
ch_cfg->stop_mode ? CONFIG_PWM_STOP_MODE : 0);
/* Config pwm division */
putreg32(ch_cfg->clk_div, BL602_PWM_N_CLKDIV(ch));
/* Config pwm period and duty */
putreg32(ch_cfg->threshold1, BL602_PWM_N_THRE1(ch));
putreg32(ch_cfg->threshold2, BL602_PWM_N_THRE2(ch));
putreg32(ch_cfg->period, BL602_PWM_N_PERIOD(ch));
/* Config interrupt pulse count */
modifyreg32(BL602_PWM_N_INTERRUPT(ch), 0,
(ch_cfg->int_pulse_cnt & PERIOD_PWM_PERIOD_MASK));
}
static int32_t pwm_init(uint8_t id, uint32_t freq)
{
struct bl602_pwm_ch_cfgs pwm_cfg =
{
.ch = BL602_PWM_CH0,
#ifdef CONFIG_BL602_PWM0_BUS_BCLK_SRC
.clk = BL602_PWM_CLK_BCLK,
#else
.clk = BL602_PWM_CLK_XCLK,
#endif
.stop_mode = BL602_PWM_STOP_ABRUPT,
.pol = BL602_PWM_POL_NORMAL,
.clk_div = 0,
.period = 100,
.threshold1 = 0,
.threshold2 = 0,
.int_pulse_cnt = 0,
};
pwm_cfg.period = BL_PWM_CLK / freq;
pwm_cfg.ch = id;
pwm_channel_disable(id);
pwm_channel_init(id, &pwm_cfg);
return 0;
}
/****************************************************************************
* Name: bl602_pwm_duty
*
* Description:
* Configure PWM duty
*
****************************************************************************/
static int bl602_pwm_duty(struct bl602_pwm_s *priv, uint8_t chan,
ub16_t duty)
{
uint16_t period;
uint16_t threshold1;
uint16_t threshold2;
uint32_t tmp_val;
/* get pwm period and duty */
tmp_val = getreg32(BL602_PWM_N_THRE1(chan));
threshold1 = tmp_val & THRE1_PWM_THRE1_MASK;
tmp_val = getreg32(BL602_PWM_N_THRE2(chan));
threshold2 = tmp_val & THRE2_PWM_THRE2_MASK;
tmp_val = getreg32(BL602_PWM_N_PERIOD(chan));
period = tmp_val & PERIOD_PWM_PERIOD_MASK;
threshold1 = 0;
threshold2 = (uint16_t)((uint32_t)period * duty / 65535);
putreg32(threshold1, BL602_PWM_N_THRE1(chan));
putreg32(threshold2, BL602_PWM_N_THRE2(chan));
return OK;
}
/****************************************************************************
* Name: bl602_pwm_freq
*
* Description:
* Configure PWM frequency
*
****************************************************************************/
static int bl602_pwm_freq(struct bl602_pwm_s *priv, uint8_t chan,
uint32_t freq)
{
uint16_t period = BL_PWM_CLK / freq;
uint16_t threshold1 = 0;
uint16_t threshold2 = 0;
pwm_channel_disable(chan);
/* Config pwm period and duty */
putreg32(threshold1, BL602_PWM_N_THRE1(chan));
putreg32(threshold2, BL602_PWM_N_THRE2(chan));
putreg32(period, BL602_PWM_N_PERIOD(chan));
pwm_channel_enable(chan);
return OK;
}
/****************************************************************************
* Name: bl602_pwm_setup
*
* Description:
* This method is called when the driver is opened. The lower half driver
* should configure and initialize the device so that it is ready for use.
* It should not, however, output pulses until the start method is called.
*
****************************************************************************/
static int bl602_pwm_setup(struct pwm_lowerhalf_s *dev)
{
int i;
int ret = OK;
struct bl602_pwm_s *priv = (struct bl602_pwm_s *)dev;
UNUSED(i);
#ifdef CONFIG_PWM_NCHANNELS
for (i = 0; i < CONFIG_PWM_NCHANNELS; i++)
{
bl602_configgpio(priv->chan_pin[i]);
pwm_init(i, BL_PWM_FREQ_DEFAULT);
}
#else
bl602_configgpio(priv->chan_pin[0]);
pwm_init(0, BL_PWM_FREQ_DEFAULT);
#endif
return ret;
}
/****************************************************************************
* Name: bl602_pwm_shutdown
*
* Description:
* This method is called when the driver is closed. The lower half driver
* stop pulsed output, free any resources, disable the timer hardware, and
* put the system into the lowest possible power usage state
*
****************************************************************************/
static int bl602_pwm_shutdown(struct pwm_lowerhalf_s *dev)
{
int i;
int ret = OK;
struct bl602_pwm_s *priv = (struct bl602_pwm_s *)dev;
UNUSED(i);
#ifdef CONFIG_PWM_NCHANNELS
for (i = 0; i < CONFIG_PWM_NCHANNELS; i++)
{
pwm_channel_disable(priv->chan_pin[i]);
bl602_gpio_deinit(i);
}
#else
pwm_channel_disable(priv->chan_pin[0]);
bl602_gpio_deinit(0);
#endif
return ret;
}
/****************************************************************************
* Name: bl602_pwm_start
*
* Description:
* (Re-)initialize the PWM and start the pulsed output
*
****************************************************************************/
static int bl602_pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info)
{
struct bl602_pwm_s *priv = (struct bl602_pwm_s *)dev;
int ret = OK;
int i;
UNUSED(i);
#ifdef CONFIG_PWM_NCHANNELS
for (i = 0; i < CONFIG_PWM_NCHANNELS; i++)
{
int8_t chan = info->channels[i].channel;
/* Break the loop if all following channels are not configured */
if (chan == -1)
{
break;
}
bl602_pwm_freq(priv, chan, info->frequency);
bl602_pwm_duty(priv, chan, info->channels[i].duty);
pwm_channel_enable(chan);
}
#else
bl602_pwm_freq(priv, 0, info->frequency);
bl602_pwm_duty(priv, 0, info->duty);
pwm_channel_enable(0);
#endif
return ret;
}
/****************************************************************************
* Name: bl602_pwm_stop
*
* Description:
* Stop the PWM
*
****************************************************************************/
static int bl602_pwm_stop(struct pwm_lowerhalf_s *dev)
{
int i;
struct bl602_pwm_s *priv = (struct bl602_pwm_s *)dev;
UNUSED(priv);
UNUSED(i);
#ifdef CONFIG_PWM_NCHANNELS
for (i = 0; i < CONFIG_PWM_NCHANNELS; i++)
{
pwm_channel_disable(i);
}
#else
pwm_channel_disable(0);
#endif
return OK;
}
/****************************************************************************
* Name: bl602_pwm_ioctl
*
* Description:
* Lower-half logic may support platform-specific ioctl commands
*
****************************************************************************/
static int bl602_pwm_ioctl(struct pwm_lowerhalf_s *dev,
int cmd, unsigned long arg)
{
struct bl602_pwm_s *priv = (struct bl602_pwm_s *)dev;
DEBUGASSERT(dev);
/* There are no platform-specific ioctl commands */
UNUSED(priv);
return -ENOTTY;
}
/****************************************************************************
* Public Function
****************************************************************************/
/****************************************************************************
* Name: bl602_pwminitialize
*
* Description:
* Initialize one timer for use with the upper_level PWM driver.
*
* Input Parameters:
* pwm - A number identifying the pwm instance.
*
* Returned Value:
* On success, a pointer to the BL602 lower half PWM driver is returned.
* NULL is returned on any failure.
*
****************************************************************************/
struct pwm_lowerhalf_s *bl602_pwminitialize(int pwm)
{
struct bl602_pwm_s *lower = NULL;
if (pwm > 0)
{
pwminfo("Initialize PWM%u failed\n", pwm);
return NULL;
}
pwminfo("Initialize PWM%u\n", pwm);
lower = &g_bl602_pwm0;
return (struct pwm_lowerhalf_s *)lower;
}