332 lines
9.0 KiB
C
332 lines
9.0 KiB
C
/*
|
||
* Copyright : (C) 2022 Phytium Information Technology, Inc.
|
||
* All Rights Reserved.
|
||
*
|
||
* This program is OPEN SOURCE software: you can redistribute it and/or modify it
|
||
* under the terms of the Phytium Public License as published by the Phytium Technology Co.,Ltd,
|
||
* either version 1.0 of the License, or (at your option) any later version.
|
||
*
|
||
* 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 Phytium Public License for more details.
|
||
*
|
||
*
|
||
* FilePath: fusb_generic_hub.c
|
||
* Date: 2022-02-11 13:33:11
|
||
* LastEditTime: 2022-02-18 09:19:27
|
||
* Description: This files is for implmentation of generic hub function
|
||
*
|
||
* Modify History:
|
||
* Ver Who Date Changes
|
||
* ----- ------ -------- --------------------------------------
|
||
* 1.0 zhugengyu 2022/2/7 init commit
|
||
*/
|
||
|
||
#include "fsleep.h"
|
||
#include "fdebug.h"
|
||
#include "fusb.h"
|
||
#include "fusb_generic_hub.h"
|
||
|
||
#define FUSB_DEBUG_TAG "FUSB_HUB"
|
||
#define FUSB_ERROR(format, ...) FT_DEBUG_PRINT_E(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
#define FUSB_WARN(format, ...) FT_DEBUG_PRINT_W(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
#define FUSB_INFO(format, ...) FT_DEBUG_PRINT_I(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
#define FUSB_DEBUG(format, ...) FT_DEBUG_PRINT_D(FUSB_DEBUG_TAG, format, ##__VA_ARGS__)
|
||
|
||
void FUsbGenericHubDestory(FUsbDev *const dev)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
FUsb *instace = dev->controller->usb;
|
||
if (!hub)
|
||
{
|
||
return;
|
||
}
|
||
|
||
/* First, detach all devices behind this hub */
|
||
int port;
|
||
for (port = 1; port <= hub->num_ports; ++port)
|
||
{
|
||
if (hub->ports[port] >= 0)
|
||
{
|
||
FUSB_INFO("Generic hub: Detachment at port %d ", port);
|
||
FUsbDetachDev(dev->controller, hub->ports[port]);
|
||
hub->ports[port] = FUSB_NO_DEV_ADDR;
|
||
}
|
||
}
|
||
|
||
/* Disable all ports */
|
||
if (hub->ops->disable_port)
|
||
{
|
||
for (port = 1; port <= hub->num_ports; ++port)
|
||
{
|
||
hub->ops->disable_port(dev, port);
|
||
}
|
||
}
|
||
|
||
FUSB_FREE(instace, hub->ports); // free(hub->ports);
|
||
FUSB_FREE(instace, hub); // free(hub);
|
||
}
|
||
|
||
static int FUsbGenericHubDebounce(FUsbDev *const dev, const int port)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
|
||
const int step_ms = 1; /* linux uses 25ms, we're busy anyway */
|
||
const int at_least_ms = 100; /* 100ms as in usb20 spec 9.1.2 */
|
||
const int timeout_ms = 1500; /* linux uses this value */
|
||
|
||
int total_ms = 0;
|
||
int stable_ms = 0;
|
||
while (stable_ms < at_least_ms && total_ms < timeout_ms)
|
||
{
|
||
fsleep_millisec(step_ms);
|
||
|
||
const int changed = hub->ops->port_status_changed(dev, port);
|
||
const int connected = hub->ops->port_connected(dev, port);
|
||
if (changed < 0 || connected < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (!changed && connected)
|
||
{
|
||
stable_ms += step_ms;
|
||
}
|
||
else
|
||
{
|
||
FUSB_INFO("Generic hub: Unstable connection at %d ",
|
||
port);
|
||
stable_ms = 0;
|
||
}
|
||
total_ms += step_ms;
|
||
}
|
||
if (total_ms >= timeout_ms)
|
||
{
|
||
FUSB_INFO("Generic hub: Debouncing timed out at %d ", port);
|
||
}
|
||
return 0; /* ignore timeouts, try to always go on */
|
||
}
|
||
|
||
int FUsbGenericHubWaitForPort(FUsbDev *const dev, const int port,
|
||
const int wait_for,
|
||
int (*const port_op)(FUsbDev *, int),
|
||
int timeout_steps, const int step_us)
|
||
{
|
||
int state;
|
||
do
|
||
{
|
||
state = port_op(dev, port);
|
||
if (state < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
else if (!!state == wait_for)
|
||
{
|
||
return timeout_steps;
|
||
}
|
||
fsleep_microsec(step_us);
|
||
--timeout_steps;
|
||
}
|
||
while (timeout_steps);
|
||
|
||
return 0;
|
||
}
|
||
|
||
int FUsbGenericHubResetPort(FUsbDev *const dev, const int port)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
|
||
if (hub->ops->start_port_reset(dev, port) < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
/* wait for 10ms (usb20 spec 11.5.1.5: reset should take 10 to 20ms) */
|
||
fsleep_millisec(10);
|
||
|
||
/* now wait 12ms for the hub to finish the reset */
|
||
const int ret = FUsbGenericHubWaitForPort(
|
||
/* time out after 120 * 100us = 12ms */
|
||
dev, port, 0, hub->ops->port_in_reset, 120, 100);
|
||
if (ret < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
else if (!ret)
|
||
{
|
||
FUSB_INFO("Generic hub: Reset timed out at port %d ", port);
|
||
}
|
||
|
||
return 0; /* ignore timeouts, try to always go on */
|
||
}
|
||
|
||
static int FUsbGenericHubDetachDev(FUsbDev *const dev, const int port)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
|
||
FUsbDetachDev(dev->controller, hub->ports[port]);
|
||
hub->ports[port] = FUSB_NO_DEV_ADDR;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int FUsbGenericHubAttachDev(FUsbDev *const dev, const int port)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
|
||
if (FUsbGenericHubDebounce(dev, port) < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (hub->ops->reset_port)
|
||
{
|
||
if (hub->ops->reset_port(dev, port) < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (!hub->ops->port_connected(dev, port))
|
||
{
|
||
FUSB_INFO(
|
||
"Generic hub: Port %d disconnected after "
|
||
"reset. Possibly upgraded, rescan required. ",
|
||
port);
|
||
return 0;
|
||
}
|
||
|
||
/* after reset the port will be enabled automatically */
|
||
const int ret = FUsbGenericHubWaitForPort(
|
||
/* time out after 1,000 * 10us = 10ms */
|
||
dev, port, 1, hub->ops->port_enabled, 1000, 10);
|
||
if (ret < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
else if (!ret)
|
||
FUSB_INFO("Generic hub: Port %d still "
|
||
"disabled after 10ms ",
|
||
port);
|
||
}
|
||
|
||
const FUsbSpeed speed = hub->ops->port_speed(dev, port);
|
||
if (speed >= 0)
|
||
{
|
||
FUSB_DEBUG("Generic hub: Success at port %d ", port);
|
||
if (hub->ops->reset_port)
|
||
{
|
||
fsleep_millisec(10);
|
||
} /* Reset recovery time
|
||
(usb20 spec 7.1.7.5) */
|
||
hub->ports[port] = FUsbAttachDev(
|
||
dev->controller, dev->address, port, speed);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int FUsbGenericHubScanPort(FUsbDev *const dev, const int port)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
|
||
if (hub->ports[port] >= 0)
|
||
{
|
||
FUSB_INFO("Generic hub: Detachment at port %d ", port);
|
||
const int ret = FUsbGenericHubDetachDev(dev, port);
|
||
if (ret < 0)
|
||
{
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
if (hub->ops->port_connected(dev, port))
|
||
{
|
||
FUSB_INFO("Generic hub: Attachment at port %d ", port);
|
||
return FUsbGenericHubAttachDev(dev, port);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void FUsbGenericHubPoll(FUsbDev *const dev)
|
||
{
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
if (!hub)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (hub->ops->hub_status_changed &&
|
||
hub->ops->hub_status_changed(dev) != FUSB_CC_SUCCESS)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int port;
|
||
for (port = 1; port <= hub->num_ports; ++port)
|
||
{
|
||
const FUsbTransCode ret = hub->ops->port_status_changed(dev, port);
|
||
if (ret < 0)
|
||
{
|
||
FUSB_WARN("Transcode %d", ret);
|
||
return;
|
||
}
|
||
else if (ret == FUSB_CC_SUCCESS)
|
||
{
|
||
FUSB_INFO("Generic hub: Port change at %d ", port);
|
||
if (FUsbGenericHubScanPort(dev, port) < 0)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
int FUsbGenericHubInit(FUsbDev *const dev, const int num_ports,
|
||
const FUsbGenericHubOps *const ops)
|
||
{
|
||
int port;
|
||
FUsb *instance = dev->controller->usb;
|
||
|
||
dev->destroy = FUsbGenericHubDestory;
|
||
dev->poll = FUsbGenericHubPoll;
|
||
FASSERT(NULL == dev->data);
|
||
dev->data = FUSB_ALLOCATE(instance, sizeof(FUsbGenericHub), FUSB_DEFAULT_ALIGN);
|
||
if (NULL == dev->data)
|
||
{
|
||
FUSB_ERROR("Generic hub: Out of memory ");
|
||
return -1;
|
||
}
|
||
|
||
FUsbGenericHub *const hub = FUSB_GEN_HUB_GET(dev);
|
||
hub->num_ports = num_ports;
|
||
FASSERT(NULL == hub->ports);
|
||
hub->ports = FUSB_ALLOCATE(instance, sizeof(*hub->ports) * (num_ports + 1), FUSB_DEFAULT_ALIGN);
|
||
hub->ops = ops;
|
||
if (NULL == hub->ports)
|
||
{
|
||
FUSB_ERROR("Generic hub: Out of memory ");
|
||
FUSB_FREE(instance, dev->data);
|
||
dev->data = NULL;
|
||
return -1;
|
||
}
|
||
|
||
for (port = 1; port <= num_ports; ++port)
|
||
{
|
||
hub->ports[port] = FUSB_NO_DEV_ADDR;
|
||
}
|
||
|
||
/* Enable all ports */
|
||
if (ops->enable_port)
|
||
{
|
||
for (port = 1; port <= num_ports; ++port)
|
||
{
|
||
ops->enable_port(dev, port);
|
||
}
|
||
|
||
/* wait once for all ports */
|
||
fsleep_millisec(20);
|
||
}
|
||
|
||
return 0;
|
||
}
|