blob: ff58813024d2b1740c3a9d9ce5d4a3897d9842af [file] [log] [blame]
/*
* ICUSB - for MUSB Host Driver
*
* Copyright 2015 Mediatek Inc.
* Marvin Lin <marvin.lin@mediatek.com>
* Arvin Wang <arvin.wang@mediatek.com>
* Vincent Fan <vincent.fan@mediatek.com>
* Bryant Lu <bryant.lu@mediatek.com>
* Yu-Chang Wang <yu-chang.wang@mediatek.com>
* Macpaul Lin <macpaul.lin@mediatek.com>
*
* 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 <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/skbuff.h>
/*
* 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");
}
/* 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();
}
}