Hi all,
here is version 4 of my "UART slave device" patch set, previously
known as "tty slave devices".
The most obvious change is the name. I realized that this isn't
really about "tty"s at all - it is about UARTs.
When a device is connected to a UART via RS-232 (or similar), there
is a DTR line that can be used for power management, and other "modem
control" lines.
On an embedded board, it is very likely that there is no "DTR", and
any power management need to be done using some completely separate
mechanism.
So these "slaves" are really just for devices permanently attached to
UARTs without a full "RS-232" (or similar) connection. The driver
does all the extra control beyond Tx/Rx.
The core serial code now "initializes" the "tty device" but may not
"add" it.
If it successfully finds a slave device, it gives the tty device to
the slave. If not, it calls "device_add()" itself.
The slave device is responsible for adding the tty device when it is
ready.
There are two things that I'm not entirely happy with.
Firstly there is the "uart_slave_activate()" call in tty_init_dev().
Possibly this belongs more in tty_driver_install_tty(), but it is a
bit out-off-place in either.
To make it really clean I think I would need to create my own
"tty_driver" which mirrors the one supplied by the uart but has a
few little modification. But that seems like a lot of clumsy work
for very little gain.
Secondly, between the creation of the slave device and the binding of
a driver to it I'm holding an extra reference to the "tty_driver".
This is to prevent calls to destruct_tty_driver().
destruct_tty_driver() will unregister all the tty devices.
But it cannot get at the uart_slave devices.
I'm not at all sure I have the device unregistering side of things
right. As I was writing the code I imagined that I could arrange it
so that then the tty was unregistered, that would drop the last ref
on the slave and it would go away... I don't think that is right
after all. So that bit needs more work.
Should the slave devices only be removed when the "uart_slave_core"
modules is removed?
And I'd very much like comment on the changed to be uart-based
rather than tty-based.
I've tested this set and it seems to work ... except that something
is sadly broken with bluetooth support in 4.1-rc1 so I've only really
tested the GPS driver. I guess it is time to rebase to -rc3.
Thanks,
NeilBrown
---
NeilBrown (4):
TTY: use class_find_device to find port in uart_suspend/resume.
TTY: split tty_register_device_attr into 'initialize' and 'add' parts.
TTY: add support for uart_slave devices.
tty/slaves: add a driver to power on/off UART attached devices.
.../bindings/uart_slave/wi2wi,w2cbw003.txt | 19 +
.../bindings/uart_slave/wi2wi,w2sg0004.txt | 37 +
.../devicetree/bindings/vendor-prefixes.txt | 1
drivers/tty/serial/Kconfig | 1
drivers/tty/serial/Makefile | 2
drivers/tty/serial/serial_core.c | 30 +
drivers/tty/serial/slave/Kconfig | 21 +
drivers/tty/serial/slave/Makefile | 3
drivers/tty/serial/slave/serial-power-manager.c | 510 ++++++++++++++++++++
drivers/tty/serial/slave/uart_slave_core.c | 168 +++++++
drivers/tty/tty_io.c | 111 +++-
drivers/tty/tty_port.c | 24 +
include/linux/tty.h | 10
include/linux/uart_slave.h | 29 +
14 files changed, 918 insertions(+), 48 deletions(-)
create mode 100644 Documentation/devicetree/bindings/uart_slave/wi2wi,w2cbw003.txt
create mode 100644 Documentation/devicetree/bindings/uart_slave/wi2wi,w2sg0004.txt
create mode 100644 drivers/tty/serial/slave/Kconfig
create mode 100644 drivers/tty/serial/slave/Makefile
create mode 100644 drivers/tty/serial/slave/serial-power-manager.c
create mode 100644 drivers/tty/serial/slave/uart_slave_core.c
create mode 100644 include/linux/uart_slave.h
--
Signature
uart_{suspend,resume}_port search the children of a uart device
to find a particular tty device.
This requires all the ttys to be direct children of the uart.
A future patch will allow a 'tty_slave' to intervene between
the port and the uart, voiding this requirement.
So change to use class_find_device. This is made possible by
exporting a "tty_find_device" from tty_io.c.
This new "tty_find_device" is very similar to the existing
tty_get_device() which has a single caller, so discard
tty_get_device() and just use tty_find_device().
Signed-off-by: NeilBrown <[email protected]>
---
drivers/tty/serial/serial_core.c | 21 ++++++++-------------
drivers/tty/tty_io.c | 6 +++---
include/linux/tty.h | 1 +
3 files changed, 12 insertions(+), 16 deletions(-)
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index eb5b03be9dfd..3ea16f524e89 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -2005,26 +2005,19 @@ struct uart_match {
struct uart_driver *driver;
};
-static int serial_match_port(struct device *dev, void *data)
-{
- struct uart_match *match = data;
- struct tty_driver *tty_drv = match->driver->tty_driver;
- dev_t devt = MKDEV(tty_drv->major, tty_drv->minor_start) +
- match->port->line;
-
- return dev->devt == devt; /* Actually, only one tty per port */
-}
int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
struct device *tty_dev;
- struct uart_match match = {uport, drv};
+ dev_t devt = MKDEV(drv->tty_driver->major,
+ drv->tty_driver->minor_start) +
+ uport->line;
mutex_lock(&port->mutex);
- tty_dev = device_find_child(uport->dev, &match, serial_match_port);
+ tty_dev = tty_find_device(devt);
if (device_may_wakeup(tty_dev)) {
if (!enable_irq_wake(uport->irq))
uport->irq_wake = 1;
@@ -2084,12 +2077,14 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
struct device *tty_dev;
- struct uart_match match = {uport, drv};
struct ktermios termios;
+ dev_t devt = MKDEV(drv->tty_driver->major,
+ drv->tty_driver->minor_start) +
+ uport->line;
mutex_lock(&port->mutex);
- tty_dev = device_find_child(uport->dev, &match, serial_match_port);
+ tty_dev = tty_find_device(devt);
if (!uport->suspended && device_may_wakeup(tty_dev)) {
if (uport->irq_wake) {
disable_irq_wake(uport->irq);
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index e5695467598f..ccb99b772965 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3077,11 +3077,11 @@ static int dev_match_devt(struct device *dev, const void *data)
}
/* Must put_device() after it's unused! */
-static struct device *tty_get_device(struct tty_struct *tty)
+struct device *tty_find_device(dev_t devt)
{
- dev_t devt = tty_devnum(tty);
return class_find_device(tty_class, NULL, &devt, dev_match_devt);
}
+EXPORT_SYMBOL(tty_find_device);
/**
@@ -3123,7 +3123,7 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
tty->ops = driver->ops;
tty->index = idx;
tty_line_name(driver, idx, tty->name);
- tty->dev = tty_get_device(tty);
+ tty->dev = tty_find_device(tty_devnum(tty));
return tty;
}
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 358a337af598..04d5f1213700 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -461,6 +461,7 @@ extern void tty_vhangup(struct tty_struct *tty);
extern int tty_hung_up_p(struct file *filp);
extern void do_SAK(struct tty_struct *tty);
extern void __do_SAK(struct tty_struct *tty);
+extern struct device *tty_find_device(dev_t devt);
extern void no_tty(void);
extern void tty_flush_to_ldisc(struct tty_struct *tty);
extern void tty_buffer_free_all(struct tty_port *port);
This provides seperate
tty_device_initialize_attr()
and
tty_device_add_attr()
similar to device_initialize() and device_add().
Similiarly tty_port_initialize_device_attr() is added to match
tty_port_register_device_attr().
It will allow a client to separate initalization and adding of the
device.
Signed-off-by: NeilBrown <[email protected]>
---
drivers/tty/tty_io.c | 102 +++++++++++++++++++++++++++++++++---------------
drivers/tty/tty_port.c | 24 +++++++++++
include/linux/tty.h | 9 ++++
3 files changed, 104 insertions(+), 31 deletions(-)
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index ccb99b772965..83ca25b9c2da 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3205,8 +3205,29 @@ static void tty_device_create_release(struct device *dev)
kfree(dev);
}
+int tty_device_add(struct tty_driver *driver, struct device *dev)
+{
+ int retval;
+ bool cdev = false;
+ int index = dev->devt - MKDEV(driver->major,
+ driver->minor_start);
+
+ if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
+ retval = tty_cdev_add(driver, dev->devt, index, 1);
+ if (retval)
+ return retval;
+ cdev = true;
+ }
+ retval = device_add(dev);
+ if (retval == 0)
+ return 0;
+ if (cdev)
+ cdev_del(&driver->cdevs[index]);
+ return retval;
+}
+EXPORT_SYMBOL(tty_device_add);
/**
- * tty_register_device_attr - register a tty device
+ * tty_device_initialize_attr - initialize a tty device, but don't 'add'
* @driver: the tty driver that describes the tty device
* @index: the index in the tty driver for this tty device
* @device: a struct device that is associated with this tty device.
@@ -3218,23 +3239,19 @@ static void tty_device_create_release(struct device *dev)
* Returns a pointer to the struct device for this tty device
* (or ERR_PTR(-EFOO) on error).
*
- * This call is required to be made to register an individual tty device
- * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If
- * that bit is not set, this function should not be called by a tty
- * driver.
+ * tty_device_add() must be called after this call returns successfully
+ * before the device will be full registered and available.
*
* Locking: ??
*/
-struct device *tty_register_device_attr(struct tty_driver *driver,
- unsigned index, struct device *device,
- void *drvdata,
- const struct attribute_group **attr_grp)
+struct device *tty_device_initialize_attr(struct tty_driver *driver,
+ unsigned index, struct device *device,
+ void *drvdata,
+ const struct attribute_group **attr_grp)
{
char name[64];
dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
struct device *dev = NULL;
- int retval = -ENODEV;
- bool cdev = false;
if (index >= driver->num) {
printk(KERN_ERR "Attempt to register invalid tty line number "
@@ -3247,19 +3264,11 @@ struct device *tty_register_device_attr(struct tty_driver *driver,
else
tty_line_name(driver, index, name);
- if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
- retval = tty_cdev_add(driver, devt, index, 1);
- if (retval)
- goto error;
- cdev = true;
- }
-
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev) {
- retval = -ENOMEM;
- goto error;
- }
+ if (!dev)
+ return ERR_PTR(-ENOMEM);
+ device_initialize(dev);
dev->devt = devt;
dev->class = tty_class;
dev->parent = device;
@@ -3268,17 +3277,48 @@ struct device *tty_register_device_attr(struct tty_driver *driver,
dev->groups = attr_grp;
dev_set_drvdata(dev, drvdata);
- retval = device_register(dev);
- if (retval)
- goto error;
-
return dev;
+}
+EXPORT_SYMBOL_GPL(tty_device_initialize_attr);
-error:
- put_device(dev);
- if (cdev)
- cdev_del(&driver->cdevs[index]);
- return ERR_PTR(retval);
+/**
+ * tty_register_device_attr - register a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ * @device: a struct device that is associated with this tty device.
+ * This field is optional, if there is no known struct device
+ * for this tty device it can be set to NULL safely.
+ * @drvdata: Driver data to be set to device.
+ * @attr_grp: Attribute group to be set on device.
+ *
+ * Returns a pointer to the struct device for this tty device
+ * (or ERR_PTR(-EFOO) on error).
+ *
+ * This call is required to be made to register an individual tty device
+ * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If
+ * that bit is not set, this function should not be called by a tty
+ * driver.
+ *
+ * Locking: ??
+ */
+struct device *tty_register_device_attr(struct tty_driver *driver,
+ unsigned index, struct device *device,
+ void *drvdata,
+ const struct attribute_group **attr_grp)
+{
+ struct device *dev;
+ int ret;
+
+ dev = tty_device_initialize_attr(driver, index, device,
+ drvdata, attr_grp);
+ if (IS_ERR(dev))
+ return dev;
+ ret = tty_device_add(driver, dev);
+ if (ret) {
+ put_device(dev);
+ return ERR_PTR(ret);
+ }
+ return dev;
}
EXPORT_SYMBOL_GPL(tty_register_device_attr);
diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c
index 40b31835f80b..f4145c0f1f45 100644
--- a/drivers/tty/tty_port.c
+++ b/drivers/tty/tty_port.c
@@ -97,6 +97,30 @@ struct device *tty_port_register_device_attr(struct tty_port *port,
}
EXPORT_SYMBOL_GPL(tty_port_register_device_attr);
+/**
+ * tty_port_initialize_device_attr - initialize tty device
+ * @port: tty_port of the device
+ * @driver: tty_driver for this device
+ * @index: index of the tty
+ * @device: parent if exists, otherwise NULL
+ * @drvdata: Driver data to be set to device.
+ * @attr_grp: Attribute group to be set on device.
+ *
+ * It is the same as tty_initialize_device_attr except the provided @port is
+ * linked to a concrete tty specified by @index. Use this or tty_port_install
+ * (or both). Call tty_port_link_device as a last resort.
+ */
+struct device *tty_port_initialize_device_attr(struct tty_port *port,
+ struct tty_driver *driver, unsigned index,
+ struct device *device, void *drvdata,
+ const struct attribute_group **attr_grp)
+{
+ tty_port_link_device(port, driver, index);
+ return tty_device_initialize_attr(driver, index, device, drvdata,
+ attr_grp);
+}
+EXPORT_SYMBOL_GPL(tty_port_initialize_device_attr);
+
int tty_port_alloc_xmit_buf(struct tty_port *port)
{
/* We may sleep in get_zeroed_page() */
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 04d5f1213700..1b371ba9ed09 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -436,6 +436,11 @@ extern struct device *tty_register_device_attr(struct tty_driver *driver,
unsigned index, struct device *device,
void *drvdata,
const struct attribute_group **attr_grp);
+extern struct device *tty_device_initialize_attr(struct tty_driver *driver,
+ unsigned index, struct device *device,
+ void *drvdata,
+ const struct attribute_group **attr_grp);
+extern int tty_device_add(struct tty_driver *driver, struct device *dev);
extern void tty_unregister_device(struct tty_driver *driver, unsigned index);
extern int tty_read_raw_data(struct tty_struct *tty, unsigned char *bufp,
int buflen);
@@ -534,6 +539,10 @@ extern struct device *tty_port_register_device_attr(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device, void *drvdata,
const struct attribute_group **attr_grp);
+extern struct device *tty_port_initialize_device_attr(struct tty_port *port,
+ struct tty_driver *driver, unsigned index,
+ struct device *device, void *drvdata,
+ const struct attribute_group **attr_grp);
extern int tty_port_alloc_xmit_buf(struct tty_port *port);
extern void tty_port_free_xmit_buf(struct tty_port *port);
extern void tty_port_destroy(struct tty_port *port);
If a platform has a particular device permanently attached to a UART,
there may be out-of-band signaling necessary to power the device
on and off.
This driver controls that signalling for a number of different devices.
It can
- enable/disable a regulator
- toggle a GPIO
- register an 'rfkill' which can force the device to be off.
When the rfkill is absent or unblocked, the device will be on when the
associated tty device is open, and closed otherwise.
Signed-off-by: NeilBrown <[email protected]>
---
.../bindings/uart_slave/wi2wi,w2cbw003.txt | 19 +
.../bindings/uart_slave/wi2wi,w2sg0004.txt | 37 +
.../devicetree/bindings/vendor-prefixes.txt | 1
drivers/tty/serial/slave/Kconfig | 15 +
drivers/tty/serial/slave/Makefile | 1
drivers/tty/serial/slave/serial-power-manager.c | 510 ++++++++++++++++++++
6 files changed, 583 insertions(+)
create mode 100644 Documentation/devicetree/bindings/uart_slave/wi2wi,w2cbw003.txt
create mode 100644 Documentation/devicetree/bindings/uart_slave/wi2wi,w2sg0004.txt
create mode 100644 drivers/tty/serial/slave/serial-power-manager.c
diff --git a/Documentation/devicetree/bindings/uart_slave/wi2wi,w2cbw003.txt b/Documentation/devicetree/bindings/uart_slave/wi2wi,w2cbw003.txt
new file mode 100644
index 000000000000..cfe6ee5e01e9
--- /dev/null
+++ b/Documentation/devicetree/bindings/uart_slave/wi2wi,w2cbw003.txt
@@ -0,0 +1,19 @@
+wi2wi bluetooth module
+
+This is accessed via a serial port and is largely controlled via that
+link. Extra configuration is needed to enable power on/off
+
+Required properties:
+- compatible: "wi2wi,w2cbw003"
+- vdd-supply: regulator used to power the device.
+
+The node for this device must be the child of a UART.
+
+Example:
+
+&uart1 {
+ bluetooth {
+ compatible = "wi2wi,w2cbw003";
+ vdd-supply = <&vaux4>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/uart_slave/wi2wi,w2sg0004.txt b/Documentation/devicetree/bindings/uart_slave/wi2wi,w2sg0004.txt
new file mode 100644
index 000000000000..6bcf3006c1a4
--- /dev/null
+++ b/Documentation/devicetree/bindings/uart_slave/wi2wi,w2sg0004.txt
@@ -0,0 +1,37 @@
+wi2wi GPS device
+
+This is accessed via a serial port and is largely controlled via that
+link. Extra configuration is needed to enable power on/off
+
+Required properties:
+- compatible: "wi2wi,w2sg0004"
+- gpios: gpio used to toggle 'on/off' pin
+- interrupts: interrupt generated by RX pin when device
+ should be off
+
+Optional properties:
+- vdd-supply: regulator used to power antenna
+- pinctrl: "default", "off"
+ if "off" setting is provided it is imposed when device should
+ be off. This can route the RX pin to a GPIO interrupt.
+
+The w2sg0004 uses a pin-toggle both to power-on and to
+power-off, so the driver needs to detect what state it is in.
+It does this by detecting characters on the RX line.
+When it should be off, these can optionally be detected by a GPIO.
+
+The node for this device must be the child of a UART.
+
+Example:
+&uart2 {
+ gps {
+ compatible = "wi2iw,w2sg0004";
+ vdd-supply = <&vsim>;
+ gpios = <&gpio5 17 0>; /* GPIO_145 */
+ interrupts-extended = <&gpio5 19 0>; /* GPIO_147 */
+ /* When off, switch RX to be an interrupt */
+ pinctrl-names = "default", "off";
+ pinctrl-0 = <&uart2_pins>;
+ pinctrl-1 = <&uart2_pins_rx_gpio>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 80339192c93e..45c703a3b29d 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -202,6 +202,7 @@ variscite Variscite Ltd.
via VIA Technologies, Inc.
virtio Virtual I/O Device Specification, developed by the OASIS consortium
voipac Voipac Technologies s.r.o.
+wi2wi wi2wi Inc. http://www.wi2wi.com/
winbond Winbond Electronics corp.
wlf Wolfson Microelectronics
wm Wondermedia Technologies, Inc.
diff --git a/drivers/tty/serial/slave/Kconfig b/drivers/tty/serial/slave/Kconfig
index 6620e78b763e..46285d8895b7 100644
--- a/drivers/tty/serial/slave/Kconfig
+++ b/drivers/tty/serial/slave/Kconfig
@@ -4,3 +4,18 @@ menuconfig UART_SLAVE
help
Devices which attach via a uart, but need extra
driver support for power management etc.
+
+if UART_SLAVE
+
+config SERIAL_POWER_MANAGER
+ tristate "Power Management controller for serial-attached devices"
+ default n
+ help
+ Some devices permanently attached via a UART can benefit from
+ being power-managed when the tty device is opened or closed.
+ This driver can support several such devices with simple
+ power requirements such as enabling a regulator.
+
+ If in doubt, say 'N'
+
+endif
diff --git a/drivers/tty/serial/slave/Makefile b/drivers/tty/serial/slave/Makefile
index aac8697fa406..5d9b3d373138 100644
--- a/drivers/tty/serial/slave/Makefile
+++ b/drivers/tty/serial/slave/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_UART_SLAVE) += uart_slave_core.o
+obj-$(CONFIG_SERIAL_POWER_MANAGER) += serial-power-manager.o
diff --git a/drivers/tty/serial/slave/serial-power-manager.c b/drivers/tty/serial/slave/serial-power-manager.c
new file mode 100644
index 000000000000..81b8f909fd3a
--- /dev/null
+++ b/drivers/tty/serial/slave/serial-power-manager.c
@@ -0,0 +1,510 @@
+/*
+ * Serial-power-manager
+ * uart-slave device that intercepts open/close events on the tty,
+ * and turns power on/off for the device which is connected.
+ *
+ * Currently supported devices:
+ * wi2wi,w2sg0004 - GPS with on/off toggle on a GPIO
+ * wi2wi,w2cbw003 - bluetooth port; powered by regulator.
+ *
+ * When appropriate, an RFKILL will be registered which
+ * can power-down the device even when it is open.
+ *
+ * Device can be turned on either by
+ * - enabling a regulator. Disable to turn off
+ * - toggling a GPIO. Toggle again to turn off. This requires
+ * that we know the current state. It is assumed to be 'off'
+ * at boot, however if an interrupt can be generated when on,
+ * such as by connecting RX to a GPIO, that can be used to detect
+ * if the device is on when it should be off.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/tty.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/rfkill.h>
+
+#include <linux/uart_slave.h>
+
+/* This is used for testing. Setting this module parameter
+ * will simulate booting with the device "on"
+ */
+static bool toggle_on_probe = false;
+module_param(toggle_on_probe, bool, 0);
+MODULE_PARM_DESC(toggle_on_probe, "simulate power-on with devices active");
+
+struct spm_config {
+ int rfkill_type; /* type of rfkill to register */
+ int toggle_time; /* msec to pulse GPIO for on/off */
+ int toggle_gap; /* minimum msecs between toggles */
+ bool off_in_suspend;
+}
+ simple_config = {
+ .off_in_suspend = true,
+ },
+ w2sg_config = {
+ .rfkill_type = RFKILL_TYPE_GPS,
+ .toggle_time = 10,
+ .toggle_gap = 500,
+ .off_in_suspend = true,
+ };
+
+const static struct of_device_id spm_dt_ids[] = {
+ { .compatible = "wi2wi,w2sg0004", .data = &w2sg_config},
+ { .compatible = "wi2wi,w2cbw003", .data = &simple_config},
+ {}
+};
+
+struct spm_data {
+ const struct spm_config *config;
+ struct gpio_desc *gpiod;
+ int irq; /* irq line from RX pin when pinctrl
+ * set to 'idle' */
+ struct regulator *reg;
+
+ unsigned long toggle_time;
+ unsigned long toggle_gap;
+ unsigned long last_toggle; /* jiffies when last toggle completed. */
+ unsigned long backoff; /* jiffies since last_toggle when
+ * we try again
+ */
+ enum {Idle, Down, Up} state; /* state-machine state. */
+
+ int open_cnt;
+ bool requested, is_on;
+ bool suspended;
+ bool reg_enabled;
+
+ struct pinctrl *pins;
+ struct pinctrl_state *pins_off;
+
+ struct delayed_work work;
+ spinlock_t lock;
+ struct device *dev;
+
+ struct rfkill *rfkill;
+
+ int (*old_open)(struct tty_struct * tty, struct file * filp);
+ void (*old_close)(struct tty_struct * tty, struct file * filp);
+
+};
+
+/* When a device is powered on/off by toggling a GPIO we perform
+ * all the toggling via a workqueue to ensure only one toggle happens
+ * at a time and to allow easy timing.
+ * This is managed as a state machine which transitions
+ * Idle -> Down -> Up -> Idle
+ * The GPIO is held down for toggle_time and then up for toggle_time,
+ * and then we assume the device has changed state.
+ * We never toggle until at least toggle_gap has passed since the
+ * last toggle.
+ */
+static void toggle_work(struct work_struct *work)
+{
+ struct spm_data *data = container_of(
+ work, struct spm_data, work.work);
+
+ if (data->gpiod == NULL)
+ return;
+
+ spin_lock_irq(&data->lock);
+ switch (data->state) {
+ case Up:
+ data->state = Idle;
+ if (data->requested == data->is_on)
+ break;
+ if (!data->requested)
+ /* Assume it is off unless activity is detected */
+ break;
+ /* Try again in a while unless we get some activity */
+ dev_dbg(data->dev, "Wait %dusec until retry\n",
+ jiffies_to_msecs(data->backoff));
+ schedule_delayed_work(&data->work, data->backoff);
+ break;
+ case Idle:
+ if (data->requested == data->is_on)
+ break;
+
+ /* Time to toggle */
+ dev_dbg(data->dev, "Starting toggle to turn %s\n",
+ data->requested ? "on" : "off");
+ data->state = Down;
+ spin_unlock_irq(&data->lock);
+ gpiod_set_value_cansleep(data->gpiod, 1);
+ schedule_delayed_work(&data->work, data->toggle_time);
+
+ return;
+
+ case Down:
+ data->state = Up;
+ data->last_toggle = jiffies;
+ dev_dbg(data->dev, "Toggle completed, should be %s now.\n",
+ data->is_on ? "off" : "on");
+ data->is_on = ! data->is_on;
+ spin_unlock_irq(&data->lock);
+
+ gpiod_set_value_cansleep(data->gpiod, 0);
+ schedule_delayed_work(&data->work, data->toggle_time);
+
+ return;
+ }
+ spin_unlock_irq(&data->lock);
+}
+
+static irqreturn_t spm_isr(int irq, void *dev_id)
+{
+ struct spm_data *data = dev_id;
+ unsigned long flags;
+
+ spin_lock_irqsave(&data->lock, flags);
+ if (!data->requested && !data->is_on && data->state == Idle &&
+ time_after(jiffies, data->last_toggle + data->backoff)) {
+ data->is_on = 1;
+ data->backoff *= 2;
+ dev_dbg(data->dev, "Received data, must be on. Try to turn off\n");
+ if (!data->suspended)
+ schedule_delayed_work(&data->work, 0);
+ }
+ spin_unlock_irqrestore(&data->lock, flags);
+ return IRQ_HANDLED;
+}
+
+static void spm_on(struct spm_data *data)
+{
+ if (!data->rfkill || !rfkill_blocked(data->rfkill)) {
+ unsigned long flags;
+
+ if (!data->reg_enabled &&
+ data->reg &&
+ regulator_enable(data->reg) == 0)
+ data->reg_enabled = true;
+
+ spin_lock_irqsave(&data->lock, flags);
+ if (!data->requested) {
+ dev_dbg(data->dev, "TTY open - turn device on\n");
+ data->requested = true;
+ data->backoff = data->toggle_gap;
+ if (data->irq > 0) {
+ disable_irq(data->irq);
+ pinctrl_pm_select_default_state(data->dev);
+ }
+ if (!data->suspended && data->state == Idle)
+ schedule_delayed_work(&data->work, 0);
+ }
+ spin_unlock_irqrestore(&data->lock, flags);
+ }
+}
+
+static int spm_open(struct tty_struct *tty, struct file *filp)
+{
+ struct spm_data *data = dev_get_drvdata(tty->dev->parent);
+
+ data->open_cnt++;
+ spm_on(data);
+ if (data->old_open)
+ return data->old_open(tty, filp);
+ return -ENODEV;
+}
+
+static void spm_off(struct spm_data *data)
+{
+ unsigned long flags;
+
+ if (data->reg && data->reg_enabled)
+ if (regulator_disable(data->reg) == 0)
+ data->reg_enabled = false;
+
+ spin_lock_irqsave(&data->lock, flags);
+ if (data->requested) {
+ data->requested = false;
+ data->backoff = data->toggle_gap;
+ if (data->pins_off) {
+ pinctrl_select_state(data->pins,
+ data->pins_off);
+ enable_irq(data->irq);
+ }
+ if (!data->suspended && data->state == Idle)
+ schedule_delayed_work(&data->work, 0);
+ }
+ spin_unlock_irqrestore(&data->lock, flags);
+}
+
+static void spm_close(struct tty_struct *tty, struct file *filp)
+{
+ struct spm_data *data = dev_get_drvdata(tty->dev->parent);
+
+ data->open_cnt--;
+ if (!data->open_cnt) {
+ dev_dbg(data->dev, "TTY closed - turn device off\n");
+ spm_off(data);
+ }
+
+ if (data->old_close)
+ data->old_close(tty, filp);
+}
+
+static int spm_rfkill_set_block(void *vdata, bool blocked)
+{
+ struct spm_data *data = vdata;
+
+ dev_dbg(data->dev, "rfkill_set_blocked %d\n", blocked);
+ if (blocked)
+ spm_off(data);
+
+ if (!blocked &&
+ data->open_cnt)
+ spm_on(data);
+
+ return 0;
+}
+
+static struct rfkill_ops spm_rfkill_ops = {
+ .set_block = spm_rfkill_set_block,
+};
+
+static int spm_suspend(struct device *dev)
+{
+ /* Ignore incoming data and just turn device off.
+ * we cannot really wait for a separate thread to
+ * do things, so we disable that and do it all
+ * here
+ */
+ struct spm_data *data = dev_get_drvdata(dev);
+
+ spin_lock_irq(&data->lock);
+ data->suspended = true;
+ spin_unlock_irq(&data->lock);
+ if (!data->config->off_in_suspend)
+ return 0;
+
+ if (data->gpiod) {
+
+ cancel_delayed_work_sync(&data->work);
+ if (data->state == Down) {
+ dev_dbg(data->dev, "Suspending while GPIO down - raising\n");
+ msleep(data->config->toggle_time);
+ gpiod_set_value_cansleep(data->gpiod, 0);
+ data->last_toggle = jiffies;
+ data->is_on = !data->is_on;
+ data->state = Up;
+ }
+ if (data->state == Up) {
+ msleep(data->config->toggle_time);
+ data->state = Idle;
+ }
+ if (data->is_on) {
+ dev_dbg(data->dev, "Suspending while device on: toggling\n");
+ gpiod_set_value_cansleep(data->gpiod, 1);
+ msleep(data->config->toggle_time);
+ gpiod_set_value_cansleep(data->gpiod, 0);
+ data->is_on = 0;
+ }
+ }
+
+ if (data->reg && data->reg_enabled)
+ if (regulator_disable(data->reg) == 0)
+ data->reg_enabled = false;
+
+ return 0;
+}
+
+static int spm_resume(struct device *dev)
+{
+ struct spm_data *data = dev_get_drvdata(dev);
+
+ spin_lock_irq(&data->lock);
+ data->suspended = false;
+ spin_unlock_irq(&data->lock);
+ schedule_delayed_work(&data->work, 0);
+
+ if (data->open_cnt &&
+ (!data->rfkill || !rfkill_blocked(data->rfkill))) {
+ if (!data->reg_enabled &&
+ data->reg &&
+ regulator_enable(data->reg) == 0)
+ data->reg_enabled = true;
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops spm_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(spm_suspend, spm_resume)
+};
+
+static int spm_probe(struct device *dev)
+{
+ struct uart_slave *slave = container_of(dev, struct uart_slave, dev);
+ struct spm_data *data;
+ struct regulator *reg;
+ int err;
+ const struct of_device_id *id;
+ const char *name;
+
+ if (dev->parent == NULL)
+ return -ENODEV;
+
+ id = of_match_device(spm_dt_ids, dev);
+ if (!id)
+ return -ENODEV;
+
+ if (dev->of_node && dev->of_node->name)
+ name = dev->of_node->name;
+ else
+ name = "serial-power-manager";
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->config = id->data;
+ data->toggle_time = msecs_to_jiffies(data->config->toggle_time) + 1;
+ data->toggle_gap = msecs_to_jiffies(data->config->toggle_gap) + 1;
+ data->last_toggle = jiffies;
+ data->backoff = data->toggle_gap;
+ data->state = Idle;
+ spin_lock_init(&data->lock);
+ INIT_DELAYED_WORK(&data->work, toggle_work);
+
+ /* If a regulator is provided, it is enabled on 'open'
+ * and disabled on 'release'
+ */
+ reg = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(reg)) {
+ err = PTR_ERR(reg);
+ if (err != -ENODEV)
+ goto out;
+ } else
+ data->reg = reg;
+
+ /* If an irq is provided, any transitions are taken as
+ * indication that the device is currently "on"
+ */
+ data->irq = of_irq_get(dev->of_node, 0);
+ if (data->irq < 0) {
+ err = data->irq;
+ if (err != -EINVAL)
+ goto out;
+ } else {
+ dev_dbg(dev, "IRQ configured: %d\n", data->irq);
+
+ irq_set_status_flags(data->irq, IRQ_NOAUTOEN);
+ err = devm_request_irq(dev, data->irq, spm_isr,
+ IRQF_TRIGGER_FALLING,
+ name, data);
+
+ if (err)
+ goto out;
+
+ }
+
+ /* If a gpio is provided, then it is used to turn the device
+ * on/off.
+ * The GPIO is toggled to change the state of the device.
+ * A "toggle" involves holding the GPIO active for toggle_time.
+ */
+ data->gpiod = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpiod)) {
+ err = PTR_ERR(data->gpiod);
+ if (err != -ENOENT)
+ goto out;
+ data->gpiod = NULL;
+ } else
+ dev_dbg(dev, "GPIO configured: %d\n",
+ desc_to_gpio(data->gpiod));
+
+ /* If an 'off' pinctrl state is defined, we apply that
+ * when the device is assumed to be off. This is expected to
+ * route the 'rx' line to the 'irq' interrupt.
+ */
+ data->pins = devm_pinctrl_get(dev);
+ if (data->pins && data->irq > 0) {
+ data->pins_off = pinctrl_lookup_state(data->pins, "off");
+ if (IS_ERR(data->pins_off))
+ data->pins_off = NULL;
+ }
+
+ if (data->config->rfkill_type) {
+ data->rfkill = rfkill_alloc(name, dev,
+ data->config->rfkill_type,
+ &spm_rfkill_ops, data);
+ if (!data->rfkill) {
+ err = -ENOMEM;
+ goto out;
+ }
+ err = rfkill_register(data->rfkill);
+ if (err) {
+ dev_err(dev, "Cannot register rfkill device");
+ rfkill_destroy(data->rfkill);
+ goto out;
+ }
+ }
+ dev_set_drvdata(dev, data);
+ data->dev = dev;
+ data->old_open = slave->ops.open;
+ data->old_close = slave->ops.close;
+ slave->ops.open = spm_open;
+ slave->ops.close = spm_close;
+ uart_slave_add_tty(slave);
+
+ if (data->pins_off)
+ pinctrl_select_state(data->pins, data->pins_off);
+ if (data->irq > 0)
+ enable_irq(data->irq);
+
+ if (toggle_on_probe && data->gpiod) {
+ dev_dbg(data->dev, "Performing initial toggle\n");
+ gpiod_set_value_cansleep(data->gpiod, 1);
+ msleep(data->config->toggle_time);
+ gpiod_set_value_cansleep(data->gpiod, 0);
+ msleep(data->config->toggle_time);
+ }
+ err = 0;
+out:
+ dev_dbg(data->dev, "Probed: err=%d\n", err);
+ return err;
+}
+
+static int spm_remove(struct device *dev)
+{
+ struct spm_data *data = dev_get_drvdata(dev);
+
+ if (data->rfkill) {
+ rfkill_unregister(data->rfkill);
+ rfkill_destroy(data->rfkill);
+ }
+ return 0;
+}
+
+static struct device_driver spm_driver = {
+ .name = "serial-power-manager",
+ .owner = THIS_MODULE,
+ .of_match_table = spm_dt_ids,
+ .probe = spm_probe,
+ .remove = spm_remove,
+};
+
+static int __init spm_init(void)
+{
+ return uart_slave_driver_register(&spm_driver);
+}
+module_init(spm_init);
+
+static void __exit spm_exit(void)
+{
+ driver_unregister(&spm_driver);
+}
+module_exit(spm_exit);
+
+MODULE_AUTHOR("NeilBrown <[email protected]>");
+MODULE_DEVICE_TABLE(of, spm_dt_ids);
+MODULE_DESCRIPTION("Power management for Serial-attached device.");
+MODULE_LICENSE("GPL v2");
A "uart slave" is a device permanently connected via UART.
Such a device may need its own driver, e.g. for powering
it up on tty open and powering it down on tty release.
When a device is connected to a UART by a 'standard' bus, such as
RS-232, signaling for power control typically uses "DTR". When the
connection is permanent, as is common on "embedded" boards, separate
signaling may be needed and this requires a separate driver.
uart-slave is a new bus-type which drivers can be written and devices
created.
A "uart slave" device is declared as a child of the uart in
device-tree:
&uart1 {
bluetooth {
compatible = "wi2wi,w2cbw003";
vdd-supply = <&vaux4>;
};
};
This device will be inserted in the driver-model tree between the uart
and the tty.
The uart-slave driver can replace any of the tty_operations functions
so a call by the tty can be intercepted before being handled by the
uart.
When the tty port is initialized, the uart_slave device is created and
waits for a driver to be bound to it. Once this happens the tty
device, which was previously initialized, will be added. This slave
is now considered "finalized".
Any "finalized" slaves will be removed when the tty device is
unregistered. e.g. by destruct_tty_driver.
While slaves are non-finalized they hold a reference to the tty driver
to prevent destruct_tty_driver from being called, as it cannot find
and free slave devices.
Signed-off-by: NeilBrown <[email protected]>
---
drivers/tty/serial/Kconfig | 1
drivers/tty/serial/Makefile | 2
drivers/tty/serial/serial_core.c | 9 +-
drivers/tty/serial/slave/Kconfig | 6 +
drivers/tty/serial/slave/Makefile | 2
drivers/tty/serial/slave/uart_slave_core.c | 168 ++++++++++++++++++++++++++++
drivers/tty/tty_io.c | 3 +
include/linux/uart_slave.h | 29 +++++
8 files changed, 219 insertions(+), 1 deletion(-)
create mode 100644 drivers/tty/serial/slave/Kconfig
create mode 100644 drivers/tty/serial/slave/Makefile
create mode 100644 drivers/tty/serial/slave/uart_slave_core.c
create mode 100644 include/linux/uart_slave.h
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index f8120c1bde14..2601a8fb41a3 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1594,4 +1594,5 @@ endmenu
config SERIAL_MCTRL_GPIO
tristate
+source drivers/tty/serial/slave/Kconfig
endif # TTY
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index c3ac3d930b33..7a6ed85257f6 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -96,3 +96,5 @@ obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o
# GPIOLIB helpers for modem control lines
obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
+
+obj-y += slave/
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 3ea16f524e89..fcad5b30486f 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -34,6 +34,7 @@
#include <linux/serial_core.h>
#include <linux/delay.h>
#include <linux/mutex.h>
+#include <linux/uart_slave.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
@@ -2710,10 +2711,16 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this port's parameters.
*/
- tty_dev = tty_port_register_device_attr(port, drv->tty_driver,
+ tty_dev = tty_port_initialize_device_attr(port, drv->tty_driver,
uport->line, uport->dev, port, uport->tty_groups);
if (likely(!IS_ERR(tty_dev))) {
device_set_wakeup_capable(tty_dev, 1);
+ if (uart_slave_register(uport->dev, tty_dev,
+ drv->tty_driver) < 0) {
+ ret = tty_device_add(drv->tty_driver, tty_dev);
+ if (ret)
+ put_device(tty_dev);
+ }
} else {
dev_err(uport->dev, "Cannot register tty device on line %d\n",
uport->line);
diff --git a/drivers/tty/serial/slave/Kconfig b/drivers/tty/serial/slave/Kconfig
new file mode 100644
index 000000000000..6620e78b763e
--- /dev/null
+++ b/drivers/tty/serial/slave/Kconfig
@@ -0,0 +1,6 @@
+menuconfig UART_SLAVE
+ tristate "UART slave devices"
+ depends on OF
+ help
+ Devices which attach via a uart, but need extra
+ driver support for power management etc.
diff --git a/drivers/tty/serial/slave/Makefile b/drivers/tty/serial/slave/Makefile
new file mode 100644
index 000000000000..aac8697fa406
--- /dev/null
+++ b/drivers/tty/serial/slave/Makefile
@@ -0,0 +1,2 @@
+
+obj-$(CONFIG_UART_SLAVE) += uart_slave_core.o
diff --git a/drivers/tty/serial/slave/uart_slave_core.c b/drivers/tty/serial/slave/uart_slave_core.c
new file mode 100644
index 000000000000..d48d672300c2
--- /dev/null
+++ b/drivers/tty/serial/slave/uart_slave_core.c
@@ -0,0 +1,168 @@
+/*
+ * uart slave core - device bus for uart slaves
+ *
+ * Copyright (C) 2015 NeilBrown <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * A "uart-slave" is a device permanently attached to a particular
+ * wired to a UART.
+ * A uart-slave has two particular roles.
+ * Firstly it can intercept any tty_operations to provide extra control
+ * of the device. For example it might intercept "open" and "close"
+ * in order to power the device up and down. It might intercept
+ * "hangup" to toggle a reset line on the device.
+ *
+ * Secondly it appears as a parent of the tty in the device model, so
+ * that any attributes it presents are visible to udev when the tty
+ * is added. This allows udev to start appropriate handlers such as
+ * hciattach or inputattach.
+ *
+ * uart-slave devices must be described in devicetree as a child node
+ * of the node which described the attaching UART.
+ *
+ * If such a child is present, the tty device will not be registered
+ * until the slave device is fully probed and initialized.
+ */
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/uart_slave.h>
+
+static int uart_slave_match(struct device *dev, struct device_driver *drv)
+{
+ return of_driver_match_device(dev, drv);
+}
+
+static void uart_slave_release(struct device *dev)
+{
+ struct uart_slave *slave =
+ container_of(dev, struct uart_slave, dev);
+
+ if (!slave->finalized) {
+ put_device(slave->tty_dev);
+ tty_driver_kref_put(slave->tty_drv);
+ }
+ of_node_put(dev->of_node);
+ kfree(slave);
+}
+
+struct bus_type uart_slave_bus_type = {
+ .name = "uart-slave",
+ .match = uart_slave_match,
+};
+
+int uart_slave_register(struct device *parent,
+ struct device *tty, struct tty_driver *drv)
+{
+ struct device_node *node, *found = NULL;
+ struct uart_slave *slave;
+ int retval;
+
+ if (!parent || !parent->of_node)
+ return -ENODEV;
+
+ for_each_available_child_of_node(parent->of_node, node) {
+ if (!of_get_property(node, "compatible", NULL))
+ continue;
+ if (found) {
+ dev_err(parent, "Multiple connected children found - non registered");
+ return -ENODEV;
+ }
+ found = node;
+ }
+ if (!found)
+ return -ENODEV;
+
+ slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+ if (!slave)
+ return -ENOMEM;
+
+ slave->dev.bus = &uart_slave_bus_type;
+ slave->dev.parent = parent;
+ slave->dev.release = uart_slave_release;
+ slave->dev.of_node = of_node_get(found);
+ dev_set_name(&slave->dev, "%s", found->name);
+ slave->tty_dev = tty;
+ slave->tty_drv = tty_driver_kref_get(drv);
+ slave->ops = *drv->ops;
+ retval = device_register(&slave->dev);
+ if (retval)
+ put_device(&slave->dev);
+ return retval;
+}
+EXPORT_SYMBOL(uart_slave_register);
+
+void uart_slave_activate(struct tty_struct *tty)
+{
+ struct device *parent = NULL;
+ if (tty->dev)
+ parent = tty->dev->parent;
+ if (parent &&
+ parent->bus == &uart_slave_bus_type)
+ {
+ struct uart_slave *dev =
+ container_of(parent, struct uart_slave, dev);
+ tty->ops = &dev->ops;
+ }
+}
+EXPORT_SYMBOL(uart_slave_activate);
+
+int uart_slave_add_tty(struct uart_slave *slave)
+{
+ int retval;
+ if (slave->finalized)
+ return -EBUSY;
+ slave->tty_dev->parent = &slave->dev;
+ retval = tty_device_add(slave->tty_drv,
+ slave->tty_dev);
+ /* If that succeeded, the tty now holds a reference to
+ * the slave through ->parent, so that ref we hold
+ * can be dropped, as can our ref on the tty driver.
+ */
+ slave->finalized = true;
+ tty_driver_kref_put(slave->tty_drv);
+ put_device(&slave->dev);
+ return retval;
+}
+EXPORT_SYMBOL(uart_slave_add_tty);
+
+int uart_slave_driver_register(struct device_driver *drv)
+{
+ drv->bus = &uart_slave_bus_type;
+ return driver_register(drv);
+}
+EXPORT_SYMBOL(uart_slave_driver_register);
+
+static int __init uart_slave_init(void)
+{
+ return bus_register(&uart_slave_bus_type);
+}
+
+static void __exit uart_slave_exit(void)
+{
+ bus_unregister(&uart_slave_bus_type);
+}
+
+postcore_initcall(uart_slave_init);
+module_exit(uart_slave_exit);
+MODULE_AUTHOR("NeilBrown <[email protected]>");
+MODULE_DESCRIPTION("UART-slave core support.");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 83ca25b9c2da..2bf516f99dd2 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -95,6 +95,7 @@
#include <linux/seq_file.h>
#include <linux/serial.h>
#include <linux/ratelimit.h>
+#include <linux/uart_slave.h>
#include <linux/uaccess.h>
@@ -1531,6 +1532,8 @@ struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
if (retval < 0)
goto err_deinit_tty;
+ uart_slave_activate(tty);
+
if (!tty->port)
tty->port = driver->ports[idx];
diff --git a/include/linux/uart_slave.h b/include/linux/uart_slave.h
new file mode 100644
index 000000000000..9a19825ce47a
--- /dev/null
+++ b/include/linux/uart_slave.h
@@ -0,0 +1,29 @@
+#ifndef _LINUX_UART_SLAVE_H
+#define _LINUX_UART_SLAVE_H
+struct uart_slave {
+ struct device *tty_dev;
+ struct tty_driver *tty_drv;
+ struct tty_operations ops;
+ struct device dev;
+ bool finalized;
+};
+
+int uart_slave_add_tty(struct uart_slave *slave);
+int uart_slave_driver_register(struct device_driver *drv);
+#if IS_ENABLED(CONFIG_UART_SLAVE)
+void uart_slave_activate(struct tty_struct *tty);
+int uart_slave_register(struct device *parent,
+ struct device *tty, struct tty_driver *drv);
+#else
+static inline void uart_slave_activate(struct tty_struct *tty)
+{
+}
+static inline int uart_slave_register(struct device *parent,
+ struct device *tty,
+ struct tty_driver *drv)
+{
+ return -ENODEV;
+}
+#endif
+
+#endif /* _LINUX_UART_SLAVE_H */
Just a nit: a license mismatch.
On Mon, 2015-05-11 at 11:56 +1000, NeilBrown wrote:
> --- /dev/null
> +++ b/drivers/tty/serial/slave/uart_slave_core.c
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
This states the license is GPL v2 or later.
> +MODULE_LICENSE("GPL v2");
And, according to include/linux/module.h, this states the license is
just GPL v2. So I think either the comment at the top of this file or
the license ident used in the MODULE_LICENSE() macro needs to change.
Thanks,
Paul Bolle
On Tue, 12 May 2015 10:31:01 +0200 Paul Bolle <[email protected]> wrote:
> Just a nit: a license mismatch.
>
> On Mon, 2015-05-11 at 11:56 +1000, NeilBrown wrote:
> > --- /dev/null
> > +++ b/drivers/tty/serial/slave/uart_slave_core.c
>
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > + * GNU General Public License for more details.
>
> This states the license is GPL v2 or later.
>
> > +MODULE_LICENSE("GPL v2");
>
> And, according to include/linux/module.h, this states the license is
> just GPL v2. So I think either the comment at the top of this file or
> the license ident used in the MODULE_LICENSE() macro needs to change.
>
> Thanks,
>
>
> Paul Bolle
>
Thanks. I've modified the comment to be explicit that only 'version 2' is
appropriate.
NeilBrown
Hi Neil,
first, this is a *VERY* interesting and much needed patch series,
I intend to look closer at it, and if possible test it with some
(heh) board file device. Would be happy of you put me on CC for these.
On Mon, May 11, 2015 at 3:56 AM, NeilBrown <[email protected]> wrote:
> When a device is connected to a UART via RS-232 (or similar), there
> is a DTR line that can be used for power management, and other "modem
> control" lines.
>
> On an embedded board, it is very likely that there is no "DTR", and
> any power management need to be done using some completely separate
> mechanism.
>
> So these "slaves" are really just for devices permanently attached to
> UARTs without a full "RS-232" (or similar) connection. The driver
> does all the extra control beyond Tx/Rx.
What is usually happening (and I have seen it in a few places) is that
the SoC has *one* fully featured RS232 with CTS/RTS and even
DTS,DCD,RI and other esoterica, which is intended to be connected to a
host serial port or so, for example if this SoC is to act as a modem
or a fax machine, or if it is to drive one.
Then they often have a few more UART blocks, usually identical, which
only have RxD+TxD available, so they are "just" UARTs.
To complicate things further, you may wonder what happened with
the CTS/RTS (etc) signals from the other blocks. Usually they are there
in the silicon but just routed to dead ends.
To complicate it even further, usually all these pins are placed under
pin control multiplexing, so in an actual electronic design, the
system will mux out CTS/RTS (etc) from the fully featured RS232
blocks and only use them as UARTs anyways.
Then there are those who created real simple RxD/TxD-only UARTs
("yeah lets dump this RS232 legacy crap" / "yeah yeah")
and then realized they want to drive modems ("oh crap, it seemed
like a good idea at the time"). Then they usually take
two GPIO pins for CTS/RTS and drive them as GPIOs using
software and you have a cheap 4-line modem line. This is what
drivers/tty/serial/serial_mctrl_gpio.c is for if you wondered.
> I've tested this set and it seems to work ... except that something
> is sadly broken with bluetooth support in 4.1-rc1 so I've only really
> tested the GPS driver. I guess it is time to rebase to -rc3.
You have a hardware taget I see. Which one?
Yours,
Linus Walleij
On Fri, 7 Aug 2015 15:01:47 +0200 Linus Walleij
<[email protected]> wrote:
> Hi Neil,
>
> first, this is a *VERY* interesting and much needed patch series,
> I intend to look closer at it, and if possible test it with some
> (heh) board file device. Would be happy of you put me on CC for these.
>
> On Mon, May 11, 2015 at 3:56 AM, NeilBrown <[email protected]> wrote:
>
> > When a device is connected to a UART via RS-232 (or similar), there
> > is a DTR line that can be used for power management, and other "modem
> > control" lines.
> >
> > On an embedded board, it is very likely that there is no "DTR", and
> > any power management need to be done using some completely separate
> > mechanism.
> >
> > So these "slaves" are really just for devices permanently attached to
> > UARTs without a full "RS-232" (or similar) connection. The driver
> > does all the extra control beyond Tx/Rx.
>
> What is usually happening (and I have seen it in a few places) is that
> the SoC has *one* fully featured RS232 with CTS/RTS and even
> DTS,DCD,RI and other esoterica, which is intended to be connected to a
> host serial port or so, for example if this SoC is to act as a modem
> or a fax machine, or if it is to drive one.
>
> Then they often have a few more UART blocks, usually identical, which
> only have RxD+TxD available, so they are "just" UARTs.
>
> To complicate things further, you may wonder what happened with
> the CTS/RTS (etc) signals from the other blocks. Usually they are there
> in the silicon but just routed to dead ends.
>
> To complicate it even further, usually all these pins are placed under
> pin control multiplexing, so in an actual electronic design, the
> system will mux out CTS/RTS (etc) from the fully featured RS232
> blocks and only use them as UARTs anyways.
>
> Then there are those who created real simple RxD/TxD-only UARTs
> ("yeah lets dump this RS232 legacy crap" / "yeah yeah")
> and then realized they want to drive modems ("oh crap, it seemed
> like a good idea at the time"). Then they usually take
> two GPIO pins for CTS/RTS and drive them as GPIOs using
> software and you have a cheap 4-line modem line. This is what
> drivers/tty/serial/serial_mctrl_gpio.c is for if you wondered.
>
> > I've tested this set and it seems to work ... except that something
> > is sadly broken with bluetooth support in 4.1-rc1 so I've only really
> > tested the GPS driver. I guess it is time to rebase to -rc3.
>
> You have a hardware taget I see. Which one?
GTA04 (http://www.gta04.org - openmoko successor).
3 uarts on OMAP3 are wired: one as RS-232 for console, one to bluetooth
half of a wifi/bluetooth module, and one to a GPS.
For the GPS, I just want to power on/off when the TTY is opened/closed,
but the power-on sequence is non-trivial as both "turn on" and
"turn-off' toggle the same line, so I need to be able to detect current
state.
For the bluetooth, the power is a (shared) regulator. As well as
power-on when the TTY is opened, I'd like regulator to be turned of
when I "hciconfig down" - even though the TTY is still open.
I did a patch a while ago which hooked in to hci_uart_{open,close} to
make this work, but it isn't a really good patch.
It would be nice to hide the TTY from user-space in the bluetooth case,
and have the "hciattach" happen in the kernel, but I think hciattach
does extra initialisation...
NeilBrown
Hi Linus,
Am 12.08.2015 um 01:20 schrieb NeilBrown <[email protected]>:
> On Fri, 7 Aug 2015 15:01:47 +0200 Linus Walleij
> <[email protected]> wrote:
>
>> Hi Neil,
>>
>> first, this is a *VERY* interesting and much needed patch series,
>> I intend to look closer at it, and if possible test it with some
>> (heh) board file device. Would be happy of you put me on CC for these.
>>
>> On Mon, May 11, 2015 at 3:56 AM, NeilBrown <[email protected]> wrote:
>>
>>> When a device is connected to a UART via RS-232 (or similar), there
>>> is a DTR line that can be used for power management, and other "modem
>>> control" lines.
>>>
>>> On an embedded board, it is very likely that there is no "DTR", and
>>> any power management need to be done using some completely separate
>>> mechanism.
>>>
>>> So these "slaves" are really just for devices permanently attached to
>>> UARTs without a full "RS-232" (or similar) connection. The driver
>>> does all the extra control beyond Tx/Rx.
>>
>> What is usually happening (and I have seen it in a few places) is that
>> the SoC has *one* fully featured RS232 with CTS/RTS and even
>> DTS,DCD,RI and other esoterica, which is intended to be connected to a
>> host serial port or so, for example if this SoC is to act as a modem
>> or a fax machine, or if it is to drive one.
>>
>> Then they often have a few more UART blocks, usually identical, which
>> only have RxD+TxD available, so they are "just" UARTs.
>>
>> To complicate things further, you may wonder what happened with
>> the CTS/RTS (etc) signals from the other blocks. Usually they are there
>> in the silicon but just routed to dead ends.
>>
>> To complicate it even further, usually all these pins are placed under
>> pin control multiplexing, so in an actual electronic design, the
>> system will mux out CTS/RTS (etc) from the fully featured RS232
>> blocks and only use them as UARTs anyways.
>>
>> Then there are those who created real simple RxD/TxD-only UARTs
>> ("yeah lets dump this RS232 legacy crap" / "yeah yeah")
>> and then realized they want to drive modems ("oh crap, it seemed
>> like a good idea at the time"). Then they usually take
>> two GPIO pins for CTS/RTS and drive them as GPIOs using
>> software and you have a cheap 4-line modem line. This is what
>> drivers/tty/serial/serial_mctrl_gpio.c is for if you wondered.
>>
>>> I've tested this set and it seems to work ... except that something
>>> is sadly broken with bluetooth support in 4.1-rc1 so I've only really
>>> tested the GPS driver. I guess it is time to rebase to -rc3.
>>
>> You have a hardware taget I see. Which one?
>
> GTA04 (http://www.gta04.org - openmoko successor).
>
> 3 uarts on OMAP3 are wired: one as RS-232 for console, one to bluetooth
> half of a wifi/bluetooth module, and one to a GPS.
>
> For the GPS, I just want to power on/off when the TTY is opened/closed,
> but the power-on sequence is non-trivial as both "turn on" and
> "turn-off' toggle the same line, so I need to be able to detect current
> state.
>
> For the bluetooth, the power is a (shared) regulator. As well as
> power-on when the TTY is opened, I'd like regulator to be turned of
> when I "hciconfig down" - even though the TTY is still open.
> I did a patch a while ago which hooked in to hci_uart_{open,close} to
> make this work, but it isn't a really good patch.
>
> It would be nice to hide the TTY from user-space in the bluetooth case,
> and have the "hciattach" happen in the kernel, but I think hciattach
> does extra initialisation...
>
> NeilBrown
we (the developers of the hardware) have proposed an alternative
approach to Neil?s implementation - for the same device and solving
the same problem (notifying tty open/close and uart activity to the
slave device driver), but differently.
See:
https://lkml.org/lkml/2015/6/28/91
Discussion has not yet settled on which approach is better. So your
opinion of comparing both is welcome.
BR,
Nikolaus
Hi!
> we (the developers of the hardware) have proposed an alternative
> approach to Neil’s implementation - for the same device and solving
> the same problem (notifying tty open/close and uart activity to the
> slave device driver), but differently.
>
> See:
> https://lkml.org/lkml/2015/6/28/91
>
> Discussion has not yet settled on which approach is better. So your
> opinion of comparing both is welcome.
Actually, yes, discussion has settled, agreeing that phandle reference
for the uart is a bad idea, Nikolaus just refuses to listen to anyone,
asking "device tree maintainer opinion", and then just simply ignoring
it when he does not like it, and then making promises he did not keep.
Please don't stall patches just because of that.
Best regards,
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Hi Pavel,
Am 28.08.2015 um 09:02 schrieb Pavel Machek <[email protected]>:
> Hi!
>
>> we (the developers of the hardware) have proposed an alternative
>> approach to Neil?s implementation - for the same device and solving
>> the same problem (notifying tty open/close and uart activity to the
>> slave device driver), but differently.
>>
>> See:
>> https://lkml.org/lkml/2015/6/28/91
>>
>> Discussion has not yet settled on which approach is better. So your
>> opinion of comparing both is welcome.
>
> Actually, yes, discussion has settled, agreeing that phandle reference
> for the uart is a bad idea, Nikolaus just refuses to listen to anyone,
no, I only refuse to listen to you.
You are neither maintainer for any subsystem that is involved nor have
you contributed technical arguments pro/con and it appears to me that
you refuse to listen to my argumentation.
> asking "device tree maintainer opinion", and then just simply ignoring
> it when he does not like it, and then making promises he did not keep.
Which promises did I not keep? Please be specific, instead of insulting.
I bring up this alternative again, since I get the impression that most readers
are simply not aware of *both* alternative proposals.
> Please don't stall patches just because of that.
Please provide better arguments and don?t spread FUD.
Please have a look into our RFC implementation and study it carefully
to learn why it is the better (IMHO more flexible, easier to maintain, more
modular) approach. Even if you don?t like phandles.
>
> Best regards,
> Pavel
> --
> (english) http://www.livejournal.com/~pavelmachek
> (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
BR,
Nikolaus
> > asking "device tree maintainer opinion", and then just simply ignoring
> > it when he does not like it, and then making promises he did not keep.
>
> Which promises did I not keep? Please be specific, instead of
You promised to shut up.
> Please have a look into our RFC implementation and study it carefully
> to learn why it is the better (IMHO more flexible, easier to maintain, more
> modular) approach. Even if you don’t like phandles.
It was also NAKed by device tree maintainers.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Hi all,
I will reverse a part of this e-mail to make replying easier.
On Fri, Aug 28, 2015 at 1:04 PM, Pavel Machek <[email protected]> wrote:
>> Please have a look into our RFC implementation and study it carefully
>> to learn why it is the better (IMHO more flexible, easier to maintain, more
>> modular) approach. Even if you don’t like phandles.
>
> It was also NAKed by device tree maintainers.
This may be true, I can't recall those specific mails...
> You promised to shut up.
But this is uncalled for.
IIRC there was, indeed, a long technical discussion about how to
connect the Bluetooth chip to the 'UART'.
I've seen Neil patches, and I've seen patches by Nikolaus.
Both appear to have their problems. I think it's fair to:
- Listen carefully to all arguments.
- Have a benevolent dictator, which we have, look into the problem
- Only 'shut up' after a thorough technical analysis has been made
about both approaches to the problem.
Both may have their merit in a number of ways. Don't just close your
eyes, put your fingers in your ears and say 'I can't hear you!'. We
are all adults, or at least, we should be in behaviour. The Linux
system has grown so much _beacuse_ of people working together. Calling
each other names and exiling them will _not_ solve the problem.
Christ van Willegen
--
09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0