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:
Janne Grunau 2019-03-24 10:25:56 +01:00 committed by Kevin O'Connor
parent e6c3eeafd7
commit aab89e7f85
6 changed files with 110 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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

97
src/linux/hard_pwm.c Normal file
View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)