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 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html