gpiolib: cdev: Add hardware timestamp clock type

This patch adds new clock type for the GPIO controller which can
timestamp gpio lines in using hardware means. To expose such
functionalities to the userspace, code has been added where
during line create or set config API calls, it checks for new
clock type and if requested, calls HTE API. During line change
event, the HTE subsystem pushes timestamp data to userspace
through gpiolib-cdev.

Signed-off-by: Dipen Patel <dipenp@nvidia.com>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Dipen Patel 2022-04-22 13:52:18 -07:00 коммит произвёл Thierry Reding
Родитель 10e4afd6cc
Коммит 2068339a6c
2 изменённых файлов: 222 добавлений и 33 удалений

Просмотреть файл

@ -24,6 +24,7 @@
#include <linux/timekeeping.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/hte.h>
#include <uapi/linux/gpio.h>
#include "gpiolib.h"
@ -464,6 +465,25 @@ struct line {
* stale value.
*/
unsigned int level;
/*
* -- hte specific fields --
*/
struct hte_ts_desc hdesc;
/*
* HTE provider sets line level at the time of event. The valid
* value is 0 or 1 and negative value for an error.
*/
int raw_level;
/*
* when sw_debounce is set on HTE enabled line, this is running
* counter of the discarded events.
*/
u32 total_discard_seq;
/*
* when sw_debounce is set on HTE enabled line, this variable records
* last sequence number before debounce period expires.
*/
u32 last_seqno;
};
/**
@ -518,6 +538,7 @@ struct linereq {
GPIO_V2_LINE_DRIVE_FLAGS | \
GPIO_V2_LINE_EDGE_FLAGS | \
GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME | \
GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE | \
GPIO_V2_LINE_BIAS_FLAGS)
static void linereq_put_event(struct linereq *lr,
@ -542,10 +563,98 @@ static u64 line_event_timestamp(struct line *line)
{
if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &line->desc->flags))
return ktime_get_real_ns();
else if (test_bit(FLAG_EVENT_CLOCK_HTE, &line->desc->flags))
return line->timestamp_ns;
return ktime_get_ns();
}
static enum hte_return process_hw_ts_thread(void *p)
{
struct line *line;
struct linereq *lr;
struct gpio_v2_line_event le;
int level;
u64 eflags;
if (!p)
return HTE_CB_HANDLED;
line = p;
lr = line->req;
memset(&le, 0, sizeof(le));
le.timestamp_ns = line->timestamp_ns;
eflags = READ_ONCE(line->eflags);
if (eflags == GPIO_V2_LINE_FLAG_EDGE_BOTH) {
if (line->raw_level >= 0) {
if (test_bit(FLAG_ACTIVE_LOW, &line->desc->flags))
level = !line->raw_level;
else
level = line->raw_level;
} else {
level = gpiod_get_value_cansleep(line->desc);
}
if (level)
le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
else
le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
} else if (eflags == GPIO_V2_LINE_FLAG_EDGE_RISING) {
/* Emit low-to-high event */
le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
} else if (eflags == GPIO_V2_LINE_FLAG_EDGE_FALLING) {
/* Emit high-to-low event */
le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
} else {
return HTE_CB_HANDLED;
}
le.line_seqno = line->line_seqno;
le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno;
le.offset = gpio_chip_hwgpio(line->desc);
linereq_put_event(lr, &le);
return HTE_CB_HANDLED;
}
static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p)
{
struct line *line;
struct linereq *lr;
int diff_seqno = 0;
if (!ts || !p)
return HTE_CB_HANDLED;
line = p;
line->timestamp_ns = ts->tsc;
line->raw_level = ts->raw_level;
lr = line->req;
if (READ_ONCE(line->sw_debounced)) {
line->total_discard_seq++;
line->last_seqno = ts->seq;
mod_delayed_work(system_wq, &line->work,
usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
} else {
if (unlikely(ts->seq < line->line_seqno))
return HTE_CB_HANDLED;
diff_seqno = ts->seq - line->line_seqno;
line->line_seqno = ts->seq;
if (lr->num_lines != 1)
line->req_seqno = atomic_add_return(diff_seqno,
&lr->seqno);
return HTE_RUN_SECOND_CB;
}
return HTE_CB_HANDLED;
}
static irqreturn_t edge_irq_thread(int irq, void *p)
{
struct line *line = p;
@ -651,10 +760,16 @@ static void debounce_work_func(struct work_struct *work)
struct gpio_v2_line_event le;
struct line *line = container_of(work, struct line, work.work);
struct linereq *lr;
int level;
int level, diff_seqno;
u64 eflags;
level = gpiod_get_raw_value_cansleep(line->desc);
if (test_bit(FLAG_EVENT_CLOCK_HTE, &line->desc->flags)) {
level = line->raw_level;
if (level < 0)
level = gpiod_get_raw_value_cansleep(line->desc);
} else {
level = gpiod_get_raw_value_cansleep(line->desc);
}
if (level < 0) {
pr_debug_ratelimited("debouncer failed to read line value\n");
return;
@ -685,10 +800,21 @@ static void debounce_work_func(struct work_struct *work)
lr = line->req;
le.timestamp_ns = line_event_timestamp(line);
le.offset = gpio_chip_hwgpio(line->desc);
line->line_seqno++;
le.line_seqno = line->line_seqno;
le.seqno = (lr->num_lines == 1) ?
le.line_seqno : atomic_inc_return(&lr->seqno);
if (test_bit(FLAG_EVENT_CLOCK_HTE, &line->desc->flags)) {
/* discard events except the last one */
line->total_discard_seq -= 1;
diff_seqno = line->last_seqno - line->total_discard_seq -
line->line_seqno;
line->line_seqno = line->last_seqno - line->total_discard_seq;
le.line_seqno = line->line_seqno;
le.seqno = (lr->num_lines == 1) ?
le.line_seqno : atomic_add_return(diff_seqno, &lr->seqno);
} else {
line->line_seqno++;
le.line_seqno = line->line_seqno;
le.seqno = (lr->num_lines == 1) ?
le.line_seqno : atomic_inc_return(&lr->seqno);
}
if (level)
/* Emit low-to-high event */
@ -700,8 +826,34 @@ static void debounce_work_func(struct work_struct *work)
linereq_put_event(lr, &le);
}
static int hte_edge_setup(struct line *line, u64 eflags)
{
int ret;
unsigned long flags = 0;
struct hte_ts_desc *hdesc = &line->hdesc;
if (eflags & GPIO_V2_LINE_FLAG_EDGE_RISING)
flags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
HTE_FALLING_EDGE_TS : HTE_RISING_EDGE_TS;
if (eflags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
flags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
HTE_RISING_EDGE_TS : HTE_FALLING_EDGE_TS;
line->total_discard_seq = 0;
hte_init_line_attr(hdesc, desc_to_gpio(line->desc), flags,
NULL, line->desc);
ret = hte_ts_get(NULL, hdesc, 0);
if (ret)
return ret;
return hte_request_ts_ns(hdesc, process_hw_ts,
process_hw_ts_thread, line);
}
static int debounce_setup(struct line *line,
unsigned int debounce_period_us)
unsigned int debounce_period_us, bool hte_req)
{
unsigned long irqflags;
int ret, level, irq;
@ -721,19 +873,27 @@ static int debounce_setup(struct line *line,
if (level < 0)
return level;
irq = gpiod_to_irq(line->desc);
if (irq < 0)
return -ENXIO;
if (!hte_req) {
irq = gpiod_to_irq(line->desc);
if (irq < 0)
return -ENXIO;
irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
ret = request_irq(irq, debounce_irq_handler, irqflags,
line->req->label, line);
if (ret)
return ret;
line->irq = irq;
} else {
ret = hte_edge_setup(line,
GPIO_V2_LINE_FLAG_EDGE_RISING |
GPIO_V2_LINE_FLAG_EDGE_FALLING);
if (ret)
return ret;
}
WRITE_ONCE(line->level, level);
irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
ret = request_irq(irq, debounce_irq_handler, irqflags,
line->req->label, line);
if (ret)
return ret;
WRITE_ONCE(line->sw_debounced, 1);
line->irq = irq;
}
return 0;
}
@ -766,13 +926,16 @@ static u32 gpio_v2_line_config_debounce_period(struct gpio_v2_line_config *lc,
return 0;
}
static void edge_detector_stop(struct line *line)
static void edge_detector_stop(struct line *line, bool hte_en)
{
if (line->irq) {
if (line->irq && !hte_en) {
free_irq(line->irq, line);
line->irq = 0;
}
if (hte_en)
hte_ts_put(&line->hdesc);
cancel_delayed_work_sync(&line->work);
WRITE_ONCE(line->sw_debounced, 0);
WRITE_ONCE(line->eflags, 0);
@ -784,7 +947,7 @@ static void edge_detector_stop(struct line *line)
static int edge_detector_setup(struct line *line,
struct gpio_v2_line_config *lc,
unsigned int line_idx,
u64 eflags)
u64 eflags, bool hte_req)
{
u32 debounce_period_us;
unsigned long irqflags = 0;
@ -799,7 +962,7 @@ static int edge_detector_setup(struct line *line,
WRITE_ONCE(line->eflags, eflags);
if (gpio_v2_line_config_debounced(lc, line_idx)) {
debounce_period_us = gpio_v2_line_config_debounce_period(lc, line_idx);
ret = debounce_setup(line, debounce_period_us);
ret = debounce_setup(line, debounce_period_us, hte_req);
if (ret)
return ret;
WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
@ -809,6 +972,9 @@ static int edge_detector_setup(struct line *line,
if (!eflags || READ_ONCE(line->sw_debounced))
return 0;
if (hte_req)
return hte_edge_setup(line, eflags);
irq = gpiod_to_irq(line->desc);
if (irq < 0)
return -ENXIO;
@ -834,13 +1000,18 @@ static int edge_detector_setup(struct line *line,
static int edge_detector_update(struct line *line,
struct gpio_v2_line_config *lc,
unsigned int line_idx,
u64 eflags, bool polarity_change)
u64 flags, bool polarity_change,
bool prev_hte_flag)
{
u64 eflags = flags & GPIO_V2_LINE_EDGE_FLAGS;
unsigned int debounce_period_us =
gpio_v2_line_config_debounce_period(lc, line_idx);
gpio_v2_line_config_debounce_period(lc, line_idx);
bool hte_change = (prev_hte_flag !=
((flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE) != 0));
if ((READ_ONCE(line->eflags) == eflags) && !polarity_change &&
(READ_ONCE(line->desc->debounce_period_us) == debounce_period_us))
(READ_ONCE(line->desc->debounce_period_us) == debounce_period_us)
&& !hte_change)
return 0;
/* sw debounced and still will be...*/
@ -851,11 +1022,12 @@ static int edge_detector_update(struct line *line,
}
/* reconfiguring edge detection or sw debounce being disabled */
if ((line->irq && !READ_ONCE(line->sw_debounced)) ||
if ((line->irq && !READ_ONCE(line->sw_debounced)) || prev_hte_flag ||
(!debounce_period_us && READ_ONCE(line->sw_debounced)))
edge_detector_stop(line);
edge_detector_stop(line, prev_hte_flag);
return edge_detector_setup(line, lc, line_idx, eflags);
return edge_detector_setup(line, lc, line_idx, eflags,
flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
}
static u64 gpio_v2_line_config_flags(struct gpio_v2_line_config *lc,
@ -891,7 +1063,6 @@ static int gpio_v2_line_flags_validate(u64 flags)
/* Return an error if an unknown flag is set */
if (flags & ~GPIO_V2_LINE_VALID_FLAGS)
return -EINVAL;
/*
* Do not allow both INPUT and OUTPUT flags to be set as they are
* contradictory.
@ -900,6 +1071,11 @@ static int gpio_v2_line_flags_validate(u64 flags)
(flags & GPIO_V2_LINE_FLAG_OUTPUT))
return -EINVAL;
/* Only allow one event clock source */
if ((flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME) &&
(flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
return -EINVAL;
/* Edge detection requires explicit input. */
if ((flags & GPIO_V2_LINE_EDGE_FLAGS) &&
!(flags & GPIO_V2_LINE_FLAG_INPUT))
@ -992,6 +1168,8 @@ static void gpio_v2_line_config_flags_to_desc_flags(u64 flags,
assign_bit(FLAG_EVENT_CLOCK_REALTIME, flagsp,
flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
assign_bit(FLAG_EVENT_CLOCK_HTE, flagsp,
flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
}
static long linereq_get_values(struct linereq *lr, void __user *ip)
@ -1121,6 +1299,7 @@ static long linereq_set_config_unlocked(struct linereq *lr,
unsigned int i;
u64 flags;
bool polarity_change;
bool prev_hte_flag;
int ret;
for (i = 0; i < lr->num_lines; i++) {
@ -1130,6 +1309,8 @@ static long linereq_set_config_unlocked(struct linereq *lr,
(!!test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=
((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));
prev_hte_flag = !!test_bit(FLAG_EVENT_CLOCK_HTE, &desc->flags);
gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);
/*
* Lines have to be requested explicitly for input
@ -1138,7 +1319,7 @@ static long linereq_set_config_unlocked(struct linereq *lr,
if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
int val = gpio_v2_line_config_output_value(lc, i);
edge_detector_stop(&lr->lines[i]);
edge_detector_stop(&lr->lines[i], prev_hte_flag);
ret = gpiod_direction_output(desc, val);
if (ret)
return ret;
@ -1148,8 +1329,7 @@ static long linereq_set_config_unlocked(struct linereq *lr,
return ret;
ret = edge_detector_update(&lr->lines[i], lc, i,
flags & GPIO_V2_LINE_EDGE_FLAGS,
polarity_change);
flags, polarity_change, prev_hte_flag);
if (ret)
return ret;
}
@ -1278,9 +1458,12 @@ static ssize_t linereq_read(struct file *file,
static void linereq_free(struct linereq *lr)
{
unsigned int i;
bool hte;
for (i = 0; i < lr->num_lines; i++) {
edge_detector_stop(&lr->lines[i]);
hte = !!test_bit(FLAG_EVENT_CLOCK_HTE,
&lr->lines[i].desc->flags);
edge_detector_stop(&lr->lines[i], hte);
if (lr->lines[i].desc)
gpiod_free(lr->lines[i].desc);
}
@ -1406,7 +1589,8 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
goto out_free_linereq;
ret = edge_detector_setup(&lr->lines[i], lc, i,
flags & GPIO_V2_LINE_EDGE_FLAGS);
flags & GPIO_V2_LINE_EDGE_FLAGS,
flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
if (ret)
goto out_free_linereq;
}
@ -1959,6 +2143,8 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &desc->flags))
info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
else if (test_bit(FLAG_EVENT_CLOCK_HTE, &desc->flags))
info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
debounce_period_us = READ_ONCE(desc->debounce_period_us);
if (debounce_period_us) {

Просмотреть файл

@ -66,6 +66,8 @@ struct gpiochip_info {
* @GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN: line has pull-down bias enabled
* @GPIO_V2_LINE_FLAG_BIAS_DISABLED: line has bias disabled
* @GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME: line events contain REALTIME timestamps
* @GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE: line events contain timestamps from
* hardware timestamp engine
*/
enum gpio_v2_line_flag {
GPIO_V2_LINE_FLAG_USED = _BITULL(0),
@ -80,6 +82,7 @@ enum gpio_v2_line_flag {
GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN = _BITULL(9),
GPIO_V2_LINE_FLAG_BIAS_DISABLED = _BITULL(10),
GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME = _BITULL(11),
GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE = _BITULL(12),
};
/**