litex: Add PWM driver.

PWM driver for multiple peripherals supplied in gateware.

Only single channel frequency and duty cycle control is implemented. Pulse counting and multichannel features are not currently feasible.

Additions also include a new board configuration for arty-a7 which enables the PWM driver and example application.
This commit is contained in:
Stuart Ianna 2023-02-01 14:35:51 +11:00 committed by Alan Carvalho de Assis
parent 52eaefcc8e
commit 05c6d7c7b9
10 changed files with 628 additions and 0 deletions

View File

@ -102,6 +102,10 @@ config LITEX_SDIO1
select LITEX_SDIO_DMA
depends on LITEX_SDIO
config LITEX_PWM
bool "Enable PWM controller"
default n
if LITEX_SDIO1
config LITEX_IDMODE_FREQ

View File

@ -43,3 +43,7 @@ endif
ifeq ($(CONFIG_LITEX_ETHMAC),y)
CHIP_CSRCS += litex_emac.c
endif
ifeq ($(CONFIG_LITEX_PWM),y)
CHIP_CSRCS += litex_pwm.c
endif

View File

@ -64,6 +64,21 @@
#define LITEX_GPIO_OFFSET 0x00000020
#define LITEX_GPIO_MAX 8
/* PWM peripheral definitions.
* - LITEX_PWM_BASE is the first 32-bit address which contains a block
* of PWM registers (peripheral). Each block controls a single output
* channel.
* - LITEX_PWM_OFFSET is the number of bytes between each PWM peripheral.
* - LITEX_PWM_MAX is the number of peripherals enabled in gateware.
*
* Each peripheral is referenced by an index in the PWM driver. E.g Index 0
* is the first PWM peripheral at 0xF0009800. Index 1 is at 0xF0009008C.
*/
#define LITEX_PWM_BASE 0xf0009800
#define LITEX_PWM_OFFSET 0x0000000c
#define LITEX_PWM_MAX 4
#define LITEX_ETHMAC_RXBASE 0x80000000
#define LITEX_ETHMAC_TXBASE 0x80001000

View File

@ -0,0 +1,357 @@
/****************************************************************************
* 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;
}

View File

@ -0,0 +1,57 @@
/****************************************************************************
* arch/risc-v/src/litex/litex_pwm.h
*
* 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.
*
****************************************************************************/
#ifndef __ARCH_RISCV_LITEX_LITEX_PWM_H
#define __ARCH_RISCV_LITEX_LITEX_PWM_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/timers/pwm.h>
#include "hardware/litex_memorymap.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: litex_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 Litex lower half PWM driver is returned.
* NULL is returned on any failure.
*
****************************************************************************/
struct pwm_lowerhalf_s *litex_pwminitialize(int pwm);
#endif /* __ARCH_RISCV_LITEX_LITEX_PWM_H */

View File

@ -0,0 +1,72 @@
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_DISABLE_PTHREAD is not set
# CONFIG_FS_PROCFS_EXCLUDE_BLOCKS is not set
# CONFIG_FS_PROCFS_EXCLUDE_ENVIRON is not set
# CONFIG_FS_PROCFS_EXCLUDE_MEMDUMP is not set
# CONFIG_FS_PROCFS_EXCLUDE_MEMINFO is not set
# CONFIG_FS_PROCFS_EXCLUDE_MOUNT is not set
# CONFIG_FS_PROCFS_EXCLUDE_MOUNTS is not set
# CONFIG_FS_PROCFS_EXCLUDE_PROCESS is not set
# CONFIG_FS_PROCFS_EXCLUDE_UPTIME is not set
# CONFIG_FS_PROCFS_EXCLUDE_USAGE is not set
# CONFIG_FS_PROCFS_EXCLUDE_VERSION is not set
# CONFIG_NSH_DISABLEBG is not set
# CONFIG_NSH_DISABLE_LOSMART is not set
# CONFIG_NSH_DISABLE_UNAME is not set
# CONFIG_STANDARD_SERIAL is not set
CONFIG_ARCH="risc-v"
CONFIG_ARCH_BOARD="arty_a7"
CONFIG_ARCH_BOARD_ARTY_A7=y
CONFIG_ARCH_CHIP="litex"
CONFIG_ARCH_CHIP_LITEX=y
CONFIG_ARCH_INTERRUPTSTACK=8192
CONFIG_ARCH_RISCV=y
CONFIG_ARCH_STACKDUMP=y
CONFIG_BINFMT_DISABLE=y
CONFIG_BOARD_LOOPSPERMSEC=10000
CONFIG_BUILTIN=y
CONFIG_DEBUG_FULLOPT=y
CONFIG_DEBUG_SYMBOLS=y
CONFIG_DEFAULT_SMALL=y
CONFIG_DEV_ZERO=y
CONFIG_EXAMPLES_HELLO=y
CONFIG_EXAMPLES_HELLO_STACKSIZE=8192
CONFIG_EXAMPLES_PWM=y
CONFIG_FS_PROCFS=y
CONFIG_IDLETHREAD_STACKSIZE=8192
CONFIG_INIT_ENTRYPOINT="nsh_main"
CONFIG_INIT_STACKSIZE=8192
CONFIG_INTELHEX_BINARY=y
CONFIG_LIBC_PERROR_STDOUT=y
CONFIG_LIBC_STRERROR=y
CONFIG_LITEX_PWM=y
CONFIG_NFILE_DESCRIPTORS_PER_BLOCK=6
CONFIG_NSH_ARCHINIT=y
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_FILEIOSIZE=64
CONFIG_NSH_STRERROR=y
CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE=8192
CONFIG_PTHREAD_STACK_DEFAULT=8192
CONFIG_PWM=y
CONFIG_RAM_SIZE=268435456
CONFIG_RAM_START=0x40000000
CONFIG_RAW_BINARY=y
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_WAITPID=y
CONFIG_STACK_COLORATION=y
CONFIG_START_DAY=20
CONFIG_START_MONTH=3
CONFIG_START_YEAR=2020
CONFIG_STDIO_DISABLE_BUFFERING=y
CONFIG_SYSTEM_NSH=y
CONFIG_TASK_NAME_SIZE=12
CONFIG_TESTING_GETPRIME=y
CONFIG_UART0_RXBUFSIZE=128
CONFIG_UART0_SERIAL_CONSOLE=y
CONFIG_UART0_TXBUFSIZE=128

