[PATCH] usb: misc: supports Apple Carplay driver

From: Chunfeng Yun
Date: Wed Mar 14 2018 - 02:08:41 EST


The driver is used to support Apple carplay feature by a debugfs
interface which can force the driver to send a USB Vendor Request
of "Apple Device to Host Mode Switch" to switch Apple Device
into host mode.

Signed-off-by: Chunfeng Yun <chunfeng.yun@xxxxxxxxxxxx>
---
drivers/usb/misc/Kconfig | 9 ++
drivers/usb/misc/Makefile | 1 +
drivers/usb/misc/carplay.c | 205 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 215 insertions(+)
create mode 100644 drivers/usb/misc/carplay.c

diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 68d2f2c..c010c95 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -275,3 +275,12 @@ config USB_CHAOSKEY

To compile this driver as a module, choose M here: the
module will be called chaoskey.
+
+config USB_CARPLAY
+ tristate "USB carplay driver support"
+ help
+ The driver is used to support Apple carplay feature.
+ It is realized by sending a USB Vendor Request of "Apple Device to
+ Host Mode Switch" to switch Apple Device into host mode.
+ When the users want to use carplay, they can force the driver to send
+ this Vendor Request by a debugfs interface.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 109f54f..94380e7 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -29,5 +29,6 @@ obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o
obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o
obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o

+obj-$(CONFIG_USB_CARPLAY) += carplay.o
obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o
diff --git a/drivers/usb/misc/carplay.c b/drivers/usb/misc/carplay.c
new file mode 100644
index 0000000..bfd41f3
--- /dev/null
+++ b/drivers/usb/misc/carplay.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * carplay.c - carplay usb driver
+ *
+ * Copyright (C) 2018 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@xxxxxxxxxxxx>
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+/*
+ * usage:
+ * The requirement for the platform using Carplay feature is that support
+ * the USB Dual Role Switch feature, and must have a USB-A receptacle
+ * that is capable of functioning in both USB Host and USB Device roles.
+ *
+ * 1. Apple iphone is enumerated as a usb device
+ * 2. switch Apple iphone to host mode, by, e.g.
+ * echo host > /sys/kernel/debug/usb/carplay.1-1/mode
+ * 3. switch the platform to device mode, but meanwhile should keep vbus alive;
+ * 4. use carplay feature after the platform is enumerated as a usb device;
+ * 5. when unplug usb cable, switch the platform back to host mode.
+ *
+ * step 2 is supported by this driver;
+ * step 1, 3, 4, 5 should be supported by the USB Dual-Role Controller Driver
+ * on the platform.
+ *
+ * For more detailed information, please refer to "Chapter 46. USB Role Switch"
+ * in MFI Accessroy Interface Specification.pdf
+ */
+
+#define CARPLAY_NAME "carplay"
+#define VENDER_REQ_DEV_TO_HOST 0x51
+
+struct usb_carplay {
+ struct usb_interface *intf;
+ struct usb_device *udev;
+ struct dentry *droot;
+ struct device *idev;
+ bool is_host;
+};
+
+static int carplay_switch_to_host(struct usb_carplay *ucp)
+{
+ struct usb_device *udev = ucp->udev;
+ int retval;
+
+ if (!ucp->udev)
+ return -ENODEV;
+
+ retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ VENDER_REQ_DEV_TO_HOST, USB_TYPE_VENDOR,
+ 1, 0, NULL, 0, USB_CTRL_GET_TIMEOUT);
+
+ dev_dbg(ucp->idev, "%s retval = %d\n", __func__, retval);
+
+ if (retval != 0) {
+ dev_err(ucp->idev, "%s fail retval = %d\n", __func__, retval);
+ return retval;
+ }
+ ucp->is_host = true;
+
+ return 0;
+}
+
+static int carplay_mode_show(struct seq_file *sf, void *unused)
+{
+ struct usb_carplay *ucp = sf->private;
+
+ seq_printf(sf, "current mode: %s\n(usage: echo host > mode)\n",
+ ucp->is_host ? "host" : "device");
+
+ return 0;
+}
+
+static int carplay_mode_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, carplay_mode_show, inode->i_private);
+}
+
+static ssize_t carplay_mode_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ struct seq_file *sf = file->private_data;
+ struct usb_carplay *ucp = sf->private;
+ char buf[16];
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ if (!strncmp(buf, "host", 4) && !ucp->is_host) {
+ carplay_switch_to_host(ucp);
+ } else {
+ dev_err(ucp->idev, "wrong setting\n");
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations carplay_mode_fops = {
+ .open = carplay_mode_open,
+ .write = carplay_mode_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry *carplay_debugfs_init(struct usb_carplay *ucp)
+{
+ struct dentry *root;
+ const char *udev_name = dev_name(&ucp->udev->dev);
+ char name[16];
+
+ snprintf(name, sizeof(name), "%s.%s", CARPLAY_NAME, udev_name);
+ root = debugfs_create_dir(name, usb_debug_root);
+ if (!root) {
+ dev_err(ucp->idev, "create debugfs root failed\n");
+ return root;
+ }
+ ucp->droot = root;
+
+ return debugfs_create_file("mode", 0664, root, ucp,
+ &carplay_mode_fops);
+}
+
+static void carplay_debugfs_exit(struct usb_carplay *ucp)
+{
+ debugfs_remove_recursive(ucp->droot);
+}
+
+static int carplay_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev;
+ struct usb_carplay *ucp;
+ struct dentry *de;
+
+ udev = interface_to_usbdev(intf);
+
+ ucp = kzalloc(sizeof(*ucp), GFP_KERNEL);
+ if (!ucp)
+ return -ENOMEM;
+
+ ucp->udev = usb_get_dev(udev);
+ ucp->intf = intf;
+ ucp->idev = &intf->dev;
+ usb_set_intfdata(intf, ucp);
+ ucp->is_host = false;
+
+ de = carplay_debugfs_init(ucp);
+ if (IS_ERR_OR_NULL(de)) {
+ usb_set_intfdata(intf, NULL);
+ usb_put_dev(ucp->udev);
+ kfree(ucp);
+ return -ENOMEM;
+ }
+
+ dev_info(ucp->idev, "carplay attached\n");
+ return 0;
+}
+
+static void carplay_disconnect(struct usb_interface *intf)
+{
+ struct usb_carplay *ucp = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+ usb_put_dev(ucp->udev);
+ carplay_debugfs_exit(ucp);
+ kfree(ucp);
+ dev_info(&intf->dev, "carplay disconnected\n");
+}
+
+static const struct usb_device_id carplay_id_table[] = {
+ /* generic EZ-USB FX2 controller (or development board) */
+ { USB_DEVICE(0x05ac, 0x12a8) },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, carplay_id_table);
+
+static struct usb_driver carplay_driver = {
+ .name = CARPLAY_NAME,
+ .id_table = carplay_id_table,
+ .probe = carplay_probe,
+ .disconnect = carplay_disconnect,
+};
+
+module_usb_driver(carplay_driver);
+
+MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@xxxxxxxxxxxx>");
+MODULE_DESCRIPTION("USB Carplay Driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5