/* * ICUSB - for MUSB Host Driver * * Copyright 2015 Mediatek Inc. * Marvin Lin * Arvin Wang * Vincent Fan * Bryant Lu * Yu-Chang Wang * Macpaul Lin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include /* * Version Information */ #define DRIVER_VERSION "" #define DRIVER_AUTHOR "" #define DRIVER_DESC "USB ICUSB DRIVER" #define DRIVER_LICENSE "GPL" MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE(DRIVER_LICENSE); #define ICCD_INTERFACE_CLASS 0x0B #define ICCD_CLASS_DESCRIPTOR_LENGTH (0x36) #include "usb.h" #include "musbfsh_icusb.h" struct usb_icusb { char name[128]; }; struct my_attr power_resume_time_neogo_attr = { .attr.name = "power_resume_time_neogo", .attr.mode = 0644, #ifdef MTK_ICUSB_POWER_AND_RESUME_TIME_NEOGO_SUPPORT .value = 1 #else .value = 0 #endif }; static struct my_attr my_attr_test = { .attr.name = "my_attr_test", .attr.mode = 0644, .value = 1 }; static struct attribute *myattr[] = { (struct attribute *)&my_attr_test, (struct attribute *)&power_resume_time_neogo_attr, (struct attribute *)&skip_session_req_attr, (struct attribute *)&skip_enable_session_attr, (struct attribute *)&skip_mac_init_attr, (struct attribute *)&resistor_control_attr, (struct attribute *)&hw_dbg_attr, (struct attribute *)&skip_port_pm_attr, NULL }; static struct IC_USB_CMD ic_cmd; unsigned int g_ic_usb_status = ((USB_PORT1_DISCONNECT_DONE) << USB_PORT1_STS_SHIFT); static struct sock *netlink_sock; static u_int g_pid; static struct proc_dir_entry *proc_drv_icusb_dir_entry; static void icusb_dump_data(char *buf, int len); static void set_icusb_phy_power_negotiation_fail(void); static void set_icusb_phy_power_negotiation_ok(void); static void set_icusb_data_of_interface_power_request(short data); static void icusb_resume_time_negotiation(struct usb_device *dev) { int ret; int retries = IC_USB_RETRIES_RESUME_TIME_NEGOTIATION; char resume_time_negotiation_data[IC_USB_LEN_RESUME_TIME_NEGOTIATION]; while (retries-- > 0) { MYDBG(""); ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), IC_USB_REQ_GET_INTERFACE_RESUME_TIME, IC_USB_REQ_TYPE_GET_INTERFACE_RESUME_TIME, IC_USB_WVALUE_RESUME_TIME_NEGOTIATION, IC_USB_WINDEX_RESUME_TIME_NEGOTIATION, resume_time_negotiation_data, IC_USB_LEN_RESUME_TIME_NEGOTIATION, USB_CTRL_GET_TIMEOUT); if (ret < 0) { MYDBG("ret : %d\n", ret); continue; } else { MYDBG(""); icusb_dump_data(resume_time_negotiation_data, IC_USB_LEN_RESUME_TIME_NEGOTIATION); break; } } } void icusb_power_negotiation(struct usb_device *dev) { int ret; int retries = IC_USB_RETRIES_POWER_NEGOTIATION; char get_power_negotiation_data[IC_USB_LEN_POWER_NEGOTIATION]; char set_power_negotiation_data[IC_USB_LEN_POWER_NEGOTIATION]; int power_negotiation_done = 0; enum PHY_VOLTAGE_TYPE phy_volt; while (retries-- > 0) { MYDBG(""); power_negotiation_done = 0; ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), IC_USB_REQ_GET_IFACE_POWER, IC_USB_REQ_TYPE_GET_IFACE_POWER, IC_USB_WVALUE_POWER_NEGOTIATION, IC_USB_WINDEX_POWER_NEGOTIATION, get_power_negotiation_data, IC_USB_LEN_POWER_NEGOTIATION, USB_CTRL_GET_TIMEOUT); if (ret < 0) { MYDBG("ret : %d\n", ret); continue; } else { MYDBG(""); icusb_dump_data(get_power_negotiation_data, IC_USB_LEN_POWER_NEGOTIATION); /* copy the prefer bit from get interface power */ set_power_negotiation_data[0] = (get_power_negotiation_data[0] & IC_USB_PREFER_CLASSB_ENABLE_BIT); /* set our current voltage */ phy_volt = get_usb11_phy_voltage(); if (phy_volt == VOL_33) set_power_negotiation_data[0] |= (char)IC_USB_CLASSB; else if (phy_volt == VOL_18) set_power_negotiation_data[0] |= (char)IC_USB_CLASSC; else MYDBG(""); /* set current */ if (set_power_negotiation_data[1] > IC_USB_CURRENT) { MYDBG(""); set_power_negotiation_data[1] = IC_USB_CURRENT; } else { MYDBG(""); set_power_negotiation_data[1] = get_power_negotiation_data[1]; } MYDBG("power_negotiation_data[0] : 0x%x", set_power_negotiation_data[0]); MYDBG("power_negotiation_data[1] : 0x%x", set_power_negotiation_data[1]); MYDBG("IC_USB_CURRENT :%d\n", IC_USB_CURRENT); ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), IC_USB_REQ_SET_IFACE_POWER, IC_USB_REQ_TYPE_SET_IFACE_POWER, IC_USB_WVALUE_POWER_NEGOTIATION, IC_USB_WINDEX_POWER_NEGOTIATION, set_power_negotiation_data, IC_USB_LEN_POWER_NEGOTIATION, USB_CTRL_SET_TIMEOUT); if (ret < 0) { MYDBG("ret : %d\n", ret); } else { MYDBG(""); power_negotiation_done = 1; break; } /* break; */ } } MYDBG("retries : %d\n", retries); if (!power_negotiation_done) { set_icusb_phy_power_negotiation_fail(); } else { set_icusb_data_of_interface_power_request( *((short *)get_power_negotiation_data)); set_icusb_phy_power_negotiation_ok(); } } void usb11_wait_disconnect_done(int value) { if (is_usb11_enabled()) { while (1) { unsigned int ic_usb_status = g_ic_usb_status; MYDBG("ic_usb_status : %x\n", ic_usb_status); ic_usb_status &= (USB_PORT1_STS_MSK << USB_PORT1_STS_SHIFT); MYDBG("ic_usb_status : %x\n", ic_usb_status); if (ic_usb_status == (USB_PORT1_DISCONNECT_DONE << USB_PORT1_STS_SHIFT)) { MYDBG("USB_PORT1_DISCONNECT_DONE\n"); break; } if (ic_usb_status == (USB_PORT1_DISCONNECTING << USB_PORT1_STS_SHIFT)) MYDBG("USB_PORT1_DISCONNECTING\n"); mdelay(10); } } else { MYDBG("usb11 is not enabled, skip\n"); MYDBG("usb11_wait_disconnect_done()\n"); } } int check_usb11_sts_disconnect_done(void) { unsigned int ic_usb_status = g_ic_usb_status; MYDBG("ic_usb_status : %x\n", ic_usb_status); ic_usb_status &= (USB_PORT1_STS_MSK << USB_PORT1_STS_SHIFT); MYDBG("ic_usb_status : %x\n", ic_usb_status); if (ic_usb_status == (USB_PORT1_DISCONNECT_DONE << USB_PORT1_STS_SHIFT)) { MYDBG("USB_PORT1_DISCONNECT_DONE got\n"); return 1; } else { return 0; } } void set_usb11_sts_connect(void) { MYDBG("..................."); g_ic_usb_status &= ~(USB_PORT1_STS_MSK << USB_PORT1_STS_SHIFT); g_ic_usb_status |= ((USB_PORT1_CONNECT) << USB_PORT1_STS_SHIFT); } void set_usb11_sts_disconnecting(void) { MYDBG("..................."); g_ic_usb_status &= ~(USB_PORT1_STS_MSK << USB_PORT1_STS_SHIFT); g_ic_usb_status |= ((USB_PORT1_DISCONNECTING) << USB_PORT1_STS_SHIFT); } void set_icusb_sts_disconnect_done(void) { MYDBG("..................."); g_ic_usb_status &= ~(USB_PORT1_STS_MSK << USB_PORT1_STS_SHIFT); g_ic_usb_status |= ((USB_PORT1_DISCONNECT_DONE) << USB_PORT1_STS_SHIFT); } void set_icusb_data_of_interface_power_request(short data) { MYDBG("..................."); g_ic_usb_status |= ((data) << PREFER_VOL_CLASS_SHIFT); } void reset_usb11_phy_power_negotiation_status(void) { MYDBG("..................."); g_ic_usb_status &= ~(PREFER_VOL_STS_MSK << PREFER_VOL_STS_SHIFT); g_ic_usb_status |= ((PREFER_VOL_NOT_INITED) << PREFER_VOL_STS_SHIFT); } void set_icusb_phy_power_negotiation_fail(void) { MYDBG("..................."); g_ic_usb_status &= ~(PREFER_VOL_STS_MSK << PREFER_VOL_STS_SHIFT); g_ic_usb_status |= ((PREFER_VOL_PWR_NEG_FAIL) << PREFER_VOL_STS_SHIFT); } void set_icusb_phy_power_negotiation_ok(void) { MYDBG("..................."); g_ic_usb_status &= ~(PREFER_VOL_STS_MSK << PREFER_VOL_STS_SHIFT); g_ic_usb_status |= ((PREFER_VOL_PWR_NEG_OK) << PREFER_VOL_STS_SHIFT); } void usb11_phy_prefer_3v_status_check(void) { unsigned int ic_usb_status = g_ic_usb_status; MYDBG("ic_usb_status : %x\n", ic_usb_status); ic_usb_status &= (PREFER_VOL_STS_MSK << PREFER_VOL_STS_SHIFT); MYDBG("ic_usb_status : %x\n", ic_usb_status); } void icusb_dump_data(char *buf, int len) { int i; for (i = 0; i < len; i++) MYDBG("data[%d]: %x\n", i, buf[i]); } int usb11_init_phy_by_voltage(enum PHY_VOLTAGE_TYPE phy_volt) { musbfsh_init_phy_by_voltage(phy_volt); return 0; } int usb11_session_control(enum SESSION_CONTROL_ACTION action) { if (action == START_SESSION) musbfsh_start_session(); else if (action == STOP_SESSION) { /* musbfsh_stop_session(); */ if (!is_usb11_enabled()) { mt65xx_usb11_mac_reset_and_phy_stress_set(); } else { MYDBG("usb11 has been enabled, skip"); MYDBG("mt65xx_usb11_mac_reset_and_phy_stress_set()\n"); } } else MYDBG("unknown action\n"); return 0; } static void udp_reply(int pid, int seq, void *payload) { struct sk_buff *skb; struct nlmsghdr *nlh; int size = strlen(payload) + 1; int len = NLMSG_SPACE(size); void *data; int ret; skb = alloc_skb(len, GFP_ATOMIC); if (!skb) return; /* 3.10 specific */ nlh = __nlmsg_put(skb, pid, seq, 0, size, 0); nlh->nlmsg_flags = 0; data = NLMSG_DATA(nlh); memcpy(data, payload, size); /* 3.10 specific */ NETLINK_CB(skb).portid = 0; /* from kernel */ NETLINK_CB(skb).dst_group = 0; /* unicast */ ret = netlink_unicast(netlink_sock, skb, pid, MSG_DONTWAIT); if (ret < 0) MYDBG("send failed\n"); return; #if 0 nlmsg_failure: /* Used by NLMSG_PUT */ if (skb) kfree_skb(skb); #endif } /* Receive messages from netlink socket. */ static void udp_receive(struct sk_buff *skb) { kuid_t uid, u_int seq; void *data; struct nlmsghdr *nlh; char reply_data[16]; MYDBG(""); nlh = (struct nlmsghdr *)skb->data; /* global here */ g_pid = NETLINK_CREDS(skb)->pid; uid = NETLINK_CREDS(skb)->uid; seq = nlh->nlmsg_seq; data = NLMSG_DATA(nlh); MYDBG("recv skb from user space pid:%d seq:%d\n", g_pid, seq); MYDBG("data is :%s\n", (char *)data); sprintf(reply_data, "%d", g_pid); udp_reply(g_pid, 0, reply_data); } struct netlink_kernel_cfg nl_cfg = { .input = udp_receive, }; static ssize_t default_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct my_attr *a = container_of(attr, struct my_attr, attr); return scnprintf(buf, PAGE_SIZE, "%d\n", a->value); } static ssize_t default_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct my_attr *a = container_of(attr, struct my_attr, attr); int result = kstrtoul(buf, 0, (unsigned long *)&a->value); if (result) return sizeof(int); else return -EINVAL; } static const struct sysfs_ops myops = { .show = default_show, .store = default_store, }; static struct kobj_type mytype = { .sysfs_ops = &myops, .default_attrs = myattr, }; struct kobject *mykobj; void create_icusb_sysfs_attr(void) { int err = -1; mykobj = kzalloc(sizeof(*mykobj), GFP_KERNEL); if (mykobj) { MYDBG(""); kobject_init(mykobj, &mytype); if (kobject_add(mykobj, NULL, "%s", "icusb_attr")) { err = -1; MYDBG("Sysfs creation failed\n"); kobject_put(mykobj); mykobj = NULL; } err = 0; } return; } static ssize_t musbfsh_ic_tmp_proc_entry(struct file *file_ptr, const char __user *user_buffer, size_t count, loff_t *position) { char cmd[64]; int ret = copy_from_user((char *)&cmd, user_buffer, count); if (ret != 0) return -EFAULT; if (cmd[0] == '4') { MYDBG(""); udp_reply(g_pid, 0, "HELLO, SS7_IC_USB!!!"); } MYDBG(""); return count; } const struct file_operations musbfsh_ic_tmp_proc_fops = { .write = musbfsh_ic_tmp_proc_entry }; void create_ic_tmp_entry(void) { struct proc_dir_entry *pr_entry; if (NULL == proc_drv_icusb_dir_entry) { MYDBG("[%s]: /proc/driver/icusb not exist\n", __func__); return; } pr_entry = proc_create("IC_TMP_ENTRY", 0660, proc_drv_icusb_dir_entry, &musbfsh_ic_tmp_proc_fops); if (pr_entry) MYDBG("add /proc/IC_TMP_ENTRY ok\n"); else MYDBG("add /proc/IC_TMP_ENTRY fail\n"); } static ssize_t musbfsh_ic_usb_cmd_proc_status_read(struct file *file_ptr, char __user *user_buffer, size_t count, loff_t *position) { int len; MYDBG(""); if (copy_to_user(user_buffer, &g_ic_usb_status, sizeof(g_ic_usb_status)) != 0) return -EFAULT; /* *position += count; */ len = sizeof(g_ic_usb_status); return len; } ssize_t musbfsh_ic_usb_cmd_proc_entry(struct file *file_ptr, const char __user *user_buffer, size_t count, loff_t *position) { int ret = copy_from_user((char *)&ic_cmd, user_buffer, count); if (ret != 0) return -EFAULT; MYDBG("type : %x, length : %x, data[0] : %x\n", ic_cmd.type, ic_cmd.length, ic_cmd.data[0]); switch (ic_cmd.type) { case USB11_SESSION_CONTROL: MYDBG(""); usb11_session_control(ic_cmd.data[0]); break; case USB11_INIT_PHY_BY_VOLTAGE: MYDBG(""); usb11_init_phy_by_voltage(ic_cmd.data[0]); break; case USB11_WAIT_DISCONNECT_DONE: MYDBG(""); usb11_wait_disconnect_done(ic_cmd.data[0]); break; /*--- special purpose ---*/ case 's': MYDBG("create sysfs\n"); create_icusb_sysfs_attr(); break; case 't': MYDBG("create tmp proc\n"); create_ic_tmp_entry(); break; } return count; } static const struct file_operations musbfsh_ic_usb_cmd_proc_fops = { .read = musbfsh_ic_usb_cmd_proc_status_read, .write = musbfsh_ic_usb_cmd_proc_entry }; void create_ic_usb_cmd_proc_entry(void) { struct proc_dir_entry *prEntry; MYDBG(""); proc_drv_icusb_dir_entry = proc_mkdir("driver/icusb", NULL); if (NULL == proc_drv_icusb_dir_entry) { MYDBG("[%s]: mkdir /proc/driver/icusb failed\n", __func__); return; } prEntry = proc_create("IC_USB_CMD_ENTRY", 0660, proc_drv_icusb_dir_entry, &musbfsh_ic_usb_cmd_proc_fops); if (prEntry) { MYDBG("add IC_USB_CMD_ENTRY ok\n"); netlink_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &nl_cfg); } else { MYDBG("add IC_USB_CMD_ENTRY fail\n"); } } void set_icusb_phy_power_negotiation(struct usb_device *udev) { if (power_resume_time_neogo_attr.value) { icusb_power_negotiation(udev); icusb_resume_time_negotiation(udev); } else { set_icusb_phy_power_negotiation_ok(); } } #if 0 static int usb_icusb_probe(struct usb_interface *iface, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(iface); struct usb_host_interface *interface; struct usb_icusb *icusb; interface = iface->altsetting; pr_debug(" extralen = %d\n", interface->extralen); if (interface->extralen < ICCD_CLASS_DESCRIPTOR_LENGTH) return -ENODEV; icusb = kzalloc(sizeof(struct usb_icusb), GFP_KERNEL); if (dev->manufacturer) strlcpy(icusb->name, dev->manufacturer, sizeof(icusb->name)); if (dev->product) { if (dev->manufacturer) strlcat(icusb->name, " ", sizeof(icusb->name)); strlcat(icusb->name, dev->product, sizeof(icusb->name)); } if (!strlen(icusb->name)) snprintf(icusb->name, sizeof(icusb->name), "USB ICUSB = %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); pr_debug("icusb_DRIVER = %s\n", icusb->name); if (power_resume_time_neogo_attr.value) { icusb_power_negotiation(dev); icusb_resume_time_negotiation(dev); } else { set_icusb_phy_power_negotiation_ok(); } /* usb_set_intfdata(iface, icusb); */ return -ENODEV; } static void usb_icusb_disconnect(struct usb_interface *intf) { struct usb_icusb *icusb = usb_get_intfdata(intf); pr_debug("usb_icusb_disconnect\n"); if (!check_usb11_sts_disconnect_done()) set_usb11_sts_disconnecting(); mt65xx_usb11_mac_reset_and_phy_stress_set(); /* usb_set_intfdata(intf, NULL); */ if (icusb != NULL) kfree(icusb); set_icusb_sts_disconnect_done(); } static int usb_icusb_suspend(struct usb_interface *intf, pm_message_t message) { pr_debug("usb_icusb_suspend\n"); return 0; } static int usb_icusb_resume(struct usb_interface *intf) { pr_debug("usb_icusb_resume\n"); return 0; } static int usb_icusb_pre_reset(struct usb_interface *intf) { pr_debug("usb_icusb_pre_reset\n"); return 0; } static int usb_icusb_post_reset(struct usb_interface *intf) { pr_debug("usb_icusb_post_reset\n"); return 0; } static int usb_icusb_reset_resume(struct usb_interface *intf) { pr_debug("usb_icusb_reset_resume\n"); return 0; } static struct usb_device_id usb_icusb_id_table[] = { {.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, .bInterfaceClass = ICCD_INTERFACE_CLASS}, {} /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, usb_icusb_id_table); static struct usb_driver usb_icusb_driver = { .name = "usbicusb", .probe = usb_icusb_probe, .disconnect = usb_icusb_disconnect, .suspend = usb_icusb_suspend, .resume = usb_icusb_resume, .pre_reset = usb_icusb_pre_reset, .post_reset = usb_icusb_post_reset, .reset_resume = usb_icusb_reset_resume, .id_table = usb_icusb_id_table, }; static int __init icusb_init(void) { int rc; pr_debug("icusb_init\n"); rc = usb_register(&usb_icusb_driver); if (rc != 0) goto err_register; pr_debug("icusb_register done\n"); /* create_icusb_cmd_proc_entry(); */ /* 3.10 specific */ netlink_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &nl_cfg); err_register: return rc; } static void __exit icusb_exit(void) { usb_deregister(&usb_icusb_driver); } module_init(icusb_init); module_exit(icusb_exit); #endif