[PATCH v3 1/3] tty: serial core: provide a method to search uart by phandle
From: H. Nikolaus Schaller
Date: Fri Oct 16 2015 - 14:09:07 EST
1. add uart_ports to a search list as soon as they are registered
2. provide a function to search an uart_port by phandle. This copies the
mechanism how devm_usb_get_phy_by_phandle() works
3. add a bindings document how serial slaves should use this feature
4. add Documentation how serla slaves work in general
Signed-off-by: H. Nikolaus Schaller <hns@xxxxxxxxxxxxx>
---
.../devicetree/bindings/serial/slaves.txt | 16 +++
Documentation/serial/slaves.txt | 36 +++++++
drivers/tty/serial/serial_core.c | 107 +++++++++++++++++++++
include/linux/serial_core.h | 10 ++
4 files changed, 169 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/slaves.txt
create mode 100644 Documentation/serial/slaves.txt
diff --git a/Documentation/devicetree/bindings/serial/slaves.txt b/Documentation/devicetree/bindings/serial/slaves.txt
new file mode 100644
index 0000000..353b87f
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/slaves.txt
@@ -0,0 +1,16 @@
+Device-Tree bindings for UART slave devices
+
+A node describing a slave device defines a phandle to reference the UART
+the device is connected to. In the (unexpected) case of two or more UARTs
+a list of phandles can be specified.
+
+properties:
+ - uart: (list of) phandle(s) of UART(s) the device is connected to
+
+
+example:
+
+ gps {
+ compatible = "wi2wi,w2sg0004";
+ uart = <&uart1>;
+ };
diff --git a/Documentation/serial/slaves.txt b/Documentation/serial/slaves.txt
new file mode 100644
index 0000000..6f8d44d
--- /dev/null
+++ b/Documentation/serial/slaves.txt
@@ -0,0 +1,36 @@
+UART slave device support
+
+A remote device connected to a RS232 interface is usually power controlled by the DTR line.
+The DTR line is managed automatically by the UART driver for open() and close() syscalls
+and on demand by tcsetattr().
+
+With embedded devices, the serial peripheral might be directly and always connected to the UART
+and there might be no physical DTR line involved. Power control (on/off) has to be done by some
+chip specific device driver (which we call "UART slave") through some mechanisms (I2C, GPIOs etc.)
+not related to the serial interface. Some devices do not explicitly tell their power state except
+by sending or not sending data to the UART. In such a case the device driver must be able to monitor
+data activity. The role of the device driver is to encapsulate such power control in a single place.
+
+This patch series allows to support such drivers by providing:
+* a mechanism that a slave driver can identify the UART instance it is connected to
+* a mechanism that UART slave drivers can register to be notified
+* notfications for DTR (and other modem control) state changes
+* notifications that the UART has received some data from the UART
+
+A slave device simply adds a phandle reference to the UART it is connected to, e.g.
+
+ gps {
+ compatible = "wi2wi,w2sg0004";
+ uart = <&uart1>;
+ };
+
+The slave driver calls devm_serial_get_uart_by_phandle() to identify the uart driver.
+This API follows the concept of devm_usb_get_phy_by_phandle().
+
+A slave device driver registers itself with serial_register_slave() to receive notifications.
+Notification handler callbacks can be registered by serial_register_mctrl_notification() and
+serial_register_rx_notification(). If an UART has registered a NULL slave or a NULL handler,
+no notifications are sent.
+
+RX notification handlers can define a ktermios during setup and the handler function can modify
+or decide to throw away each character that is passed upwards.
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 603d2cc..9caa33e 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -38,6 +38,36 @@
#include <asm/irq.h>
#include <asm/uaccess.h>
+static LIST_HEAD(uart_list);
+static DEFINE_SPINLOCK(uart_lock);
+
+/* same concept as __of_usb_find_phy */
+static struct uart_port *__of_serial_find_uart(struct device_node *node)
+{
+ struct uart_port *uart;
+
+ if (!of_device_is_available(node))
+ return ERR_PTR(-ENODEV);
+
+ list_for_each_entry(uart, &uart_list, head) {
+ if (node != uart->dev->of_node)
+ continue;
+
+ return uart;
+ }
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+
+static void devm_serial_uart_release(struct device *dev, void *res)
+{
+ struct uart_port *uart = *(struct uart_port **)res;
+
+ /* FIXME: I don't understand the serial subsystem well enough
+ * to know if we should call serial_put_uart(uart); here
+ */
+}
+
/*
* This is used to lock changes in serial line configuration.
*/
@@ -64,6 +94,79 @@ static int uart_dcd_enabled(struct uart_port *uport)
return !!(uport->status & UPSTAT_DCD_ENABLE);
}
+/**
+ * devm_serial_get_uart_by_phandle - find the uart by phandle
+ * @dev - device that requests this uart
+ * @phandle - name of the property holding the uart phandle value
+ * @index - the index of the uart
+ *
+ * Returns the uart_port associated with the given phandle value,
+ * after getting a refcount to it, -ENODEV if there is no such uart or
+ * -EPROBE_DEFER if there is a phandle to the uart, but the device is
+ * not yet loaded. While at that, it also associates the device with
+ * the uart using devres. On driver detach, release function is invoked
+ * on the devres data, then, devres data is freed.
+ *
+ * For use by tty host and peripheral drivers.
+ */
+
+/* same concept as devm_usb_get_phy_by_phandle() */
+
+struct uart_port *devm_serial_get_uart_by_phandle(struct device *dev,
+ const char *phandle, u8 index)
+{
+ struct uart_port *uart = ERR_PTR(-ENOMEM), **ptr;
+ unsigned long flags;
+ struct device_node *node;
+
+ if (!dev->of_node) {
+ dev_err(dev, "device does not have a device node entry\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ node = of_parse_phandle(dev->of_node, phandle, index);
+ if (!node) {
+ dev_err(dev, "failed to get %s phandle in %s node\n", phandle,
+ dev->of_node->full_name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ ptr = devres_alloc(devm_serial_uart_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr) {
+ dev_err(dev, "failed to allocate memory for devres\n");
+ goto err0;
+ }
+
+ spin_lock_irqsave(&uart_lock, flags);
+
+ uart = __of_serial_find_uart(node);
+ if (IS_ERR(uart)) {
+ devres_free(ptr);
+ goto err1;
+ }
+
+ if (!try_module_get(uart->dev->driver->owner)) {
+ uart = ERR_PTR(-ENODEV);
+ devres_free(ptr);
+ goto err1;
+ }
+
+ *ptr = uart;
+ devres_add(dev, ptr);
+
+ get_device(uart->dev);
+
+err1:
+ spin_unlock_irqrestore(&uart_lock, flags);
+
+err0:
+ of_node_put(node);
+
+ return uart;
+}
+EXPORT_SYMBOL_GPL(devm_serial_get_uart_by_phandle);
+
+
/*
* This routine is used by the interrupt handler to schedule processing in
* the software interrupt portion of the driver.
@@ -2733,6 +2836,8 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
*/
uport->flags &= ~UPF_DEAD;
+ list_add_tail(&uport->head, &uart_list);
+
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
@@ -2764,6 +2869,8 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
mutex_lock(&port_mutex);
+ list_del(&uport->head);
+
/*
* Mark the port "dead" - this prevents any opens from
* succeeding while we shut down the port.
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 297d4fa..d7a2e15 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -247,6 +247,7 @@ struct uart_port {
const struct attribute_group **tty_groups; /* all attributes (serial core use only) */
struct serial_rs485 rs485;
void *private_data; /* generic platform data pointer */
+ struct list_head head; /* uarts list (lookup by phandle) */
};
static inline int serial_port_in(struct uart_port *up, int offset)
@@ -475,4 +476,13 @@ static inline int uart_handle_break(struct uart_port *port)
(cflag) & CRTSCTS || \
!((cflag) & CLOCAL))
+/*
+ * Helper functions for UART slave drivers
+ */
+
+/* find UART by phandle (e.g. with 'uart = <&uart2>;' then call as
+ * devm_serial_get_uart_by_phandle(dev, "uart", 0);
+ */
+extern struct uart_port *devm_serial_get_uart_by_phandle(struct device *dev,
+ const char *phandle, u8 index);
#endif /* LINUX_SERIAL_CORE_H */
--
2.5.1
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/