diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 7ba0929b2741..81c2fc4f0e49 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -394,6 +394,9 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
  * for the corresponding line request. Ths is drawn from the @line.
  * @line_seqno: the seqno for the current edge event in the sequence of
  * events for this line.
+ * @sw_debounced: flag indicating if the software debouncer is active
+ * @work: the worker that implements software debouncing
+ * @level: the current debounced physical level of the line
  */
 struct edge_detector {
 	struct line *line;
@@ -404,7 +407,15 @@ struct edge_detector {
 	 */
 	u64 timestamp;
 	u32 seqno;
+	/*
+	 * line_seqno is used by either edge_irq_thread or debounce_work_func
+	 * which are themselves mutually exclusive.
+	 */
 	u32 line_seqno;
+	/* debouncer specific fields */
+	atomic_t sw_debounced;
+	struct delayed_work work;
+	atomic_t level;
 };
 
 /**
@@ -524,6 +535,10 @@ static int edge_detector_start(struct edge_detector *edet)
 	int ret, irq, irqflags = 0;
 	struct gpio_desc *desc;
 
+	if (atomic_read(&edet->sw_debounced))
+		/* debouncer is setup and will provide edge detection */
+		return 0;
+
 	desc = edge_detector_desc(edet);
 	irq = gpiod_to_irq(desc);
 
@@ -555,23 +570,192 @@ static int edge_detector_start(struct edge_detector *edet)
 	return 0;
 }
 
