linux: add support for Linux hardware PWM
The replicape servo pins (P9_14/P9_16) are muxed to the SOCs hardware PWM unit driven by a 13MHz GP timer. They have to be driven by the linux host mcu. This commits adds hardware PWM support using the linux sysfs user space interface. Signed-off-by: Janne Grunau <janne-3d@jannau.net> Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
e6c3eeafd7
commit
aab89e7f85
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// HW PWM upport via Linux PWM sysfs interface
|
||||
//
|
||||
// Copyright (C) 2019 Janne Grunau <janne-3d@jannau.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,10 @@
|
|||
// Local definitions for micro-controllers running on linux
|
||||
|
||||
#include <time.h> // 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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue