diff --git a/src/linux/Kconfig b/src/linux/Kconfig index 1647ef91..0ae58516 100644 --- a/src/linux/Kconfig +++ b/src/linux/Kconfig @@ -8,6 +8,7 @@ config LINUX_SELECT default y select HAVE_GPIO_ADC select HAVE_GPIO_SPI + select HAVE_GPIO_HARD_PWM config BOARD_DIRECTORY string diff --git a/src/linux/Makefile b/src/linux/Makefile index 6df57f44..4ceed5f3 100644 --- a/src/linux/Makefile +++ b/src/linux/Makefile @@ -3,7 +3,7 @@ dirs-y += src/linux src/generic src-y += linux/main.c linux/timer.c linux/console.c linux/watchdog.c -src-y += linux/pca9685.c linux/spidev.c linux/analog.c +src-y += linux/pca9685.c linux/spidev.c linux/analog.c linux/hard_pwm.c src-y += generic/crc16_ccitt.c generic/alloc.c CFLAGS_klipper.elf += -lutil diff --git a/src/linux/gpio.h b/src/linux/gpio.h index ff8e0cc2..d177581c 100644 --- a/src/linux/gpio.h +++ b/src/linux/gpio.h @@ -28,4 +28,11 @@ void spi_prepare(struct spi_config config); void spi_transfer(struct spi_config config, uint8_t receive_data , uint8_t len, uint8_t *data); +struct gpio_pwm { + int fd; + uint32_t period; +}; +struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint16_t val); +void gpio_pwm_write(struct gpio_pwm g, uint16_t val); + #endif // gpio.h diff --git a/src/linux/hard_pwm.c b/src/linux/hard_pwm.c new file mode 100644 index 00000000..25bfb2e7 --- /dev/null +++ b/src/linux/hard_pwm.c @@ -0,0 +1,97 @@ +// HW PWM upport via Linux PWM sysfs interface +// +// Copyright (C) 2019 Janne Grunau +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include +#include +#include +#include +#include +#include + +#include "gpio.h" // struct gpio_pwm +#include "internal.h" // NSECS_PER_TICK +#include "command.h" // shutdown +#include "sched.h" // sched_shutdown + +#define MAX_PWM (1 << 15) +DECL_CONSTANT("PWM_MAX", MAX_PWM); + +#define HARD_PWM_START (1<<16) +#define HARD_PWM(chip, pin) (((chip)<<8) + (pin) + HARD_PWM_START) +#define HARD_PWM_TO_CHIP(hard_pwm) (((hard_pwm) - HARD_PWM_START) >> 8) +#define HARD_PWM_TO_PIN(hard_pwm) (((hard_pwm) - HARD_PWM_START) & 0xff) + +DECL_ENUMERATION_RANGE("pin", "pwmchip0/pwm0", HARD_PWM(0, 0), 256); +DECL_ENUMERATION_RANGE("pin", "pwmchip1/pwm0", HARD_PWM(1, 0), 256); +DECL_ENUMERATION_RANGE("pin", "pwmchip2/pwm0", HARD_PWM(2, 0), 256); +DECL_ENUMERATION_RANGE("pin", "pwmchip3/pwm0", HARD_PWM(3, 0), 256); + +#define PWM_PATH "/sys/class/pwm/pwmchip%u/pwm%u/%s" + +struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint16_t val) +{ + char filename[256]; + char scratch[16]; + uint8_t chip_id = HARD_PWM_TO_CHIP(pin); + uint8_t pwm_id = HARD_PWM_TO_PIN(pin); + + struct gpio_pwm g = {}; + g.period = cycle_time * NSECS_PER_TICK; + + // configure period/cycle time. Always in nanoseconds + snprintf(filename, sizeof(filename), PWM_PATH, chip_id, pwm_id, "period"); + int fd = open(filename, O_WRONLY|O_CLOEXEC); + if (fd == -1) { + report_errno("pwm period", fd); + goto fail; + } + snprintf(scratch, sizeof(scratch), "%u", cycle_time * NSECS_PER_TICK); + write(fd, scratch, strlen(scratch)); + close(fd); + + // write duty cycle + snprintf(filename, sizeof(filename), PWM_PATH, chip_id, pwm_id, + "duty_cycle"); + fd = open(filename, O_WRONLY|O_CLOEXEC); + if (fd == -1) { + report_errno("pwm duty_cycle", fd); + goto fail; + } + + g.fd = fd; + gpio_pwm_write(g, val); + + // enable PWM + snprintf(filename, sizeof(filename), PWM_PATH, chip_id, pwm_id, "enable"); + fd = open(filename, O_WRONLY|O_CLOEXEC); + if (fd == -1) { + close(g.fd); + report_errno("pwm enable", fd); + goto fail; + } + write(fd, "1", 2); + close(fd); + + return g; + +fail: + if (fd >= 0) + close(fd); + shutdown("Unable to config pwm device"); +} + + +void gpio_pwm_write(struct gpio_pwm g, uint16_t val) +{ + char scratch[16]; + uint32_t duty_cycle = g.period * (uint64_t)val / MAX_PWM; + snprintf(scratch, sizeof(scratch), "%u", duty_cycle); + if (g.fd != -1) { + write(g.fd, scratch, strlen(scratch)); + } else { + report_errno("pwm set duty_cycle", g.fd); + } +} diff --git a/src/linux/internal.h b/src/linux/internal.h index b9e77d18..b47426b3 100644 --- a/src/linux/internal.h +++ b/src/linux/internal.h @@ -3,6 +3,10 @@ // Local definitions for micro-controllers running on linux #include // struct timespec +#include "autoconf.h" // CONFIG_CLOCK_FREQ + +#define NSECS 1000000000 +#define NSECS_PER_TICK (NSECS / CONFIG_CLOCK_FREQ) // console.c void report_errno(char *where, int rc); diff --git a/src/linux/timer.c b/src/linux/timer.c index 15e5ab76..fb8319af 100644 --- a/src/linux/timer.c +++ b/src/linux/timer.c @@ -21,9 +21,6 @@ static uint32_t last_read_time_counter; static struct timespec last_read_time, next_wake_time; static time_t start_sec; -#define NSECS 1000000000 -#define NSECS_PER_TICK (NSECS / CONFIG_CLOCK_FREQ) - // Compare two 'struct timespec' times static inline uint8_t timespec_is_before(struct timespec ts1, struct timespec ts2)