oleavr-rgl-a500-mini-linux-.../drivers/usb/sunxi_usb/manager/usb_hw_scan.c
Ole André Vadla Ravnås 169c65d57e Initial commit
2022-05-07 01:01:45 +02:00

617 lines
14 KiB
C
Executable file

/*
* drivers/usb/sunxi_usb/manager/usb_hw_scan.c
* (C) Copyright 2010-2015
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* javen, 2011-4-14, create this file
*
* usb detect module.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <asm/byteorder.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/unaligned.h>
#include <linux/gpio.h>
#include "../include/sunxi_usb_config.h"
#include "usb_manager.h"
#include "usb_hw_scan.h"
#include "usb_msg_center.h"
static struct usb_scan_info g_usb_scan_info;
#if defined(CONFIG_AW_AXP)
extern int axp_usb_det(void);
#endif
int device_insmod_delay = 0;
static void (*__usb_hw_scan) (struct usb_scan_info *);
#ifndef SUNXI_USB_FPGA
static __u32 get_pin_data(struct usb_gpio *usb_gpio)
{
return __gpio_get_value(usb_gpio->gpio_set.gpio.gpio);
}
#if defined(CONFIG_USB_G_ANDROID) || defined(CONFIG_USB_MASS_STORAGE)
static int get_usb_gadget_functions(void)
{
struct file *filep;
loff_t pos;
char buf[32] = {0};
filep = filp_open("/sys/class/android_usb/android0/functions", O_RDONLY, 0);
if (IS_ERR(filep)) {
return 0;
}
pos = 0;
vfs_read(filep, (char __user *)buf, 32, &pos);
filp_close(filep, NULL);
if (strlen(buf) == 0){
return 0;
}else{
return 1;
}
}
#endif
/*
* filter the PIO burr
* @usb_gpio: .
* @value: store the value to be read
*
* try 10 times, if value is the same, then consider no shake. return any one.
* if not the same, then the current read is invalid
*
* return: 0 - the valude is the same, valid, 1 - the value change, invalid.
*/
static __u32 PIODataIn_debounce(struct usb_gpio *usb_gpio, __u32 *value)
{
__u32 retry = 0;
__u32 time = 10;
__u32 temp1 = 0;
__u32 cnt = 0;
__u32 change = 0; /* if have shake */
/* try 10 times, if value is the same, then current read is valid; otherwise invalid */
if (usb_gpio->valid) {
retry = time;
while(retry--) {
temp1 = get_pin_data(usb_gpio);
if (temp1) {
cnt++;
}
}
/* 10 times, the value is all 0 or 1 */
if ((cnt == time)||(cnt == 0)) {
change = 0;
} else {
change = 1;
}
} else {
change = 1;
}
if (!change) {
*value = temp1;
}
DMSG_DBG_MANAGER("usb_gpio->valid = %x, cnt = %x, change= %d, temp1 = %x\n",
usb_gpio->valid, cnt, change, temp1);
return change;
}
static u32 get_id_state(struct usb_scan_info *info)
{
enum usb_id_state id_state = USB_DEVICE_MODE;
__u32 pin_data = 0;
if (info->cfg->port.id.valid) {
if (!PIODataIn_debounce(&info->cfg->port.id, &pin_data)) {
if (pin_data) {
id_state = USB_DEVICE_MODE;
} else {
id_state = USB_HOST_MODE;
}
info->id_old_state = id_state;
} else {
id_state = info->id_old_state;
}
}
return id_state;
}
static u32 get_detect_vbus_state(struct usb_scan_info *info)
{
enum usb_det_vbus_state det_vbus_state = USB_DET_VBUS_INVALID;
__u32 pin_data = 0;
if (info->cfg->port.detect_mode == USB_DETECT_MODE_THREAD) {
if (info->cfg->port.det_vbus_type == USB_DET_VBUS_TYPE_GIPO) {
if (info->cfg->port.det_vbus.valid) {
if (!PIODataIn_debounce(&info->cfg->port.det_vbus, &pin_data)) {
if (pin_data)
det_vbus_state = USB_DET_VBUS_VALID;
else
det_vbus_state = USB_DET_VBUS_INVALID;
info->det_vbus_old_state = det_vbus_state;
} else {
det_vbus_state = info->det_vbus_old_state;
}
}
} else if (info->cfg->port.det_vbus_type == USB_DET_VBUS_TYPE_AXP) {
#if defined(CONFIG_AW_AXP)
if (axp_usb_det())
det_vbus_state = USB_DET_VBUS_VALID;
else
det_vbus_state = USB_DET_VBUS_INVALID;
#endif
} else {
det_vbus_state = info->det_vbus_old_state;
}
} else if (info->cfg->port.detect_mode == USB_DETECT_MODE_INTR) {
det_vbus_state = USB_DET_VBUS_VALID;
} else {
DMSG_PANIC("ERR: get_detect_vbus_state, usb det mode isn't supported\n");
}
return det_vbus_state;
}
static u32 get_dp_dm_status(struct usb_scan_info *info)
{
u32 ret = 0;
u32 ret0 = 0;
u32 ret1 = 0;
u32 ret2 = 0;
ret0 = get_dp_dm_status_normal();
ret1 = get_dp_dm_status_normal();
ret2 = get_dp_dm_status_normal();
//continous 3 times, to avoid the voltage sudden changes
if ((ret0 == ret1) && (ret0 == ret2)) {
ret = ret0;
} else if (ret2 == 0x11) {
if (get_usb_role() == USB_ROLE_DEVICE) {
ret = 0x11;
DMSG_PANIC("ERR: dp/dm status is continuous(0x11)\n");
}
} else {
ret = ret2;
}
return ret;
}
#endif
static void do_vbus0_id0(struct usb_scan_info *info)
{
enum usb_role role = USB_ROLE_NULL;
role = get_usb_role();
device_insmod_delay = 0;
switch(role) {
case USB_ROLE_NULL:
/* delay for vbus is stably */
if (atomic_read(&thread_suspend_flag)) {
break;
}
if (info->host_insmod_delay < USB_SCAN_INSMOD_HOST_DRIVER_DELAY) {
info->host_insmod_delay++;
break;
}
info->host_insmod_delay = 0;
/* insmod usb host */
hw_insmod_usb_host();
break;
case USB_ROLE_HOST:
/* nothing to do */
break;
case USB_ROLE_DEVICE:
/* rmmod usb device */
if (atomic_read(&thread_suspend_flag)) {
break;
}
hw_rmmod_usb_device();
break;
default:
DMSG_PANIC("ERR: unkown usb role(%d)\n", role);
}
return;
}
static void do_vbus0_id1(struct usb_scan_info *info)
{
enum usb_role role = USB_ROLE_NULL;
role = get_usb_role();
device_insmod_delay = 0;
info->host_insmod_delay = 0;
switch(role) {
case USB_ROLE_NULL:
/* nothing to do */
break;
case USB_ROLE_HOST:
if (atomic_read(&thread_suspend_flag)) {
break;
}
hw_rmmod_usb_host();
break;
case USB_ROLE_DEVICE:
if (atomic_read(&thread_suspend_flag)) {
break;
}
hw_rmmod_usb_device();
break;
default:
DMSG_PANIC("ERR: unkown usb role(%d)\n", role);
}
return;
}
static void do_vbus1_id0(struct usb_scan_info *info)
{
enum usb_role role = USB_ROLE_NULL;
role = get_usb_role();
device_insmod_delay = 0;
switch(role) {
case USB_ROLE_NULL:
if (info->cfg->port.detect_mode == USB_DETECT_MODE_THREAD) {
if (atomic_read(&thread_suspend_flag))
break;
/* delay for vbus is stably */
if (info->host_insmod_delay < USB_SCAN_INSMOD_HOST_DRIVER_DELAY) {
info->host_insmod_delay++;
break;
}
info->host_insmod_delay = 0;
hw_insmod_usb_host();
} else if (info->cfg->port.detect_mode == USB_DETECT_MODE_INTR) {
hw_insmod_usb_host();
} else {
DMSG_PANIC("ERR: do_vbus1_id0, usb det mode isn't supported, role=%d\n",
role);
}
break;
case USB_ROLE_HOST:
/* nothing to do */
break;
case USB_ROLE_DEVICE:
if (info->cfg->port.detect_mode == USB_DETECT_MODE_THREAD) {
if (atomic_read(&thread_suspend_flag))
break;
hw_rmmod_usb_device();
} else if (info->cfg->port.detect_mode == USB_DETECT_MODE_INTR) {
hw_rmmod_usb_device();
} else {
DMSG_PANIC("ERR: do_vbus1_id0, usb det mode isn't supported, role=%d\n",
role);
}
break;
default:
DMSG_PANIC("ERR: unkown usb role(%d)\n", role);
}
return;
}
static void do_vbus1_id1(struct usb_scan_info *info)
{
enum usb_role role = USB_ROLE_NULL;
role = get_usb_role();
info->host_insmod_delay = 0;
switch(role) {
case USB_ROLE_NULL:
#ifndef SUNXI_USB_FPGA
if (info->cfg->port.detect_mode == USB_DETECT_MODE_THREAD) {
if (get_dp_dm_status(info) == 0x00) {
if (atomic_read(&thread_suspend_flag))
break;
/* delay for vbus is stably */
if (device_insmod_delay < USB_SCAN_INSMOD_DEVICE_DRIVER_DELAY) {
device_insmod_delay++;
break;
}
device_insmod_delay = 0;
#if defined(CONFIG_USB_G_ANDROID) || defined(CONFIG_USB_MASS_STORAGE)
if (get_usb_gadget_functions())
#endif
hw_insmod_usb_device();
}
} else if (info->cfg->port.detect_mode == USB_DETECT_MODE_INTR) {
hw_insmod_usb_device();
} else {
DMSG_PANIC("ERR: do_vbus1_id1, usb det mode isn't supported, role=%d\n",
role);
}
#else
hw_insmod_usb_device();
#endif
break;
case USB_ROLE_HOST:
if (info->cfg->port.detect_mode == USB_DETECT_MODE_THREAD) {
if (atomic_read(&thread_suspend_flag))
break;
hw_rmmod_usb_host();
} else if (info->cfg->port.detect_mode == USB_DETECT_MODE_INTR) {
hw_rmmod_usb_host();
} else {
DMSG_PANIC("ERR: do_vbus1_id1, usb det mode isn't supported, role=%d\n",
role);
}
break;
case USB_ROLE_DEVICE:
/* nothing to do */
break;
default:
DMSG_PANIC("ERR: unkown usb role(%d)\n", role);
}
return;
}
#ifdef SUNXI_USB_FPGA
static u32 usb_vbus_id_state = 1;
__u32 set_vbus_id_state(u32 state)
{
usb_vbus_id_state = state;
return 0;
}
static __u32 get_vbus_id_state(struct usb_scan_info *info)
{
return usb_vbus_id_state;
}
#else
static __u32 get_vbus_id_state(struct usb_scan_info *info)
{
u32 state = 0;
if (get_id_state(info) == USB_DEVICE_MODE) {
x_set_bit(state, 0);
}
if (get_detect_vbus_state(info) == USB_DET_VBUS_VALID) {
x_set_bit(state, 1);
}
return state;
}
#endif
static void vbus_id_hw_scan(struct usb_scan_info *info)
{
__u32 vbus_id_state = 0;
vbus_id_state = get_vbus_id_state(info);
if (usb_hw_scan_debug)
DMSG_INFO("Id=%d,role=%d\n", vbus_id_state, get_usb_role());
switch(vbus_id_state) {
case 0x00:
do_vbus0_id0(info);
break;
case 0x01:
do_vbus0_id1(info);
break;
case 0x02:
do_vbus1_id0(info);
break;
case 0x03:
do_vbus1_id1(info);
break;
default:
DMSG_PANIC("ERR: vbus_id_hw_scan: unkown vbus_id_state(0x%x)\n", vbus_id_state);
}
return;
}
static void null_hw_scan(struct usb_scan_info *info)
{
DMSG_DBG_MANAGER("null_hw_scan\n");
return;
}
void usb_hw_scan(struct usb_cfg *cfg)
{
__usb_hw_scan(&g_usb_scan_info);
}
__s32 usb_hw_scan_init(struct usb_cfg *cfg)
{
struct usb_scan_info *scan_info = &g_usb_scan_info;
struct usb_port_info *port_info = NULL;
__s32 ret = 0;
memset(scan_info, 0, sizeof(struct usb_scan_info));
device_insmod_delay = 0;
scan_info->cfg = cfg;
scan_info->id_old_state = USB_DEVICE_MODE;
scan_info->det_vbus_old_state = USB_DET_VBUS_INVALID;
port_info =&(cfg->port);
switch(port_info->port_type) {
case USB_PORT_TYPE_DEVICE:
__usb_hw_scan = null_hw_scan;
break;
case USB_PORT_TYPE_HOST:
__usb_hw_scan = null_hw_scan;
break;
case USB_PORT_TYPE_OTG:
#ifdef SUNXI_USB_FPGA
{
__usb_hw_scan = vbus_id_hw_scan;
}
#else
{
if (port_info->detect_mode == USB_DETECT_MODE_THREAD) {
__u32 need_det_vbus_req_gpio = 1;
if (port_info->det_vbus_type == USB_DET_VBUS_TYPE_GIPO) {
if ((port_info->id.valid == 0) || (port_info->det_vbus.valid == 0)) {
DMSG_PANIC("ERR: usb detect tpye is vbus/id, but id(%d)/vbus(%d) is invalid\n",
port_info->id.valid, port_info->det_vbus.valid);
ret = -1;
port_info->id.valid = 0;
port_info->det_vbus.valid = 0;
goto failed;
}
/* if id and vbus use the same pin, then need not to pull pio */
if (port_info->id.gpio_set.gpio.gpio == port_info->det_vbus.gpio_set.gpio.gpio) {
/* when id and det_vbus reuse, the det bus need not request gpio again */
need_det_vbus_req_gpio = 0;
}
}
/* request id gpio */
if (port_info->id.valid) {
ret = gpio_request(port_info->id.gpio_set.gpio.gpio, "otg_id");
if (ret != 0) {
DMSG_PANIC("ERR: id gpio_request failed\n");
ret = -1;
port_info->id.valid = 0;
port_info->det_vbus.valid = 0;
goto failed;
}
/* set config, input */
//sunxi_gpio_setcfg(port_info->id.gpio_set.gpio.gpio, 0);
gpio_direction_input(port_info->id.gpio_set.gpio.gpio);
__gpio_set_value(port_info->id.gpio_set.gpio.gpio, 1);
}
/* request det_vbus gpio */
if (port_info->det_vbus.valid && need_det_vbus_req_gpio) {
ret = gpio_request(port_info->det_vbus.gpio_set.gpio.gpio, "otg_det");
if (ret != 0) {
DMSG_PANIC("ERR: det_vbus gpio_request failed\n");
ret = -1;
port_info->det_vbus.valid = 0;
goto failed;
}
/* set config, input */
gpio_direction_input(port_info->det_vbus.gpio_set.gpio.gpio);
}
} else if (port_info->detect_mode == USB_DETECT_MODE_INTR) {
if (port_info->id.valid) {
long unsigned int config_set;
long unsigned int config_get;
char pin_name[SUNXI_PIN_NAME_MAX_LEN];
int pull = 1;
ret = gpio_request(port_info->id.gpio_set.gpio.gpio, "otg_id");
if (ret != 0) {
DMSG_PANIC("ERR: id gpio_request failed\n");
ret = -1;
port_info->id.valid = 0;
port_info->det_vbus.valid = 0;
goto failed;
}
sunxi_gpio_to_name(port_info->id.gpio_set.gpio.gpio, pin_name);
/* set id gpio pull up */
config_set = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, pull);
pin_config_set(SUNXI_PINCTRL, pin_name, config_set);
config_get = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, 0XFFFF);
pin_config_get(SUNXI_PINCTRL, pin_name, &config_get);
if (pull != SUNXI_PINCFG_UNPACK_VALUE(config_get)) {
DMSG_PANIC("ERR: id gpio pull up failed\n");
port_info->det_vbus.valid = 0;
goto failed;
}
}
}
__usb_hw_scan = vbus_id_hw_scan;
}
#endif
break;
default:
DMSG_PANIC("ERR: unkown port_type(%d)\n", cfg->port.port_type);
ret = -1;
goto failed;
}
return 0;
failed:
#ifndef SUNXI_USB_FPGA
if (port_info->id.valid) {
gpio_free(port_info->id.gpio_set.gpio.gpio);
}
if (port_info->det_vbus.valid) {
gpio_free(port_info->det_vbus.gpio_set.gpio.gpio);
}
#endif
__usb_hw_scan = null_hw_scan;
return ret;
}
#ifdef SUNXI_USB_FPGA
__s32 usb_hw_scan_exit(struct usb_cfg *cfg)
{
return 0;
}
#else
__s32 usb_hw_scan_exit(struct usb_cfg *cfg)
{
if (cfg->port.id.valid) {
gpio_free(cfg->port.id.gpio_set.gpio.gpio);
}
if (cfg->port.det_vbus.valid) {
gpio_free(cfg->port.det_vbus.gpio_set.gpio.gpio);
}
return 0;
}
#endif