nuttx/arch/arm/src/tlsr82/tlsr82_pwm.c
wangbowen6 589647308c arm/tlsr82: move peripherals pin config to board.h
Signed-off-by: wangbowen6 <wangbowen6@xiaomi.com>
2022-09-28 09:30:06 +08:00

861 lines
22 KiB
C

/****************************************************************************
* arch/arm/src/tlsr82/tlsr82_pwm.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 <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/timers/pwm.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "chip.h"
#include "tlsr82_pwm.h"
#include "tlsr82_gpio.h"
#include "tlsr82_gpio_cfg.h"
#include "hardware/tlsr82_pwm.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* The pwm clock source is system clock */
#define PWM_SRC_CLK_HZ (CONFIG_TLSR82_CPU_CLK_MHZ * 1000000)
/* The default clkdiv = 9, default pwm clk = PWM_SRC_CLK_HZ / 10 */
#define PWM_INIT_CLKDIV 9
/* Make sure the max frequency has at least 1% duty accruay */
#define PWM_MAX_FREQ (g_pwmclk / 100)
#define PWM_MIN_FREQ (g_pwmclk / UINT16_MAX + 1)
/* PWM max pulse count number, bit 0 ~ 13 */
#define PWM_MAX_COUNT (0x3fff)
/* PWM simple operation definition */
#define PWM_INT_PEND(id) BM_IS_SET(PWM_IRQ_STA_REG, 1 << ((id) + 2))
#define PWM_INT_CLEAR(id) BM_CLR(PWM_IRQ_STA_REG, 1 << ((id) + 2))
#define PWM_INT_ENABLE(id) BM_SET(PWM_IRQ_CTRL_REG, 1 << ((id) + 2))
#define PWM_INT_DISABLE(id) BM_CLR(PWM_IRQ_CTRL_REG, 1 << ((id) + 2))
#define PWM0_PNUM_INT_PEND() BM_IS_SET(PWM_IRQ_STA_REG, 1 << 0)
#define PWM0_PNUM_INT_CLEAR() BM_CLR(PWM_IRQ_STA_REG, 1 << 0)
#define PWM0_PNUM_INT_ENABLE() BM_SET(PWM_IRQ_CTRL_REG, 1 << 0)
#define PWM0_PNUM_INT_ISENABLE() BM_IS_SET(PWM_IRQ_CTRL_REG, 1 << 0)
#define PWM0_PNUM_INT_DISABLE() BM_CLR(PWM_IRQ_CTRL_REG, 1 << 0)
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure represents the state of one PWM timer */
struct tlsr82_pwmtimer_s
{
const struct pwm_ops_s *ops; /* PWM operations */
uint32_t pincfg; /* PWM pin config */
uint8_t id; /* PWM hardware id */
bool invert; /* PWM output is inverted or not */
bool started; /* PWM output has started or not */
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
uint8_t irq; /* Timer update IRQ */
uint32_t count; /* Remaining pulse count */
#endif
uint32_t frequency; /* Current frequency setting */
uint16_t max;
uint16_t cmp;
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
void *handle; /* Handle used for upper-half callback */
#endif
};
/****************************************************************************
* Static Function Prototypes
****************************************************************************/
/* Register access */
#ifdef CONFIG_DEBUG_PWM_INFO
static void pwm_dumpregs(const char *msg);
#else
# define pwm_dumpregs(msg)
#endif
/* Timer management */
static void pwm_enable(struct tlsr82_pwmtimer_s *priv, bool en);
static int pwm_cfg_check(struct tlsr82_pwmtimer_s *priv);
static int pwm_config(struct tlsr82_pwmtimer_s *priv,
const struct pwm_info_s *info);
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
static int pwm_interrupt(int irq, void *context, void *arg);
#endif
/* PWM driver methods */
static int pwm_setup(struct pwm_lowerhalf_s *dev);
static int pwm_shutdown(struct pwm_lowerhalf_s *dev);
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
static int pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info,
void *handle);
#else
static int pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info);
#endif
static int pwm_stop(struct pwm_lowerhalf_s *dev);
static int pwm_ioctl(struct pwm_lowerhalf_s *dev,
int cmd, unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static uint32_t g_pwmclk;
/* This is the list of lower half PWM driver methods used by the upper half
* driver
*/
static const struct pwm_ops_s g_pwmops =
{
.setup = pwm_setup,
.shutdown = pwm_shutdown,
.start = pwm_start,
.stop = pwm_stop,
.ioctl = pwm_ioctl,
};
#ifdef CONFIG_TLSR82_PWM0
static struct tlsr82_pwmtimer_s g_pwm0dev =
{
.ops = &g_pwmops,
.pincfg = BOARD_PWM0_PIN,
.id = 0,
.invert = false,
.started = false,
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
.irq = NR_SW_PWM_IRQ,
#endif
};
#endif
#ifdef CONFIG_TLSR82_PWM1
static struct tlsr82_pwmtimer_s g_pwm1dev =
{
.ops = &g_pwmops,
.pincfg = BOARD_PWM1_PIN,
.id = 1,
.invert = true,
.started = false,
};
#endif
#ifdef CONFIG_TLSR82_PWM2
static struct tlsr82_pwmtimer_s g_pwm2dev =
{
.ops = &g_pwmops,
.pincfg = BOARD_PWM2_PIN,
.id = 2,
.invert = true,
.started = false,
};
#endif
#ifdef CONFIG_TLSR82_PWM3
static struct tlsr82_pwmtimer_s g_pwm3dev =
{
.ops = &g_pwmops,
.pincfg = BOARD_PWM3_PIN,
.id = 3,
.invert = false,
.started = false,
};
#endif
#ifdef CONFIG_TLSR82_PWM4
static struct tlsr82_pwmtimer_s g_pwm4dev =
{
.ops = &g_pwmops,
.pincfg = BOARD_PWM4_PIN,
.id = 4,
.invert = false,
.started = false,
};
#endif
#ifdef CONFIG_TLSR82_PWM5
static struct tlsr82_pwmtimer_s g_pwm5dev =
{
.ops = &g_pwmops,
.pincfg = BOARD_PWM5_PIN,
.id = 5,
.invert = false,
.started = false,
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: pwm_dumpregs
*
* Description:
* Dump all pwm registers.
*
* Input Parameters:
* msg - message.
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_DEBUG_PWM_INFO
static void pwm_dumpregs(const char *msg)
{
int i;
pwminfo("%s:\n", msg);
pwminfo("PWM_CLKDIV_REG : 0x%x\n", PWM_CLKDIV_REG);
pwminfo("PWM_ENABLE0_REG : 0x%x\n", PWM_ENABLE0_REG);
pwminfo("PWM_MODE0_REG : 0x%x\n", PWM_MODE0_REG);
pwminfo("PWM_ENABLE_REG : 0x%x\n", PWM_ENABLE_REG);
pwminfo("PWM_INVERT_REG : 0x%x\n", PWM_INVERT_REG);
pwminfo("PWM_N_INVERT_REG : 0x%x\n", PWM_N_INVERT_REG);
pwminfo("PWM_POL_REG : 0x%x\n", PWM_POL_REG);
for (i = 0; i < 5; i++)
{
pwminfo("PWM_CYCLE_REG(%d): 0x%lx\n", i, PWM_CYCLE_REG(i));
pwminfo("PWM_CMP_REG(%d) : 0x%x\n", i, PWM_CMP_REG(i));
pwminfo("PWM_MAX_REG(%d) : 0x%x\n", i, PWM_MAX_REG(i));
}
pwminfo("PWM_PLUSE_NUM_RE : 0x%x\n", PWM_PLUSE_NUM_REG);
pwminfo("PWM_IRQ_CTRL_REG : 0x%x\n", PWM_IRQ_CTRL_REG);
pwminfo("PWM_IRQ_STA_REG : 0x%x\n", PWM_IRQ_STA_REG);
}
#endif
/****************************************************************************
* Name: pwm_config
*
* Description:
* (Re-)initialize the timer resources and start the pulsed output
*
* Input Parameters:
* priv - A reference to the lower half PWM driver state structure
* info - A reference to the characteristics of the pulsed output
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
static int pwm_config(struct tlsr82_pwmtimer_s *priv,
const struct pwm_info_s *info)
{
/* Calculated values */
uint16_t max;
uint16_t cmp;
if (priv == NULL || info == NULL)
{
pwmerr("priv or info is null, priv=%p info=%p\n", priv, info);
return -EINVAL;
}
pwminfo("PWM%d invert: %d frequency: %lu duty: %08lx\n",
(int)priv->id, (int)priv->invert, info->frequency,
info->duty);
if (info->frequency == 0 || info->duty >= uitoub16(100))
{
pwrerr("pwm info is invalid, fre: %lu duty: %08lx\n",
info->frequency, info->duty);
return -EINVAL;
}
if (info->frequency > PWM_MAX_FREQ || info->frequency < PWM_MIN_FREQ)
{
pwmerr("pwm freq out of range, freq: %ld, max: %ld, min: %ld\n",
info->frequency, PWM_MAX_FREQ, PWM_MIN_FREQ);
return -EINVAL;
}
/* Calculate the MAX and CMP register value
* period = max_reg / g_pwmclk
* freq = g_pwmclk / max_reg
*/
max = (uint16_t)((g_pwmclk / info->frequency));
cmp = (uint16_t)(((uint64_t)max * (uint64_t)info->duty) >> 16);
pwminfo("PWM%d PCLK: %lu freq: %lu duty: %lu max: %u cmp: %u\n",
priv->id, g_pwmclk, info->frequency, info->duty, max, cmp);
priv->max = max;
priv->cmp = cmp;
PWM_MAX_REG(priv->id) = priv->max;
PWM_CMP_REG(priv->id) = priv->cmp;
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
if (priv->count > 0)
{
PWM_MODE0_REG = PWM_MODE0_COUNT;
PWM_PLUSE_NUM_REG = priv->count;
PWM0_PNUM_INT_ENABLE();
up_enable_irq(priv->irq);
}
else
{
PWM_MODE0_REG = PWM_MODE0_NORMAL;
}
#endif
pwm_enable(priv, true);
return OK;
}
/****************************************************************************
* Name: pwm_interrupt
*
* Description:
* Handle timer interrupts.
*
* Input Parameters:
* priv - A reference to the lower half PWM driver state structure
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
static int pwm_interrupt(int irq, void *context, void *arg)
{
struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)arg;
if (PWM0_PNUM_INT_PEND() && PWM0_PNUM_INT_ISENABLE())
{
/* Disable first interrupts, stop and reset the timer */
pwm_enable(priv, false);
/* Disable PWMn interrupt and clear interrupt flag */
PWM_INT_DISABLE(priv->id);
PWM_INT_CLEAR(priv->id);
/* Then perform the callback into the upper half driver */
pwm_expired(priv->handle);
priv->handle = NULL;
}
return OK;
}
#endif
/****************************************************************************
* Name: pwm_enable
*
* Description:
* Enable or disable pwm
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* en - Enable clock if 'en' is 'true' and disable if 'false'
*
****************************************************************************/
static void pwm_enable(struct tlsr82_pwmtimer_s *priv, bool en)
{
int id = (int)priv->id;
if (en)
{
if (priv->started)
{
return;
}
if (id == 0)
{
BM_SET(PWM_ENABLE0_REG, 1);
}
else
{
BM_SET(PWM_ENABLE_REG, 1 << id);
}
priv->started = true;
}
else
{
if (!priv->started)
{
return;
}
if (id == 0)
{
BM_CLR(PWM_ENABLE0_REG, 1);
}
else
{
BM_CLR(PWM_ENABLE_REG, 1 << id);
}
priv->started = false;
}
}
/****************************************************************************
* Name: pwm_cfg_check
*
* Description:
* This method is called when the driver intialize. This function will
* check the pincfg, if pincfg is not valid or current pin can not be used
* as PWM, this function will call
* PANIC() to assert here.
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
*
****************************************************************************/
static int pwm_cfg_check(struct tlsr82_pwmtimer_s *priv)
{
uint32_t pincfg = priv->pincfg;
if (pincfg == GPIO_INVLD_CFG)
{
pwmerr("pwm pincfg is not valid, pincfg=0x%lx\n", pincfg);
PANIC();
return -EINVAL;
}
if (tlsr82_gpio_cfg_check(pincfg, TLSR82_MUX_PWM) != 0)
{
pwmerr("pincfg=0x%lx not support pwm mux\n", pincfg);
PANIC();
return -EINVAL;
}
return OK;
}
/****************************************************************************
* Name: 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.
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
static int pwm_setup(struct pwm_lowerhalf_s *dev)
{
struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)dev;
uint32_t pincfg;
int ret = OK;
pwm_dumpregs("Initially");
/* Configure the PWM output pins, but do not start the timer yet */
pincfg = priv->pincfg;
pwminfo("pincfg: %08lx\n", pincfg);
/* Set the pwm_id_n be inverted to unify the pwm duty config, this
* configuration must precede the gpio configuration, otherwise, the
* gpio output is high between the invert and gpio config, and become
* low after the invert configuration.
*/
if (priv->invert)
{
BM_SET(PWM_N_INVERT_REG, 1 << priv->id);
}
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
if (priv->id == 0)
{
ret = irq_attach(priv->irq, pwm_interrupt, dev);
if (ret < 0)
{
pwmerr("ERROR: PWM0 plusecount interrupt attach failed, ret=%d\n",
ret);
return ret;
}
}
#endif
tlsr82_gpioconfig(pincfg);
return ret;
}
/****************************************************************************
* Name: 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
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
static int pwm_shutdown(struct pwm_lowerhalf_s *dev)
{
struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)dev;
/* Make sure that the output has been stopped */
pwm_stop(dev);
/* Then put the GPIO pins back to the default state */
tlsr82_gpiounconfig(priv->pincfg);
return OK;
}
/****************************************************************************
* Name: pwm_start
*
* Description:
* (Re-)initialize the timer resources and start the pulsed output
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* info - A reference to the characteristics of the pulsed output
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
#ifdef CONFIG_PWM_PULSECOUNT
static int pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info,
void *handle)
#else
static int pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info)
#endif
{
int ret = OK;
struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)dev;
#ifdef CONFIG_PWM_PULSECOUNT
/* Check if a pulsecount has been selected */
if (info->count > 0)
{
/* Only the PWM0 support the pulse counting */
if (priv->id != 0)
{
pwmerr("ERROR: PWM%d cannot support pulse count: %lu\n",
priv->id, info->count);
return -EPERM;
}
if (info->count > PWM_MAX_COUNT)
{
pwmerr("ERROR: PWM0 count out of range, count: %lu, max: %d",
info->count, PWM_MAX_COUNT);
return -EINVAL;
}
priv->count = info->count;
}
/* Save the handle */
priv->handle = handle;
#endif
/* Config the PWM */
ret = pwm_config(priv, info);
/* Save current frequency */
if (ret == OK)
{
priv->frequency = info->frequency;
}
return ret;
}
/****************************************************************************
* Name: pwm_stop
*
* Description:
* Stop the pulsed output and reset the timer resources
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
* Assumptions:
* This function is called to stop the pulsed output at anytime. This
* method is also called from the timer interrupt handler when a repetition
* count expires... automatically stopping the timer.
*
****************************************************************************/
static int pwm_stop(struct pwm_lowerhalf_s *dev)
{
struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)dev;
irqstate_t flags;
pwminfo("PWM%u\n", priv->id);
/* Disable interrupts momentary to stop any ongoing pwm processing and
* to prevent any concurrent access to the reset register.
*/
flags = enter_critical_section();
/* Disable PWMn output */
pwm_enable(priv, false);
/* Disable PWMn interrupt and clear interrupt flag */
PWM_INT_DISABLE(priv->id);
PWM_INT_CLEAR(priv->id);
#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT
/* Diable and clear PWM0 count interrupt flag */
if (priv->count > 0)
{
priv->count = 0;
up_disable_irq(priv->irq);
PWM0_PNUM_INT_DISABLE();
PWM0_PNUM_INT_CLEAR();
}
#endif
leave_critical_section(flags);
pwm_dumpregs("After stop");
return OK;
}
/****************************************************************************
* Name: pwm_ioctl
*
* Description:
* Lower-half logic may support platform-specific ioctl commands
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* cmd - The ioctl command
* arg - The argument accompanying the ioctl command
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
static int pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd,
unsigned long arg)
{
#ifdef CONFIG_DEBUG_PWM_INFO
struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)dev;
/* There are no platform-specific ioctl commands */
pwminfo("PWM%d\n", priv->id);
#endif
return -ENOTTY;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: tlsr82_pwminitialize
*
* Description:
* Initialize one timer for use with the upper_level PWM driver.
*
* Input Parameters:
* timer - A number identifying the timer use. The number of valid timer
* IDs varies with the STM32 MCU and MCU family but is somewhere in
* the range of {1,..,17}.
*
* Returned Value:
* On success, a pointer to the STM32 lower half PWM driver is returned.
* NULL is returned on any failure.
*
****************************************************************************/
int tlsr82_pwminitialize(const char *devpath, int miror)
{
struct tlsr82_pwmtimer_s *lower;
int ret = OK;
static bool pwm_initialized = false;
if (!pwm_initialized)
{
pwm_initialized = true;
/* Disable all the output */
BM_CLR(PWM_ENABLE0_REG, 0xff);
BM_CLR(PWM_ENABLE_REG, 0xff);
/* Config the pwm clock */
g_pwmclk = PWM_SRC_CLK_HZ / (PWM_INIT_CLKDIV + 1);
PWM_CLKDIV_REG = PWM_INIT_CLKDIV;
/* Disable all the pwm interrupt and clear all the interrupt flags */
BM_CLR(PWM_IRQ_CTRL_REG, 0xff);
BM_SET(PWM_IRQ_STA_REG, 0xff);
/* Enable the pwm total interrupt */
up_enable_irq(NR_SW_PWM_IRQ);
}
pwminfo("PWM%u\n", miror);
switch (miror)
{
#ifdef CONFIG_TLSR82_PWM0
case 0:
lower = &g_pwm0dev;
break;
#endif
#ifdef CONFIG_TLSR82_PWM1
case 1:
lower = &g_pwm1dev;
break;
#endif
#ifdef CONFIG_TLSR82_PWM2
case 2:
lower = &g_pwm2dev;
break;
#endif
#ifdef CONFIG_TLSR82_PWM3
case 3:
lower = &g_pwm3dev;
break;
#endif
#ifdef CONFIG_TLSR82_PWM4
case 4:
lower = &g_pwm4dev;
break;
#endif
#ifdef CONFIG_TLSR82_PWM5
case 5:
lower = &g_pwm5dev;
break;
#endif
default:
pwmerr("ERROR: No such pwm configured\n");
return -EINVAL;
}
ret = pwm_cfg_check(lower);
if (ret < 0)
{
return ret;
}
ret = pwm_register(devpath, (struct pwm_lowerhalf_s *)lower);
if (ret < 0)
{
pwmerr("%s has existed\n", devpath);
return ret;
}
pwm_dumpregs("tlsr82_pwminitialize");
return ret;
}