diff options
Diffstat (limited to 'drivers/gpio/gpiolib-cdev.c')
-rw-r--r-- | drivers/gpio/gpiolib-cdev.c | 252 |
1 files changed, 219 insertions, 33 deletions
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index c2900b1be69d..f5aa5f93342a 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -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; - 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; + 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); 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; } @@ -1280,9 +1460,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); } @@ -1408,7 +1591,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; } @@ -1961,6 +2145,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) { |