+/*
+ * returns the current debounced value, or -1 if the debouncer is inactive.
+ */
+static int debounced_value(struct edge_detector *edet)
+{
+	int value;
+
+	if (!atomic_read(&edet->sw_debounced))
+		return -1;
+
+	/*
+	 * minor race - debouncer may be stopped here, so edge_detector_stop
+	 * must leave the value unchanged so the following will read the level
+	 * from when the debouncer was last running.
+	 */
+	value = atomic_read(&edet->level);
+
+	if (test_bit(FLAG_ACTIVE_LOW, &edge_detector_desc(edet)->flags))
+		value = !value;
+
+	return value;
+}
+
+static irqreturn_t debounce_irq_handler(int irq, void *p)
+{
+	struct edge_detector *edet = p;
+	struct gpio_desc *desc = edge_detector_desc(edet);
+
+	mod_delayed_work(system_wq,
+			 &edet->work,
+			 usecs_to_jiffies(atomic_read(&desc->debounce_period)));
+
+	return IRQ_HANDLED;
+}
+
+static void debounce_work_func(struct work_struct *work)
+{
+	struct gpioline_event le;
+	int ret, level, oldlevel;
+	struct edge_detector *edet =
+		container_of(work, struct edge_detector, work.work);
+	struct gpio_desc *desc = edge_detector_desc(edet);
+	struct line *line;
+
+	level = gpiod_get_raw_value_cansleep(desc);
+	if (level < 0) {
+		pr_debug_ratelimited("debouncer failed to read line value\n");
+		return;
+	}
+
+	oldlevel = atomic_xchg(&edet->level, level);
+	if (oldlevel == level)
+		return;
+
+	/* -- edge detection -- */
+	line = edet->line;
+	if (line->edge_detection == GPIOLINE_EDGE_NONE)
+		return;
+
+	/* switch from physical level to logical - if they differ */
+	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+		level = !level;
+
+	/* ignore edges that are not being monitored */
+	if (((line->edge_detection == GPIOLINE_EDGE_RISING) && (level == 0)) ||
+	    ((line->edge_detection == GPIOLINE_EDGE_FALLING) && (level == 1)))
+		return;
+
+	/* Do not leak kernel stack to userspace */
+	memset(&le, 0, sizeof(le));
+
+	le.timestamp = ktime_get_ns();
+	le.offset = gpio_chip_hwgpio(desc);
+	edet->line_seqno++;
+	le.line_seqno = edet->line_seqno;
+	le.seqno = (line->num_descs == 1) ?
+		le.line_seqno : atomic_inc_return(&line->seqno);
+
+	if (level)
+		/* Emit low-to-high event */
+		le.id = GPIOLINE_EVENT_RISING_EDGE;
+	else
+		/* Emit high-to-low event */
+		le.id = GPIOLINE_EVENT_FALLING_EDGE;
+
+	ret = kfifo_in_spinlocked_noirqsave(&line->events, &le,
+					    1, &line->wait.lock);
+	if (ret)
+		wake_up_poll(&line->wait, EPOLLIN);
+	else
+		pr_debug_ratelimited("event FIFO is full - event dropped\n");
+}
+
+static int debounce_setup(struct edge_detector *edet,
+			  unsigned int debounce_period)
+{
+	int ret, level, irq, irqflags;
+	struct gpio_desc *desc = edge_detector_desc(edet);
+
+	/* try hardware */
+	ret = gpiod_set_debounce(desc, debounce_period);
+	if (!ret) {
+		atomic_set(&desc->debounce_period, debounce_period);
+		return ret;
+	}
+	if (ret != -ENOTSUPP)
+		return ret;
+
+	if (debounce_period) {
+		/* setup software debounce */
+		level = gpiod_get_raw_value_cansleep(desc);
+		if (level < 0)
+			return level;
+
+		irq = gpiod_to_irq(desc);
+		if (irq <= 0)
+			return -ENODEV;
+
+		atomic_set(&edet->level, level);
+		edet->line_seqno = 0;
+		irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
+		ret = request_irq(irq,
+				  debounce_irq_handler,
+				  irqflags,
+				  edet->line->label,
+				  edet);
+		if (ret)
+			return ret;
+
+		atomic_set(&edet->sw_debounced, 1);
+		edet->irq = irq;
+	}
+	atomic_set(&desc->debounce_period, debounce_period);
+	return 0;
+}
+
 static void edge_detector_stop(struct edge_detector *edet)
 {
+	struct gpio_desc *desc = edge_detector_desc(edet);
+
 	if (edet->irq) {
 		free_irq(edet->irq, edet);
 		edet->irq = 0;
 	}
+
+	cancel_delayed_work_sync(&edet->work);
+	atomic_set(&edet->sw_debounced, 0);
+	/* do not change edet->level - see comment in debounced_value */
+
+	if (desc)
+		atomic_set(&desc->debounce_period, 0);
+}
+
+static int debounce_update(struct edge_detector *edet,
+			   unsigned int debounce_period)
+{
+	struct gpio_desc *desc = edge_detector_desc(edet);
+
+	if (atomic_read(&desc->debounce_period) == debounce_period)
+		return 0;
+
+	if (!atomic_read(&edet->sw_debounced))
+		return debounce_setup(edet, debounce_period);
+
+	if (!debounce_period)
+		edge_detector_stop(edet);
+	else
+		atomic_set(&desc->debounce_period, debounce_period);
+	return 0;
 }
 
 static int edge_detector_setup(struct edge_detector *edet,
 			       struct gpioline_config *lc)
 {
 	struct gpio_desc *desc = edge_detector_desc(edet);
+	int ret;
 
 	if (lc->edge_detection & GPIOLINE_EDGE_RISING)
 		set_bit(FLAG_EDGE_RISING, &desc->flags);
 	if (lc->edge_detection & GPIOLINE_EDGE_FALLING)
 		set_bit(FLAG_EDGE_FALLING, &desc->flags);
+	if (lc->flags & GPIOLINE_FLAG_V2_DEBOUNCE) {
+		ret = debounce_setup(edet, lc->debounce_period);
+		if (ret)
+			return ret;
+	}
 	if (lc->edge_detection)
 		return edge_detector_start(edet);
 	return 0;
@@ -703,6 +887,10 @@ static long line_set_config_locked(struct line *line,
 		if (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=
 		    ((lc->flags & GPIOLINE_FLAG_V2_ACTIVE_LOW) != 0))
 			return -EINVAL;
+
+		/* disallow debounce changes */
+		if (atomic_read(&desc->debounce_period) != lc->debounce_period)
+			return -EINVAL;
 	}
 
 	vals = (unsigned long *)lc->values.bitmap;
@@ -718,6 +906,7 @@ static long line_set_config_locked(struct line *line,
 			if (lc->direction == GPIOLINE_DIRECTION_OUTPUT) {
 				int val = test_bit(i, vals);
 
+				edge_detector_stop(&line->edets[i]);
 				ret = gpiod_direction_output(desc, val);
 				if (ret)
 					return ret;
@@ -725,6 +914,12 @@ static long line_set_config_locked(struct line *line,
 				ret = gpiod_direction_input(desc);
 				if (ret)
 					return ret;
+				if (lc->flags & GPIOLINE_FLAG_V2_DEBOUNCE) {
+					ret = debounce_update(&line->edets[i],
+						lc->debounce_period);
+					if (ret)
+						return ret;
+				}
 			}
 		}
 
@@ -762,7 +957,7 @@ static long line_ioctl(struct file *file, unsigned int cmd,
 	void __user *ip = (void __user *)arg;
 	struct gpioline_values glv;
 	unsigned long *vals = (unsigned long *)glv.bitmap;
-	int ret;
+	int ret, i, value;
 
 	if (cmd == GPIOLINE_GET_VALUES_IOCTL) {
 		/* NOTE: It's ok to read values of output lines. */
@@ -776,6 +971,12 @@ static long line_ioctl(struct file *file, unsigned int cmd,
 		if (ret)
 			return ret;
 
+		for (i = 0; i < line->num_descs; i++) {
+			value = debounced_value(&line->edets[i]);
+			if (value >= 0)
+				assign_bit(i, vals, value);
+		}
+
 		if (copy_to_user(ip, &glv, sizeof(glv)))
 			return -EFAULT;
 
@@ -954,8 +1155,11 @@ static int line_create(struct gpio_device *gdev, void __user *ip)
 	if (!line->edets)
 		return -ENOMEM;
 
-	for (i = 0; i < linereq.num_lines; i++)
+	for (i = 0; i < linereq.num_lines; i++) {
 		line->edets[i].line = line;
+		atomic_set(&line->edets[i].sw_debounced, 0);
+		INIT_DELAYED_WORK(&line->edets[i].work, debounce_work_func);
+	}
 
 	line->gdev = gdev;
 	get_device(&gdev->dev);
@@ -1589,6 +1793,10 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
 		lc->edge_detection |= GPIOLINE_EDGE_FALLING;
 	}
 
+	lc->debounce_period = atomic_read(&desc->debounce_period);
+	if (lc->debounce_period)
+		lc->flags |= GPIOLINE_FLAG_V2_DEBOUNCE;
+
 	spin_unlock_irqrestore(&gpio_lock, flags);
 }
 
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index 1dc6d2b191af..02a46af0c69f 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -124,6 +124,10 @@ struct gpio_desc {
 #ifdef CONFIG_OF_DYNAMIC
 	struct device_node	*hog;
 #endif
+#ifdef CONFIG_GPIO_CDEV
+	/* debounce period in microseconds */
+	atomic_t		debounce_period;
+#endif
 };
 
 int gpiod_request(struct gpio_desc *desc, const char *label);
