From: lakshmi.sowjanya.d@intel.com
To: linus.walleij@linaro.org
Cc: linux-gpio@vger.kernel.org, bgolaszewski@baylibre.com,
linux-kernel@vger.kernel.org, mgross@linux.intel.com,
andriy.shevchenko@linux.intel.com, tamal.saha@intel.com,
bala.senthil@intel.com, lakshmi.sowjanya.d@intel.com
Subject: [RFC PATCH v1 20/20] tools: gpio: Add PWM monitor application
Date: Tue, 24 Aug 2021 22:18:01 +0530 [thread overview]
Message-ID: <20210824164801.28896-21-lakshmi.sowjanya.d@intel.com> (raw)
In-Reply-To: <20210824164801.28896-1-lakshmi.sowjanya.d@intel.com>
From: Christopher Hall <christopher.s.hall@intel.com>
The Intel(R) Timed I/O driver produces periodic output using the PMW
interface. The output alignment is set initially with respect to the system
clock using the PWM 'alignment' parameter. There will always be a small
frequency difference between the system clock and the hardware ART clock
used to drive the PWM output especially if the system clock is adjusted by
the PTP/NTP time sync daemon.
A GPIO line is used to 'monitor' the PWM output's drift. Small changes are
made to the output frequency, using a PI controller, to compensate for the
drift. The PWM monitor application (gpio-pwm-mon) implements the PI
controller. The application takes four arguments: GPIO device, PWM device,
output alignment in nanoseconds (ns), and period in ns. The first two
arguments are mandatory, the last two are optional defaulting to a one
second period aligned to 0 ns. Example usage is:
gpio-pwm-mon -g gpiochip0 -p pwmchip0 -a <alignment> -r <period>
-o <num_of_lines>
Signed-off-by: Christopher Hall <christopher.s.hall@intel.com>
Signed-off-by: Tamal Saha <tamal.saha@intel.com>
Co-developed-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>
Signed-off-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>
Reviewed-by: Mark Gross <mgross@linux.intel.com>
---
tools/gpio/Build | 1 +
tools/gpio/Makefile | 12 +-
tools/gpio/gpio-pwm-mon.c | 431 ++++++++++++++++++++++++++++++++++++++
3 files changed, 443 insertions(+), 1 deletion(-)
create mode 100644 tools/gpio/gpio-pwm-mon.c
diff --git a/tools/gpio/Build b/tools/gpio/Build
index dc6a178c195a..1d4fc7bb9d7d 100644
--- a/tools/gpio/Build
+++ b/tools/gpio/Build
@@ -3,4 +3,5 @@ lsgpio-y += lsgpio.o gpio-utils.o
gpio-hammer-y += gpio-hammer.o gpio-utils.o
gpio-event-mon-y += gpio-event-mon.o gpio-utils.o
gpio-event-gen-y += gpio-event-gen.o gpio-utils.o
+gpio-pwm-mon-y += gpio-pwm-mon.o gpio-utils.o
gpio-watch-y += gpio-watch.o
diff --git a/tools/gpio/Makefile b/tools/gpio/Makefile
index c9efaee76f28..63e158ef8f7f 100644
--- a/tools/gpio/Makefile
+++ b/tools/gpio/Makefile
@@ -18,7 +18,8 @@ MAKEFLAGS += -r
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
-ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon gpio-event-gen gpio-watch
+ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon gpio-event-gen gpio-pwm-mon \
+ gpio-watch
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
all: $(ALL_PROGRAMS)
@@ -75,6 +76,15 @@ $(GPIO_EVENT_GEN_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
$(OUTPUT)gpio-event-gen: $(GPIO_EVENT_GEN_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
+#
+# gpio-pwm-mon
+#
+GPIO_EVENT_MON_IN := $(OUTPUT)gpio-pwm-mon-in.o
+$(GPIO_EVENT_MON_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
+ $(Q)$(MAKE) $(build)=gpio-pwm-mon
+$(OUTPUT)gpio-pwm-mon: $(GPIO_EVENT_MON_IN)
+ $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) -lpthread -pthread $< -o $@
+
#
# gpio-watch
#
diff --git a/tools/gpio/gpio-pwm-mon.c b/tools/gpio/gpio-pwm-mon.c
new file mode 100644
index 000000000000..71e02aca8b27
--- /dev/null
+++ b/tools/gpio/gpio-pwm-mon.c
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gpio-pwm-mon - Use 'virtual' GPIO input line to monitor PWM output and
+ * adjust period to align the clock with the system clock
+ *
+ * Copyright (C) 2020 Intel Corporation
+ * Author: christopher.s.hall@intel.com
+ *
+ * Usage:
+ * gpio-pwm-mon -g gpiochip0 -p pwmchip0
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/gpio.h>
+#include <sys/ioctl.h>
+
+#define PWM_PATH "/sys/class/pwm"
+
+#define NSEC_PER_SEC (1000000000U)
+#define MAX_PERIOD (4000000000U /*ns*/)
+#define DEFAULT_PERIOD (125000000U /*ns*/)
+
+#define PWM_ENABLE (1)
+#define PWM_DISABLE (0)
+#define GPIO_MONITOR (1)
+#define PWM_LINE (0)
+
+#define KPROP_DEFAULT (1.0)
+#define KINT_DEFAULT (0.25)
+
+#define EVENT_SIZE (sizeof(struct gpio_v2_line_event) + sizeof(struct gpio_v2_line_event_ext))
+
+void print_usage(void)
+{
+ fprintf(stdout, "Usage: gpio-pwm-mon [options]...\n"
+ "Listen to events on virtual GPIO lines, adjust PWM\n"
+ "\t -g <name>\t Listen on the GPIO device (required)\n"
+ "\t -p <name>\t Generate output on the PWM device (required)\n"
+ "\t -a <ns>\t Output alignment (ns) to the second\n"
+ "\t -r <ns>\t Output period (ns) (default: %u, maximum: %u)\n"
+ "\t -?\t\t This helptext\n"
+ "\n"
+ "Example:\n"
+ "gpio-pwm-mon -g gpiochip0 -p pwmchip0\n",
+ DEFAULT_PERIOD, MAX_PERIOD
+ );
+}
+
+int write_unsigned_int_to_file(unsigned int val, char *file)
+{
+ char *buf;
+ int fd;
+ int ret = 0;
+
+ fd = open(file, O_WRONLY);
+ if (fd == -1)
+ return -errno;
+
+ ret = asprintf(&buf, "%u", val);
+ if (ret == -1) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = write(fd, buf, strlen(buf));
+ if (ret == -1)
+ ret = -errno;
+ else if (ret != strlen(buf))
+ ret = -EIO;
+ else
+ ret = 0;
+
+ free(buf);
+out:
+ close(fd);
+
+ return ret;
+}
+
+int start_pwm(const char *pwm_name, unsigned int pwm_number,
+ unsigned int period, unsigned int alignment)
+{
+ char *pwm_path;
+ int ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/export", PWM_PATH, pwm_name);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(pwm_number, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/period", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(MAX_PERIOD, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/duty_cycle", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(period / 2, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/period", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(period, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/alignment", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(alignment, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/enable", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(PWM_ENABLE, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int set_period_pwm(const char *pwm_name, unsigned int pwm_number,
+ unsigned int period)
+{
+ char *pwm_path;
+ int ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/period", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(period, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int stop_pwm(const char *pwm_name, unsigned int pwm_number)
+{
+ char *pwm_path;
+ int ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/pwm%u/enable", PWM_PATH, pwm_name,
+ pwm_number);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(PWM_DISABLE, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ ret = asprintf(&pwm_path, "%s/%s/unexport", PWM_PATH, pwm_name);
+ if (ret == -1)
+ return -errno;
+ ret = write_unsigned_int_to_file(pwm_number, pwm_path);
+ free(pwm_path);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int adjust_pwm_loop(const char *gpio_name, const char *pwm_name,
+ const char *consumer, unsigned int period,
+ unsigned int align, double kprop, double kint,
+ volatile bool *exit, int *lines, int num_lines)
+{
+ unsigned int adjusted_period = period;
+ struct gpio_v2_line_config config;
+ struct gpio_v2_line_event event[EVENT_SIZE];
+ struct gpio_v2_line_request req;
+ uint64_t last_event_timestamp;
+ uint64_t total_event_count;
+ uint64_t last_event_count;
+ uint64_t start_time = 0;
+ char *chrdev_name;
+ size_t event_size;
+ int i = 0;
+ int ret;
+ int fd;
+
+ ret = asprintf(&chrdev_name, "/dev/%s", gpio_name);
+ if (ret < 0)
+ return -ENOMEM;
+
+ fd = open(chrdev_name, 0);
+ if (fd == -1) {
+ ret = -errno;
+ fprintf(stderr, "Failed to open %s\n", chrdev_name);
+ goto exit_close_error;
+ }
+
+ memset(&config, 0, sizeof(config));
+ config.flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_FALLING |
+ GPIO_V2_LINE_FLAG_EDGE_RISING | GPIO_V2_LINE_FLAG_EVENT_COUNT;
+ memset(&req, 0, sizeof(req));
+ for (i = 0; i < num_lines; i++)
+ req.offsets[i] = lines[i];
+ req.num_lines = num_lines;
+ req.config = config;
+ req.config.flags = config.flags;
+ strcpy(req.consumer, consumer);
+
+ ret = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req);
+ if (ret == -1) {
+ ret = -errno;
+ fprintf(stderr, "Failed to issue GET EVENT IOCTL (%d)\n", ret);
+ goto exit_close_error;
+ }
+
+ while (!*exit) {
+ ret = read(req.fd, event, event_size);
+ if (ret == -1) {
+ if (errno == EAGAIN) {
+ continue;
+ } else {
+ ret = -errno;
+ fprintf(stderr, "Failed to read event (%d)\n",
+ ret);
+ break;
+ }
+ }
+
+ if (ret != event_size) {
+ ret = -EIO;
+ fprintf(stderr, "Reading event failed (%d)\n", ret);
+ break;
+ }
+
+ if (start_time == 0) {
+ uint32_t res;
+ bool round_up;
+
+ start_time = event[0].timestamp_ns;
+ if (event[0].ext[0].event_count > 1) {
+ ret = -EINTR;
+ fprintf(stderr, "Lost start event\n");
+ break;
+ } start_time -= align;
+ res = start_time % NSEC_PER_SEC;
+ round_up = res > NSEC_PER_SEC / 2;
+ start_time -= res;
+ start_time += round_up ? NSEC_PER_SEC : 0;
+ start_time += align;
+ total_event_count = event[0].ext[0].event_count - 1;
+ last_event_timestamp = start_time;
+ last_event_count = total_event_count;
+ } else {
+ uint64_t duration;
+ int int_error;
+ double prop_error;
+ double adjust;
+
+ total_event_count += event[0].ext[0].event_count;
+ if (total_event_count % 2 == 1)
+ continue;
+ duration = event[0].timestamp_ns - start_time;
+ int_error = duration > total_event_count / 2 * period ?
+ -(duration - total_event_count / 2 * period) :
+ total_event_count / 2 * period - duration;
+ prop_error = event[0].timestamp_ns - last_event_timestamp;
+ prop_error *= -1;
+ prop_error /= (total_event_count - last_event_count) / 2;
+ prop_error += period;
+ adjust = prop_error * kprop + int_error * kint;
+ adjusted_period += adjust;
+ set_period_pwm(pwm_name, PWM_LINE, adjusted_period);
+ last_event_count = total_event_count;
+ last_event_timestamp = event[0].timestamp_ns;
+ }
+ printf("Event %04llu timestamp: %llu\n", last_event_count,
+ last_event_timestamp);
+ }
+
+exit_close_error:
+ if (close(fd) == -1)
+ perror("Failed to close GPIO character device file");
+ free(chrdev_name);
+ return ret;
+}
+
+struct wait_arg {
+ volatile bool *exit;
+ sigset_t *sigint;
+};
+
+void *wait_for_interrupt(void *arg)
+{
+ volatile bool *exit = ((struct wait_arg *)arg)->exit;
+ sigset_t *sigint = ((struct wait_arg *)arg)->sigint;
+ siginfo_t info;
+ int ret;
+
+ do {
+ ret = sigwaitinfo(sigint, &info);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret != -1) {
+ printf("Received %s\n", strsignal(info.si_signo));
+ *exit = true;
+ }
+
+ return NULL;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned int period = DEFAULT_PERIOD;
+ const char *gpio_name = NULL;
+ double kprop = KPROP_DEFAULT;
+ const char *pwm_name = NULL;
+ double kint = KINT_DEFAULT;
+ unsigned int alignment = 0;
+ volatile bool exit = false;
+ struct wait_arg wait_arg;
+ pthread_t int_thread;
+ sigset_t sigint;
+ int num_lines = 0;
+ int lines[10];
+ int err;
+ int c;
+
+ while ((c = getopt(argc, argv, "g:p:r:a:t:n:o:?")) != -1) {
+ switch (c) {
+ case 'g':
+ gpio_name = optarg;
+ break;
+ case 'p':
+ pwm_name = optarg;
+ break;
+ case 'r':
+ period = strtoul(optarg, NULL, 10);
+ break;
+ case 'a':
+ alignment = strtoul(optarg, NULL, 10);
+ break;
+ case 't':
+ kprop = strtod(optarg, NULL);
+ break;
+ case 'n':
+ kint = strtod(optarg, NULL);
+ break;
+ case 'o':
+ if (num_lines >= GPIO_V2_LINES_MAX) {
+ print_usage();
+ return -1;
+ }
+ lines[num_lines] = strtoul(optarg, NULL, 10);
+ num_lines++;
+ break;
+
+ case '?':
+ print_usage();
+ return 0;
+ }
+ }
+
+ if (!pwm_name || !gpio_name || period > MAX_PERIOD) {
+ print_usage();
+ return -1;
+ }
+
+ err = start_pwm(pwm_name, PWM_LINE, period, alignment);
+ if (err) {
+ printf("Failed to start PWM: %s (%s)\n", pwm_name, strerror(-err));
+ return -1;
+ }
+
+ sigemptyset(&sigint);
+ sigaddset(&sigint, SIGINT);
+ err = pthread_sigmask(SIG_BLOCK, &sigint, NULL);
+ if (err) {
+ printf("Failed to block interrupt signals: %s\n", strerror(err));
+ goto cleanup_pwm;
+ }
+ wait_arg.sigint = &sigint;
+ wait_arg.exit = &exit;
+ err = pthread_create(&int_thread, NULL, wait_for_interrupt, &wait_arg);
+ if (err) {
+ printf("Failed to listen on user interrupt: %s\n", strerror(err));
+ goto cleanup_pwm;
+ }
+
+ err = adjust_pwm_loop(gpio_name, pwm_name, argv[0], period, alignment,
+ kprop, kint, &exit, lines, num_lines);
+ if (err) {
+ printf("Failed to monitor PWM: %s\n", strerror(-err));
+ pthread_kill(int_thread, SIGINT);
+ }
+
+ pthread_join(int_thread, NULL);
+
+cleanup_pwm:
+ err = stop_pwm(pwm_name, PWM_LINE);
+ if (err)
+ printf("Failed to stop PWM: %s (%s)\n", pwm_name, strerror(-err));
+
+ return err ? -1 : 0;
+}
--
2.17.1
next prev parent reply other threads:[~2021-08-24 16:49 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-08-24 16:47 [RFC PATCH v1 00/20] Review Request: Add support for Intel PMC lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 01/20] gpio: Add basic GPIO driver for Intel PMC Timed I/O device lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 02/20] gpio: Add GPIO polling interface to GPIO lib lakshmi.sowjanya.d
2021-09-22 10:03 ` Bartosz Golaszewski
2021-10-07 2:14 ` Kent Gibson
2021-08-24 16:47 ` [RFC PATCH v1 03/20] arch: x86: Add ART support function to tsc code lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 04/20] gpio: Add input code to Intel PMC Timed I/O Driver lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 05/20] tools: gpio: Add additional polling support to gpio-event-mon lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 06/20] gpio: Add set_input and polling interface to GPIO lib code lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 07/20] gpio: Add output event generation method to GPIOLIB and PMC Driver lakshmi.sowjanya.d
2021-09-16 21:42 ` Linus Walleij
2021-09-17 7:27 ` Uwe Kleine-König
2021-09-19 19:38 ` Linus Walleij
2021-09-19 21:21 ` Clemens Gruber
2021-09-20 7:14 ` Uwe Kleine-König
2021-08-24 16:47 ` [RFC PATCH v1 08/20] kernel: time: Add system time to system counter translation lakshmi.sowjanya.d
2021-09-16 21:48 ` Linus Walleij
2021-08-24 16:47 ` [RFC PATCH v1 09/20] arch: x86: Add TSC to ART translation lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 10/20] tools: gpio: Add GPIO output generation user application lakshmi.sowjanya.d
2021-09-16 21:52 ` Linus Walleij
2021-08-24 16:47 ` [RFC PATCH v1 11/20] gpio: Add event count interface to gpiolib lakshmi.sowjanya.d
2021-09-22 9:53 ` Bartosz Golaszewski
2021-08-24 16:47 ` [RFC PATCH v1 12/20] gpio: Add event count to Intel(R) PMC Timed I/O driver lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 13/20] tools: gpio: Add event count capability to event monitor application lakshmi.sowjanya.d
2021-09-16 21:57 ` Linus Walleij
2021-08-24 16:47 ` [RFC PATCH v1 14/20] arch/x86: Add ART nanosecond to ART conversion lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 15/20] pwm: Add capability for PWM Driver managed state lakshmi.sowjanya.d
2021-09-16 22:00 ` Linus Walleij
2021-08-24 16:47 ` [RFC PATCH v1 16/20] gpio: Add PWM capabilities to Intel(R) PMC TIO driver lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 17/20] pwm: Add second alignment to the core PWM interface lakshmi.sowjanya.d
2021-08-24 16:47 ` [RFC PATCH v1 18/20] gpio: Add PWM alignment support to the Intel(R) PMC Timed I/O driver lakshmi.sowjanya.d
2021-08-24 16:48 ` [RFC PATCH v1 19/20] gpio: Add GPIO monitor line to Intel(R) Timed I/O Driver lakshmi.sowjanya.d
2021-08-24 16:48 ` lakshmi.sowjanya.d [this message]
2021-09-16 21:21 ` [RFC PATCH v1 00/20] Review Request: Add support for Intel PMC Linus Walleij
2021-10-11 21:14 ` Dipen Patel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210824164801.28896-21-lakshmi.sowjanya.d@intel.com \
--to=lakshmi.sowjanya.d@intel.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=bala.senthil@intel.com \
--cc=bgolaszewski@baylibre.com \
--cc=linus.walleij@linaro.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mgross@linux.intel.com \
--cc=tamal.saha@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).