aboutsummaryrefslogtreecommitdiffstats
path: root/src/linux
diff options
context:
space:
mode:
authorJanne Grunau <janne-3d@jannau.net>2019-03-24 10:25:56 +0100
committerKevin O'Connor <kevin@koconnor.net>2019-04-05 19:06:15 -0400
commitaab89e7f85d01fffb349b2046daebdc0deb1ce92 (patch)
treec875725a48bce1170d6039103671f7331cb1fda1 /src/linux
parente6c3eeafd79cac08172377f447c1dd963b5cb530 (diff)
downloadkutter-aab89e7f85d01fffb349b2046daebdc0deb1ce92.tar.gz
kutter-aab89e7f85d01fffb349b2046daebdc0deb1ce92.tar.xz
kutter-aab89e7f85d01fffb349b2046daebdc0deb1ce92.zip
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>
Diffstat (limited to 'src/linux')
-rw-r--r--src/linux/Kconfig1
-rw-r--r--src/linux/Makefile2
-rw-r--r--src/linux/gpio.h7
-rw-r--r--src/linux/hard_pwm.c97
-rw-r--r--src/linux/internal.h4
-rw-r--r--src/linux/timer.c3
6 files changed, 110 insertions, 4 deletions
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 <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);
+ }
+}
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 <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);
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)