296 lines
8.7 KiB
C
296 lines
8.7 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: ERROR: 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: ERROR: 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;
|
|||
|
}
|