View File

@ -34,4 +34,8 @@ ifeq ($(CONFIG_FS_AUTOMOUNTER),y)
CSRCS += litex_automount.c
endif
ifeq ($(CONFIG_LITEX_PWM),y)
CSRCS += litex_pwm.c
endif
include $(TOPDIR)/boards/Board.mk

View File

@ -144,4 +144,21 @@ void litex_automount_event(int slotno, bool inserted);
bool litex_cardinserted(int slotno);
#endif
/****************************************************************************
* Name: litex_pwm_setup
*
* Description:
* Initialise all PWM channels enabled in gateware and map them to
* /dev/pwmX. Where X is the PMW channel number. From 0 ... LITEX_PWM_MAX.
*
* Returned Value:
* OK is returned on success.
* -ENODEV is return on the first PWM device initialise failure.
*
****************************************************************************/
#ifdef CONFIG_LITEX_PWM
int litex_pwm_setup(void);
#endif
#endif /* __BOARDS_RISCV_LITEX_ARTY_A7_SRC_ARTY_A7_H */

View File

@ -102,5 +102,14 @@ int litex_bringup(void)
#endif /* CONFIG_LITEX_SDIO */
#ifdef CONFIG_LITEX_PWM
ret = litex_pwm_setup();
if (ret != OK)
{
syslog(LOG_ERR, "ERROR: Failed to setup PWM driver \n");
}
#endif /* CONFIG_LITEX_PWM */
return ret;
}

View File

@ -0,0 +1,89 @@
/****************************************************************************
* boards/risc-v/litex/arty_a7/src/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 <errno.h>
#include <debug.h>
#include <stddef.h>
#include <stdio.h>
#include <nuttx/timers/pwm.h>
#include <arch/board/board.h>
#include "litex_pwm.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: litex_pwm_setup
*
* Description:
* Initialise all PWM channels enabled in gateware and map them to
* /dev/pwmX. Where X is the PMW channel number. From 0 ... LITEX_PWM_MAX.
*
* Returned Value:
* OK is returned on success.
* -ENODEV is return on the first PWM device initialise failure.
*
****************************************************************************/
int litex_pwm_setup(void)
{
struct pwm_lowerhalf_s *pwm = NULL;
int ret = OK;
int channel;
char devpath[12] =
{
0
};
for (channel = 0; channel < LITEX_PWM_MAX; channel++)
{
pwm = litex_pwminitialize(channel);
if (!pwm)
{
pwmerr("Failed fetching PWM channel %d lower half\n", channel);
return -ENODEV;
}
/* Register the PWM driver at "/dev/pwmX" */
snprintf(devpath, 12, "/dev/pwm%d", channel);
ret = pwm_register(devpath, pwm);
if (ret < 0)
{
pwmerr("pwm_register channel %d failed: %d\n", channel, ret);
return ret;
}
}
return ret;
}