blob: db0175a63c5dbaa8b0682f356e6d7e224ab0ac14 [file] [log] [blame]
/****************************************************************************
* arch/risc-v/src/litex/litex_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 <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 "litex_pwm.h"
#include "litex_clockconfig.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_PWM_PULSECOUNT
#error PWM puslecount not supported for Litex.
#endif
#ifdef CONFIG_PWM_MULTICHAN
#error PWM multichannel not supported for Litex.
#endif
/* Control register offsets from peripheral base address */
#define PWM_ENABLE_REG_OFFSET 0 /* Enable register */
#define PWM_WIDTH_REG_OFFSET 4 /* Pulse width control register */
#define PWM_PERIOD_REG_OFFSET 8 /* Period register*/
/* Enable register bit definitions */
#define PWM_ENABLE_SET_BIT 1 /* Bit used to enable/disable PWM */
/* The minimum period required for *sane* operation. This ensures that
* setting the duty cycle actually makes sense. However, it does limit
* the maximum PWM frequency.
*/
#define PWM_MINIMUM_PERIOD 10
/****************************************************************************
* Private Types
****************************************************************************/
struct litex_pwm_s
{
const struct pwm_ops_s *ops; /* PWM operations */
uint32_t base; /* Base address of PWM register */
uint32_t frequency; /* The current frequency */
uint32_t duty; /* The current duty cycle */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* PWM driver methods needed by lower half driver operations */
static int litex_pwm_setup(struct pwm_lowerhalf_s *dev);
static int litex_pwm_shutdown(struct pwm_lowerhalf_s *dev);
static int litex_pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info);
static int litex_pwm_stop(struct pwm_lowerhalf_s *dev);
static int litex_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_litex_pwmops =
{
.setup = litex_pwm_setup,
.shutdown = litex_pwm_shutdown,
.start = litex_pwm_start,
.stop = litex_pwm_stop,
.ioctl = litex_pwm_ioctl,
};
/* Data structure containing the operations and base address for all enabled
* peripherals.
*/
struct litex_pwm_s g_litex_pwm_inst[] =
{ [0 ... LITEX_PWM_MAX]
{
.ops = &g_litex_pwmops,
.base = 0,
.frequency = 0,
.duty = 0
}
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: litex_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 pointer to the lower half instance to operate on.
*
* Returned Value:
* OK on success. A negated error number is returned on failure.
*
****************************************************************************/
static int litex_pwm_setup(struct pwm_lowerhalf_s *dev)
{
struct litex_pwm_s *priv = (struct litex_pwm_s *)dev;
int ret = OK;
DEBUGASSERT(dev);
DEBUGASSERT(priv->base);
/* Just make sure that the device is not going to output anything */
putreg32(0, priv->base + PWM_ENABLE_REG_OFFSET);
return ret;
}
/****************************************************************************
* Name: litex_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 pointer to the lower half instance to operate on.
*
* Returned Value:
* OK on success. A negated error number is returned on failure.
*
****************************************************************************/
static int litex_pwm_shutdown(struct pwm_lowerhalf_s *dev)
{
struct litex_pwm_s *priv = (struct litex_pwm_s *)dev;
int ret = OK;
DEBUGASSERT(dev);
DEBUGASSERT(priv->base);
/* Disable PWM output */
putreg32(0, priv->base + PWM_ENABLE_REG_OFFSET);
return ret;
}
/****************************************************************************
* Name: litex_pwm_start
*
* Description:
* (Re-)initialize the PWM and start the pulsed output
*
* Input Parameters:
* dev - A pointer to the lower half instance to operate on.
* info - Structure containing the desired PWM characteristics.
*
* Returned Value:
* OK on success. A negated error number is returned on failure.
*
****************************************************************************/
static int litex_pwm_start(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info)
{
struct litex_pwm_s *priv = (struct litex_pwm_s *)dev;
int ret = OK;
uint32_t sysclk_freq;
uint32_t period;
uint32_t duty;
bool update_frequency;
bool update_duty;
const uint32_t max_in_duty = 65536;
const uint32_t min_in_duty = 1;
update_frequency = priv->frequency != info->frequency;
update_duty = update_frequency | (priv->duty != info->duty);
DEBUGASSERT(dev);
DEBUGASSERT(priv->base);
if (update_frequency)
{
if (info->frequency == 0)
{
pwmwarn("Cannot set PMW to a frequency of 0Hz\n");
return -EPERM;
}
/* Calculate the period for the required frequency */
sysclk_freq = litex_get_hfclk();
period = sysclk_freq / info->frequency;
if (period < PWM_MINIMUM_PERIOD)
{
pwmwarn("Frequency %luHz too high for sysclk %luHz\n",
info->frequency, sysclk_freq);
return -EPERM;
}
priv->frequency = info->frequency;
putreg32(period, priv->base + PWM_PERIOD_REG_OFFSET);
pwminfo("Update PWM period to %lu\n", period);
}
if (update_duty)
{
/* Map the duty cycle compare to the period */
/* The period may have already been calculated when adjusting the
* frequency. However, if the frequency doesn't change, it will be
* set to an undefined value. Just fetch it each time from hardware.
*/
period = getreg32(priv->base + PWM_PERIOD_REG_OFFSET);
duty = period * (info->duty / (float)(max_in_duty - min_in_duty));
priv->duty = info->duty;
putreg32(duty, priv->base + PWM_WIDTH_REG_OFFSET);
pwminfo("Update PWM duty to %lu\n", duty);
}
putreg32(PWM_ENABLE_SET_BIT, priv->base + PWM_ENABLE_REG_OFFSET);
return ret;
}
/****************************************************************************
* Name: litex_pwm_stop
*
* Description:
* Stop the PWM
*
* Input Parameters:
* dev - A pointer to the lower half instance to operate on.
*
* Returned Value:
* OK on success. A negated error number is returned on failure.
*
****************************************************************************/
static int litex_pwm_stop(struct pwm_lowerhalf_s *dev)
{
struct litex_pwm_s *priv = (struct litex_pwm_s *)dev;
DEBUGASSERT(dev);
DEBUGASSERT(priv->base);
putreg32(0, priv->base + PWM_ENABLE_REG_OFFSET);
return OK;
}
/****************************************************************************
* Name: litex_pwm_ioctl
*
* Description:
* Lower-half logic may support platform-specific ioctl commands.
* Not implemented for Litex.
*
* Input Parameters:
* dev - A pointer to the lower half instance to operate on.
* cmd - IO control command.
* arg - IO control command argument.
*
* Returned Value:
* OK on success. A negated error number is returned on failure.
*
****************************************************************************/
static int litex_pwm_ioctl(struct pwm_lowerhalf_s *dev,
int cmd, unsigned long arg)
{
/* There are no platform-specific ioctl commands */
UNUSED(dev);
UNUSED(cmd);
UNUSED(arg);
return -ENOTTY;
}
/****************************************************************************
* Public Function
****************************************************************************/
/****************************************************************************
* Name: litex_pwminitialize
*
* Description:
* Initialize one PWM channel
*
* Input Parameters:
* pwm - A number identifying the pwm instance.
*
* Returned Value:
* On success, a pointer to the Litex lower half PWM driver is returned.
* NULL is returned on any failure.
*
****************************************************************************/
struct pwm_lowerhalf_s *litex_pwminitialize(int pwm)
{
struct litex_pwm_s *lower = NULL;
if (pwm >= LITEX_PWM_MAX)
{
return NULL;
}
lower = &g_litex_pwm_inst[pwm];
lower->base = LITEX_PWM_BASE + (LITEX_PWM_OFFSET * pwm);
return (struct pwm_lowerhalf_s *)lower;
}