/* * 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("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"); } /* 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 (proc_drv_icusb_dir_entry == NULL) { 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 (proc_drv_icusb_dir_entry == NULL) { 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(); } }