|
Adapted from |
|
https://www.spinics.net/lists/linux-serial/msg31949.html |
|
|
|
Add a "pps_4wire" file to serial ports in sysfs in case the kernel is |
|
configured with CONFIG_PPS_CLIENT_LDISC. Writing 1 to the file enables |
|
the use of CTS instead of DCD for PPS signal input. This is necessary |
|
in case a serial port is not completely wired. |
|
Though this affects PPS processing the patch is against the serial core |
|
as the source of the serial port PPS event dispatching has to be |
|
modified. Furthermore it should be possible to modify the source of |
|
serial port PPS event dispatching before changing the line discipline. |
|
|
|
Signed-off-by: Andreas Steinmetz <[email protected]> |
|
Signed-off-by: Steve Sakoman <[email protected]> |
|
Tested-by: Steve Sakoman <[email protected]> |
|
--- |
|
|
|
--- a/Documentation/ABI/testing/sysfs-tty 2021-10-05 00:57:15.386317636 +0000 |
|
+++ b/Documentation/ABI/testing/sysfs-tty 2021-10-05 00:58:27.316013095 +0000 |
|
@@ -154,3 +154,12 @@ |
|
device specification. For example, when user sets 7bytes on |
|
16550A, which has 1/4/8/14 bytes trigger, the RX trigger is |
|
automatically changed to 4 bytes. |
|
+ |
|
+What: /sys/class/tty/ttyS0/pps_4wire |
|
+Date: September 2018 |
|
+Contact: Steve Sakoman <[email protected]> |
|
+Description: |
|
+ Shows/sets "4 wire" mode for the PPS input to the serial driver. |
|
+ For fully implemented serial ports PPS is normally provided |
|
+ on the DCD line. For partial "4 wire" implementations CTS is |
|
+ used instead of DCD. |
|
--- a/drivers/tty/serial/serial_core.c 2021-10-05 00:56:18.066442716 +0000 |
|
+++ b/drivers/tty/serial/serial_core.c 2021-10-05 01:02:32.225098247 +0000 |
|
@@ -2711,6 +2711,56 @@ |
|
return snprintf(buf, PAGE_SIZE, "%d\n", tmp.iomem_reg_shift); |
|
} |
|
|
|
+static ssize_t uart_get_attr_pps_4wire(struct device *dev, |
|
+ struct device_attribute *attr, char *buf) |
|
+{ |
|
+ struct tty_port *port = dev_get_drvdata(dev); |
|
+ struct uart_state *state = container_of(port, struct uart_state, port); |
|
+ struct uart_port *uport; |
|
+ int mode = 0; |
|
+ |
|
+ mutex_lock(&port->mutex); |
|
+ uport = uart_port_check(state); |
|
+ if (!uport) |
|
+ goto out; |
|
+ |
|
+ mode = uport->pps_4wire; |
|
+ |
|
+out: |
|
+ mutex_unlock(&port->mutex); |
|
+ return snprintf(buf, PAGE_SIZE, mode ? "yes" : "no"); |
|
+} |
|
+ |
|
+static ssize_t uart_set_attr_pps_4wire(struct device *dev, |
|
+ struct device_attribute *attr, const char *buf, size_t count) |
|
+{ |
|
+ struct tty_port *port = dev_get_drvdata(dev); |
|
+ struct uart_state *state = container_of(port, struct uart_state, port); |
|
+ struct uart_port *uport; |
|
+ bool mode; |
|
+ int ret; |
|
+ |
|
+ if (!count) |
|
+ return -EINVAL; |
|
+ |
|
+ ret = kstrtobool(buf, &mode); |
|
+ if (ret < 0) |
|
+ return ret; |
|
+ |
|
+ mutex_lock(&port->mutex); |
|
+ uport = uart_port_check(state); |
|
+ if (!uport) |
|
+ goto out; |
|
+ |
|
+ spin_lock_irq(&uport->lock); |
|
+ uport->pps_4wire = mode; |
|
+ spin_unlock_irq(&uport->lock); |
|
+ |
|
+out: |
|
+ mutex_unlock(&port->mutex); |
|
+ return count; |
|
+} |
|
+ |
|
static DEVICE_ATTR(type, S_IRUSR | S_IRGRP, uart_get_attr_type, NULL); |
|
static DEVICE_ATTR(line, S_IRUSR | S_IRGRP, uart_get_attr_line, NULL); |
|
static DEVICE_ATTR(port, S_IRUSR | S_IRGRP, uart_get_attr_port, NULL); |
|
@@ -2724,6 +2774,7 @@ |
|
static DEVICE_ATTR(io_type, S_IRUSR | S_IRGRP, uart_get_attr_io_type, NULL); |
|
static DEVICE_ATTR(iomem_base, S_IRUSR | S_IRGRP, uart_get_attr_iomem_base, NULL); |
|
static DEVICE_ATTR(iomem_reg_shift, S_IRUSR | S_IRGRP, uart_get_attr_iomem_reg_shift, NULL); |
|
+static DEVICE_ATTR(pps_4wire, S_IRUSR | S_IWUSR | S_IRGRP, uart_get_attr_pps_4wire, uart_set_attr_pps_4wire); |
|
|
|
static struct attribute *tty_dev_attrs[] = { |
|
&dev_attr_type.attr, |
|
@@ -2739,6 +2790,7 @@ |
|
&dev_attr_io_type.attr, |
|
&dev_attr_iomem_base.attr, |
|
&dev_attr_iomem_reg_shift.attr, |
|
+ &dev_attr_pps_4wire.attr, |
|
NULL, |
|
}; |
|
|
|
@@ -2795,6 +2847,9 @@ |
|
goto out; |
|
} |
|
|
|
+ /* assert that pps handling is done via DCD as default */ |
|
+ uport->pps_4wire = 0; |
|
+ |
|
/* |
|
* If this port is a console, then the spinlock is already |
|
* initialised. |
|
@@ -2971,7 +3026,7 @@ |
|
|
|
lockdep_assert_held_once(&uport->lock); |
|
|
|
- if (tty) { |
|
+ if (tty && !uport->pps_4wire) { |
|
ld = tty_ldisc_ref(tty); |
|
if (ld) { |
|
if (ld->ops->dcd_change) |
|
@@ -3000,8 +3055,21 @@ |
|
*/ |
|
void uart_handle_cts_change(struct uart_port *uport, unsigned int status) |
|
{ |
|
+ struct tty_port *port = &uport->state->port; |
|
+ struct tty_struct *tty = port->tty; |
|
+ struct tty_ldisc *ld; |
|
+ |
|
lockdep_assert_held_once(&uport->lock); |
|
|
|
+ if (tty && uport->pps_4wire) { |
|
+ ld = tty_ldisc_ref(tty); |
|
+ if (ld) { |
|
+ if (ld->ops->dcd_change) |
|
+ ld->ops->dcd_change(tty, status); |
|
+ tty_ldisc_deref(ld); |
|
+ } |
|
+ } |
|
+ |
|
uport->icount.cts++; |
|
|
|
if (uart_softcts_mode(uport)) { |
|
--- a/include/linux/serial_core.h 2021-10-05 01:10:35.360386349 +0000 |
|
+++ b/include/linux/serial_core.h 2021-10-05 01:10:45.073386641 +0000 |
|
@@ -248,7 +248,8 @@ |
|
struct device *dev; /* parent device */ |
|
unsigned char hub6; /* this should be in the 8250 driver */ |
|
unsigned char suspended; |
|
- unsigned char unused[2]; |
|
+ unsigned char pps_4wire; /* CTS instead of DCD */ |
|
+ unsigned char unused; |
|
const char *name; /* port name */ |
|
struct attribute_group *attr_group; /* port specific attributes */ |
|
const struct attribute_group **tty_groups; /* all attributes (serial core use only) */ |