[RFC 3/3] misc: Add w2cbw003 (wifi/bluetooth) power control driver

2017-05-21 Thread H. Nikolaus Schaller
Add driver for Wi2Wi W2CBW003 WiFi and Bluetooth module
where the Bluetooth interface is connected through uart.

Uses the new serdev API to glue with tty and turn on/off the
module if the tty port (/dev/ttyBTn) is opened.

Note that this is only for the Bluetooth side. The WLAN
(libertas) sdio driver should be abe to enable the same vdd
regulator. So that the device is powered on if either WiFi or
Bluetooth is activated (or both) and powered down if neither
is in use.

Signed-off-by: H. Nikolaus Schaller 
---
 drivers/misc/Kconfig  |   7 +
 drivers/misc/Makefile |   1 +
 drivers/misc/w2cbw003-bluetooth.c | 390 ++
 3 files changed, 398 insertions(+)
 create mode 100644 drivers/misc/w2cbw003-bluetooth.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7f97ef8fb6cd..90ce23bef77b 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -525,4 +525,11 @@ config W2SG0004_DEBUG
help
  Enable driver debugging mode of W2SG0004 GPS.
 
+config W2CBW003
+   tristate "W2CBW003 power on/off control"
+   depends on GPIOLIB
+   help
+ Enable on/off power control of W2CBW003 if the /dev/tty$n for
+ Bluetooth is opened.
+
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 0e88e06e5ee0..f6d3f096c5e0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -51,6 +51,7 @@ obj-y += mic/
 obj-$(CONFIG_GENWQE)   += genwqe/
 obj-$(CONFIG_ECHO) += echo/
 obj-$(CONFIG_W2SG0004) += w2sg0004.o
+obj-$(CONFIG_W2CBW003) += w2cbw003-bluetooth.o
 obj-$(CONFIG_VEXPRESS_SYSCFG)  += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE) += cxl/
 obj-$(CONFIG_ASPEED_LPC_CTRL)  += aspeed-lpc-ctrl.o
diff --git a/drivers/misc/w2cbw003-bluetooth.c 
b/drivers/misc/w2cbw003-bluetooth.c
new file mode 100644
index ..3bcfd51bbf09
--- /dev/null
+++ b/drivers/misc/w2cbw003-bluetooth.c
@@ -0,0 +1,390 @@
+/*
+ * w2scbw003.c
+ * Driver for power controlling the w2cbw003 WiFi/Bluetooth chip.
+ *
+ * powers on the chip if the tty port associated/connected
+ * to the bluetooth subsystem is opened (e.g. hciattach /dev/ttyBT0)
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#undef pr_debug
+#define pr_debug printk
+
+struct w2cbw_data {
+   struct  regulator *vdd_regulator;
+   struct  serdev_device *uart;/* the uart connected to the 
chip */
+   struct  tty_driver *tty_drv;/* this is the user space tty */
+   struct  device *dev;/* returned by 
tty_port_register_device() */
+   struct  tty_port port;
+   int open_count; /* how often we were opened */
+};
+
+static struct w2cbw_data *w2cbw_by_minor[1];
+
+static void w2cbw_set_power(void *pdata, int val)
+{
+   struct w2cbw_data *data = (struct w2cbw_data *) pdata;
+
+   pr_debug("%s(...,%x)\n", __func__, val);
+
+   if (val != 0)
+   WARN_ON(regulator_enable(data->vdd_regulator));
+   else
+   regulator_disable(data->vdd_regulator);
+}
+
+/* called each time data is received by the UART (i.e. sent by the w2cbw003) */
+
+static int w2cbw_uart_receive_buf(struct serdev_device *serdev, const unsigned 
char *rxdata,
+   size_t count)
+{
+   struct w2cbw_data *data = (struct w2cbw_data *) 
serdev_device_get_drvdata(serdev);
+
+// pr_debug("%s() characters\n", __func__, count);
+
+   if (data->open_count > 0) {
+   int n;
+
+   pr_debug("w2cbw003: uart->tty %d chars\n", count);
+   n = tty_insert_flip_string(>port, rxdata, count); /* pass 
to user-space */
+   if (n != count)
+   pr_debug("w2cbw003: did loose %d characters\n", count - 
n);
+   tty_flip_buffer_push(>port);
+   return n;
+   }
+
+   /* nobody is listenig - ignore incoming data */
+   return count;
+}
+
+static struct serdev_device_ops serdev_ops = {
+   .receive_buf = w2cbw_uart_receive_buf,
+#if 0
+   .write_wakeup = w2cbw_uart_wakeup,
+#endif
+};
+
+static struct w2cbw_data *w2cbw_get_by_minor(unsigned int minor)
+{
+   return w2cbw_by_minor[minor];
+}
+
+static int w2cbw_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+   struct w2cbw_data *data;
+   int retval;
+
+   pr_debug("%s() tty = %p\n", __func__, tty);
+
+   data = w2cbw_get_by_minor(tty->index);
+   pr_debug("%s() data = %p\n", __func__, data);
+
+   if (!data)
+   return -ENODEV;
+
+   retval = tty_standard_install(driver, tty);
+   if (retval)
+   goto error_init_termios;
+
+   tty->driver_data = data;
+
+   return 0;
+
+error_init_termios:
+   tty_port_put(>port);
+   return retval;
+}
+
+static int w2cbw_tty_open(struct 

[RFC 3/3] misc: Add w2cbw003 (wifi/bluetooth) power control driver

2017-05-21 Thread H. Nikolaus Schaller
Add driver for Wi2Wi W2CBW003 WiFi and Bluetooth module
where the Bluetooth interface is connected through uart.

Uses the new serdev API to glue with tty and turn on/off the
module if the tty port (/dev/ttyBTn) is opened.

Note that this is only for the Bluetooth side. The WLAN
(libertas) sdio driver should be abe to enable the same vdd
regulator. So that the device is powered on if either WiFi or
Bluetooth is activated (or both) and powered down if neither
is in use.

Signed-off-by: H. Nikolaus Schaller 
---
 drivers/misc/Kconfig  |   7 +
 drivers/misc/Makefile |   1 +
 drivers/misc/w2cbw003-bluetooth.c | 390 ++
 3 files changed, 398 insertions(+)
 create mode 100644 drivers/misc/w2cbw003-bluetooth.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7f97ef8fb6cd..90ce23bef77b 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -525,4 +525,11 @@ config W2SG0004_DEBUG
help
  Enable driver debugging mode of W2SG0004 GPS.
 
+config W2CBW003
+   tristate "W2CBW003 power on/off control"
+   depends on GPIOLIB
+   help
+ Enable on/off power control of W2CBW003 if the /dev/tty$n for
+ Bluetooth is opened.
+
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 0e88e06e5ee0..f6d3f096c5e0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -51,6 +51,7 @@ obj-y += mic/
 obj-$(CONFIG_GENWQE)   += genwqe/
 obj-$(CONFIG_ECHO) += echo/
 obj-$(CONFIG_W2SG0004) += w2sg0004.o
+obj-$(CONFIG_W2CBW003) += w2cbw003-bluetooth.o
 obj-$(CONFIG_VEXPRESS_SYSCFG)  += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE) += cxl/
 obj-$(CONFIG_ASPEED_LPC_CTRL)  += aspeed-lpc-ctrl.o
