From 05c6d7c7b9279c5c457b628885f233dd8936cae2 Mon Sep 17 00:00:00 2001 From: Stuart Ianna Date: Wed, 1 Feb 2023 14:35:51 +1100 Subject: [PATCH] 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. --- arch/risc-v/src/litex/Kconfig | 4 + arch/risc-v/src/litex/Make.defs | 4 + .../src/litex/hardware/litex_memorymap.h | 15 + arch/risc-v/src/litex/litex_pwm.c | 357 ++++++++++++++++++ arch/risc-v/src/litex/litex_pwm.h | 57 +++ .../litex/arty_a7/configs/pwm/defconfig | 72 ++++ boards/risc-v/litex/arty_a7/src/Makefile | 4 + boards/risc-v/litex/arty_a7/src/arty_a7.h | 17 + .../risc-v/litex/arty_a7/src/litex_bringup.c | 9 + boards/risc-v/litex/arty_a7/src/litex_pwm.c | 89 +++++ 10 files changed, 628 insertions(+) create mode 100644 arch/risc-v/src/litex/litex_pwm.c create mode 100644 arch/risc-v/src/litex/litex_pwm.h create mode 100644 boards/risc-v/litex/arty_a7/configs/pwm/defconfig create mode 100644 boards/risc-v/litex/arty_a7/src/litex_pwm.c diff --git a/arch/risc-v/src/litex/Kconfig b/arch/risc-v/src/litex/Kconfig index f3a04ed4ee..56f94b03fa 100644 --- a/arch/risc-v/src/litex/Kconfig +++ b/arch/risc-v/src/litex/Kconfig @@ -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 diff --git a/arch/risc-v/src/litex/Make.defs b/arch/risc-v/src/litex/Make.defs index fc72c9c145..f6b5c3ac03 100644 --- a/arch/risc-v/src/litex/Make.defs +++ b/arch/risc-v/src/litex/Make.defs @@ -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 diff --git a/arch/risc-v/src/litex/hardware/litex_memorymap.h b/arch/risc-v/src/litex/hardware/litex_memorymap.h index 3cf0fbe232..ce56347952 100644 --- a/arch/risc-v/src/litex/hardware/litex_memorymap.h +++ b/arch/risc-v/src/litex/hardware/litex_memorymap.h @@ -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 diff --git a/arch/risc-v/src/litex/litex_pwm.c b/arch/risc-v/src/litex/litex_pwm.c new file mode 100644 index 0000000000..c502cac04d --- /dev/null +++ b/arch/risc-v/src/litex/litex_pwm.c @@ -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 + +#include +#include +#include +#include +#include +#include + +#include + +#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; +} diff --git a/arch/risc-v/src/litex/litex_pwm.h b/arch/risc-v/src/litex/litex_pwm.h new file mode 100644 index 0000000000..ffd5792d61 --- /dev/null +++ b/arch/risc-v/src/litex/litex_pwm.h @@ -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 +#include +#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 */ diff --git a/boards/risc-v/litex/arty_a7/configs/pwm/defconfig b/boards/risc-v/litex/arty_a7/configs/pwm/defconfig new file mode 100644 index 0000000000..0717b994fa --- /dev/null +++ b/boards/risc-v/litex/arty_a7/configs/pwm/defconfig @@ -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 diff --git a/boards/risc-v/litex/arty_a7/src/Makefile b/boards/risc-v/litex/arty_a7/src/Makefile index 511f8a84c9..497c7ab275 100644 --- a/boards/risc-v/litex/arty_a7/src/Makefile +++ b/boards/risc-v/litex/arty_a7/src/Makefile @@ -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 diff --git a/boards/risc-v/litex/arty_a7/src/arty_a7.h b/boards/risc-v/litex/arty_a7/src/arty_a7.h index 1eab373aea..e224c18f75 100644 --- a/boards/risc-v/litex/arty_a7/src/arty_a7.h +++ b/boards/risc-v/litex/arty_a7/src/arty_a7.h @@ -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 */ diff --git a/boards/risc-v/litex/arty_a7/src/litex_bringup.c b/boards/risc-v/litex/arty_a7/src/litex_bringup.c index 826d2eb4f2..426dba8e75 100644 --- a/boards/risc-v/litex/arty_a7/src/litex_bringup.c +++ b/boards/risc-v/litex/arty_a7/src/litex_bringup.c @@ -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; } diff --git a/boards/risc-v/litex/arty_a7/src/litex_pwm.c b/boards/risc-v/litex/arty_a7/src/litex_pwm.c new file mode 100644 index 0000000000..df32534fbc --- /dev/null +++ b/boards/risc-v/litex/arty_a7/src/litex_pwm.c @@ -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 + +#include +#include +#include +#include + +#include +#include + +#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; +}