diff --git a/drivers/misc/w2cbw003-bluetooth.c 
b/drivers/misc/w2cbw003-bluetooth.c
new file mode 100644
index ..3bcfd51bbf09
--- /dev/null
+++ b/drivers/misc/w2cbw003-bluetooth.c
@@ -0,0 +1,390 @@
+/*
+ * w2scbw003.c
+ * Driver for power controlling the w2cbw003 WiFi/Bluetooth chip.
+ *
+ * powers on the chip if the tty port associated/connected
+ * to the bluetooth subsystem is opened (e.g. hciattach /dev/ttyBT0)
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#undef pr_debug
+#define pr_debug printk
+
+struct w2cbw_data {
+   struct  regulator *vdd_regulator;
+   struct  serdev_device *uart;/* the uart connected to the 
chip */
+   struct  tty_driver *tty_drv;/* this is the user space tty */
+   struct  device *dev;/* returned by 
tty_port_register_device() */
+   struct  tty_port port;
+   int open_count; /* how often we were opened */
+};
+
+static struct w2cbw_data *w2cbw_by_minor[1];
+
+static void w2cbw_set_power(void *pdata, int val)
+{
+   struct w2cbw_data *data = (struct w2cbw_data *) pdata;
+
+   pr_debug("%s(...,%x)\n", __func__, val);
+
+   if (val != 0)
+   WARN_ON(regulator_enable(data->vdd_regulator));
+   else
+   regulator_disable(data->vdd_regulator);
+}
+
+/* called each time data is received by the UART (i.e. sent by the w2cbw003) */
+
+static int w2cbw_uart_receive_buf(struct serdev_device *serdev, const unsigned 
char *rxdata,
+   size_t count)
+{
+   struct w2cbw_data *data = (struct w2cbw_data *) 
serdev_device_get_drvdata(serdev);
+
+// pr_debug("%s() characters\n", __func__, count);
+
+   if (data->open_count > 0) {
+   int n;
+
+   pr_debug("w2cbw003: uart->tty %d chars\n", count);
+   n = tty_insert_flip_string(>port, rxdata, count); /* pass 
to user-space */
+   if (n != count)
+   pr_debug("w2cbw003: did loose %d characters\n", count - 
n);
+   tty_flip_buffer_push(>port);
+   return n;
+   }
+
+   /* nobody is listenig - ignore incoming data */
+   return count;
+}
+
+static struct serdev_device_ops serdev_ops = {
+   .receive_buf = w2cbw_uart_receive_buf,
+#if 0
+   .write_wakeup = w2cbw_uart_wakeup,
+#endif
+};
+
+static struct w2cbw_data *w2cbw_get_by_minor(unsigned int minor)
+{
+   return w2cbw_by_minor[minor];
+}
+
+static int w2cbw_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+   struct w2cbw_data *data;
+   int retval;
+
+   pr_debug("%s() tty = %p\n", __func__, tty);
+
+   data = w2cbw_get_by_minor(tty->index);
+   pr_debug("%s() data = %p\n", __func__, data);
+
+   if (!data)
+   return -ENODEV;
+
+   retval = tty_standard_install(driver, tty);
+   if (retval)
+   goto error_init_termios;
+
+   tty->driver_data = data;
+
+   return 0;
+
+error_init_termios:
+   tty_port_put(>port);
+   return retval;
+}
+
+static int w2cbw_tty_open(struct tty_struct *tty, struct