From 990f4b85d7d9bb954a24ada7145947dec501fe3d Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Thu, 26 May 2022 13:59:16 +0300 Subject: thunderbolt: Silently ignore CLx enabling in case CLx is not supported We can't enable CLx if it is not supported either by the host or device, or by the USB4/TBT link (e.g. when an optical cable is used). We silently ignore CLx enabling in this case. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 9a3214fb5038..8ab3530dfa0d 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -575,6 +575,7 @@ static void tb_scan_port(struct tb_port *port) struct tb_cm *tcm = tb_priv(port->sw->tb); struct tb_port *upstream_port; struct tb_switch *sw; + int ret; if (tb_is_upstream_port(port)) return; @@ -663,7 +664,9 @@ static void tb_scan_port(struct tb_port *port) tb_switch_lane_bonding_enable(sw); /* Set the link configured */ tb_switch_configure_link(sw); - if (tb_switch_enable_clx(sw, TB_CL0S)) + /* Silently ignore CLx enabling in case CLx is not supported */ + ret = tb_switch_enable_clx(sw, TB_CL0S); + if (ret && ret != -EOPNOTSUPP) tb_sw_warn(sw, "failed to enable CLx on upstream port\n"); tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, @@ -1446,12 +1449,15 @@ static int tb_suspend_noirq(struct tb *tb) static void tb_restore_children(struct tb_switch *sw) { struct tb_port *port; + int ret; /* No need to restore if the router is already unplugged */ if (sw->is_unplugged) return; - if (tb_switch_enable_clx(sw, TB_CL0S)) + /* Silently ignore CLx re-enabling in case CLx is not supported */ + ret = tb_switch_enable_clx(sw, TB_CL0S); + if (ret && ret != -EOPNOTSUPP) tb_sw_warn(sw, "failed to re-enable CLx on upstream port\n"); /* -- cgit v1.2.3 From 418a5a3d6596f62424b24192b642d959d4c73d25 Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Thu, 26 May 2022 13:59:17 +0300 Subject: thunderbolt: CLx disable before system suspend only if previously enabled Disable CLx before system suspended only if previously was enabled. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 561e1d77240e..0395229a3562 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3134,8 +3134,10 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) * Actually only needed for Titan Ridge but for simplicity can be * done for USB4 device too as CLx is re-enabled at resume. */ - if (tb_switch_disable_clx(sw, TB_CL0S)) - tb_sw_warn(sw, "failed to disable CLx on upstream port\n"); + if (tb_switch_is_clx_enabled(sw)) { + if (tb_switch_disable_clx(sw, TB_CL0S)) + tb_sw_warn(sw, "failed to disable CLx on upstream port\n"); + } err = tb_plug_events_active(sw, false); if (err) -- cgit v1.2.3 From b4e08d5d08192699e68ffa796bd2c3ab58af5730 Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Thu, 26 May 2022 13:59:18 +0300 Subject: thunderbolt: Fix typos in CLx enabling Fix few typos in CLx enabling. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 2 +- drivers/thunderbolt/tmu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 0395229a3562..e584077b2f4f 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3556,7 +3556,7 @@ static int tb_switch_enable_cl0s(struct tb_switch *sw) * to improve performance. CLx is enabled only if both sides of the link * support CLx, and if both sides of the link are not configured as two * single lane links and only if the link is not inter-domain link. The - * complete set of conditions is descibed in CM Guide 1.0 section 8.1. + * complete set of conditions is described in CM Guide 1.0 section 8.1. * * Return: Returns 0 on success or an error code on failure. */ diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index e4a07a26f693..b656659d02fb 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -606,7 +606,7 @@ int tb_switch_tmu_enable(struct tb_switch *sw) /** * tb_switch_tmu_configure() - Configure the TMU rate and directionality * @sw: Router whose mode to change - * @rate: Rate to configure Off/LowRes/HiFi + * @rate: Rate to configure Off/Normal/HiFi * @unidirectional: If uni-directional (bi-directional otherwise) * * Selects the rate of the TMU and directionality (uni-directional or -- cgit v1.2.3 From 5fd6b9a5cbe63fea4c490fee8af34144a139a266 Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Thu, 26 May 2022 13:59:19 +0300 Subject: thunderbolt: Change downstream router's TMU rate in both TMU uni/bidir mode In case of uni-directional time sync, TMU handshake is initiated by upstream router. In case of bi-directional time sync, TMU handshake is initiated by downstream router. In order to handle correctly the case of uni-directional mode, we avoid changing the upstream router's rate to off, because it might have another downstream router plugged that is set to uni-directional mode (and we don't want to change its mode). Instead, we always change downstream router's rate. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tmu.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index b656659d02fb..985ca43b8f39 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -359,13 +359,14 @@ int tb_switch_tmu_disable(struct tb_switch *sw) * In case of uni-directional time sync, TMU handshake is * initiated by upstream router. In case of bi-directional * time sync, TMU handshake is initiated by downstream router. - * Therefore, we change the rate to off in the respective - * router. + * We change downstream router's rate to off for both uni/bidir + * cases although it is needed only for the bi-directional mode. + * We avoid changing upstream router's mode since it might + * have another downstream router plugged, that is set to + * uni-directional mode and we don't want to change it's TMU + * mode. */ - if (unidirectional) - tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_OFF); - else - tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); + tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); tb_port_tmu_time_sync_disable(up); ret = tb_port_tmu_time_sync_disable(down); -- cgit v1.2.3 From b017a46d486cd4113b1856f3fd611f54cd0f9c03 Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Thu, 26 May 2022 13:59:20 +0300 Subject: thunderbolt: Add CL1 support for USB4 and Titan Ridge routers In this patch we add support for a second low power state of the link: CL1. Low power states (called collectively CLx) are used to reduce transmitter and receiver power when a high-speed lane is idle. We enable it, if both sides of the link support it, and only for the first hop router (i.e. the first device that connected to the host router). This is needed for better thermal management. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 89 +++++++++++---------- drivers/thunderbolt/tb.c | 55 +++++++++---- drivers/thunderbolt/tb.h | 46 +++++------ drivers/thunderbolt/tb_regs.h | 6 ++ drivers/thunderbolt/tmu.c | 177 +++++++++++++++++++++++++++++++++++------- 5 files changed, 266 insertions(+), 107 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e584077b2f4f..244f8cd38b25 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3133,10 +3133,12 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) /* * Actually only needed for Titan Ridge but for simplicity can be * done for USB4 device too as CLx is re-enabled at resume. + * CL0s and CL1 are enabled and supported together. */ - if (tb_switch_is_clx_enabled(sw)) { - if (tb_switch_disable_clx(sw, TB_CL0S)) - tb_sw_warn(sw, "failed to disable CLx on upstream port\n"); + if (tb_switch_is_clx_enabled(sw, TB_CL1)) { + if (tb_switch_disable_clx(sw, TB_CL1)) + tb_sw_warn(sw, "failed to disable %s on upstream port\n", + tb_switch_clx_name(TB_CL1)); } err = tb_plug_events_active(sw, false); @@ -3428,13 +3430,12 @@ static bool tb_port_clx_supported(struct tb_port *port, enum tb_clx clx) } switch (clx) { - case TB_CL0S: - /* CL0s support requires also CL1 support */ + case TB_CL1: + /* CL0s and CL1 are enabled and supported together */ mask = LANE_ADP_CS_0_CL0S_SUPPORT | LANE_ADP_CS_0_CL1_SUPPORT; break; - /* For now we support only CL0s. Not CL1, CL2 */ - case TB_CL1: + /* For now we support only CL0s and CL1. Not CL2 */ case TB_CL2: default: return false; @@ -3448,18 +3449,18 @@ static bool tb_port_clx_supported(struct tb_port *port, enum tb_clx clx) return !!(val & mask); } -static inline bool tb_port_cl0s_supported(struct tb_port *port) -{ - return tb_port_clx_supported(port, TB_CL0S); -} - -static int __tb_port_cl0s_set(struct tb_port *port, bool enable) +static int __tb_port_clx_set(struct tb_port *port, enum tb_clx clx, bool enable) { u32 phy, mask; int ret; - /* To enable CL0s also required to enable CL1 */ - mask = LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE; + /* CL0s and CL1 are enabled and supported together */ + if (clx == TB_CL1) + mask = LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE; + else + /* For now we support only CL0s and CL1. Not CL2 */ + return -EOPNOTSUPP; + ret = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy + LANE_ADP_CS_1, 1); if (ret) @@ -3474,20 +3475,20 @@ static int __tb_port_cl0s_set(struct tb_port *port, bool enable) port->cap_phy + LANE_ADP_CS_1, 1); } -static int tb_port_cl0s_disable(struct tb_port *port) +static int tb_port_clx_disable(struct tb_port *port, enum tb_clx clx) { - return __tb_port_cl0s_set(port, false); + return __tb_port_clx_set(port, clx, false); } -static int tb_port_cl0s_enable(struct tb_port *port) +static int tb_port_clx_enable(struct tb_port *port, enum tb_clx clx) { - return __tb_port_cl0s_set(port, true); + return __tb_port_clx_set(port, clx, true); } -static int tb_switch_enable_cl0s(struct tb_switch *sw) +static int __tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx) { struct tb_switch *parent = tb_switch_parent(sw); - bool up_cl0s_support, down_cl0s_support; + bool up_clx_support, down_clx_support; struct tb_port *up, *down; int ret; @@ -3512,37 +3513,37 @@ static int tb_switch_enable_cl0s(struct tb_switch *sw) up = tb_upstream_port(sw); down = tb_port_at(tb_route(sw), parent); - up_cl0s_support = tb_port_cl0s_supported(up); - down_cl0s_support = tb_port_cl0s_supported(down); + up_clx_support = tb_port_clx_supported(up, clx); + down_clx_support = tb_port_clx_supported(down, clx); - tb_port_dbg(up, "CL0s %ssupported\n", - up_cl0s_support ? "" : "not "); - tb_port_dbg(down, "CL0s %ssupported\n", - down_cl0s_support ? "" : "not "); + tb_port_dbg(up, "%s %ssupported\n", tb_switch_clx_name(clx), + up_clx_support ? "" : "not "); + tb_port_dbg(down, "%s %ssupported\n", tb_switch_clx_name(clx), + down_clx_support ? "" : "not "); - if (!up_cl0s_support || !down_cl0s_support) + if (!up_clx_support || !down_clx_support) return -EOPNOTSUPP; - ret = tb_port_cl0s_enable(up); + ret = tb_port_clx_enable(up, clx); if (ret) return ret; - ret = tb_port_cl0s_enable(down); + ret = tb_port_clx_enable(down, clx); if (ret) { - tb_port_cl0s_disable(up); + tb_port_clx_disable(up, clx); return ret; } ret = tb_switch_mask_clx_objections(sw); if (ret) { - tb_port_cl0s_disable(up); - tb_port_cl0s_disable(down); + tb_port_clx_disable(up, clx); + tb_port_clx_disable(down, clx); return ret; } - sw->clx = TB_CL0S; + sw->clx = clx; - tb_port_dbg(up, "CL0s enabled\n"); + tb_port_dbg(up, "%s enabled\n", tb_switch_clx_name(clx)); return 0; } @@ -3575,15 +3576,16 @@ int tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx) return 0; switch (clx) { - case TB_CL0S: - return tb_switch_enable_cl0s(sw); + case TB_CL1: + /* CL0s and CL1 are enabled and supported together */ + return __tb_switch_enable_clx(sw, clx); default: return -EOPNOTSUPP; } } -static int tb_switch_disable_cl0s(struct tb_switch *sw) +static int __tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx) { struct tb_switch *parent = tb_switch_parent(sw); struct tb_port *up, *down; @@ -3605,17 +3607,17 @@ static int tb_switch_disable_cl0s(struct tb_switch *sw) up = tb_upstream_port(sw); down = tb_port_at(tb_route(sw), parent); - ret = tb_port_cl0s_disable(up); + ret = tb_port_clx_disable(up, clx); if (ret) return ret; - ret = tb_port_cl0s_disable(down); + ret = tb_port_clx_disable(down, clx); if (ret) return ret; sw->clx = TB_CLX_DISABLE; - tb_port_dbg(up, "CL0s disabled\n"); + tb_port_dbg(up, "%s disabled\n", tb_switch_clx_name(clx)); return 0; } @@ -3632,8 +3634,9 @@ int tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx) return 0; switch (clx) { - case TB_CL0S: - return tb_switch_disable_cl0s(sw); + case TB_CL1: + /* CL0s and CL1 are enabled and supported together */ + return __tb_switch_disable_clx(sw, clx); default: return -EOPNOTSUPP; diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 8ab3530dfa0d..8e9fdc4e0650 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -215,7 +215,7 @@ static int tb_enable_tmu(struct tb_switch *sw) int ret; /* If it is already enabled in correct mode, don't touch it */ - if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request)) + if (tb_switch_tmu_is_enabled(sw, sw->tmu.unidirectional_request)) return 0; ret = tb_switch_tmu_disable(sw); @@ -664,13 +664,24 @@ static void tb_scan_port(struct tb_port *port) tb_switch_lane_bonding_enable(sw); /* Set the link configured */ tb_switch_configure_link(sw); - /* Silently ignore CLx enabling in case CLx is not supported */ - ret = tb_switch_enable_clx(sw, TB_CL0S); + /* + * CL0s and CL1 are enabled and supported together. + * Silently ignore CLx enabling in case CLx is not supported. + */ + ret = tb_switch_enable_clx(sw, TB_CL1); if (ret && ret != -EOPNOTSUPP) - tb_sw_warn(sw, "failed to enable CLx on upstream port\n"); + tb_sw_warn(sw, "failed to enable %s on upstream port\n", + tb_switch_clx_name(TB_CL1)); - tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, - tb_switch_is_clx_enabled(sw)); + if (tb_switch_is_clx_enabled(sw, TB_CL1)) + /* + * To support highest CLx state, we set router's TMU to + * Normal-Uni mode. + */ + tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_NORMAL, true); + else + /* If CLx disabled, configure router's TMU to HiFi-Bidir mode*/ + tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, false); if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to enable TMU\n"); @@ -1410,7 +1421,12 @@ static int tb_start(struct tb *tb) return ret; } - tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_RATE_HIFI, false); + /* + * To support highest CLx state, we set host router's TMU to + * Normal mode. + */ + tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_RATE_NORMAL, + false); /* Enable TMU if it is off */ tb_switch_tmu_enable(tb->root_switch); /* Full scan to discover devices added before the driver was loaded. */ @@ -1455,16 +1471,25 @@ static void tb_restore_children(struct tb_switch *sw) if (sw->is_unplugged) return; - /* Silently ignore CLx re-enabling in case CLx is not supported */ - ret = tb_switch_enable_clx(sw, TB_CL0S); - if (ret && ret != -EOPNOTSUPP) - tb_sw_warn(sw, "failed to re-enable CLx on upstream port\n"); - /* - * tb_switch_tmu_configure() was already called when the switch was - * added before entering system sleep or runtime suspend, - * so no need to call it again before enabling TMU. + * CL0s and CL1 are enabled and supported together. + * Silently ignore CLx re-enabling in case CLx is not supported. */ + ret = tb_switch_enable_clx(sw, TB_CL1); + if (ret && ret != -EOPNOTSUPP) + tb_sw_warn(sw, "failed to re-enable %s on upstream port\n", + tb_switch_clx_name(TB_CL1)); + + if (tb_switch_is_clx_enabled(sw, TB_CL1)) + /* + * To support highest CLx state, we set router's TMU to + * Normal-Uni mode. + */ + tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_NORMAL, true); + else + /* If CLx disabled, configure router's TMU to HiFi-Bidir mode*/ + tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, false); + if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to restore TMU configuration\n"); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 4602c69913fa..3882b6eb9f51 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "tb_regs.h" #include "ctl.h" @@ -111,7 +112,7 @@ struct tb_switch_tmu { enum tb_clx { TB_CLX_DISABLE, - TB_CL0S, + /* CL0s and CL1 are enabled and supported together */ TB_CL1, TB_CL2, }; @@ -934,45 +935,46 @@ void tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_rate rate, bool unidirectional); /** - * tb_switch_tmu_hifi_is_enabled() - Checks if the specified TMU mode is enabled + * tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled * @sw: Router whose TMU mode to check * @unidirectional: If uni-directional (bi-directional otherwise) * * Return true if hardware TMU configuration matches the one passed in - * as parameter. That is HiFi and either uni-directional or bi-directional. + * as parameter. That is HiFi/Normal and either uni-directional or bi-directional. */ -static inline bool tb_switch_tmu_hifi_is_enabled(const struct tb_switch *sw, - bool unidirectional) +static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw, + bool unidirectional) { - return sw->tmu.rate == TB_SWITCH_TMU_RATE_HIFI && + return sw->tmu.rate == sw->tmu.rate_request && sw->tmu.unidirectional == unidirectional; } +static inline const char *tb_switch_clx_name(enum tb_clx clx) +{ + switch (clx) { + /* CL0s and CL1 are enabled and supported together */ + case TB_CL1: + return "CL0s/CL1"; + default: + return "unknown"; + } +} + int tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx); int tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx); /** * tb_switch_is_clx_enabled() - Checks if the CLx is enabled - * @sw: Router to check the CLx state for - * - * Checks if the CLx is enabled on the router upstream link. - * Not applicable for a host router. - */ -static inline bool tb_switch_is_clx_enabled(const struct tb_switch *sw) -{ - return sw->clx != TB_CLX_DISABLE; -} - -/** - * tb_switch_is_cl0s_enabled() - Checks if the CL0s is enabled - * @sw: Router to check for the CL0s + * @sw: Router to check for the CLx + * @clx: The CLx state to check for * - * Checks if the CL0s is enabled on the router upstream link. + * Checks if the specified CLx is enabled on the router upstream link. * Not applicable for a host router. */ -static inline bool tb_switch_is_cl0s_enabled(const struct tb_switch *sw) +static inline bool tb_switch_is_clx_enabled(const struct tb_switch *sw, + enum tb_clx clx) { - return sw->clx == TB_CL0S; + return sw->clx == clx; } /** diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 6a16f61a72a1..166054110388 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -234,6 +234,7 @@ enum usb4_switch_op { /* Router TMU configuration */ #define TMU_RTR_CS_0 0x00 +#define TMU_RTR_CS_0_FREQ_WIND_MASK GENMASK(26, 16) #define TMU_RTR_CS_0_TD BIT(27) #define TMU_RTR_CS_0_UCAP BIT(30) #define TMU_RTR_CS_1 0x01 @@ -244,6 +245,11 @@ enum usb4_switch_op { #define TMU_RTR_CS_3_LOCAL_TIME_NS_MASK GENMASK(15, 0) #define TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK GENMASK(31, 16) #define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT 16 +#define TMU_RTR_CS_15 0xf +#define TMU_RTR_CS_15_FREQ_AVG_MASK GENMASK(5, 0) +#define TMU_RTR_CS_15_DELAY_AVG_MASK GENMASK(11, 6) +#define TMU_RTR_CS_15_OFFSET_AVG_MASK GENMASK(17, 12) +#define TMU_RTR_CS_15_ERROR_AVG_MASK GENMASK(23, 18) #define TMU_RTR_CS_22 0x16 #define TMU_RTR_CS_24 0x18 #define TMU_RTR_CS_25 0x19 diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index 985ca43b8f39..e822ab90338b 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -11,6 +11,55 @@ #include "tb.h" +static int tb_switch_set_tmu_mode_params(struct tb_switch *sw, + enum tb_switch_tmu_rate rate) +{ + u32 freq_meas_wind[2] = { 30, 800 }; + u32 avg_const[2] = { 4, 8 }; + u32 freq, avg, val; + int ret; + + if (rate == TB_SWITCH_TMU_RATE_NORMAL) { + freq = freq_meas_wind[0]; + avg = avg_const[0]; + } else if (rate == TB_SWITCH_TMU_RATE_HIFI) { + freq = freq_meas_wind[1]; + avg = avg_const[1]; + } else { + return 0; + } + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_0, 1); + if (ret) + return ret; + + val &= ~TMU_RTR_CS_0_FREQ_WIND_MASK; + val |= FIELD_PREP(TMU_RTR_CS_0_FREQ_WIND_MASK, freq); + + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_0, 1); + if (ret) + return ret; + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_15, 1); + if (ret) + return ret; + + val &= ~TMU_RTR_CS_15_FREQ_AVG_MASK & + ~TMU_RTR_CS_15_DELAY_AVG_MASK & + ~TMU_RTR_CS_15_OFFSET_AVG_MASK & + ~TMU_RTR_CS_15_ERROR_AVG_MASK; + val |= FIELD_PREP(TMU_RTR_CS_15_FREQ_AVG_MASK, avg) | + FIELD_PREP(TMU_RTR_CS_15_DELAY_AVG_MASK, avg) | + FIELD_PREP(TMU_RTR_CS_15_OFFSET_AVG_MASK, avg) | + FIELD_PREP(TMU_RTR_CS_15_ERROR_AVG_MASK, avg); + + return tb_sw_write(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_15, 1); +} + static const char *tb_switch_tmu_mode_name(const struct tb_switch *sw) { bool root_switch = !tb_route(sw); @@ -348,7 +397,7 @@ int tb_switch_tmu_disable(struct tb_switch *sw) if (tb_route(sw)) { - bool unidirectional = tb_switch_tmu_hifi_is_enabled(sw, true); + bool unidirectional = sw->tmu.unidirectional; struct tb_switch *parent = tb_switch_parent(sw); struct tb_port *down, *up; int ret; @@ -412,6 +461,7 @@ static void __tb_switch_tmu_off(struct tb_switch *sw, bool unidirectional) else tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); + tb_switch_set_tmu_mode_params(sw, sw->tmu.rate); tb_port_tmu_unidirectional_disable(down); tb_port_tmu_unidirectional_disable(up); } @@ -493,7 +543,11 @@ static int __tb_switch_tmu_enable_unidirectional(struct tb_switch *sw) up = tb_upstream_port(sw); down = tb_port_at(tb_route(sw), parent); - ret = tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_HIFI); + ret = tb_switch_tmu_rate_write(parent, sw->tmu.rate_request); + if (ret) + return ret; + + ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.rate_request); if (ret) return ret; @@ -520,7 +574,83 @@ out: return ret; } -static int tb_switch_tmu_hifi_enable(struct tb_switch *sw) +static void __tb_switch_tmu_change_mode_prev(struct tb_switch *sw) +{ + struct tb_switch *parent = tb_switch_parent(sw); + struct tb_port *down, *up; + + down = tb_port_at(tb_route(sw), parent); + up = tb_upstream_port(sw); + /* + * In case of any failure in one of the steps when change mode, + * get back to the TMU configurations in previous mode. + * In case of additional failures in the functions below, + * ignore them since the caller shall already report a failure. + */ + tb_port_tmu_set_unidirectional(down, sw->tmu.unidirectional); + if (sw->tmu.unidirectional_request) + tb_switch_tmu_rate_write(parent, sw->tmu.rate); + else + tb_switch_tmu_rate_write(sw, sw->tmu.rate); + + tb_switch_set_tmu_mode_params(sw, sw->tmu.rate); + tb_port_tmu_set_unidirectional(up, sw->tmu.unidirectional); +} + +static int __tb_switch_tmu_change_mode(struct tb_switch *sw) +{ + struct tb_switch *parent = tb_switch_parent(sw); + struct tb_port *up, *down; + int ret; + + up = tb_upstream_port(sw); + down = tb_port_at(tb_route(sw), parent); + ret = tb_port_tmu_set_unidirectional(down, sw->tmu.unidirectional_request); + if (ret) + goto out; + + if (sw->tmu.unidirectional_request) + ret = tb_switch_tmu_rate_write(parent, sw->tmu.rate_request); + else + ret = tb_switch_tmu_rate_write(sw, sw->tmu.rate_request); + if (ret) + return ret; + + ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.rate_request); + if (ret) + return ret; + + ret = tb_port_tmu_set_unidirectional(up, sw->tmu.unidirectional_request); + if (ret) + goto out; + + ret = tb_port_tmu_time_sync_enable(down); + if (ret) + goto out; + + ret = tb_port_tmu_time_sync_enable(up); + if (ret) + goto out; + + return 0; + +out: + __tb_switch_tmu_change_mode_prev(sw); + return ret; +} + +/** + * tb_switch_tmu_enable() - Enable TMU on a router + * @sw: Router whose TMU to enable + * + * Enables TMU of a router to be in uni-directional Normal/HiFi + * or bi-directional HiFi mode. Calling tb_switch_tmu_configure() is required + * before calling this function, to select the mode Normal/HiFi and + * directionality (uni-directional/bi-directional). + * In HiFi mode all tunneling should work. In Normal mode, DP tunneling can't + * work. Uni-directional mode is required for CLx (Link Low-Power) to work. + */ +int tb_switch_tmu_enable(struct tb_switch *sw) { bool unidirectional = sw->tmu.unidirectional_request; int ret; @@ -536,12 +666,15 @@ static int tb_switch_tmu_hifi_enable(struct tb_switch *sw) if (!tb_switch_is_clx_supported(sw)) return 0; - if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request)) + if (tb_switch_tmu_is_enabled(sw, sw->tmu.unidirectional_request)) return 0; if (tb_switch_is_titan_ridge(sw) && unidirectional) { - /* Titan Ridge supports only CL0s */ - if (!tb_switch_is_cl0s_enabled(sw)) + /* + * Titan Ridge supports CL0s and CL1 only. CL0s and CL1 are + * enabled and supported together. + */ + if (!tb_switch_is_clx_enabled(sw, TB_CL1)) return -EOPNOTSUPP; ret = tb_switch_tmu_objection_mask(sw); @@ -558,7 +691,11 @@ static int tb_switch_tmu_hifi_enable(struct tb_switch *sw) return ret; if (tb_route(sw)) { - /* The used mode changes are from OFF to HiFi-Uni/HiFi-BiDir */ + /* + * The used mode changes are from OFF to + * HiFi-Uni/HiFi-BiDir/Normal-Uni or from Normal-Uni to + * HiFi-Uni. + */ if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF) { if (unidirectional) ret = __tb_switch_tmu_enable_unidirectional(sw); @@ -566,6 +703,10 @@ static int tb_switch_tmu_hifi_enable(struct tb_switch *sw) ret = __tb_switch_tmu_enable_bidirectional(sw); if (ret) return ret; + } else if (sw->tmu.rate == TB_SWITCH_TMU_RATE_NORMAL) { + ret = __tb_switch_tmu_change_mode(sw); + if (ret) + return ret; } sw->tmu.unidirectional = unidirectional; } else { @@ -575,35 +716,17 @@ static int tb_switch_tmu_hifi_enable(struct tb_switch *sw) * of the child node - see above. * Here only the host router' rate configuration is written. */ - ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI); + ret = tb_switch_tmu_rate_write(sw, sw->tmu.rate_request); if (ret) return ret; } - sw->tmu.rate = TB_SWITCH_TMU_RATE_HIFI; + sw->tmu.rate = sw->tmu.rate_request; tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw)); return tb_switch_tmu_set_time_disruption(sw, false); } -/** - * tb_switch_tmu_enable() - Enable TMU on a router - * @sw: Router whose TMU to enable - * - * Enables TMU of a router to be in uni-directional or bi-directional HiFi mode. - * Calling tb_switch_tmu_configure() is required before calling this function, - * to select the mode HiFi and directionality (uni-directional/bi-directional). - * In both modes all tunneling should work. Uni-directional mode is required for - * CLx (Link Low-Power) to work. - */ -int tb_switch_tmu_enable(struct tb_switch *sw) -{ - if (sw->tmu.rate_request == TB_SWITCH_TMU_RATE_NORMAL) - return -EOPNOTSUPP; - - return tb_switch_tmu_hifi_enable(sw); -} - /** * tb_switch_tmu_configure() - Configure the TMU rate and directionality * @sw: Router whose mode to change -- cgit v1.2.3 From 3084b48fa13931400f316e2b9ffdd98b8ba6600e Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Thu, 26 May 2022 13:59:21 +0300 Subject: thunderbolt: Change TMU mode to HiFi uni-directional once DisplayPort tunneled Here we configure TMU mode to HiFi uni-directional once DP tunnel is created. This is due to accuracy requirement for DP tunneling as appears in CM guide 1.0, section 7.3.2. Due to Intel hardware limitation, once we changed the TMU mode to HiFi uni-directional (when DP tunnel exists), we don't change TMU mode back to normal uni-directional, even if DP tunnel is torn down later. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 13 +++++++++++++ drivers/thunderbolt/tb.h | 2 ++ drivers/thunderbolt/tmu.c | 29 +++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) (limited to 'drivers') diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 8e9fdc4e0650..9853f6c7e81d 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -118,6 +118,13 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, switch (port->config.type) { case TB_TYPE_DP_HDMI_IN: tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); + /* + * In case of DP tunnel exists, change host router's + * 1st children TMU mode to HiFi for CL0s to work. + */ + if (tunnel) + tb_switch_enable_tmu_1st_child(tb->root_switch, + TB_SWITCH_TMU_RATE_HIFI); break; case TB_TYPE_PCIE_DOWN: @@ -979,6 +986,12 @@ static void tb_tunnel_dp(struct tb *tb) list_add_tail(&tunnel->list, &tcm->tunnel_list); tb_reclaim_usb3_bandwidth(tb, in, out); + /* + * In case of DP tunnel exists, change host router's 1st children + * TMU mode to HiFi for CL0s to work. + */ + tb_switch_enable_tmu_1st_child(tb->root_switch, TB_SWITCH_TMU_RATE_HIFI); + return; err_free: diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 3882b6eb9f51..e2f50fd90ba9 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -934,6 +934,8 @@ int tb_switch_tmu_enable(struct tb_switch *sw); void tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_rate rate, bool unidirectional); +void tb_switch_enable_tmu_1st_child(struct tb_switch *sw, + enum tb_switch_tmu_rate rate); /** * tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled * @sw: Router whose TMU mode to check diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index e822ab90338b..626aca3124b1 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -742,3 +742,32 @@ void tb_switch_tmu_configure(struct tb_switch *sw, sw->tmu.unidirectional_request = unidirectional; sw->tmu.rate_request = rate; } + +static int tb_switch_tmu_config_enable(struct device *dev, void *rate) +{ + if (tb_is_switch(dev)) { + struct tb_switch *sw = tb_to_switch(dev); + + tb_switch_tmu_configure(sw, *(enum tb_switch_tmu_rate *)rate, + tb_switch_is_clx_enabled(sw, TB_CL1)); + if (tb_switch_tmu_enable(sw)) + tb_sw_dbg(sw, "fail switching TMU mode for 1st depth router\n"); + } + + return 0; +} + +/** + * tb_switch_enable_tmu_1st_child - Configure and enable TMU for 1st chidren + * @sw: The router to configure and enable it's children TMU + * @rate: Rate of the TMU to configure the router's chidren to + * + * Configures and enables the TMU mode of 1st depth children of the specified + * router to the specified rate. + */ +void tb_switch_enable_tmu_1st_child(struct tb_switch *sw, + enum tb_switch_tmu_rate rate) +{ + device_for_each_child(&sw->dev, &rate, + tb_switch_tmu_config_enable); +} -- cgit v1.2.3 From e173b7d46c0413f004450bfc48f45bfc0f0f2fc2 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Thu, 26 May 2022 21:01:54 +0800 Subject: thunderbolt: Fix some kernel-doc comments Remove some warnings found by running scripts/kernel-doc, which is caused by using 'make W=1'. drivers/thunderbolt/ctl.c:701: warning: expecting prototype for tb_cfg_start(). Prototype was for tb_ctl_start() instead drivers/thunderbolt/ctl.c:722: warning: expecting prototype for tb_ctrl_stop(). Prototype was for tb_ctl_stop() instead drivers/thunderbolt/ctl.c:930: warning: expecting prototype for tb_cfg_write(). Prototype was for tb_cfg_write_raw() instead Reported-by: Abaci Robot Signed-off-by: Yang Li Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index e92c658dba1c..e5ede5debfb0 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -694,7 +694,7 @@ void tb_ctl_free(struct tb_ctl *ctl) } /** - * tb_cfg_start() - start/resume the control channel + * tb_ctl_start() - start/resume the control channel * @ctl: Control channel to start */ void tb_ctl_start(struct tb_ctl *ctl) @@ -710,7 +710,7 @@ void tb_ctl_start(struct tb_ctl *ctl) } /** - * tb_ctrl_stop() - pause the control channel + * tb_ctl_stop() - pause the control channel * @ctl: Control channel to stop * * All invocations of ctl->callback will have finished after this method @@ -912,7 +912,7 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, } /** - * tb_cfg_write() - write from buffer into config space + * tb_cfg_write_raw() - write from buffer into config space * @ctl: Pointer to the control channel * @buffer: Data to write * @route: Route string of the router -- cgit v1.2.3 From 7ec58378a985618909ffae18e4ac0de2ae625f33 Mon Sep 17 00:00:00 2001 From: George D Sworo Date: Wed, 1 Jun 2022 15:41:02 -0700 Subject: thunderbolt: Add support for Intel Raptor Lake Intel Raptor Lake has the same integrated Thunderbolt/USB4 controller as Intel Alder Lake. By default it is still using firmware based connection manager so we can use most of the Alder Lake flows. Signed-off-by: George D Sworo Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 2 ++ drivers/thunderbolt/nhi.c | 4 ++++ drivers/thunderbolt/nhi.h | 2 ++ 3 files changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index fff0c740c8f3..ae38f0d25a8d 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -2516,6 +2516,8 @@ struct tb *icm_probe(struct tb_nhi *nhi) case PCI_DEVICE_ID_INTEL_TGL_H_NHI1: case PCI_DEVICE_ID_INTEL_ADL_NHI0: case PCI_DEVICE_ID_INTEL_ADL_NHI1: + case PCI_DEVICE_ID_INTEL_RPL_NHI0: + case PCI_DEVICE_ID_INTEL_RPL_NHI1: icm->is_supported = icm_tgl_is_supported; icm->driver_ready = icm_icl_driver_ready; icm->set_uuid = icm_icl_set_uuid; diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 1333b158a95e..cb8c9c4ae93a 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1410,6 +1410,10 @@ static struct pci_device_id nhi_ids[] = { .driver_data = (kernel_ulong_t)&icl_nhi_ops }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, /* Any USB4 compliant host */ { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 69083aab2736..f09da5b62233 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -80,6 +80,8 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_DEVICE_ID_INTEL_TGL_NHI1 0x9a1d #define PCI_DEVICE_ID_INTEL_TGL_H_NHI0 0x9a1f #define PCI_DEVICE_ID_INTEL_TGL_H_NHI1 0x9a21 +#define PCI_DEVICE_ID_INTEL_RPL_NHI0 0xa73e +#define PCI_DEVICE_ID_INTEL_RPL_NHI1 0xa76d #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 -- cgit v1.2.3 From c5d337a358b3e41bb4f7abd99a79b68a28eafaa2 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 8 Jun 2022 20:49:18 +0300 Subject: usb: gadget: uvc: Fix comment blocks style The UVC gadget driver historically uses the /* Comment * style */ for multi-line block comments, which is frowned upon. Patches for the driver are required to use the more standard /* * Comment * style */ style. This result in inconsistencies. Fix it by converting all remaining instances of the old style. Reviewed-by: Kieran Bingham Signed-off-by: Laurent Pinchart Link: https://lore.kernel.org/r/20220608174918.14656-1-laurent.pinchart@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 30 +++++++++++++++++++----------- drivers/usb/gadget/function/uvc_queue.c | 6 ++++-- drivers/usb/gadget/function/uvc_video.c | 12 ++++++++---- 3 files changed, 31 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index d3feeeb50841..71669e0e4d00 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -141,7 +141,8 @@ static struct usb_endpoint_descriptor uvc_fs_streaming_ep = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_XFER_ISOC, - /* The wMaxPacketSize and bInterval values will be initialized from + /* + * The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ }; @@ -152,7 +153,8 @@ static struct usb_endpoint_descriptor uvc_hs_streaming_ep = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_XFER_ISOC, - /* The wMaxPacketSize and bInterval values will be initialized from + /* + * The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ }; @@ -164,7 +166,8 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_XFER_ISOC, - /* The wMaxPacketSize and bInterval values will be initialized from + /* + * The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ }; @@ -172,7 +175,8 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep = { static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = { .bLength = sizeof(uvc_ss_streaming_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - /* The bMaxBurst, bmAttributes and wBytesPerInterval values will be + /* + * The bMaxBurst, bmAttributes and wBytesPerInterval values will be * initialized from module parameters. */ }; @@ -234,7 +238,8 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE) return -EINVAL; - /* Tell the complete callback to generate an event for the next request + /* + * Tell the complete callback to generate an event for the next request * that will be enqueued by UVCIOC_SEND_RESPONSE. */ uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN); @@ -500,7 +505,8 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) if (!uvc_control_desc || !uvc_streaming_cls) return ERR_PTR(-ENODEV); - /* Descriptors layout + /* + * Descriptors layout * * uvc_iad * uvc_control_intf @@ -597,8 +603,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvcg_info(f, "%s()\n", __func__); opts = fi_to_f_uvc_opts(f->fi); - /* Sanity check the streaming endpoint module parameters. - */ + /* Sanity check the streaming endpoint module parameters. */ opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U); opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U); opts->streaming_maxburst = min(opts->streaming_maxburst, 15U); @@ -611,7 +616,8 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) opts->streaming_maxpacket); } - /* Fill in the FS/HS/SS Video Streaming specific descriptors from the + /* + * Fill in the FS/HS/SS Video Streaming specific descriptors from the * module parameters. * * NOTE: We assume that the user knows what they are doing and won't @@ -895,7 +901,8 @@ static void uvc_function_unbind(struct usb_configuration *c, uvcg_info(f, "%s()\n", __func__); - /* If we know we're connected via v4l2, then there should be a cleanup + /* + * If we know we're connected via v4l2, then there should be a cleanup * of the device from userspace either via UVC_EVENT_DISCONNECT or * though the video device removal uevent. Allow some time for the * application to close out before things get deleted. @@ -912,7 +919,8 @@ static void uvc_function_unbind(struct usb_configuration *c, v4l2_device_unregister(&uvc->v4l2_dev); if (uvc->func_connected) { - /* Wait for the release to occur to ensure there are no longer any + /* + * Wait for the release to occur to ensure there are no longer any * pending operations that may cause panics when resources are cleaned * up. */ diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index d25edc3d2174..951934aa4454 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -104,7 +104,8 @@ static void uvc_buffer_queue(struct vb2_buffer *vb) if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) { list_add_tail(&buf->queue, &queue->irqqueue); } else { - /* If the device is disconnected return the buffer to userspace + /* + * If the device is disconnected return the buffer to userspace * directly. The next QBUF call will fail with -ENODEV. */ buf->state = UVC_BUF_STATE_ERROR; @@ -255,7 +256,8 @@ void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect) } queue->buf_used = 0; - /* This must be protected by the irqlock spinlock to avoid race + /* + * This must be protected by the irqlock spinlock to avoid race * conditions between uvc_queue_buffer and the disconnection event that * could result in an interruptible wait in uvc_dequeue_buffer. Do not * blindly replace this logic by checking for the UVC_DEV_DISCONNECTED diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index a9bb4553db84..5876bc73f929 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -378,7 +378,8 @@ static void uvcg_video_pump(struct work_struct *work) int ret; while (video->ep->enabled) { - /* Retrieve the first available USB request, protected by the + /* + * Retrieve the first available USB request, protected by the * request lock. */ spin_lock_irqsave(&video->req_lock, flags); @@ -391,7 +392,8 @@ static void uvcg_video_pump(struct work_struct *work) list_del(&req->list); spin_unlock_irqrestore(&video->req_lock, flags); - /* Retrieve the first available video buffer and fill the + /* + * Retrieve the first available video buffer and fill the * request, protected by the video queue irqlock. */ spin_lock_irqsave(&queue->irqlock, flags); @@ -403,9 +405,11 @@ static void uvcg_video_pump(struct work_struct *work) video->encode(req, video, buf); - /* With usb3 we have more requests. This will decrease the + /* + * With usb3 we have more requests. This will decrease the * interrupt load to a quarter but also catches the corner - * cases, which needs to be handled */ + * cases, which needs to be handled. + */ if (list_empty(&video->req_free) || buf->state == UVC_BUF_STATE_DONE || !(video->req_int_count % -- cgit v1.2.3 From 7d602f30149a117eea260208b1661bc404c21dfd Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 8 Jun 2022 14:04:30 -0500 Subject: usb: cdns3 fix use-after-free at workaround 2 BUG: KFENCE: use-after-free read in __list_del_entry_valid+0x10/0xac cdns3_wa2_remove_old_request() { ... kfree(priv_req->request.buf); cdns3_gadget_ep_free_request(&priv_ep->endpoint, &priv_req->request); list_del_init(&priv_req->list); ^^^ use after free ... } cdns3_gadget_ep_free_request() free the space pointed by priv_req, but priv_req is used in the following list_del_init(). This patch move list_del_init() before cdns3_gadget_ep_free_request(). Signed-off-by: Frank Li Signed-off-by: Faqiang Zhu Link: https://lore.kernel.org/r/20220608190430.2814358-1-Frank.Li@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 5c15c48952a6..29662c8ac024 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -625,9 +625,9 @@ static void cdns3_wa2_remove_old_request(struct cdns3_endpoint *priv_ep) trace_cdns3_wa2(priv_ep, "removes eldest request"); kfree(priv_req->request.buf); + list_del_init(&priv_req->list); cdns3_gadget_ep_free_request(&priv_ep->endpoint, &priv_req->request); - list_del_init(&priv_req->list); --priv_ep->wa2_counter; if (!chain) -- cgit v1.2.3 From 8659ab3d936fcf0084676f98b75b317017aa8f82 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 9 Jun 2022 10:44:56 -0500 Subject: usb: cdns3: fix random warning message when driver load Warning log: [ 4.141392] Unexpected gfp: 0x4 (GFP_DMA32). Fixing up to gfp: 0xa20 (GFP_ATOMIC). Fix your code! [ 4.150340] CPU: 1 PID: 175 Comm: 1-0050 Not tainted 5.15.5-00039-g2fd9ae1b568c #20 [ 4.158010] Hardware name: Freescale i.MX8QXP MEK (DT) [ 4.163155] Call trace: [ 4.165600] dump_backtrace+0x0/0x1b0 [ 4.169286] show_stack+0x18/0x68 [ 4.172611] dump_stack_lvl+0x68/0x84 [ 4.176286] dump_stack+0x18/0x34 [ 4.179613] kmalloc_fix_flags+0x60/0x88 [ 4.183550] new_slab+0x334/0x370 [ 4.186878] ___slab_alloc.part.108+0x4d4/0x748 [ 4.191419] __slab_alloc.isra.109+0x30/0x78 [ 4.195702] kmem_cache_alloc+0x40c/0x420 [ 4.199725] dma_pool_alloc+0xac/0x1f8 [ 4.203486] cdns3_allocate_trb_pool+0xb4/0xd0 pool_alloc_page(struct dma_pool *pool, gfp_t mem_flags) { ... page = kmalloc(sizeof(*page), mem_flags); page->vaddr = dma_alloc_coherent(pool->dev, pool->allocation, &page->dma, mem_flags); ... } kmalloc was called with mem_flags, which is passed down in cdns3_allocate_trb_pool() and have GFP_DMA32 flags. kmall_fix_flags() report warning. GFP_DMA32 is not useful at all. dma_alloc_coherent() will handle DMA memory region correctly by pool->dev. GFP_DMA32 can be removed safely. Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20220609154456.2871672-1-Frank.Li@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 29662c8ac024..555caafe4f04 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -220,7 +220,7 @@ int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) if (!priv_ep->trb_pool) { priv_ep->trb_pool = dma_pool_alloc(priv_dev->eps_dma_pool, - GFP_DMA32 | GFP_ATOMIC, + GFP_ATOMIC, &priv_ep->trb_pool_dma); if (!priv_ep->trb_pool) -- cgit v1.2.3 From 3497b9a5c8c3d4efaa15ab542cc6a774b0e0a7c6 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 7 Jun 2022 10:20:04 +0800 Subject: usb: dwc3: add power down scale setting Some SoC(e.g NXP imx8MQ) may have a wrong default power down scale setting so need init it to be the correct value, the power down scale setting description in DWC3 databook: Power Down Scale (PwrDnScale) The USB3 suspend_clk input replaces pipe3_rx_pclk as a clock source to a small part of the USB3 core that operates when the SS PHY is in its lowest power (P3) state, and therefore does not provide a clock. The Power Down Scale field specifies how many suspend_clk periods fit into a 16 kHz clock period. When performing the division, round up the remainder. For example, when using an 8-bit/16-bit/32-bit PHY and 25-MHz Suspend clock, Power Down Scale = 25000 kHz/16 kHz = 13'd1563 (rounder up) So use the suspend clock rate to calculate it. Reviewed-by: Thinh Nguyen Signed-off-by: Li Jun Link: https://lore.kernel.org/r/1654568404-3461-1-git-send-email-jun.li@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 34 ++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/core.h | 1 + 2 files changed, 35 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index e027c0420dc3..b4c47002828c 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1029,6 +1029,37 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, cfg); } +static void dwc3_set_power_down_clk_scale(struct dwc3 *dwc) +{ + u32 scale; + u32 reg; + + if (!dwc->susp_clk) + return; + + /* + * The power down scale field specifies how many suspend_clk + * periods fit into a 16KHz clock period. When performing + * the division, round up the remainder. + * + * The power down scale value is calculated using the fastest + * frequency of the suspend_clk. If it isn't fixed (but within + * the accuracy requirement), the driver may not know the max + * rate of the suspend_clk, so only update the power down scale + * if the default is less than the calculated value from + * clk_get_rate() or if the default is questionably high + * (3x or more) to be within the requirement. + */ + scale = DIV_ROUND_UP(clk_get_rate(dwc->susp_clk), 16000); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + if ((reg & DWC3_GCTL_PWRDNSCALE_MASK) < DWC3_GCTL_PWRDNSCALE(scale) || + (reg & DWC3_GCTL_PWRDNSCALE_MASK) > DWC3_GCTL_PWRDNSCALE(scale*3)) { + reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK); + reg |= DWC3_GCTL_PWRDNSCALE(scale); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + } +} + /** * dwc3_core_init - Low-level initialization of DWC3 Core * @dwc: Pointer to our controller context structure @@ -1105,6 +1136,9 @@ static int dwc3_core_init(struct dwc3 *dwc) if (ret) goto err1; + /* Set power down scale of suspend_clk */ + dwc3_set_power_down_clk_scale(dwc); + /* Adjust Frame Length */ dwc3_frame_length_adjustment(dwc); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 81c486b3941c..722808d8c0af 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -231,6 +231,7 @@ /* Global Configuration Register */ #define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_PWRDNSCALE_MASK GENMASK(31, 19) #define DWC3_GCTL_U2RSTECN BIT(16) #define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) #define DWC3_GCTL_CLK_BUS (0) -- cgit v1.2.3 From b5c5b13cb45e2c88181308186b0001992cb41954 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Thu, 2 Jun 2022 15:08:49 +0400 Subject: usb: host: Fix refcount leak in ehci_hcd_ppc_of_probe of_find_compatible_node() returns a node pointer with refcount incremented, we should use of_node_put() on it when done. Add missing of_node_put() to avoid refcount leak. Fixes: 796bcae7361c ("USB: powerpc: Workaround for the PPC440EPX USBH_23 errata [take 3]") Acked-by: Alan Stern Signed-off-by: Miaoqian Lin Link: https://lore.kernel.org/r/20220602110849.58549-1-linmq006@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-ppc-of.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index 6bbaee74f7e7..28a19693c19f 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -148,6 +148,7 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op) } else { ehci->has_amcc_usb23 = 1; } + of_node_put(np); } if (of_get_property(dn, "big-endian", NULL)) { -- cgit v1.2.3 From 302970b4cad3ebfda2c05ce06c322ccdc447d17e Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Fri, 3 Jun 2022 18:12:30 +0400 Subject: usb: ohci-nxp: Fix refcount leak in ohci_hcd_nxp_probe of_parse_phandle() returns a node pointer with refcount incremented, we should use of_node_put() on it when not need anymore. Add missing of_node_put() to avoid refcount leak. Fixes: 73108aa90cbf ("USB: ohci-nxp: Use isp1301 driver") Acked-by: Alan Stern Signed-off-by: Miaoqian Lin Link: https://lore.kernel.org/r/20220603141231.979-1-linmq006@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-nxp.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c index 85878e8ad331..106a6bcefb08 100644 --- a/drivers/usb/host/ohci-nxp.c +++ b/drivers/usb/host/ohci-nxp.c @@ -164,6 +164,7 @@ static int ohci_hcd_nxp_probe(struct platform_device *pdev) } isp1301_i2c_client = isp1301_get_client(isp1301_node); + of_node_put(isp1301_node); if (!isp1301_i2c_client) return -EPROBE_DEFER; -- cgit v1.2.3 From 1e073e3ed9ff9ec14e7e360ed89a81256d895588 Mon Sep 17 00:00:00 2001 From: Clément Léger Date: Tue, 7 Jun 2022 15:34:54 +0200 Subject: usb: host: ohci-at91: add support to enter suspend using SMC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Linux is running under OP-TEE, the SFR is set as secured and thus the AT91_OHCIICR_USB_SUSPEND register isn't accessible. Add a SMC to do the appropriate call to suspend the controller. The SMC id is fetched from the device-tree property "microchip,suspend-smc-id". if present, then the syscon regmap is not used to enter suspend and a SMC is issued. Reviewed-by: Claudiu Beznea Acked-by: Alan Stern Signed-off-by: Clément Léger Link: https://lore.kernel.org/r/20220607133454.727063-1-clement.leger@bootlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-at91.c | 69 +++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index a24aea3d2759..98326465e2dc 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -13,6 +13,7 @@ * This file is licenced under the GPL. */ +#include #include #include #include @@ -55,6 +56,7 @@ struct ohci_at91_priv { bool clocked; bool wakeup; /* Saved wake-up state for resume */ struct regmap *sfr_regmap; + u32 suspend_smc_id; }; /* interface and function clocks; sometimes also an AHB clock */ @@ -135,6 +137,19 @@ static void at91_stop_hc(struct platform_device *pdev) static void usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *); +static u32 at91_dt_suspend_smc(struct device *dev) +{ + u32 suspend_smc_id; + + if (!dev->of_node) + return 0; + + if (of_property_read_u32(dev->of_node, "microchip,suspend-smc-id", &suspend_smc_id)) + return 0; + + return suspend_smc_id; +} + static struct regmap *at91_dt_syscon_sfr(void) { struct regmap *regmap; @@ -215,9 +230,13 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, goto err; } - ohci_at91->sfr_regmap = at91_dt_syscon_sfr(); - if (!ohci_at91->sfr_regmap) - dev_dbg(dev, "failed to find sfr node\n"); + ohci_at91->suspend_smc_id = at91_dt_suspend_smc(dev); + if (!ohci_at91->suspend_smc_id) { + dev_dbg(dev, "failed to find sfr suspend smc id, using regmap\n"); + ohci_at91->sfr_regmap = at91_dt_syscon_sfr(); + if (!ohci_at91->sfr_regmap) + dev_dbg(dev, "failed to find sfr node\n"); + } board = hcd->self.controller->platform_data; ohci = hcd_to_ohci(hcd); @@ -303,24 +322,30 @@ static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf) return length; } -static int ohci_at91_port_suspend(struct regmap *regmap, u8 set) +static int ohci_at91_port_suspend(struct ohci_at91_priv *ohci_at91, u8 set) { + struct regmap *regmap = ohci_at91->sfr_regmap; u32 regval; int ret; - if (!regmap) - return 0; + if (ohci_at91->suspend_smc_id) { + struct arm_smccc_res res; - ret = regmap_read(regmap, AT91_SFR_OHCIICR, ®val); - if (ret) - return ret; + arm_smccc_smc(ohci_at91->suspend_smc_id, set, 0, 0, 0, 0, 0, 0, &res); + if (res.a0) + return -EINVAL; + } else if (regmap) { + ret = regmap_read(regmap, AT91_SFR_OHCIICR, ®val); + if (ret) + return ret; - if (set) - regval |= AT91_OHCIICR_USB_SUSPEND; - else - regval &= ~AT91_OHCIICR_USB_SUSPEND; + if (set) + regval |= AT91_OHCIICR_USB_SUSPEND; + else + regval &= ~AT91_OHCIICR_USB_SUSPEND; - regmap_write(regmap, AT91_SFR_OHCIICR, regval); + regmap_write(regmap, AT91_SFR_OHCIICR, regval); + } return 0; } @@ -357,9 +382,8 @@ static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_SUSPEND: dev_dbg(hcd->self.controller, "SetPortFeat: SUSPEND\n"); - if (valid_port(wIndex) && ohci_at91->sfr_regmap) { - ohci_at91_port_suspend(ohci_at91->sfr_regmap, - 1); + if (valid_port(wIndex)) { + ohci_at91_port_suspend(ohci_at91, 1); return 0; } break; @@ -400,9 +424,8 @@ static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_SUSPEND: dev_dbg(hcd->self.controller, "ClearPortFeature: SUSPEND\n"); - if (valid_port(wIndex) && ohci_at91->sfr_regmap) { - ohci_at91_port_suspend(ohci_at91->sfr_regmap, - 0); + if (valid_port(wIndex)) { + ohci_at91_port_suspend(ohci_at91, 0); return 0; } break; @@ -630,10 +653,10 @@ ohci_hcd_at91_drv_suspend(struct device *dev) /* flush the writes */ (void) ohci_readl (ohci, &ohci->regs->control); msleep(1); - ohci_at91_port_suspend(ohci_at91->sfr_regmap, 1); + ohci_at91_port_suspend(ohci_at91, 1); at91_stop_clock(ohci_at91); } else { - ohci_at91_port_suspend(ohci_at91->sfr_regmap, 1); + ohci_at91_port_suspend(ohci_at91, 1); } return ret; @@ -645,7 +668,7 @@ ohci_hcd_at91_drv_resume(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); - ohci_at91_port_suspend(ohci_at91->sfr_regmap, 0); + ohci_at91_port_suspend(ohci_at91, 0); if (ohci_at91->wakeup) disable_irq_wake(hcd->irq); -- cgit v1.2.3 From df22ecc41b54def624735b83784857e708bd1502 Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Fri, 3 Jun 2022 13:52:15 +0530 Subject: usb: dwc3: Remove the checks of -ENOSYS Commit 57303488cd37d ("usb: dwc3: adapt dwc3 core to use Generic PHY Framework") added if statements that check 'ret == -ENOSYS || ret == -ENODEV', but the function phy_get() which is called by devm_phy_get() returns the phy driver or -ENODEV if the phy driver was not found. So, remove the check of -ENOSYS in the if statements. Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/HK0PR01MB2801E19D4FE569545BB7592DF8A19@HK0PR01MB2801.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index b4c47002828c..a8b42530b603 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1318,7 +1318,7 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) dwc->usb2_generic_phy = devm_phy_get(dev, "usb2-phy"); if (IS_ERR(dwc->usb2_generic_phy)) { ret = PTR_ERR(dwc->usb2_generic_phy); - if (ret == -ENOSYS || ret == -ENODEV) + if (ret == -ENODEV) dwc->usb2_generic_phy = NULL; else return dev_err_probe(dev, ret, "no usb2 phy configured\n"); @@ -1327,7 +1327,7 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) dwc->usb3_generic_phy = devm_phy_get(dev, "usb3-phy"); if (IS_ERR(dwc->usb3_generic_phy)) { ret = PTR_ERR(dwc->usb3_generic_phy); - if (ret == -ENOSYS || ret == -ENODEV) + if (ret == -ENODEV) dwc->usb3_generic_phy = NULL; else return dev_err_probe(dev, ret, "no usb3 phy configured\n"); -- cgit v1.2.3 From ecf6dedd03b03f05459c3662d9b141bedbc1c39d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 25 May 2022 15:27:20 +0300 Subject: USB: usbip: clean up mixed use of _irq() and _irqsave() It generally doesn't make sense to use _irq() and _irqsave() in the same function because either some of the callers have disabled IRQs or they haven't. In this case, the v_recv_cmd_submit() appears to always be called with IRQs enabled so the code works fine. That means I could convert it to either _irq() or _irqsave() but I chose to use _irqsave() because it's more conservative and easier to review. Reviewed-by: Shuah Khan Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Yo4gqLPtHO6XKMLn@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vudc_rx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/usbip/vudc_rx.c b/drivers/usb/usbip/vudc_rx.c index 1e8a23d92cb4..d4a2f30a7580 100644 --- a/drivers/usb/usbip/vudc_rx.c +++ b/drivers/usb/usbip/vudc_rx.c @@ -104,18 +104,18 @@ static int v_recv_cmd_submit(struct vudc *udc, if (pdu->base.direction == USBIP_DIR_IN) address |= USB_DIR_IN; - spin_lock_irq(&udc->lock); + spin_lock_irqsave(&udc->lock, flags); urb_p->ep = vudc_find_endpoint(udc, address); if (!urb_p->ep) { /* we don't know the type, there may be isoc data! */ dev_err(&udc->pdev->dev, "request to nonexistent endpoint"); - spin_unlock_irq(&udc->lock); + spin_unlock_irqrestore(&udc->lock, flags); usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); ret = -EPIPE; goto free_urbp; } urb_p->type = urb_p->ep->type; - spin_unlock_irq(&udc->lock); + spin_unlock_irqrestore(&udc->lock, flags); urb_p->new = 1; urb_p->seqnum = pdu->base.seqnum; -- cgit v1.2.3 From 62e4efe3375eb30292dabaec4481dc04550d3644 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 25 May 2022 15:30:13 +0300 Subject: usbip: vudc: Don't enable IRQs prematurely This code does: spin_unlock_irq(&udc->ud.lock); spin_unlock_irqrestore(&udc->lock, flags); which does not make sense. In theory, the first unlock could enable IRQs and then the second _irqrestore could disable them again. There would be a brief momemt where IRQs were enabled improperly. In real life, however, this function is always called with IRQs enabled and the bug does not affect runtime. Reviewed-by: Shuah Khan Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Yo4hVWcZNYzKEkIQ@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vudc_sysfs.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/usbip/vudc_sysfs.c b/drivers/usb/usbip/vudc_sysfs.c index d1cf6b51bf85..c95e6b2bfd32 100644 --- a/drivers/usb/usbip/vudc_sysfs.c +++ b/drivers/usb/usbip/vudc_sysfs.c @@ -128,7 +128,7 @@ static ssize_t usbip_sockfd_store(struct device *dev, goto unlock; } - spin_lock_irq(&udc->ud.lock); + spin_lock(&udc->ud.lock); if (udc->ud.status != SDEV_ST_AVAILABLE) { ret = -EINVAL; @@ -150,7 +150,7 @@ static ssize_t usbip_sockfd_store(struct device *dev, } /* unlock and create threads and get tasks */ - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); spin_unlock_irqrestore(&udc->lock, flags); tcp_rx = kthread_create(&v_rx_loop, &udc->ud, "vudc_rx"); @@ -173,14 +173,14 @@ static ssize_t usbip_sockfd_store(struct device *dev, /* lock and update udc->ud state */ spin_lock_irqsave(&udc->lock, flags); - spin_lock_irq(&udc->ud.lock); + spin_lock(&udc->ud.lock); udc->ud.tcp_socket = socket; udc->ud.tcp_rx = tcp_rx; udc->ud.tcp_tx = tcp_tx; udc->ud.status = SDEV_ST_USED; - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); ktime_get_ts64(&udc->start_time); v_start_timer(udc); @@ -201,12 +201,12 @@ static ssize_t usbip_sockfd_store(struct device *dev, goto unlock; } - spin_lock_irq(&udc->ud.lock); + spin_lock(&udc->ud.lock); if (udc->ud.status != SDEV_ST_USED) { ret = -EINVAL; goto unlock_ud; } - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); usbip_event_add(&udc->ud, VUDC_EVENT_DOWN); } @@ -219,7 +219,7 @@ static ssize_t usbip_sockfd_store(struct device *dev, sock_err: sockfd_put(socket); unlock_ud: - spin_unlock_irq(&udc->ud.lock); + spin_unlock(&udc->ud.lock); unlock: spin_unlock_irqrestore(&udc->lock, flags); mutex_unlock(&udc->ud.sysfs_lock); -- cgit v1.2.3 From 7afe69ad9221a77dc782b81f49cd7f99987740ed Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Fri, 27 May 2022 12:19:06 +0530 Subject: usb: common: usb-conn-gpio: Allow wakeup from system suspend Currently the VBUS/ID detection interrupts are disabled during system suspend. So the USB cable connect/disconnect event can't wakeup the system from low power mode. To allow this, we keep these interrupts enabled and configure them as wakeup capable. This behavior can be controlled through device wakeup source policy by the user space. Signed-off-by: Prashanth K Link: https://lore.kernel.org/r/1653634146-12215-1-git-send-email-quic_prashk@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/usb-conn-gpio.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index 395f9bbe3056..b39c9f1c375d 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -257,6 +257,7 @@ static int usb_conn_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); + device_set_wakeup_capable(&pdev->dev, true); /* Perform initial detection */ usb_conn_queue_dwork(info, 0); @@ -286,6 +287,14 @@ static int __maybe_unused usb_conn_suspend(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + enable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + enable_irq_wake(info->vbus_irq); + return 0; + } + if (info->id_gpiod) disable_irq(info->id_irq); if (info->vbus_gpiod) @@ -300,6 +309,14 @@ static int __maybe_unused usb_conn_resume(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + disable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + disable_irq_wake(info->vbus_irq); + return 0; + } + pinctrl_pm_select_default_state(dev); if (info->id_gpiod) -- cgit v1.2.3 From f08aa7c80dac27ee00fa6827f447597d2fba5465 Mon Sep 17 00:00:00 2001 From: Tang Bin Date: Wed, 25 May 2022 21:53:32 +0800 Subject: usb: gadget: tegra-xudc: Fix error check in tegra_xudc_powerdomain_init() dev_pm_domain_attach_by_name() may return NULL in some cases, so IS_ERR() doesn't meet the requirements. Thus fix it. Fixes: 49db427232fe ("usb: gadget: Add UDC driver for tegra XUSB device mode controller") Signed-off-by: Tang Bin Link: https://lore.kernel.org/r/20220525135332.23144-1-tangbin@cmss.chinamobile.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 6d31ccf6aee5..3c37effdfa64 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3691,15 +3691,15 @@ static int tegra_xudc_powerdomain_init(struct tegra_xudc *xudc) int err; xudc->genpd_dev_device = dev_pm_domain_attach_by_name(dev, "dev"); - if (IS_ERR(xudc->genpd_dev_device)) { - err = PTR_ERR(xudc->genpd_dev_device); + if (IS_ERR_OR_NULL(xudc->genpd_dev_device)) { + err = PTR_ERR(xudc->genpd_dev_device) ? : -ENODATA; dev_err(dev, "failed to get device power domain: %d\n", err); return err; } xudc->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "ss"); - if (IS_ERR(xudc->genpd_dev_ss)) { - err = PTR_ERR(xudc->genpd_dev_ss); + if (IS_ERR_OR_NULL(xudc->genpd_dev_ss)) { + err = PTR_ERR(xudc->genpd_dev_ss) ? : -ENODATA; dev_err(dev, "failed to get SuperSpeed power domain: %d\n", err); return err; } -- cgit v1.2.3 From 18fc7c435be3f17ea26a21b2e2312fcb9088e01f Mon Sep 17 00:00:00 2001 From: Tang Bin Date: Tue, 24 May 2022 20:14:04 +0800 Subject: usb: xhci: tegra: Fix error check In the function tegra_xusb_powerdomain_init(), dev_pm_domain_attach_by_name() may return NULL in some cases, so IS_ERR() doesn't meet the requirements. Thus fix it. Fixes: 6494a9ad86de ("usb: xhci: tegra: Add genpd support") Signed-off-by: Tang Bin Link: https://lore.kernel.org/r/20220524121404.18376-1-tangbin@cmss.chinamobile.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 996958a6565c..bdb776553826 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1010,15 +1010,15 @@ static int tegra_xusb_powerdomain_init(struct device *dev, int err; tegra->genpd_dev_host = dev_pm_domain_attach_by_name(dev, "xusb_host"); - if (IS_ERR(tegra->genpd_dev_host)) { - err = PTR_ERR(tegra->genpd_dev_host); + if (IS_ERR_OR_NULL(tegra->genpd_dev_host)) { + err = PTR_ERR(tegra->genpd_dev_host) ? : -ENODATA; dev_err(dev, "failed to get host pm-domain: %d\n", err); return err; } tegra->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "xusb_ss"); - if (IS_ERR(tegra->genpd_dev_ss)) { - err = PTR_ERR(tegra->genpd_dev_ss); + if (IS_ERR_OR_NULL(tegra->genpd_dev_ss)) { + err = PTR_ERR(tegra->genpd_dev_ss) ? : -ENODATA; dev_err(dev, "failed to get superspeed pm-domain: %d\n", err); return err; } -- cgit v1.2.3 From 4e27465a1506ea638f5adf80d50f50e911570701 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 21 May 2022 13:11:02 +0200 Subject: usb: typec: tcpm: fix typo in comment Spelling mistake (triple letters) in comment. Detected with the help of Coccinelle. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Julia Lawall Link: https://lore.kernel.org/r/20220521111145.81697-52-Julia.Lawall@inria.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3bc2f4ebd1fe..7039383eac6d 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -471,7 +471,7 @@ struct tcpm_port { /* * When set, port requests PD_P_SNK_STDBY_MW upon entering SNK_DISCOVERY and - * the actual currrent limit after RX of PD_CTRL_PSRDY for PD link, + * the actual current limit after RX of PD_CTRL_PSRDY for PD link, * SNK_READY for non-pd link. */ bool slow_charger_loop; -- cgit v1.2.3 From 7563bc7327f0671570f259790a937c1373e378c0 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 21 May 2022 13:11:42 +0200 Subject: usb: gadget: bdc: fix typo in comment Spelling mistake (triple letters) in comment. Detected with the help of Coccinelle. Acked-by: Florian Fainelli Signed-off-by: Julia Lawall Link: https://lore.kernel.org/r/20220521111145.81697-92-Julia.Lawall@inria.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.c b/drivers/usb/gadget/udc/bdc/bdc_cmd.c index 67887316a1a6..1848ced073f8 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.c +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.c @@ -307,7 +307,7 @@ int bdc_ep_clear_stall(struct bdc *bdc, int epnum) * his will reset the seq number for non EP0. */ if (epnum != 1) { - /* if the endpoint it not stallled */ + /* if the endpoint it not stalled */ if (!(ep->flags & BDC_EP_STALL)) { ret = bdc_ep_set_stall(bdc, epnum); if (ret) -- cgit v1.2.3 From 7013b2624c7b7b749b10fdce760eeb7f6af3fe99 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 21 May 2022 13:11:35 +0200 Subject: USB: chipidea: fix typo in comment Spelling mistake (triple letters) in comment. Detected with the help of Coccinelle. Signed-off-by: Julia Lawall Link: https://lore.kernel.org/r/20220521111145.81697-85-Julia.Lawall@inria.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 999c65390b7f..7daccb9c5006 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -21,7 +21,7 @@ struct imx_usbmisc_data { unsigned int pwr_pol:1; /* power polarity */ unsigned int evdo:1; /* set external vbus divider option */ unsigned int ulpi:1; /* connected to an ULPI phy */ - unsigned int hsic:1; /* HSIC controlller */ + unsigned int hsic:1; /* HSIC controller */ unsigned int ext_id:1; /* ID from exteranl event */ unsigned int ext_vbus:1; /* Vbus from exteranl event */ struct usb_phy *usb_phy; -- cgit v1.2.3 From 32b615ed4b7da586eb447212e3d2665b8aa6fdb2 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 23 May 2022 17:04:48 +0800 Subject: usb: xhci-mtk: add support optional controller reset Add support controller reset via a reset-controller usually in infracfg, it's different with the software reset by IPPC which only used to reset MAC, and it will also reset IPPC meanwhile. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220523090449.14430-3-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index b1045f534a4b..01705e559c42 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-mtk.h" @@ -550,6 +551,12 @@ static int xhci_mtk_probe(struct platform_device *pdev) if (ret) goto disable_ldos; + ret = device_reset_optional(dev); + if (ret) { + dev_err_probe(dev, ret, "failed to reset controller\n"); + goto disable_clk; + } + hcd = usb_create_hcd(driver, dev, dev_name(dev)); if (!hcd) { ret = -ENOMEM; -- cgit v1.2.3 From e84e3e99256e2aedab5f45f8e02bc98c891b9188 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 23 May 2022 17:04:49 +0800 Subject: usb: mtu3: add support controller reset Add support controller reset via a reset-controller usually in infracfg, it's different with the software reset by IPPC which only used to reset MAC, and it will also reset IPPC meanwhile. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220523090449.14430-4-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_plat.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 4309ed939178..d14494b30064 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "mtu3.h" #include "mtu3_dr.h" @@ -345,6 +346,12 @@ static int mtu3_probe(struct platform_device *pdev) dev_info(dev, "wakeup irq %d\n", ssusb->wakeup_irq); } + ret = device_reset_optional(dev); + if (ret) { + dev_err_probe(dev, ret, "failed to reset controller\n"); + goto comm_exit; + } + ssusb_ip_sw_reset(ssusb); if (IS_ENABLED(CONFIG_USB_MTU3_HOST)) -- cgit v1.2.3 From 87d76b5f1d8eeb49efa16e2018e188864cbb9401 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 30 May 2022 00:38:46 +0200 Subject: usb: gadget: uvc: calculate the number of request depending on framesize The current limitation of possible number of requests being handled is dependent on the gadget speed. It makes more sense to depend on the typical frame size when calculating the number of requests. This patch is changing this and is using the previous limits as boundaries for reasonable minimum and maximum number of requests. For a 1080p jpeg encoded video stream with a maximum imagesize of e.g. 800kB with a maxburst of 8 and an multiplier of 1 the resulting number of requests is calculated to 49. 800768 1 nreqs = ------ * -------------- ~= 49 2 (1024 * 8 * 1) Tested-by: Dan Vacura Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220529223848.105914-2-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_queue.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index 951934aa4454..ec500ee499ee 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -44,7 +44,8 @@ static int uvc_queue_setup(struct vb2_queue *vq, { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); struct uvc_video *video = container_of(queue, struct uvc_video, queue); - struct usb_composite_dev *cdev = video->uvc->func.config->cdev; + unsigned int req_size; + unsigned int nreq; if (*nbuffers > UVC_MAX_VIDEO_BUFFERS) *nbuffers = UVC_MAX_VIDEO_BUFFERS; @@ -53,10 +54,16 @@ static int uvc_queue_setup(struct vb2_queue *vq, sizes[0] = video->imagesize; - if (cdev->gadget->speed < USB_SPEED_SUPER) - video->uvc_num_requests = 4; - else - video->uvc_num_requests = 64; + req_size = video->ep->maxpacket + * max_t(unsigned int, video->ep->maxburst, 1) + * (video->ep->mult); + + /* We divide by two, to increase the chance to run + * into fewer requests for smaller framesizes. + */ + nreq = DIV_ROUND_UP(DIV_ROUND_UP(sizes[0], 2), req_size); + nreq = clamp(nreq, 4U, 64U); + video->uvc_num_requests = nreq; return 0; } -- cgit v1.2.3 From a725d0f6dfc5d3739d6499f30ec865305ba3544d Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 30 May 2022 00:38:48 +0200 Subject: usb: gadget: uvc: call uvc uvcg_warn on completed status instead of uvcg_info Likewise to the uvcvideo hostside driver, this patch is changing the usb_request message of an non zero completion handler call from dev_info to dev_warn. Reviewed-by: Laurent Pinchart Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220529223848.105914-4-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 5876bc73f929..93f42c7f800d 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -261,7 +261,7 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) break; default: - uvcg_info(&video->uvc->func, + uvcg_warn(&video->uvc->func, "VS request completed with status %d.\n", req->status); uvcg_queue_cancel(queue, 0); -- cgit v1.2.3 From 757bdf1f2fb5f1281cb13da5f24860ae81c6140e Mon Sep 17 00:00:00 2001 From: Saurav Girepunje Date: Wed, 8 Jun 2022 23:47:42 +0530 Subject: usb: musb: remove schedule work called after flush MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In musb_remove() cancel_delayed_work_sync(&musb->irq_work) is called which flush the irq_work work queue. After cancel delayed work, musb_remove() call musb_gadget_cleanup->usb_del_gadget_udc->usb_del_gadget ->usb_gadget_remove_driver->usb_gadget_udc_stop->udc ->gadget->ops->udc_stop(udc->gadget); Where musb_gadget_stop() call "schedule_delayed_work(&musb->irq_work, 0)” which is already cancel/flush. So remove the schedule_delayed_work(&musb->irq_work, 0); from musb_gadget_stop function. Signed-off-by: Saurav Girepunje Link: https://lore.kernel.org/r/YqDnxkWZV2KfZh5q@Sauravs-MacBook-Air.local Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_gadget.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 51274b87f46c..daada4b66a92 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1910,8 +1910,6 @@ static int musb_gadget_stop(struct usb_gadget *g) */ /* Force check of devctl register for PM runtime */ - schedule_delayed_work(&musb->irq_work, 0); - pm_runtime_mark_last_busy(musb->controller); pm_runtime_put_autosuspend(musb->controller); -- cgit v1.2.3 From 5e1fa6dd4caa7ce26029427647bc2f424784a559 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 9 Jun 2022 15:42:31 +0800 Subject: usb: mtu3: sync interrupt before unbind the udc Register gadget irq to avoid the interrupt handler is occurred or scheduled during the unbind flow, may happen when do android function switch stress test. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220609074233.15532-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_gadget.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 9977600616d7..d57cea62fe6b 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -680,6 +680,7 @@ int mtu3_gadget_setup(struct mtu3 *mtu) mtu->g.speed = USB_SPEED_UNKNOWN; mtu->g.sg_supported = 0; mtu->g.name = MTU3_DRIVER_NAME; + mtu->g.irq = mtu->irq; mtu->is_active = 0; mtu->delayed_status = false; -- cgit v1.2.3 From 54c4862f29725a0a20b09a10f2fa788a973713ee Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 9 Jun 2022 15:42:32 +0800 Subject: usb: mtu3: implement udc_async_callbacks of gadget operation Implement udc_async_callbacks hook function to avoid a race when unnbinding gadget drivers, refer to: 7dc0c55e9f30 ('USB: UDC core: Add udc_async_callbacks gadget op') Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220609074233.15532-2-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 1 + drivers/usb/mtu3/mtu3_gadget.c | 19 ++++++++++++++++--- drivers/usb/mtu3/mtu3_gadget_ep0.c | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 022bbdc54e68..8408e1b1a24a 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -357,6 +357,7 @@ struct mtu3 { unsigned delayed_status:1; unsigned gen2cp:1; unsigned connected:1; + unsigned async_callbacks:1; u8 address; u8 test_mode_nr; diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index d57cea62fe6b..30999b4debb8 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -592,6 +592,18 @@ mtu3_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed speed) spin_unlock_irqrestore(&mtu->lock, flags); } +static void mtu3_gadget_async_callbacks(struct usb_gadget *g, bool enable) +{ + struct mtu3 *mtu = gadget_to_mtu3(g); + unsigned long flags; + + dev_dbg(mtu->dev, "%s %s\n", __func__, enable ? "en" : "dis"); + + spin_lock_irqsave(&mtu->lock, flags); + mtu->async_callbacks = enable; + spin_unlock_irqrestore(&mtu->lock, flags); +} + static const struct usb_gadget_ops mtu3_gadget_ops = { .get_frame = mtu3_gadget_get_frame, .wakeup = mtu3_gadget_wakeup, @@ -600,6 +612,7 @@ static const struct usb_gadget_ops mtu3_gadget_ops = { .udc_start = mtu3_gadget_start, .udc_stop = mtu3_gadget_stop, .udc_set_speed = mtu3_gadget_set_speed, + .udc_async_callbacks = mtu3_gadget_async_callbacks, }; static void mtu3_state_reset(struct mtu3 *mtu) @@ -697,7 +710,7 @@ void mtu3_gadget_cleanup(struct mtu3 *mtu) void mtu3_gadget_resume(struct mtu3 *mtu) { dev_dbg(mtu->dev, "gadget RESUME\n"); - if (mtu->gadget_driver && mtu->gadget_driver->resume) { + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->resume) { spin_unlock(&mtu->lock); mtu->gadget_driver->resume(&mtu->g); spin_lock(&mtu->lock); @@ -708,7 +721,7 @@ void mtu3_gadget_resume(struct mtu3 *mtu) void mtu3_gadget_suspend(struct mtu3 *mtu) { dev_dbg(mtu->dev, "gadget SUSPEND\n"); - if (mtu->gadget_driver && mtu->gadget_driver->suspend) { + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->suspend) { spin_unlock(&mtu->lock); mtu->gadget_driver->suspend(&mtu->g); spin_lock(&mtu->lock); @@ -719,7 +732,7 @@ void mtu3_gadget_suspend(struct mtu3 *mtu) void mtu3_gadget_disconnect(struct mtu3 *mtu) { dev_dbg(mtu->dev, "gadget DISCONNECT\n"); - if (mtu->gadget_driver && mtu->gadget_driver->disconnect) { + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->disconnect) { spin_unlock(&mtu->lock); mtu->gadget_driver->disconnect(&mtu->g); spin_lock(&mtu->lock); diff --git a/drivers/usb/mtu3/mtu3_gadget_ep0.c b/drivers/usb/mtu3/mtu3_gadget_ep0.c index 0ca47212f1ec..7e7013508af6 100644 --- a/drivers/usb/mtu3/mtu3_gadget_ep0.c +++ b/drivers/usb/mtu3/mtu3_gadget_ep0.c @@ -66,7 +66,7 @@ __acquires(mtu->lock) { int ret; - if (!mtu->gadget_driver) + if (!mtu->gadget_driver || !mtu->async_callbacks) return -EOPNOTSUPP; spin_unlock(&mtu->lock); -- cgit v1.2.3 From 13118959cb1a67eb80bf06152e35e0df733615e2 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 9 Jun 2022 15:42:33 +0800 Subject: usb: mtu3: register mtu3_irq by threaded irq Use threaded irq to improve time consuming top-half Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220609074233.15532-3-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index c4a2c37abf62..3c6a670efafa 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -965,7 +965,8 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb) goto dma_mask_err; } - ret = devm_request_irq(dev, mtu->irq, mtu3_irq, 0, dev_name(dev), mtu); + ret = devm_request_threaded_irq(dev, mtu->irq, NULL, mtu3_irq, + IRQF_ONESHOT, dev_name(dev), mtu); if (ret) { dev_err(dev, "request irq %d failed!\n", mtu->irq); goto irq_err; -- cgit v1.2.3 From 5767f40053ebf599e47d688e65b259d0b6f3227a Mon Sep 17 00:00:00 2001 From: Sanket Goswami Date: Thu, 26 May 2022 12:03:04 +0530 Subject: ucsi_ccg: Do not hardcode interrupt polarity and type The current implementation supports only Level trigger with ACTIVE HIGH, which is overriding level and polarity set by the ACPI table, hence implement the common utility function to manage irq requests. Suggested-by: Heikki Krogerus Signed-off-by: Sanket Goswami Link: https://lore.kernel.org/r/20220526063305.3144352-2-Sanket.Goswami@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_ccg.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 6db7c8ddd51c..0707a7156299 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -627,6 +627,16 @@ err_clear_irq: return IRQ_HANDLED; } +static int ccg_request_irq(struct ucsi_ccg *uc) +{ + unsigned long flags = IRQF_ONESHOT; + + if (!has_acpi_companion(uc->dev)) + flags |= IRQF_TRIGGER_HIGH; + + return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc); +} + static void ccg_pm_workaround_work(struct work_struct *pm_work) { ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work)); @@ -1250,9 +1260,7 @@ static int ccg_restart(struct ucsi_ccg *uc) return status; } - status = request_threaded_irq(uc->irq, NULL, ccg_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, - dev_name(dev), uc); + status = ccg_request_irq(uc); if (status < 0) { dev_err(dev, "request_threaded_irq failed - %d\n", status); return status; @@ -1331,6 +1339,7 @@ static int ucsi_ccg_probe(struct i2c_client *client, uc->dev = dev; uc->client = client; + uc->irq = client->irq; mutex_init(&uc->lock); init_completion(&uc->complete); INIT_WORK(&uc->work, ccg_update_firmware); @@ -1366,16 +1375,12 @@ static int ucsi_ccg_probe(struct i2c_client *client, ucsi_set_drvdata(uc->ucsi, uc); - status = request_threaded_irq(client->irq, NULL, ccg_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, - dev_name(dev), uc); + status = ccg_request_irq(uc); if (status < 0) { dev_err(uc->dev, "request_threaded_irq failed - %d\n", status); goto out_ucsi_destroy; } - uc->irq = client->irq; - status = ucsi_register(uc->ucsi); if (status) goto out_free_irq; -- cgit v1.2.3 From 5fd6c4f0a649c956b2c50c18e0da765b7f5e3eff Mon Sep 17 00:00:00 2001 From: Sanket Goswami Date: Thu, 26 May 2022 12:03:05 +0530 Subject: ucsi_ccg: ACPI based I2c client enumeration for AMD ASICs Some of the AMD platforms have Cypress CCGX PD controller connected to system I2C i.e designware I2C controller. Added support to enumerate the CCGX client by adding ACPI ID to the firmware. Suggested-by: Heikki Krogerus Signed-off-by: Sanket Goswami Link: https://lore.kernel.org/r/20220526063305.3144352-3-Sanket.Goswami@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_ccg.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 0707a7156299..5c0bf48be766 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -1423,6 +1423,12 @@ static const struct i2c_device_id ucsi_ccg_device_id[] = { }; MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id); +static const struct acpi_device_id amd_i2c_ucsi_match[] = { + {"AMDI0042"}, + {} +}; +MODULE_DEVICE_TABLE(acpi, amd_i2c_ucsi_match); + static int ucsi_ccg_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -1464,6 +1470,7 @@ static struct i2c_driver ucsi_ccg_driver = { .name = "ucsi_ccg", .pm = &ucsi_ccg_pm, .dev_groups = ucsi_ccg_groups, + .acpi_match_table = amd_i2c_ucsi_match, }, .probe = ucsi_ccg_probe, .remove = ucsi_ccg_remove, -- cgit v1.2.3 From f061f43d7418cb62b8d073e221ec75d3f5b89e17 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 7 Jun 2022 13:45:22 +0200 Subject: usb: hub: port: add sysfs entry to switch port power In some cases the port of an hub needs to be disabled or switched off and on again. E.g. when the connected device needs to be re-enumerated. Or it needs to be explicitly disabled while the rest of the usb tree stays working. For this purpose this patch adds an sysfs switch to enable/disable the port on any hub. In the case the hub is supporting power switching, the power line will be disabled to the connected device. When the port gets disabled, the associated device gets disconnected and removed from the logical usb tree. No further device will be enumerated on that port until the port gets enabled again. Reviewed-by: Alan Stern Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220607114522.3359148-1-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 11 +++++ drivers/usb/core/hub.c | 39 ++++++++-------- drivers/usb/core/hub.h | 3 ++ drivers/usb/core/port.c | 83 +++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 7efe31ed3a25..568103d3376e 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -253,6 +253,17 @@ Description: only if the system firmware is capable of describing the connection between a port and its connector. +What: /sys/bus/usb/devices/...//port/disable +Date: June 2022 +Contact: Michael Grzeschik +Description: + This file controls the state of a USB port, including + Vbus power output (but only on hubs that support + power switching -- most hubs don't support it). If + a port is disabled, the port is unusable: Devices + attached to the port will not be detected, initialized, + or enumerated. + What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout Date: May 2013 Contact: Mathias Nyman diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 68e9121c1878..ba406b8d688d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -613,7 +613,7 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, return ret; } -static int hub_port_status(struct usb_hub *hub, int port1, +int usb_hub_port_status(struct usb_hub *hub, int port1, u16 *status, u16 *change) { return hub_ext_port_status(hub, port1, HUB_PORT_STATUS, @@ -1126,7 +1126,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) u16 portstatus, portchange; portstatus = portchange = 0; - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (status) goto abort; @@ -2855,7 +2855,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, &portstatus, &portchange, &ext_portstatus); else - ret = hub_port_status(hub, port1, &portstatus, + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (ret < 0) return ret; @@ -2956,7 +2956,8 @@ static int hub_port_reset(struct usb_hub *hub, int port1, * If the caller hasn't explicitly requested a warm reset, * double check and see if one is needed. */ - if (hub_port_status(hub, port1, &portstatus, &portchange) == 0) + if (usb_hub_port_status(hub, port1, &portstatus, + &portchange) == 0) if (hub_port_warm_reset_required(hub, port1, portstatus)) warm = true; @@ -3008,7 +3009,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1, * If a USB 3.0 device migrates from reset to an error * state, re-issue the warm reset. */ - if (hub_port_status(hub, port1, + if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) goto done; @@ -3074,7 +3075,7 @@ done: } /* Check if a port is power on */ -static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) +int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus) { int ret = 0; @@ -3140,13 +3141,13 @@ static int check_port_resume_type(struct usb_device *udev, } /* Is the device still present? */ else if (status || port_is_suspended(hub, portstatus) || - !port_is_power_on(hub, portstatus)) { + !usb_port_is_power_on(hub, portstatus)) { if (status >= 0) status = -ENODEV; } else if (!(portstatus & USB_PORT_STAT_CONNECTION)) { if (retries--) { usleep_range(200, 300); - status = hub_port_status(hub, port1, &portstatus, + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); goto retry; } @@ -3409,7 +3410,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) u16 portstatus, portchange; portstatus = portchange = 0; - ret = hub_port_status(hub, port1, &portstatus, + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); dev_dbg(&port_dev->dev, @@ -3587,13 +3588,13 @@ static int wait_for_connected(struct usb_device *udev, while (delay_ms < 2000) { if (status || *portstatus & USB_PORT_STAT_CONNECTION) break; - if (!port_is_power_on(hub, *portstatus)) { + if (!usb_port_is_power_on(hub, *portstatus)) { status = -ENODEV; break; } msleep(20); delay_ms += 20; - status = hub_port_status(hub, port1, portstatus, portchange); + status = usb_hub_port_status(hub, port1, portstatus, portchange); } dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); return status; @@ -3653,7 +3654,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) usb_lock_port(port_dev); /* Skip the initial Clear-Suspend step for a remote wakeup */ - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (status == 0 && !port_is_suspended(hub, portstatus)) { if (portchange & USB_PORT_STAT_C_SUSPEND) pm_wakeup_event(&udev->dev, 0); @@ -3678,7 +3679,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) * stop resume signaling. Then finish the resume * sequence. */ - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); } SuspendCleared: @@ -3791,7 +3792,7 @@ static int check_ports_changed(struct usb_hub *hub) u16 portstatus, portchange; int status; - status = hub_port_status(hub, port1, &portstatus, &portchange); + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (!status && portchange) return 1; } @@ -4554,7 +4555,7 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected) struct usb_port *port_dev = hub->ports[port1 - 1]; for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { - ret = hub_port_status(hub, port1, &portstatus, &portchange); + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); if (ret < 0) return ret; @@ -5240,7 +5241,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, * but only if the port isn't owned by someone else. */ if (hub_is_port_power_switchable(hub) - && !port_is_power_on(hub, portstatus) + && !usb_port_is_power_on(hub, portstatus) && !port_dev->port_owner) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); @@ -5557,7 +5558,7 @@ static void port_event(struct usb_hub *hub, int port1) clear_bit(port1, hub->event_bits); clear_bit(port1, hub->wakeup_bits); - if (hub_port_status(hub, port1, &portstatus, &portchange) < 0) + if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) return; if (portchange & USB_PORT_STAT_C_CONNECTION) { @@ -5594,7 +5595,7 @@ static void port_event(struct usb_hub *hub, int port1) USB_PORT_FEAT_C_OVER_CURRENT); msleep(100); /* Cool down */ hub_power_on(hub, true); - hub_port_status(hub, port1, &status, &unused); + usb_hub_port_status(hub, port1, &status, &unused); if (status & USB_PORT_STAT_OVERCURRENT) dev_err(&port_dev->dev, "over-current condition\n"); } @@ -5638,7 +5639,7 @@ static void port_event(struct usb_hub *hub, int port1) u16 unused; msleep(20); - hub_port_status(hub, port1, &portstatus, &unused); + usb_hub_port_status(hub, port1, &portstatus, &unused); dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n"); continue; } else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 22ea1f4f2d66..3fcb38099ce3 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -121,6 +121,9 @@ extern int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected); extern int usb_clear_port_feature(struct usb_device *hdev, int port1, int feature); +extern int usb_hub_port_status(struct usb_hub *hub, int port1, + u16 *status, u16 *change); +extern int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus); static inline bool hub_is_port_power_switchable(struct usb_hub *hub) { diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index d5bc36ca5b1f..38c1a4f4fdea 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -17,6 +17,88 @@ static int usb_port_block_power_off; static const struct attribute_group *port_dev_group[]; +static ssize_t disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_interface *intf = to_usb_interface(hub->intfdev); + int port1 = port_dev->portnum; + u16 portstatus, unused; + bool disabled; + int rc; + + rc = usb_autopm_get_interface(intf); + if (rc < 0) + return rc; + + usb_lock_device(hdev); + if (hub->disconnected) { + rc = -ENODEV; + goto out_hdev_lock; + } + + usb_hub_port_status(hub, port1, &portstatus, &unused); + disabled = !usb_port_is_power_on(hub, portstatus); + +out_hdev_lock: + usb_unlock_device(hdev); + usb_autopm_put_interface(intf); + + if (rc) + return rc; + + return sysfs_emit(buf, "%s\n", disabled ? "1" : "0"); +} + +static ssize_t disable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_interface *intf = to_usb_interface(hub->intfdev); + int port1 = port_dev->portnum; + bool disabled; + int rc; + + rc = strtobool(buf, &disabled); + if (rc) + return rc; + + rc = usb_autopm_get_interface(intf); + if (rc < 0) + return rc; + + usb_lock_device(hdev); + if (hub->disconnected) { + rc = -ENODEV; + goto out_hdev_lock; + } + + if (disabled && port_dev->child) + usb_disconnect(&port_dev->child); + + rc = usb_hub_set_port_power(hdev, hub, port1, !disabled); + + if (disabled) { + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + if (!port_dev->is_superspeed) + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + } + + if (!rc) + rc = count; + +out_hdev_lock: + usb_unlock_device(hdev); + usb_autopm_put_interface(intf); + + return rc; +} +static DEVICE_ATTR_RW(disable); + static ssize_t location_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -153,6 +235,7 @@ static struct attribute *port_dev_attrs[] = { &dev_attr_location.attr, &dev_attr_quirks.attr, &dev_attr_over_current_count.attr, + &dev_attr_disable.attr, NULL, }; -- cgit v1.2.3 From 662a60102c122e44fdaf5c826f7f415eb57d48ad Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 2 May 2022 16:20:56 +0300 Subject: usb: typec: Separate USB Power Delivery from USB Type-C Introducing a small device class for USB Power Delivery. The idea with it is that we do not mix any more USB Power Delivery information into the USB Type-C connectors only. This separation will make it possible to register USB Power Delivery devices also from other places, for example from USB Type-C Bridges (see USB Type-C Bridge Specification). The device class will not always deal with only the messages and objects that were negotiated with the partner, but instead messages and objects that can be used in the negotiation. That allows the USB PD devices to be shared and reconfigured. The ports can decide which objects are to be advertised to the partner before the contract is negotiated. It is also possible to allow the user space to make that decision if needed. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220502132058.86236-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-class-usb_power_delivery | 240 +++++++ drivers/usb/typec/Makefile | 2 +- drivers/usb/typec/pd.c | 708 +++++++++++++++++++++ drivers/usb/typec/pd.h | 30 + include/linux/usb/pd.h | 38 ++ include/linux/usb/typec.h | 10 + 6 files changed, 1027 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-class-usb_power_delivery create mode 100644 drivers/usb/typec/pd.c create mode 100644 drivers/usb/typec/pd.h (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-class-usb_power_delivery b/Documentation/ABI/testing/sysfs-class-usb_power_delivery new file mode 100644 index 000000000000..ce2b1b563cb3 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-usb_power_delivery @@ -0,0 +1,240 @@ +What: /sys/class/usb_power_delivery +Date: May 2022 +Contact: Heikki Krogerus +Description: + Directory for USB Power Delivery devices. + +What: /sys/class/usb_power_delivery/.../revision +Date: May 2022 +Contact: Heikki Krogerus +Description: + File showing the USB Power Delivery Specification Revision used + in communication. + +What: /sys/class/usb_power_delivery/.../version +Date: May 2022 +Contact: Heikki Krogerus +Description: + This is an optional attribute file showing the version of the + specific revision of the USB Power Delivery Specification. In + most cases the specification version is not known and the file + is not available. + +What: /sys/class/usb_power_delivery/.../source-capabilities +Date: May 2022 +Contact: Heikki Krogerus +Description: + The source capabilities message "Source_Capabilities" contains a + set of Power Data Objects (PDO), each representing a type of + power supply. The order of the PDO objects is defined in the USB + Power Delivery Specification. Each PDO - power supply - will + have its own device, and the PDO device name will start with the + object position number as the first character followed by the + power supply type name (":" as delimiter). + + /sys/class/usb_power_delivery/.../source_capabilities/: + +What: /sys/class/usb_power_delivery/.../sink-capabilities +Date: May 2022 +Contact: Heikki Krogerus +Description: + The sink capability message "Sink_Capabilities" contains a set + of Power Data Objects (PDO) just like with source capabilities, + but instead of describing the power capabilities, these objects + describe the power requirements. + + The order of the objects in the sink capability message is the + same as with the source capabilities message. + +Fixed Supplies + +What: /sys/class/usb_power_delivery/...//:fixed_supply +Date: May 2022 +Contact: Heikki Krogerus +Description: + Devices containing the attributes (the bit fields) defined for + Fixed Supplies. + + The device "1:fixed_supply" is special. USB Power Delivery + Specification dictates that the first PDO (at object position + 1), and the only mandatory PDO, is always the vSafe5V Fixed + Supply Object. vSafe5V Object has additional fields defined for + it that the other Fixed Supply Objects do not have and that are + related to the USB capabilities rather than power capabilities. + +What: /sys/class/usb_power_delivery/...//1:fixed_supply/dual_role_power +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file contains boolean value that tells does the device + support both source and sink power roles. + +What: /sys/class/usb_power_delivery/...//1:fixed_supply/usb_suspend_supported +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file shows the value of the USB Suspend Supported bit in + vSafe5V Fixed Supply Object. If the bit is set then the device + will follow the USB 2.0 and USB 3.2 rules for suspend and + resume. + +What: /sys/class/usb_power_delivery/...//1:fixed_supply/unconstrained_power +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file shows the value of the Unconstrained Power bit in + vSafe5V Fixed Supply Object. The bit is set when an external + source of power, powerful enough to power the entire system on + its own, is available for the device. + +What: /sys/class/usb_power_delivery/...//1:fixed_supply/usb_communication_capable +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file shows the value of the USB Communication Capable bit in + vSafe5V Fixed Supply Object. + +What: /sys/class/usb_power_delivery/...//1:fixed_supply/dual_role_data +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file shows the value of the Dual-Role Data bit in vSafe5V + Fixed Supply Object. Dual role data means ability act as both + USB host and USB device. + +What: /sys/class/usb_power_delivery/...//1:fixed_supply/unchunked_extended_messages_supported +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file shows the value of the Unchunked Extended Messages + Supported bit in vSafe5V Fixed Supply Object. + +What: /sys/class/usb_power_delivery/...//:fixed_supply/voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + The voltage the supply supports in millivolts. + +What: /sys/class/usb_power_delivery/.../source-capabilities/:fixed_supply/maximum_current +Date: May 2022 +Contact: Heikki Krogerus +Description: + Maximum current of the fixed source supply in milliamperes. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/:fixed_supply/operational_current +Date: May 2022 +Contact: Heikki Krogerus +Description: + Operational current of the sink in milliamperes. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/:fixed_supply/fast_role_swap_current +Date: May 2022 +Contact: Heikki Krogerus +Description: + This file contains the value of the "Fast Role Swap USB Type-C + Current" field that tells the current level the sink requires + after a Fast Role Swap. + 0 - Fast Swap not supported" + 1 - Default USB Power" + 2 - 1.5A@5V" + 3 - 3.0A@5V" + +Variable Supplies + +What: /sys/class/usb_power_delivery/...//:variable_supply +Date: May 2022 +Contact: Heikki Krogerus +Description: + Variable Power Supply PDO. + +What: /sys/class/usb_power_delivery/...//:variable_supply/maximum_voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + Maximum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/...//:variable_supply/minimum_voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + Minimum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../source-capabilities/:variable_supply/maximum_current +Date: May 2022 +Contact: Heikki Krogerus +Description: + The maximum current in milliamperes that the source can supply + at the given Voltage range. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/:variable_supply/operational_current +Date: May 2022 +Contact: Heikki Krogerus +Description: + The operational current in milliamperes that the sink requires + at the given Voltage range. + +Battery Supplies + +What: /sys/class/usb_power_delivery/...//:battery +Date: May 2022 +Contact: Heikki Krogerus +Description: + Battery PDO. + +What: /sys/class/usb_power_delivery/...//:battery/maximum_voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + Maximum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/...//:battery/minimum_voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + Minimum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/.../source-capabilities/:battery/maximum_power +Date: May 2022 +Contact: Heikki Krogerus +Description: + Maximum allowable Power in milliwatts. + +What: /sys/class/usb_power_delivery/.../sink-capabilities/:battery/operational_power +Date: May 2022 +Contact: Heikki Krogerus +Description: + The operational power that the sink requires at the given + voltage range. + +Standard Power Range (SPR) Programmable Power Supplies + +What: /sys/class/usb_power_delivery/...//:programmable_supply +Date: May 2022 +Contact: Heikki Krogerus +Description: + Programmable Power Supply (PPS) Augmented PDO (APDO). + +What: /sys/class/usb_power_delivery/...//:programmable_supply/maximum_voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + Maximum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/...//:programmable_supply/minimum_voltage +Date: May 2022 +Contact: Heikki Krogerus +Description: + Minimum Voltage in millivolts. + +What: /sys/class/usb_power_delivery/...//:programmable_supply/maximum_current +Date: May 2022 +Contact: Heikki Krogerus +Description: + Maximum Current in milliamperes. + +What: /sys/class/usb_power_delivery/.../source-capabilities/:programmable_supply/pps_power_limited +Date: May 2022 +Contact: Heikki Krogerus +Description: + The PPS Power Limited bit indicates whether or not the source + supply will exceed the rated output power if requested. diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 43626acc0aaf..2f174cd3e5df 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o -typec-y := class.o mux.o bus.o +typec-y := class.o mux.o bus.o pd.o typec-$(CONFIG_ACPI) += port-mapper.o obj-$(CONFIG_TYPEC) += altmodes/ obj-$(CONFIG_TYPEC_TCPM) += tcpm/ diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c new file mode 100644 index 000000000000..dc72005d68db --- /dev/null +++ b/drivers/usb/typec/pd.c @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Power Delivery sysfs entries + * + * Copyright (C) 2022, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include + +#include "pd.h" + +static DEFINE_IDA(pd_ida); + +static struct class pd_class = { + .name = "usb_power_delivery", + .owner = THIS_MODULE, +}; + +#define to_pdo(o) container_of(o, struct pdo, dev) + +struct pdo { + struct device dev; + int object_position; + u32 pdo; +}; + +static void pdo_release(struct device *dev) +{ + kfree(to_pdo(dev)); +} + +/* -------------------------------------------------------------------------- */ +/* Fixed Supply */ + +static ssize_t +dual_role_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DUAL_ROLE)); +} +static DEVICE_ATTR_RO(dual_role_power); + +static ssize_t +usb_suspend_supported_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_SUSPEND)); +} +static DEVICE_ATTR_RO(usb_suspend_supported); + +static ssize_t +unconstrained_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_EXTPOWER)); +} +static DEVICE_ATTR_RO(unconstrained_power); + +static ssize_t +usb_communication_capable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_USB_COMM)); +} +static DEVICE_ATTR_RO(usb_communication_capable); + +static ssize_t +dual_role_data_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DATA_SWAP)); +} +static DEVICE_ATTR_RO(dual_role_data); + +static ssize_t +unchunked_extended_messages_supported_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_UNCHUNK_EXT)); +} +static DEVICE_ATTR_RO(unchunked_extended_messages_supported); + +/* + * REVISIT: Peak Current requires access also to the RDO. +static ssize_t +peak_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + ... +} +*/ + +static ssize_t +fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", to_pdo(dev)->pdo >> PDO_FIXED_FRS_CURR_SHIFT) & 3; +} +static DEVICE_ATTR_RO(fast_role_swap_current); + +static ssize_t voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_fixed_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(voltage); + +/* Shared with Variable supplies, both source and sink */ +static ssize_t current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", pdo_max_current(to_pdo(dev)->pdo)); +} + +/* Shared with Variable type supplies */ +static struct device_attribute maximum_current_attr = { + .attr = { + .name = "maximum_current", + .mode = 0444, + }, + .show = current_show, +}; + +static struct device_attribute operational_current_attr = { + .attr = { + .name = "operational_current", + .mode = 0444, + }, + .show = current_show, +}; + +static struct attribute *source_fixed_supply_attrs[] = { + &dev_attr_dual_role_power.attr, + &dev_attr_usb_suspend_supported.attr, + &dev_attr_unconstrained_power.attr, + &dev_attr_usb_communication_capable.attr, + &dev_attr_dual_role_data.attr, + &dev_attr_unchunked_extended_messages_supported.attr, + /*&dev_attr_peak_current.attr,*/ + &dev_attr_voltage.attr, + &maximum_current_attr.attr, + NULL +}; + +static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + if (to_pdo(kobj_to_dev(kobj))->object_position && + /*attr != &dev_attr_peak_current.attr &&*/ + attr != &dev_attr_voltage.attr && + attr != &maximum_current_attr.attr && + attr != &operational_current_attr.attr) + return 0; + + return attr->mode; +} + +static const struct attribute_group source_fixed_supply_group = { + .is_visible = fixed_attr_is_visible, + .attrs = source_fixed_supply_attrs, +}; +__ATTRIBUTE_GROUPS(source_fixed_supply); + +static struct device_type source_fixed_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_fixed_supply_groups, +}; + +static struct attribute *sink_fixed_supply_attrs[] = { + &dev_attr_dual_role_power.attr, + &dev_attr_usb_suspend_supported.attr, + &dev_attr_unconstrained_power.attr, + &dev_attr_usb_communication_capable.attr, + &dev_attr_dual_role_data.attr, + &dev_attr_unchunked_extended_messages_supported.attr, + &dev_attr_fast_role_swap_current.attr, + &dev_attr_voltage.attr, + &operational_current_attr.attr, + NULL +}; + +static const struct attribute_group sink_fixed_supply_group = { + .is_visible = fixed_attr_is_visible, + .attrs = sink_fixed_supply_attrs, +}; +__ATTRIBUTE_GROUPS(sink_fixed_supply); + +static struct device_type sink_fixed_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_fixed_supply_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Variable Supply */ + +static ssize_t +maximum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_max_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(maximum_voltage); + +static ssize_t +minimum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_min_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(minimum_voltage); + +static struct attribute *source_variable_supply_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &maximum_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_variable_supply); + +static struct device_type source_variable_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_variable_supply_groups, +}; + +static struct attribute *sink_variable_supply_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &operational_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_variable_supply); + +static struct device_type sink_variable_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_variable_supply_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Battery */ + +static ssize_t +maximum_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(maximum_power); + +static ssize_t +operational_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(operational_power); + +static struct attribute *source_battery_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &dev_attr_maximum_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_battery); + +static struct device_type source_battery_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_battery_groups, +}; + +static struct attribute *sink_battery_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &dev_attr_operational_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_battery); + +static struct device_type sink_battery_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_battery_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Standard Power Range (SPR) Programmable Power Supply (PPS) */ + +static ssize_t +pps_power_limited_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & BIT(27))); +} +static DEVICE_ATTR_RO(pps_power_limited); + +static ssize_t +pps_max_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_max_voltage(to_pdo(dev)->pdo)); +} + +static ssize_t +pps_min_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_min_voltage(to_pdo(dev)->pdo)); +} + +static ssize_t +pps_max_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", pdo_pps_apdo_max_current(to_pdo(dev)->pdo)); +} + +static struct device_attribute pps_max_voltage_attr = { + .attr = { + .name = "maximum_voltage", + .mode = 0444, + }, + .show = pps_max_voltage_show, +}; + +static struct device_attribute pps_min_voltage_attr = { + .attr = { + .name = "minimum_voltage", + .mode = 0444, + }, + .show = pps_min_voltage_show, +}; + +static struct device_attribute pps_max_current_attr = { + .attr = { + .name = "maximum_current", + .mode = 0444, + }, + .show = pps_max_current_show, +}; + +static struct attribute *source_pps_attrs[] = { + &dev_attr_pps_power_limited.attr, + &pps_max_voltage_attr.attr, + &pps_min_voltage_attr.attr, + &pps_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_pps); + +static struct device_type source_pps_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_pps_groups, +}; + +static struct attribute *sink_pps_attrs[] = { + &pps_max_voltage_attr.attr, + &pps_min_voltage_attr.attr, + &pps_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_pps); + +static struct device_type sink_pps_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_pps_groups, +}; + +/* -------------------------------------------------------------------------- */ + +static const char * const supply_name[] = { + [PDO_TYPE_FIXED] = "fixed_supply", + [PDO_TYPE_BATT] = "battery", + [PDO_TYPE_VAR] = "variable_supply", +}; + +static const char * const apdo_supply_name[] = { + [APDO_TYPE_PPS] = "programmable_supply", +}; + +static struct device_type *source_type[] = { + [PDO_TYPE_FIXED] = &source_fixed_supply_type, + [PDO_TYPE_BATT] = &source_battery_type, + [PDO_TYPE_VAR] = &source_variable_supply_type, +}; + +static struct device_type *source_apdo_type[] = { + [APDO_TYPE_PPS] = &source_pps_type, +}; + +static struct device_type *sink_type[] = { + [PDO_TYPE_FIXED] = &sink_fixed_supply_type, + [PDO_TYPE_BATT] = &sink_battery_type, + [PDO_TYPE_VAR] = &sink_variable_supply_type, +}; + +static struct device_type *sink_apdo_type[] = { + [APDO_TYPE_PPS] = &sink_pps_type, +}; + +/* REVISIT: Export when EPR_*_Capabilities need to be supported. */ +static int add_pdo(struct usb_power_delivery_capabilities *cap, u32 pdo, int position) +{ + struct device_type *type; + const char *name; + struct pdo *p; + int ret; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->pdo = pdo; + p->object_position = position; + + if (pdo_type(pdo) == PDO_TYPE_APDO) { + /* FIXME: Only PPS supported for now! Skipping others. */ + if (pdo_apdo_type(pdo) > APDO_TYPE_PPS) { + dev_warn(&cap->dev, "Unknown APDO type. PDO 0x%08x\n", pdo); + kfree(p); + return 0; + } + + if (is_source(cap->role)) + type = source_apdo_type[pdo_apdo_type(pdo)]; + else + type = sink_apdo_type[pdo_apdo_type(pdo)]; + + name = apdo_supply_name[pdo_apdo_type(pdo)]; + } else { + if (is_source(cap->role)) + type = source_type[pdo_type(pdo)]; + else + type = sink_type[pdo_type(pdo)]; + + name = supply_name[pdo_type(pdo)]; + } + + p->dev.parent = &cap->dev; + p->dev.type = type; + dev_set_name(&p->dev, "%u:%s", position + 1, name); + + ret = device_register(&p->dev); + if (ret) { + put_device(&p->dev); + return ret; + } + + return 0; +} + +static int remove_pdo(struct device *dev, void *data) +{ + device_unregister(dev); + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static const char * const cap_name[] = { + [TYPEC_SINK] = "sink-capabilities", + [TYPEC_SOURCE] = "source-capabilities", +}; + +static void pd_capabilities_release(struct device *dev) +{ + kfree(to_usb_power_delivery_capabilities(dev)); +} + +static struct device_type pd_capabilities_type = { + .name = "capabilities", + .release = pd_capabilities_release, +}; + +/** + * usb_power_delivery_register_capabilities - Register a set of capabilities. + * @pd: The USB PD instance that the capabilities belong to. + * @desc: Description of the Capablities Message. + * + * This function registers a Capabilities Message described in @desc. The + * capabilities will have their own sub-directory under @pd in sysfs. + * + * The function returns pointer to struct usb_power_delivery_capabilities, or + * ERR_PRT(errno). + */ +struct usb_power_delivery_capabilities * +usb_power_delivery_register_capabilities(struct usb_power_delivery *pd, + struct usb_power_delivery_capabilities_desc *desc) +{ + struct usb_power_delivery_capabilities *cap; + int ret; + int i; + + cap = kzalloc(sizeof(*cap), GFP_KERNEL); + if (!cap) + return ERR_PTR(-ENOMEM); + + cap->pd = pd; + cap->role = desc->role; + + cap->dev.parent = &pd->dev; + cap->dev.type = &pd_capabilities_type; + dev_set_name(&cap->dev, "%s", cap_name[cap->role]); + + ret = device_register(&cap->dev); + if (ret) { + put_device(&cap->dev); + return ERR_PTR(ret); + } + + for (i = 0; i < PDO_MAX_OBJECTS && desc->pdo[i]; i++) { + ret = add_pdo(cap, desc->pdo[i], i); + if (ret) { + usb_power_delivery_unregister_capabilities(cap); + return ERR_PTR(ret); + } + } + + return cap; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_register_capabilities); + +/** + * usb_power_delivery_unregister_capabilities - Unregister a set of capabilities + * @cap: The capabilities + */ +void usb_power_delivery_unregister_capabilities(struct usb_power_delivery_capabilities *cap) +{ + if (!cap) + return; + + device_for_each_child(&cap->dev, NULL, remove_pdo); + device_unregister(&cap->dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unregister_capabilities); + +/* -------------------------------------------------------------------------- */ + +static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + return sysfs_emit(buf, "%u.%u\n", (pd->revision >> 8) & 0xff, (pd->revision >> 4) & 0xf); +} +static DEVICE_ATTR_RO(revision); + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + return sysfs_emit(buf, "%u.%u\n", (pd->version >> 8) & 0xff, (pd->version >> 4) & 0xf); +} +static DEVICE_ATTR_RO(version); + +static struct attribute *pd_attrs[] = { + &dev_attr_revision.attr, + &dev_attr_version.attr, + NULL +}; + +static umode_t pd_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(kobj_to_dev(kobj)); + + if (attr == &dev_attr_version.attr && !pd->version) + return 0; + + return attr->mode; +} + +static const struct attribute_group pd_group = { + .is_visible = pd_attr_is_visible, + .attrs = pd_attrs, +}; +__ATTRIBUTE_GROUPS(pd); + +static void pd_release(struct device *dev) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + ida_simple_remove(&pd_ida, pd->id); + kfree(pd); +} + +static struct device_type pd_type = { + .name = "usb_power_delivery", + .release = pd_release, + .groups = pd_groups, +}; + +struct usb_power_delivery *usb_power_delivery_find(const char *name) +{ + struct device *dev; + + dev = class_find_device_by_name(&pd_class, name); + + return dev ? to_usb_power_delivery(dev) : NULL; +} + +/** + * usb_power_delivery_register - Register USB Power Delivery Support. + * @parent: Parent device. + * @desc: Description of the USB PD contract. + * + * This routine can be used to register USB Power Delivery capabilities that a + * device or devices can support. These capabilities represent all the + * capabilities that can be negotiated with a partner, so not only the Power + * Capabilities that are negotiated using the USB PD Capabilities Message. + * + * The USB Power Delivery Support object that this routine generates can be used + * as the parent object for all the actual USB Power Delivery Messages and + * objects that can be negotiated with the partner. + * + * Returns handle to struct usb_power_delivery or ERR_PTR. + */ +struct usb_power_delivery * +usb_power_delivery_register(struct device *parent, struct usb_power_delivery_desc *desc) +{ + struct usb_power_delivery *pd; + int ret; + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + ret = ida_simple_get(&pd_ida, 0, 0, GFP_KERNEL); + if (ret < 0) { + kfree(pd); + return ERR_PTR(ret); + } + + pd->id = ret; + pd->revision = desc->revision; + pd->version = desc->version; + + pd->dev.parent = parent; + pd->dev.type = &pd_type; + pd->dev.class = &pd_class; + dev_set_name(&pd->dev, "pd%d", pd->id); + + ret = device_register(&pd->dev); + if (ret) { + put_device(&pd->dev); + return ERR_PTR(ret); + } + + return pd; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_register); + +/** + * usb_power_delivery_unregister - Unregister USB Power Delivery Support. + * @pd: The USB PD contract. + */ +void usb_power_delivery_unregister(struct usb_power_delivery *pd) +{ + if (IS_ERR_OR_NULL(pd)) + return; + + device_unregister(&pd->dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unregister); + +/** + * usb_power_delivery_link_device - Link device to its USB PD object. + * @pd: The USB PD instance. + * @dev: The device. + * + * This function can be used to create a symlink named "usb_power_delivery" for + * @dev that points to @pd. + */ +int usb_power_delivery_link_device(struct usb_power_delivery *pd, struct device *dev) +{ + int ret; + + if (IS_ERR_OR_NULL(pd) || !dev) + return 0; + + ret = sysfs_create_link(&dev->kobj, &pd->dev.kobj, "usb_power_delivery"); + if (ret) + return ret; + + get_device(&pd->dev); + get_device(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_link_device); + +/** + * usb_power_delivery_unlink_device - Unlink device from its USB PD object. + * @pd: The USB PD instance. + * @dev: The device. + * + * Remove the symlink that was previously created with pd_link_device(). + */ +void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct device *dev) +{ + if (IS_ERR_OR_NULL(pd) || !dev) + return; + + sysfs_remove_link(&dev->kobj, "usb_power_delivery"); + put_device(&pd->dev); + put_device(dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unlink_device); + +/* -------------------------------------------------------------------------- */ + +int __init usb_power_delivery_init(void) +{ + return class_register(&pd_class); +} + +void __exit usb_power_delivery_exit(void) +{ + ida_destroy(&pd_ida); + class_unregister(&pd_class); +} diff --git a/drivers/usb/typec/pd.h b/drivers/usb/typec/pd.h new file mode 100644 index 000000000000..049a1aad440a --- /dev/null +++ b/drivers/usb/typec/pd.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_POWER_DELIVERY__ +#define __USB_POWER_DELIVERY__ + +#include +#include + +struct usb_power_delivery { + struct device dev; + int id; + u16 revision; + u16 version; +}; + +struct usb_power_delivery_capabilities { + struct device dev; + struct usb_power_delivery *pd; + enum typec_role role; +}; + +#define to_usb_power_delivery_capabilities(o) container_of(o, struct usb_power_delivery_capabilities, dev) +#define to_usb_power_delivery(o) container_of(o, struct usb_power_delivery, dev) + +struct usb_power_delivery *usb_power_delivery_find(const char *name); + +int usb_power_delivery_init(void); +void usb_power_delivery_exit(void); + +#endif /* __USB_POWER_DELIVERY__ */ diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 96b7ff66f074..c59fb79a42e8 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -495,4 +495,42 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_P_SNK_STDBY_MW 2500 /* 2500 mW */ +#if IS_ENABLED(CONFIG_TYPEC) + +struct usb_power_delivery; + +/** + * usb_power_delivery_desc - USB Power Delivery Descriptor + * @revision: USB Power Delivery Specification Revision + * @version: USB Power Delivery Specicication Version - optional + */ +struct usb_power_delivery_desc { + u16 revision; + u16 version; +}; + +/** + * usb_power_delivery_capabilities_desc - Description of USB Power Delivery Capabilities Message + * @pdo: The Power Data Objects in the Capability Message + * @role: Power role of the capabilities + */ +struct usb_power_delivery_capabilities_desc { + u32 pdo[PDO_MAX_OBJECTS]; + enum typec_role role; +}; + +struct usb_power_delivery_capabilities * +usb_power_delivery_register_capabilities(struct usb_power_delivery *pd, + struct usb_power_delivery_capabilities_desc *desc); +void usb_power_delivery_unregister_capabilities(struct usb_power_delivery_capabilities *cap); + +struct usb_power_delivery *usb_power_delivery_register(struct device *parent, + struct usb_power_delivery_desc *desc); +void usb_power_delivery_unregister(struct usb_power_delivery *pd); + +int usb_power_delivery_link_device(struct usb_power_delivery *pd, struct device *dev); +void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct device *dev); + +#endif /* CONFIG_TYPEC */ + #endif /* __LINUX_USB_PD_H */ diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index fdf737d48b3b..45e28d14ae56 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -52,6 +52,16 @@ enum typec_role { TYPEC_SOURCE, }; +static inline int is_sink(enum typec_role role) +{ + return role == TYPEC_SINK; +} + +static inline int is_source(enum typec_role role) +{ + return role == TYPEC_SOURCE; +} + enum typec_pwr_opmode { TYPEC_PWR_MODE_USB, TYPEC_PWR_MODE_1_5A, -- cgit v1.2.3 From a7cff92f0635c794e2198a69a7ff4ecfe0decab9 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 2 May 2022 16:20:57 +0300 Subject: usb: typec: USB Power Delivery helpers for ports and partners All the USB Type-C Connector Class devices are protected, so the drivers can not directly access them. This will adds a few helpers that can be used to link the ports and partners to the correct USB Power Delivery objects. For ports a new optional sysfs attribute file is also added that can be used to select the USB Power Delivery capabilities that the port will advertise to the partner. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220502132058.86236-3-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 8 ++ drivers/usb/typec/class.c | 149 ++++++++++++++++++++++++++++ drivers/usb/typec/class.h | 4 + include/linux/usb/typec.h | 13 +++ 4 files changed, 174 insertions(+) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 75088ecad202..281b995beb05 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -141,6 +141,14 @@ Description: - "reverse": CC2 orientation - "unknown": Orientation cannot be determined. +What: /sys/class/typec//select_usb_power_delivery +Date: May 2022 +Contact: Heikki Krogerus +Description: + Lists the USB Power Delivery Capabilities that the port can + advertise to the partner. The currently used capabilities are in + brackets. Selection happens by writing to the file. + USB Type-C partner devices (eg. /sys/class/typec/port0-partner/) What: /sys/class/typec/-partner/accessory_mode diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index ee0e520707dd..bbc46b14f99a 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -15,6 +15,7 @@ #include "bus.h" #include "class.h" +#include "pd.h" static DEFINE_IDA(typec_index_ida); @@ -720,6 +721,39 @@ void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revisio } EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision); +/** + * typec_partner_set_usb_power_delivery - Declare USB Power Delivery Contract. + * @partner: The partner device. + * @pd: The USB PD instance. + * + * This routine can be used to declare USB Power Delivery Contract with @partner + * by linking @partner to @pd which contains the objects that were used during the + * negotiation of the contract. + * + * If @pd is NULL, the link is removed and the contract with @partner has ended. + */ +int typec_partner_set_usb_power_delivery(struct typec_partner *partner, + struct usb_power_delivery *pd) +{ + int ret; + + if (IS_ERR_OR_NULL(partner) || partner->pd == pd) + return 0; + + if (pd) { + ret = usb_power_delivery_link_device(pd, &partner->dev); + if (ret) + return ret; + } else { + usb_power_delivery_unlink_device(partner->pd, &partner->dev); + } + + partner->pd = pd; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_usb_power_delivery); + /** * typec_partner_set_num_altmodes - Set the number of available partner altmodes * @partner: The partner to be updated. @@ -1170,6 +1204,104 @@ EXPORT_SYMBOL_GPL(typec_unregister_cable); /* ------------------------------------------------------------------------- */ /* USB Type-C ports */ +/** + * typec_port_set_usb_power_delivery - Assign USB PD for port. + * @port: USB Type-C port. + * @pd: USB PD instance. + * + * This routine can be used to set the USB Power Delivery Capabilities for @port + * that it will advertise to the partner. + * + * If @pd is NULL, the assignment is removed. + */ +int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd) +{ + int ret; + + if (IS_ERR_OR_NULL(port) || port->pd == pd) + return 0; + + if (pd) { + ret = usb_power_delivery_link_device(pd, &port->dev); + if (ret) + return ret; + } else { + usb_power_delivery_unlink_device(port->pd, &port->dev); + } + + port->pd = pd; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_port_set_usb_power_delivery); + +static ssize_t select_usb_power_delivery_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + struct usb_power_delivery *pd; + + if (!port->ops || !port->ops->pd_set) + return -EOPNOTSUPP; + + pd = usb_power_delivery_find(buf); + if (!pd) + return -EINVAL; + + return port->ops->pd_set(port, pd); +} + +static ssize_t select_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + struct usb_power_delivery **pds; + struct usb_power_delivery *pd; + int ret = 0; + + if (!port->ops || !port->ops->pd_get) + return -EOPNOTSUPP; + + pds = port->ops->pd_get(port); + if (!pds) + return 0; + + for (pd = pds[0]; pd; pd++) { + if (pd == port->pd) + ret += sysfs_emit(buf + ret, "[%s] ", dev_name(&pd->dev)); + else + ret += sysfs_emit(buf + ret, "%s ", dev_name(&pd->dev)); + } + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RW(select_usb_power_delivery); + +static struct attribute *port_attrs[] = { + &dev_attr_select_usb_power_delivery.attr, + NULL +}; + +static umode_t port_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); + + if (!port->pd || !port->ops || !port->ops->pd_get) + return 0; + if (!port->ops->pd_set) + return 0444; + + return attr->mode; +} + +static const struct attribute_group pd_group = { + .is_visible = port_attr_is_visible, + .attrs = port_attrs, +}; + static const char * const typec_orientations[] = { [TYPEC_ORIENTATION_NONE] = "unknown", [TYPEC_ORIENTATION_NORMAL] = "normal", @@ -1581,6 +1713,7 @@ static const struct attribute_group typec_group = { static const struct attribute_group *typec_groups[] = { &typec_group, + &pd_group, NULL }; @@ -2123,6 +2256,13 @@ struct typec_port *typec_register_port(struct device *parent, return ERR_PTR(ret); } + ret = typec_port_set_usb_power_delivery(port, cap->pd); + if (ret) { + dev_err(&port->dev, "failed to link pd\n"); + device_unregister(&port->dev); + return ERR_PTR(ret); + } + ret = typec_link_ports(port); if (ret) dev_warn(&port->dev, "failed to create symlinks (%d)\n", ret); @@ -2141,6 +2281,7 @@ void typec_unregister_port(struct typec_port *port) { if (!IS_ERR_OR_NULL(port)) { typec_unlink_ports(port); + typec_port_set_usb_power_delivery(port, NULL); device_unregister(&port->dev); } } @@ -2162,8 +2303,15 @@ static int __init typec_init(void) if (ret) goto err_unregister_mux_class; + ret = usb_power_delivery_init(); + if (ret) + goto err_unregister_class; + return 0; +err_unregister_class: + class_unregister(&typec_class); + err_unregister_mux_class: class_unregister(&typec_mux_class); @@ -2176,6 +2324,7 @@ subsys_initcall(typec_init); static void __exit typec_exit(void) { + usb_power_delivery_exit(); class_unregister(&typec_class); ida_destroy(&typec_index_ida); bus_unregister(&typec_bus); diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index 0f1bd6d19d67..b531f9853bc0 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -33,6 +33,8 @@ struct typec_partner { int num_altmodes; u16 pd_revision; /* 0300H = "3.0" */ enum usb_pd_svdm_ver svdm_version; + + struct usb_power_delivery *pd; }; struct typec_port { @@ -40,6 +42,8 @@ struct typec_port { struct device dev; struct ida mode_ids; + struct usb_power_delivery *pd; + int prefer_role; enum typec_data_role data_role; enum typec_role pwr_role; diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 45e28d14ae56..7751bedcae5d 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -22,6 +22,8 @@ struct typec_altmode_ops; struct fwnode_handle; struct device; +struct usb_power_delivery; + enum typec_port_type { TYPEC_PORT_SRC, TYPEC_PORT_SNK, @@ -223,6 +225,8 @@ struct typec_partner_desc { * @pr_set: Set Power Role * @vconn_set: Source VCONN * @port_type_set: Set port type + * @pd_get: Get available USB Power Delivery Capabilities. + * @pd_set: Set USB Power Delivery Capabilities. */ struct typec_operations { int (*try_role)(struct typec_port *port, int role); @@ -231,6 +235,8 @@ struct typec_operations { int (*vconn_set)(struct typec_port *port, enum typec_role role); int (*port_type_set)(struct typec_port *port, enum typec_port_type type); + struct usb_power_delivery **(*pd_get)(struct typec_port *port); + int (*pd_set)(struct typec_port *port, struct usb_power_delivery *pd); }; enum usb_pd_svdm_ver { @@ -250,6 +256,7 @@ enum usb_pd_svdm_ver { * @accessory: Supported Accessory Modes * @fwnode: Optional fwnode of the port * @driver_data: Private pointer for driver specific info + * @pd: Optional USB Power Delivery Support * @ops: Port operations vector * * Static capabilities of a single USB Type-C port. @@ -267,6 +274,8 @@ struct typec_capability { struct fwnode_handle *fwnode; void *driver_data; + struct usb_power_delivery *pd; + const struct typec_operations *ops; }; @@ -318,4 +327,8 @@ void typec_partner_set_svdm_version(struct typec_partner *partner, enum usb_pd_svdm_ver svdm_version); int typec_get_negotiated_svdm_version(struct typec_port *port); +int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd); +int typec_partner_set_usb_power_delivery(struct typec_partner *partner, + struct usb_power_delivery *pd); + #endif /* __LINUX_USB_TYPEC_H */ -- cgit v1.2.3 From 8203d26905eee083fbe76ec449f32953aa729193 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 2 May 2022 16:20:58 +0300 Subject: usb: typec: tcpm: Register USB Power Delivery Capabilities Register both the port and partner USB Power Delivery Capabilities so they are exposed to the user space. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220502132058.86236-4-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 142 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 7039383eac6d..e1126a6c8e46 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -394,6 +394,14 @@ struct tcpm_port { bool explicit_contract; unsigned int rx_msgid; + /* USB PD objects */ + struct usb_power_delivery *pd; + struct usb_power_delivery_capabilities *port_source_caps; + struct usb_power_delivery_capabilities *port_sink_caps; + struct usb_power_delivery *partner_pd; + struct usb_power_delivery_capabilities *partner_source_caps; + struct usb_power_delivery_capabilities *partner_sink_caps; + /* Partner capabilities/requests */ u32 sink_request; u32 source_caps[PDO_MAX_OBJECTS]; @@ -2352,6 +2360,52 @@ static void tcpm_pd_handle_msg(struct tcpm_port *port, } } +static int tcpm_register_source_caps(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->negotiated_rev }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + + if (!port->partner_pd) + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) + return PTR_ERR(port->partner_pd); + + memcpy(caps.pdo, port->source_caps, sizeof(u32) * port->nr_source_caps); + caps.role = TYPEC_SOURCE; + + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + port->partner_source_caps = cap; + + return 0; +} + +static int tcpm_register_sink_caps(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->negotiated_rev }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + + if (!port->partner_pd) + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) + return PTR_ERR(port->partner_pd); + + memcpy(caps.pdo, port->sink_caps, sizeof(u32) * port->nr_sink_caps); + caps.role = TYPEC_SINK; + + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + port->partner_sink_caps = cap; + + return 0; +} + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -2381,6 +2435,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_validate_caps(port, port->source_caps, port->nr_source_caps); + tcpm_register_source_caps(port); + /* * Adjust revision in subsequent message headers, as required, * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't @@ -2488,6 +2544,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port, port->nr_sink_caps = cnt; port->sink_cap_done = true; + tcpm_register_sink_caps(port); + if (port->ams == GET_SINK_CAPABILITIES) tcpm_set_state(port, ready_state(port), 0); /* Unexpected Sink Capabilities */ @@ -3554,6 +3612,7 @@ static void tcpm_typec_connect(struct tcpm_port *port) port->partner = typec_register_partner(port->typec_port, &port->partner_desc); port->connected = true; + typec_partner_set_usb_power_delivery(port->partner, port->partner_pd); } } @@ -3622,6 +3681,7 @@ out_disable_mux: static void tcpm_typec_disconnect(struct tcpm_port *port) { if (port->connected) { + typec_partner_set_usb_power_delivery(port->partner, NULL); typec_unregister_partner(port->partner); port->partner = NULL; port->connected = false; @@ -3684,6 +3744,13 @@ static void tcpm_reset_port(struct tcpm_port *port) port->sink_cap_done = false; if (port->tcpc->enable_frs) port->tcpc->enable_frs(port->tcpc, false); + + usb_power_delivery_unregister_capabilities(port->partner_sink_caps); + port->partner_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(port->partner_source_caps); + port->partner_source_caps = NULL; + usb_power_delivery_unregister(port->partner_pd); + port->partner_pd = NULL; } static void tcpm_detach(struct tcpm_port *port) @@ -5924,6 +5991,68 @@ void tcpm_tcpc_reset(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); +static void tcpm_port_unregister_pd(struct tcpm_port *port) +{ + usb_power_delivery_unregister_capabilities(port->port_sink_caps); + port->port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(port->port_source_caps); + port->port_source_caps = NULL; + usb_power_delivery_unregister(port->pd); + port->pd = NULL; +} + +static int tcpm_port_register_pd(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->typec_caps.pd_revision }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + int ret; + + if (!port->nr_src_pdo && !port->nr_snk_pdo) + return 0; + + port->pd = usb_power_delivery_register(port->dev, &desc); + if (IS_ERR(port->pd)) { + ret = PTR_ERR(port->pd); + goto err_unregister; + } + + if (port->nr_src_pdo) { + memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->src_pdo, + port->nr_src_pdo * sizeof(u32), 0); + caps.role = TYPEC_SOURCE; + + cap = usb_power_delivery_register_capabilities(port->pd, &caps); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + + port->port_source_caps = cap; + } + + if (port->nr_snk_pdo) { + memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->snk_pdo, + port->nr_snk_pdo * sizeof(u32), 0); + caps.role = TYPEC_SINK; + + cap = usb_power_delivery_register_capabilities(port->pd, &caps); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + + port->port_sink_caps = cap; + } + + return 0; + +err_unregister: + tcpm_port_unregister_pd(port); + + return ret; +} + static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { @@ -6382,10 +6511,16 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_role_sw_put; power_supply_changed(port->psy); + err = tcpm_port_register_pd(port); + if (err) + goto out_role_sw_put; + + port->typec_caps.pd = port->pd; + port->typec_port = typec_register_port(port->dev, &port->typec_caps); if (IS_ERR(port->typec_port)) { err = PTR_ERR(port->typec_port); - goto out_role_sw_put; + goto out_unregister_pd; } typec_port_register_altmodes(port->typec_port, @@ -6400,6 +6535,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) tcpm_log(port, "%s: registered", dev_name(dev)); return port; +out_unregister_pd: + tcpm_port_unregister_pd(port); out_role_sw_put: usb_role_switch_put(port->role_sw); out_destroy_wq: @@ -6422,6 +6559,9 @@ void tcpm_unregister_port(struct tcpm_port *port) hrtimer_cancel(&port->state_machine_timer); tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); + for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) typec_unregister_altmode(port->port_altmode[i]); typec_unregister_port(port->typec_port); -- cgit v1.2.3 From ca80ca61863f70df1eb055bcccb302013d2d6308 Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Fri, 20 May 2022 17:40:45 +0530 Subject: usb: dwc3: Fix bare use of unsigned checkpatch warning Fixes the bare use of unsigned warning from checkpatch.pl in core.c by changing 'unsigned' to 'unsigned int'. Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/HK0PR01MB280160BCA168FA9FE159F02AF8D39@HK0PR01MB2801.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index a8b42530b603..2c12bbbcd55c 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -426,7 +426,7 @@ static void dwc3_free_one_event_buffer(struct dwc3 *dwc, * otherwise ERR_PTR(errno). */ static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc, - unsigned length) + unsigned int length) { struct dwc3_event_buffer *evt; @@ -469,7 +469,7 @@ static void dwc3_free_event_buffers(struct dwc3 *dwc) * Returns 0 on success otherwise negative errno. In the error case, dwc * may contain some buffers allocated but not all which were requested. */ -static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) +static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned int length) { struct dwc3_event_buffer *evt; -- cgit v1.2.3 From d1b39dd5819a0ea5e09fb2c7f7bcb2e127cdbd89 Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Fri, 20 May 2022 17:40:46 +0530 Subject: usb: dwc3: Fix a repeated word checkpatch warning Fixes a repeated word checkpatch warning in ep0.c by removing the repeated 'only' word. Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/HK0PR01MB2801996E815208393170010FF8D39@HK0PR01MB2801.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/ep0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 5d642660fd15..2a510e84eef4 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -473,7 +473,7 @@ static int dwc3_ep0_handle_device(struct dwc3 *dwc, case USB_DEVICE_REMOTE_WAKEUP: break; /* - * 9.4.1 says only only for SS, in AddressState only for + * 9.4.1 says only for SS, in AddressState only for * default control pipe */ case USB_DEVICE_U1_ENABLE: -- cgit v1.2.3 From 3085d1bd47f2bfdd0b34b8399011f2ed6292fe8c Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Fri, 20 May 2022 17:40:47 +0530 Subject: usb: dwc3: Fix typos in Kconfig Fixes the following 2 typos in Kconfig: 1. is -> as 2. progammed -> programmed Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/HK0PR01MB280151A3B2CF6C3E4DC2F9CAF8D39@HK0PR01MB2801.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index cd9a734522a7..03ededa86da1 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -9,7 +9,7 @@ config USB_DWC3 Say Y or M here if your system has a Dual Role SuperSpeed USB controller based on the DesignWare USB3 IP Core. - If you choose to build this driver is a dynamically linked + If you choose to build this driver as a dynamically linked module, the module will be called dwc3.ko. if USB_DWC3 @@ -165,7 +165,7 @@ config USB_DWC3_AM62 default USB_DWC3 help Support TI's AM62 platforms with DesignWare Core USB3 IP. - The Designware Core USB3 IP is progammed to operate in + The Designware Core USB3 IP is programmed to operate in in USB 2.0 mode only. Say 'Y' or 'M' here if you have one such device endif -- cgit v1.2.3 From e146caf303493c4f2458173d7f1598b76a9b1396 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 6 May 2022 19:18:07 +0300 Subject: usb: Avoid extra usb SET_SEL requests when enabling link power management The host needs to tell the device the exit latencies using the SET_SEL request before device initiated link powermanagement can be enabled. The exit latency values do not change after enumeration, it's enough to set them once. So do like Windows 10 and issue the SET_SEL request once just before setting the configuration. This is also the sequence described in USB 3.2 specs "9.1.2 Bus enumeration". SET_SEL is issued once before the Set Configuration request, and won't be cleared by the Set Configuration, Set Interface or ClearFeature (STALL) requests. Only warm reset, hot reset, set Address 0 clears the exit latencies. See USB 3.2 section 9.4.14 Table 9-10 Device parameters and events Add udev->lpm_devinit_allow, and set it if SET_SEL was successful. If not set, then don't try to enable device initiated LPM We used to issue a SET_SEL request every time lpm is enabled for either U1 or U2 link states, meaning a SET_SEL was issued twice after every Set Configuration and Set Interface requests, easily accumulating to over 15 SET_SEL requets during a USB3 webcam enumeration. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220506161807.3369439-1-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 60 ++++++++++++++++++-------------------------------- include/linux/usb.h | 2 ++ 2 files changed, 23 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ba406b8d688d..b7f66dcd1fe0 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3947,7 +3947,7 @@ static const char * const usb3_lpm_names[] = { * This function will fail if the SEL or PEL values for udev are greater than * the maximum allowed values for the link state to be enabled. */ -static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) +static int usb_req_set_sel(struct usb_device *udev) { struct usb_set_sel_req *sel_values; unsigned long long u1_sel; @@ -3956,7 +3956,7 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) unsigned long long u2_pel; int ret; - if (udev->state != USB_STATE_CONFIGURED) + if (!udev->parent || udev->speed < USB_SPEED_SUPER || !udev->lpm_capable) return 0; /* Convert SEL and PEL stored in ns to us */ @@ -3973,34 +3973,14 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) * latency for the link state, and could start a device-initiated * U1/U2 when the exit latencies are too high. */ - if ((state == USB3_LPM_U1 && - (u1_sel > USB3_LPM_MAX_U1_SEL_PEL || - u1_pel > USB3_LPM_MAX_U1_SEL_PEL)) || - (state == USB3_LPM_U2 && - (u2_sel > USB3_LPM_MAX_U2_SEL_PEL || - u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) { - dev_dbg(&udev->dev, "Device-initiated %s disabled due to long SEL %llu us or PEL %llu us\n", - usb3_lpm_names[state], u1_sel, u1_pel); + if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL || + u1_pel > USB3_LPM_MAX_U1_SEL_PEL || + u2_sel > USB3_LPM_MAX_U2_SEL_PEL || + u2_pel > USB3_LPM_MAX_U2_SEL_PEL) { + dev_dbg(&udev->dev, "Device-initiated U1/U2 disabled due to long SEL or PEL\n"); return -EINVAL; } - /* - * If we're enabling device-initiated LPM for one link state, - * but the other link state has a too high SEL or PEL value, - * just set those values to the max in the Set SEL request. - */ - if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL) - u1_sel = USB3_LPM_MAX_U1_SEL_PEL; - - if (u1_pel > USB3_LPM_MAX_U1_SEL_PEL) - u1_pel = USB3_LPM_MAX_U1_SEL_PEL; - - if (u2_sel > USB3_LPM_MAX_U2_SEL_PEL) - u2_sel = USB3_LPM_MAX_U2_SEL_PEL; - - if (u2_pel > USB3_LPM_MAX_U2_SEL_PEL) - u2_pel = USB3_LPM_MAX_U2_SEL_PEL; - /* * usb_enable_lpm() can be called as part of a failed device reset, * which may be initiated by an error path of a mass storage driver. @@ -4022,6 +4002,10 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) sel_values, sizeof *(sel_values), USB_CTRL_SET_TIMEOUT); kfree(sel_values); + + if (ret > 0) + udev->lpm_devinit_allow = 1; + return ret; } @@ -4137,6 +4121,9 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev, unsigned int sel; /* us */ int i, j; + if (!udev->lpm_devinit_allow) + return false; + if (state == USB3_LPM_U1) sel = DIV_ROUND_UP(udev->u1_params.sel, 1000); else if (state == USB3_LPM_U2) @@ -4185,7 +4172,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev, static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { - int timeout, ret; + int timeout; __u8 u1_mel = udev->bos->ss_cap->bU1devExitLat; __le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat; @@ -4197,17 +4184,6 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, (state == USB3_LPM_U2 && u2_mel == 0)) return; - /* - * First, let the device know about the exit latencies - * associated with the link state we're about to enable. - */ - ret = usb_req_set_sel(udev, state); - if (ret < 0) { - dev_warn(&udev->dev, "Set SEL for device-initiated %s failed.\n", - usb3_lpm_names[state]); - return; - } - /* We allow the host controller to set the U1/U2 timeout internally * first, so that it can change its schedule to account for the * additional latency to send data to a device in a lower power @@ -4487,6 +4463,11 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, return 0; } +static int usb_req_set_sel(struct usb_device *udev) +{ + return 0; +} + #endif /* CONFIG_PM */ /* @@ -5012,6 +4993,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, udev->lpm_capable = usb_device_supports_lpm(udev); udev->lpm_disable_count = 1; usb_set_lpm_parameters(udev); + usb_req_set_sel(udev); } } diff --git a/include/linux/usb.h b/include/linux/usb.h index 60bee864d897..f7a9914fc97f 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -584,6 +584,7 @@ struct usb3_lpm_parameters { * @authenticated: Crypto authentication passed * @wusb: device is Wireless USB * @lpm_capable: device supports LPM + * @lpm_devinit_allow: Allow USB3 device initiated LPM, exit latency is in range * @usb2_hw_lpm_capable: device can perform USB2 hardware LPM * @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM * @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled @@ -666,6 +667,7 @@ struct usb_device { unsigned authenticated:1; unsigned wusb:1; unsigned lpm_capable:1; + unsigned lpm_devinit_allow:1; unsigned usb2_hw_lpm_capable:1; unsigned usb2_hw_lpm_besl_capable:1; unsigned usb2_hw_lpm_enabled:1; -- cgit v1.2.3 From 055276c1320564b0192b3af323b8cc67f9b665e1 Mon Sep 17 00:00:00 2001 From: Neal Liu Date: Mon, 23 May 2022 11:01:32 +0800 Subject: usb: gadget: add Aspeed ast2600 udc driver Aspeed udc is compliant with USB2.0, supports USB High Speed and Full Speed, backward compatible with USB1.1. Supports independent DMA channel for each generic endpoint. Supports 32/256 stages descriptor mode for all generic endpoints. This driver supports full functionality including single/multiple stages descriptor mode, and exposes 1 UDC gadget driver. Signed-off-by: Neal Liu Link: https://lore.kernel.org/r/20220523030134.2977116-2-neal_liu@aspeedtech.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 7 + drivers/usb/gadget/udc/Kconfig | 13 + drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/aspeed_udc.c | 1596 +++++++++++++++++++++++++++++++++++ 4 files changed, 1617 insertions(+) create mode 100644 drivers/usb/gadget/udc/aspeed_udc.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index a6d3bd9d2a8d..fc5d2780d04c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3136,6 +3136,13 @@ S: Maintained F: Documentation/devicetree/bindings/media/aspeed-video.txt F: drivers/media/platform/aspeed/ +ASPEED USB UDC DRIVER +M: Neal Liu +L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/usb/aspeed,udc.yaml +F: drivers/usb/gadget/udc/aspeed_udc.c + ASUS NOTEBOOKS AND EEEPC ACPI/WMI EXTRAS DRIVERS M: Corentin Chary L: acpi4asus-user@lists.sourceforge.net diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 69394dc1cdfb..03535f33511b 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -463,6 +463,19 @@ config USB_TEGRA_XUDC dynamically linked module called "tegra_xudc" and force all gadget drivers to also be dynamically linked. +config USB_ASPEED_UDC + tristate "Aspeed UDC driver support" + depends on ARCH_ASPEED || COMPILE_TEST + depends on USB_LIBCOMPOSITE + help + Enables Aspeed USB2.0 Device Controller driver for AST260x + family SoCs. The controller supports 1 control endpoint and + 4 programmable endpoints. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "aspeed_udc" and force all + gadget drivers to also be dynamically linked. + source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" # diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index a21f2224e7eb..12f9e4c9eb0c 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -40,5 +40,6 @@ obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ +obj-$(CONFIG_USB_ASPEED_UDC) += aspeed_udc.o obj-$(CONFIG_USB_BDC_UDC) += bdc/ obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c new file mode 100644 index 000000000000..1fc15228ff15 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -0,0 +1,1596 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021 Aspeed Technology Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AST_UDC_NUM_ENDPOINTS (1 + 4) +#define AST_UDC_EP0_MAX_PACKET 64 /* EP0's max packet size */ +#define AST_UDC_EPn_MAX_PACKET 1024 /* Generic EPs max packet size */ +#define AST_UDC_DESCS_COUNT 256 /* Use 256 stages descriptor mode (32/256) */ +#define AST_UDC_DESC_MODE 1 /* Single/Multiple Stage(s) Descriptor Mode */ + +#define AST_UDC_EP_DMA_SIZE (AST_UDC_EPn_MAX_PACKET + 8 * AST_UDC_DESCS_COUNT) + +/***************************** + * * + * UDC register definitions * + * * + *****************************/ + +#define AST_UDC_FUNC_CTRL 0x00 /* Root Function Control & Status Register */ +#define AST_UDC_CONFIG 0x04 /* Root Configuration Setting Register */ +#define AST_UDC_IER 0x08 /* Interrupt Control Register */ +#define AST_UDC_ISR 0x0C /* Interrupt Status Register */ +#define AST_UDC_EP_ACK_IER 0x10 /* Programmable ep Pool ACK Interrupt Enable Reg */ +#define AST_UDC_EP_NAK_IER 0x14 /* Programmable ep Pool NAK Interrupt Enable Reg */ +#define AST_UDC_EP_ACK_ISR 0x18 /* Programmable ep Pool ACK Interrupt Status Reg */ +#define AST_UDC_EP_NAK_ISR 0x1C /* Programmable ep Pool NAK Interrupt Status Reg */ +#define AST_UDC_DEV_RESET 0x20 /* Device Controller Soft Reset Enable Register */ +#define AST_UDC_STS 0x24 /* USB Status Register */ +#define AST_VHUB_EP_DATA 0x28 /* Programmable ep Pool Data Toggle Value Set */ +#define AST_VHUB_ISO_TX_FAIL 0x2C /* Isochronous Transaction Fail Accumulator */ +#define AST_UDC_EP0_CTRL 0x30 /* Endpoint 0 Control/Status Register */ +#define AST_UDC_EP0_DATA_BUFF 0x34 /* Base Address of ep0 IN/OUT Data Buffer Reg */ +#define AST_UDC_SETUP0 0x80 /* Root Device Setup Data Buffer0 */ +#define AST_UDC_SETUP1 0x84 /* Root Device Setup Data Buffer1 */ + + +/* Main control reg */ +#define USB_PHY_CLK_EN BIT(31) +#define USB_FIFO_DYN_PWRD_EN BIT(19) +#define USB_EP_LONG_DESC BIT(18) +#define USB_BIST_TEST_PASS BIT(13) +#define USB_BIST_TURN_ON BIT(12) +#define USB_PHY_RESET_DIS BIT(11) +#define USB_TEST_MODE(x) ((x) << 8) +#define USB_FORCE_TIMER_HS BIT(7) +#define USB_FORCE_HS BIT(6) +#define USB_REMOTE_WAKEUP_12MS BIT(5) +#define USB_REMOTE_WAKEUP_EN BIT(4) +#define USB_AUTO_REMOTE_WAKEUP_EN BIT(3) +#define USB_STOP_CLK_IN_SUPEND BIT(2) +#define USB_UPSTREAM_FS BIT(1) +#define USB_UPSTREAM_EN BIT(0) + +/* Main config reg */ +#define UDC_CFG_SET_ADDR(x) ((x) & 0x3f) +#define UDC_CFG_ADDR_MASK (0x3f) + +/* Interrupt ctrl & status reg */ +#define UDC_IRQ_EP_POOL_NAK BIT(17) +#define UDC_IRQ_EP_POOL_ACK_STALL BIT(16) +#define UDC_IRQ_BUS_RESUME BIT(8) +#define UDC_IRQ_BUS_SUSPEND BIT(7) +#define UDC_IRQ_BUS_RESET BIT(6) +#define UDC_IRQ_EP0_IN_DATA_NAK BIT(4) +#define UDC_IRQ_EP0_IN_ACK_STALL BIT(3) +#define UDC_IRQ_EP0_OUT_NAK BIT(2) +#define UDC_IRQ_EP0_OUT_ACK_STALL BIT(1) +#define UDC_IRQ_EP0_SETUP BIT(0) +#define UDC_IRQ_ACK_ALL (0x1ff) + +/* EP isr reg */ +#define USB_EP3_ISR BIT(3) +#define USB_EP2_ISR BIT(2) +#define USB_EP1_ISR BIT(1) +#define USB_EP0_ISR BIT(0) +#define UDC_IRQ_EP_ACK_ALL (0xf) + +/*Soft reset reg */ +#define ROOT_UDC_SOFT_RESET BIT(0) + +/* USB status reg */ +#define UDC_STS_HIGHSPEED BIT(27) + +/* Programmable EP data toggle */ +#define EP_TOGGLE_SET_EPNUM(x) ((x) & 0x3) + +/* EP0 ctrl reg */ +#define EP0_GET_RX_LEN(x) ((x >> 16) & 0x7f) +#define EP0_TX_LEN(x) ((x & 0x7f) << 8) +#define EP0_RX_BUFF_RDY BIT(2) +#define EP0_TX_BUFF_RDY BIT(1) +#define EP0_STALL BIT(0) + +/************************************* + * * + * per-endpoint register definitions * + * * + *************************************/ + +#define AST_UDC_EP_CONFIG 0x00 /* Endpoint Configuration Register */ +#define AST_UDC_EP_DMA_CTRL 0x04 /* DMA Descriptor List Control/Status Register */ +#define AST_UDC_EP_DMA_BUFF 0x08 /* DMA Descriptor/Buffer Base Address */ +#define AST_UDC_EP_DMA_STS 0x0C /* DMA Descriptor List R/W Pointer and Status */ + +#define AST_UDC_EP_BASE 0x200 +#define AST_UDC_EP_OFFSET 0x10 + +/* EP config reg */ +#define EP_SET_MAX_PKT(x) ((x & 0x3ff) << 16) +#define EP_DATA_FETCH_CTRL(x) ((x & 0x3) << 14) +#define EP_AUTO_DATA_DISABLE (0x1 << 13) +#define EP_SET_EP_STALL (0x1 << 12) +#define EP_SET_EP_NUM(x) ((x & 0xf) << 8) +#define EP_SET_TYPE_MASK(x) ((x) << 5) +#define EP_TYPE_BULK (0x1) +#define EP_TYPE_INT (0x2) +#define EP_TYPE_ISO (0x3) +#define EP_DIR_OUT (0x1 << 4) +#define EP_ALLOCATED_MASK (0x7 << 1) +#define EP_ENABLE BIT(0) + +/* EP DMA ctrl reg */ +#define EP_DMA_CTRL_GET_PROC_STS(x) ((x >> 4) & 0xf) +#define EP_DMA_CTRL_STS_RX_IDLE 0x0 +#define EP_DMA_CTRL_STS_TX_IDLE 0x8 +#define EP_DMA_CTRL_IN_LONG_MODE (0x1 << 3) +#define EP_DMA_CTRL_RESET (0x1 << 2) +#define EP_DMA_SINGLE_STAGE (0x1 << 1) +#define EP_DMA_DESC_MODE (0x1 << 0) + +/* EP DMA status reg */ +#define EP_DMA_SET_TX_SIZE(x) ((x & 0x7ff) << 16) +#define EP_DMA_GET_TX_SIZE(x) (((x) >> 16) & 0x7ff) +#define EP_DMA_GET_RPTR(x) (((x) >> 8) & 0xff) +#define EP_DMA_GET_WPTR(x) ((x) & 0xff) +#define EP_DMA_SINGLE_KICK (1 << 0) /* WPTR = 1 for single mode */ + +/* EP desc reg */ +#define AST_EP_DMA_DESC_INTR_ENABLE BIT(31) +#define AST_EP_DMA_DESC_PID_DATA0 (0 << 14) +#define AST_EP_DMA_DESC_PID_DATA2 BIT(14) +#define AST_EP_DMA_DESC_PID_DATA1 (2 << 14) +#define AST_EP_DMA_DESC_PID_MDATA (3 << 14) +#define EP_DESC1_IN_LEN(x) ((x) & 0x1fff) +#define AST_EP_DMA_DESC_MAX_LEN (7680) /* Max packet length for trasmit in 1 desc */ + +struct ast_udc_request { + struct usb_request req; + struct list_head queue; + unsigned mapped:1; + unsigned int actual_dma_length; + u32 saved_dma_wptr; +}; + +#define to_ast_req(__req) container_of(__req, struct ast_udc_request, req) + +struct ast_dma_desc { + u32 des_0; + u32 des_1; +}; + +struct ast_udc_ep { + struct usb_ep ep; + + /* Request queue */ + struct list_head queue; + + struct ast_udc_dev *udc; + void __iomem *ep_reg; + void *epn_buf; + dma_addr_t epn_buf_dma; + const struct usb_endpoint_descriptor *desc; + + /* DMA Descriptors */ + struct ast_dma_desc *descs; + dma_addr_t descs_dma; + u32 descs_wptr; + u32 chunk_max; + + bool dir_in:1; + unsigned stopped:1; + bool desc_mode:1; +}; + +#define to_ast_ep(__ep) container_of(__ep, struct ast_udc_ep, ep) + +struct ast_udc_dev { + struct platform_device *pdev; + void __iomem *reg; + int irq; + spinlock_t lock; + struct clk *clk; + struct work_struct wake_work; + + /* EP0 DMA buffers allocated in one chunk */ + void *ep0_buf; + dma_addr_t ep0_buf_dma; + struct ast_udc_ep ep[AST_UDC_NUM_ENDPOINTS]; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + void __iomem *creq; + enum usb_device_state suspended_from; + int desc_mode; + + /* Force full speed only */ + bool force_usb1:1; + unsigned is_control_tx:1; + bool wakeup_en:1; +}; + +#define to_ast_dev(__g) container_of(__g, struct ast_udc_dev, gadget) + +static const char * const ast_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4" +}; + +#ifdef AST_UDC_DEBUG_ALL +#define AST_UDC_DEBUG +#define AST_SETUP_DEBUG +#define AST_EP_DEBUG +#define AST_ISR_DEBUG +#endif + +#ifdef AST_SETUP_DEBUG +#define SETUP_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define SETUP_DBG(u, fmt, ...) +#endif + +#ifdef AST_EP_DEBUG +#define EP_DBG(e, fmt, ...) \ + dev_dbg(&(e)->udc->pdev->dev, "%s():%s " fmt, __func__, \ + (e)->ep.name, ##__VA_ARGS__) +#else +#define EP_DBG(ep, fmt, ...) ((void)(ep)) +#endif + +#ifdef AST_UDC_DEBUG +#define UDC_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define UDC_DBG(u, fmt, ...) +#endif + +#ifdef AST_ISR_DEBUG +#define ISR_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define ISR_DBG(u, fmt, ...) +#endif + +/*-------------------------------------------------------------------------*/ +#define ast_udc_read(udc, offset) \ + readl((udc)->reg + (offset)) +#define ast_udc_write(udc, val, offset) \ + writel((val), (udc)->reg + (offset)) + +#define ast_ep_read(ep, reg) \ + readl((ep)->ep_reg + (reg)) +#define ast_ep_write(ep, val, reg) \ + writel((val), (ep)->ep_reg + (reg)) + +/*-------------------------------------------------------------------------*/ + +static void ast_udc_done(struct ast_udc_ep *ep, struct ast_udc_request *req, + int status) +{ + struct ast_udc_dev *udc = ep->udc; + + EP_DBG(ep, "req @%p, len (%d/%d), buf:0x%x, dir:0x%x\n", + req, req->req.actual, req->req.length, + (u32)req->req.buf, ep->dir_in); + + list_del(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + EP_DBG(ep, "done req:%p, status:%d\n", req, status); + + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); +} + +static void ast_udc_nuke(struct ast_udc_ep *ep, int status) +{ + int count = 0; + + while (!list_empty(&ep->queue)) { + struct ast_udc_request *req; + + req = list_entry(ep->queue.next, struct ast_udc_request, + queue); + ast_udc_done(ep, req, status); + count++; + } + + if (count) + EP_DBG(ep, "Nuked %d request(s)\n", count); +} + +/* + * Stop activity on all endpoints. + * Device controller for which EP activity is to be stopped. + * + * All the endpoints are stopped and any pending transfer requests if any on + * the endpoint are terminated. + */ +static void ast_udc_stop_activity(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep; + int i; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->stopped = 1; + ast_udc_nuke(ep, -ESHUTDOWN); + } +} + +static int ast_udc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + u16 maxpacket = usb_endpoint_maxp(desc); + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + u8 epnum = usb_endpoint_num(desc); + unsigned long flags; + u32 ep_conf = 0; + u8 dir_in; + u8 type; + + if (!_ep || !ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + maxpacket == 0 || maxpacket > ep->ep.maxpacket) { + EP_DBG(ep, "Failed, invalid EP enable param\n"); + return -EINVAL; + } + + if (!udc->driver) { + EP_DBG(ep, "bogus device state\n"); + return -ESHUTDOWN; + } + + EP_DBG(ep, "maxpacket:0x%x\n", maxpacket); + + spin_lock_irqsave(&udc->lock, flags); + + ep->desc = desc; + ep->stopped = 0; + ep->ep.maxpacket = maxpacket; + ep->chunk_max = AST_EP_DMA_DESC_MAX_LEN; + + if (maxpacket < AST_UDC_EPn_MAX_PACKET) + ep_conf = EP_SET_MAX_PKT(maxpacket); + + ep_conf |= EP_SET_EP_NUM(epnum); + + type = usb_endpoint_type(desc); + dir_in = usb_endpoint_dir_in(desc); + ep->dir_in = dir_in; + if (!ep->dir_in) + ep_conf |= EP_DIR_OUT; + + EP_DBG(ep, "type %d, dir_in %d\n", type, dir_in); + switch (type) { + case USB_ENDPOINT_XFER_ISOC: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_ISO); + break; + + case USB_ENDPOINT_XFER_BULK: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_BULK); + break; + + case USB_ENDPOINT_XFER_INT: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_INT); + break; + } + + ep->desc_mode = udc->desc_mode && ep->descs_dma && ep->dir_in; + if (ep->desc_mode) { + ast_ep_write(ep, EP_DMA_CTRL_RESET, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, 0, AST_UDC_EP_DMA_STS); + ast_ep_write(ep, ep->descs_dma, AST_UDC_EP_DMA_BUFF); + + /* Enable Long Descriptor Mode */ + ast_ep_write(ep, EP_DMA_CTRL_IN_LONG_MODE | EP_DMA_DESC_MODE, + AST_UDC_EP_DMA_CTRL); + + ep->descs_wptr = 0; + + } else { + ast_ep_write(ep, EP_DMA_CTRL_RESET, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, EP_DMA_SINGLE_STAGE, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, 0, AST_UDC_EP_DMA_STS); + } + + /* Cleanup data toggle just in case */ + ast_udc_write(udc, EP_TOGGLE_SET_EPNUM(epnum), AST_VHUB_EP_DATA); + + /* Enable EP */ + ast_ep_write(ep, ep_conf | EP_ENABLE, AST_UDC_EP_CONFIG); + + EP_DBG(ep, "ep_config: 0x%x\n", ast_ep_read(ep, AST_UDC_EP_CONFIG)); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_ep_disable(struct usb_ep *_ep) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + ep->ep.desc = NULL; + ep->stopped = 1; + + ast_udc_nuke(ep, -ESHUTDOWN); + ast_ep_write(ep, 0, AST_UDC_EP_CONFIG); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request *ast_udc_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_request *req; + + req = kzalloc(sizeof(struct ast_udc_request), gfp_flags); + if (!req) { + EP_DBG(ep, "request allocation failed\n"); + return NULL; + } + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void ast_udc_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct ast_udc_request *req = to_ast_req(_req); + + kfree(req); +} + +static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, + u16 tx_len, struct ast_udc_request *req) +{ + struct ast_udc_dev *udc = ep->udc; + struct device *dev = &udc->pdev->dev; + u32 offset, chunk; + int count, last; + + if (!ep->descs) { + dev_warn(dev, "%s: Empty DMA descs list failure\n", + ep->ep.name); + return -EINVAL; + } + + chunk = tx_len; + offset = count = last = 0; + + EP_DBG(ep, "req @%p, %s:%d, %s:0x%x, %s:0x%x\n", req, + "wptr", ep->descs_wptr, "dma_buf", dma_buf, + "tx_len", tx_len); + + /* Create Descriptor Lists */ + while (chunk >= 0 && !last && count < AST_UDC_DESCS_COUNT) { + + ep->descs[ep->descs_wptr].des_0 = dma_buf + offset; + + if (chunk <= ep->chunk_max) { + ep->descs[ep->descs_wptr].des_1 = chunk; + last = 1; + } else { + ep->descs[ep->descs_wptr].des_1 = ep->chunk_max; + chunk -= ep->chunk_max; + } + + EP_DBG(ep, "descs[%d]: 0x%x 0x%x, last:%d\n", + ep->descs_wptr, + ep->descs[ep->descs_wptr].des_0, + ep->descs[ep->descs_wptr].des_1, + last); + + if (count == 0) + req->saved_dma_wptr = ep->descs_wptr; + + ep->descs_wptr++; + count++; + + if (ep->descs_wptr >= AST_UDC_DESCS_COUNT) + ep->descs_wptr = 0; + + offset = ep->chunk_max * count; + } + + return 0; +} + +static void ast_udc_epn_kick(struct ast_udc_ep *ep, struct ast_udc_request *req) +{ + u32 tx_len; + u32 last; + + last = req->req.length - req->req.actual; + tx_len = last > ep->ep.maxpacket ? ep->ep.maxpacket : last; + + EP_DBG(ep, "kick req @%p, len:%d, dir:%d\n", + req, tx_len, ep->dir_in); + + ast_ep_write(ep, req->req.dma + req->req.actual, AST_UDC_EP_DMA_BUFF); + + /* Start DMA */ + ast_ep_write(ep, EP_DMA_SET_TX_SIZE(tx_len), AST_UDC_EP_DMA_STS); + ast_ep_write(ep, EP_DMA_SET_TX_SIZE(tx_len) | EP_DMA_SINGLE_KICK, + AST_UDC_EP_DMA_STS); +} + +static void ast_udc_epn_kick_desc(struct ast_udc_ep *ep, + struct ast_udc_request *req) +{ + u32 descs_max_size; + u32 tx_len; + u32 last; + + descs_max_size = AST_EP_DMA_DESC_MAX_LEN * AST_UDC_DESCS_COUNT; + + last = req->req.length - req->req.actual; + tx_len = last > descs_max_size ? descs_max_size : last; + + EP_DBG(ep, "kick req @%p, %s:%d, %s:0x%x, %s:0x%x (%d/%d), %s:0x%x\n", + req, "tx_len", tx_len, "dir_in", ep->dir_in, + "dma", req->req.dma + req->req.actual, + req->req.actual, req->req.length, + "descs_max_size", descs_max_size); + + if (!ast_dma_descriptor_setup(ep, req->req.dma + req->req.actual, + tx_len, req)) + req->actual_dma_length += tx_len; + + /* make sure CPU done everything before triggering DMA */ + mb(); + + ast_ep_write(ep, ep->descs_wptr, AST_UDC_EP_DMA_STS); + + EP_DBG(ep, "descs_wptr:%d, dstat:0x%x, dctrl:0x%x\n", + ep->descs_wptr, + ast_ep_read(ep, AST_UDC_EP_DMA_STS), + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); +} + +static void ast_udc_ep0_queue(struct ast_udc_ep *ep, + struct ast_udc_request *req) +{ + struct ast_udc_dev *udc = ep->udc; + u32 tx_len; + u32 last; + + last = req->req.length - req->req.actual; + tx_len = last > ep->ep.maxpacket ? ep->ep.maxpacket : last; + + ast_udc_write(udc, req->req.dma + req->req.actual, + AST_UDC_EP0_DATA_BUFF); + + if (ep->dir_in) { + /* IN requests, send data */ + SETUP_DBG(udc, "IN: %s:0x%x, %s:0x%x, %s:%d (%d/%d), %s:%d\n", + "buf", (u32)req->req.buf, + "dma", req->req.dma + req->req.actual, + "tx_len", tx_len, + req->req.actual, req->req.length, + "dir_in", ep->dir_in); + + req->req.actual += tx_len; + ast_udc_write(udc, EP0_TX_LEN(tx_len), AST_UDC_EP0_CTRL); + ast_udc_write(udc, EP0_TX_LEN(tx_len) | EP0_TX_BUFF_RDY, + AST_UDC_EP0_CTRL); + + } else { + /* OUT requests, receive data */ + SETUP_DBG(udc, "OUT: %s:%x, %s:%x, %s:(%d/%d), %s:%d\n", + "buf", (u32)req->req.buf, + "dma", req->req.dma + req->req.actual, + "len", req->req.actual, req->req.length, + "dir_in", ep->dir_in); + + if (!req->req.length) { + /* 0 len request, send tx as completion */ + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); + ep->dir_in = 0x1; + } else + ast_udc_write(udc, EP0_RX_BUFF_RDY, AST_UDC_EP0_CTRL); + } +} + +static int ast_udc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct ast_udc_request *req = to_ast_req(_req); + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + struct device *dev = &udc->pdev->dev; + unsigned long flags; + int rc; + + if (unlikely(!_req || !_req->complete || !_req->buf || !_ep)) { + dev_warn(dev, "Invalid EP request !\n"); + return -EINVAL; + } + + if (ep->stopped) { + dev_warn(dev, "%s is already stopped !\n", _ep->name); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&udc->lock, flags); + + list_add_tail(&req->queue, &ep->queue); + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->actual_dma_length = 0; + + rc = usb_gadget_map_request(&udc->gadget, &req->req, ep->dir_in); + if (rc) { + EP_DBG(ep, "Request mapping failure %d\n", rc); + dev_warn(dev, "Request mapping failure %d\n", rc); + goto end; + } + + EP_DBG(ep, "enqueue req @%p\n", req); + EP_DBG(ep, "l=%d, dma:0x%x, zero:%d, is_in:%d\n", + _req->length, _req->dma, _req->zero, ep->dir_in); + + /* EP0 request enqueue */ + if (ep->ep.desc == NULL) { + if ((req->req.dma % 4) != 0) { + dev_warn(dev, "EP0 req dma alignment error\n"); + return -ESHUTDOWN; + } + + ast_udc_ep0_queue(ep, req); + goto end; + } + + /* EPn request enqueue */ + if (list_is_singular(&ep->queue)) { + if (ep->desc_mode) + ast_udc_epn_kick_desc(ep, req); + else + ast_udc_epn_kick(ep, req); + } + +end: + spin_unlock_irqrestore(&udc->lock, flags); + + return rc; +} + +static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + struct ast_udc_request *req; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) { + list_del_init(&req->queue); + ast_udc_done(ep, req, -ESHUTDOWN); + _req->status = -ECONNRESET; + break; + } + } + + /* dequeue request not found */ + if (&req->req != _req) + rc = -EINVAL; + + spin_unlock_irqrestore(&udc->lock, flags); + + return rc; +} + +static int ast_udc_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + unsigned long flags; + int epnum; + u32 ctrl; + + EP_DBG(ep, "val:%d\n", value); + + spin_lock_irqsave(&udc->lock, flags); + + epnum = usb_endpoint_num(ep->desc); + + /* EP0 */ + if (epnum == 0) { + ctrl = ast_udc_read(udc, AST_UDC_EP0_CTRL); + if (value) + ctrl |= EP0_STALL; + else + ctrl &= ~EP0_STALL; + + ast_udc_write(udc, ctrl, AST_UDC_EP0_CTRL); + + } else { + /* EPn */ + ctrl = ast_udc_read(udc, AST_UDC_EP_CONFIG); + if (value) + ctrl |= EP_SET_EP_STALL; + else + ctrl &= ~EP_SET_EP_STALL; + + ast_ep_write(ep, ctrl, AST_UDC_EP_CONFIG); + + /* only epn is stopped and waits for clear */ + ep->stopped = value ? 1 : 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_ep_ops ast_udc_ep_ops = { + .enable = ast_udc_ep_enable, + .disable = ast_udc_ep_disable, + .alloc_request = ast_udc_ep_alloc_request, + .free_request = ast_udc_ep_free_request, + .queue = ast_udc_ep_queue, + .dequeue = ast_udc_ep_dequeue, + .set_halt = ast_udc_ep_set_halt, + /* there's only imprecise fifo status reporting */ +}; + +static void ast_udc_ep0_rx(struct ast_udc_dev *udc) +{ + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_RX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_tx(struct ast_udc_dev *udc) +{ + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_out(struct ast_udc_dev *udc) +{ + struct device *dev = &udc->pdev->dev; + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + u16 rx_len; + + if (list_empty(&ep->queue)) + return; + + req = list_entry(ep->queue.next, struct ast_udc_request, queue); + + rx_len = EP0_GET_RX_LEN(ast_udc_read(udc, AST_UDC_EP0_CTRL)); + req->req.actual += rx_len; + + SETUP_DBG(udc, "req %p (%d/%d)\n", req, + req->req.actual, req->req.length); + + if ((rx_len < ep->ep.maxpacket) || + (req->req.actual == req->req.length)) { + ast_udc_ep0_tx(udc); + if (!ep->dir_in) + ast_udc_done(ep, req, 0); + + } else { + if (rx_len > req->req.length) { + // Issue Fix + dev_warn(dev, "Something wrong (%d/%d)\n", + req->req.actual, req->req.length); + ast_udc_ep0_tx(udc); + ast_udc_done(ep, req, 0); + return; + } + + ep->dir_in = 0; + + /* More works */ + ast_udc_ep0_queue(ep, req); + } +} + +static void ast_udc_ep0_in(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + + if (list_empty(&ep->queue)) { + if (udc->is_control_tx) { + ast_udc_ep0_rx(udc); + udc->is_control_tx = 0; + } + + return; + } + + req = list_entry(ep->queue.next, struct ast_udc_request, queue); + + SETUP_DBG(udc, "req %p (%d/%d)\n", req, + req->req.actual, req->req.length); + + if (req->req.length == req->req.actual) { + if (req->req.length) + ast_udc_ep0_rx(udc); + + if (ep->dir_in) + ast_udc_done(ep, req, 0); + + } else { + /* More works */ + ast_udc_ep0_queue(ep, req); + } +} + +static void ast_udc_epn_handle(struct ast_udc_dev *udc, u16 ep_num) +{ + struct ast_udc_ep *ep = &udc->ep[ep_num]; + struct ast_udc_request *req; + u16 len = 0; + + if (list_empty(&ep->queue)) + return; + + req = list_first_entry(&ep->queue, struct ast_udc_request, queue); + + len = EP_DMA_GET_TX_SIZE(ast_ep_read(ep, AST_UDC_EP_DMA_STS)); + req->req.actual += len; + + EP_DBG(ep, "req @%p, length:(%d/%d), %s:0x%x\n", req, + req->req.actual, req->req.length, "len", len); + + /* Done this request */ + if (req->req.length == req->req.actual) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + + } else { + /* Check for short packet */ + if (len < ep->ep.maxpacket) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + } + } + + /* More requests */ + if (req) + ast_udc_epn_kick(ep, req); +} + +static void ast_udc_epn_handle_desc(struct ast_udc_dev *udc, u16 ep_num) +{ + struct ast_udc_ep *ep = &udc->ep[ep_num]; + struct device *dev = &udc->pdev->dev; + struct ast_udc_request *req; + u32 proc_sts, wr_ptr, rd_ptr; + u32 len_in_desc, ctrl; + u16 total_len = 0; + int i; + + if (list_empty(&ep->queue)) { + dev_warn(dev, "%s reqest queue empty !\n", ep->ep.name); + return; + } + + req = list_first_entry(&ep->queue, struct ast_udc_request, queue); + + ctrl = ast_ep_read(ep, AST_UDC_EP_DMA_CTRL); + proc_sts = EP_DMA_CTRL_GET_PROC_STS(ctrl); + + /* Check processing status is idle */ + if (proc_sts != EP_DMA_CTRL_STS_RX_IDLE && + proc_sts != EP_DMA_CTRL_STS_TX_IDLE) { + dev_warn(dev, "EP DMA CTRL: 0x%x, PS:0x%x\n", + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL), + proc_sts); + return; + } + + ctrl = ast_ep_read(ep, AST_UDC_EP_DMA_STS); + rd_ptr = EP_DMA_GET_RPTR(ctrl); + wr_ptr = EP_DMA_GET_WPTR(ctrl); + + if (rd_ptr != wr_ptr) { + dev_warn(dev, "desc list is not empty ! %s:%d, %s:%d\n", + "rptr", rd_ptr, "wptr", wr_ptr); + return; + } + + EP_DBG(ep, "rd_ptr:%d, wr_ptr:%d\n", rd_ptr, wr_ptr); + i = req->saved_dma_wptr; + + do { + len_in_desc = EP_DESC1_IN_LEN(ep->descs[i].des_1); + EP_DBG(ep, "desc[%d] len: %d\n", i, len_in_desc); + total_len += len_in_desc; + i++; + if (i >= AST_UDC_DESCS_COUNT) + i = 0; + + } while (i != wr_ptr); + + req->req.actual += total_len; + + EP_DBG(ep, "req @%p, length:(%d/%d), %s:0x%x\n", req, + req->req.actual, req->req.length, "len", total_len); + + /* Done this request */ + if (req->req.length == req->req.actual) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + + } else { + /* Check for short packet */ + if (total_len < ep->ep.maxpacket) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + } + } + + /* More requests & dma descs not setup yet */ + if (req && (req->actual_dma_length == req->req.actual)) { + EP_DBG(ep, "More requests\n"); + ast_udc_epn_kick_desc(ep, req); + } +} + +static void ast_udc_ep0_data_tx(struct ast_udc_dev *udc, u8 *tx_data, u32 len) +{ + if (len) { + memcpy(udc->ep0_buf, tx_data, len); + + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_TX_LEN(len), AST_UDC_EP0_CTRL); + ast_udc_write(udc, EP0_TX_LEN(len) | EP0_TX_BUFF_RDY, + AST_UDC_EP0_CTRL); + udc->is_control_tx = 1; + + } else + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_getstatus(struct ast_udc_dev *udc) +{ + struct usb_ctrlrequest crq; + struct ast_udc_ep *ep; + u16 status = 0; + u16 epnum = 0; + + memcpy_fromio(&crq, udc->creq, sizeof(crq)); + + switch (crq.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = 1 << USB_DEVICE_SELF_POWERED; + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + epnum = crq.wIndex & USB_ENDPOINT_NUMBER_MASK; + status = udc->ep[epnum].stopped; + break; + default: + goto stall; + } + + ep = &udc->ep[epnum]; + EP_DBG(ep, "status: 0x%x\n", status); + ast_udc_ep0_data_tx(udc, (u8 *)&status, sizeof(status)); + + return; + +stall: + EP_DBG(ep, "Can't respond request\n"); + ast_udc_write(udc, ast_udc_read(udc, AST_UDC_EP0_CTRL) | EP0_STALL, + AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_handle_setup(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + struct usb_ctrlrequest crq; + int req_num = 0; + int rc = 0; + u32 reg; + + memcpy_fromio(&crq, udc->creq, sizeof(crq)); + + SETUP_DBG(udc, "SETEUP packet: %02x/%02x/%04x/%04x/%04x\n", + crq.bRequestType, crq.bRequest, le16_to_cpu(crq.wValue), + le16_to_cpu(crq.wIndex), le16_to_cpu(crq.wLength)); + + /* + * Cleanup ep0 request(s) in queue because + * there is a new control setup comes. + */ + list_for_each_entry(req, &udc->ep[0].queue, queue) { + req_num++; + EP_DBG(ep, "there is req %p in ep0 queue !\n", req); + } + + if (req_num) + ast_udc_nuke(&udc->ep[0], -ETIMEDOUT); + + udc->ep[0].dir_in = crq.bRequestType & USB_DIR_IN; + + if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (crq.bRequest) { + case USB_REQ_SET_ADDRESS: + if (ast_udc_read(udc, AST_UDC_STS) & UDC_STS_HIGHSPEED) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + SETUP_DBG(udc, "set addr: 0x%x\n", crq.wValue); + reg = ast_udc_read(udc, AST_UDC_CONFIG); + reg &= ~UDC_CFG_ADDR_MASK; + reg |= UDC_CFG_SET_ADDR(crq.wValue); + ast_udc_write(udc, reg, AST_UDC_CONFIG); + goto req_complete; + + case USB_REQ_CLEAR_FEATURE: + SETUP_DBG(udc, "ep0: CLEAR FEATURE\n"); + goto req_driver; + + case USB_REQ_SET_FEATURE: + SETUP_DBG(udc, "ep0: SET FEATURE\n"); + goto req_driver; + + case USB_REQ_GET_STATUS: + ast_udc_getstatus(udc); + return; + + default: + goto req_driver; + } + + } + +req_driver: + if (udc->driver) { + SETUP_DBG(udc, "Forwarding %s to gadget...\n", + udc->gadget.name); + + spin_unlock(&udc->lock); + rc = udc->driver->setup(&udc->gadget, &crq); + spin_lock(&udc->lock); + + } else { + SETUP_DBG(udc, "No gadget for request !\n"); + } + + if (rc >= 0) + return; + + /* Stall if gadget failed */ + SETUP_DBG(udc, "Stalling, rc:0x%x\n", rc); + ast_udc_write(udc, ast_udc_read(udc, AST_UDC_EP0_CTRL) | EP0_STALL, + AST_UDC_EP0_CTRL); + return; + +req_complete: + SETUP_DBG(udc, "ep0: Sending IN status without data\n"); + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static irqreturn_t ast_udc_isr(int irq, void *data) +{ + struct ast_udc_dev *udc = (struct ast_udc_dev *)data; + struct ast_udc_ep *ep; + u32 isr, ep_isr; + int i; + + spin_lock(&udc->lock); + + isr = ast_udc_read(udc, AST_UDC_ISR); + if (!isr) + goto done; + + /* Ack interrupts */ + ast_udc_write(udc, isr, AST_UDC_ISR); + + if (isr & UDC_IRQ_BUS_RESET) { + ISR_DBG(udc, "UDC_IRQ_BUS_RESET\n"); + udc->gadget.speed = USB_SPEED_UNKNOWN; + + ep = &udc->ep[1]; + EP_DBG(ep, "dctrl:0x%x\n", + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); + + if (udc->driver && udc->driver->reset) { + spin_unlock(&udc->lock); + udc->driver->reset(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_BUS_SUSPEND) { + ISR_DBG(udc, "UDC_IRQ_BUS_SUSPEND\n"); + udc->suspended_from = udc->gadget.state; + usb_gadget_set_state(&udc->gadget, USB_STATE_SUSPENDED); + + if (udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_BUS_RESUME) { + ISR_DBG(udc, "UDC_IRQ_BUS_RESUME\n"); + usb_gadget_set_state(&udc->gadget, udc->suspended_from); + + if (udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_EP0_IN_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_IN_ACK_STALL\n"); + ast_udc_ep0_in(udc); + } + + if (isr & UDC_IRQ_EP0_OUT_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_OUT_ACK_STALL\n"); + ast_udc_ep0_out(udc); + } + + if (isr & UDC_IRQ_EP0_SETUP) { + ISR_DBG(udc, "UDC_IRQ_EP0_SETUP\n"); + ast_udc_ep0_handle_setup(udc); + } + + if (isr & UDC_IRQ_EP_POOL_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP_POOL_ACK_STALL\n"); + ep_isr = ast_udc_read(udc, AST_UDC_EP_ACK_ISR); + + /* Ack EP interrupts */ + ast_udc_write(udc, ep_isr, AST_UDC_EP_ACK_ISR); + + /* Handle each EP */ + for (i = 0; i < AST_UDC_NUM_ENDPOINTS - 1; i++) { + if (ep_isr & (0x1 << i)) { + ep = &udc->ep[i + 1]; + if (ep->desc_mode) + ast_udc_epn_handle_desc(udc, i + 1); + else + ast_udc_epn_handle(udc, i + 1); + } + } + } + +done: + spin_unlock(&udc->lock); + return IRQ_HANDLED; +} + +static int ast_udc_gadget_getframe(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + + return (ast_udc_read(udc, AST_UDC_STS) >> 16) & 0x7ff; +} + +static void ast_udc_wake_work(struct work_struct *work) +{ + struct ast_udc_dev *udc = container_of(work, struct ast_udc_dev, + wake_work); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "Wakeup Host !\n"); + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL); + ast_udc_write(udc, ctrl | USB_REMOTE_WAKEUP_EN, AST_UDC_FUNC_CTRL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void ast_udc_wakeup_all(struct ast_udc_dev *udc) +{ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain + * using a work queue + */ + schedule_work(&udc->wake_work); +} + +static int ast_udc_wakeup(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->wakeup_en) { + UDC_DBG(udc, "Remote Wakeup is disabled\n"); + rc = -EINVAL; + goto err; + } + + UDC_DBG(udc, "Device initiated wakeup\n"); + ast_udc_wakeup_all(udc); + +err: + spin_unlock_irqrestore(&udc->lock, flags); + return rc; +} + +/* + * Activate/Deactivate link with host + */ +static int ast_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "is_on: %d\n", is_on); + if (is_on) + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) | USB_UPSTREAM_EN; + else + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + struct ast_udc_ep *ep; + unsigned long flags; + int i; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "\n"); + udc->driver = driver; + udc->gadget.dev.of_node = udc->pdev->dev.of_node; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->stopped = 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_stop(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "\n"); + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + + ast_udc_stop_activity(udc); + usb_gadget_set_state(&udc->gadget, USB_STATE_NOTATTACHED); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops ast_udc_ops = { + .get_frame = ast_udc_gadget_getframe, + .wakeup = ast_udc_wakeup, + .pullup = ast_udc_pullup, + .udc_start = ast_udc_start, + .udc_stop = ast_udc_stop, +}; + +/* + * Support 1 Control Endpoint. + * Support multiple programmable endpoints that can be configured to + * Bulk IN/OUT, Interrupt IN/OUT, and Isochronous IN/OUT type endpoint. + */ +static void ast_udc_init_ep(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep; + int i; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->ep.name = ast_ep_name[i]; + if (i == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + ep->ep.ops = &ast_udc_ep_ops; + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + if (i == 0) { + usb_ep_set_maxpacket_limit(&ep->ep, + AST_UDC_EP0_MAX_PACKET); + continue; + } + + ep->ep_reg = udc->reg + AST_UDC_EP_BASE + + (AST_UDC_EP_OFFSET * (i - 1)); + + ep->epn_buf = udc->ep0_buf + (i * AST_UDC_EP_DMA_SIZE); + ep->epn_buf_dma = udc->ep0_buf_dma + (i * AST_UDC_EP_DMA_SIZE); + usb_ep_set_maxpacket_limit(&ep->ep, AST_UDC_EPn_MAX_PACKET); + + ep->descs = ep->epn_buf + AST_UDC_EPn_MAX_PACKET; + ep->descs_dma = ep->epn_buf_dma + AST_UDC_EPn_MAX_PACKET; + ep->descs_wptr = 0; + + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } +} + +static void ast_udc_init_dev(struct ast_udc_dev *udc) +{ + INIT_WORK(&udc->wake_work, ast_udc_wake_work); +} + +static void ast_udc_init_hw(struct ast_udc_dev *udc) +{ + u32 ctrl; + + /* Enable PHY */ + ctrl = USB_PHY_CLK_EN | USB_PHY_RESET_DIS; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + udelay(1); + ast_udc_write(udc, 0, AST_UDC_DEV_RESET); + + /* Set descriptor ring size */ + if (AST_UDC_DESCS_COUNT == 256) { + ctrl |= USB_EP_LONG_DESC; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + } + + /* Mask & ack all interrupts before installing the handler */ + ast_udc_write(udc, 0, AST_UDC_IER); + ast_udc_write(udc, UDC_IRQ_ACK_ALL, AST_UDC_ISR); + + /* Enable some interrupts */ + ctrl = UDC_IRQ_EP_POOL_ACK_STALL | UDC_IRQ_BUS_RESUME | + UDC_IRQ_BUS_SUSPEND | UDC_IRQ_BUS_RESET | + UDC_IRQ_EP0_IN_ACK_STALL | UDC_IRQ_EP0_OUT_ACK_STALL | + UDC_IRQ_EP0_SETUP; + ast_udc_write(udc, ctrl, AST_UDC_IER); + + /* Cleanup and enable ep ACK interrupts */ + ast_udc_write(udc, UDC_IRQ_EP_ACK_ALL, AST_UDC_EP_ACK_IER); + ast_udc_write(udc, UDC_IRQ_EP_ACK_ALL, AST_UDC_EP_ACK_ISR); + + ast_udc_write(udc, 0, AST_UDC_EP0_CTRL); +} + +static int ast_udc_remove(struct platform_device *pdev) +{ + struct ast_udc_dev *udc = platform_get_drvdata(pdev); + unsigned long flags; + u32 ctrl; + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) + return -EBUSY; + + spin_lock_irqsave(&udc->lock, flags); + + /* Disable upstream port connection */ + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + clk_disable_unprepare(udc->clk); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->ep0_buf) + dma_free_coherent(&pdev->dev, + AST_UDC_EP_DMA_SIZE * AST_UDC_NUM_ENDPOINTS, + udc->ep0_buf, + udc->ep0_buf_dma); + + udc->ep0_buf = NULL; + + return 0; +} + +static int ast_udc_probe(struct platform_device *pdev) +{ + enum usb_device_speed max_speed; + struct device *dev = &pdev->dev; + struct ast_udc_dev *udc; + struct resource *res; + int rc; + + udc = devm_kzalloc(&pdev->dev, sizeof(struct ast_udc_dev), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->gadget.dev.parent = dev; + udc->pdev = pdev; + spin_lock_init(&udc->lock); + + udc->gadget.ops = &ast_udc_ops; + udc->gadget.ep0 = &udc->ep[0].ep; + udc->gadget.name = "aspeed-udc"; + udc->gadget.dev.init_name = "gadget"; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + udc->reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(udc->reg)) { + dev_err(&pdev->dev, "Failed to map resources\n"); + return PTR_ERR(udc->reg); + } + + platform_set_drvdata(pdev, udc); + + udc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(udc->clk)) { + rc = PTR_ERR(udc->clk); + goto err; + } + rc = clk_prepare_enable(udc->clk); + if (rc) { + dev_err(&pdev->dev, "Failed to enable clock (0x%x)\n", rc); + goto err; + } + + /* Check if we need to limit the HW to USB1 */ + max_speed = usb_get_maximum_speed(&pdev->dev); + if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH) + udc->force_usb1 = true; + + /* + * Allocate DMA buffers for all EPs in one chunk + */ + udc->ep0_buf = dma_alloc_coherent(&pdev->dev, + AST_UDC_EP_DMA_SIZE * + AST_UDC_NUM_ENDPOINTS, + &udc->ep0_buf_dma, GFP_KERNEL); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->creq = udc->reg + AST_UDC_SETUP0; + + /* + * Support single stage mode or 32/256 stages descriptor mode. + * Set default as Descriptor Mode. + */ + udc->desc_mode = AST_UDC_DESC_MODE; + + dev_info(&pdev->dev, "DMA %s\n", udc->desc_mode ? + "descriptor mode" : "single mode"); + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + /* Initialized udc ep */ + ast_udc_init_ep(udc); + + /* Initialized udc device */ + ast_udc_init_dev(udc); + + /* Initialized udc hardware */ + ast_udc_init_hw(udc); + + /* Find interrupt and install handler */ + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) { + dev_err(&pdev->dev, "Failed to get interrupt\n"); + rc = udc->irq; + goto err; + } + + rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0, + KBUILD_MODNAME, udc); + if (rc) { + dev_err(&pdev->dev, "Failed to request interrupt\n"); + goto err; + } + + rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (rc) { + dev_err(&pdev->dev, "Failed to add gadget udc\n"); + goto err; + } + + dev_info(&pdev->dev, "Initialized udc in USB%s mode\n", + udc->force_usb1 ? "1" : "2"); + + return 0; + +err: + dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc); + ast_udc_remove(pdev); + + return rc; +} + +static const struct of_device_id ast_udc_of_dt_ids[] = { + { .compatible = "aspeed,ast2600-udc", }, + {} +}; + +MODULE_DEVICE_TABLE(of, ast_udc_of_dt_ids); + +static struct platform_driver ast_udc_driver = { + .probe = ast_udc_probe, + .remove = ast_udc_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ast_udc_of_dt_ids, + }, +}; + +module_platform_driver(ast_udc_driver); + +MODULE_DESCRIPTION("ASPEED UDC driver"); +MODULE_AUTHOR("Neal Liu "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 34b9715b7caee2e2b7d74bb4230f2be2c2765c0a Mon Sep 17 00:00:00 2001 From: Xiang Wangx Date: Fri, 17 Jun 2022 00:38:30 +0800 Subject: thunderbolt: Fix typo in comment Delete the redundant word 'the'. Signed-off-by: Xiang Wangx Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h index e8c64898dfce..7c7d80f96c0c 100644 --- a/drivers/thunderbolt/ctl.h +++ b/drivers/thunderbolt/ctl.h @@ -35,7 +35,7 @@ struct tb_cfg_result { * If err = 1 then this is the port that send the * error. * If err = 0 and if this was a cfg_read/write then - * this is the the upstream port of the responding + * this is the upstream port of the responding * switch. * Otherwise the field is set to zero. */ -- cgit v1.2.3 From fb119dcb97f43cf2a6164094209a08e7e6cd6f08 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 14 Jun 2022 18:07:25 -0700 Subject: Revert "usb: dwc3: Remove the checks of -ENOSYS" This reverts commit df22ecc41b54def624735b83784857e708bd1502. If CONFIG_GENERIC_PHY is not enabled, then the devm_phy_get() returns -ENOSYS. Don't remove this check. Fixes: df22ecc41b54 ("usb: dwc3: Remove the checks of -ENOSYS") Cc: Kushagra Verma Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/eb1df7ef954b5af093c0528982db52a41df18615.1655255152.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 1bd87da0bda7..cb8742f2a4b0 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1318,7 +1318,7 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) dwc->usb2_generic_phy = devm_phy_get(dev, "usb2-phy"); if (IS_ERR(dwc->usb2_generic_phy)) { ret = PTR_ERR(dwc->usb2_generic_phy); - if (ret == -ENODEV) + if (ret == -ENOSYS || ret == -ENODEV) dwc->usb2_generic_phy = NULL; else return dev_err_probe(dev, ret, "no usb2 phy configured\n"); @@ -1327,7 +1327,7 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) dwc->usb3_generic_phy = devm_phy_get(dev, "usb3-phy"); if (IS_ERR(dwc->usb3_generic_phy)) { ret = PTR_ERR(dwc->usb3_generic_phy); - if (ret == -ENODEV) + if (ret == -ENOSYS || ret == -ENODEV) dwc->usb3_generic_phy = NULL; else return dev_err_probe(dev, ret, "no usb3 phy configured\n"); -- cgit v1.2.3 From 3d393f0303b5120aa8c98a8ee70535ea9604ef20 Mon Sep 17 00:00:00 2001 From: Zheng Bin Date: Thu, 16 Jun 2022 21:35:08 +0800 Subject: usb: gadget: aspeed_udc: fix missing spin_unlock_irqrestore in ast_udc_ep_queue ast_udc_ep_queue misses spin_unlock_irqrestore in an error path, this patch fixes that. Fixes: 055276c13205 ("usb: gadget: add Aspeed ast2600 udc driver") Reviewed-by: Neal Liu Signed-off-by: Zheng Bin Link: https://lore.kernel.org/r/20220616133508.3655864-1-zhengbin13@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed_udc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 1fc15228ff15..6c91f7f288a2 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -665,7 +665,8 @@ static int ast_udc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, if (ep->ep.desc == NULL) { if ((req->req.dma % 4) != 0) { dev_warn(dev, "EP0 req dma alignment error\n"); - return -ESHUTDOWN; + rc = -ESHUTDOWN; + goto end; } ast_udc_ep0_queue(ep, req); -- cgit v1.2.3 From c09b1f372e746aeeb61ef8ffe0fea3970fd9273e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 18 Jun 2022 11:54:20 +0300 Subject: usb: gadget: aspeed_udc: cleanup loop in ast_dma_descriptor_setup() The "chunk >= 0" condition does not work because count is a u32. Also, really we shouldn't enter the loop when "chunk" is zero. Once that condition is fixed then there is no need for the "last" variable. I reversed the "if (chunk <= ep->chunk_max)" as well. The new loop is much simpler. Fixes: 055276c13205 ("usb: gadget: add Aspeed ast2600 udc driver") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Yq2SvM2bbrtSd1H9@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed_udc.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 6c91f7f288a2..4dead40895dd 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -476,8 +476,8 @@ static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, { struct ast_udc_dev *udc = ep->udc; struct device *dev = &udc->pdev->dev; - u32 offset, chunk; - int count, last; + int chunk, count; + u32 offset; if (!ep->descs) { dev_warn(dev, "%s: Empty DMA descs list failure\n", @@ -486,30 +486,28 @@ static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, } chunk = tx_len; - offset = count = last = 0; + offset = count = 0; EP_DBG(ep, "req @%p, %s:%d, %s:0x%x, %s:0x%x\n", req, "wptr", ep->descs_wptr, "dma_buf", dma_buf, "tx_len", tx_len); /* Create Descriptor Lists */ - while (chunk >= 0 && !last && count < AST_UDC_DESCS_COUNT) { + while (chunk > 0 && count < AST_UDC_DESCS_COUNT) { ep->descs[ep->descs_wptr].des_0 = dma_buf + offset; - if (chunk <= ep->chunk_max) { - ep->descs[ep->descs_wptr].des_1 = chunk; - last = 1; - } else { + if (chunk > ep->chunk_max) ep->descs[ep->descs_wptr].des_1 = ep->chunk_max; - chunk -= ep->chunk_max; - } + else + ep->descs[ep->descs_wptr].des_1 = chunk; + + chunk -= ep->chunk_max; - EP_DBG(ep, "descs[%d]: 0x%x 0x%x, last:%d\n", + EP_DBG(ep, "descs[%d]: 0x%x 0x%x\n", ep->descs_wptr, ep->descs[ep->descs_wptr].des_0, - ep->descs[ep->descs_wptr].des_1, - last); + ep->descs[ep->descs_wptr].des_1); if (count == 0) req->saved_dma_wptr = ep->descs_wptr; -- cgit v1.2.3 From e2900f7466ddf013686520f2d23c3a9d50a6a516 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 15 Jun 2022 08:35:18 +0100 Subject: usb: gadget: ast2600: Fix a couple of spelling mistakes There are a couple of spelling mistakes, one in a dev_warn message and another in a SETUP_DBG message. Fix these and remove an extraneous white space too. Acked-by: Neal Liu Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20220615073518.192827-1-colin.i.king@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed_udc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 4dead40895dd..d771612e6387 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -903,7 +903,7 @@ static void ast_udc_epn_handle_desc(struct ast_udc_dev *udc, u16 ep_num) int i; if (list_empty(&ep->queue)) { - dev_warn(dev, "%s reqest queue empty !\n", ep->ep.name); + dev_warn(dev, "%s request queue empty!\n", ep->ep.name); return; } @@ -1035,7 +1035,7 @@ static void ast_udc_ep0_handle_setup(struct ast_udc_dev *udc) memcpy_fromio(&crq, udc->creq, sizeof(crq)); - SETUP_DBG(udc, "SETEUP packet: %02x/%02x/%04x/%04x/%04x\n", + SETUP_DBG(udc, "SETUP packet: %02x/%02x/%04x/%04x/%04x\n", crq.bRequestType, crq.bRequest, le16_to_cpu(crq.wValue), le16_to_cpu(crq.wIndex), le16_to_cpu(crq.wLength)); -- cgit v1.2.3 From 44830e11ae9e56120e9d8edba447d0d0c44bfbbc Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Thu, 16 Jun 2022 17:04:10 +0800 Subject: usb: gadget: Remove unnecessary print function dev_err() The print function dev_err() is redundant because platform_get_irq() already prints an error. This was found by coccicheck: ./drivers/usb/gadget/udc/aspeed_udc.c:1546:2-9: line 1546 is redundant because platform_get_irq() already prints an error. Acked-by: Neal Liu Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/20220616090410.128483-1-jiapeng.chong@linux.alibaba.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed_udc.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index d771612e6387..d75a4e070bf7 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -1542,7 +1542,6 @@ static int ast_udc_probe(struct platform_device *pdev) /* Find interrupt and install handler */ udc->irq = platform_get_irq(pdev, 0); if (udc->irq < 0) { - dev_err(&pdev->dev, "Failed to get interrupt\n"); rc = udc->irq; goto err; } -- cgit v1.2.3 From 7a96b6ea90a439f73e42b640eef2386c2c644570 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 13 Jun 2022 12:46:42 +0100 Subject: usb: musb: Add support for PolarFire SoC's musb controller Add support for Microchips's PolarFire SoC's musb controller in host, peripheral and otg mode. Tested-by: Valentina Fernandez Signed-off-by: Conor Dooley Link: https://lore.kernel.org/r/20220613114642.1615292-2-conor.dooley@microchip.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/Kconfig | 13 ++- drivers/usb/musb/Makefile | 1 + drivers/usb/musb/mpfs.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/musb/mpfs.c (limited to 'drivers') diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 4d61df6a9b5c..f906dfd360d3 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -123,6 +123,17 @@ config USB_MUSB_MEDIATEK select GENERIC_PHY select USB_ROLE_SWITCH +config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV + select USB_MUSB_DUAL_ROLE + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + + This support is also available as a module. If so, the module + will be called mpfs. + comment "MUSB DMA mode" config MUSB_PIO_ONLY @@ -146,7 +157,7 @@ config USB_UX500_DMA config USB_INVENTRA_DMA bool 'Inventra' - depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 + depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 || USB_MUSB_POLARFIRE_SOC help Enable DMA transfers using Mentor's engine. diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index 932247360a9f..51dd54a8de49 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_USB_MUSB_UX500) += ux500.o obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o obj-$(CONFIG_USB_MUSB_MEDIATEK) += mediatek.o +obj-$(CONFIG_USB_MUSB_POLARFIRE_SOC) += mpfs.o # the kconfig must guarantee that only one of the # possible I/O schemes will be enabled at a time ... diff --git a/drivers/usb/musb/mpfs.c b/drivers/usb/musb/mpfs.c new file mode 100644 index 000000000000..99666ef8af06 --- /dev/null +++ b/drivers/usb/musb/mpfs.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PolarFire SoC (MPFS) MUSB Glue Layer + * + * Copyright (c) 2020-2022 Microchip Corporation. All rights reserved. + * Based on {omap2430,tusb6010,ux500}.c + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "musb_core.h" +#include "musb_dma.h" + +#define MPFS_MUSB_MAX_EP_NUM 8 +#define MPFS_MUSB_RAM_BITS 12 + +struct mpfs_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *phy; + struct clk *clk; +}; + +static struct musb_fifo_cfg mpfs_musb_mode_cfg[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 1024, }, + { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 4096, }, +}; + +static const struct musb_hdrc_config mpfs_musb_hdrc_config = { + .fifo_cfg = mpfs_musb_mode_cfg, + .fifo_cfg_size = ARRAY_SIZE(mpfs_musb_mode_cfg), + .multipoint = true, + .dyn_fifo = true, + .num_eps = MPFS_MUSB_MAX_EP_NUM, + .ram_bits = MPFS_MUSB_RAM_BITS, +}; + +static irqreturn_t mpfs_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) { + musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb); + musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx); + musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx); + ret = musb_interrupt(musb); + } + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static void mpfs_musb_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + + /* + * HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + musb->is_active = 1; + musb->xceiv->otg->default_a = 1; + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + MUSB_HST_MODE(musb); + } else { + musb->is_active = 0; + + /* + * NOTE: skipping A_WAIT_VFALL -> A_IDLE and + * jumping right to B_IDLE... + */ + musb->xceiv->otg->default_a = 0; + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + devctl &= ~MUSB_DEVCTL_SESSION; + + MUSB_DEV_MODE(musb); + } + + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x\n", + usb_otg_state_string(musb->xceiv->otg->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int mpfs_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + + musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(musb->xceiv)) { + dev_err(dev, "HS UDC: no transceiver configured\n"); + return PTR_ERR(musb->xceiv); + } + + musb->dyn_fifo = true; + musb->isr = mpfs_musb_interrupt; + + musb_platform_set_vbus(musb, 1); + + return 0; +} + +static const struct musb_platform_ops mpfs_ops = { + .quirks = MUSB_DMA_INVENTRA, + .init = mpfs_musb_init, + .fifo_mode = 2, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create, + .dma_exit = musbhs_dma_controller_destroy, +#endif + .set_vbus = mpfs_musb_set_vbus +}; + +static int mpfs_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mpfs_glue *glue; + struct platform_device *musb_pdev; + struct device *dev = &pdev->dev; + struct clk *clk; + int ret; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + musb_pdev = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb_pdev) { + dev_err(dev, "failed to allocate musb device\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err_phy_release; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err_phy_release; + } + + musb_pdev->dev.parent = dev; + musb_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(39); + musb_pdev->dev.dma_mask = &musb_pdev->dev.coherent_dma_mask; + device_set_of_node_from_dev(&musb_pdev->dev, dev); + + glue->dev = dev; + glue->musb = musb_pdev; + glue->clk = clk; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err_clk_disable; + + pdata->config = &mpfs_musb_hdrc_config; + pdata->platform_ops = &mpfs_ops; + + pdata->mode = usb_get_dr_mode(dev); + if (pdata->mode == USB_DR_MODE_UNKNOWN) { + dev_info(dev, "No dr_mode property found, defaulting to otg\n"); + pdata->mode = USB_DR_MODE_OTG; + } + + glue->phy = usb_phy_generic_register(); + if (IS_ERR(glue->phy)) { + dev_err(dev, "failed to register usb-phy %ld\n", + PTR_ERR(glue->phy)); + goto err_clk_disable; + } + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb_pdev, pdev->resource, pdev->num_resources); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err_clk_disable; + } + + ret = platform_device_add_data(musb_pdev, pdata, sizeof(*pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err_clk_disable; + } + + ret = platform_device_add(musb_pdev); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err_clk_disable; + } + + dev_info(&pdev->dev, "Registered MPFS MUSB driver\n"); + return 0; + +err_clk_disable: + clk_disable_unprepare(clk); + +err_phy_release: + usb_phy_generic_unregister(glue->phy); + platform_device_put(musb_pdev); + return ret; +} + +static int mpfs_remove(struct platform_device *pdev) +{ + struct mpfs_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(pdev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id mpfs_id_table[] = { + { .compatible = "microchip,mpfs-musb" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpfs_id_table); +#endif + +static struct platform_driver mpfs_musb_driver = { + .probe = mpfs_probe, + .remove = mpfs_remove, + .driver = { + .name = "mpfs-musb", + .of_match_table = of_match_ptr(mpfs_id_table) + }, +}; + +module_platform_driver(mpfs_musb_driver); + +MODULE_DESCRIPTION("PolarFire SoC MUSB Glue Layer"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From afbd04e66e5d16ca3c7ea2e3c56eca25558eacf3 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 15 Jun 2022 17:24:32 -0700 Subject: usb: dwc3: core: Deprecate GCTL.CORESOFTRESET Synopsys IP DWC_usb32 and DWC_usb31 version 1.90a and above deprecated GCTL.CORESOFTRESET. The DRD mode switching flow is updated to remove the GCTL soft reset. Add version checks to prevent using deprecated setting in mode switching flow. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/9df529fde6e55f5508321b6bc26e92848044ef2b.1655338967.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index cb8742f2a4b0..3d716c7fd34e 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -159,7 +159,8 @@ static void __dwc3_set_mode(struct work_struct *work) } /* For DRD host or device mode only */ - if (dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) { + if ((DWC3_IP_IS(DWC3) || DWC3_VER_IS_PRIOR(DWC31, 190A)) && + dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) { reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg |= DWC3_GCTL_CORESOFTRESET; dwc3_writel(dwc->regs, DWC3_GCTL, reg); -- cgit v1.2.3 From 649f5c842ba3acfe6a06a36229759f328e98508a Mon Sep 17 00:00:00 2001 From: Sandeep Maheswaram Date: Mon, 13 Jun 2022 10:00:51 +0530 Subject: usb: dwc3: core: Host wake up support from system suspend Check wakeup-source property for dwc3 core node to set the wakeup capability. Drop the device_init_wakeup call from runtime suspend and resume. If the dwc3 is wakeup capable, don't power down the USB PHY(s). The glue drivers are expected to take care of configuring the additional wakeup settings if needed based on the dwc3 wakeup capability status. In some SOC designs, powering off the PHY is resulting in higher leakage, so this patch save power on such boards. Reviewed-by: Pavankumar Kondeti Signed-off-by: Sandeep Maheswaram Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/1655094654-24052-3-git-send-email-quic_kriskura@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 3d716c7fd34e..050b2ba5986d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1817,6 +1817,7 @@ static int dwc3_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); + device_init_wakeup(&pdev->dev, of_property_read_bool(dev->of_node, "wakeup-source")); spin_lock_init(&dwc->lock); mutex_init(&dwc->mutex); @@ -1978,7 +1979,7 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) dwc3_core_exit(dwc); break; case DWC3_GCTL_PRTCAP_HOST: - if (!PMSG_IS_AUTO(msg)) { + if (!PMSG_IS_AUTO(msg) && !device_can_wakeup(dwc->dev)) { dwc3_core_exit(dwc); break; } @@ -2039,7 +2040,7 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) spin_unlock_irqrestore(&dwc->lock, flags); break; case DWC3_GCTL_PRTCAP_HOST: - if (!PMSG_IS_AUTO(msg)) { + if (!PMSG_IS_AUTO(msg) && !device_can_wakeup(dwc->dev)) { ret = dwc3_core_init_for_resume(dwc); if (ret) return ret; @@ -2116,8 +2117,6 @@ static int dwc3_runtime_suspend(struct device *dev) if (ret) return ret; - device_init_wakeup(dev, true); - return 0; } @@ -2126,8 +2125,6 @@ static int dwc3_runtime_resume(struct device *dev) struct dwc3 *dwc = dev_get_drvdata(dev); int ret; - device_init_wakeup(dev, false); - ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME); if (ret) return ret; -- cgit v1.2.3 From 360e8230516de94d74d30c64f0cdcf228b8e8b67 Mon Sep 17 00:00:00 2001 From: Sandeep Maheswaram Date: Mon, 13 Jun 2022 10:00:52 +0530 Subject: usb: dwc3: qcom: Add helper functions to enable,disable wake irqs Adding helper functions to enable,disable wake irqs to make the code simple and readable. Reviewed-by: Matthias Kaehlcke Reviewed-by: Pavankumar Kondeti Signed-off-by: Sandeep Maheswaram Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/1655094654-24052-4-git-send-email-quic_kriskura@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 58 ++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 6cba990da32e..7352124355d2 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -296,50 +296,44 @@ static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) icc_put(qcom->icc_path_apps); } +static void dwc3_qcom_enable_wakeup_irq(int irq) +{ + if (!irq) + return; + + enable_irq(irq); + enable_irq_wake(irq); +} + +static void dwc3_qcom_disable_wakeup_irq(int irq) +{ + if (!irq) + return; + + disable_irq_wake(irq); + disable_irq_nosync(irq); +} + static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) { - if (qcom->hs_phy_irq) { - disable_irq_wake(qcom->hs_phy_irq); - disable_irq_nosync(qcom->hs_phy_irq); - } + dwc3_qcom_disable_wakeup_irq(qcom->hs_phy_irq); - if (qcom->dp_hs_phy_irq) { - disable_irq_wake(qcom->dp_hs_phy_irq); - disable_irq_nosync(qcom->dp_hs_phy_irq); - } + dwc3_qcom_disable_wakeup_irq(qcom->dp_hs_phy_irq); - if (qcom->dm_hs_phy_irq) { - disable_irq_wake(qcom->dm_hs_phy_irq); - disable_irq_nosync(qcom->dm_hs_phy_irq); - } + dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq); - if (qcom->ss_phy_irq) { - disable_irq_wake(qcom->ss_phy_irq); - disable_irq_nosync(qcom->ss_phy_irq); - } + dwc3_qcom_disable_wakeup_irq(qcom->ss_phy_irq); } static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) { - if (qcom->hs_phy_irq) { - enable_irq(qcom->hs_phy_irq); - enable_irq_wake(qcom->hs_phy_irq); - } + dwc3_qcom_enable_wakeup_irq(qcom->hs_phy_irq); - if (qcom->dp_hs_phy_irq) { - enable_irq(qcom->dp_hs_phy_irq); - enable_irq_wake(qcom->dp_hs_phy_irq); - } + dwc3_qcom_enable_wakeup_irq(qcom->dp_hs_phy_irq); - if (qcom->dm_hs_phy_irq) { - enable_irq(qcom->dm_hs_phy_irq); - enable_irq_wake(qcom->dm_hs_phy_irq); - } + dwc3_qcom_enable_wakeup_irq(qcom->dm_hs_phy_irq); - if (qcom->ss_phy_irq) { - enable_irq(qcom->ss_phy_irq); - enable_irq_wake(qcom->ss_phy_irq); - } + dwc3_qcom_enable_wakeup_irq(qcom->ss_phy_irq); } static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) -- cgit v1.2.3 From 6895ea55c385c9afdd2aec1eef27ec24917a112f Mon Sep 17 00:00:00 2001 From: Sandeep Maheswaram Date: Mon, 13 Jun 2022 10:00:53 +0530 Subject: usb: dwc3: qcom: Configure wakeup interrupts during suspend Configure DP/DM line interrupts based on the USB2 device attached to the root hub port. When HS/FS device is connected, configure the DP line as falling edge to detect both disconnect and remote wakeup scenarios. When LS device is connected, configure DM line as falling edge to detect both disconnect and remote wakeup. When no device is connected, configure both DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. Reviewed-by: Pavankumar Kondeti Reviewed-by: Matthias Kaehlcke Signed-off-by: Sandeep Maheswaram Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/1655094654-24052-5-git-send-email-quic_kriskura@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 72 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 7352124355d2..1046ea8790ba 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -20,7 +20,8 @@ #include #include #include - +#include +#include #include "core.h" /* USB QSCRATCH Hardware registers */ @@ -76,6 +77,7 @@ struct dwc3_qcom { int dp_hs_phy_irq; int dm_hs_phy_irq; int ss_phy_irq; + enum usb_device_speed usb2_speed; struct extcon_dev *edev; struct extcon_dev *host_edev; @@ -296,11 +298,34 @@ static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) icc_put(qcom->icc_path_apps); } -static void dwc3_qcom_enable_wakeup_irq(int irq) +static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom) +{ + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + struct usb_hcd *hcd = platform_get_drvdata(dwc->xhci); + struct usb_device *udev; + + /* + * It is possible to query the speed of all children of + * USB2.0 root hub via usb_hub_for_each_child(). DWC3 code + * currently supports only 1 port per controller. So + * this is sufficient. + */ + udev = usb_hub_find_child(hcd->self.root_hub, 1); + + if (!udev) + return USB_SPEED_UNKNOWN; + + return udev->speed; +} + +static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) { if (!irq) return; + if (polarity) + irq_set_irq_type(irq, polarity); + enable_irq(irq); enable_irq_wake(irq); } @@ -318,22 +343,47 @@ static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) { dwc3_qcom_disable_wakeup_irq(qcom->hs_phy_irq); - dwc3_qcom_disable_wakeup_irq(qcom->dp_hs_phy_irq); - - dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq); + if (qcom->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq); + } else if ((qcom->usb2_speed == USB_SPEED_HIGH) || + (qcom->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_disable_wakeup_irq(qcom->dp_hs_phy_irq); + } else { + dwc3_qcom_disable_wakeup_irq(qcom->dp_hs_phy_irq); + dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq); + } dwc3_qcom_disable_wakeup_irq(qcom->ss_phy_irq); } static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) { - dwc3_qcom_enable_wakeup_irq(qcom->hs_phy_irq); + dwc3_qcom_enable_wakeup_irq(qcom->hs_phy_irq, 0); - dwc3_qcom_enable_wakeup_irq(qcom->dp_hs_phy_irq); + /* + * Configure DP/DM line interrupts based on the USB2 device attached to + * the root hub port. When HS/FS device is connected, configure the DP line + * as falling edge to detect both disconnect and remote wakeup scenarios. When + * LS device is connected, configure DM line as falling edge to detect both + * disconnect and remote wakeup. When no device is connected, configure both + * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. + */ - dwc3_qcom_enable_wakeup_irq(qcom->dm_hs_phy_irq); + if (qcom->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_enable_wakeup_irq(qcom->dm_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else if ((qcom->usb2_speed == USB_SPEED_HIGH) || + (qcom->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_enable_wakeup_irq(qcom->dp_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else { + dwc3_qcom_enable_wakeup_irq(qcom->dp_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + dwc3_qcom_enable_wakeup_irq(qcom->dm_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + } - dwc3_qcom_enable_wakeup_irq(qcom->ss_phy_irq); + dwc3_qcom_enable_wakeup_irq(qcom->ss_phy_irq, 0); } static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) @@ -355,8 +405,10 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) if (ret) dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret); - if (device_may_wakeup(qcom->dev)) + if (device_may_wakeup(qcom->dev)) { + qcom->usb2_speed = dwc3_qcom_read_usb2_speed(qcom); dwc3_qcom_enable_interrupts(qcom); + } qcom->is_suspended = true; -- cgit v1.2.3 From d9be8d5c5b032e5383ff5c404ff4155e9c705429 Mon Sep 17 00:00:00 2001 From: Sandeep Maheswaram Date: Mon, 13 Jun 2022 10:00:54 +0530 Subject: usb: dwc3: qcom: Keep power domain on to retain controller status If dwc3 is wakeup capable, keep the power domain always ON so that wakeup from system suspend can be supported. Otherwise, keep the power domain ON only during runtime suspend to support wakeup from runtime suspend. Reviewed-by: Pavankumar Kondeti Signed-off-by: Sandeep Maheswaram Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/1655094654-24052-6-git-send-email-quic_kriskura@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 1046ea8790ba..77036551987a 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -756,12 +757,13 @@ dwc3_qcom_create_urs_usb_platdev(struct device *dev) static int dwc3_qcom_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; - struct device *dev = &pdev->dev; - struct dwc3_qcom *qcom; - struct resource *res, *parent_res = NULL; - int ret, i; - bool ignore_pipe_clk; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct dwc3_qcom *qcom; + struct resource *res, *parent_res = NULL; + int ret, i; + bool ignore_pipe_clk; + struct generic_pm_domain *genpd; qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL); if (!qcom) @@ -770,6 +772,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev) platform_set_drvdata(pdev, qcom); qcom->dev = &pdev->dev; + genpd = pd_to_genpd(qcom->dev->pm_domain); + if (has_acpi_companion(dev)) { qcom->acpi_pdata = acpi_device_get_match_data(dev); if (!qcom->acpi_pdata) { @@ -877,7 +881,17 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ret) goto interconnect_exit; - device_init_wakeup(&pdev->dev, 1); + if (device_can_wakeup(&qcom->dwc3->dev)) { + /* + * Setting GENPD_FLAG_ALWAYS_ON flag takes care of keeping + * genpd on in both runtime suspend and system suspend cases. + */ + genpd->flags |= GENPD_FLAG_ALWAYS_ON; + device_init_wakeup(&pdev->dev, true); + } else { + genpd->flags |= GENPD_FLAG_RPM_ALWAYS_ON; + } + qcom->is_suspended = false; pm_runtime_set_active(dev); pm_runtime_enable(dev); -- cgit v1.2.3 From 144a96f7f96e412c3367f51cf2c57f52e3f1110d Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Wed, 15 Jun 2022 17:20:17 +0000 Subject: usb: typec: mux: Allow muxes to specify mode-switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loosen the typec_mux_match() requirements so that searches where an alt mode is not specified, but the target mux device lists the "mode-switch" property, return a success. This is helpful in Type C port drivers which would like to get a pointer to the mux switch associated with a Type C port, but don't want to specify a particular alt mode. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Nícolas F. R. A. Prado Tested-by: Nícolas F. R. A. Prado Link: https://lore.kernel.org/r/20220615172129.1314056-2-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index fd55c2c516a5..464330776cd6 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -281,9 +281,13 @@ static void *typec_mux_match(struct fwnode_handle *fwnode, const char *id, if (match) goto find_mux; - /* Accessory Mode muxes */ if (!desc) { - match = fwnode_property_present(fwnode, "accessory"); + /* + * Accessory Mode muxes & muxes which explicitly specify + * the required identifier can avoid SVID matching. + */ + match = fwnode_property_present(fwnode, "accessory") || + fwnode_property_present(fwnode, id); if (match) goto find_mux; return NULL; -- cgit v1.2.3 From 40a959d7042bb7711e404ad2318b30e9f92c6b9b Mon Sep 17 00:00:00 2001 From: Liang He Date: Fri, 17 Jun 2022 11:46:37 +0800 Subject: usb: host: ohci-ppc-of: Fix refcount leak bug In ohci_hcd_ppc_of_probe(), of_find_compatible_node() will return a node pointer with refcount incremented. We should use of_node_put() when it is not used anymore. Acked-by: Alan Stern Signed-off-by: Liang He Link: https://lore.kernel.org/r/20220617034637.4003115-1-windhl@126.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-ppc-of.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 1960b8dfdba5..591f675cc930 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -166,6 +166,7 @@ static int ohci_hcd_ppc_of_probe(struct platform_device *op) release_mem_region(res.start, 0x4); } else pr_debug("%s: cannot get ehci offset from fdt\n", __FILE__); + of_node_put(np); } irq_dispose_mapping(irq); -- cgit v1.2.3 From 9d6d5303c39b8bc182475b22f45504106a07f086 Mon Sep 17 00:00:00 2001 From: Liang He Date: Sat, 18 Jun 2022 10:32:05 +0800 Subject: usb: renesas: Fix refcount leak bug In usbhs_rza1_hardware_init(), of_find_node_by_name() will return a node pointer with refcount incremented. We should use of_node_put() when it is not used anymore. Signed-off-by: Liang He Link: https://lore.kernel.org/r/20220618023205.4056548-1-windhl@126.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/rza.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/renesas_usbhs/rza.c b/drivers/usb/renesas_usbhs/rza.c index 24de64edb674..2d77edefb4b3 100644 --- a/drivers/usb/renesas_usbhs/rza.c +++ b/drivers/usb/renesas_usbhs/rza.c @@ -23,6 +23,10 @@ static int usbhs_rza1_hardware_init(struct platform_device *pdev) extal_clk = of_find_node_by_name(NULL, "extal"); of_property_read_u32(usb_x1_clk, "clock-frequency", &freq_usb); of_property_read_u32(extal_clk, "clock-frequency", &freq_extal); + + of_node_put(usb_x1_clk); + of_node_put(extal_clk); + if (freq_usb == 0) { if (freq_extal == 12000000) { /* Select 12MHz XTAL */ -- cgit v1.2.3 From 196a58bdec7c324d6d862cba19069b77ddd48b90 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Sat, 18 Jun 2022 15:06:16 +0300 Subject: usb: musb: core: drop redundant checks In musb_{save|restore}_context() the expression '&musb->endpoints[i]' just cannot be NULL, so the checks have no sense at all -- after dropping them, the local variables 'hw_ep' are no longer necessary, so drop them as well. Found by Linux Verification Center (linuxtesting.org) with the SVACE static analysis tool. Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/3f8f60d9-f1b5-6b2c-1222-39b156151a22@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_core.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index f7b1d5993f8c..bbbcfd49fb35 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2684,13 +2684,7 @@ static void musb_save_context(struct musb *musb) musb->context.devctl = musb_readb(musb_base, MUSB_DEVCTL); for (i = 0; i < musb->config->num_eps; ++i) { - struct musb_hw_ep *hw_ep; - - hw_ep = &musb->endpoints[i]; - if (!hw_ep) - continue; - - epio = hw_ep->regs; + epio = musb->endpoints[i].regs; if (!epio) continue; @@ -2765,13 +2759,7 @@ static void musb_restore_context(struct musb *musb) musb_writeb(musb_base, MUSB_DEVCTL, musb->context.devctl); for (i = 0; i < musb->config->num_eps; ++i) { - struct musb_hw_ep *hw_ep; - - hw_ep = &musb->endpoints[i]; - if (!hw_ep) - continue; - - epio = hw_ep->regs; + epio = musb->endpoints[i].regs; if (!epio) continue; -- cgit v1.2.3 From 7d34b0717c058f1d4a80342501d1711b8ac539c6 Mon Sep 17 00:00:00 2001 From: Xiang wangx Date: Mon, 20 Jun 2022 18:15:56 +0800 Subject: USB: ohci-sm501: Fix typo in comment Delete the redundant word 'the'. Signed-off-by: Xiang wangx Link: https://lore.kernel.org/r/20220620101556.2290-1-wangxiang@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-sm501.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index b91d50da6127..f5de586454e3 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -153,7 +153,7 @@ static int ohci_hcd_sm501_drv_probe(struct platform_device *pdev) * fine. This is however not always the case - buffers may be allocated * using kmalloc() - so the usb core needs to be told that it must copy * data into our local memory if the buffers happen to be placed in - * regular memory. A non-null hcd->localmem_pool initialized by the + * regular memory. A non-null hcd->localmem_pool initialized by * the call to usb_hcd_setup_local_mem() below does just that. */ -- cgit v1.2.3 From 274a12ea4007bf62a3abdc5600f85cd4de678169 Mon Sep 17 00:00:00 2001 From: Xiang wangx Date: Mon, 20 Jun 2022 18:42:43 +0800 Subject: USB: storage: Fix typo in comment Delete the redundant word 'the'. Signed-off-by: Xiang wangx Link: https://lore.kernel.org/r/20220620104243.4979-1-wangxiang@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 64d96d210e02..7449e379077a 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -1178,7 +1178,7 @@ int usb_stor_Bulk_transport(struct scsi_cmnd *srb, struct us_data *us) /* * If the device tried to send back more data than the * amount requested, the spec requires us to transfer - * the CSW anyway. Since there's no point retrying the + * the CSW anyway. Since there's no point retrying * the command, we'll return fake sense data indicating * Illegal Request, Invalid Field in CDB. */ -- cgit v1.2.3 From 5c586db8465274ce5db64171d42a099ce89ec905 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Mon, 20 Jun 2022 23:42:42 -0700 Subject: usb: host: ehci-q: Fix ehci_submit_single_step_set_feature annotation typo I found the "argument" typo. It seems that "argument" is more correct than "arguement". Signed-off-by: Hyunwoo Kim Link: https://lore.kernel.org/r/20220621064242.GA698757@ubuntu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-q.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 1163af6fad77..807e64991e3e 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1162,7 +1162,7 @@ submit_async ( * This is done in two parts: first SETUP req for GetDesc is sent then * 15 seconds later, the IN stage for GetDesc starts to req data from dev * - * is_setup : i/p arguement decides which of the two stage needs to be + * is_setup : i/p argument decides which of the two stage needs to be * performed; TRUE - SETUP and FALSE - IN+STATUS * Returns 0 if success */ -- cgit v1.2.3 From 8709115180c612bd8e6686a7b6c6fe94e5b51cdc Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Thu, 16 Jun 2022 21:44:59 +0200 Subject: usb: chipidea: udc: implement get_frame The chipidea udc core is capable of reading the current frame index from hardware. This patch adds the get_frame callback to the driver. Acked-by: Peter Chen Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220616194459.2981519-1-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci.h | 1 + drivers/usb/chipidea/core.c | 2 ++ drivers/usb/chipidea/udc.c | 14 ++++++++++++++ 3 files changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 99440baa6458..a4a3be049910 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -49,6 +49,7 @@ enum ci_hw_regs { OP_USBCMD, OP_USBSTS, OP_USBINTR, + OP_FRINDEX, OP_DEVICEADDR, OP_ENDPTLISTADDR, OP_TTCTRL, diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 5359b2a2e4d2..6330fa911792 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -53,6 +53,7 @@ static const u8 ci_regs_nolpm[] = { [OP_USBCMD] = 0x00U, [OP_USBSTS] = 0x04U, [OP_USBINTR] = 0x08U, + [OP_FRINDEX] = 0x0CU, [OP_DEVICEADDR] = 0x14U, [OP_ENDPTLISTADDR] = 0x18U, [OP_TTCTRL] = 0x1CU, @@ -78,6 +79,7 @@ static const u8 ci_regs_lpm[] = { [OP_USBCMD] = 0x00U, [OP_USBSTS] = 0x04U, [OP_USBINTR] = 0x08U, + [OP_FRINDEX] = 0x0CU, [OP_DEVICEADDR] = 0x14U, [OP_ENDPTLISTADDR] = 0x18U, [OP_TTCTRL] = 0x1CU, diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index dc6c96e04bcf..0c9ae9768a67 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1651,6 +1651,19 @@ static const struct usb_ep_ops usb_ep_ops = { /****************************************************************************** * GADGET block *****************************************************************************/ + +static int ci_udc_get_frame(struct usb_gadget *_gadget) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ci->lock, flags); + ret = hw_read(ci, OP_FRINDEX, 0x3fff); + spin_unlock_irqrestore(&ci->lock, flags); + return ret >> 3; +} + /* * ci_hdrc_gadget_connect: caller makes sure gadget driver is binded */ @@ -1807,6 +1820,7 @@ static struct usb_ep *ci_udc_match_ep(struct usb_gadget *gadget, * Check "usb_gadget.h" for details */ static const struct usb_gadget_ops usb_gadget_ops = { + .get_frame = ci_udc_get_frame, .vbus_session = ci_udc_vbus_session, .wakeup = ci_udc_wakeup, .set_selfpowered = ci_udc_selfpowered, -- cgit v1.2.3 From 18171cfc3c236a1587dcad9adc27c6e781af4438 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 14 Jun 2022 14:05:22 +0200 Subject: usb: chipidea: ci_hdrc_imx: use dev_err_probe() Use dev_err_probe() to simplify handling errors in ci_hdrc_imx_probe() Acked-by: Peter Chen Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20220614120522.1469957-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 097142ffb184..9ffcecd3058c 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -348,25 +348,18 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) data->pinctrl = devm_pinctrl_get(dev); if (PTR_ERR(data->pinctrl) == -ENODEV) data->pinctrl = NULL; - else if (IS_ERR(data->pinctrl)) { - if (PTR_ERR(data->pinctrl) != -EPROBE_DEFER) - dev_err(dev, "pinctrl get failed, err=%ld\n", - PTR_ERR(data->pinctrl)); - return PTR_ERR(data->pinctrl); - } + else if (IS_ERR(data->pinctrl)) + return dev_err_probe(dev, PTR_ERR(data->pinctrl), + "pinctrl get failed\n"); data->hsic_pad_regulator = devm_regulator_get_optional(dev, "hsic"); if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) { /* no pad regualator is needed */ data->hsic_pad_regulator = NULL; - } else if (IS_ERR(data->hsic_pad_regulator)) { - if (PTR_ERR(data->hsic_pad_regulator) != -EPROBE_DEFER) - dev_err(dev, - "Get HSIC pad regulator error: %ld\n", - PTR_ERR(data->hsic_pad_regulator)); - return PTR_ERR(data->hsic_pad_regulator); - } + } else if (IS_ERR(data->hsic_pad_regulator)) + return dev_err_probe(dev, PTR_ERR(data->hsic_pad_regulator), + "Get HSIC pad regulator error\n"); if (data->hsic_pad_regulator) { ret = regulator_enable(data->hsic_pad_regulator); @@ -458,9 +451,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) &pdata); if (IS_ERR(data->ci_pdev)) { ret = PTR_ERR(data->ci_pdev); - if (ret != -EPROBE_DEFER) - dev_err(dev, "ci_hdrc_add_device failed, err=%d\n", - ret); + dev_err_probe(dev, ret, "ci_hdrc_add_device failed\n"); goto err_clk; } -- cgit v1.2.3 From 9dbdac024d4d9f3fc234399cb8c1f1cc6d2bcb2d Mon Sep 17 00:00:00 2001 From: Artur Bujdoso Date: Tue, 24 May 2022 18:25:40 +0200 Subject: staging: octeon-usb: move driver out of staging The Octeon usb driver has been in staging for a long time and used in Ubiquiti routers for a while now. It's been built and then tested on real hardware with several usb devices and it is proven to be stable and ready to be moved to its proper place in the kernel tree. Move it to drivers/usb/host and adjust its Makefile, Kconfig and defconfig dependencies. Many thanks to the developers who made it happen. Signed-off-by: Artur Bujdoso Link: https://lore.kernel.org/r/Yo0HBIlSXOBM+//9@crux Signed-off-by: Greg Kroah-Hartman --- arch/mips/configs/cavium_octeon_defconfig | 2 +- drivers/staging/Kconfig | 2 - drivers/staging/Makefile | 1 - drivers/staging/octeon-usb/Kconfig | 11 - drivers/staging/octeon-usb/Makefile | 2 - drivers/staging/octeon-usb/TODO | 8 - drivers/staging/octeon-usb/octeon-hcd.c | 3740 ----------------------------- drivers/staging/octeon-usb/octeon-hcd.h | 1847 -------------- drivers/usb/host/Kconfig | 10 + drivers/usb/host/Makefile | 1 + drivers/usb/host/octeon-hcd.c | 3740 +++++++++++++++++++++++++++++ drivers/usb/host/octeon-hcd.h | 1847 ++++++++++++++ 12 files changed, 5599 insertions(+), 5612 deletions(-) delete mode 100644 drivers/staging/octeon-usb/Kconfig delete mode 100644 drivers/staging/octeon-usb/Makefile delete mode 100644 drivers/staging/octeon-usb/TODO delete mode 100644 drivers/staging/octeon-usb/octeon-hcd.c delete mode 100644 drivers/staging/octeon-usb/octeon-hcd.h create mode 100644 drivers/usb/host/octeon-hcd.c create mode 100644 drivers/usb/host/octeon-hcd.h (limited to 'drivers') diff --git a/arch/mips/configs/cavium_octeon_defconfig b/arch/mips/configs/cavium_octeon_defconfig index b6695367aa33..97ceaf080c0c 100644 --- a/arch/mips/configs/cavium_octeon_defconfig +++ b/arch/mips/configs/cavium_octeon_defconfig @@ -134,7 +134,7 @@ CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_DS1307=y CONFIG_STAGING=y CONFIG_OCTEON_ETHERNET=y -CONFIG_OCTEON_USB=y +CONFIG_USB_OCTEON_HCD=y # CONFIG_IOMMU_SUPPORT is not set CONFIG_RAS=y CONFIG_EXT4_FS=y diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 0a993c47273e..3bd80f9695ac 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -42,8 +42,6 @@ source "drivers/staging/rts5208/Kconfig" source "drivers/staging/octeon/Kconfig" -source "drivers/staging/octeon-usb/Kconfig" - source "drivers/staging/vt6655/Kconfig" source "drivers/staging/vt6656/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 2800ab9b2d1d..1d9ae39fea14 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -11,7 +11,6 @@ obj-$(CONFIG_R8712U) += rtl8712/ obj-$(CONFIG_R8188EU) += r8188eu/ obj-$(CONFIG_RTS5208) += rts5208/ obj-$(CONFIG_OCTEON_ETHERNET) += octeon/ -obj-$(CONFIG_OCTEON_USB) += octeon-usb/ obj-$(CONFIG_VT6655) += vt6655/ obj-$(CONFIG_VT6656) += vt6656/ obj-$(CONFIG_VME_BUS) += vme_user/ diff --git a/drivers/staging/octeon-usb/Kconfig b/drivers/staging/octeon-usb/Kconfig deleted file mode 100644 index 6a5d842ee0f2..000000000000 --- a/drivers/staging/octeon-usb/Kconfig +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -config OCTEON_USB - tristate "Cavium Networks Octeon USB support" - depends on CAVIUM_OCTEON_SOC && USB - help - This driver supports USB host controller on some Cavium - Networks' products in the Octeon family. - - To compile this driver as a module, choose M here. The module - will be called octeon-hcd. - diff --git a/drivers/staging/octeon-usb/Makefile b/drivers/staging/octeon-usb/Makefile deleted file mode 100644 index 9873a0130ad5..000000000000 --- a/drivers/staging/octeon-usb/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -obj-${CONFIG_OCTEON_USB} := octeon-hcd.o diff --git a/drivers/staging/octeon-usb/TODO b/drivers/staging/octeon-usb/TODO deleted file mode 100644 index 2b29acca5caa..000000000000 --- a/drivers/staging/octeon-usb/TODO +++ /dev/null @@ -1,8 +0,0 @@ -This driver is functional and has been tested on EdgeRouter Lite, -D-Link DSR-1000N and EBH5600 evaluation board with USB mass storage. - -TODO: - - kernel coding style - - checkpatch warnings - -Contact: Aaro Koskinen diff --git a/drivers/staging/octeon-usb/octeon-hcd.c b/drivers/staging/octeon-usb/octeon-hcd.c deleted file mode 100644 index a1cd81d4a114..000000000000 --- a/drivers/staging/octeon-usb/octeon-hcd.c +++ /dev/null @@ -1,3740 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - * - * Copyright (C) 2008 Cavium Networks - * - * Some parts of the code were originally released under BSD license: - * - * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * * Neither the name of Cavium Networks nor the names of - * its contributors may be used to endorse or promote products - * derived from this software without specific prior written - * permission. - * - * This Software, including technical data, may be subject to U.S. export - * control laws, including the U.S. Export Administration Act and its associated - * regulations, and may be subject to export or import regulations in other - * countries. - * - * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" - * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR - * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO - * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION - * OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM - * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, - * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF - * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR - * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR - * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "octeon-hcd.h" - -/** - * enum cvmx_usb_speed - the possible USB device speeds - * - * @CVMX_USB_SPEED_HIGH: Device is operation at 480Mbps - * @CVMX_USB_SPEED_FULL: Device is operation at 12Mbps - * @CVMX_USB_SPEED_LOW: Device is operation at 1.5Mbps - */ -enum cvmx_usb_speed { - CVMX_USB_SPEED_HIGH = 0, - CVMX_USB_SPEED_FULL = 1, - CVMX_USB_SPEED_LOW = 2, -}; - -/** - * enum cvmx_usb_transfer - the possible USB transfer types - * - * @CVMX_USB_TRANSFER_CONTROL: USB transfer type control for hub and status - * transfers - * @CVMX_USB_TRANSFER_ISOCHRONOUS: USB transfer type isochronous for low - * priority periodic transfers - * @CVMX_USB_TRANSFER_BULK: USB transfer type bulk for large low priority - * transfers - * @CVMX_USB_TRANSFER_INTERRUPT: USB transfer type interrupt for high priority - * periodic transfers - */ -enum cvmx_usb_transfer { - CVMX_USB_TRANSFER_CONTROL = 0, - CVMX_USB_TRANSFER_ISOCHRONOUS = 1, - CVMX_USB_TRANSFER_BULK = 2, - CVMX_USB_TRANSFER_INTERRUPT = 3, -}; - -/** - * enum cvmx_usb_direction - the transfer directions - * - * @CVMX_USB_DIRECTION_OUT: Data is transferring from Octeon to the device/host - * @CVMX_USB_DIRECTION_IN: Data is transferring from the device/host to Octeon - */ -enum cvmx_usb_direction { - CVMX_USB_DIRECTION_OUT, - CVMX_USB_DIRECTION_IN, -}; - -/** - * enum cvmx_usb_status - possible callback function status codes - * - * @CVMX_USB_STATUS_OK: The transaction / operation finished without - * any errors - * @CVMX_USB_STATUS_SHORT: FIXME: This is currently not implemented - * @CVMX_USB_STATUS_CANCEL: The transaction was canceled while in flight - * by a user call to cvmx_usb_cancel - * @CVMX_USB_STATUS_ERROR: The transaction aborted with an unexpected - * error status - * @CVMX_USB_STATUS_STALL: The transaction received a USB STALL response - * from the device - * @CVMX_USB_STATUS_XACTERR: The transaction failed with an error from the - * device even after a number of retries - * @CVMX_USB_STATUS_DATATGLERR: The transaction failed with a data toggle - * error even after a number of retries - * @CVMX_USB_STATUS_BABBLEERR: The transaction failed with a babble error - * @CVMX_USB_STATUS_FRAMEERR: The transaction failed with a frame error - * even after a number of retries - */ -enum cvmx_usb_status { - CVMX_USB_STATUS_OK, - CVMX_USB_STATUS_SHORT, - CVMX_USB_STATUS_CANCEL, - CVMX_USB_STATUS_ERROR, - CVMX_USB_STATUS_STALL, - CVMX_USB_STATUS_XACTERR, - CVMX_USB_STATUS_DATATGLERR, - CVMX_USB_STATUS_BABBLEERR, - CVMX_USB_STATUS_FRAMEERR, -}; - -/** - * struct cvmx_usb_port_status - the USB port status information - * - * @port_enabled: 1 = Usb port is enabled, 0 = disabled - * @port_over_current: 1 = Over current detected, 0 = Over current not - * detected. Octeon doesn't support over current detection. - * @port_powered: 1 = Port power is being supplied to the device, 0 = - * power is off. Octeon doesn't support turning port power - * off. - * @port_speed: Current port speed. - * @connected: 1 = A device is connected to the port, 0 = No device is - * connected. - * @connect_change: 1 = Device connected state changed since the last set - * status call. - */ -struct cvmx_usb_port_status { - u32 reserved : 25; - u32 port_enabled : 1; - u32 port_over_current : 1; - u32 port_powered : 1; - enum cvmx_usb_speed port_speed : 2; - u32 connected : 1; - u32 connect_change : 1; -}; - -/** - * struct cvmx_usb_iso_packet - descriptor for Isochronous packets - * - * @offset: This is the offset in bytes into the main buffer where this data - * is stored. - * @length: This is the length in bytes of the data. - * @status: This is the status of this individual packet transfer. - */ -struct cvmx_usb_iso_packet { - int offset; - int length; - enum cvmx_usb_status status; -}; - -/** - * enum cvmx_usb_initialize_flags - flags used by the initialization function - * - * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI: The USB port uses a 12MHz crystal - * as clock source at USB_XO and - * USB_XI. - * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND: The USB port uses 12/24/48MHz 2.5V - * board clock source at USB_XO. - * USB_XI should be tied to GND. - * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK: Mask for clock speed field - * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: Speed of reference clock or - * crystal - * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: Speed of reference clock - * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: Speed of reference clock - * @CVMX_USB_INITIALIZE_FLAGS_NO_DMA: Disable DMA and used polled IO for - * data transfer use for the USB - */ -enum cvmx_usb_initialize_flags { - CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI = 1 << 0, - CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND = 1 << 1, - CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK = 3 << 3, - CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ = 1 << 3, - CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ = 2 << 3, - CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ = 3 << 3, - /* Bits 3-4 used to encode the clock frequency */ - CVMX_USB_INITIALIZE_FLAGS_NO_DMA = 1 << 5, -}; - -/** - * enum cvmx_usb_pipe_flags - internal flags for a pipe. - * - * @CVMX_USB_PIPE_FLAGS_SCHEDULED: Used internally to determine if a pipe is - * actively using hardware. - * @CVMX_USB_PIPE_FLAGS_NEED_PING: Used internally to determine if a high speed - * pipe is in the ping state. - */ -enum cvmx_usb_pipe_flags { - CVMX_USB_PIPE_FLAGS_SCHEDULED = 1 << 17, - CVMX_USB_PIPE_FLAGS_NEED_PING = 1 << 18, -}; - -/* Maximum number of times to retry failed transactions */ -#define MAX_RETRIES 3 - -/* Maximum number of hardware channels supported by the USB block */ -#define MAX_CHANNELS 8 - -/* - * The low level hardware can transfer a maximum of this number of bytes in each - * transfer. The field is 19 bits wide - */ -#define MAX_TRANSFER_BYTES ((1 << 19) - 1) - -/* - * The low level hardware can transfer a maximum of this number of packets in - * each transfer. The field is 10 bits wide - */ -#define MAX_TRANSFER_PACKETS ((1 << 10) - 1) - -/** - * Logical transactions may take numerous low level - * transactions, especially when splits are concerned. This - * enum represents all of the possible stages a transaction can - * be in. Note that split completes are always even. This is so - * the NAK handler can backup to the previous low level - * transaction with a simple clearing of bit 0. - */ -enum cvmx_usb_stage { - CVMX_USB_STAGE_NON_CONTROL, - CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE, - CVMX_USB_STAGE_SETUP, - CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE, - CVMX_USB_STAGE_DATA, - CVMX_USB_STAGE_DATA_SPLIT_COMPLETE, - CVMX_USB_STAGE_STATUS, - CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE, -}; - -/** - * struct cvmx_usb_transaction - describes each pending USB transaction - * regardless of type. These are linked together - * to form a list of pending requests for a pipe. - * - * @node: List node for transactions in the pipe. - * @type: Type of transaction, duplicated of the pipe. - * @flags: State flags for this transaction. - * @buffer: User's physical buffer address to read/write. - * @buffer_length: Size of the user's buffer in bytes. - * @control_header: For control transactions, physical address of the 8 - * byte standard header. - * @iso_start_frame: For ISO transactions, the starting frame number. - * @iso_number_packets: For ISO transactions, the number of packets in the - * request. - * @iso_packets: For ISO transactions, the sub packets in the request. - * @actual_bytes: Actual bytes transfer for this transaction. - * @stage: For control transactions, the current stage. - * @urb: URB. - */ -struct cvmx_usb_transaction { - struct list_head node; - enum cvmx_usb_transfer type; - u64 buffer; - int buffer_length; - u64 control_header; - int iso_start_frame; - int iso_number_packets; - struct cvmx_usb_iso_packet *iso_packets; - int xfersize; - int pktcnt; - int retries; - int actual_bytes; - enum cvmx_usb_stage stage; - struct urb *urb; -}; - -/** - * struct cvmx_usb_pipe - a pipe represents a virtual connection between Octeon - * and some USB device. It contains a list of pending - * request to the device. - * - * @node: List node for pipe list - * @next: Pipe after this one in the list - * @transactions: List of pending transactions - * @interval: For periodic pipes, the interval between packets in - * frames - * @next_tx_frame: The next frame this pipe is allowed to transmit on - * @flags: State flags for this pipe - * @device_speed: Speed of device connected to this pipe - * @transfer_type: Type of transaction supported by this pipe - * @transfer_dir: IN or OUT. Ignored for Control - * @multi_count: Max packet in a row for the device - * @max_packet: The device's maximum packet size in bytes - * @device_addr: USB device address at other end of pipe - * @endpoint_num: USB endpoint number at other end of pipe - * @hub_device_addr: Hub address this device is connected to - * @hub_port: Hub port this device is connected to - * @pid_toggle: This toggles between 0/1 on every packet send to track - * the data pid needed - * @channel: Hardware DMA channel for this pipe - * @split_sc_frame: The low order bits of the frame number the split - * complete should be sent on - */ -struct cvmx_usb_pipe { - struct list_head node; - struct list_head transactions; - u64 interval; - u64 next_tx_frame; - enum cvmx_usb_pipe_flags flags; - enum cvmx_usb_speed device_speed; - enum cvmx_usb_transfer transfer_type; - enum cvmx_usb_direction transfer_dir; - int multi_count; - u16 max_packet; - u8 device_addr; - u8 endpoint_num; - u8 hub_device_addr; - u8 hub_port; - u8 pid_toggle; - u8 channel; - s8 split_sc_frame; -}; - -struct cvmx_usb_tx_fifo { - struct { - int channel; - int size; - u64 address; - } entry[MAX_CHANNELS + 1]; - int head; - int tail; -}; - -/** - * struct octeon_hcd - the state of the USB block - * - * lock: Serialization lock. - * init_flags: Flags passed to initialize. - * index: Which USB block this is for. - * idle_hardware_channels: Bit set for every idle hardware channel. - * usbcx_hprt: Stored port status so we don't need to read a CSR to - * determine splits. - * pipe_for_channel: Map channels to pipes. - * pipe: Storage for pipes. - * indent: Used by debug output to indent functions. - * port_status: Last port status used for change notification. - * idle_pipes: List of open pipes that have no transactions. - * active_pipes: Active pipes indexed by transfer type. - * frame_number: Increments every SOF interrupt for time keeping. - * active_split: Points to the current active split, or NULL. - */ -struct octeon_hcd { - spinlock_t lock; /* serialization lock */ - int init_flags; - int index; - int idle_hardware_channels; - union cvmx_usbcx_hprt usbcx_hprt; - struct cvmx_usb_pipe *pipe_for_channel[MAX_CHANNELS]; - int indent; - struct cvmx_usb_port_status port_status; - struct list_head idle_pipes; - struct list_head active_pipes[4]; - u64 frame_number; - struct cvmx_usb_transaction *active_split; - struct cvmx_usb_tx_fifo periodic; - struct cvmx_usb_tx_fifo nonperiodic; -}; - -/* - * This macro logically sets a single field in a CSR. It does the sequence - * read, modify, and write - */ -#define USB_SET_FIELD32(address, _union, field, value) \ - do { \ - union _union c; \ - \ - c.u32 = cvmx_usb_read_csr32(usb, address); \ - c.s.field = value; \ - cvmx_usb_write_csr32(usb, address, c.u32); \ - } while (0) - -/* Returns the IO address to push/pop stuff data from the FIFOs */ -#define USB_FIFO_ADDRESS(channel, usb_index) \ - (CVMX_USBCX_GOTGCTL(usb_index) + ((channel) + 1) * 0x1000) - -/** - * struct octeon_temp_buffer - a bounce buffer for USB transfers - * @orig_buffer: the original buffer passed by the USB stack - * @data: the newly allocated temporary buffer (excluding meta-data) - * - * Both the DMA engine and FIFO mode will always transfer full 32-bit words. If - * the buffer is too short, we need to allocate a temporary one, and this struct - * represents it. - */ -struct octeon_temp_buffer { - void *orig_buffer; - u8 data[]; -}; - -static inline struct usb_hcd *octeon_to_hcd(struct octeon_hcd *p) -{ - return container_of((void *)p, struct usb_hcd, hcd_priv); -} - -/** - * octeon_alloc_temp_buffer - allocate a temporary buffer for USB transfer - * (if needed) - * @urb: URB. - * @mem_flags: Memory allocation flags. - * - * This function allocates a temporary bounce buffer whenever it's needed - * due to HW limitations. - */ -static int octeon_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) -{ - struct octeon_temp_buffer *temp; - - if (urb->num_sgs || urb->sg || - (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || - !(urb->transfer_buffer_length % sizeof(u32))) - return 0; - - temp = kmalloc(ALIGN(urb->transfer_buffer_length, sizeof(u32)) + - sizeof(*temp), mem_flags); - if (!temp) - return -ENOMEM; - - temp->orig_buffer = urb->transfer_buffer; - if (usb_urb_dir_out(urb)) - memcpy(temp->data, urb->transfer_buffer, - urb->transfer_buffer_length); - urb->transfer_buffer = temp->data; - urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; - - return 0; -} - -/** - * octeon_free_temp_buffer - free a temporary buffer used by USB transfers. - * @urb: URB. - * - * Frees a buffer allocated by octeon_alloc_temp_buffer(). - */ -static void octeon_free_temp_buffer(struct urb *urb) -{ - struct octeon_temp_buffer *temp; - size_t length; - - if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) - return; - - temp = container_of(urb->transfer_buffer, struct octeon_temp_buffer, - data); - if (usb_urb_dir_in(urb)) { - if (usb_pipeisoc(urb->pipe)) - length = urb->transfer_buffer_length; - else - length = urb->actual_length; - - memcpy(temp->orig_buffer, urb->transfer_buffer, length); - } - urb->transfer_buffer = temp->orig_buffer; - urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; - kfree(temp); -} - -/** - * octeon_map_urb_for_dma - Octeon-specific map_urb_for_dma(). - * @hcd: USB HCD structure. - * @urb: URB. - * @mem_flags: Memory allocation flags. - */ -static int octeon_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - int ret; - - ret = octeon_alloc_temp_buffer(urb, mem_flags); - if (ret) - return ret; - - ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); - if (ret) - octeon_free_temp_buffer(urb); - - return ret; -} - -/** - * octeon_unmap_urb_for_dma - Octeon-specific unmap_urb_for_dma() - * @hcd: USB HCD structure. - * @urb: URB. - */ -static void octeon_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) -{ - usb_hcd_unmap_urb_for_dma(hcd, urb); - octeon_free_temp_buffer(urb); -} - -/** - * Read a USB 32bit CSR. It performs the necessary address swizzle - * for 32bit CSRs and logs the value in a readable format if - * debugging is on. - * - * @usb: USB block this access is for - * @address: 64bit address to read - * - * Returns: Result of the read - */ -static inline u32 cvmx_usb_read_csr32(struct octeon_hcd *usb, u64 address) -{ - return cvmx_read64_uint32(address ^ 4); -} - -/** - * Write a USB 32bit CSR. It performs the necessary address - * swizzle for 32bit CSRs and logs the value in a readable format - * if debugging is on. - * - * @usb: USB block this access is for - * @address: 64bit address to write - * @value: Value to write - */ -static inline void cvmx_usb_write_csr32(struct octeon_hcd *usb, - u64 address, u32 value) -{ - cvmx_write64_uint32(address ^ 4, value); - cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); -} - -/** - * Return non zero if this pipe connects to a non HIGH speed - * device through a high speed hub. - * - * @usb: USB block this access is for - * @pipe: Pipe to check - * - * Returns: Non zero if we need to do split transactions - */ -static inline int cvmx_usb_pipe_needs_split(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe) -{ - return pipe->device_speed != CVMX_USB_SPEED_HIGH && - usb->usbcx_hprt.s.prtspd == CVMX_USB_SPEED_HIGH; -} - -/** - * Trivial utility function to return the correct PID for a pipe - * - * @pipe: pipe to check - * - * Returns: PID for pipe - */ -static inline int cvmx_usb_get_data_pid(struct cvmx_usb_pipe *pipe) -{ - if (pipe->pid_toggle) - return 2; /* Data1 */ - return 0; /* Data0 */ -} - -/* Loops through register until txfflsh or rxfflsh become zero.*/ -static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) -{ - int result; - u64 address = CVMX_USBCX_GRSTCTL(usb->index); - u64 done = cvmx_get_cycle() + 100 * - (u64)octeon_get_clock_rate / 1000000; - union cvmx_usbcx_grstctl c; - - while (1) { - c.u32 = cvmx_usb_read_csr32(usb, address); - if (fflsh_type == 0 && c.s.txfflsh == 0) { - result = 0; - break; - } else if (fflsh_type == 1 && c.s.rxfflsh == 0) { - result = 0; - break; - } else if (cvmx_get_cycle() > done) { - result = -1; - break; - } - - __delay(100); - } - return result; -} - -static void cvmx_fifo_setup(struct octeon_hcd *usb) -{ - union cvmx_usbcx_ghwcfg3 usbcx_ghwcfg3; - union cvmx_usbcx_gnptxfsiz npsiz; - union cvmx_usbcx_hptxfsiz psiz; - - usbcx_ghwcfg3.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GHWCFG3(usb->index)); - - /* - * Program the USBC_GRXFSIZ register to select the size of the receive - * FIFO (25%). - */ - USB_SET_FIELD32(CVMX_USBCX_GRXFSIZ(usb->index), cvmx_usbcx_grxfsiz, - rxfdep, usbcx_ghwcfg3.s.dfifodepth / 4); - - /* - * Program the USBC_GNPTXFSIZ register to select the size and the start - * address of the non-periodic transmit FIFO for nonperiodic - * transactions (50%). - */ - npsiz.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index)); - npsiz.s.nptxfdep = usbcx_ghwcfg3.s.dfifodepth / 2; - npsiz.s.nptxfstaddr = usbcx_ghwcfg3.s.dfifodepth / 4; - cvmx_usb_write_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index), npsiz.u32); - - /* - * Program the USBC_HPTXFSIZ register to select the size and start - * address of the periodic transmit FIFO for periodic transactions - * (25%). - */ - psiz.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index)); - psiz.s.ptxfsize = usbcx_ghwcfg3.s.dfifodepth / 4; - psiz.s.ptxfstaddr = 3 * usbcx_ghwcfg3.s.dfifodepth / 4; - cvmx_usb_write_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index), psiz.u32); - - /* Flush all FIFOs */ - USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), - cvmx_usbcx_grstctl, txfnum, 0x10); - USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), - cvmx_usbcx_grstctl, txfflsh, 1); - cvmx_wait_tx_rx(usb, 0); - USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), - cvmx_usbcx_grstctl, rxfflsh, 1); - cvmx_wait_tx_rx(usb, 1); -} - -/** - * Shutdown a USB port after a call to cvmx_usb_initialize(). - * The port should be disabled with all pipes closed when this - * function is called. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * - * Returns: 0 or a negative error code. - */ -static int cvmx_usb_shutdown(struct octeon_hcd *usb) -{ - union cvmx_usbnx_clk_ctl usbn_clk_ctl; - - /* Make sure all pipes are closed */ - if (!list_empty(&usb->idle_pipes) || - !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_ISOCHRONOUS]) || - !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_INTERRUPT]) || - !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_CONTROL]) || - !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_BULK])) - return -EBUSY; - - /* Disable the clocks and put them in power on reset */ - usbn_clk_ctl.u64 = cvmx_read64_uint64(CVMX_USBNX_CLK_CTL(usb->index)); - usbn_clk_ctl.s.enable = 1; - usbn_clk_ctl.s.por = 1; - usbn_clk_ctl.s.hclk_rst = 1; - usbn_clk_ctl.s.prst = 0; - usbn_clk_ctl.s.hrst = 0; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - return 0; -} - -/** - * Initialize a USB port for use. This must be called before any - * other access to the Octeon USB port is made. The port starts - * off in the disabled state. - * - * @dev: Pointer to struct device for logging purposes. - * @usb: Pointer to struct octeon_hcd. - * - * Returns: 0 or a negative error code. - */ -static int cvmx_usb_initialize(struct device *dev, - struct octeon_hcd *usb) -{ - int channel; - int divisor; - int retries = 0; - union cvmx_usbcx_hcfg usbcx_hcfg; - union cvmx_usbnx_clk_ctl usbn_clk_ctl; - union cvmx_usbcx_gintsts usbc_gintsts; - union cvmx_usbcx_gahbcfg usbcx_gahbcfg; - union cvmx_usbcx_gintmsk usbcx_gintmsk; - union cvmx_usbcx_gusbcfg usbcx_gusbcfg; - union cvmx_usbnx_usbp_ctl_status usbn_usbp_ctl_status; - -retry: - /* - * Power On Reset and PHY Initialization - * - * 1. Wait for DCOK to assert (nothing to do) - * - * 2a. Write USBN0/1_CLK_CTL[POR] = 1 and - * USBN0/1_CLK_CTL[HRST,PRST,HCLK_RST] = 0 - */ - usbn_clk_ctl.u64 = cvmx_read64_uint64(CVMX_USBNX_CLK_CTL(usb->index)); - usbn_clk_ctl.s.por = 1; - usbn_clk_ctl.s.hrst = 0; - usbn_clk_ctl.s.prst = 0; - usbn_clk_ctl.s.hclk_rst = 0; - usbn_clk_ctl.s.enable = 0; - /* - * 2b. Select the USB reference clock/crystal parameters by writing - * appropriate values to USBN0/1_CLK_CTL[P_C_SEL, P_RTYPE, P_COM_ON] - */ - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND) { - /* - * The USB port uses 12/24/48MHz 2.5V board clock - * source at USB_XO. USB_XI should be tied to GND. - * Most Octeon evaluation boards require this setting - */ - if (OCTEON_IS_MODEL(OCTEON_CN3XXX) || - OCTEON_IS_MODEL(OCTEON_CN56XX) || - OCTEON_IS_MODEL(OCTEON_CN50XX)) - /* From CN56XX,CN50XX,CN31XX,CN30XX manuals */ - usbn_clk_ctl.s.p_rtype = 2; /* p_rclk=1 & p_xenbn=0 */ - else - /* From CN52XX manual */ - usbn_clk_ctl.s.p_rtype = 1; - - switch (usb->init_flags & - CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK) { - case CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: - usbn_clk_ctl.s.p_c_sel = 0; - break; - case CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: - usbn_clk_ctl.s.p_c_sel = 1; - break; - case CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: - usbn_clk_ctl.s.p_c_sel = 2; - break; - } - } else { - /* - * The USB port uses a 12MHz crystal as clock source - * at USB_XO and USB_XI - */ - if (OCTEON_IS_MODEL(OCTEON_CN3XXX)) - /* From CN31XX,CN30XX manual */ - usbn_clk_ctl.s.p_rtype = 3; /* p_rclk=1 & p_xenbn=1 */ - else - /* From CN56XX,CN52XX,CN50XX manuals. */ - usbn_clk_ctl.s.p_rtype = 0; - - usbn_clk_ctl.s.p_c_sel = 0; - } - /* - * 2c. Select the HCLK via writing USBN0/1_CLK_CTL[DIVIDE, DIVIDE2] and - * setting USBN0/1_CLK_CTL[ENABLE] = 1. Divide the core clock down - * such that USB is as close as possible to 125Mhz - */ - divisor = DIV_ROUND_UP(octeon_get_clock_rate(), 125000000); - /* Lower than 4 doesn't seem to work properly */ - if (divisor < 4) - divisor = 4; - usbn_clk_ctl.s.divide = divisor; - usbn_clk_ctl.s.divide2 = 0; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - - /* 2d. Write USBN0/1_CLK_CTL[HCLK_RST] = 1 */ - usbn_clk_ctl.s.hclk_rst = 1; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - /* 2e. Wait 64 core-clock cycles for HCLK to stabilize */ - __delay(64); - /* - * 3. Program the power-on reset field in the USBN clock-control - * register: - * USBN_CLK_CTL[POR] = 0 - */ - usbn_clk_ctl.s.por = 0; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - /* 4. Wait 1 ms for PHY clock to start */ - mdelay(1); - /* - * 5. Program the Reset input from automatic test equipment field in the - * USBP control and status register: - * USBN_USBP_CTL_STATUS[ATE_RESET] = 1 - */ - usbn_usbp_ctl_status.u64 = - cvmx_read64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index)); - usbn_usbp_ctl_status.s.ate_reset = 1; - cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), - usbn_usbp_ctl_status.u64); - /* 6. Wait 10 cycles */ - __delay(10); - /* - * 7. Clear ATE_RESET field in the USBN clock-control register: - * USBN_USBP_CTL_STATUS[ATE_RESET] = 0 - */ - usbn_usbp_ctl_status.s.ate_reset = 0; - cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), - usbn_usbp_ctl_status.u64); - /* - * 8. Program the PHY reset field in the USBN clock-control register: - * USBN_CLK_CTL[PRST] = 1 - */ - usbn_clk_ctl.s.prst = 1; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - /* - * 9. Program the USBP control and status register to select host or - * device mode. USBN_USBP_CTL_STATUS[HST_MODE] = 0 for host, = 1 for - * device - */ - usbn_usbp_ctl_status.s.hst_mode = 0; - cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), - usbn_usbp_ctl_status.u64); - /* 10. Wait 1 us */ - udelay(1); - /* - * 11. Program the hreset_n field in the USBN clock-control register: - * USBN_CLK_CTL[HRST] = 1 - */ - usbn_clk_ctl.s.hrst = 1; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - /* 12. Proceed to USB core initialization */ - usbn_clk_ctl.s.enable = 1; - cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); - udelay(1); - - /* - * USB Core Initialization - * - * 1. Read USBC_GHWCFG1, USBC_GHWCFG2, USBC_GHWCFG3, USBC_GHWCFG4 to - * determine USB core configuration parameters. - * - * Nothing needed - * - * 2. Program the following fields in the global AHB configuration - * register (USBC_GAHBCFG) - * DMA mode, USBC_GAHBCFG[DMAEn]: 1 = DMA mode, 0 = slave mode - * Burst length, USBC_GAHBCFG[HBSTLEN] = 0 - * Nonperiodic TxFIFO empty level (slave mode only), - * USBC_GAHBCFG[NPTXFEMPLVL] - * Periodic TxFIFO empty level (slave mode only), - * USBC_GAHBCFG[PTXFEMPLVL] - * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 - */ - usbcx_gahbcfg.u32 = 0; - usbcx_gahbcfg.s.dmaen = !(usb->init_flags & - CVMX_USB_INITIALIZE_FLAGS_NO_DMA); - usbcx_gahbcfg.s.hbstlen = 0; - usbcx_gahbcfg.s.nptxfemplvl = 1; - usbcx_gahbcfg.s.ptxfemplvl = 1; - usbcx_gahbcfg.s.glblintrmsk = 1; - cvmx_usb_write_csr32(usb, CVMX_USBCX_GAHBCFG(usb->index), - usbcx_gahbcfg.u32); - - /* - * 3. Program the following fields in USBC_GUSBCFG register. - * HS/FS timeout calibration, USBC_GUSBCFG[TOUTCAL] = 0 - * ULPI DDR select, USBC_GUSBCFG[DDRSEL] = 0 - * USB turnaround time, USBC_GUSBCFG[USBTRDTIM] = 0x5 - * PHY low-power clock select, USBC_GUSBCFG[PHYLPWRCLKSEL] = 0 - */ - usbcx_gusbcfg.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GUSBCFG(usb->index)); - usbcx_gusbcfg.s.toutcal = 0; - usbcx_gusbcfg.s.ddrsel = 0; - usbcx_gusbcfg.s.usbtrdtim = 0x5; - usbcx_gusbcfg.s.phylpwrclksel = 0; - cvmx_usb_write_csr32(usb, CVMX_USBCX_GUSBCFG(usb->index), - usbcx_gusbcfg.u32); - - /* - * 4. The software must unmask the following bits in the USBC_GINTMSK - * register. - * OTG interrupt mask, USBC_GINTMSK[OTGINTMSK] = 1 - * Mode mismatch interrupt mask, USBC_GINTMSK[MODEMISMSK] = 1 - */ - usbcx_gintmsk.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GINTMSK(usb->index)); - usbcx_gintmsk.s.otgintmsk = 1; - usbcx_gintmsk.s.modemismsk = 1; - usbcx_gintmsk.s.hchintmsk = 1; - usbcx_gintmsk.s.sofmsk = 0; - /* We need RX FIFO interrupts if we don't have DMA */ - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) - usbcx_gintmsk.s.rxflvlmsk = 1; - cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTMSK(usb->index), - usbcx_gintmsk.u32); - - /* - * Disable all channel interrupts. We'll enable them per channel later. - */ - for (channel = 0; channel < 8; channel++) - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCINTMSKX(channel, usb->index), - 0); - - /* - * Host Port Initialization - * - * 1. Program the host-port interrupt-mask field to unmask, - * USBC_GINTMSK[PRTINT] = 1 - */ - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, prtintmsk, 1); - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, disconnintmsk, 1); - - /* - * 2. Program the USBC_HCFG register to select full-speed host - * or high-speed host. - */ - usbcx_hcfg.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HCFG(usb->index)); - usbcx_hcfg.s.fslssupp = 0; - usbcx_hcfg.s.fslspclksel = 0; - cvmx_usb_write_csr32(usb, CVMX_USBCX_HCFG(usb->index), usbcx_hcfg.u32); - - cvmx_fifo_setup(usb); - - /* - * If the controller is getting port events right after the reset, it - * means the initialization failed. Try resetting the controller again - * in such case. This is seen to happen after cold boot on DSR-1000N. - */ - usbc_gintsts.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GINTSTS(usb->index)); - cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), - usbc_gintsts.u32); - dev_dbg(dev, "gintsts after reset: 0x%x\n", (int)usbc_gintsts.u32); - if (!usbc_gintsts.s.disconnint && !usbc_gintsts.s.prtint) - return 0; - if (retries++ >= 5) - return -EAGAIN; - dev_info(dev, "controller reset failed (gintsts=0x%x) - retrying\n", - (int)usbc_gintsts.u32); - msleep(50); - cvmx_usb_shutdown(usb); - msleep(50); - goto retry; -} - -/** - * Reset a USB port. After this call succeeds, the USB port is - * online and servicing requests. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - */ -static void cvmx_usb_reset_port(struct octeon_hcd *usb) -{ - usb->usbcx_hprt.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HPRT(usb->index)); - - /* Program the port reset bit to start the reset process */ - USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, - prtrst, 1); - - /* - * Wait at least 50ms (high speed), or 10ms (full speed) for the reset - * process to complete. - */ - mdelay(50); - - /* Program the port reset bit to 0, USBC_HPRT[PRTRST] = 0 */ - USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, - prtrst, 0); - - /* - * Read the port speed field to get the enumerated speed, - * USBC_HPRT[PRTSPD]. - */ - usb->usbcx_hprt.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HPRT(usb->index)); -} - -/** - * Disable a USB port. After this call the USB port will not - * generate data transfers and will not generate events. - * Transactions in process will fail and call their - * associated callbacks. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * - * Returns: 0 or a negative error code. - */ -static int cvmx_usb_disable(struct octeon_hcd *usb) -{ - /* Disable the port */ - USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, - prtena, 1); - return 0; -} - -/** - * Get the current state of the USB port. Use this call to - * determine if the usb port has anything connected, is enabled, - * or has some sort of error condition. The return value of this - * call has "changed" bits to signal of the value of some fields - * have changed between calls. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * - * Returns: Port status information - */ -static struct cvmx_usb_port_status cvmx_usb_get_status(struct octeon_hcd *usb) -{ - union cvmx_usbcx_hprt usbc_hprt; - struct cvmx_usb_port_status result; - - memset(&result, 0, sizeof(result)); - - usbc_hprt.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); - result.port_enabled = usbc_hprt.s.prtena; - result.port_over_current = usbc_hprt.s.prtovrcurract; - result.port_powered = usbc_hprt.s.prtpwr; - result.port_speed = usbc_hprt.s.prtspd; - result.connected = usbc_hprt.s.prtconnsts; - result.connect_change = - result.connected != usb->port_status.connected; - - return result; -} - -/** - * Open a virtual pipe between the host and a USB device. A pipe - * must be opened before data can be transferred between a device - * and Octeon. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @device_addr: - * USB device address to open the pipe to - * (0-127). - * @endpoint_num: - * USB endpoint number to open the pipe to - * (0-15). - * @device_speed: - * The speed of the device the pipe is going - * to. This must match the device's speed, - * which may be different than the port speed. - * @max_packet: The maximum packet length the device can - * transmit/receive (low speed=0-8, full - * speed=0-1023, high speed=0-1024). This value - * comes from the standard endpoint descriptor - * field wMaxPacketSize bits <10:0>. - * @transfer_type: - * The type of transfer this pipe is for. - * @transfer_dir: - * The direction the pipe is in. This is not - * used for control pipes. - * @interval: For ISOCHRONOUS and INTERRUPT transfers, - * this is how often the transfer is scheduled - * for. All other transfers should specify - * zero. The units are in frames (8000/sec at - * high speed, 1000/sec for full speed). - * @multi_count: - * For high speed devices, this is the maximum - * allowed number of packet per microframe. - * Specify zero for non high speed devices. This - * value comes from the standard endpoint descriptor - * field wMaxPacketSize bits <12:11>. - * @hub_device_addr: - * Hub device address this device is connected - * to. Devices connected directly to Octeon - * use zero. This is only used when the device - * is full/low speed behind a high speed hub. - * The address will be of the high speed hub, - * not and full speed hubs after it. - * @hub_port: Which port on the hub the device is - * connected. Use zero for devices connected - * directly to Octeon. Like hub_device_addr, - * this is only used for full/low speed - * devices behind a high speed hub. - * - * Returns: A non-NULL value is a pipe. NULL means an error. - */ -static struct cvmx_usb_pipe *cvmx_usb_open_pipe(struct octeon_hcd *usb, - int device_addr, - int endpoint_num, - enum cvmx_usb_speed - device_speed, - int max_packet, - enum cvmx_usb_transfer - transfer_type, - enum cvmx_usb_direction - transfer_dir, - int interval, int multi_count, - int hub_device_addr, - int hub_port) -{ - struct cvmx_usb_pipe *pipe; - - pipe = kzalloc(sizeof(*pipe), GFP_ATOMIC); - if (!pipe) - return NULL; - if ((device_speed == CVMX_USB_SPEED_HIGH) && - (transfer_dir == CVMX_USB_DIRECTION_OUT) && - (transfer_type == CVMX_USB_TRANSFER_BULK)) - pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; - pipe->device_addr = device_addr; - pipe->endpoint_num = endpoint_num; - pipe->device_speed = device_speed; - pipe->max_packet = max_packet; - pipe->transfer_type = transfer_type; - pipe->transfer_dir = transfer_dir; - INIT_LIST_HEAD(&pipe->transactions); - - /* - * All pipes use interval to rate limit NAK processing. Force an - * interval if one wasn't supplied - */ - if (!interval) - interval = 1; - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - pipe->interval = interval * 8; - /* Force start splits to be schedule on uFrame 0 */ - pipe->next_tx_frame = ((usb->frame_number + 7) & ~7) + - pipe->interval; - } else { - pipe->interval = interval; - pipe->next_tx_frame = usb->frame_number + pipe->interval; - } - pipe->multi_count = multi_count; - pipe->hub_device_addr = hub_device_addr; - pipe->hub_port = hub_port; - pipe->pid_toggle = 0; - pipe->split_sc_frame = -1; - list_add_tail(&pipe->node, &usb->idle_pipes); - - /* - * We don't need to tell the hardware about this pipe yet since - * it doesn't have any submitted requests - */ - - return pipe; -} - -/** - * Poll the RX FIFOs and remove data as needed. This function is only used - * in non DMA mode. It is very important that this function be called quickly - * enough to prevent FIFO overflow. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - */ -static void cvmx_usb_poll_rx_fifo(struct octeon_hcd *usb) -{ - union cvmx_usbcx_grxstsph rx_status; - int channel; - int bytes; - u64 address; - u32 *ptr; - - rx_status.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GRXSTSPH(usb->index)); - /* Only read data if IN data is there */ - if (rx_status.s.pktsts != 2) - return; - /* Check if no data is available */ - if (!rx_status.s.bcnt) - return; - - channel = rx_status.s.chnum; - bytes = rx_status.s.bcnt; - if (!bytes) - return; - - /* Get where the DMA engine would have written this data */ - address = cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index) + - channel * 8); - - ptr = cvmx_phys_to_ptr(address); - cvmx_write64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index) + channel * 8, - address + bytes); - - /* Loop writing the FIFO data for this packet into memory */ - while (bytes > 0) { - *ptr++ = cvmx_usb_read_csr32(usb, - USB_FIFO_ADDRESS(channel, usb->index)); - bytes -= 4; - } - CVMX_SYNCW; -} - -/** - * Fill the TX hardware fifo with data out of the software - * fifos - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @fifo: Software fifo to use - * @available: Amount of space in the hardware fifo - * - * Returns: Non zero if the hardware fifo was too small and needs - * to be serviced again. - */ -static int cvmx_usb_fill_tx_hw(struct octeon_hcd *usb, - struct cvmx_usb_tx_fifo *fifo, int available) -{ - /* - * We're done either when there isn't anymore space or the software FIFO - * is empty - */ - while (available && (fifo->head != fifo->tail)) { - int i = fifo->tail; - const u32 *ptr = cvmx_phys_to_ptr(fifo->entry[i].address); - u64 csr_address = USB_FIFO_ADDRESS(fifo->entry[i].channel, - usb->index) ^ 4; - int words = available; - - /* Limit the amount of data to what the SW fifo has */ - if (fifo->entry[i].size <= available) { - words = fifo->entry[i].size; - fifo->tail++; - if (fifo->tail > MAX_CHANNELS) - fifo->tail = 0; - } - - /* Update the next locations and counts */ - available -= words; - fifo->entry[i].address += words * 4; - fifo->entry[i].size -= words; - - /* - * Write the HW fifo data. The read every three writes is due - * to an errata on CN3XXX chips - */ - while (words > 3) { - cvmx_write64_uint32(csr_address, *ptr++); - cvmx_write64_uint32(csr_address, *ptr++); - cvmx_write64_uint32(csr_address, *ptr++); - cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); - words -= 3; - } - cvmx_write64_uint32(csr_address, *ptr++); - if (--words) { - cvmx_write64_uint32(csr_address, *ptr++); - if (--words) - cvmx_write64_uint32(csr_address, *ptr++); - } - cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); - } - return fifo->head != fifo->tail; -} - -/** - * Check the hardware FIFOs and fill them as needed - * - * @usb: USB device state populated by cvmx_usb_initialize(). - */ -static void cvmx_usb_poll_tx_fifo(struct octeon_hcd *usb) -{ - if (usb->periodic.head != usb->periodic.tail) { - union cvmx_usbcx_hptxsts tx_status; - - tx_status.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HPTXSTS(usb->index)); - if (cvmx_usb_fill_tx_hw(usb, &usb->periodic, - tx_status.s.ptxfspcavail)) - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, ptxfempmsk, 1); - else - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, ptxfempmsk, 0); - } - - if (usb->nonperiodic.head != usb->nonperiodic.tail) { - union cvmx_usbcx_gnptxsts tx_status; - - tx_status.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GNPTXSTS(usb->index)); - if (cvmx_usb_fill_tx_hw(usb, &usb->nonperiodic, - tx_status.s.nptxfspcavail)) - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, nptxfempmsk, 1); - else - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, nptxfempmsk, 0); - } -} - -/** - * Fill the TX FIFO with an outgoing packet - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @channel: Channel number to get packet from - */ -static void cvmx_usb_fill_tx_fifo(struct octeon_hcd *usb, int channel) -{ - union cvmx_usbcx_hccharx hcchar; - union cvmx_usbcx_hcspltx usbc_hcsplt; - union cvmx_usbcx_hctsizx usbc_hctsiz; - struct cvmx_usb_tx_fifo *fifo; - - /* We only need to fill data on outbound channels */ - hcchar.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCCHARX(channel, usb->index)); - if (hcchar.s.epdir != CVMX_USB_DIRECTION_OUT) - return; - - /* OUT Splits only have data on the start and not the complete */ - usbc_hcsplt.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCSPLTX(channel, usb->index)); - if (usbc_hcsplt.s.spltena && usbc_hcsplt.s.compsplt) - return; - - /* - * Find out how many bytes we need to fill and convert it into 32bit - * words. - */ - usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCTSIZX(channel, usb->index)); - if (!usbc_hctsiz.s.xfersize) - return; - - if ((hcchar.s.eptype == CVMX_USB_TRANSFER_INTERRUPT) || - (hcchar.s.eptype == CVMX_USB_TRANSFER_ISOCHRONOUS)) - fifo = &usb->periodic; - else - fifo = &usb->nonperiodic; - - fifo->entry[fifo->head].channel = channel; - fifo->entry[fifo->head].address = - cvmx_read64_uint64(CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + - channel * 8); - fifo->entry[fifo->head].size = (usbc_hctsiz.s.xfersize + 3) >> 2; - fifo->head++; - if (fifo->head > MAX_CHANNELS) - fifo->head = 0; - - cvmx_usb_poll_tx_fifo(usb); -} - -/** - * Perform channel specific setup for Control transactions. All - * the generic stuff will already have been done in cvmx_usb_start_channel(). - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @channel: Channel to setup - * @pipe: Pipe for control transaction - */ -static void cvmx_usb_start_channel_control(struct octeon_hcd *usb, - int channel, - struct cvmx_usb_pipe *pipe) -{ - struct usb_hcd *hcd = octeon_to_hcd(usb); - struct device *dev = hcd->self.controller; - struct cvmx_usb_transaction *transaction = - list_first_entry(&pipe->transactions, typeof(*transaction), - node); - struct usb_ctrlrequest *header = - cvmx_phys_to_ptr(transaction->control_header); - int bytes_to_transfer = transaction->buffer_length - - transaction->actual_bytes; - int packets_to_transfer; - union cvmx_usbcx_hctsizx usbc_hctsiz; - - usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCTSIZX(channel, usb->index)); - - switch (transaction->stage) { - case CVMX_USB_STAGE_NON_CONTROL: - case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: - dev_err(dev, "%s: ERROR - Non control stage\n", __func__); - break; - case CVMX_USB_STAGE_SETUP: - usbc_hctsiz.s.pid = 3; /* Setup */ - bytes_to_transfer = sizeof(*header); - /* All Control operations start with a setup going OUT */ - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, epdir, - CVMX_USB_DIRECTION_OUT); - /* - * Setup send the control header instead of the buffer data. The - * buffer data will be used in the next stage - */ - cvmx_write64_uint64(CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + - channel * 8, - transaction->control_header); - break; - case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: - usbc_hctsiz.s.pid = 3; /* Setup */ - bytes_to_transfer = 0; - /* All Control operations start with a setup going OUT */ - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, epdir, - CVMX_USB_DIRECTION_OUT); - - USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), - cvmx_usbcx_hcspltx, compsplt, 1); - break; - case CVMX_USB_STAGE_DATA: - usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - if (header->bRequestType & USB_DIR_IN) - bytes_to_transfer = 0; - else if (bytes_to_transfer > pipe->max_packet) - bytes_to_transfer = pipe->max_packet; - } - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, epdir, - ((header->bRequestType & USB_DIR_IN) ? - CVMX_USB_DIRECTION_IN : - CVMX_USB_DIRECTION_OUT)); - break; - case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: - usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); - if (!(header->bRequestType & USB_DIR_IN)) - bytes_to_transfer = 0; - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, epdir, - ((header->bRequestType & USB_DIR_IN) ? - CVMX_USB_DIRECTION_IN : - CVMX_USB_DIRECTION_OUT)); - USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), - cvmx_usbcx_hcspltx, compsplt, 1); - break; - case CVMX_USB_STAGE_STATUS: - usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); - bytes_to_transfer = 0; - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, epdir, - ((header->bRequestType & USB_DIR_IN) ? - CVMX_USB_DIRECTION_OUT : - CVMX_USB_DIRECTION_IN)); - break; - case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: - usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); - bytes_to_transfer = 0; - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, epdir, - ((header->bRequestType & USB_DIR_IN) ? - CVMX_USB_DIRECTION_OUT : - CVMX_USB_DIRECTION_IN)); - USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), - cvmx_usbcx_hcspltx, compsplt, 1); - break; - } - - /* - * Make sure the transfer never exceeds the byte limit of the hardware. - * Further bytes will be sent as continued transactions - */ - if (bytes_to_transfer > MAX_TRANSFER_BYTES) { - /* Round MAX_TRANSFER_BYTES to a multiple of out packet size */ - bytes_to_transfer = MAX_TRANSFER_BYTES / pipe->max_packet; - bytes_to_transfer *= pipe->max_packet; - } - - /* - * Calculate the number of packets to transfer. If the length is zero - * we still need to transfer one packet - */ - packets_to_transfer = DIV_ROUND_UP(bytes_to_transfer, - pipe->max_packet); - if (packets_to_transfer == 0) { - packets_to_transfer = 1; - } else if ((packets_to_transfer > 1) && - (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { - /* - * Limit to one packet when not using DMA. Channels must be - * restarted between every packet for IN transactions, so there - * is no reason to do multiple packets in a row - */ - packets_to_transfer = 1; - bytes_to_transfer = packets_to_transfer * pipe->max_packet; - } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { - /* - * Limit the number of packet and data transferred to what the - * hardware can handle - */ - packets_to_transfer = MAX_TRANSFER_PACKETS; - bytes_to_transfer = packets_to_transfer * pipe->max_packet; - } - - usbc_hctsiz.s.xfersize = bytes_to_transfer; - usbc_hctsiz.s.pktcnt = packets_to_transfer; - - cvmx_usb_write_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index), - usbc_hctsiz.u32); -} - -/** - * Start a channel to perform the pipe's head transaction - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @channel: Channel to setup - * @pipe: Pipe to start - */ -static void cvmx_usb_start_channel(struct octeon_hcd *usb, int channel, - struct cvmx_usb_pipe *pipe) -{ - struct cvmx_usb_transaction *transaction = - list_first_entry(&pipe->transactions, typeof(*transaction), - node); - - /* Make sure all writes to the DMA region get flushed */ - CVMX_SYNCW; - - /* Attach the channel to the pipe */ - usb->pipe_for_channel[channel] = pipe; - pipe->channel = channel; - pipe->flags |= CVMX_USB_PIPE_FLAGS_SCHEDULED; - - /* Mark this channel as in use */ - usb->idle_hardware_channels &= ~(1 << channel); - - /* Enable the channel interrupt bits */ - { - union cvmx_usbcx_hcintx usbc_hcint; - union cvmx_usbcx_hcintmskx usbc_hcintmsk; - union cvmx_usbcx_haintmsk usbc_haintmsk; - - /* Clear all channel status bits */ - usbc_hcint.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCINTX(channel, usb->index)); - - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCINTX(channel, usb->index), - usbc_hcint.u32); - - usbc_hcintmsk.u32 = 0; - usbc_hcintmsk.s.chhltdmsk = 1; - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { - /* - * Channels need these extra interrupts when we aren't - * in DMA mode. - */ - usbc_hcintmsk.s.datatglerrmsk = 1; - usbc_hcintmsk.s.frmovrunmsk = 1; - usbc_hcintmsk.s.bblerrmsk = 1; - usbc_hcintmsk.s.xacterrmsk = 1; - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - /* - * Splits don't generate xfercompl, so we need - * ACK and NYET. - */ - usbc_hcintmsk.s.nyetmsk = 1; - usbc_hcintmsk.s.ackmsk = 1; - } - usbc_hcintmsk.s.nakmsk = 1; - usbc_hcintmsk.s.stallmsk = 1; - usbc_hcintmsk.s.xfercomplmsk = 1; - } - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCINTMSKX(channel, usb->index), - usbc_hcintmsk.u32); - - /* Enable the channel interrupt to propagate */ - usbc_haintmsk.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HAINTMSK(usb->index)); - usbc_haintmsk.s.haintmsk |= 1 << channel; - cvmx_usb_write_csr32(usb, CVMX_USBCX_HAINTMSK(usb->index), - usbc_haintmsk.u32); - } - - /* Setup the location the DMA engine uses. */ - { - u64 reg; - u64 dma_address = transaction->buffer + - transaction->actual_bytes; - - if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) - dma_address = transaction->buffer + - transaction->iso_packets[0].offset + - transaction->actual_bytes; - - if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) - reg = CVMX_USBNX_DMA0_OUTB_CHN0(usb->index); - else - reg = CVMX_USBNX_DMA0_INB_CHN0(usb->index); - cvmx_write64_uint64(reg + channel * 8, dma_address); - } - - /* Setup both the size of the transfer and the SPLIT characteristics */ - { - union cvmx_usbcx_hcspltx usbc_hcsplt = {.u32 = 0}; - union cvmx_usbcx_hctsizx usbc_hctsiz = {.u32 = 0}; - int packets_to_transfer; - int bytes_to_transfer = transaction->buffer_length - - transaction->actual_bytes; - - /* - * ISOCHRONOUS transactions store each individual transfer size - * in the packet structure, not the global buffer_length - */ - if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) - bytes_to_transfer = - transaction->iso_packets[0].length - - transaction->actual_bytes; - - /* - * We need to do split transactions when we are talking to non - * high speed devices that are behind a high speed hub - */ - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - /* - * On the start split phase (stage is even) record the - * frame number we will need to send the split complete. - * We only store the lower two bits since the time ahead - * can only be two frames - */ - if ((transaction->stage & 1) == 0) { - if (transaction->type == CVMX_USB_TRANSFER_BULK) - pipe->split_sc_frame = - (usb->frame_number + 1) & 0x7f; - else - pipe->split_sc_frame = - (usb->frame_number + 2) & 0x7f; - } else { - pipe->split_sc_frame = -1; - } - - usbc_hcsplt.s.spltena = 1; - usbc_hcsplt.s.hubaddr = pipe->hub_device_addr; - usbc_hcsplt.s.prtaddr = pipe->hub_port; - usbc_hcsplt.s.compsplt = (transaction->stage == - CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE); - - /* - * SPLIT transactions can only ever transmit one data - * packet so limit the transfer size to the max packet - * size - */ - if (bytes_to_transfer > pipe->max_packet) - bytes_to_transfer = pipe->max_packet; - - /* - * ISOCHRONOUS OUT splits are unique in that they limit - * data transfers to 188 byte chunks representing the - * begin/middle/end of the data or all - */ - if (!usbc_hcsplt.s.compsplt && - (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && - (pipe->transfer_type == - CVMX_USB_TRANSFER_ISOCHRONOUS)) { - /* - * Clear the split complete frame number as - * there isn't going to be a split complete - */ - pipe->split_sc_frame = -1; - /* - * See if we've started this transfer and sent - * data - */ - if (transaction->actual_bytes == 0) { - /* - * Nothing sent yet, this is either a - * begin or the entire payload - */ - if (bytes_to_transfer <= 188) - /* Entire payload in one go */ - usbc_hcsplt.s.xactpos = 3; - else - /* First part of payload */ - usbc_hcsplt.s.xactpos = 2; - } else { - /* - * Continuing the previous data, we must - * either be in the middle or at the end - */ - if (bytes_to_transfer <= 188) - /* End of payload */ - usbc_hcsplt.s.xactpos = 1; - else - /* Middle of payload */ - usbc_hcsplt.s.xactpos = 0; - } - /* - * Again, the transfer size is limited to 188 - * bytes - */ - if (bytes_to_transfer > 188) - bytes_to_transfer = 188; - } - } - - /* - * Make sure the transfer never exceeds the byte limit of the - * hardware. Further bytes will be sent as continued - * transactions - */ - if (bytes_to_transfer > MAX_TRANSFER_BYTES) { - /* - * Round MAX_TRANSFER_BYTES to a multiple of out packet - * size - */ - bytes_to_transfer = MAX_TRANSFER_BYTES / - pipe->max_packet; - bytes_to_transfer *= pipe->max_packet; - } - - /* - * Calculate the number of packets to transfer. If the length is - * zero we still need to transfer one packet - */ - packets_to_transfer = - DIV_ROUND_UP(bytes_to_transfer, pipe->max_packet); - if (packets_to_transfer == 0) { - packets_to_transfer = 1; - } else if ((packets_to_transfer > 1) && - (usb->init_flags & - CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { - /* - * Limit to one packet when not using DMA. Channels must - * be restarted between every packet for IN - * transactions, so there is no reason to do multiple - * packets in a row - */ - packets_to_transfer = 1; - bytes_to_transfer = packets_to_transfer * - pipe->max_packet; - } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { - /* - * Limit the number of packet and data transferred to - * what the hardware can handle - */ - packets_to_transfer = MAX_TRANSFER_PACKETS; - bytes_to_transfer = packets_to_transfer * - pipe->max_packet; - } - - usbc_hctsiz.s.xfersize = bytes_to_transfer; - usbc_hctsiz.s.pktcnt = packets_to_transfer; - - /* Update the DATA0/DATA1 toggle */ - usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); - /* - * High speed pipes may need a hardware ping before they start - */ - if (pipe->flags & CVMX_USB_PIPE_FLAGS_NEED_PING) - usbc_hctsiz.s.dopng = 1; - - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCSPLTX(channel, usb->index), - usbc_hcsplt.u32); - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCTSIZX(channel, usb->index), - usbc_hctsiz.u32); - } - - /* Setup the Host Channel Characteristics Register */ - { - union cvmx_usbcx_hccharx usbc_hcchar = {.u32 = 0}; - - /* - * Set the startframe odd/even properly. This is only used for - * periodic - */ - usbc_hcchar.s.oddfrm = usb->frame_number & 1; - - /* - * Set the number of back to back packets allowed by this - * endpoint. Split transactions interpret "ec" as the number of - * immediate retries of failure. These retries happen too - * quickly, so we disable these entirely for splits - */ - if (cvmx_usb_pipe_needs_split(usb, pipe)) - usbc_hcchar.s.ec = 1; - else if (pipe->multi_count < 1) - usbc_hcchar.s.ec = 1; - else if (pipe->multi_count > 3) - usbc_hcchar.s.ec = 3; - else - usbc_hcchar.s.ec = pipe->multi_count; - - /* Set the rest of the endpoint specific settings */ - usbc_hcchar.s.devaddr = pipe->device_addr; - usbc_hcchar.s.eptype = transaction->type; - usbc_hcchar.s.lspddev = - (pipe->device_speed == CVMX_USB_SPEED_LOW); - usbc_hcchar.s.epdir = pipe->transfer_dir; - usbc_hcchar.s.epnum = pipe->endpoint_num; - usbc_hcchar.s.mps = pipe->max_packet; - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCCHARX(channel, usb->index), - usbc_hcchar.u32); - } - - /* Do transaction type specific fixups as needed */ - switch (transaction->type) { - case CVMX_USB_TRANSFER_CONTROL: - cvmx_usb_start_channel_control(usb, channel, pipe); - break; - case CVMX_USB_TRANSFER_BULK: - case CVMX_USB_TRANSFER_INTERRUPT: - break; - case CVMX_USB_TRANSFER_ISOCHRONOUS: - if (!cvmx_usb_pipe_needs_split(usb, pipe)) { - /* - * ISO transactions require different PIDs depending on - * direction and how many packets are needed - */ - if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { - if (pipe->multi_count < 2) /* Need DATA0 */ - USB_SET_FIELD32( - CVMX_USBCX_HCTSIZX(channel, - usb->index), - cvmx_usbcx_hctsizx, pid, 0); - else /* Need MDATA */ - USB_SET_FIELD32( - CVMX_USBCX_HCTSIZX(channel, - usb->index), - cvmx_usbcx_hctsizx, pid, 3); - } - } - break; - } - { - union cvmx_usbcx_hctsizx usbc_hctsiz = { .u32 = - cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCTSIZX(channel, - usb->index)) - }; - transaction->xfersize = usbc_hctsiz.s.xfersize; - transaction->pktcnt = usbc_hctsiz.s.pktcnt; - } - /* Remember when we start a split transaction */ - if (cvmx_usb_pipe_needs_split(usb, pipe)) - usb->active_split = transaction; - USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), - cvmx_usbcx_hccharx, chena, 1); - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) - cvmx_usb_fill_tx_fifo(usb, channel); -} - -/** - * Find a pipe that is ready to be scheduled to hardware. - * @usb: USB device state populated by cvmx_usb_initialize(). - * @xfer_type: Transfer type - * - * Returns: Pipe or NULL if none are ready - */ -static struct cvmx_usb_pipe *cvmx_usb_find_ready_pipe(struct octeon_hcd *usb, - enum cvmx_usb_transfer xfer_type) -{ - struct list_head *list = usb->active_pipes + xfer_type; - u64 current_frame = usb->frame_number; - struct cvmx_usb_pipe *pipe; - - list_for_each_entry(pipe, list, node) { - struct cvmx_usb_transaction *t = - list_first_entry(&pipe->transactions, typeof(*t), - node); - if (!(pipe->flags & CVMX_USB_PIPE_FLAGS_SCHEDULED) && t && - (pipe->next_tx_frame <= current_frame) && - ((pipe->split_sc_frame == -1) || - ((((int)current_frame - pipe->split_sc_frame) & 0x7f) < - 0x40)) && - (!usb->active_split || (usb->active_split == t))) { - prefetch(t); - return pipe; - } - } - return NULL; -} - -static struct cvmx_usb_pipe *cvmx_usb_next_pipe(struct octeon_hcd *usb, - int is_sof) -{ - struct cvmx_usb_pipe *pipe; - - /* Find a pipe needing service. */ - if (is_sof) { - /* - * Only process periodic pipes on SOF interrupts. This way we - * are sure that the periodic data is sent in the beginning of - * the frame. - */ - pipe = cvmx_usb_find_ready_pipe(usb, - CVMX_USB_TRANSFER_ISOCHRONOUS); - if (pipe) - return pipe; - pipe = cvmx_usb_find_ready_pipe(usb, - CVMX_USB_TRANSFER_INTERRUPT); - if (pipe) - return pipe; - } - pipe = cvmx_usb_find_ready_pipe(usb, CVMX_USB_TRANSFER_CONTROL); - if (pipe) - return pipe; - return cvmx_usb_find_ready_pipe(usb, CVMX_USB_TRANSFER_BULK); -} - -/** - * Called whenever a pipe might need to be scheduled to the - * hardware. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @is_sof: True if this schedule was called on a SOF interrupt. - */ -static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) -{ - int channel; - struct cvmx_usb_pipe *pipe; - int need_sof; - enum cvmx_usb_transfer ttype; - - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { - /* - * Without DMA we need to be careful to not schedule something - * at the end of a frame and cause an overrun. - */ - union cvmx_usbcx_hfnum hfnum = { - .u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HFNUM(usb->index)) - }; - - union cvmx_usbcx_hfir hfir = { - .u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HFIR(usb->index)) - }; - - if (hfnum.s.frrem < hfir.s.frint / 4) - goto done; - } - - while (usb->idle_hardware_channels) { - /* Find an idle channel */ - channel = __fls(usb->idle_hardware_channels); - if (unlikely(channel > 7)) - break; - - pipe = cvmx_usb_next_pipe(usb, is_sof); - if (!pipe) - break; - - cvmx_usb_start_channel(usb, channel, pipe); - } - -done: - /* - * Only enable SOF interrupts when we have transactions pending in the - * future that might need to be scheduled - */ - need_sof = 0; - for (ttype = CVMX_USB_TRANSFER_CONTROL; - ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { - list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { - if (pipe->next_tx_frame > usb->frame_number) { - need_sof = 1; - break; - } - } - } - USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), - cvmx_usbcx_gintmsk, sofmsk, need_sof); -} - -static void octeon_usb_urb_complete_callback(struct octeon_hcd *usb, - enum cvmx_usb_status status, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction - *transaction, - int bytes_transferred, - struct urb *urb) -{ - struct usb_hcd *hcd = octeon_to_hcd(usb); - struct device *dev = hcd->self.controller; - - if (likely(status == CVMX_USB_STATUS_OK)) - urb->actual_length = bytes_transferred; - else - urb->actual_length = 0; - - urb->hcpriv = NULL; - - /* For Isochronous transactions we need to update the URB packet status - * list from data in our private copy - */ - if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { - int i; - /* - * The pointer to the private list is stored in the setup_packet - * field. - */ - struct cvmx_usb_iso_packet *iso_packet = - (struct cvmx_usb_iso_packet *)urb->setup_packet; - /* Recalculate the transfer size by adding up each packet */ - urb->actual_length = 0; - for (i = 0; i < urb->number_of_packets; i++) { - if (iso_packet[i].status == CVMX_USB_STATUS_OK) { - urb->iso_frame_desc[i].status = 0; - urb->iso_frame_desc[i].actual_length = - iso_packet[i].length; - urb->actual_length += - urb->iso_frame_desc[i].actual_length; - } else { - dev_dbg(dev, "ISOCHRONOUS packet=%d of %d status=%d pipe=%p transaction=%p size=%d\n", - i, urb->number_of_packets, - iso_packet[i].status, pipe, - transaction, iso_packet[i].length); - urb->iso_frame_desc[i].status = -EREMOTEIO; - } - } - /* Free the private list now that we don't need it anymore */ - kfree(iso_packet); - urb->setup_packet = NULL; - } - - switch (status) { - case CVMX_USB_STATUS_OK: - urb->status = 0; - break; - case CVMX_USB_STATUS_CANCEL: - if (urb->status == 0) - urb->status = -ENOENT; - break; - case CVMX_USB_STATUS_STALL: - dev_dbg(dev, "status=stall pipe=%p transaction=%p size=%d\n", - pipe, transaction, bytes_transferred); - urb->status = -EPIPE; - break; - case CVMX_USB_STATUS_BABBLEERR: - dev_dbg(dev, "status=babble pipe=%p transaction=%p size=%d\n", - pipe, transaction, bytes_transferred); - urb->status = -EPIPE; - break; - case CVMX_USB_STATUS_SHORT: - dev_dbg(dev, "status=short pipe=%p transaction=%p size=%d\n", - pipe, transaction, bytes_transferred); - urb->status = -EREMOTEIO; - break; - case CVMX_USB_STATUS_ERROR: - case CVMX_USB_STATUS_XACTERR: - case CVMX_USB_STATUS_DATATGLERR: - case CVMX_USB_STATUS_FRAMEERR: - dev_dbg(dev, "status=%d pipe=%p transaction=%p size=%d\n", - status, pipe, transaction, bytes_transferred); - urb->status = -EPROTO; - break; - } - usb_hcd_unlink_urb_from_ep(octeon_to_hcd(usb), urb); - spin_unlock(&usb->lock); - usb_hcd_giveback_urb(octeon_to_hcd(usb), urb, urb->status); - spin_lock(&usb->lock); -} - -/** - * Signal the completion of a transaction and free it. The - * transaction will be removed from the pipe transaction list. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Pipe the transaction is on - * @transaction: - * Transaction that completed - * @complete_code: - * Completion code - */ -static void cvmx_usb_complete(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction *transaction, - enum cvmx_usb_status complete_code) -{ - /* If this was a split then clear our split in progress marker */ - if (usb->active_split == transaction) - usb->active_split = NULL; - - /* - * Isochronous transactions need extra processing as they might not be - * done after a single data transfer - */ - if (unlikely(transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS)) { - /* Update the number of bytes transferred in this ISO packet */ - transaction->iso_packets[0].length = transaction->actual_bytes; - transaction->iso_packets[0].status = complete_code; - - /* - * If there are more ISOs pending and we succeeded, schedule the - * next one - */ - if ((transaction->iso_number_packets > 1) && - (complete_code == CVMX_USB_STATUS_OK)) { - /* No bytes transferred for this packet as of yet */ - transaction->actual_bytes = 0; - /* One less ISO waiting to transfer */ - transaction->iso_number_packets--; - /* Increment to the next location in our packet array */ - transaction->iso_packets++; - transaction->stage = CVMX_USB_STAGE_NON_CONTROL; - return; - } - } - - /* Remove the transaction from the pipe list */ - list_del(&transaction->node); - if (list_empty(&pipe->transactions)) - list_move_tail(&pipe->node, &usb->idle_pipes); - octeon_usb_urb_complete_callback(usb, complete_code, pipe, - transaction, - transaction->actual_bytes, - transaction->urb); - kfree(transaction); -} - -/** - * Submit a usb transaction to a pipe. Called for all types - * of transactions. - * - * @usb: - * @pipe: Which pipe to submit to. - * @type: Transaction type - * @buffer: User buffer for the transaction - * @buffer_length: - * User buffer's length in bytes - * @control_header: - * For control transactions, the 8 byte standard header - * @iso_start_frame: - * For ISO transactions, the start frame - * @iso_number_packets: - * For ISO, the number of packet in the transaction. - * @iso_packets: - * A description of each ISO packet - * @urb: URB for the callback - * - * Returns: Transaction or NULL on failure. - */ -static struct cvmx_usb_transaction *cvmx_usb_submit_transaction( - struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - enum cvmx_usb_transfer type, - u64 buffer, - int buffer_length, - u64 control_header, - int iso_start_frame, - int iso_number_packets, - struct cvmx_usb_iso_packet *iso_packets, - struct urb *urb) -{ - struct cvmx_usb_transaction *transaction; - - if (unlikely(pipe->transfer_type != type)) - return NULL; - - transaction = kzalloc(sizeof(*transaction), GFP_ATOMIC); - if (unlikely(!transaction)) - return NULL; - - transaction->type = type; - transaction->buffer = buffer; - transaction->buffer_length = buffer_length; - transaction->control_header = control_header; - /* FIXME: This is not used, implement it. */ - transaction->iso_start_frame = iso_start_frame; - transaction->iso_number_packets = iso_number_packets; - transaction->iso_packets = iso_packets; - transaction->urb = urb; - if (transaction->type == CVMX_USB_TRANSFER_CONTROL) - transaction->stage = CVMX_USB_STAGE_SETUP; - else - transaction->stage = CVMX_USB_STAGE_NON_CONTROL; - - if (!list_empty(&pipe->transactions)) { - list_add_tail(&transaction->node, &pipe->transactions); - } else { - list_add_tail(&transaction->node, &pipe->transactions); - list_move_tail(&pipe->node, - &usb->active_pipes[pipe->transfer_type]); - - /* - * We may need to schedule the pipe if this was the head of the - * pipe. - */ - cvmx_usb_schedule(usb, 0); - } - - return transaction; -} - -/** - * Call to submit a USB Bulk transfer to a pipe. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Handle to the pipe for the transfer. - * @urb: URB. - * - * Returns: A submitted transaction or NULL on failure. - */ -static struct cvmx_usb_transaction *cvmx_usb_submit_bulk( - struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct urb *urb) -{ - return cvmx_usb_submit_transaction(usb, pipe, CVMX_USB_TRANSFER_BULK, - urb->transfer_dma, - urb->transfer_buffer_length, - 0, /* control_header */ - 0, /* iso_start_frame */ - 0, /* iso_number_packets */ - NULL, /* iso_packets */ - urb); -} - -/** - * Call to submit a USB Interrupt transfer to a pipe. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Handle to the pipe for the transfer. - * @urb: URB returned when the callback is called. - * - * Returns: A submitted transaction or NULL on failure. - */ -static struct cvmx_usb_transaction *cvmx_usb_submit_interrupt( - struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct urb *urb) -{ - return cvmx_usb_submit_transaction(usb, pipe, - CVMX_USB_TRANSFER_INTERRUPT, - urb->transfer_dma, - urb->transfer_buffer_length, - 0, /* control_header */ - 0, /* iso_start_frame */ - 0, /* iso_number_packets */ - NULL, /* iso_packets */ - urb); -} - -/** - * Call to submit a USB Control transfer to a pipe. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Handle to the pipe for the transfer. - * @urb: URB. - * - * Returns: A submitted transaction or NULL on failure. - */ -static struct cvmx_usb_transaction *cvmx_usb_submit_control( - struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct urb *urb) -{ - int buffer_length = urb->transfer_buffer_length; - u64 control_header = urb->setup_dma; - struct usb_ctrlrequest *header = cvmx_phys_to_ptr(control_header); - - if ((header->bRequestType & USB_DIR_IN) == 0) - buffer_length = le16_to_cpu(header->wLength); - - return cvmx_usb_submit_transaction(usb, pipe, - CVMX_USB_TRANSFER_CONTROL, - urb->transfer_dma, buffer_length, - control_header, - 0, /* iso_start_frame */ - 0, /* iso_number_packets */ - NULL, /* iso_packets */ - urb); -} - -/** - * Call to submit a USB Isochronous transfer to a pipe. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Handle to the pipe for the transfer. - * @urb: URB returned when the callback is called. - * - * Returns: A submitted transaction or NULL on failure. - */ -static struct cvmx_usb_transaction *cvmx_usb_submit_isochronous( - struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct urb *urb) -{ - struct cvmx_usb_iso_packet *packets; - - packets = (struct cvmx_usb_iso_packet *)urb->setup_packet; - return cvmx_usb_submit_transaction(usb, pipe, - CVMX_USB_TRANSFER_ISOCHRONOUS, - urb->transfer_dma, - urb->transfer_buffer_length, - 0, /* control_header */ - urb->start_frame, - urb->number_of_packets, - packets, urb); -} - -/** - * Cancel one outstanding request in a pipe. Canceling a request - * can fail if the transaction has already completed before cancel - * is called. Even after a successful cancel call, it may take - * a frame or two for the cvmx_usb_poll() function to call the - * associated callback. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Pipe to cancel requests in. - * @transaction: Transaction to cancel, returned by the submit function. - * - * Returns: 0 or a negative error code. - */ -static int cvmx_usb_cancel(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction *transaction) -{ - /* - * If the transaction is the HEAD of the queue and scheduled. We need to - * treat it special - */ - if (list_first_entry(&pipe->transactions, typeof(*transaction), node) == - transaction && (pipe->flags & CVMX_USB_PIPE_FLAGS_SCHEDULED)) { - union cvmx_usbcx_hccharx usbc_hcchar; - - usb->pipe_for_channel[pipe->channel] = NULL; - pipe->flags &= ~CVMX_USB_PIPE_FLAGS_SCHEDULED; - - CVMX_SYNCW; - - usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCCHARX(pipe->channel, - usb->index)); - /* - * If the channel isn't enabled then the transaction already - * completed. - */ - if (usbc_hcchar.s.chena) { - usbc_hcchar.s.chdis = 1; - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCCHARX(pipe->channel, - usb->index), - usbc_hcchar.u32); - } - } - cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_CANCEL); - return 0; -} - -/** - * Cancel all outstanding requests in a pipe. Logically all this - * does is call cvmx_usb_cancel() in a loop. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Pipe to cancel requests in. - * - * Returns: 0 or a negative error code. - */ -static int cvmx_usb_cancel_all(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe) -{ - struct cvmx_usb_transaction *transaction, *next; - - /* Simply loop through and attempt to cancel each transaction */ - list_for_each_entry_safe(transaction, next, &pipe->transactions, node) { - int result = cvmx_usb_cancel(usb, pipe, transaction); - - if (unlikely(result != 0)) - return result; - } - return 0; -} - -/** - * Close a pipe created with cvmx_usb_open_pipe(). - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * @pipe: Pipe to close. - * - * Returns: 0 or a negative error code. EBUSY is returned if the pipe has - * outstanding transfers. - */ -static int cvmx_usb_close_pipe(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe) -{ - /* Fail if the pipe has pending transactions */ - if (!list_empty(&pipe->transactions)) - return -EBUSY; - - list_del(&pipe->node); - kfree(pipe); - - return 0; -} - -/** - * Get the current USB protocol level frame number. The frame - * number is always in the range of 0-0x7ff. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * - * Returns: USB frame number - */ -static int cvmx_usb_get_frame_number(struct octeon_hcd *usb) -{ - union cvmx_usbcx_hfnum usbc_hfnum; - - usbc_hfnum.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); - - return usbc_hfnum.s.frnum; -} - -static void cvmx_usb_transfer_control(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction *transaction, - union cvmx_usbcx_hccharx usbc_hcchar, - int buffer_space_left, - int bytes_in_last_packet) -{ - switch (transaction->stage) { - case CVMX_USB_STAGE_NON_CONTROL: - case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: - /* This should be impossible */ - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_ERROR); - break; - case CVMX_USB_STAGE_SETUP: - pipe->pid_toggle = 1; - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - transaction->stage = - CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE; - } else { - struct usb_ctrlrequest *header = - cvmx_phys_to_ptr(transaction->control_header); - if (header->wLength) - transaction->stage = CVMX_USB_STAGE_DATA; - else - transaction->stage = CVMX_USB_STAGE_STATUS; - } - break; - case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: - { - struct usb_ctrlrequest *header = - cvmx_phys_to_ptr(transaction->control_header); - if (header->wLength) - transaction->stage = CVMX_USB_STAGE_DATA; - else - transaction->stage = CVMX_USB_STAGE_STATUS; - } - break; - case CVMX_USB_STAGE_DATA: - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - transaction->stage = CVMX_USB_STAGE_DATA_SPLIT_COMPLETE; - /* - * For setup OUT data that are splits, - * the hardware doesn't appear to count - * transferred data. Here we manually - * update the data transferred - */ - if (!usbc_hcchar.s.epdir) { - if (buffer_space_left < pipe->max_packet) - transaction->actual_bytes += - buffer_space_left; - else - transaction->actual_bytes += - pipe->max_packet; - } - } else if ((buffer_space_left == 0) || - (bytes_in_last_packet < pipe->max_packet)) { - pipe->pid_toggle = 1; - transaction->stage = CVMX_USB_STAGE_STATUS; - } - break; - case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: - if ((buffer_space_left == 0) || - (bytes_in_last_packet < pipe->max_packet)) { - pipe->pid_toggle = 1; - transaction->stage = CVMX_USB_STAGE_STATUS; - } else { - transaction->stage = CVMX_USB_STAGE_DATA; - } - break; - case CVMX_USB_STAGE_STATUS: - if (cvmx_usb_pipe_needs_split(usb, pipe)) - transaction->stage = - CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE; - else - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_OK); - break; - case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: - cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); - break; - } -} - -static void cvmx_usb_transfer_bulk(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction *transaction, - union cvmx_usbcx_hcintx usbc_hcint, - int buffer_space_left, - int bytes_in_last_packet) -{ - /* - * The only time a bulk transfer isn't complete when it finishes with - * an ACK is during a split transaction. For splits we need to continue - * the transfer if more data is needed. - */ - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) - transaction->stage = - CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; - else if (buffer_space_left && - (bytes_in_last_packet == pipe->max_packet)) - transaction->stage = CVMX_USB_STAGE_NON_CONTROL; - else - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_OK); - } else { - if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && - (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && - (usbc_hcint.s.nak)) - pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; - if (!buffer_space_left || - (bytes_in_last_packet < pipe->max_packet)) - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_OK); - } -} - -static void cvmx_usb_transfer_intr(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction *transaction, - int buffer_space_left, - int bytes_in_last_packet) -{ - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) { - transaction->stage = - CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; - } else if (buffer_space_left && - (bytes_in_last_packet == pipe->max_packet)) { - transaction->stage = CVMX_USB_STAGE_NON_CONTROL; - } else { - pipe->next_tx_frame += pipe->interval; - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_OK); - } - } else if (!buffer_space_left || - (bytes_in_last_packet < pipe->max_packet)) { - pipe->next_tx_frame += pipe->interval; - cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); - } -} - -static void cvmx_usb_transfer_isoc(struct octeon_hcd *usb, - struct cvmx_usb_pipe *pipe, - struct cvmx_usb_transaction *transaction, - int buffer_space_left, - int bytes_in_last_packet, - int bytes_this_transfer) -{ - if (cvmx_usb_pipe_needs_split(usb, pipe)) { - /* - * ISOCHRONOUS OUT splits don't require a complete split stage. - * Instead they use a sequence of begin OUT splits to transfer - * the data 188 bytes at a time. Once the transfer is complete, - * the pipe sleeps until the next schedule interval. - */ - if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { - /* - * If no space left or this wasn't a max size packet - * then this transfer is complete. Otherwise start it - * again to send the next 188 bytes - */ - if (!buffer_space_left || (bytes_this_transfer < 188)) { - pipe->next_tx_frame += pipe->interval; - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_OK); - } - return; - } - if (transaction->stage == - CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE) { - /* - * We are in the incoming data phase. Keep getting data - * until we run out of space or get a small packet - */ - if ((buffer_space_left == 0) || - (bytes_in_last_packet < pipe->max_packet)) { - pipe->next_tx_frame += pipe->interval; - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_OK); - } - } else { - transaction->stage = - CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; - } - } else { - pipe->next_tx_frame += pipe->interval; - cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); - } -} - -/** - * Poll a channel for status - * - * @usb: USB device - * @channel: Channel to poll - * - * Returns: Zero on success - */ -static int cvmx_usb_poll_channel(struct octeon_hcd *usb, int channel) -{ - struct usb_hcd *hcd = octeon_to_hcd(usb); - struct device *dev = hcd->self.controller; - union cvmx_usbcx_hcintx usbc_hcint; - union cvmx_usbcx_hctsizx usbc_hctsiz; - union cvmx_usbcx_hccharx usbc_hcchar; - struct cvmx_usb_pipe *pipe; - struct cvmx_usb_transaction *transaction; - int bytes_this_transfer; - int bytes_in_last_packet; - int packets_processed; - int buffer_space_left; - - /* Read the interrupt status bits for the channel */ - usbc_hcint.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCINTX(channel, usb->index)); - - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { - usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCCHARX(channel, - usb->index)); - - if (usbc_hcchar.s.chena && usbc_hcchar.s.chdis) { - /* - * There seems to be a bug in CN31XX which can cause - * interrupt IN transfers to get stuck until we do a - * write of HCCHARX without changing things - */ - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCCHARX(channel, - usb->index), - usbc_hcchar.u32); - return 0; - } - - /* - * In non DMA mode the channels don't halt themselves. We need - * to manually disable channels that are left running - */ - if (!usbc_hcint.s.chhltd) { - if (usbc_hcchar.s.chena) { - union cvmx_usbcx_hcintmskx hcintmsk; - /* Disable all interrupts except CHHLTD */ - hcintmsk.u32 = 0; - hcintmsk.s.chhltdmsk = 1; - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCINTMSKX(channel, usb->index), - hcintmsk.u32); - usbc_hcchar.s.chdis = 1; - cvmx_usb_write_csr32(usb, - CVMX_USBCX_HCCHARX(channel, usb->index), - usbc_hcchar.u32); - return 0; - } else if (usbc_hcint.s.xfercompl) { - /* - * Successful IN/OUT with transfer complete. - * Channel halt isn't needed. - */ - } else { - dev_err(dev, "USB%d: Channel %d interrupt without halt\n", - usb->index, channel); - return 0; - } - } - } else { - /* - * There is are no interrupts that we need to process when the - * channel is still running - */ - if (!usbc_hcint.s.chhltd) - return 0; - } - - /* Disable the channel interrupts now that it is done */ - cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), 0); - usb->idle_hardware_channels |= (1 << channel); - - /* Make sure this channel is tied to a valid pipe */ - pipe = usb->pipe_for_channel[channel]; - prefetch(pipe); - if (!pipe) - return 0; - transaction = list_first_entry(&pipe->transactions, - typeof(*transaction), - node); - prefetch(transaction); - - /* - * Disconnect this pipe from the HW channel. Later the schedule - * function will figure out which pipe needs to go - */ - usb->pipe_for_channel[channel] = NULL; - pipe->flags &= ~CVMX_USB_PIPE_FLAGS_SCHEDULED; - - /* - * Read the channel config info so we can figure out how much data - * transferred - */ - usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCCHARX(channel, usb->index)); - usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HCTSIZX(channel, usb->index)); - - /* - * Calculating the number of bytes successfully transferred is dependent - * on the transfer direction - */ - packets_processed = transaction->pktcnt - usbc_hctsiz.s.pktcnt; - if (usbc_hcchar.s.epdir) { - /* - * IN transactions are easy. For every byte received the - * hardware decrements xfersize. All we need to do is subtract - * the current value of xfersize from its starting value and we - * know how many bytes were written to the buffer - */ - bytes_this_transfer = transaction->xfersize - - usbc_hctsiz.s.xfersize; - } else { - /* - * OUT transaction don't decrement xfersize. Instead pktcnt is - * decremented on every successful packet send. The hardware - * does this when it receives an ACK, or NYET. If it doesn't - * receive one of these responses pktcnt doesn't change - */ - bytes_this_transfer = packets_processed * usbc_hcchar.s.mps; - /* - * The last packet may not be a full transfer if we didn't have - * enough data - */ - if (bytes_this_transfer > transaction->xfersize) - bytes_this_transfer = transaction->xfersize; - } - /* Figure out how many bytes were in the last packet of the transfer */ - if (packets_processed) - bytes_in_last_packet = bytes_this_transfer - - (packets_processed - 1) * usbc_hcchar.s.mps; - else - bytes_in_last_packet = bytes_this_transfer; - - /* - * As a special case, setup transactions output the setup header, not - * the user's data. For this reason we don't count setup data as bytes - * transferred - */ - if ((transaction->stage == CVMX_USB_STAGE_SETUP) || - (transaction->stage == CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE)) - bytes_this_transfer = 0; - - /* - * Add the bytes transferred to the running total. It is important that - * bytes_this_transfer doesn't count any data that needs to be - * retransmitted - */ - transaction->actual_bytes += bytes_this_transfer; - if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) - buffer_space_left = transaction->iso_packets[0].length - - transaction->actual_bytes; - else - buffer_space_left = transaction->buffer_length - - transaction->actual_bytes; - - /* - * We need to remember the PID toggle state for the next transaction. - * The hardware already updated it for the next transaction - */ - pipe->pid_toggle = !(usbc_hctsiz.s.pid == 0); - - /* - * For high speed bulk out, assume the next transaction will need to do - * a ping before proceeding. If this isn't true the ACK processing below - * will clear this flag - */ - if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && - (pipe->transfer_type == CVMX_USB_TRANSFER_BULK) && - (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT)) - pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; - - if (WARN_ON_ONCE(bytes_this_transfer < 0)) { - /* - * In some rare cases the DMA engine seems to get stuck and - * keeps substracting same byte count over and over again. In - * such case we just need to fail every transaction. - */ - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_ERROR); - return 0; - } - - if (usbc_hcint.s.stall) { - /* - * STALL as a response means this transaction cannot be - * completed because the device can't process transactions. Tell - * the user. Any data that was transferred will be counted on - * the actual bytes transferred - */ - pipe->pid_toggle = 0; - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_STALL); - } else if (usbc_hcint.s.xacterr) { - /* - * XactErr as a response means the device signaled - * something wrong with the transfer. For example, PID - * toggle errors cause these. - */ - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_XACTERR); - } else if (usbc_hcint.s.bblerr) { - /* Babble Error (BblErr) */ - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_BABBLEERR); - } else if (usbc_hcint.s.datatglerr) { - /* Data toggle error */ - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_DATATGLERR); - } else if (usbc_hcint.s.nyet) { - /* - * NYET as a response is only allowed in three cases: as a - * response to a ping, as a response to a split transaction, and - * as a response to a bulk out. The ping case is handled by - * hardware, so we only have splits and bulk out - */ - if (!cvmx_usb_pipe_needs_split(usb, pipe)) { - transaction->retries = 0; - /* - * If there is more data to go then we need to try - * again. Otherwise this transaction is complete - */ - if ((buffer_space_left == 0) || - (bytes_in_last_packet < pipe->max_packet)) - cvmx_usb_complete(usb, pipe, - transaction, - CVMX_USB_STATUS_OK); - } else { - /* - * Split transactions retry the split complete 4 times - * then rewind to the start split and do the entire - * transactions again - */ - transaction->retries++; - if ((transaction->retries & 0x3) == 0) { - /* - * Rewind to the beginning of the transaction by - * anding off the split complete bit - */ - transaction->stage &= ~1; - pipe->split_sc_frame = -1; - } - } - } else if (usbc_hcint.s.ack) { - transaction->retries = 0; - /* - * The ACK bit can only be checked after the other error bits. - * This is because a multi packet transfer may succeed in a - * number of packets and then get a different response on the - * last packet. In this case both ACK and the last response bit - * will be set. If none of the other response bits is set, then - * the last packet must have been an ACK - * - * Since we got an ACK, we know we don't need to do a ping on - * this pipe - */ - pipe->flags &= ~CVMX_USB_PIPE_FLAGS_NEED_PING; - - switch (transaction->type) { - case CVMX_USB_TRANSFER_CONTROL: - cvmx_usb_transfer_control(usb, pipe, transaction, - usbc_hcchar, - buffer_space_left, - bytes_in_last_packet); - break; - case CVMX_USB_TRANSFER_BULK: - cvmx_usb_transfer_bulk(usb, pipe, transaction, - usbc_hcint, buffer_space_left, - bytes_in_last_packet); - break; - case CVMX_USB_TRANSFER_INTERRUPT: - cvmx_usb_transfer_intr(usb, pipe, transaction, - buffer_space_left, - bytes_in_last_packet); - break; - case CVMX_USB_TRANSFER_ISOCHRONOUS: - cvmx_usb_transfer_isoc(usb, pipe, transaction, - buffer_space_left, - bytes_in_last_packet, - bytes_this_transfer); - break; - } - } else if (usbc_hcint.s.nak) { - /* - * If this was a split then clear our split in progress marker. - */ - if (usb->active_split == transaction) - usb->active_split = NULL; - /* - * NAK as a response means the device couldn't accept the - * transaction, but it should be retried in the future. Rewind - * to the beginning of the transaction by anding off the split - * complete bit. Retry in the next interval - */ - transaction->retries = 0; - transaction->stage &= ~1; - pipe->next_tx_frame += pipe->interval; - if (pipe->next_tx_frame < usb->frame_number) - pipe->next_tx_frame = usb->frame_number + - pipe->interval - - (usb->frame_number - pipe->next_tx_frame) % - pipe->interval; - } else { - struct cvmx_usb_port_status port; - - port = cvmx_usb_get_status(usb); - if (port.port_enabled) { - /* We'll retry the exact same transaction again */ - transaction->retries++; - } else { - /* - * We get channel halted interrupts with no result bits - * sets when the cable is unplugged - */ - cvmx_usb_complete(usb, pipe, transaction, - CVMX_USB_STATUS_ERROR); - } - } - return 0; -} - -static void octeon_usb_port_callback(struct octeon_hcd *usb) -{ - spin_unlock(&usb->lock); - usb_hcd_poll_rh_status(octeon_to_hcd(usb)); - spin_lock(&usb->lock); -} - -/** - * Poll the USB block for status and call all needed callback - * handlers. This function is meant to be called in the interrupt - * handler for the USB controller. It can also be called - * periodically in a loop for non-interrupt based operation. - * - * @usb: USB device state populated by cvmx_usb_initialize(). - * - * Returns: 0 or a negative error code. - */ -static int cvmx_usb_poll(struct octeon_hcd *usb) -{ - union cvmx_usbcx_hfnum usbc_hfnum; - union cvmx_usbcx_gintsts usbc_gintsts; - - prefetch_range(usb, sizeof(*usb)); - - /* Update the frame counter */ - usbc_hfnum.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); - if ((usb->frame_number & 0x3fff) > usbc_hfnum.s.frnum) - usb->frame_number += 0x4000; - usb->frame_number &= ~0x3fffull; - usb->frame_number |= usbc_hfnum.s.frnum; - - /* Read the pending interrupts */ - usbc_gintsts.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_GINTSTS(usb->index)); - - /* Clear the interrupts now that we know about them */ - cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), - usbc_gintsts.u32); - - if (usbc_gintsts.s.rxflvl) { - /* - * RxFIFO Non-Empty (RxFLvl) - * Indicates that there is at least one packet pending to be - * read from the RxFIFO. - * - * In DMA mode this is handled by hardware - */ - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) - cvmx_usb_poll_rx_fifo(usb); - } - if (usbc_gintsts.s.ptxfemp || usbc_gintsts.s.nptxfemp) { - /* Fill the Tx FIFOs when not in DMA mode */ - if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) - cvmx_usb_poll_tx_fifo(usb); - } - if (usbc_gintsts.s.disconnint || usbc_gintsts.s.prtint) { - union cvmx_usbcx_hprt usbc_hprt; - /* - * Disconnect Detected Interrupt (DisconnInt) - * Asserted when a device disconnect is detected. - * - * Host Port Interrupt (PrtInt) - * The core sets this bit to indicate a change in port status of - * one of the O2P USB core ports in Host mode. The application - * must read the Host Port Control and Status (HPRT) register to - * determine the exact event that caused this interrupt. The - * application must clear the appropriate status bit in the Host - * Port Control and Status register to clear this bit. - * - * Call the user's port callback - */ - octeon_usb_port_callback(usb); - /* Clear the port change bits */ - usbc_hprt.u32 = - cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); - usbc_hprt.s.prtena = 0; - cvmx_usb_write_csr32(usb, CVMX_USBCX_HPRT(usb->index), - usbc_hprt.u32); - } - if (usbc_gintsts.s.hchint) { - /* - * Host Channels Interrupt (HChInt) - * The core sets this bit to indicate that an interrupt is - * pending on one of the channels of the core (in Host mode). - * The application must read the Host All Channels Interrupt - * (HAINT) register to determine the exact number of the channel - * on which the interrupt occurred, and then read the - * corresponding Host Channel-n Interrupt (HCINTn) register to - * determine the exact cause of the interrupt. The application - * must clear the appropriate status bit in the HCINTn register - * to clear this bit. - */ - union cvmx_usbcx_haint usbc_haint; - - usbc_haint.u32 = cvmx_usb_read_csr32(usb, - CVMX_USBCX_HAINT(usb->index)); - while (usbc_haint.u32) { - int channel; - - channel = __fls(usbc_haint.u32); - cvmx_usb_poll_channel(usb, channel); - usbc_haint.u32 ^= 1 << channel; - } - } - - cvmx_usb_schedule(usb, usbc_gintsts.s.sof); - - return 0; -} - -/* convert between an HCD pointer and the corresponding struct octeon_hcd */ -static inline struct octeon_hcd *hcd_to_octeon(struct usb_hcd *hcd) -{ - return (struct octeon_hcd *)(hcd->hcd_priv); -} - -static irqreturn_t octeon_usb_irq(struct usb_hcd *hcd) -{ - struct octeon_hcd *usb = hcd_to_octeon(hcd); - unsigned long flags; - - spin_lock_irqsave(&usb->lock, flags); - cvmx_usb_poll(usb); - spin_unlock_irqrestore(&usb->lock, flags); - return IRQ_HANDLED; -} - -static int octeon_usb_start(struct usb_hcd *hcd) -{ - hcd->state = HC_STATE_RUNNING; - return 0; -} - -static void octeon_usb_stop(struct usb_hcd *hcd) -{ - hcd->state = HC_STATE_HALT; -} - -static int octeon_usb_get_frame_number(struct usb_hcd *hcd) -{ - struct octeon_hcd *usb = hcd_to_octeon(hcd); - - return cvmx_usb_get_frame_number(usb); -} - -static int octeon_usb_urb_enqueue(struct usb_hcd *hcd, - struct urb *urb, - gfp_t mem_flags) -{ - struct octeon_hcd *usb = hcd_to_octeon(hcd); - struct device *dev = hcd->self.controller; - struct cvmx_usb_transaction *transaction = NULL; - struct cvmx_usb_pipe *pipe; - unsigned long flags; - struct cvmx_usb_iso_packet *iso_packet; - struct usb_host_endpoint *ep = urb->ep; - int rc; - - urb->status = 0; - spin_lock_irqsave(&usb->lock, flags); - - rc = usb_hcd_link_urb_to_ep(hcd, urb); - if (rc) { - spin_unlock_irqrestore(&usb->lock, flags); - return rc; - } - - if (!ep->hcpriv) { - enum cvmx_usb_transfer transfer_type; - enum cvmx_usb_speed speed; - int split_device = 0; - int split_port = 0; - - switch (usb_pipetype(urb->pipe)) { - case PIPE_ISOCHRONOUS: - transfer_type = CVMX_USB_TRANSFER_ISOCHRONOUS; - break; - case PIPE_INTERRUPT: - transfer_type = CVMX_USB_TRANSFER_INTERRUPT; - break; - case PIPE_CONTROL: - transfer_type = CVMX_USB_TRANSFER_CONTROL; - break; - default: - transfer_type = CVMX_USB_TRANSFER_BULK; - break; - } - switch (urb->dev->speed) { - case USB_SPEED_LOW: - speed = CVMX_USB_SPEED_LOW; - break; - case USB_SPEED_FULL: - speed = CVMX_USB_SPEED_FULL; - break; - default: - speed = CVMX_USB_SPEED_HIGH; - break; - } - /* - * For slow devices on high speed ports we need to find the hub - * that does the speed translation so we know where to send the - * split transactions. - */ - if (speed != CVMX_USB_SPEED_HIGH) { - /* - * Start at this device and work our way up the usb - * tree. - */ - struct usb_device *dev = urb->dev; - - while (dev->parent) { - /* - * If our parent is high speed then he'll - * receive the splits. - */ - if (dev->parent->speed == USB_SPEED_HIGH) { - split_device = dev->parent->devnum; - split_port = dev->portnum; - break; - } - /* - * Move up the tree one level. If we make it all - * the way up the tree, then the port must not - * be in high speed mode and we don't need a - * split. - */ - dev = dev->parent; - } - } - pipe = cvmx_usb_open_pipe(usb, usb_pipedevice(urb->pipe), - usb_pipeendpoint(urb->pipe), speed, - le16_to_cpu(ep->desc.wMaxPacketSize) - & 0x7ff, - transfer_type, - usb_pipein(urb->pipe) ? - CVMX_USB_DIRECTION_IN : - CVMX_USB_DIRECTION_OUT, - urb->interval, - (le16_to_cpu(ep->desc.wMaxPacketSize) - >> 11) & 0x3, - split_device, split_port); - if (!pipe) { - usb_hcd_unlink_urb_from_ep(hcd, urb); - spin_unlock_irqrestore(&usb->lock, flags); - dev_dbg(dev, "Failed to create pipe\n"); - return -ENOMEM; - } - ep->hcpriv = pipe; - } else { - pipe = ep->hcpriv; - } - - switch (usb_pipetype(urb->pipe)) { - case PIPE_ISOCHRONOUS: - dev_dbg(dev, "Submit isochronous to %d.%d\n", - usb_pipedevice(urb->pipe), - usb_pipeendpoint(urb->pipe)); - /* - * Allocate a structure to use for our private list of - * isochronous packets. - */ - iso_packet = kmalloc_array(urb->number_of_packets, - sizeof(struct cvmx_usb_iso_packet), - GFP_ATOMIC); - if (iso_packet) { - int i; - /* Fill the list with the data from the URB */ - for (i = 0; i < urb->number_of_packets; i++) { - iso_packet[i].offset = - urb->iso_frame_desc[i].offset; - iso_packet[i].length = - urb->iso_frame_desc[i].length; - iso_packet[i].status = CVMX_USB_STATUS_ERROR; - } - /* - * Store a pointer to the list in the URB setup_packet - * field. We know this currently isn't being used and - * this saves us a bunch of logic. - */ - urb->setup_packet = (char *)iso_packet; - transaction = cvmx_usb_submit_isochronous(usb, - pipe, urb); - /* - * If submit failed we need to free our private packet - * list. - */ - if (!transaction) { - urb->setup_packet = NULL; - kfree(iso_packet); - } - } - break; - case PIPE_INTERRUPT: - dev_dbg(dev, "Submit interrupt to %d.%d\n", - usb_pipedevice(urb->pipe), - usb_pipeendpoint(urb->pipe)); - transaction = cvmx_usb_submit_interrupt(usb, pipe, urb); - break; - case PIPE_CONTROL: - dev_dbg(dev, "Submit control to %d.%d\n", - usb_pipedevice(urb->pipe), - usb_pipeendpoint(urb->pipe)); - transaction = cvmx_usb_submit_control(usb, pipe, urb); - break; - case PIPE_BULK: - dev_dbg(dev, "Submit bulk to %d.%d\n", - usb_pipedevice(urb->pipe), - usb_pipeendpoint(urb->pipe)); - transaction = cvmx_usb_submit_bulk(usb, pipe, urb); - break; - } - if (!transaction) { - usb_hcd_unlink_urb_from_ep(hcd, urb); - spin_unlock_irqrestore(&usb->lock, flags); - dev_dbg(dev, "Failed to submit\n"); - return -ENOMEM; - } - urb->hcpriv = transaction; - spin_unlock_irqrestore(&usb->lock, flags); - return 0; -} - -static int octeon_usb_urb_dequeue(struct usb_hcd *hcd, - struct urb *urb, - int status) -{ - struct octeon_hcd *usb = hcd_to_octeon(hcd); - unsigned long flags; - int rc; - - if (!urb->dev) - return -EINVAL; - - spin_lock_irqsave(&usb->lock, flags); - - rc = usb_hcd_check_unlink_urb(hcd, urb, status); - if (rc) - goto out; - - urb->status = status; - cvmx_usb_cancel(usb, urb->ep->hcpriv, urb->hcpriv); - -out: - spin_unlock_irqrestore(&usb->lock, flags); - - return rc; -} - -static void octeon_usb_endpoint_disable(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct device *dev = hcd->self.controller; - - if (ep->hcpriv) { - struct octeon_hcd *usb = hcd_to_octeon(hcd); - struct cvmx_usb_pipe *pipe = ep->hcpriv; - unsigned long flags; - - spin_lock_irqsave(&usb->lock, flags); - cvmx_usb_cancel_all(usb, pipe); - if (cvmx_usb_close_pipe(usb, pipe)) - dev_dbg(dev, "Closing pipe %p failed\n", pipe); - spin_unlock_irqrestore(&usb->lock, flags); - ep->hcpriv = NULL; - } -} - -static int octeon_usb_hub_status_data(struct usb_hcd *hcd, char *buf) -{ - struct octeon_hcd *usb = hcd_to_octeon(hcd); - struct cvmx_usb_port_status port_status; - unsigned long flags; - - spin_lock_irqsave(&usb->lock, flags); - port_status = cvmx_usb_get_status(usb); - spin_unlock_irqrestore(&usb->lock, flags); - buf[0] = port_status.connect_change << 1; - - return buf[0] != 0; -} - -static int octeon_usb_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, - u16 wIndex, char *buf, u16 wLength) -{ - struct octeon_hcd *usb = hcd_to_octeon(hcd); - struct device *dev = hcd->self.controller; - struct cvmx_usb_port_status usb_port_status; - int port_status; - struct usb_hub_descriptor *desc; - unsigned long flags; - - switch (typeReq) { - case ClearHubFeature: - dev_dbg(dev, "ClearHubFeature\n"); - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* Nothing required here */ - break; - default: - return -EINVAL; - } - break; - case ClearPortFeature: - dev_dbg(dev, "ClearPortFeature\n"); - if (wIndex != 1) { - dev_dbg(dev, " INVALID\n"); - return -EINVAL; - } - - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - dev_dbg(dev, " ENABLE\n"); - spin_lock_irqsave(&usb->lock, flags); - cvmx_usb_disable(usb); - spin_unlock_irqrestore(&usb->lock, flags); - break; - case USB_PORT_FEAT_SUSPEND: - dev_dbg(dev, " SUSPEND\n"); - /* Not supported on Octeon */ - break; - case USB_PORT_FEAT_POWER: - dev_dbg(dev, " POWER\n"); - /* Not supported on Octeon */ - break; - case USB_PORT_FEAT_INDICATOR: - dev_dbg(dev, " INDICATOR\n"); - /* Port inidicator not supported */ - break; - case USB_PORT_FEAT_C_CONNECTION: - dev_dbg(dev, " C_CONNECTION\n"); - /* Clears drivers internal connect status change flag */ - spin_lock_irqsave(&usb->lock, flags); - usb->port_status = cvmx_usb_get_status(usb); - spin_unlock_irqrestore(&usb->lock, flags); - break; - case USB_PORT_FEAT_C_RESET: - dev_dbg(dev, " C_RESET\n"); - /* - * Clears the driver's internal Port Reset Change flag. - */ - spin_lock_irqsave(&usb->lock, flags); - usb->port_status = cvmx_usb_get_status(usb); - spin_unlock_irqrestore(&usb->lock, flags); - break; - case USB_PORT_FEAT_C_ENABLE: - dev_dbg(dev, " C_ENABLE\n"); - /* - * Clears the driver's internal Port Enable/Disable - * Change flag. - */ - spin_lock_irqsave(&usb->lock, flags); - usb->port_status = cvmx_usb_get_status(usb); - spin_unlock_irqrestore(&usb->lock, flags); - break; - case USB_PORT_FEAT_C_SUSPEND: - dev_dbg(dev, " C_SUSPEND\n"); - /* - * Clears the driver's internal Port Suspend Change - * flag, which is set when resume signaling on the host - * port is complete. - */ - break; - case USB_PORT_FEAT_C_OVER_CURRENT: - dev_dbg(dev, " C_OVER_CURRENT\n"); - /* Clears the driver's overcurrent Change flag */ - spin_lock_irqsave(&usb->lock, flags); - usb->port_status = cvmx_usb_get_status(usb); - spin_unlock_irqrestore(&usb->lock, flags); - break; - default: - dev_dbg(dev, " UNKNOWN\n"); - return -EINVAL; - } - break; - case GetHubDescriptor: - dev_dbg(dev, "GetHubDescriptor\n"); - desc = (struct usb_hub_descriptor *)buf; - desc->bDescLength = 9; - desc->bDescriptorType = 0x29; - desc->bNbrPorts = 1; - desc->wHubCharacteristics = cpu_to_le16(0x08); - desc->bPwrOn2PwrGood = 1; - desc->bHubContrCurrent = 0; - desc->u.hs.DeviceRemovable[0] = 0; - desc->u.hs.DeviceRemovable[1] = 0xff; - break; - case GetHubStatus: - dev_dbg(dev, "GetHubStatus\n"); - *(__le32 *)buf = 0; - break; - case GetPortStatus: - dev_dbg(dev, "GetPortStatus\n"); - if (wIndex != 1) { - dev_dbg(dev, " INVALID\n"); - return -EINVAL; - } - - spin_lock_irqsave(&usb->lock, flags); - usb_port_status = cvmx_usb_get_status(usb); - spin_unlock_irqrestore(&usb->lock, flags); - port_status = 0; - - if (usb_port_status.connect_change) { - port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); - dev_dbg(dev, " C_CONNECTION\n"); - } - - if (usb_port_status.port_enabled) { - port_status |= (1 << USB_PORT_FEAT_C_ENABLE); - dev_dbg(dev, " C_ENABLE\n"); - } - - if (usb_port_status.connected) { - port_status |= (1 << USB_PORT_FEAT_CONNECTION); - dev_dbg(dev, " CONNECTION\n"); - } - - if (usb_port_status.port_enabled) { - port_status |= (1 << USB_PORT_FEAT_ENABLE); - dev_dbg(dev, " ENABLE\n"); - } - - if (usb_port_status.port_over_current) { - port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT); - dev_dbg(dev, " OVER_CURRENT\n"); - } - - if (usb_port_status.port_powered) { - port_status |= (1 << USB_PORT_FEAT_POWER); - dev_dbg(dev, " POWER\n"); - } - - if (usb_port_status.port_speed == CVMX_USB_SPEED_HIGH) { - port_status |= USB_PORT_STAT_HIGH_SPEED; - dev_dbg(dev, " HIGHSPEED\n"); - } else if (usb_port_status.port_speed == CVMX_USB_SPEED_LOW) { - port_status |= (1 << USB_PORT_FEAT_LOWSPEED); - dev_dbg(dev, " LOWSPEED\n"); - } - - *((__le32 *)buf) = cpu_to_le32(port_status); - break; - case SetHubFeature: - dev_dbg(dev, "SetHubFeature\n"); - /* No HUB features supported */ - break; - case SetPortFeature: - dev_dbg(dev, "SetPortFeature\n"); - if (wIndex != 1) { - dev_dbg(dev, " INVALID\n"); - return -EINVAL; - } - - switch (wValue) { - case USB_PORT_FEAT_SUSPEND: - dev_dbg(dev, " SUSPEND\n"); - return -EINVAL; - case USB_PORT_FEAT_POWER: - dev_dbg(dev, " POWER\n"); - /* - * Program the port power bit to drive VBUS on the USB. - */ - spin_lock_irqsave(&usb->lock, flags); - USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), - cvmx_usbcx_hprt, prtpwr, 1); - spin_unlock_irqrestore(&usb->lock, flags); - return 0; - case USB_PORT_FEAT_RESET: - dev_dbg(dev, " RESET\n"); - spin_lock_irqsave(&usb->lock, flags); - cvmx_usb_reset_port(usb); - spin_unlock_irqrestore(&usb->lock, flags); - return 0; - case USB_PORT_FEAT_INDICATOR: - dev_dbg(dev, " INDICATOR\n"); - /* Not supported */ - break; - default: - dev_dbg(dev, " UNKNOWN\n"); - return -EINVAL; - } - break; - default: - dev_dbg(dev, "Unknown root hub request\n"); - return -EINVAL; - } - return 0; -} - -static const struct hc_driver octeon_hc_driver = { - .description = "Octeon USB", - .product_desc = "Octeon Host Controller", - .hcd_priv_size = sizeof(struct octeon_hcd), - .irq = octeon_usb_irq, - .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, - .start = octeon_usb_start, - .stop = octeon_usb_stop, - .urb_enqueue = octeon_usb_urb_enqueue, - .urb_dequeue = octeon_usb_urb_dequeue, - .endpoint_disable = octeon_usb_endpoint_disable, - .get_frame_number = octeon_usb_get_frame_number, - .hub_status_data = octeon_usb_hub_status_data, - .hub_control = octeon_usb_hub_control, - .map_urb_for_dma = octeon_map_urb_for_dma, - .unmap_urb_for_dma = octeon_unmap_urb_for_dma, -}; - -static int octeon_usb_probe(struct platform_device *pdev) -{ - int status; - int initialize_flags; - int usb_num; - struct resource *res_mem; - struct device_node *usbn_node; - int irq = platform_get_irq(pdev, 0); - struct device *dev = &pdev->dev; - struct octeon_hcd *usb; - struct usb_hcd *hcd; - u32 clock_rate = 48000000; - bool is_crystal_clock = false; - const char *clock_type; - int i; - - if (!dev->of_node) { - dev_err(dev, "Error: empty of_node\n"); - return -ENXIO; - } - usbn_node = dev->of_node->parent; - - i = of_property_read_u32(usbn_node, - "clock-frequency", &clock_rate); - if (i) - i = of_property_read_u32(usbn_node, - "refclk-frequency", &clock_rate); - if (i) { - dev_err(dev, "No USBN \"clock-frequency\"\n"); - return -ENXIO; - } - switch (clock_rate) { - case 12000000: - initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ; - break; - case 24000000: - initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ; - break; - case 48000000: - initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ; - break; - default: - dev_err(dev, "Illegal USBN \"clock-frequency\" %u\n", - clock_rate); - return -ENXIO; - } - - i = of_property_read_string(usbn_node, - "cavium,refclk-type", &clock_type); - if (i) - i = of_property_read_string(usbn_node, - "refclk-type", &clock_type); - - if (!i && strcmp("crystal", clock_type) == 0) - is_crystal_clock = true; - - if (is_crystal_clock) - initialize_flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI; - else - initialize_flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND; - - res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res_mem) { - dev_err(dev, "found no memory resource\n"); - return -ENXIO; - } - usb_num = (res_mem->start >> 44) & 1; - - if (irq < 0) { - /* Defective device tree, but we know how to fix it. */ - irq_hw_number_t hwirq = usb_num ? (1 << 6) + 17 : 56; - - irq = irq_create_mapping(NULL, hwirq); - } - - /* - * Set the DMA mask to 64bits so we get buffers already translated for - * DMA. - */ - i = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); - if (i) - return i; - - /* - * Only cn52XX and cn56XX have DWC_OTG USB hardware and the - * IOB priority registers. Under heavy network load USB - * hardware can be starved by the IOB causing a crash. Give - * it a priority boost if it has been waiting more than 400 - * cycles to avoid this situation. - * - * Testing indicates that a cnt_val of 8192 is not sufficient, - * but no failures are seen with 4096. We choose a value of - * 400 to give a safety factor of 10. - */ - if (OCTEON_IS_MODEL(OCTEON_CN52XX) || OCTEON_IS_MODEL(OCTEON_CN56XX)) { - union cvmx_iob_n2c_l2c_pri_cnt pri_cnt; - - pri_cnt.u64 = 0; - pri_cnt.s.cnt_enb = 1; - pri_cnt.s.cnt_val = 400; - cvmx_write_csr(CVMX_IOB_N2C_L2C_PRI_CNT, pri_cnt.u64); - } - - hcd = usb_create_hcd(&octeon_hc_driver, dev, dev_name(dev)); - if (!hcd) { - dev_dbg(dev, "Failed to allocate memory for HCD\n"); - return -1; - } - hcd->uses_new_polling = 1; - usb = (struct octeon_hcd *)hcd->hcd_priv; - - spin_lock_init(&usb->lock); - - usb->init_flags = initialize_flags; - - /* Initialize the USB state structure */ - usb->index = usb_num; - INIT_LIST_HEAD(&usb->idle_pipes); - for (i = 0; i < ARRAY_SIZE(usb->active_pipes); i++) - INIT_LIST_HEAD(&usb->active_pipes[i]); - - /* Due to an errata, CN31XX doesn't support DMA */ - if (OCTEON_IS_MODEL(OCTEON_CN31XX)) { - usb->init_flags |= CVMX_USB_INITIALIZE_FLAGS_NO_DMA; - /* Only use one channel with non DMA */ - usb->idle_hardware_channels = 0x1; - } else if (OCTEON_IS_MODEL(OCTEON_CN5XXX)) { - /* CN5XXX have an errata with channel 3 */ - usb->idle_hardware_channels = 0xf7; - } else { - usb->idle_hardware_channels = 0xff; - } - - status = cvmx_usb_initialize(dev, usb); - if (status) { - dev_dbg(dev, "USB initialization failed with %d\n", status); - usb_put_hcd(hcd); - return -1; - } - - status = usb_add_hcd(hcd, irq, 0); - if (status) { - dev_dbg(dev, "USB add HCD failed with %d\n", status); - usb_put_hcd(hcd); - return -1; - } - device_wakeup_enable(hcd->self.controller); - - dev_info(dev, "Registered HCD for port %d on irq %d\n", usb_num, irq); - - return 0; -} - -static int octeon_usb_remove(struct platform_device *pdev) -{ - int status; - struct device *dev = &pdev->dev; - struct usb_hcd *hcd = dev_get_drvdata(dev); - struct octeon_hcd *usb = hcd_to_octeon(hcd); - unsigned long flags; - - usb_remove_hcd(hcd); - spin_lock_irqsave(&usb->lock, flags); - status = cvmx_usb_shutdown(usb); - spin_unlock_irqrestore(&usb->lock, flags); - if (status) - dev_dbg(dev, "USB shutdown failed with %d\n", status); - - usb_put_hcd(hcd); - - return 0; -} - -static const struct of_device_id octeon_usb_match[] = { - { - .compatible = "cavium,octeon-5750-usbc", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, octeon_usb_match); - -static struct platform_driver octeon_usb_driver = { - .driver = { - .name = "octeon-hcd", - .of_match_table = octeon_usb_match, - }, - .probe = octeon_usb_probe, - .remove = octeon_usb_remove, -}; - -static int __init octeon_usb_driver_init(void) -{ - if (usb_disabled()) - return 0; - - return platform_driver_register(&octeon_usb_driver); -} -module_init(octeon_usb_driver_init); - -static void __exit octeon_usb_driver_exit(void) -{ - if (usb_disabled()) - return; - - platform_driver_unregister(&octeon_usb_driver); -} -module_exit(octeon_usb_driver_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Cavium, Inc. "); -MODULE_DESCRIPTION("Cavium Inc. OCTEON USB Host driver."); diff --git a/drivers/staging/octeon-usb/octeon-hcd.h b/drivers/staging/octeon-usb/octeon-hcd.h deleted file mode 100644 index 9ed619c93a4e..000000000000 --- a/drivers/staging/octeon-usb/octeon-hcd.h +++ /dev/null @@ -1,1847 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Octeon HCD hardware register definitions. - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - * - * Some parts of the code were originally released under BSD license: - * - * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * * Neither the name of Cavium Networks nor the names of - * its contributors may be used to endorse or promote products - * derived from this software without specific prior written - * permission. - * - * This Software, including technical data, may be subject to U.S. export - * control laws, including the U.S. Export Administration Act and its associated - * regulations, and may be subject to export or import regulations in other - * countries. - * - * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" - * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR - * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO - * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION - * OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM - * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, - * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF - * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR - * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR - * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. - */ - -#ifndef __OCTEON_HCD_H__ -#define __OCTEON_HCD_H__ - -#include - -#define CVMX_USBCXBASE 0x00016F0010000000ull -#define CVMX_USBCXREG1(reg, bid) \ - (CVMX_ADD_IO_SEG(CVMX_USBCXBASE | reg) + \ - ((bid) & 1) * 0x100000000000ull) -#define CVMX_USBCXREG2(reg, bid, off) \ - (CVMX_ADD_IO_SEG(CVMX_USBCXBASE | reg) + \ - (((off) & 7) + ((bid) & 1) * 0x8000000000ull) * 32) - -#define CVMX_USBCX_GAHBCFG(bid) CVMX_USBCXREG1(0x008, bid) -#define CVMX_USBCX_GHWCFG3(bid) CVMX_USBCXREG1(0x04c, bid) -#define CVMX_USBCX_GINTMSK(bid) CVMX_USBCXREG1(0x018, bid) -#define CVMX_USBCX_GINTSTS(bid) CVMX_USBCXREG1(0x014, bid) -#define CVMX_USBCX_GNPTXFSIZ(bid) CVMX_USBCXREG1(0x028, bid) -#define CVMX_USBCX_GNPTXSTS(bid) CVMX_USBCXREG1(0x02c, bid) -#define CVMX_USBCX_GOTGCTL(bid) CVMX_USBCXREG1(0x000, bid) -#define CVMX_USBCX_GRSTCTL(bid) CVMX_USBCXREG1(0x010, bid) -#define CVMX_USBCX_GRXFSIZ(bid) CVMX_USBCXREG1(0x024, bid) -#define CVMX_USBCX_GRXSTSPH(bid) CVMX_USBCXREG1(0x020, bid) -#define CVMX_USBCX_GUSBCFG(bid) CVMX_USBCXREG1(0x00c, bid) -#define CVMX_USBCX_HAINT(bid) CVMX_USBCXREG1(0x414, bid) -#define CVMX_USBCX_HAINTMSK(bid) CVMX_USBCXREG1(0x418, bid) -#define CVMX_USBCX_HCCHARX(off, bid) CVMX_USBCXREG2(0x500, bid, off) -#define CVMX_USBCX_HCFG(bid) CVMX_USBCXREG1(0x400, bid) -#define CVMX_USBCX_HCINTMSKX(off, bid) CVMX_USBCXREG2(0x50c, bid, off) -#define CVMX_USBCX_HCINTX(off, bid) CVMX_USBCXREG2(0x508, bid, off) -#define CVMX_USBCX_HCSPLTX(off, bid) CVMX_USBCXREG2(0x504, bid, off) -#define CVMX_USBCX_HCTSIZX(off, bid) CVMX_USBCXREG2(0x510, bid, off) -#define CVMX_USBCX_HFIR(bid) CVMX_USBCXREG1(0x404, bid) -#define CVMX_USBCX_HFNUM(bid) CVMX_USBCXREG1(0x408, bid) -#define CVMX_USBCX_HPRT(bid) CVMX_USBCXREG1(0x440, bid) -#define CVMX_USBCX_HPTXFSIZ(bid) CVMX_USBCXREG1(0x100, bid) -#define CVMX_USBCX_HPTXSTS(bid) CVMX_USBCXREG1(0x410, bid) - -#define CVMX_USBNXBID1(bid) (((bid) & 1) * 0x10000000ull) -#define CVMX_USBNXBID2(bid) (((bid) & 1) * 0x100000000000ull) - -#define CVMX_USBNXREG1(reg, bid) \ - (CVMX_ADD_IO_SEG(0x0001180068000000ull | reg) + CVMX_USBNXBID1(bid)) -#define CVMX_USBNXREG2(reg, bid) \ - (CVMX_ADD_IO_SEG(0x00016F0000000000ull | reg) + CVMX_USBNXBID2(bid)) - -#define CVMX_USBNX_CLK_CTL(bid) CVMX_USBNXREG1(0x10, bid) -#define CVMX_USBNX_DMA0_INB_CHN0(bid) CVMX_USBNXREG2(0x818, bid) -#define CVMX_USBNX_DMA0_OUTB_CHN0(bid) CVMX_USBNXREG2(0x858, bid) -#define CVMX_USBNX_USBP_CTL_STATUS(bid) CVMX_USBNXREG1(0x18, bid) - -/** - * cvmx_usbc#_gahbcfg - * - * Core AHB Configuration Register (GAHBCFG) - * - * This register can be used to configure the core after power-on or a change in - * mode of operation. This register mainly contains AHB system-related - * configuration parameters. The AHB is the processor interface to the O2P USB - * core. In general, software need not know about this interface except to - * program the values as specified. - * - * The application must program this register as part of the O2P USB core - * initialization. Do not change this register after the initial programming. - */ -union cvmx_usbcx_gahbcfg { - u32 u32; - /** - * struct cvmx_usbcx_gahbcfg_s - * @ptxfemplvl: Periodic TxFIFO Empty Level (PTxFEmpLvl) - * Software should set this bit to 0x1. - * Indicates when the Periodic TxFIFO Empty Interrupt bit in the - * Core Interrupt register (GINTSTS.PTxFEmp) is triggered. This - * bit is used only in Slave mode. - * * 1'b0: GINTSTS.PTxFEmp interrupt indicates that the Periodic - * TxFIFO is half empty - * * 1'b1: GINTSTS.PTxFEmp interrupt indicates that the Periodic - * TxFIFO is completely empty - * @nptxfemplvl: Non-Periodic TxFIFO Empty Level (NPTxFEmpLvl) - * Software should set this bit to 0x1. - * Indicates when the Non-Periodic TxFIFO Empty Interrupt bit in - * the Core Interrupt register (GINTSTS.NPTxFEmp) is triggered. - * This bit is used only in Slave mode. - * * 1'b0: GINTSTS.NPTxFEmp interrupt indicates that the Non- - * Periodic TxFIFO is half empty - * * 1'b1: GINTSTS.NPTxFEmp interrupt indicates that the Non- - * Periodic TxFIFO is completely empty - * @dmaen: DMA Enable (DMAEn) - * * 1'b0: Core operates in Slave mode - * * 1'b1: Core operates in a DMA mode - * @hbstlen: Burst Length/Type (HBstLen) - * This field has not effect and should be left as 0x0. - * @glblintrmsk: Global Interrupt Mask (GlblIntrMsk) - * Software should set this field to 0x1. - * The application uses this bit to mask or unmask the interrupt - * line assertion to itself. Irrespective of this bit's setting, - * the interrupt status registers are updated by the core. - * * 1'b0: Mask the interrupt assertion to the application. - * * 1'b1: Unmask the interrupt assertion to the application. - */ - struct cvmx_usbcx_gahbcfg_s { - __BITFIELD_FIELD(u32 reserved_9_31 : 23, - __BITFIELD_FIELD(u32 ptxfemplvl : 1, - __BITFIELD_FIELD(u32 nptxfemplvl : 1, - __BITFIELD_FIELD(u32 reserved_6_6 : 1, - __BITFIELD_FIELD(u32 dmaen : 1, - __BITFIELD_FIELD(u32 hbstlen : 4, - __BITFIELD_FIELD(u32 glblintrmsk : 1, - ;))))))) - } s; -}; - -/** - * cvmx_usbc#_ghwcfg3 - * - * User HW Config3 Register (GHWCFG3) - * - * This register contains the configuration options of the O2P USB core. - */ -union cvmx_usbcx_ghwcfg3 { - u32 u32; - /** - * struct cvmx_usbcx_ghwcfg3_s - * @dfifodepth: DFIFO Depth (DfifoDepth) - * This value is in terms of 32-bit words. - * * Minimum value is 32 - * * Maximum value is 32768 - * @ahbphysync: AHB and PHY Synchronous (AhbPhySync) - * Indicates whether AHB and PHY clocks are synchronous to - * each other. - * * 1'b0: No - * * 1'b1: Yes - * This bit is tied to 1. - * @rsttype: Reset Style for Clocked always Blocks in RTL (RstType) - * * 1'b0: Asynchronous reset is used in the core - * * 1'b1: Synchronous reset is used in the core - * @optfeature: Optional Features Removed (OptFeature) - * Indicates whether the User ID register, GPIO interface ports, - * and SOF toggle and counter ports were removed for gate count - * optimization. - * @vendor_control_interface_support: Vendor Control Interface Support - * * 1'b0: Vendor Control Interface is not available on the core. - * * 1'b1: Vendor Control Interface is available. - * @i2c_selection: I2C Selection - * * 1'b0: I2C Interface is not available on the core. - * * 1'b1: I2C Interface is available on the core. - * @otgen: OTG Function Enabled (OtgEn) - * The application uses this bit to indicate the O2P USB core's - * OTG capabilities. - * * 1'b0: Not OTG capable - * * 1'b1: OTG Capable - * @pktsizewidth: Width of Packet Size Counters (PktSizeWidth) - * * 3'b000: 4 bits - * * 3'b001: 5 bits - * * 3'b010: 6 bits - * * 3'b011: 7 bits - * * 3'b100: 8 bits - * * 3'b101: 9 bits - * * 3'b110: 10 bits - * * Others: Reserved - * @xfersizewidth: Width of Transfer Size Counters (XferSizeWidth) - * * 4'b0000: 11 bits - * * 4'b0001: 12 bits - * - ... - * * 4'b1000: 19 bits - * * Others: Reserved - */ - struct cvmx_usbcx_ghwcfg3_s { - __BITFIELD_FIELD(u32 dfifodepth : 16, - __BITFIELD_FIELD(u32 reserved_13_15 : 3, - __BITFIELD_FIELD(u32 ahbphysync : 1, - __BITFIELD_FIELD(u32 rsttype : 1, - __BITFIELD_FIELD(u32 optfeature : 1, - __BITFIELD_FIELD(u32 vendor_control_interface_support : 1, - __BITFIELD_FIELD(u32 i2c_selection : 1, - __BITFIELD_FIELD(u32 otgen : 1, - __BITFIELD_FIELD(u32 pktsizewidth : 3, - __BITFIELD_FIELD(u32 xfersizewidth : 4, - ;)))))))))) - } s; -}; - -/** - * cvmx_usbc#_gintmsk - * - * Core Interrupt Mask Register (GINTMSK) - * - * This register works with the Core Interrupt register to interrupt the - * application. When an interrupt bit is masked, the interrupt associated with - * that bit will not be generated. However, the Core Interrupt (GINTSTS) - * register bit corresponding to that interrupt will still be set. - * Mask interrupt: 1'b0, Unmask interrupt: 1'b1 - */ -union cvmx_usbcx_gintmsk { - u32 u32; - /** - * struct cvmx_usbcx_gintmsk_s - * @wkupintmsk: Resume/Remote Wakeup Detected Interrupt Mask - * (WkUpIntMsk) - * @sessreqintmsk: Session Request/New Session Detected Interrupt Mask - * (SessReqIntMsk) - * @disconnintmsk: Disconnect Detected Interrupt Mask (DisconnIntMsk) - * @conidstschngmsk: Connector ID Status Change Mask (ConIDStsChngMsk) - * @ptxfempmsk: Periodic TxFIFO Empty Mask (PTxFEmpMsk) - * @hchintmsk: Host Channels Interrupt Mask (HChIntMsk) - * @prtintmsk: Host Port Interrupt Mask (PrtIntMsk) - * @fetsuspmsk: Data Fetch Suspended Mask (FetSuspMsk) - * @incomplpmsk: Incomplete Periodic Transfer Mask (incomplPMsk) - * Incomplete Isochronous OUT Transfer Mask - * (incompISOOUTMsk) - * @incompisoinmsk: Incomplete Isochronous IN Transfer Mask - * (incompISOINMsk) - * @oepintmsk: OUT Endpoints Interrupt Mask (OEPIntMsk) - * @inepintmsk: IN Endpoints Interrupt Mask (INEPIntMsk) - * @epmismsk: Endpoint Mismatch Interrupt Mask (EPMisMsk) - * @eopfmsk: End of Periodic Frame Interrupt Mask (EOPFMsk) - * @isooutdropmsk: Isochronous OUT Packet Dropped Interrupt Mask - * (ISOOutDropMsk) - * @enumdonemsk: Enumeration Done Mask (EnumDoneMsk) - * @usbrstmsk: USB Reset Mask (USBRstMsk) - * @usbsuspmsk: USB Suspend Mask (USBSuspMsk) - * @erlysuspmsk: Early Suspend Mask (ErlySuspMsk) - * @i2cint: I2C Interrupt Mask (I2CINT) - * @ulpickintmsk: ULPI Carkit Interrupt Mask (ULPICKINTMsk) - * I2C Carkit Interrupt Mask (I2CCKINTMsk) - * @goutnakeffmsk: Global OUT NAK Effective Mask (GOUTNakEffMsk) - * @ginnakeffmsk: Global Non-Periodic IN NAK Effective Mask - * (GINNakEffMsk) - * @nptxfempmsk: Non-Periodic TxFIFO Empty Mask (NPTxFEmpMsk) - * @rxflvlmsk: Receive FIFO Non-Empty Mask (RxFLvlMsk) - * @sofmsk: Start of (micro)Frame Mask (SofMsk) - * @otgintmsk: OTG Interrupt Mask (OTGIntMsk) - * @modemismsk: Mode Mismatch Interrupt Mask (ModeMisMsk) - */ - struct cvmx_usbcx_gintmsk_s { - __BITFIELD_FIELD(u32 wkupintmsk : 1, - __BITFIELD_FIELD(u32 sessreqintmsk : 1, - __BITFIELD_FIELD(u32 disconnintmsk : 1, - __BITFIELD_FIELD(u32 conidstschngmsk : 1, - __BITFIELD_FIELD(u32 reserved_27_27 : 1, - __BITFIELD_FIELD(u32 ptxfempmsk : 1, - __BITFIELD_FIELD(u32 hchintmsk : 1, - __BITFIELD_FIELD(u32 prtintmsk : 1, - __BITFIELD_FIELD(u32 reserved_23_23 : 1, - __BITFIELD_FIELD(u32 fetsuspmsk : 1, - __BITFIELD_FIELD(u32 incomplpmsk : 1, - __BITFIELD_FIELD(u32 incompisoinmsk : 1, - __BITFIELD_FIELD(u32 oepintmsk : 1, - __BITFIELD_FIELD(u32 inepintmsk : 1, - __BITFIELD_FIELD(u32 epmismsk : 1, - __BITFIELD_FIELD(u32 reserved_16_16 : 1, - __BITFIELD_FIELD(u32 eopfmsk : 1, - __BITFIELD_FIELD(u32 isooutdropmsk : 1, - __BITFIELD_FIELD(u32 enumdonemsk : 1, - __BITFIELD_FIELD(u32 usbrstmsk : 1, - __BITFIELD_FIELD(u32 usbsuspmsk : 1, - __BITFIELD_FIELD(u32 erlysuspmsk : 1, - __BITFIELD_FIELD(u32 i2cint : 1, - __BITFIELD_FIELD(u32 ulpickintmsk : 1, - __BITFIELD_FIELD(u32 goutnakeffmsk : 1, - __BITFIELD_FIELD(u32 ginnakeffmsk : 1, - __BITFIELD_FIELD(u32 nptxfempmsk : 1, - __BITFIELD_FIELD(u32 rxflvlmsk : 1, - __BITFIELD_FIELD(u32 sofmsk : 1, - __BITFIELD_FIELD(u32 otgintmsk : 1, - __BITFIELD_FIELD(u32 modemismsk : 1, - __BITFIELD_FIELD(u32 reserved_0_0 : 1, - ;)))))))))))))))))))))))))))))))) - } s; -}; - -/** - * cvmx_usbc#_gintsts - * - * Core Interrupt Register (GINTSTS) - * - * This register interrupts the application for system-level events in the - * current mode of operation (Device mode or Host mode). It is shown in - * Interrupt. Some of the bits in this register are valid only in Host mode, - * while others are valid in Device mode only. This register also indicates the - * current mode of operation. In order to clear the interrupt status bits of - * type R_SS_WC, the application must write 1'b1 into the bit. The FIFO status - * interrupts are read only; once software reads from or writes to the FIFO - * while servicing these interrupts, FIFO interrupt conditions are cleared - * automatically. - */ -union cvmx_usbcx_gintsts { - u32 u32; - /** - * struct cvmx_usbcx_gintsts_s - * @wkupint: Resume/Remote Wakeup Detected Interrupt (WkUpInt) - * In Device mode, this interrupt is asserted when a resume is - * detected on the USB. In Host mode, this interrupt is asserted - * when a remote wakeup is detected on the USB. - * For more information on how to use this interrupt, see "Partial - * Power-Down and Clock Gating Programming Model" on - * page 353. - * @sessreqint: Session Request/New Session Detected Interrupt - * (SessReqInt) - * In Host mode, this interrupt is asserted when a session request - * is detected from the device. In Device mode, this interrupt is - * asserted when the utmiotg_bvalid signal goes high. - * For more information on how to use this interrupt, see "Partial - * Power-Down and Clock Gating Programming Model" on - * page 353. - * @disconnint: Disconnect Detected Interrupt (DisconnInt) - * Asserted when a device disconnect is detected. - * @conidstschng: Connector ID Status Change (ConIDStsChng) - * The core sets this bit when there is a change in connector ID - * status. - * @ptxfemp: Periodic TxFIFO Empty (PTxFEmp) - * Asserted when the Periodic Transmit FIFO is either half or - * completely empty and there is space for at least one entry to be - * written in the Periodic Request Queue. The half or completely - * empty status is determined by the Periodic TxFIFO Empty Level - * bit in the Core AHB Configuration register - * (GAHBCFG.PTxFEmpLvl). - * @hchint: Host Channels Interrupt (HChInt) - * The core sets this bit to indicate that an interrupt is pending - * on one of the channels of the core (in Host mode). The - * application must read the Host All Channels Interrupt (HAINT) - * register to determine the exact number of the channel on which - * the interrupt occurred, and then read the corresponding Host - * Channel-n Interrupt (HCINTn) register to determine the exact - * cause of the interrupt. The application must clear the - * appropriate status bit in the HCINTn register to clear this bit. - * @prtint: Host Port Interrupt (PrtInt) - * The core sets this bit to indicate a change in port status of - * one of the O2P USB core ports in Host mode. The application must - * read the Host Port Control and Status (HPRT) register to - * determine the exact event that caused this interrupt. The - * application must clear the appropriate status bit in the Host - * Port Control and Status register to clear this bit. - * @fetsusp: Data Fetch Suspended (FetSusp) - * This interrupt is valid only in DMA mode. This interrupt - * indicates that the core has stopped fetching data for IN - * endpoints due to the unavailability of TxFIFO space or Request - * Queue space. This interrupt is used by the application for an - * endpoint mismatch algorithm. - * @incomplp: Incomplete Periodic Transfer (incomplP) - * In Host mode, the core sets this interrupt bit when there are - * incomplete periodic transactions still pending which are - * scheduled for the current microframe. - * Incomplete Isochronous OUT Transfer (incompISOOUT) - * The Device mode, the core sets this interrupt to indicate that - * there is at least one isochronous OUT endpoint on which the - * transfer is not completed in the current microframe. This - * interrupt is asserted along with the End of Periodic Frame - * Interrupt (EOPF) bit in this register. - * @incompisoin: Incomplete Isochronous IN Transfer (incompISOIN) - * The core sets this interrupt to indicate that there is at least - * one isochronous IN endpoint on which the transfer is not - * completed in the current microframe. This interrupt is asserted - * along with the End of Periodic Frame Interrupt (EOPF) bit in - * this register. - * @oepint: OUT Endpoints Interrupt (OEPInt) - * The core sets this bit to indicate that an interrupt is pending - * on one of the OUT endpoints of the core (in Device mode). The - * application must read the Device All Endpoints Interrupt - * (DAINT) register to determine the exact number of the OUT - * endpoint on which the interrupt occurred, and then read the - * corresponding Device OUT Endpoint-n Interrupt (DOEPINTn) - * register to determine the exact cause of the interrupt. The - * application must clear the appropriate status bit in the - * corresponding DOEPINTn register to clear this bit. - * @iepint: IN Endpoints Interrupt (IEPInt) - * The core sets this bit to indicate that an interrupt is pending - * on one of the IN endpoints of the core (in Device mode). The - * application must read the Device All Endpoints Interrupt - * (DAINT) register to determine the exact number of the IN - * endpoint on which the interrupt occurred, and then read the - * corresponding Device IN Endpoint-n Interrupt (DIEPINTn) - * register to determine the exact cause of the interrupt. The - * application must clear the appropriate status bit in the - * corresponding DIEPINTn register to clear this bit. - * @epmis: Endpoint Mismatch Interrupt (EPMis) - * Indicates that an IN token has been received for a non-periodic - * endpoint, but the data for another endpoint is present in the - * top of the Non-Periodic Transmit FIFO and the IN endpoint - * mismatch count programmed by the application has expired. - * @eopf: End of Periodic Frame Interrupt (EOPF) - * Indicates that the period specified in the Periodic Frame - * Interval field of the Device Configuration register - * (DCFG.PerFrInt) has been reached in the current microframe. - * @isooutdrop: Isochronous OUT Packet Dropped Interrupt (ISOOutDrop) - * The core sets this bit when it fails to write an isochronous OUT - * packet into the RxFIFO because the RxFIFO doesn't have - * enough space to accommodate a maximum packet size packet - * for the isochronous OUT endpoint. - * @enumdone: Enumeration Done (EnumDone) - * The core sets this bit to indicate that speed enumeration is - * complete. The application must read the Device Status (DSTS) - * register to obtain the enumerated speed. - * @usbrst: USB Reset (USBRst) - * The core sets this bit to indicate that a reset is detected on - * the USB. - * @usbsusp: USB Suspend (USBSusp) - * The core sets this bit to indicate that a suspend was detected - * on the USB. The core enters the Suspended state when there - * is no activity on the phy_line_state_i signal for an extended - * period of time. - * @erlysusp: Early Suspend (ErlySusp) - * The core sets this bit to indicate that an Idle state has been - * detected on the USB for 3 ms. - * @i2cint: I2C Interrupt (I2CINT) - * This bit is always 0x0. - * @ulpickint: ULPI Carkit Interrupt (ULPICKINT) - * This bit is always 0x0. - * @goutnakeff: Global OUT NAK Effective (GOUTNakEff) - * Indicates that the Set Global OUT NAK bit in the Device Control - * register (DCTL.SGOUTNak), set by the application, has taken - * effect in the core. This bit can be cleared by writing the Clear - * Global OUT NAK bit in the Device Control register - * (DCTL.CGOUTNak). - * @ginnakeff: Global IN Non-Periodic NAK Effective (GINNakEff) - * Indicates that the Set Global Non-Periodic IN NAK bit in the - * Device Control register (DCTL.SGNPInNak), set by the - * application, has taken effect in the core. That is, the core has - * sampled the Global IN NAK bit set by the application. This bit - * can be cleared by clearing the Clear Global Non-Periodic IN - * NAK bit in the Device Control register (DCTL.CGNPInNak). - * This interrupt does not necessarily mean that a NAK handshake - * is sent out on the USB. The STALL bit takes precedence over - * the NAK bit. - * @nptxfemp: Non-Periodic TxFIFO Empty (NPTxFEmp) - * This interrupt is asserted when the Non-Periodic TxFIFO is - * either half or completely empty, and there is space for at least - * one entry to be written to the Non-Periodic Transmit Request - * Queue. The half or completely empty status is determined by - * the Non-Periodic TxFIFO Empty Level bit in the Core AHB - * Configuration register (GAHBCFG.NPTxFEmpLvl). - * @rxflvl: RxFIFO Non-Empty (RxFLvl) - * Indicates that there is at least one packet pending to be read - * from the RxFIFO. - * @sof: Start of (micro)Frame (Sof) - * In Host mode, the core sets this bit to indicate that an SOF - * (FS), micro-SOF (HS), or Keep-Alive (LS) is transmitted on the - * USB. The application must write a 1 to this bit to clear the - * interrupt. - * In Device mode, in the core sets this bit to indicate that an - * SOF token has been received on the USB. The application can read - * the Device Status register to get the current (micro)frame - * number. This interrupt is seen only when the core is operating - * at either HS or FS. - * @otgint: OTG Interrupt (OTGInt) - * The core sets this bit to indicate an OTG protocol event. The - * application must read the OTG Interrupt Status (GOTGINT) - * register to determine the exact event that caused this - * interrupt. The application must clear the appropriate status bit - * in the GOTGINT register to clear this bit. - * @modemis: Mode Mismatch Interrupt (ModeMis) - * The core sets this bit when the application is trying to access: - * * A Host mode register, when the core is operating in Device - * mode - * * A Device mode register, when the core is operating in Host - * mode - * The register access is completed on the AHB with an OKAY - * response, but is ignored by the core internally and doesn't - * affect the operation of the core. - * @curmod: Current Mode of Operation (CurMod) - * Indicates the current mode of operation. - * * 1'b0: Device mode - * * 1'b1: Host mode - */ - struct cvmx_usbcx_gintsts_s { - __BITFIELD_FIELD(u32 wkupint : 1, - __BITFIELD_FIELD(u32 sessreqint : 1, - __BITFIELD_FIELD(u32 disconnint : 1, - __BITFIELD_FIELD(u32 conidstschng : 1, - __BITFIELD_FIELD(u32 reserved_27_27 : 1, - __BITFIELD_FIELD(u32 ptxfemp : 1, - __BITFIELD_FIELD(u32 hchint : 1, - __BITFIELD_FIELD(u32 prtint : 1, - __BITFIELD_FIELD(u32 reserved_23_23 : 1, - __BITFIELD_FIELD(u32 fetsusp : 1, - __BITFIELD_FIELD(u32 incomplp : 1, - __BITFIELD_FIELD(u32 incompisoin : 1, - __BITFIELD_FIELD(u32 oepint : 1, - __BITFIELD_FIELD(u32 iepint : 1, - __BITFIELD_FIELD(u32 epmis : 1, - __BITFIELD_FIELD(u32 reserved_16_16 : 1, - __BITFIELD_FIELD(u32 eopf : 1, - __BITFIELD_FIELD(u32 isooutdrop : 1, - __BITFIELD_FIELD(u32 enumdone : 1, - __BITFIELD_FIELD(u32 usbrst : 1, - __BITFIELD_FIELD(u32 usbsusp : 1, - __BITFIELD_FIELD(u32 erlysusp : 1, - __BITFIELD_FIELD(u32 i2cint : 1, - __BITFIELD_FIELD(u32 ulpickint : 1, - __BITFIELD_FIELD(u32 goutnakeff : 1, - __BITFIELD_FIELD(u32 ginnakeff : 1, - __BITFIELD_FIELD(u32 nptxfemp : 1, - __BITFIELD_FIELD(u32 rxflvl : 1, - __BITFIELD_FIELD(u32 sof : 1, - __BITFIELD_FIELD(u32 otgint : 1, - __BITFIELD_FIELD(u32 modemis : 1, - __BITFIELD_FIELD(u32 curmod : 1, - ;)))))))))))))))))))))))))))))))) - } s; -}; - -/** - * cvmx_usbc#_gnptxfsiz - * - * Non-Periodic Transmit FIFO Size Register (GNPTXFSIZ) - * - * The application can program the RAM size and the memory start address for the - * Non-Periodic TxFIFO. - */ -union cvmx_usbcx_gnptxfsiz { - u32 u32; - /** - * struct cvmx_usbcx_gnptxfsiz_s - * @nptxfdep: Non-Periodic TxFIFO Depth (NPTxFDep) - * This value is in terms of 32-bit words. - * Minimum value is 16 - * Maximum value is 32768 - * @nptxfstaddr: Non-Periodic Transmit RAM Start Address (NPTxFStAddr) - * This field contains the memory start address for Non-Periodic - * Transmit FIFO RAM. - */ - struct cvmx_usbcx_gnptxfsiz_s { - __BITFIELD_FIELD(u32 nptxfdep : 16, - __BITFIELD_FIELD(u32 nptxfstaddr : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_gnptxsts - * - * Non-Periodic Transmit FIFO/Queue Status Register (GNPTXSTS) - * - * This read-only register contains the free space information for the - * Non-Periodic TxFIFO and the Non-Periodic Transmit Request Queue. - */ -union cvmx_usbcx_gnptxsts { - u32 u32; - /** - * struct cvmx_usbcx_gnptxsts_s - * @nptxqtop: Top of the Non-Periodic Transmit Request Queue (NPTxQTop) - * Entry in the Non-Periodic Tx Request Queue that is currently - * being processed by the MAC. - * * Bits [30:27]: Channel/endpoint number - * * Bits [26:25]: - * - 2'b00: IN/OUT token - * - 2'b01: Zero-length transmit packet (device IN/host OUT) - * - 2'b10: PING/CSPLIT token - * - 2'b11: Channel halt command - * * Bit [24]: Terminate (last entry for selected channel/endpoint) - * @nptxqspcavail: Non-Periodic Transmit Request Queue Space Available - * (NPTxQSpcAvail) - * Indicates the amount of free space available in the Non- - * Periodic Transmit Request Queue. This queue holds both IN - * and OUT requests in Host mode. Device mode has only IN - * requests. - * * 8'h0: Non-Periodic Transmit Request Queue is full - * * 8'h1: 1 location available - * * 8'h2: 2 locations available - * * n: n locations available (0..8) - * * Others: Reserved - * @nptxfspcavail: Non-Periodic TxFIFO Space Avail (NPTxFSpcAvail) - * Indicates the amount of free space available in the Non- - * Periodic TxFIFO. - * Values are in terms of 32-bit words. - * * 16'h0: Non-Periodic TxFIFO is full - * * 16'h1: 1 word available - * * 16'h2: 2 words available - * * 16'hn: n words available (where 0..32768) - * * 16'h8000: 32768 words available - * * Others: Reserved - */ - struct cvmx_usbcx_gnptxsts_s { - __BITFIELD_FIELD(u32 reserved_31_31 : 1, - __BITFIELD_FIELD(u32 nptxqtop : 7, - __BITFIELD_FIELD(u32 nptxqspcavail : 8, - __BITFIELD_FIELD(u32 nptxfspcavail : 16, - ;)))) - } s; -}; - -/** - * cvmx_usbc#_grstctl - * - * Core Reset Register (GRSTCTL) - * - * The application uses this register to reset various hardware features inside - * the core. - */ -union cvmx_usbcx_grstctl { - u32 u32; - /** - * struct cvmx_usbcx_grstctl_s - * @ahbidle: AHB Master Idle (AHBIdle) - * Indicates that the AHB Master State Machine is in the IDLE - * condition. - * @dmareq: DMA Request Signal (DMAReq) - * Indicates that the DMA request is in progress. Used for debug. - * @txfnum: TxFIFO Number (TxFNum) - * This is the FIFO number that must be flushed using the TxFIFO - * Flush bit. This field must not be changed until the core clears - * the TxFIFO Flush bit. - * * 5'h0: Non-Periodic TxFIFO flush - * * 5'h1: Periodic TxFIFO 1 flush in Device mode or Periodic - * TxFIFO flush in Host mode - * * 5'h2: Periodic TxFIFO 2 flush in Device mode - * - ... - * * 5'hF: Periodic TxFIFO 15 flush in Device mode - * * 5'h10: Flush all the Periodic and Non-Periodic TxFIFOs in the - * core - * @txfflsh: TxFIFO Flush (TxFFlsh) - * This bit selectively flushes a single or all transmit FIFOs, but - * cannot do so if the core is in the midst of a transaction. - * The application must only write this bit after checking that the - * core is neither writing to the TxFIFO nor reading from the - * TxFIFO. - * The application must wait until the core clears this bit before - * performing any operations. This bit takes 8 clocks (of phy_clk - * or hclk, whichever is slower) to clear. - * @rxfflsh: RxFIFO Flush (RxFFlsh) - * The application can flush the entire RxFIFO using this bit, but - * must first ensure that the core is not in the middle of a - * transaction. - * The application must only write to this bit after checking that - * the core is neither reading from the RxFIFO nor writing to the - * RxFIFO. - * The application must wait until the bit is cleared before - * performing any other operations. This bit will take 8 clocks - * (slowest of PHY or AHB clock) to clear. - * @intknqflsh: IN Token Sequence Learning Queue Flush (INTknQFlsh) - * The application writes this bit to flush the IN Token Sequence - * Learning Queue. - * @frmcntrrst: Host Frame Counter Reset (FrmCntrRst) - * The application writes this bit to reset the (micro)frame number - * counter inside the core. When the (micro)frame counter is reset, - * the subsequent SOF sent out by the core will have a - * (micro)frame number of 0. - * @hsftrst: HClk Soft Reset (HSftRst) - * The application uses this bit to flush the control logic in the - * AHB Clock domain. Only AHB Clock Domain pipelines are reset. - * * FIFOs are not flushed with this bit. - * * All state machines in the AHB clock domain are reset to the - * Idle state after terminating the transactions on the AHB, - * following the protocol. - * * CSR control bits used by the AHB clock domain state - * machines are cleared. - * * To clear this interrupt, status mask bits that control the - * interrupt status and are generated by the AHB clock domain - * state machine are cleared. - * * Because interrupt status bits are not cleared, the application - * can get the status of any core events that occurred after it set - * this bit. - * This is a self-clearing bit that the core clears after all - * necessary logic is reset in the core. This may take several - * clocks, depending on the core's current state. - * @csftrst: Core Soft Reset (CSftRst) - * Resets the hclk and phy_clock domains as follows: - * * Clears the interrupts and all the CSR registers except the - * following register bits: - * - PCGCCTL.RstPdwnModule - * - PCGCCTL.GateHclk - * - PCGCCTL.PwrClmp - * - PCGCCTL.StopPPhyLPwrClkSelclk - * - GUSBCFG.PhyLPwrClkSel - * - GUSBCFG.DDRSel - * - GUSBCFG.PHYSel - * - GUSBCFG.FSIntf - * - GUSBCFG.ULPI_UTMI_Sel - * - GUSBCFG.PHYIf - * - HCFG.FSLSPclkSel - * - DCFG.DevSpd - * * All module state machines (except the AHB Slave Unit) are - * reset to the IDLE state, and all the transmit FIFOs and the - * receive FIFO are flushed. - * * Any transactions on the AHB Master are terminated as soon - * as possible, after gracefully completing the last data phase of - * an AHB transfer. Any transactions on the USB are terminated - * immediately. - * The application can write to this bit any time it wants to reset - * the core. This is a self-clearing bit and the core clears this - * bit after all the necessary logic is reset in the core, which - * may take several clocks, depending on the current state of the - * core. Once this bit is cleared software should wait at least 3 - * PHY clocks before doing any access to the PHY domain - * (synchronization delay). Software should also should check that - * bit 31 of this register is 1 (AHB Master is IDLE) before - * starting any operation. - * Typically software reset is used during software development - * and also when you dynamically change the PHY selection bits - * in the USB configuration registers listed above. When you - * change the PHY, the corresponding clock for the PHY is - * selected and used in the PHY domain. Once a new clock is - * selected, the PHY domain has to be reset for proper operation. - */ - struct cvmx_usbcx_grstctl_s { - __BITFIELD_FIELD(u32 ahbidle : 1, - __BITFIELD_FIELD(u32 dmareq : 1, - __BITFIELD_FIELD(u32 reserved_11_29 : 19, - __BITFIELD_FIELD(u32 txfnum : 5, - __BITFIELD_FIELD(u32 txfflsh : 1, - __BITFIELD_FIELD(u32 rxfflsh : 1, - __BITFIELD_FIELD(u32 intknqflsh : 1, - __BITFIELD_FIELD(u32 frmcntrrst : 1, - __BITFIELD_FIELD(u32 hsftrst : 1, - __BITFIELD_FIELD(u32 csftrst : 1, - ;)))))))))) - } s; -}; - -/** - * cvmx_usbc#_grxfsiz - * - * Receive FIFO Size Register (GRXFSIZ) - * - * The application can program the RAM size that must be allocated to the - * RxFIFO. - */ -union cvmx_usbcx_grxfsiz { - u32 u32; - /** - * struct cvmx_usbcx_grxfsiz_s - * @rxfdep: RxFIFO Depth (RxFDep) - * This value is in terms of 32-bit words. - * * Minimum value is 16 - * * Maximum value is 32768 - */ - struct cvmx_usbcx_grxfsiz_s { - __BITFIELD_FIELD(u32 reserved_16_31 : 16, - __BITFIELD_FIELD(u32 rxfdep : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_grxstsph - * - * Receive Status Read and Pop Register, Host Mode (GRXSTSPH) - * - * A read to the Receive Status Read and Pop register returns and additionally - * pops the top data entry out of the RxFIFO. - * This Description is only valid when the core is in Host Mode. For Device Mode - * use USBC_GRXSTSPD instead. - * NOTE: GRXSTSPH and GRXSTSPD are physically the same register and share the - * same offset in the O2P USB core. The offset difference shown in this - * document is for software clarity and is actually ignored by the - * hardware. - */ -union cvmx_usbcx_grxstsph { - u32 u32; - /** - * struct cvmx_usbcx_grxstsph_s - * @pktsts: Packet Status (PktSts) - * Indicates the status of the received packet - * * 4'b0010: IN data packet received - * * 4'b0011: IN transfer completed (triggers an interrupt) - * * 4'b0101: Data toggle error (triggers an interrupt) - * * 4'b0111: Channel halted (triggers an interrupt) - * * Others: Reserved - * @dpid: Data PID (DPID) - * * 2'b00: DATA0 - * * 2'b10: DATA1 - * * 2'b01: DATA2 - * * 2'b11: MDATA - * @bcnt: Byte Count (BCnt) - * Indicates the byte count of the received IN data packet - * @chnum: Channel Number (ChNum) - * Indicates the channel number to which the current received - * packet belongs. - */ - struct cvmx_usbcx_grxstsph_s { - __BITFIELD_FIELD(u32 reserved_21_31 : 11, - __BITFIELD_FIELD(u32 pktsts : 4, - __BITFIELD_FIELD(u32 dpid : 2, - __BITFIELD_FIELD(u32 bcnt : 11, - __BITFIELD_FIELD(u32 chnum : 4, - ;))))) - } s; -}; - -/** - * cvmx_usbc#_gusbcfg - * - * Core USB Configuration Register (GUSBCFG) - * - * This register can be used to configure the core after power-on or a changing - * to Host mode or Device mode. It contains USB and USB-PHY related - * configuration parameters. The application must program this register before - * starting any transactions on either the AHB or the USB. Do not make changes - * to this register after the initial programming. - */ -union cvmx_usbcx_gusbcfg { - u32 u32; - /** - * struct cvmx_usbcx_gusbcfg_s - * @otgi2csel: UTMIFS or I2C Interface Select (OtgI2CSel) - * This bit is always 0x0. - * @phylpwrclksel: PHY Low-Power Clock Select (PhyLPwrClkSel) - * Software should set this bit to 0x0. - * Selects either 480-MHz or 48-MHz (low-power) PHY mode. In - * FS and LS modes, the PHY can usually operate on a 48-MHz - * clock to save power. - * * 1'b0: 480-MHz Internal PLL clock - * * 1'b1: 48-MHz External Clock - * In 480 MHz mode, the UTMI interface operates at either 60 or - * 30-MHz, depending upon whether 8- or 16-bit data width is - * selected. In 48-MHz mode, the UTMI interface operates at 48 - * MHz in FS mode and at either 48 or 6 MHz in LS mode - * (depending on the PHY vendor). - * This bit drives the utmi_fsls_low_power core output signal, and - * is valid only for UTMI+ PHYs. - * @usbtrdtim: USB Turnaround Time (USBTrdTim) - * Sets the turnaround time in PHY clocks. - * Specifies the response time for a MAC request to the Packet - * FIFO Controller (PFC) to fetch data from the DFIFO (SPRAM). - * This must be programmed to 0x5. - * @hnpcap: HNP-Capable (HNPCap) - * This bit is always 0x0. - * @srpcap: SRP-Capable (SRPCap) - * This bit is always 0x0. - * @ddrsel: ULPI DDR Select (DDRSel) - * Software should set this bit to 0x0. - * @physel: USB 2.0 High-Speed PHY or USB 1.1 Full-Speed Serial - * Software should set this bit to 0x0. - * @fsintf: Full-Speed Serial Interface Select (FSIntf) - * Software should set this bit to 0x0. - * @ulpi_utmi_sel: ULPI or UTMI+ Select (ULPI_UTMI_Sel) - * This bit is always 0x0. - * @phyif: PHY Interface (PHYIf) - * This bit is always 0x1. - * @toutcal: HS/FS Timeout Calibration (TOutCal) - * The number of PHY clocks that the application programs in this - * field is added to the high-speed/full-speed interpacket timeout - * duration in the core to account for any additional delays - * introduced by the PHY. This may be required, since the delay - * introduced by the PHY in generating the linestate condition may - * vary from one PHY to another. - * The USB standard timeout value for high-speed operation is - * 736 to 816 (inclusive) bit times. The USB standard timeout - * value for full-speed operation is 16 to 18 (inclusive) bit - * times. The application must program this field based on the - * speed of enumeration. The number of bit times added per PHY - * clock are: - * High-speed operation: - * * One 30-MHz PHY clock = 16 bit times - * * One 60-MHz PHY clock = 8 bit times - * Full-speed operation: - * * One 30-MHz PHY clock = 0.4 bit times - * * One 60-MHz PHY clock = 0.2 bit times - * * One 48-MHz PHY clock = 0.25 bit times - */ - struct cvmx_usbcx_gusbcfg_s { - __BITFIELD_FIELD(u32 reserved_17_31 : 15, - __BITFIELD_FIELD(u32 otgi2csel : 1, - __BITFIELD_FIELD(u32 phylpwrclksel : 1, - __BITFIELD_FIELD(u32 reserved_14_14 : 1, - __BITFIELD_FIELD(u32 usbtrdtim : 4, - __BITFIELD_FIELD(u32 hnpcap : 1, - __BITFIELD_FIELD(u32 srpcap : 1, - __BITFIELD_FIELD(u32 ddrsel : 1, - __BITFIELD_FIELD(u32 physel : 1, - __BITFIELD_FIELD(u32 fsintf : 1, - __BITFIELD_FIELD(u32 ulpi_utmi_sel : 1, - __BITFIELD_FIELD(u32 phyif : 1, - __BITFIELD_FIELD(u32 toutcal : 3, - ;))))))))))))) - } s; -}; - -/** - * cvmx_usbc#_haint - * - * Host All Channels Interrupt Register (HAINT) - * - * When a significant event occurs on a channel, the Host All Channels Interrupt - * register interrupts the application using the Host Channels Interrupt bit of - * the Core Interrupt register (GINTSTS.HChInt). This is shown in Interrupt. - * There is one interrupt bit per channel, up to a maximum of 16 bits. Bits in - * this register are set and cleared when the application sets and clears bits - * in the corresponding Host Channel-n Interrupt register. - */ -union cvmx_usbcx_haint { - u32 u32; - /** - * struct cvmx_usbcx_haint_s - * @haint: Channel Interrupts (HAINT) - * One bit per channel: Bit 0 for Channel 0, bit 15 for Channel 15 - */ - struct cvmx_usbcx_haint_s { - __BITFIELD_FIELD(u32 reserved_16_31 : 16, - __BITFIELD_FIELD(u32 haint : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_haintmsk - * - * Host All Channels Interrupt Mask Register (HAINTMSK) - * - * The Host All Channel Interrupt Mask register works with the Host All Channel - * Interrupt register to interrupt the application when an event occurs on a - * channel. There is one interrupt mask bit per channel, up to a maximum of 16 - * bits. - * Mask interrupt: 1'b0 Unmask interrupt: 1'b1 - */ -union cvmx_usbcx_haintmsk { - u32 u32; - /** - * struct cvmx_usbcx_haintmsk_s - * @haintmsk: Channel Interrupt Mask (HAINTMsk) - * One bit per channel: Bit 0 for channel 0, bit 15 for channel 15 - */ - struct cvmx_usbcx_haintmsk_s { - __BITFIELD_FIELD(u32 reserved_16_31 : 16, - __BITFIELD_FIELD(u32 haintmsk : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_hcchar# - * - * Host Channel-n Characteristics Register (HCCHAR) - * - */ -union cvmx_usbcx_hccharx { - u32 u32; - /** - * struct cvmx_usbcx_hccharx_s - * @chena: Channel Enable (ChEna) - * This field is set by the application and cleared by the OTG - * host. - * * 1'b0: Channel disabled - * * 1'b1: Channel enabled - * @chdis: Channel Disable (ChDis) - * The application sets this bit to stop transmitting/receiving - * data on a channel, even before the transfer for that channel is - * complete. The application must wait for the Channel Disabled - * interrupt before treating the channel as disabled. - * @oddfrm: Odd Frame (OddFrm) - * This field is set (reset) by the application to indicate that - * the OTG host must perform a transfer in an odd (micro)frame. - * This field is applicable for only periodic (isochronous and - * interrupt) transactions. - * * 1'b0: Even (micro)frame - * * 1'b1: Odd (micro)frame - * @devaddr: Device Address (DevAddr) - * This field selects the specific device serving as the data - * source or sink. - * @ec: Multi Count (MC) / Error Count (EC) - * When the Split Enable bit of the Host Channel-n Split Control - * register (HCSPLTn.SpltEna) is reset (1'b0), this field indicates - * to the host the number of transactions that should be executed - * per microframe for this endpoint. - * * 2'b00: Reserved. This field yields undefined results. - * * 2'b01: 1 transaction - * * 2'b10: 2 transactions to be issued for this endpoint per - * microframe - * * 2'b11: 3 transactions to be issued for this endpoint per - * microframe - * When HCSPLTn.SpltEna is set (1'b1), this field indicates the - * number of immediate retries to be performed for a periodic split - * transactions on transaction errors. This field must be set to at - * least 2'b01. - * @eptype: Endpoint Type (EPType) - * Indicates the transfer type selected. - * * 2'b00: Control - * * 2'b01: Isochronous - * * 2'b10: Bulk - * * 2'b11: Interrupt - * @lspddev: Low-Speed Device (LSpdDev) - * This field is set by the application to indicate that this - * channel is communicating to a low-speed device. - * @epdir: Endpoint Direction (EPDir) - * Indicates whether the transaction is IN or OUT. - * * 1'b0: OUT - * * 1'b1: IN - * @epnum: Endpoint Number (EPNum) - * Indicates the endpoint number on the device serving as the - * data source or sink. - * @mps: Maximum Packet Size (MPS) - * Indicates the maximum packet size of the associated endpoint. - */ - struct cvmx_usbcx_hccharx_s { - __BITFIELD_FIELD(u32 chena : 1, - __BITFIELD_FIELD(u32 chdis : 1, - __BITFIELD_FIELD(u32 oddfrm : 1, - __BITFIELD_FIELD(u32 devaddr : 7, - __BITFIELD_FIELD(u32 ec : 2, - __BITFIELD_FIELD(u32 eptype : 2, - __BITFIELD_FIELD(u32 lspddev : 1, - __BITFIELD_FIELD(u32 reserved_16_16 : 1, - __BITFIELD_FIELD(u32 epdir : 1, - __BITFIELD_FIELD(u32 epnum : 4, - __BITFIELD_FIELD(u32 mps : 11, - ;))))))))))) - } s; -}; - -/** - * cvmx_usbc#_hcfg - * - * Host Configuration Register (HCFG) - * - * This register configures the core after power-on. Do not make changes to this - * register after initializing the host. - */ -union cvmx_usbcx_hcfg { - u32 u32; - /** - * struct cvmx_usbcx_hcfg_s - * @fslssupp: FS- and LS-Only Support (FSLSSupp) - * The application uses this bit to control the core's enumeration - * speed. Using this bit, the application can make the core - * enumerate as a FS host, even if the connected device supports - * HS traffic. Do not make changes to this field after initial - * programming. - * * 1'b0: HS/FS/LS, based on the maximum speed supported by - * the connected device - * * 1'b1: FS/LS-only, even if the connected device can support HS - * @fslspclksel: FS/LS PHY Clock Select (FSLSPclkSel) - * When the core is in FS Host mode - * * 2'b00: PHY clock is running at 30/60 MHz - * * 2'b01: PHY clock is running at 48 MHz - * * Others: Reserved - * When the core is in LS Host mode - * * 2'b00: PHY clock is running at 30/60 MHz. When the - * UTMI+/ULPI PHY Low Power mode is not selected, use - * 30/60 MHz. - * * 2'b01: PHY clock is running at 48 MHz. When the UTMI+ - * PHY Low Power mode is selected, use 48MHz if the PHY - * supplies a 48 MHz clock during LS mode. - * * 2'b10: PHY clock is running at 6 MHz. In USB 1.1 FS mode, - * use 6 MHz when the UTMI+ PHY Low Power mode is - * selected and the PHY supplies a 6 MHz clock during LS - * mode. If you select a 6 MHz clock during LS mode, you must - * do a soft reset. - * * 2'b11: Reserved - */ - struct cvmx_usbcx_hcfg_s { - __BITFIELD_FIELD(u32 reserved_3_31 : 29, - __BITFIELD_FIELD(u32 fslssupp : 1, - __BITFIELD_FIELD(u32 fslspclksel : 2, - ;))) - } s; -}; - -/** - * cvmx_usbc#_hcint# - * - * Host Channel-n Interrupt Register (HCINT) - * - * This register indicates the status of a channel with respect to USB- and - * AHB-related events. The application must read this register when the Host - * Channels Interrupt bit of the Core Interrupt register (GINTSTS.HChInt) is - * set. Before the application can read this register, it must first read - * the Host All Channels Interrupt (HAINT) register to get the exact channel - * number for the Host Channel-n Interrupt register. The application must clear - * the appropriate bit in this register to clear the corresponding bits in the - * HAINT and GINTSTS registers. - */ -union cvmx_usbcx_hcintx { - u32 u32; - /** - * struct cvmx_usbcx_hcintx_s - * @datatglerr: Data Toggle Error (DataTglErr) - * @frmovrun: Frame Overrun (FrmOvrun) - * @bblerr: Babble Error (BblErr) - * @xacterr: Transaction Error (XactErr) - * @nyet: NYET Response Received Interrupt (NYET) - * @ack: ACK Response Received Interrupt (ACK) - * @nak: NAK Response Received Interrupt (NAK) - * @stall: STALL Response Received Interrupt (STALL) - * @ahberr: This bit is always 0x0. - * @chhltd: Channel Halted (ChHltd) - * Indicates the transfer completed abnormally either because of - * any USB transaction error or in response to disable request by - * the application. - * @xfercompl: Transfer Completed (XferCompl) - * Transfer completed normally without any errors. - */ - struct cvmx_usbcx_hcintx_s { - __BITFIELD_FIELD(u32 reserved_11_31 : 21, - __BITFIELD_FIELD(u32 datatglerr : 1, - __BITFIELD_FIELD(u32 frmovrun : 1, - __BITFIELD_FIELD(u32 bblerr : 1, - __BITFIELD_FIELD(u32 xacterr : 1, - __BITFIELD_FIELD(u32 nyet : 1, - __BITFIELD_FIELD(u32 ack : 1, - __BITFIELD_FIELD(u32 nak : 1, - __BITFIELD_FIELD(u32 stall : 1, - __BITFIELD_FIELD(u32 ahberr : 1, - __BITFIELD_FIELD(u32 chhltd : 1, - __BITFIELD_FIELD(u32 xfercompl : 1, - ;)))))))))))) - } s; -}; - -/** - * cvmx_usbc#_hcintmsk# - * - * Host Channel-n Interrupt Mask Register (HCINTMSKn) - * - * This register reflects the mask for each channel status described in the - * previous section. - * Mask interrupt: 1'b0 Unmask interrupt: 1'b1 - */ -union cvmx_usbcx_hcintmskx { - u32 u32; - /** - * struct cvmx_usbcx_hcintmskx_s - * @datatglerrmsk: Data Toggle Error Mask (DataTglErrMsk) - * @frmovrunmsk: Frame Overrun Mask (FrmOvrunMsk) - * @bblerrmsk: Babble Error Mask (BblErrMsk) - * @xacterrmsk: Transaction Error Mask (XactErrMsk) - * @nyetmsk: NYET Response Received Interrupt Mask (NyetMsk) - * @ackmsk: ACK Response Received Interrupt Mask (AckMsk) - * @nakmsk: NAK Response Received Interrupt Mask (NakMsk) - * @stallmsk: STALL Response Received Interrupt Mask (StallMsk) - * @ahberrmsk: AHB Error Mask (AHBErrMsk) - * @chhltdmsk: Channel Halted Mask (ChHltdMsk) - * @xfercomplmsk: Transfer Completed Mask (XferComplMsk) - */ - struct cvmx_usbcx_hcintmskx_s { - __BITFIELD_FIELD(u32 reserved_11_31 : 21, - __BITFIELD_FIELD(u32 datatglerrmsk : 1, - __BITFIELD_FIELD(u32 frmovrunmsk : 1, - __BITFIELD_FIELD(u32 bblerrmsk : 1, - __BITFIELD_FIELD(u32 xacterrmsk : 1, - __BITFIELD_FIELD(u32 nyetmsk : 1, - __BITFIELD_FIELD(u32 ackmsk : 1, - __BITFIELD_FIELD(u32 nakmsk : 1, - __BITFIELD_FIELD(u32 stallmsk : 1, - __BITFIELD_FIELD(u32 ahberrmsk : 1, - __BITFIELD_FIELD(u32 chhltdmsk : 1, - __BITFIELD_FIELD(u32 xfercomplmsk : 1, - ;)))))))))))) - } s; -}; - -/** - * cvmx_usbc#_hcsplt# - * - * Host Channel-n Split Control Register (HCSPLT) - * - */ -union cvmx_usbcx_hcspltx { - u32 u32; - /** - * struct cvmx_usbcx_hcspltx_s - * @spltena: Split Enable (SpltEna) - * The application sets this field to indicate that this channel is - * enabled to perform split transactions. - * @compsplt: Do Complete Split (CompSplt) - * The application sets this field to request the OTG host to - * perform a complete split transaction. - * @xactpos: Transaction Position (XactPos) - * This field is used to determine whether to send all, first, - * middle, or last payloads with each OUT transaction. - * * 2'b11: All. This is the entire data payload is of this - * transaction (which is less than or equal to 188 bytes). - * * 2'b10: Begin. This is the first data payload of this - * transaction (which is larger than 188 bytes). - * * 2'b00: Mid. This is the middle payload of this transaction - * (which is larger than 188 bytes). - * * 2'b01: End. This is the last payload of this transaction - * (which is larger than 188 bytes). - * @hubaddr: Hub Address (HubAddr) - * This field holds the device address of the transaction - * translator's hub. - * @prtaddr: Port Address (PrtAddr) - * This field is the port number of the recipient transaction - * translator. - */ - struct cvmx_usbcx_hcspltx_s { - __BITFIELD_FIELD(u32 spltena : 1, - __BITFIELD_FIELD(u32 reserved_17_30 : 14, - __BITFIELD_FIELD(u32 compsplt : 1, - __BITFIELD_FIELD(u32 xactpos : 2, - __BITFIELD_FIELD(u32 hubaddr : 7, - __BITFIELD_FIELD(u32 prtaddr : 7, - ;)))))) - } s; -}; - -/** - * cvmx_usbc#_hctsiz# - * - * Host Channel-n Transfer Size Register (HCTSIZ) - * - */ -union cvmx_usbcx_hctsizx { - u32 u32; - /** - * struct cvmx_usbcx_hctsizx_s - * @dopng: Do Ping (DoPng) - * Setting this field to 1 directs the host to do PING protocol. - * @pid: PID (Pid) - * The application programs this field with the type of PID to use - * for the initial transaction. The host will maintain this field - * for the rest of the transfer. - * * 2'b00: DATA0 - * * 2'b01: DATA2 - * * 2'b10: DATA1 - * * 2'b11: MDATA (non-control)/SETUP (control) - * @pktcnt: Packet Count (PktCnt) - * This field is programmed by the application with the expected - * number of packets to be transmitted (OUT) or received (IN). - * The host decrements this count on every successful - * transmission or reception of an OUT/IN packet. Once this count - * reaches zero, the application is interrupted to indicate normal - * completion. - * @xfersize: Transfer Size (XferSize) - * For an OUT, this field is the number of data bytes the host will - * send during the transfer. - * For an IN, this field is the buffer size that the application - * has reserved for the transfer. The application is expected to - * program this field as an integer multiple of the maximum packet - * size for IN transactions (periodic and non-periodic). - */ - struct cvmx_usbcx_hctsizx_s { - __BITFIELD_FIELD(u32 dopng : 1, - __BITFIELD_FIELD(u32 pid : 2, - __BITFIELD_FIELD(u32 pktcnt : 10, - __BITFIELD_FIELD(u32 xfersize : 19, - ;)))) - } s; -}; - -/** - * cvmx_usbc#_hfir - * - * Host Frame Interval Register (HFIR) - * - * This register stores the frame interval information for the current speed to - * which the O2P USB core has enumerated. - */ -union cvmx_usbcx_hfir { - u32 u32; - /** - * struct cvmx_usbcx_hfir_s - * @frint: Frame Interval (FrInt) - * The value that the application programs to this field specifies - * the interval between two consecutive SOFs (FS) or micro- - * SOFs (HS) or Keep-Alive tokens (HS). This field contains the - * number of PHY clocks that constitute the required frame - * interval. The default value set in this field for a FS operation - * when the PHY clock frequency is 60 MHz. The application can - * write a value to this register only after the Port Enable bit of - * the Host Port Control and Status register (HPRT.PrtEnaPort) - * has been set. If no value is programmed, the core calculates - * the value based on the PHY clock specified in the FS/LS PHY - * Clock Select field of the Host Configuration register - * (HCFG.FSLSPclkSel). Do not change the value of this field - * after the initial configuration. - * * 125 us (PHY clock frequency for HS) - * * 1 ms (PHY clock frequency for FS/LS) - */ - struct cvmx_usbcx_hfir_s { - __BITFIELD_FIELD(u32 reserved_16_31 : 16, - __BITFIELD_FIELD(u32 frint : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_hfnum - * - * Host Frame Number/Frame Time Remaining Register (HFNUM) - * - * This register indicates the current frame number. - * It also indicates the time remaining (in terms of the number of PHY clocks) - * in the current (micro)frame. - */ -union cvmx_usbcx_hfnum { - u32 u32; - /** - * struct cvmx_usbcx_hfnum_s - * @frrem: Frame Time Remaining (FrRem) - * Indicates the amount of time remaining in the current - * microframe (HS) or frame (FS/LS), in terms of PHY clocks. - * This field decrements on each PHY clock. When it reaches - * zero, this field is reloaded with the value in the Frame - * Interval register and a new SOF is transmitted on the USB. - * @frnum: Frame Number (FrNum) - * This field increments when a new SOF is transmitted on the - * USB, and is reset to 0 when it reaches 16'h3FFF. - */ - struct cvmx_usbcx_hfnum_s { - __BITFIELD_FIELD(u32 frrem : 16, - __BITFIELD_FIELD(u32 frnum : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_hprt - * - * Host Port Control and Status Register (HPRT) - * - * This register is available in both Host and Device modes. - * Currently, the OTG Host supports only one port. - * A single register holds USB port-related information such as USB reset, - * enable, suspend, resume, connect status, and test mode for each port. The - * R_SS_WC bits in this register can trigger an interrupt to the application - * through the Host Port Interrupt bit of the Core Interrupt register - * (GINTSTS.PrtInt). On a Port Interrupt, the application must read this - * register and clear the bit that caused the interrupt. For the R_SS_WC bits, - * the application must write a 1 to the bit to clear the interrupt. - */ -union cvmx_usbcx_hprt { - u32 u32; - /** - * struct cvmx_usbcx_hprt_s - * @prtspd: Port Speed (PrtSpd) - * Indicates the speed of the device attached to this port. - * * 2'b00: High speed - * * 2'b01: Full speed - * * 2'b10: Low speed - * * 2'b11: Reserved - * @prttstctl: Port Test Control (PrtTstCtl) - * The application writes a nonzero value to this field to put - * the port into a Test mode, and the corresponding pattern is - * signaled on the port. - * * 4'b0000: Test mode disabled - * * 4'b0001: Test_J mode - * * 4'b0010: Test_K mode - * * 4'b0011: Test_SE0_NAK mode - * * 4'b0100: Test_Packet mode - * * 4'b0101: Test_Force_Enable - * * Others: Reserved - * PrtSpd must be zero (i.e. the interface must be in high-speed - * mode) to use the PrtTstCtl test modes. - * @prtpwr: Port Power (PrtPwr) - * The application uses this field to control power to this port, - * and the core clears this bit on an overcurrent condition. - * * 1'b0: Power off - * * 1'b1: Power on - * @prtlnsts: Port Line Status (PrtLnSts) - * Indicates the current logic level USB data lines - * * Bit [10]: Logic level of D- - * * Bit [11]: Logic level of D+ - * @prtrst: Port Reset (PrtRst) - * When the application sets this bit, a reset sequence is - * started on this port. The application must time the reset - * period and clear this bit after the reset sequence is - * complete. - * * 1'b0: Port not in reset - * * 1'b1: Port in reset - * The application must leave this bit set for at least a - * minimum duration mentioned below to start a reset on the - * port. The application can leave it set for another 10 ms in - * addition to the required minimum duration, before clearing - * the bit, even though there is no maximum limit set by the - * USB standard. - * * High speed: 50 ms - * * Full speed/Low speed: 10 ms - * @prtsusp: Port Suspend (PrtSusp) - * The application sets this bit to put this port in Suspend - * mode. The core only stops sending SOFs when this is set. - * To stop the PHY clock, the application must set the Port - * Clock Stop bit, which will assert the suspend input pin of - * the PHY. - * The read value of this bit reflects the current suspend - * status of the port. This bit is cleared by the core after a - * remote wakeup signal is detected or the application sets - * the Port Reset bit or Port Resume bit in this register or the - * Resume/Remote Wakeup Detected Interrupt bit or - * Disconnect Detected Interrupt bit in the Core Interrupt - * register (GINTSTS.WkUpInt or GINTSTS.DisconnInt, - * respectively). - * * 1'b0: Port not in Suspend mode - * * 1'b1: Port in Suspend mode - * @prtres: Port Resume (PrtRes) - * The application sets this bit to drive resume signaling on - * the port. The core continues to drive the resume signal - * until the application clears this bit. - * If the core detects a USB remote wakeup sequence, as - * indicated by the Port Resume/Remote Wakeup Detected - * Interrupt bit of the Core Interrupt register - * (GINTSTS.WkUpInt), the core starts driving resume - * signaling without application intervention and clears this bit - * when it detects a disconnect condition. The read value of - * this bit indicates whether the core is currently driving - * resume signaling. - * * 1'b0: No resume driven - * * 1'b1: Resume driven - * @prtovrcurrchng: Port Overcurrent Change (PrtOvrCurrChng) - * The core sets this bit when the status of the Port - * Overcurrent Active bit (bit 4) in this register changes. - * @prtovrcurract: Port Overcurrent Active (PrtOvrCurrAct) - * Indicates the overcurrent condition of the port. - * * 1'b0: No overcurrent condition - * * 1'b1: Overcurrent condition - * @prtenchng: Port Enable/Disable Change (PrtEnChng) - * The core sets this bit when the status of the Port Enable bit - * [2] of this register changes. - * @prtena: Port Enable (PrtEna) - * A port is enabled only by the core after a reset sequence, - * and is disabled by an overcurrent condition, a disconnect - * condition, or by the application clearing this bit. The - * application cannot set this bit by a register write. It can only - * clear it to disable the port. This bit does not trigger any - * interrupt to the application. - * * 1'b0: Port disabled - * * 1'b1: Port enabled - * @prtconndet: Port Connect Detected (PrtConnDet) - * The core sets this bit when a device connection is detected - * to trigger an interrupt to the application using the Host Port - * Interrupt bit of the Core Interrupt register (GINTSTS.PrtInt). - * The application must write a 1 to this bit to clear the - * interrupt. - * @prtconnsts: Port Connect Status (PrtConnSts) - * * 0: No device is attached to the port. - * * 1: A device is attached to the port. - */ - struct cvmx_usbcx_hprt_s { - __BITFIELD_FIELD(u32 reserved_19_31 : 13, - __BITFIELD_FIELD(u32 prtspd : 2, - __BITFIELD_FIELD(u32 prttstctl : 4, - __BITFIELD_FIELD(u32 prtpwr : 1, - __BITFIELD_FIELD(u32 prtlnsts : 2, - __BITFIELD_FIELD(u32 reserved_9_9 : 1, - __BITFIELD_FIELD(u32 prtrst : 1, - __BITFIELD_FIELD(u32 prtsusp : 1, - __BITFIELD_FIELD(u32 prtres : 1, - __BITFIELD_FIELD(u32 prtovrcurrchng : 1, - __BITFIELD_FIELD(u32 prtovrcurract : 1, - __BITFIELD_FIELD(u32 prtenchng : 1, - __BITFIELD_FIELD(u32 prtena : 1, - __BITFIELD_FIELD(u32 prtconndet : 1, - __BITFIELD_FIELD(u32 prtconnsts : 1, - ;))))))))))))))) - } s; -}; - -/** - * cvmx_usbc#_hptxfsiz - * - * Host Periodic Transmit FIFO Size Register (HPTXFSIZ) - * - * This register holds the size and the memory start address of the Periodic - * TxFIFO, as shown in Figures 310 and 311. - */ -union cvmx_usbcx_hptxfsiz { - u32 u32; - /** - * struct cvmx_usbcx_hptxfsiz_s - * @ptxfsize: Host Periodic TxFIFO Depth (PTxFSize) - * This value is in terms of 32-bit words. - * * Minimum value is 16 - * * Maximum value is 32768 - * @ptxfstaddr: Host Periodic TxFIFO Start Address (PTxFStAddr) - */ - struct cvmx_usbcx_hptxfsiz_s { - __BITFIELD_FIELD(u32 ptxfsize : 16, - __BITFIELD_FIELD(u32 ptxfstaddr : 16, - ;)) - } s; -}; - -/** - * cvmx_usbc#_hptxsts - * - * Host Periodic Transmit FIFO/Queue Status Register (HPTXSTS) - * - * This read-only register contains the free space information for the Periodic - * TxFIFO and the Periodic Transmit Request Queue - */ -union cvmx_usbcx_hptxsts { - u32 u32; - /** - * struct cvmx_usbcx_hptxsts_s - * @ptxqtop: Top of the Periodic Transmit Request Queue (PTxQTop) - * This indicates the entry in the Periodic Tx Request Queue that - * is currently being processes by the MAC. - * This register is used for debugging. - * * Bit [31]: Odd/Even (micro)frame - * - 1'b0: send in even (micro)frame - * - 1'b1: send in odd (micro)frame - * * Bits [30:27]: Channel/endpoint number - * * Bits [26:25]: Type - * - 2'b00: IN/OUT - * - 2'b01: Zero-length packet - * - 2'b10: CSPLIT - * - 2'b11: Disable channel command - * * Bit [24]: Terminate (last entry for the selected - * channel/endpoint) - * @ptxqspcavail: Periodic Transmit Request Queue Space Available - * (PTxQSpcAvail) - * Indicates the number of free locations available to be written - * in the Periodic Transmit Request Queue. This queue holds both - * IN and OUT requests. - * * 8'h0: Periodic Transmit Request Queue is full - * * 8'h1: 1 location available - * * 8'h2: 2 locations available - * * n: n locations available (0..8) - * * Others: Reserved - * @ptxfspcavail: Periodic Transmit Data FIFO Space Available - * (PTxFSpcAvail) - * Indicates the number of free locations available to be written - * to in the Periodic TxFIFO. - * Values are in terms of 32-bit words - * * 16'h0: Periodic TxFIFO is full - * * 16'h1: 1 word available - * * 16'h2: 2 words available - * * 16'hn: n words available (where 0..32768) - * * 16'h8000: 32768 words available - * * Others: Reserved - */ - struct cvmx_usbcx_hptxsts_s { - __BITFIELD_FIELD(u32 ptxqtop : 8, - __BITFIELD_FIELD(u32 ptxqspcavail : 8, - __BITFIELD_FIELD(u32 ptxfspcavail : 16, - ;))) - } s; -}; - -/** - * cvmx_usbn#_clk_ctl - * - * USBN_CLK_CTL = USBN's Clock Control - * - * This register is used to control the frequency of the hclk and the - * hreset and phy_rst signals. - */ -union cvmx_usbnx_clk_ctl { - u64 u64; - /** - * struct cvmx_usbnx_clk_ctl_s - * @divide2: The 'hclk' used by the USB subsystem is derived - * from the eclk. - * Also see the field DIVIDE. DIVIDE2<1> must currently - * be zero because it is not implemented, so the maximum - * ratio of eclk/hclk is currently 16. - * The actual divide number for hclk is: - * (DIVIDE2 + 1) * (DIVIDE + 1) - * @hclk_rst: When this field is '0' the HCLK-DIVIDER used to - * generate the hclk in the USB Subsystem is held - * in reset. This bit must be set to '0' before - * changing the value os DIVIDE in this register. - * The reset to the HCLK_DIVIDERis also asserted - * when core reset is asserted. - * @p_x_on: Force USB-PHY on during suspend. - * '1' USB-PHY XO block is powered-down during - * suspend. - * '0' USB-PHY XO block is powered-up during - * suspend. - * The value of this field must be set while POR is - * active. - * @p_rtype: PHY reference clock type - * On CN50XX/CN52XX/CN56XX the values are: - * '0' The USB-PHY uses a 12MHz crystal as a clock source - * at the USB_XO and USB_XI pins. - * '1' Reserved. - * '2' The USB_PHY uses 12/24/48MHz 2.5V board clock at the - * USB_XO pin. USB_XI should be tied to ground in this - * case. - * '3' Reserved. - * On CN3xxx bits 14 and 15 are p_xenbn and p_rclk and values are: - * '0' Reserved. - * '1' Reserved. - * '2' The PHY PLL uses the XO block output as a reference. - * The XO block uses an external clock supplied on the - * XO pin. USB_XI should be tied to ground for this - * usage. - * '3' The XO block uses the clock from a crystal. - * @p_com_on: '0' Force USB-PHY XO Bias, Bandgap and PLL to - * remain powered in Suspend Mode. - * '1' The USB-PHY XO Bias, Bandgap and PLL are - * powered down in suspend mode. - * The value of this field must be set while POR is - * active. - * @p_c_sel: Phy clock speed select. - * Selects the reference clock / crystal frequency. - * '11': Reserved - * '10': 48 MHz (reserved when a crystal is used) - * '01': 24 MHz (reserved when a crystal is used) - * '00': 12 MHz - * The value of this field must be set while POR is - * active. - * NOTE: if a crystal is used as a reference clock, - * this field must be set to 12 MHz. - * @cdiv_byp: Used to enable the bypass input to the USB_CLK_DIV. - * @sd_mode: Scaledown mode for the USBC. Control timing events - * in the USBC, for normal operation this must be '0'. - * @s_bist: Starts bist on the hclk memories, during the '0' - * to '1' transition. - * @por: Power On Reset for the PHY. - * Resets all the PHYS registers and state machines. - * @enable: When '1' allows the generation of the hclk. When - * '0' the hclk will not be generated. SEE DIVIDE - * field of this register. - * @prst: When this field is '0' the reset associated with - * the phy_clk functionality in the USB Subsystem is - * help in reset. This bit should not be set to '1' - * until the time it takes 6 clocks (hclk or phy_clk, - * whichever is slower) has passed. Under normal - * operation once this bit is set to '1' it should not - * be set to '0'. - * @hrst: When this field is '0' the reset associated with - * the hclk functioanlity in the USB Subsystem is - * held in reset.This bit should not be set to '1' - * until 12ms after phy_clk is stable. Under normal - * operation, once this bit is set to '1' it should - * not be set to '0'. - * @divide: The frequency of 'hclk' used by the USB subsystem - * is the eclk frequency divided by the value of - * (DIVIDE2 + 1) * (DIVIDE + 1), also see the field - * DIVIDE2 of this register. - * The hclk frequency should be less than 125Mhz. - * After writing a value to this field the SW should - * read the field for the value written. - * The ENABLE field of this register should not be set - * until AFTER this field is set and then read. - */ - struct cvmx_usbnx_clk_ctl_s { - __BITFIELD_FIELD(u64 reserved_20_63 : 44, - __BITFIELD_FIELD(u64 divide2 : 2, - __BITFIELD_FIELD(u64 hclk_rst : 1, - __BITFIELD_FIELD(u64 p_x_on : 1, - __BITFIELD_FIELD(u64 p_rtype : 2, - __BITFIELD_FIELD(u64 p_com_on : 1, - __BITFIELD_FIELD(u64 p_c_sel : 2, - __BITFIELD_FIELD(u64 cdiv_byp : 1, - __BITFIELD_FIELD(u64 sd_mode : 2, - __BITFIELD_FIELD(u64 s_bist : 1, - __BITFIELD_FIELD(u64 por : 1, - __BITFIELD_FIELD(u64 enable : 1, - __BITFIELD_FIELD(u64 prst : 1, - __BITFIELD_FIELD(u64 hrst : 1, - __BITFIELD_FIELD(u64 divide : 3, - ;))))))))))))))) - } s; -}; - -/** - * cvmx_usbn#_usbp_ctl_status - * - * USBN_USBP_CTL_STATUS = USBP Control And Status Register - * - * Contains general control and status information for the USBN block. - */ -union cvmx_usbnx_usbp_ctl_status { - u64 u64; - /** - * struct cvmx_usbnx_usbp_ctl_status_s - * @txrisetune: HS Transmitter Rise/Fall Time Adjustment - * @txvreftune: HS DC Voltage Level Adjustment - * @txfslstune: FS/LS Source Impedance Adjustment - * @txhsxvtune: Transmitter High-Speed Crossover Adjustment - * @sqrxtune: Squelch Threshold Adjustment - * @compdistune: Disconnect Threshold Adjustment - * @otgtune: VBUS Valid Threshold Adjustment - * @otgdisable: OTG Block Disable - * @portreset: Per_Port Reset - * @drvvbus: Drive VBUS - * @lsbist: Low-Speed BIST Enable. - * @fsbist: Full-Speed BIST Enable. - * @hsbist: High-Speed BIST Enable. - * @bist_done: PHY Bist Done. - * Asserted at the end of the PHY BIST sequence. - * @bist_err: PHY Bist Error. - * Indicates an internal error was detected during - * the BIST sequence. - * @tdata_out: PHY Test Data Out. - * Presents either internally generated signals or - * test register contents, based upon the value of - * test_data_out_sel. - * @siddq: Drives the USBP (USB-PHY) SIDDQ input. - * Normally should be set to zero. - * When customers have no intent to use USB PHY - * interface, they should: - * - still provide 3.3V to USB_VDD33, and - * - tie USB_REXT to 3.3V supply, and - * - set USBN*_USBP_CTL_STATUS[SIDDQ]=1 - * @txpreemphasistune: HS Transmitter Pre-Emphasis Enable - * @dma_bmode: When set to 1 the L2C DMA address will be updated - * with byte-counts between packets. When set to 0 - * the L2C DMA address is incremented to the next - * 4-byte aligned address after adding byte-count. - * @usbc_end: Bigendian input to the USB Core. This should be - * set to '0' for operation. - * @usbp_bist: PHY, This is cleared '0' to run BIST on the USBP. - * @tclk: PHY Test Clock, used to load TDATA_IN to the USBP. - * @dp_pulld: PHY DP_PULLDOWN input to the USB-PHY. - * This signal enables the pull-down resistance on - * the D+ line. '1' pull down-resistance is connected - * to D+/ '0' pull down resistance is not connected - * to D+. When an A/B device is acting as a host - * (downstream-facing port), dp_pulldown and - * dm_pulldown are enabled. This must not toggle - * during normal operation. - * @dm_pulld: PHY DM_PULLDOWN input to the USB-PHY. - * This signal enables the pull-down resistance on - * the D- line. '1' pull down-resistance is connected - * to D-. '0' pull down resistance is not connected - * to D-. When an A/B device is acting as a host - * (downstream-facing port), dp_pulldown and - * dm_pulldown are enabled. This must not toggle - * during normal operation. - * @hst_mode: When '0' the USB is acting as HOST, when '1' - * USB is acting as device. This field needs to be - * set while the USB is in reset. - * @tuning: Transmitter Tuning for High-Speed Operation. - * Tunes the current supply and rise/fall output - * times for high-speed operation. - * [20:19] == 11: Current supply increased - * approximately 9% - * [20:19] == 10: Current supply increased - * approximately 4.5% - * [20:19] == 01: Design default. - * [20:19] == 00: Current supply decreased - * approximately 4.5% - * [22:21] == 11: Rise and fall times are increased. - * [22:21] == 10: Design default. - * [22:21] == 01: Rise and fall times are decreased. - * [22:21] == 00: Rise and fall times are decreased - * further as compared to the 01 setting. - * @tx_bs_enh: Transmit Bit Stuffing on [15:8]. - * Enables or disables bit stuffing on data[15:8] - * when bit-stuffing is enabled. - * @tx_bs_en: Transmit Bit Stuffing on [7:0]. - * Enables or disables bit stuffing on data[7:0] - * when bit-stuffing is enabled. - * @loop_enb: PHY Loopback Test Enable. - * '1': During data transmission the receive is - * enabled. - * '0': During data transmission the receive is - * disabled. - * Must be '0' for normal operation. - * @vtest_enb: Analog Test Pin Enable. - * '1' The PHY's analog_test pin is enabled for the - * input and output of applicable analog test signals. - * '0' THe analog_test pin is disabled. - * @bist_enb: Built-In Self Test Enable. - * Used to activate BIST in the PHY. - * @tdata_sel: Test Data Out Select. - * '1' test_data_out[3:0] (PHY) register contents - * are output. '0' internally generated signals are - * output. - * @taddr_in: Mode Address for Test Interface. - * Specifies the register address for writing to or - * reading from the PHY test interface register. - * @tdata_in: Internal Testing Register Input Data and Select - * This is a test bus. Data is present on [3:0], - * and its corresponding select (enable) is present - * on bits [7:4]. - * @ate_reset: Reset input from automatic test equipment. - * This is a test signal. When the USB Core is - * powered up (not in Susned Mode), an automatic - * tester can use this to disable phy_clock and - * free_clk, then re-enable them with an aligned - * phase. - * '1': The phy_clk and free_clk outputs are - * disabled. "0": The phy_clock and free_clk outputs - * are available within a specific period after the - * de-assertion. - */ - struct cvmx_usbnx_usbp_ctl_status_s { - __BITFIELD_FIELD(u64 txrisetune : 1, - __BITFIELD_FIELD(u64 txvreftune : 4, - __BITFIELD_FIELD(u64 txfslstune : 4, - __BITFIELD_FIELD(u64 txhsxvtune : 2, - __BITFIELD_FIELD(u64 sqrxtune : 3, - __BITFIELD_FIELD(u64 compdistune : 3, - __BITFIELD_FIELD(u64 otgtune : 3, - __BITFIELD_FIELD(u64 otgdisable : 1, - __BITFIELD_FIELD(u64 portreset : 1, - __BITFIELD_FIELD(u64 drvvbus : 1, - __BITFIELD_FIELD(u64 lsbist : 1, - __BITFIELD_FIELD(u64 fsbist : 1, - __BITFIELD_FIELD(u64 hsbist : 1, - __BITFIELD_FIELD(u64 bist_done : 1, - __BITFIELD_FIELD(u64 bist_err : 1, - __BITFIELD_FIELD(u64 tdata_out : 4, - __BITFIELD_FIELD(u64 siddq : 1, - __BITFIELD_FIELD(u64 txpreemphasistune : 1, - __BITFIELD_FIELD(u64 dma_bmode : 1, - __BITFIELD_FIELD(u64 usbc_end : 1, - __BITFIELD_FIELD(u64 usbp_bist : 1, - __BITFIELD_FIELD(u64 tclk : 1, - __BITFIELD_FIELD(u64 dp_pulld : 1, - __BITFIELD_FIELD(u64 dm_pulld : 1, - __BITFIELD_FIELD(u64 hst_mode : 1, - __BITFIELD_FIELD(u64 tuning : 4, - __BITFIELD_FIELD(u64 tx_bs_enh : 1, - __BITFIELD_FIELD(u64 tx_bs_en : 1, - __BITFIELD_FIELD(u64 loop_enb : 1, - __BITFIELD_FIELD(u64 vtest_enb : 1, - __BITFIELD_FIELD(u64 bist_enb : 1, - __BITFIELD_FIELD(u64 tdata_sel : 1, - __BITFIELD_FIELD(u64 taddr_in : 4, - __BITFIELD_FIELD(u64 tdata_in : 8, - __BITFIELD_FIELD(u64 ate_reset : 1, - ;))))))))))))))))))))))))))))))))))) - } s; -}; - -#endif /* __OCTEON_HCD_H__ */ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 682b3d2da623..fd9264cf6c87 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -306,6 +306,16 @@ config USB_EHCI_MV Dova, Armada 370 and Armada XP. See "Support for Marvell EBU on-chip EHCI USB controller" for those. +config USB_OCTEON_HCD + tristate "Cavium Networks Octeon USB support" + depends on CAVIUM_OCTEON_SOC && USB + help + This driver supports USB host controller on some Cavium + Networks' products in the Octeon family. + + To compile this driver as a module, choose M here. The module + will be called octeon-hcd. + config USB_CNS3XXX_EHCI bool "Cavium CNS3XXX EHCI Module (DEPRECATED)" depends on ARCH_CNS3XXX || COMPILE_TEST diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 2948983618fb..2c8a61be7e46 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_USB_OHCI_HCD_S3C2410) += ohci-s3c2410.o obj-$(CONFIG_USB_OHCI_HCD_LPC32XX) += ohci-nxp.o obj-$(CONFIG_USB_OHCI_HCD_PXA27X) += ohci-pxa27x.o obj-$(CONFIG_USB_OHCI_HCD_DAVINCI) += ohci-da8xx.o +obj-$(CONFIG_USB_OCTEON_HCD) += octeon-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c new file mode 100644 index 000000000000..a1cd81d4a114 --- /dev/null +++ b/drivers/usb/host/octeon-hcd.c @@ -0,0 +1,3740 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2008 Cavium Networks + * + * Some parts of the code were originally released under BSD license: + * + * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * Neither the name of Cavium Networks nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * This Software, including technical data, may be subject to U.S. export + * control laws, including the U.S. Export Administration Act and its associated + * regulations, and may be subject to export or import regulations in other + * countries. + * + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" + * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR + * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO + * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION + * OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM + * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, + * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF + * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR + * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR + * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "octeon-hcd.h" + +/** + * enum cvmx_usb_speed - the possible USB device speeds + * + * @CVMX_USB_SPEED_HIGH: Device is operation at 480Mbps + * @CVMX_USB_SPEED_FULL: Device is operation at 12Mbps + * @CVMX_USB_SPEED_LOW: Device is operation at 1.5Mbps + */ +enum cvmx_usb_speed { + CVMX_USB_SPEED_HIGH = 0, + CVMX_USB_SPEED_FULL = 1, + CVMX_USB_SPEED_LOW = 2, +}; + +/** + * enum cvmx_usb_transfer - the possible USB transfer types + * + * @CVMX_USB_TRANSFER_CONTROL: USB transfer type control for hub and status + * transfers + * @CVMX_USB_TRANSFER_ISOCHRONOUS: USB transfer type isochronous for low + * priority periodic transfers + * @CVMX_USB_TRANSFER_BULK: USB transfer type bulk for large low priority + * transfers + * @CVMX_USB_TRANSFER_INTERRUPT: USB transfer type interrupt for high priority + * periodic transfers + */ +enum cvmx_usb_transfer { + CVMX_USB_TRANSFER_CONTROL = 0, + CVMX_USB_TRANSFER_ISOCHRONOUS = 1, + CVMX_USB_TRANSFER_BULK = 2, + CVMX_USB_TRANSFER_INTERRUPT = 3, +}; + +/** + * enum cvmx_usb_direction - the transfer directions + * + * @CVMX_USB_DIRECTION_OUT: Data is transferring from Octeon to the device/host + * @CVMX_USB_DIRECTION_IN: Data is transferring from the device/host to Octeon + */ +enum cvmx_usb_direction { + CVMX_USB_DIRECTION_OUT, + CVMX_USB_DIRECTION_IN, +}; + +/** + * enum cvmx_usb_status - possible callback function status codes + * + * @CVMX_USB_STATUS_OK: The transaction / operation finished without + * any errors + * @CVMX_USB_STATUS_SHORT: FIXME: This is currently not implemented + * @CVMX_USB_STATUS_CANCEL: The transaction was canceled while in flight + * by a user call to cvmx_usb_cancel + * @CVMX_USB_STATUS_ERROR: The transaction aborted with an unexpected + * error status + * @CVMX_USB_STATUS_STALL: The transaction received a USB STALL response + * from the device + * @CVMX_USB_STATUS_XACTERR: The transaction failed with an error from the + * device even after a number of retries + * @CVMX_USB_STATUS_DATATGLERR: The transaction failed with a data toggle + * error even after a number of retries + * @CVMX_USB_STATUS_BABBLEERR: The transaction failed with a babble error + * @CVMX_USB_STATUS_FRAMEERR: The transaction failed with a frame error + * even after a number of retries + */ +enum cvmx_usb_status { + CVMX_USB_STATUS_OK, + CVMX_USB_STATUS_SHORT, + CVMX_USB_STATUS_CANCEL, + CVMX_USB_STATUS_ERROR, + CVMX_USB_STATUS_STALL, + CVMX_USB_STATUS_XACTERR, + CVMX_USB_STATUS_DATATGLERR, + CVMX_USB_STATUS_BABBLEERR, + CVMX_USB_STATUS_FRAMEERR, +}; + +/** + * struct cvmx_usb_port_status - the USB port status information + * + * @port_enabled: 1 = Usb port is enabled, 0 = disabled + * @port_over_current: 1 = Over current detected, 0 = Over current not + * detected. Octeon doesn't support over current detection. + * @port_powered: 1 = Port power is being supplied to the device, 0 = + * power is off. Octeon doesn't support turning port power + * off. + * @port_speed: Current port speed. + * @connected: 1 = A device is connected to the port, 0 = No device is + * connected. + * @connect_change: 1 = Device connected state changed since the last set + * status call. + */ +struct cvmx_usb_port_status { + u32 reserved : 25; + u32 port_enabled : 1; + u32 port_over_current : 1; + u32 port_powered : 1; + enum cvmx_usb_speed port_speed : 2; + u32 connected : 1; + u32 connect_change : 1; +}; + +/** + * struct cvmx_usb_iso_packet - descriptor for Isochronous packets + * + * @offset: This is the offset in bytes into the main buffer where this data + * is stored. + * @length: This is the length in bytes of the data. + * @status: This is the status of this individual packet transfer. + */ +struct cvmx_usb_iso_packet { + int offset; + int length; + enum cvmx_usb_status status; +}; + +/** + * enum cvmx_usb_initialize_flags - flags used by the initialization function + * + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI: The USB port uses a 12MHz crystal + * as clock source at USB_XO and + * USB_XI. + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND: The USB port uses 12/24/48MHz 2.5V + * board clock source at USB_XO. + * USB_XI should be tied to GND. + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK: Mask for clock speed field + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: Speed of reference clock or + * crystal + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: Speed of reference clock + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: Speed of reference clock + * @CVMX_USB_INITIALIZE_FLAGS_NO_DMA: Disable DMA and used polled IO for + * data transfer use for the USB + */ +enum cvmx_usb_initialize_flags { + CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI = 1 << 0, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND = 1 << 1, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK = 3 << 3, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ = 1 << 3, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ = 2 << 3, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ = 3 << 3, + /* Bits 3-4 used to encode the clock frequency */ + CVMX_USB_INITIALIZE_FLAGS_NO_DMA = 1 << 5, +}; + +/** + * enum cvmx_usb_pipe_flags - internal flags for a pipe. + * + * @CVMX_USB_PIPE_FLAGS_SCHEDULED: Used internally to determine if a pipe is + * actively using hardware. + * @CVMX_USB_PIPE_FLAGS_NEED_PING: Used internally to determine if a high speed + * pipe is in the ping state. + */ +enum cvmx_usb_pipe_flags { + CVMX_USB_PIPE_FLAGS_SCHEDULED = 1 << 17, + CVMX_USB_PIPE_FLAGS_NEED_PING = 1 << 18, +}; + +/* Maximum number of times to retry failed transactions */ +#define MAX_RETRIES 3 + +/* Maximum number of hardware channels supported by the USB block */ +#define MAX_CHANNELS 8 + +/* + * The low level hardware can transfer a maximum of this number of bytes in each + * transfer. The field is 19 bits wide + */ +#define MAX_TRANSFER_BYTES ((1 << 19) - 1) + +/* + * The low level hardware can transfer a maximum of this number of packets in + * each transfer. The field is 10 bits wide + */ +#define MAX_TRANSFER_PACKETS ((1 << 10) - 1) + +/** + * Logical transactions may take numerous low level + * transactions, especially when splits are concerned. This + * enum represents all of the possible stages a transaction can + * be in. Note that split completes are always even. This is so + * the NAK handler can backup to the previous low level + * transaction with a simple clearing of bit 0. + */ +enum cvmx_usb_stage { + CVMX_USB_STAGE_NON_CONTROL, + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE, + CVMX_USB_STAGE_SETUP, + CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE, + CVMX_USB_STAGE_DATA, + CVMX_USB_STAGE_DATA_SPLIT_COMPLETE, + CVMX_USB_STAGE_STATUS, + CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE, +}; + +/** + * struct cvmx_usb_transaction - describes each pending USB transaction + * regardless of type. These are linked together + * to form a list of pending requests for a pipe. + * + * @node: List node for transactions in the pipe. + * @type: Type of transaction, duplicated of the pipe. + * @flags: State flags for this transaction. + * @buffer: User's physical buffer address to read/write. + * @buffer_length: Size of the user's buffer in bytes. + * @control_header: For control transactions, physical address of the 8 + * byte standard header. + * @iso_start_frame: For ISO transactions, the starting frame number. + * @iso_number_packets: For ISO transactions, the number of packets in the + * request. + * @iso_packets: For ISO transactions, the sub packets in the request. + * @actual_bytes: Actual bytes transfer for this transaction. + * @stage: For control transactions, the current stage. + * @urb: URB. + */ +struct cvmx_usb_transaction { + struct list_head node; + enum cvmx_usb_transfer type; + u64 buffer; + int buffer_length; + u64 control_header; + int iso_start_frame; + int iso_number_packets; + struct cvmx_usb_iso_packet *iso_packets; + int xfersize; + int pktcnt; + int retries; + int actual_bytes; + enum cvmx_usb_stage stage; + struct urb *urb; +}; + +/** + * struct cvmx_usb_pipe - a pipe represents a virtual connection between Octeon + * and some USB device. It contains a list of pending + * request to the device. + * + * @node: List node for pipe list + * @next: Pipe after this one in the list + * @transactions: List of pending transactions + * @interval: For periodic pipes, the interval between packets in + * frames + * @next_tx_frame: The next frame this pipe is allowed to transmit on + * @flags: State flags for this pipe + * @device_speed: Speed of device connected to this pipe + * @transfer_type: Type of transaction supported by this pipe + * @transfer_dir: IN or OUT. Ignored for Control + * @multi_count: Max packet in a row for the device + * @max_packet: The device's maximum packet size in bytes + * @device_addr: USB device address at other end of pipe + * @endpoint_num: USB endpoint number at other end of pipe + * @hub_device_addr: Hub address this device is connected to + * @hub_port: Hub port this device is connected to + * @pid_toggle: This toggles between 0/1 on every packet send to track + * the data pid needed + * @channel: Hardware DMA channel for this pipe + * @split_sc_frame: The low order bits of the frame number the split + * complete should be sent on + */ +struct cvmx_usb_pipe { + struct list_head node; + struct list_head transactions; + u64 interval; + u64 next_tx_frame; + enum cvmx_usb_pipe_flags flags; + enum cvmx_usb_speed device_speed; + enum cvmx_usb_transfer transfer_type; + enum cvmx_usb_direction transfer_dir; + int multi_count; + u16 max_packet; + u8 device_addr; + u8 endpoint_num; + u8 hub_device_addr; + u8 hub_port; + u8 pid_toggle; + u8 channel; + s8 split_sc_frame; +}; + +struct cvmx_usb_tx_fifo { + struct { + int channel; + int size; + u64 address; + } entry[MAX_CHANNELS + 1]; + int head; + int tail; +}; + +/** + * struct octeon_hcd - the state of the USB block + * + * lock: Serialization lock. + * init_flags: Flags passed to initialize. + * index: Which USB block this is for. + * idle_hardware_channels: Bit set for every idle hardware channel. + * usbcx_hprt: Stored port status so we don't need to read a CSR to + * determine splits. + * pipe_for_channel: Map channels to pipes. + * pipe: Storage for pipes. + * indent: Used by debug output to indent functions. + * port_status: Last port status used for change notification. + * idle_pipes: List of open pipes that have no transactions. + * active_pipes: Active pipes indexed by transfer type. + * frame_number: Increments every SOF interrupt for time keeping. + * active_split: Points to the current active split, or NULL. + */ +struct octeon_hcd { + spinlock_t lock; /* serialization lock */ + int init_flags; + int index; + int idle_hardware_channels; + union cvmx_usbcx_hprt usbcx_hprt; + struct cvmx_usb_pipe *pipe_for_channel[MAX_CHANNELS]; + int indent; + struct cvmx_usb_port_status port_status; + struct list_head idle_pipes; + struct list_head active_pipes[4]; + u64 frame_number; + struct cvmx_usb_transaction *active_split; + struct cvmx_usb_tx_fifo periodic; + struct cvmx_usb_tx_fifo nonperiodic; +}; + +/* + * This macro logically sets a single field in a CSR. It does the sequence + * read, modify, and write + */ +#define USB_SET_FIELD32(address, _union, field, value) \ + do { \ + union _union c; \ + \ + c.u32 = cvmx_usb_read_csr32(usb, address); \ + c.s.field = value; \ + cvmx_usb_write_csr32(usb, address, c.u32); \ + } while (0) + +/* Returns the IO address to push/pop stuff data from the FIFOs */ +#define USB_FIFO_ADDRESS(channel, usb_index) \ + (CVMX_USBCX_GOTGCTL(usb_index) + ((channel) + 1) * 0x1000) + +/** + * struct octeon_temp_buffer - a bounce buffer for USB transfers + * @orig_buffer: the original buffer passed by the USB stack + * @data: the newly allocated temporary buffer (excluding meta-data) + * + * Both the DMA engine and FIFO mode will always transfer full 32-bit words. If + * the buffer is too short, we need to allocate a temporary one, and this struct + * represents it. + */ +struct octeon_temp_buffer { + void *orig_buffer; + u8 data[]; +}; + +static inline struct usb_hcd *octeon_to_hcd(struct octeon_hcd *p) +{ + return container_of((void *)p, struct usb_hcd, hcd_priv); +} + +/** + * octeon_alloc_temp_buffer - allocate a temporary buffer for USB transfer + * (if needed) + * @urb: URB. + * @mem_flags: Memory allocation flags. + * + * This function allocates a temporary bounce buffer whenever it's needed + * due to HW limitations. + */ +static int octeon_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) +{ + struct octeon_temp_buffer *temp; + + if (urb->num_sgs || urb->sg || + (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || + !(urb->transfer_buffer_length % sizeof(u32))) + return 0; + + temp = kmalloc(ALIGN(urb->transfer_buffer_length, sizeof(u32)) + + sizeof(*temp), mem_flags); + if (!temp) + return -ENOMEM; + + temp->orig_buffer = urb->transfer_buffer; + if (usb_urb_dir_out(urb)) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = temp->data; + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +/** + * octeon_free_temp_buffer - free a temporary buffer used by USB transfers. + * @urb: URB. + * + * Frees a buffer allocated by octeon_alloc_temp_buffer(). + */ +static void octeon_free_temp_buffer(struct urb *urb) +{ + struct octeon_temp_buffer *temp; + size_t length; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + temp = container_of(urb->transfer_buffer, struct octeon_temp_buffer, + data); + if (usb_urb_dir_in(urb)) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(temp->orig_buffer, urb->transfer_buffer, length); + } + urb->transfer_buffer = temp->orig_buffer; + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; + kfree(temp); +} + +/** + * octeon_map_urb_for_dma - Octeon-specific map_urb_for_dma(). + * @hcd: USB HCD structure. + * @urb: URB. + * @mem_flags: Memory allocation flags. + */ +static int octeon_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + ret = octeon_alloc_temp_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + octeon_free_temp_buffer(urb); + + return ret; +} + +/** + * octeon_unmap_urb_for_dma - Octeon-specific unmap_urb_for_dma() + * @hcd: USB HCD structure. + * @urb: URB. + */ +static void octeon_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + octeon_free_temp_buffer(urb); +} + +/** + * Read a USB 32bit CSR. It performs the necessary address swizzle + * for 32bit CSRs and logs the value in a readable format if + * debugging is on. + * + * @usb: USB block this access is for + * @address: 64bit address to read + * + * Returns: Result of the read + */ +static inline u32 cvmx_usb_read_csr32(struct octeon_hcd *usb, u64 address) +{ + return cvmx_read64_uint32(address ^ 4); +} + +/** + * Write a USB 32bit CSR. It performs the necessary address + * swizzle for 32bit CSRs and logs the value in a readable format + * if debugging is on. + * + * @usb: USB block this access is for + * @address: 64bit address to write + * @value: Value to write + */ +static inline void cvmx_usb_write_csr32(struct octeon_hcd *usb, + u64 address, u32 value) +{ + cvmx_write64_uint32(address ^ 4, value); + cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); +} + +/** + * Return non zero if this pipe connects to a non HIGH speed + * device through a high speed hub. + * + * @usb: USB block this access is for + * @pipe: Pipe to check + * + * Returns: Non zero if we need to do split transactions + */ +static inline int cvmx_usb_pipe_needs_split(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe) +{ + return pipe->device_speed != CVMX_USB_SPEED_HIGH && + usb->usbcx_hprt.s.prtspd == CVMX_USB_SPEED_HIGH; +} + +/** + * Trivial utility function to return the correct PID for a pipe + * + * @pipe: pipe to check + * + * Returns: PID for pipe + */ +static inline int cvmx_usb_get_data_pid(struct cvmx_usb_pipe *pipe) +{ + if (pipe->pid_toggle) + return 2; /* Data1 */ + return 0; /* Data0 */ +} + +/* Loops through register until txfflsh or rxfflsh become zero.*/ +static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) +{ + int result; + u64 address = CVMX_USBCX_GRSTCTL(usb->index); + u64 done = cvmx_get_cycle() + 100 * + (u64)octeon_get_clock_rate / 1000000; + union cvmx_usbcx_grstctl c; + + while (1) { + c.u32 = cvmx_usb_read_csr32(usb, address); + if (fflsh_type == 0 && c.s.txfflsh == 0) { + result = 0; + break; + } else if (fflsh_type == 1 && c.s.rxfflsh == 0) { + result = 0; + break; + } else if (cvmx_get_cycle() > done) { + result = -1; + break; + } + + __delay(100); + } + return result; +} + +static void cvmx_fifo_setup(struct octeon_hcd *usb) +{ + union cvmx_usbcx_ghwcfg3 usbcx_ghwcfg3; + union cvmx_usbcx_gnptxfsiz npsiz; + union cvmx_usbcx_hptxfsiz psiz; + + usbcx_ghwcfg3.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GHWCFG3(usb->index)); + + /* + * Program the USBC_GRXFSIZ register to select the size of the receive + * FIFO (25%). + */ + USB_SET_FIELD32(CVMX_USBCX_GRXFSIZ(usb->index), cvmx_usbcx_grxfsiz, + rxfdep, usbcx_ghwcfg3.s.dfifodepth / 4); + + /* + * Program the USBC_GNPTXFSIZ register to select the size and the start + * address of the non-periodic transmit FIFO for nonperiodic + * transactions (50%). + */ + npsiz.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index)); + npsiz.s.nptxfdep = usbcx_ghwcfg3.s.dfifodepth / 2; + npsiz.s.nptxfstaddr = usbcx_ghwcfg3.s.dfifodepth / 4; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index), npsiz.u32); + + /* + * Program the USBC_HPTXFSIZ register to select the size and start + * address of the periodic transmit FIFO for periodic transactions + * (25%). + */ + psiz.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index)); + psiz.s.ptxfsize = usbcx_ghwcfg3.s.dfifodepth / 4; + psiz.s.ptxfstaddr = 3 * usbcx_ghwcfg3.s.dfifodepth / 4; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index), psiz.u32); + + /* Flush all FIFOs */ + USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), + cvmx_usbcx_grstctl, txfnum, 0x10); + USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), + cvmx_usbcx_grstctl, txfflsh, 1); + cvmx_wait_tx_rx(usb, 0); + USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), + cvmx_usbcx_grstctl, rxfflsh, 1); + cvmx_wait_tx_rx(usb, 1); +} + +/** + * Shutdown a USB port after a call to cvmx_usb_initialize(). + * The port should be disabled with all pipes closed when this + * function is called. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_shutdown(struct octeon_hcd *usb) +{ + union cvmx_usbnx_clk_ctl usbn_clk_ctl; + + /* Make sure all pipes are closed */ + if (!list_empty(&usb->idle_pipes) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_ISOCHRONOUS]) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_INTERRUPT]) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_CONTROL]) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_BULK])) + return -EBUSY; + + /* Disable the clocks and put them in power on reset */ + usbn_clk_ctl.u64 = cvmx_read64_uint64(CVMX_USBNX_CLK_CTL(usb->index)); + usbn_clk_ctl.s.enable = 1; + usbn_clk_ctl.s.por = 1; + usbn_clk_ctl.s.hclk_rst = 1; + usbn_clk_ctl.s.prst = 0; + usbn_clk_ctl.s.hrst = 0; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + return 0; +} + +/** + * Initialize a USB port for use. This must be called before any + * other access to the Octeon USB port is made. The port starts + * off in the disabled state. + * + * @dev: Pointer to struct device for logging purposes. + * @usb: Pointer to struct octeon_hcd. + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_initialize(struct device *dev, + struct octeon_hcd *usb) +{ + int channel; + int divisor; + int retries = 0; + union cvmx_usbcx_hcfg usbcx_hcfg; + union cvmx_usbnx_clk_ctl usbn_clk_ctl; + union cvmx_usbcx_gintsts usbc_gintsts; + union cvmx_usbcx_gahbcfg usbcx_gahbcfg; + union cvmx_usbcx_gintmsk usbcx_gintmsk; + union cvmx_usbcx_gusbcfg usbcx_gusbcfg; + union cvmx_usbnx_usbp_ctl_status usbn_usbp_ctl_status; + +retry: + /* + * Power On Reset and PHY Initialization + * + * 1. Wait for DCOK to assert (nothing to do) + * + * 2a. Write USBN0/1_CLK_CTL[POR] = 1 and + * USBN0/1_CLK_CTL[HRST,PRST,HCLK_RST] = 0 + */ + usbn_clk_ctl.u64 = cvmx_read64_uint64(CVMX_USBNX_CLK_CTL(usb->index)); + usbn_clk_ctl.s.por = 1; + usbn_clk_ctl.s.hrst = 0; + usbn_clk_ctl.s.prst = 0; + usbn_clk_ctl.s.hclk_rst = 0; + usbn_clk_ctl.s.enable = 0; + /* + * 2b. Select the USB reference clock/crystal parameters by writing + * appropriate values to USBN0/1_CLK_CTL[P_C_SEL, P_RTYPE, P_COM_ON] + */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND) { + /* + * The USB port uses 12/24/48MHz 2.5V board clock + * source at USB_XO. USB_XI should be tied to GND. + * Most Octeon evaluation boards require this setting + */ + if (OCTEON_IS_MODEL(OCTEON_CN3XXX) || + OCTEON_IS_MODEL(OCTEON_CN56XX) || + OCTEON_IS_MODEL(OCTEON_CN50XX)) + /* From CN56XX,CN50XX,CN31XX,CN30XX manuals */ + usbn_clk_ctl.s.p_rtype = 2; /* p_rclk=1 & p_xenbn=0 */ + else + /* From CN52XX manual */ + usbn_clk_ctl.s.p_rtype = 1; + + switch (usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK) { + case CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: + usbn_clk_ctl.s.p_c_sel = 0; + break; + case CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: + usbn_clk_ctl.s.p_c_sel = 1; + break; + case CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: + usbn_clk_ctl.s.p_c_sel = 2; + break; + } + } else { + /* + * The USB port uses a 12MHz crystal as clock source + * at USB_XO and USB_XI + */ + if (OCTEON_IS_MODEL(OCTEON_CN3XXX)) + /* From CN31XX,CN30XX manual */ + usbn_clk_ctl.s.p_rtype = 3; /* p_rclk=1 & p_xenbn=1 */ + else + /* From CN56XX,CN52XX,CN50XX manuals. */ + usbn_clk_ctl.s.p_rtype = 0; + + usbn_clk_ctl.s.p_c_sel = 0; + } + /* + * 2c. Select the HCLK via writing USBN0/1_CLK_CTL[DIVIDE, DIVIDE2] and + * setting USBN0/1_CLK_CTL[ENABLE] = 1. Divide the core clock down + * such that USB is as close as possible to 125Mhz + */ + divisor = DIV_ROUND_UP(octeon_get_clock_rate(), 125000000); + /* Lower than 4 doesn't seem to work properly */ + if (divisor < 4) + divisor = 4; + usbn_clk_ctl.s.divide = divisor; + usbn_clk_ctl.s.divide2 = 0; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + + /* 2d. Write USBN0/1_CLK_CTL[HCLK_RST] = 1 */ + usbn_clk_ctl.s.hclk_rst = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* 2e. Wait 64 core-clock cycles for HCLK to stabilize */ + __delay(64); + /* + * 3. Program the power-on reset field in the USBN clock-control + * register: + * USBN_CLK_CTL[POR] = 0 + */ + usbn_clk_ctl.s.por = 0; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* 4. Wait 1 ms for PHY clock to start */ + mdelay(1); + /* + * 5. Program the Reset input from automatic test equipment field in the + * USBP control and status register: + * USBN_USBP_CTL_STATUS[ATE_RESET] = 1 + */ + usbn_usbp_ctl_status.u64 = + cvmx_read64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index)); + usbn_usbp_ctl_status.s.ate_reset = 1; + cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), + usbn_usbp_ctl_status.u64); + /* 6. Wait 10 cycles */ + __delay(10); + /* + * 7. Clear ATE_RESET field in the USBN clock-control register: + * USBN_USBP_CTL_STATUS[ATE_RESET] = 0 + */ + usbn_usbp_ctl_status.s.ate_reset = 0; + cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), + usbn_usbp_ctl_status.u64); + /* + * 8. Program the PHY reset field in the USBN clock-control register: + * USBN_CLK_CTL[PRST] = 1 + */ + usbn_clk_ctl.s.prst = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* + * 9. Program the USBP control and status register to select host or + * device mode. USBN_USBP_CTL_STATUS[HST_MODE] = 0 for host, = 1 for + * device + */ + usbn_usbp_ctl_status.s.hst_mode = 0; + cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), + usbn_usbp_ctl_status.u64); + /* 10. Wait 1 us */ + udelay(1); + /* + * 11. Program the hreset_n field in the USBN clock-control register: + * USBN_CLK_CTL[HRST] = 1 + */ + usbn_clk_ctl.s.hrst = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* 12. Proceed to USB core initialization */ + usbn_clk_ctl.s.enable = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + udelay(1); + + /* + * USB Core Initialization + * + * 1. Read USBC_GHWCFG1, USBC_GHWCFG2, USBC_GHWCFG3, USBC_GHWCFG4 to + * determine USB core configuration parameters. + * + * Nothing needed + * + * 2. Program the following fields in the global AHB configuration + * register (USBC_GAHBCFG) + * DMA mode, USBC_GAHBCFG[DMAEn]: 1 = DMA mode, 0 = slave mode + * Burst length, USBC_GAHBCFG[HBSTLEN] = 0 + * Nonperiodic TxFIFO empty level (slave mode only), + * USBC_GAHBCFG[NPTXFEMPLVL] + * Periodic TxFIFO empty level (slave mode only), + * USBC_GAHBCFG[PTXFEMPLVL] + * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 + */ + usbcx_gahbcfg.u32 = 0; + usbcx_gahbcfg.s.dmaen = !(usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA); + usbcx_gahbcfg.s.hbstlen = 0; + usbcx_gahbcfg.s.nptxfemplvl = 1; + usbcx_gahbcfg.s.ptxfemplvl = 1; + usbcx_gahbcfg.s.glblintrmsk = 1; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GAHBCFG(usb->index), + usbcx_gahbcfg.u32); + + /* + * 3. Program the following fields in USBC_GUSBCFG register. + * HS/FS timeout calibration, USBC_GUSBCFG[TOUTCAL] = 0 + * ULPI DDR select, USBC_GUSBCFG[DDRSEL] = 0 + * USB turnaround time, USBC_GUSBCFG[USBTRDTIM] = 0x5 + * PHY low-power clock select, USBC_GUSBCFG[PHYLPWRCLKSEL] = 0 + */ + usbcx_gusbcfg.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GUSBCFG(usb->index)); + usbcx_gusbcfg.s.toutcal = 0; + usbcx_gusbcfg.s.ddrsel = 0; + usbcx_gusbcfg.s.usbtrdtim = 0x5; + usbcx_gusbcfg.s.phylpwrclksel = 0; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GUSBCFG(usb->index), + usbcx_gusbcfg.u32); + + /* + * 4. The software must unmask the following bits in the USBC_GINTMSK + * register. + * OTG interrupt mask, USBC_GINTMSK[OTGINTMSK] = 1 + * Mode mismatch interrupt mask, USBC_GINTMSK[MODEMISMSK] = 1 + */ + usbcx_gintmsk.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GINTMSK(usb->index)); + usbcx_gintmsk.s.otgintmsk = 1; + usbcx_gintmsk.s.modemismsk = 1; + usbcx_gintmsk.s.hchintmsk = 1; + usbcx_gintmsk.s.sofmsk = 0; + /* We need RX FIFO interrupts if we don't have DMA */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + usbcx_gintmsk.s.rxflvlmsk = 1; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTMSK(usb->index), + usbcx_gintmsk.u32); + + /* + * Disable all channel interrupts. We'll enable them per channel later. + */ + for (channel = 0; channel < 8; channel++) + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTMSKX(channel, usb->index), + 0); + + /* + * Host Port Initialization + * + * 1. Program the host-port interrupt-mask field to unmask, + * USBC_GINTMSK[PRTINT] = 1 + */ + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, prtintmsk, 1); + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, disconnintmsk, 1); + + /* + * 2. Program the USBC_HCFG register to select full-speed host + * or high-speed host. + */ + usbcx_hcfg.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HCFG(usb->index)); + usbcx_hcfg.s.fslssupp = 0; + usbcx_hcfg.s.fslspclksel = 0; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HCFG(usb->index), usbcx_hcfg.u32); + + cvmx_fifo_setup(usb); + + /* + * If the controller is getting port events right after the reset, it + * means the initialization failed. Try resetting the controller again + * in such case. This is seen to happen after cold boot on DSR-1000N. + */ + usbc_gintsts.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GINTSTS(usb->index)); + cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), + usbc_gintsts.u32); + dev_dbg(dev, "gintsts after reset: 0x%x\n", (int)usbc_gintsts.u32); + if (!usbc_gintsts.s.disconnint && !usbc_gintsts.s.prtint) + return 0; + if (retries++ >= 5) + return -EAGAIN; + dev_info(dev, "controller reset failed (gintsts=0x%x) - retrying\n", + (int)usbc_gintsts.u32); + msleep(50); + cvmx_usb_shutdown(usb); + msleep(50); + goto retry; +} + +/** + * Reset a USB port. After this call succeeds, the USB port is + * online and servicing requests. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + */ +static void cvmx_usb_reset_port(struct octeon_hcd *usb) +{ + usb->usbcx_hprt.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HPRT(usb->index)); + + /* Program the port reset bit to start the reset process */ + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, + prtrst, 1); + + /* + * Wait at least 50ms (high speed), or 10ms (full speed) for the reset + * process to complete. + */ + mdelay(50); + + /* Program the port reset bit to 0, USBC_HPRT[PRTRST] = 0 */ + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, + prtrst, 0); + + /* + * Read the port speed field to get the enumerated speed, + * USBC_HPRT[PRTSPD]. + */ + usb->usbcx_hprt.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HPRT(usb->index)); +} + +/** + * Disable a USB port. After this call the USB port will not + * generate data transfers and will not generate events. + * Transactions in process will fail and call their + * associated callbacks. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_disable(struct octeon_hcd *usb) +{ + /* Disable the port */ + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, + prtena, 1); + return 0; +} + +/** + * Get the current state of the USB port. Use this call to + * determine if the usb port has anything connected, is enabled, + * or has some sort of error condition. The return value of this + * call has "changed" bits to signal of the value of some fields + * have changed between calls. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: Port status information + */ +static struct cvmx_usb_port_status cvmx_usb_get_status(struct octeon_hcd *usb) +{ + union cvmx_usbcx_hprt usbc_hprt; + struct cvmx_usb_port_status result; + + memset(&result, 0, sizeof(result)); + + usbc_hprt.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); + result.port_enabled = usbc_hprt.s.prtena; + result.port_over_current = usbc_hprt.s.prtovrcurract; + result.port_powered = usbc_hprt.s.prtpwr; + result.port_speed = usbc_hprt.s.prtspd; + result.connected = usbc_hprt.s.prtconnsts; + result.connect_change = + result.connected != usb->port_status.connected; + + return result; +} + +/** + * Open a virtual pipe between the host and a USB device. A pipe + * must be opened before data can be transferred between a device + * and Octeon. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @device_addr: + * USB device address to open the pipe to + * (0-127). + * @endpoint_num: + * USB endpoint number to open the pipe to + * (0-15). + * @device_speed: + * The speed of the device the pipe is going + * to. This must match the device's speed, + * which may be different than the port speed. + * @max_packet: The maximum packet length the device can + * transmit/receive (low speed=0-8, full + * speed=0-1023, high speed=0-1024). This value + * comes from the standard endpoint descriptor + * field wMaxPacketSize bits <10:0>. + * @transfer_type: + * The type of transfer this pipe is for. + * @transfer_dir: + * The direction the pipe is in. This is not + * used for control pipes. + * @interval: For ISOCHRONOUS and INTERRUPT transfers, + * this is how often the transfer is scheduled + * for. All other transfers should specify + * zero. The units are in frames (8000/sec at + * high speed, 1000/sec for full speed). + * @multi_count: + * For high speed devices, this is the maximum + * allowed number of packet per microframe. + * Specify zero for non high speed devices. This + * value comes from the standard endpoint descriptor + * field wMaxPacketSize bits <12:11>. + * @hub_device_addr: + * Hub device address this device is connected + * to. Devices connected directly to Octeon + * use zero. This is only used when the device + * is full/low speed behind a high speed hub. + * The address will be of the high speed hub, + * not and full speed hubs after it. + * @hub_port: Which port on the hub the device is + * connected. Use zero for devices connected + * directly to Octeon. Like hub_device_addr, + * this is only used for full/low speed + * devices behind a high speed hub. + * + * Returns: A non-NULL value is a pipe. NULL means an error. + */ +static struct cvmx_usb_pipe *cvmx_usb_open_pipe(struct octeon_hcd *usb, + int device_addr, + int endpoint_num, + enum cvmx_usb_speed + device_speed, + int max_packet, + enum cvmx_usb_transfer + transfer_type, + enum cvmx_usb_direction + transfer_dir, + int interval, int multi_count, + int hub_device_addr, + int hub_port) +{ + struct cvmx_usb_pipe *pipe; + + pipe = kzalloc(sizeof(*pipe), GFP_ATOMIC); + if (!pipe) + return NULL; + if ((device_speed == CVMX_USB_SPEED_HIGH) && + (transfer_dir == CVMX_USB_DIRECTION_OUT) && + (transfer_type == CVMX_USB_TRANSFER_BULK)) + pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; + pipe->device_addr = device_addr; + pipe->endpoint_num = endpoint_num; + pipe->device_speed = device_speed; + pipe->max_packet = max_packet; + pipe->transfer_type = transfer_type; + pipe->transfer_dir = transfer_dir; + INIT_LIST_HEAD(&pipe->transactions); + + /* + * All pipes use interval to rate limit NAK processing. Force an + * interval if one wasn't supplied + */ + if (!interval) + interval = 1; + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + pipe->interval = interval * 8; + /* Force start splits to be schedule on uFrame 0 */ + pipe->next_tx_frame = ((usb->frame_number + 7) & ~7) + + pipe->interval; + } else { + pipe->interval = interval; + pipe->next_tx_frame = usb->frame_number + pipe->interval; + } + pipe->multi_count = multi_count; + pipe->hub_device_addr = hub_device_addr; + pipe->hub_port = hub_port; + pipe->pid_toggle = 0; + pipe->split_sc_frame = -1; + list_add_tail(&pipe->node, &usb->idle_pipes); + + /* + * We don't need to tell the hardware about this pipe yet since + * it doesn't have any submitted requests + */ + + return pipe; +} + +/** + * Poll the RX FIFOs and remove data as needed. This function is only used + * in non DMA mode. It is very important that this function be called quickly + * enough to prevent FIFO overflow. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + */ +static void cvmx_usb_poll_rx_fifo(struct octeon_hcd *usb) +{ + union cvmx_usbcx_grxstsph rx_status; + int channel; + int bytes; + u64 address; + u32 *ptr; + + rx_status.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GRXSTSPH(usb->index)); + /* Only read data if IN data is there */ + if (rx_status.s.pktsts != 2) + return; + /* Check if no data is available */ + if (!rx_status.s.bcnt) + return; + + channel = rx_status.s.chnum; + bytes = rx_status.s.bcnt; + if (!bytes) + return; + + /* Get where the DMA engine would have written this data */ + address = cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index) + + channel * 8); + + ptr = cvmx_phys_to_ptr(address); + cvmx_write64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index) + channel * 8, + address + bytes); + + /* Loop writing the FIFO data for this packet into memory */ + while (bytes > 0) { + *ptr++ = cvmx_usb_read_csr32(usb, + USB_FIFO_ADDRESS(channel, usb->index)); + bytes -= 4; + } + CVMX_SYNCW; +} + +/** + * Fill the TX hardware fifo with data out of the software + * fifos + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @fifo: Software fifo to use + * @available: Amount of space in the hardware fifo + * + * Returns: Non zero if the hardware fifo was too small and needs + * to be serviced again. + */ +static int cvmx_usb_fill_tx_hw(struct octeon_hcd *usb, + struct cvmx_usb_tx_fifo *fifo, int available) +{ + /* + * We're done either when there isn't anymore space or the software FIFO + * is empty + */ + while (available && (fifo->head != fifo->tail)) { + int i = fifo->tail; + const u32 *ptr = cvmx_phys_to_ptr(fifo->entry[i].address); + u64 csr_address = USB_FIFO_ADDRESS(fifo->entry[i].channel, + usb->index) ^ 4; + int words = available; + + /* Limit the amount of data to what the SW fifo has */ + if (fifo->entry[i].size <= available) { + words = fifo->entry[i].size; + fifo->tail++; + if (fifo->tail > MAX_CHANNELS) + fifo->tail = 0; + } + + /* Update the next locations and counts */ + available -= words; + fifo->entry[i].address += words * 4; + fifo->entry[i].size -= words; + + /* + * Write the HW fifo data. The read every three writes is due + * to an errata on CN3XXX chips + */ + while (words > 3) { + cvmx_write64_uint32(csr_address, *ptr++); + cvmx_write64_uint32(csr_address, *ptr++); + cvmx_write64_uint32(csr_address, *ptr++); + cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); + words -= 3; + } + cvmx_write64_uint32(csr_address, *ptr++); + if (--words) { + cvmx_write64_uint32(csr_address, *ptr++); + if (--words) + cvmx_write64_uint32(csr_address, *ptr++); + } + cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); + } + return fifo->head != fifo->tail; +} + +/** + * Check the hardware FIFOs and fill them as needed + * + * @usb: USB device state populated by cvmx_usb_initialize(). + */ +static void cvmx_usb_poll_tx_fifo(struct octeon_hcd *usb) +{ + if (usb->periodic.head != usb->periodic.tail) { + union cvmx_usbcx_hptxsts tx_status; + + tx_status.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HPTXSTS(usb->index)); + if (cvmx_usb_fill_tx_hw(usb, &usb->periodic, + tx_status.s.ptxfspcavail)) + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, ptxfempmsk, 1); + else + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, ptxfempmsk, 0); + } + + if (usb->nonperiodic.head != usb->nonperiodic.tail) { + union cvmx_usbcx_gnptxsts tx_status; + + tx_status.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GNPTXSTS(usb->index)); + if (cvmx_usb_fill_tx_hw(usb, &usb->nonperiodic, + tx_status.s.nptxfspcavail)) + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, nptxfempmsk, 1); + else + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, nptxfempmsk, 0); + } +} + +/** + * Fill the TX FIFO with an outgoing packet + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @channel: Channel number to get packet from + */ +static void cvmx_usb_fill_tx_fifo(struct octeon_hcd *usb, int channel) +{ + union cvmx_usbcx_hccharx hcchar; + union cvmx_usbcx_hcspltx usbc_hcsplt; + union cvmx_usbcx_hctsizx usbc_hctsiz; + struct cvmx_usb_tx_fifo *fifo; + + /* We only need to fill data on outbound channels */ + hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index)); + if (hcchar.s.epdir != CVMX_USB_DIRECTION_OUT) + return; + + /* OUT Splits only have data on the start and not the complete */ + usbc_hcsplt.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCSPLTX(channel, usb->index)); + if (usbc_hcsplt.s.spltena && usbc_hcsplt.s.compsplt) + return; + + /* + * Find out how many bytes we need to fill and convert it into 32bit + * words. + */ + usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index)); + if (!usbc_hctsiz.s.xfersize) + return; + + if ((hcchar.s.eptype == CVMX_USB_TRANSFER_INTERRUPT) || + (hcchar.s.eptype == CVMX_USB_TRANSFER_ISOCHRONOUS)) + fifo = &usb->periodic; + else + fifo = &usb->nonperiodic; + + fifo->entry[fifo->head].channel = channel; + fifo->entry[fifo->head].address = + cvmx_read64_uint64(CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + + channel * 8); + fifo->entry[fifo->head].size = (usbc_hctsiz.s.xfersize + 3) >> 2; + fifo->head++; + if (fifo->head > MAX_CHANNELS) + fifo->head = 0; + + cvmx_usb_poll_tx_fifo(usb); +} + +/** + * Perform channel specific setup for Control transactions. All + * the generic stuff will already have been done in cvmx_usb_start_channel(). + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @channel: Channel to setup + * @pipe: Pipe for control transaction + */ +static void cvmx_usb_start_channel_control(struct octeon_hcd *usb, + int channel, + struct cvmx_usb_pipe *pipe) +{ + struct usb_hcd *hcd = octeon_to_hcd(usb); + struct device *dev = hcd->self.controller; + struct cvmx_usb_transaction *transaction = + list_first_entry(&pipe->transactions, typeof(*transaction), + node); + struct usb_ctrlrequest *header = + cvmx_phys_to_ptr(transaction->control_header); + int bytes_to_transfer = transaction->buffer_length - + transaction->actual_bytes; + int packets_to_transfer; + union cvmx_usbcx_hctsizx usbc_hctsiz; + + usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index)); + + switch (transaction->stage) { + case CVMX_USB_STAGE_NON_CONTROL: + case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: + dev_err(dev, "%s: ERROR - Non control stage\n", __func__); + break; + case CVMX_USB_STAGE_SETUP: + usbc_hctsiz.s.pid = 3; /* Setup */ + bytes_to_transfer = sizeof(*header); + /* All Control operations start with a setup going OUT */ + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + CVMX_USB_DIRECTION_OUT); + /* + * Setup send the control header instead of the buffer data. The + * buffer data will be used in the next stage + */ + cvmx_write64_uint64(CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + + channel * 8, + transaction->control_header); + break; + case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: + usbc_hctsiz.s.pid = 3; /* Setup */ + bytes_to_transfer = 0; + /* All Control operations start with a setup going OUT */ + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + CVMX_USB_DIRECTION_OUT); + + USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), + cvmx_usbcx_hcspltx, compsplt, 1); + break; + case CVMX_USB_STAGE_DATA: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + if (header->bRequestType & USB_DIR_IN) + bytes_to_transfer = 0; + else if (bytes_to_transfer > pipe->max_packet) + bytes_to_transfer = pipe->max_packet; + } + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT)); + break; + case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + if (!(header->bRequestType & USB_DIR_IN)) + bytes_to_transfer = 0; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT)); + USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), + cvmx_usbcx_hcspltx, compsplt, 1); + break; + case CVMX_USB_STAGE_STATUS: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + bytes_to_transfer = 0; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_OUT : + CVMX_USB_DIRECTION_IN)); + break; + case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + bytes_to_transfer = 0; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_OUT : + CVMX_USB_DIRECTION_IN)); + USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), + cvmx_usbcx_hcspltx, compsplt, 1); + break; + } + + /* + * Make sure the transfer never exceeds the byte limit of the hardware. + * Further bytes will be sent as continued transactions + */ + if (bytes_to_transfer > MAX_TRANSFER_BYTES) { + /* Round MAX_TRANSFER_BYTES to a multiple of out packet size */ + bytes_to_transfer = MAX_TRANSFER_BYTES / pipe->max_packet; + bytes_to_transfer *= pipe->max_packet; + } + + /* + * Calculate the number of packets to transfer. If the length is zero + * we still need to transfer one packet + */ + packets_to_transfer = DIV_ROUND_UP(bytes_to_transfer, + pipe->max_packet); + if (packets_to_transfer == 0) { + packets_to_transfer = 1; + } else if ((packets_to_transfer > 1) && + (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { + /* + * Limit to one packet when not using DMA. Channels must be + * restarted between every packet for IN transactions, so there + * is no reason to do multiple packets in a row + */ + packets_to_transfer = 1; + bytes_to_transfer = packets_to_transfer * pipe->max_packet; + } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { + /* + * Limit the number of packet and data transferred to what the + * hardware can handle + */ + packets_to_transfer = MAX_TRANSFER_PACKETS; + bytes_to_transfer = packets_to_transfer * pipe->max_packet; + } + + usbc_hctsiz.s.xfersize = bytes_to_transfer; + usbc_hctsiz.s.pktcnt = packets_to_transfer; + + cvmx_usb_write_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index), + usbc_hctsiz.u32); +} + +/** + * Start a channel to perform the pipe's head transaction + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @channel: Channel to setup + * @pipe: Pipe to start + */ +static void cvmx_usb_start_channel(struct octeon_hcd *usb, int channel, + struct cvmx_usb_pipe *pipe) +{ + struct cvmx_usb_transaction *transaction = + list_first_entry(&pipe->transactions, typeof(*transaction), + node); + + /* Make sure all writes to the DMA region get flushed */ + CVMX_SYNCW; + + /* Attach the channel to the pipe */ + usb->pipe_for_channel[channel] = pipe; + pipe->channel = channel; + pipe->flags |= CVMX_USB_PIPE_FLAGS_SCHEDULED; + + /* Mark this channel as in use */ + usb->idle_hardware_channels &= ~(1 << channel); + + /* Enable the channel interrupt bits */ + { + union cvmx_usbcx_hcintx usbc_hcint; + union cvmx_usbcx_hcintmskx usbc_hcintmsk; + union cvmx_usbcx_haintmsk usbc_haintmsk; + + /* Clear all channel status bits */ + usbc_hcint.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCINTX(channel, usb->index)); + + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTX(channel, usb->index), + usbc_hcint.u32); + + usbc_hcintmsk.u32 = 0; + usbc_hcintmsk.s.chhltdmsk = 1; + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { + /* + * Channels need these extra interrupts when we aren't + * in DMA mode. + */ + usbc_hcintmsk.s.datatglerrmsk = 1; + usbc_hcintmsk.s.frmovrunmsk = 1; + usbc_hcintmsk.s.bblerrmsk = 1; + usbc_hcintmsk.s.xacterrmsk = 1; + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * Splits don't generate xfercompl, so we need + * ACK and NYET. + */ + usbc_hcintmsk.s.nyetmsk = 1; + usbc_hcintmsk.s.ackmsk = 1; + } + usbc_hcintmsk.s.nakmsk = 1; + usbc_hcintmsk.s.stallmsk = 1; + usbc_hcintmsk.s.xfercomplmsk = 1; + } + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTMSKX(channel, usb->index), + usbc_hcintmsk.u32); + + /* Enable the channel interrupt to propagate */ + usbc_haintmsk.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HAINTMSK(usb->index)); + usbc_haintmsk.s.haintmsk |= 1 << channel; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HAINTMSK(usb->index), + usbc_haintmsk.u32); + } + + /* Setup the location the DMA engine uses. */ + { + u64 reg; + u64 dma_address = transaction->buffer + + transaction->actual_bytes; + + if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) + dma_address = transaction->buffer + + transaction->iso_packets[0].offset + + transaction->actual_bytes; + + if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) + reg = CVMX_USBNX_DMA0_OUTB_CHN0(usb->index); + else + reg = CVMX_USBNX_DMA0_INB_CHN0(usb->index); + cvmx_write64_uint64(reg + channel * 8, dma_address); + } + + /* Setup both the size of the transfer and the SPLIT characteristics */ + { + union cvmx_usbcx_hcspltx usbc_hcsplt = {.u32 = 0}; + union cvmx_usbcx_hctsizx usbc_hctsiz = {.u32 = 0}; + int packets_to_transfer; + int bytes_to_transfer = transaction->buffer_length - + transaction->actual_bytes; + + /* + * ISOCHRONOUS transactions store each individual transfer size + * in the packet structure, not the global buffer_length + */ + if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) + bytes_to_transfer = + transaction->iso_packets[0].length - + transaction->actual_bytes; + + /* + * We need to do split transactions when we are talking to non + * high speed devices that are behind a high speed hub + */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * On the start split phase (stage is even) record the + * frame number we will need to send the split complete. + * We only store the lower two bits since the time ahead + * can only be two frames + */ + if ((transaction->stage & 1) == 0) { + if (transaction->type == CVMX_USB_TRANSFER_BULK) + pipe->split_sc_frame = + (usb->frame_number + 1) & 0x7f; + else + pipe->split_sc_frame = + (usb->frame_number + 2) & 0x7f; + } else { + pipe->split_sc_frame = -1; + } + + usbc_hcsplt.s.spltena = 1; + usbc_hcsplt.s.hubaddr = pipe->hub_device_addr; + usbc_hcsplt.s.prtaddr = pipe->hub_port; + usbc_hcsplt.s.compsplt = (transaction->stage == + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE); + + /* + * SPLIT transactions can only ever transmit one data + * packet so limit the transfer size to the max packet + * size + */ + if (bytes_to_transfer > pipe->max_packet) + bytes_to_transfer = pipe->max_packet; + + /* + * ISOCHRONOUS OUT splits are unique in that they limit + * data transfers to 188 byte chunks representing the + * begin/middle/end of the data or all + */ + if (!usbc_hcsplt.s.compsplt && + (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && + (pipe->transfer_type == + CVMX_USB_TRANSFER_ISOCHRONOUS)) { + /* + * Clear the split complete frame number as + * there isn't going to be a split complete + */ + pipe->split_sc_frame = -1; + /* + * See if we've started this transfer and sent + * data + */ + if (transaction->actual_bytes == 0) { + /* + * Nothing sent yet, this is either a + * begin or the entire payload + */ + if (bytes_to_transfer <= 188) + /* Entire payload in one go */ + usbc_hcsplt.s.xactpos = 3; + else + /* First part of payload */ + usbc_hcsplt.s.xactpos = 2; + } else { + /* + * Continuing the previous data, we must + * either be in the middle or at the end + */ + if (bytes_to_transfer <= 188) + /* End of payload */ + usbc_hcsplt.s.xactpos = 1; + else + /* Middle of payload */ + usbc_hcsplt.s.xactpos = 0; + } + /* + * Again, the transfer size is limited to 188 + * bytes + */ + if (bytes_to_transfer > 188) + bytes_to_transfer = 188; + } + } + + /* + * Make sure the transfer never exceeds the byte limit of the + * hardware. Further bytes will be sent as continued + * transactions + */ + if (bytes_to_transfer > MAX_TRANSFER_BYTES) { + /* + * Round MAX_TRANSFER_BYTES to a multiple of out packet + * size + */ + bytes_to_transfer = MAX_TRANSFER_BYTES / + pipe->max_packet; + bytes_to_transfer *= pipe->max_packet; + } + + /* + * Calculate the number of packets to transfer. If the length is + * zero we still need to transfer one packet + */ + packets_to_transfer = + DIV_ROUND_UP(bytes_to_transfer, pipe->max_packet); + if (packets_to_transfer == 0) { + packets_to_transfer = 1; + } else if ((packets_to_transfer > 1) && + (usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { + /* + * Limit to one packet when not using DMA. Channels must + * be restarted between every packet for IN + * transactions, so there is no reason to do multiple + * packets in a row + */ + packets_to_transfer = 1; + bytes_to_transfer = packets_to_transfer * + pipe->max_packet; + } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { + /* + * Limit the number of packet and data transferred to + * what the hardware can handle + */ + packets_to_transfer = MAX_TRANSFER_PACKETS; + bytes_to_transfer = packets_to_transfer * + pipe->max_packet; + } + + usbc_hctsiz.s.xfersize = bytes_to_transfer; + usbc_hctsiz.s.pktcnt = packets_to_transfer; + + /* Update the DATA0/DATA1 toggle */ + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + /* + * High speed pipes may need a hardware ping before they start + */ + if (pipe->flags & CVMX_USB_PIPE_FLAGS_NEED_PING) + usbc_hctsiz.s.dopng = 1; + + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCSPLTX(channel, usb->index), + usbc_hcsplt.u32); + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index), + usbc_hctsiz.u32); + } + + /* Setup the Host Channel Characteristics Register */ + { + union cvmx_usbcx_hccharx usbc_hcchar = {.u32 = 0}; + + /* + * Set the startframe odd/even properly. This is only used for + * periodic + */ + usbc_hcchar.s.oddfrm = usb->frame_number & 1; + + /* + * Set the number of back to back packets allowed by this + * endpoint. Split transactions interpret "ec" as the number of + * immediate retries of failure. These retries happen too + * quickly, so we disable these entirely for splits + */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) + usbc_hcchar.s.ec = 1; + else if (pipe->multi_count < 1) + usbc_hcchar.s.ec = 1; + else if (pipe->multi_count > 3) + usbc_hcchar.s.ec = 3; + else + usbc_hcchar.s.ec = pipe->multi_count; + + /* Set the rest of the endpoint specific settings */ + usbc_hcchar.s.devaddr = pipe->device_addr; + usbc_hcchar.s.eptype = transaction->type; + usbc_hcchar.s.lspddev = + (pipe->device_speed == CVMX_USB_SPEED_LOW); + usbc_hcchar.s.epdir = pipe->transfer_dir; + usbc_hcchar.s.epnum = pipe->endpoint_num; + usbc_hcchar.s.mps = pipe->max_packet; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index), + usbc_hcchar.u32); + } + + /* Do transaction type specific fixups as needed */ + switch (transaction->type) { + case CVMX_USB_TRANSFER_CONTROL: + cvmx_usb_start_channel_control(usb, channel, pipe); + break; + case CVMX_USB_TRANSFER_BULK: + case CVMX_USB_TRANSFER_INTERRUPT: + break; + case CVMX_USB_TRANSFER_ISOCHRONOUS: + if (!cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * ISO transactions require different PIDs depending on + * direction and how many packets are needed + */ + if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { + if (pipe->multi_count < 2) /* Need DATA0 */ + USB_SET_FIELD32( + CVMX_USBCX_HCTSIZX(channel, + usb->index), + cvmx_usbcx_hctsizx, pid, 0); + else /* Need MDATA */ + USB_SET_FIELD32( + CVMX_USBCX_HCTSIZX(channel, + usb->index), + cvmx_usbcx_hctsizx, pid, 3); + } + } + break; + } + { + union cvmx_usbcx_hctsizx usbc_hctsiz = { .u32 = + cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, + usb->index)) + }; + transaction->xfersize = usbc_hctsiz.s.xfersize; + transaction->pktcnt = usbc_hctsiz.s.pktcnt; + } + /* Remember when we start a split transaction */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) + usb->active_split = transaction; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, chena, 1); + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + cvmx_usb_fill_tx_fifo(usb, channel); +} + +/** + * Find a pipe that is ready to be scheduled to hardware. + * @usb: USB device state populated by cvmx_usb_initialize(). + * @xfer_type: Transfer type + * + * Returns: Pipe or NULL if none are ready + */ +static struct cvmx_usb_pipe *cvmx_usb_find_ready_pipe(struct octeon_hcd *usb, + enum cvmx_usb_transfer xfer_type) +{ + struct list_head *list = usb->active_pipes + xfer_type; + u64 current_frame = usb->frame_number; + struct cvmx_usb_pipe *pipe; + + list_for_each_entry(pipe, list, node) { + struct cvmx_usb_transaction *t = + list_first_entry(&pipe->transactions, typeof(*t), + node); + if (!(pipe->flags & CVMX_USB_PIPE_FLAGS_SCHEDULED) && t && + (pipe->next_tx_frame <= current_frame) && + ((pipe->split_sc_frame == -1) || + ((((int)current_frame - pipe->split_sc_frame) & 0x7f) < + 0x40)) && + (!usb->active_split || (usb->active_split == t))) { + prefetch(t); + return pipe; + } + } + return NULL; +} + +static struct cvmx_usb_pipe *cvmx_usb_next_pipe(struct octeon_hcd *usb, + int is_sof) +{ + struct cvmx_usb_pipe *pipe; + + /* Find a pipe needing service. */ + if (is_sof) { + /* + * Only process periodic pipes on SOF interrupts. This way we + * are sure that the periodic data is sent in the beginning of + * the frame. + */ + pipe = cvmx_usb_find_ready_pipe(usb, + CVMX_USB_TRANSFER_ISOCHRONOUS); + if (pipe) + return pipe; + pipe = cvmx_usb_find_ready_pipe(usb, + CVMX_USB_TRANSFER_INTERRUPT); + if (pipe) + return pipe; + } + pipe = cvmx_usb_find_ready_pipe(usb, CVMX_USB_TRANSFER_CONTROL); + if (pipe) + return pipe; + return cvmx_usb_find_ready_pipe(usb, CVMX_USB_TRANSFER_BULK); +} + +/** + * Called whenever a pipe might need to be scheduled to the + * hardware. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @is_sof: True if this schedule was called on a SOF interrupt. + */ +static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) +{ + int channel; + struct cvmx_usb_pipe *pipe; + int need_sof; + enum cvmx_usb_transfer ttype; + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { + /* + * Without DMA we need to be careful to not schedule something + * at the end of a frame and cause an overrun. + */ + union cvmx_usbcx_hfnum hfnum = { + .u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HFNUM(usb->index)) + }; + + union cvmx_usbcx_hfir hfir = { + .u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HFIR(usb->index)) + }; + + if (hfnum.s.frrem < hfir.s.frint / 4) + goto done; + } + + while (usb->idle_hardware_channels) { + /* Find an idle channel */ + channel = __fls(usb->idle_hardware_channels); + if (unlikely(channel > 7)) + break; + + pipe = cvmx_usb_next_pipe(usb, is_sof); + if (!pipe) + break; + + cvmx_usb_start_channel(usb, channel, pipe); + } + +done: + /* + * Only enable SOF interrupts when we have transactions pending in the + * future that might need to be scheduled + */ + need_sof = 0; + for (ttype = CVMX_USB_TRANSFER_CONTROL; + ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { + list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { + if (pipe->next_tx_frame > usb->frame_number) { + need_sof = 1; + break; + } + } + } + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, sofmsk, need_sof); +} + +static void octeon_usb_urb_complete_callback(struct octeon_hcd *usb, + enum cvmx_usb_status status, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction + *transaction, + int bytes_transferred, + struct urb *urb) +{ + struct usb_hcd *hcd = octeon_to_hcd(usb); + struct device *dev = hcd->self.controller; + + if (likely(status == CVMX_USB_STATUS_OK)) + urb->actual_length = bytes_transferred; + else + urb->actual_length = 0; + + urb->hcpriv = NULL; + + /* For Isochronous transactions we need to update the URB packet status + * list from data in our private copy + */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + /* + * The pointer to the private list is stored in the setup_packet + * field. + */ + struct cvmx_usb_iso_packet *iso_packet = + (struct cvmx_usb_iso_packet *)urb->setup_packet; + /* Recalculate the transfer size by adding up each packet */ + urb->actual_length = 0; + for (i = 0; i < urb->number_of_packets; i++) { + if (iso_packet[i].status == CVMX_USB_STATUS_OK) { + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = + iso_packet[i].length; + urb->actual_length += + urb->iso_frame_desc[i].actual_length; + } else { + dev_dbg(dev, "ISOCHRONOUS packet=%d of %d status=%d pipe=%p transaction=%p size=%d\n", + i, urb->number_of_packets, + iso_packet[i].status, pipe, + transaction, iso_packet[i].length); + urb->iso_frame_desc[i].status = -EREMOTEIO; + } + } + /* Free the private list now that we don't need it anymore */ + kfree(iso_packet); + urb->setup_packet = NULL; + } + + switch (status) { + case CVMX_USB_STATUS_OK: + urb->status = 0; + break; + case CVMX_USB_STATUS_CANCEL: + if (urb->status == 0) + urb->status = -ENOENT; + break; + case CVMX_USB_STATUS_STALL: + dev_dbg(dev, "status=stall pipe=%p transaction=%p size=%d\n", + pipe, transaction, bytes_transferred); + urb->status = -EPIPE; + break; + case CVMX_USB_STATUS_BABBLEERR: + dev_dbg(dev, "status=babble pipe=%p transaction=%p size=%d\n", + pipe, transaction, bytes_transferred); + urb->status = -EPIPE; + break; + case CVMX_USB_STATUS_SHORT: + dev_dbg(dev, "status=short pipe=%p transaction=%p size=%d\n", + pipe, transaction, bytes_transferred); + urb->status = -EREMOTEIO; + break; + case CVMX_USB_STATUS_ERROR: + case CVMX_USB_STATUS_XACTERR: + case CVMX_USB_STATUS_DATATGLERR: + case CVMX_USB_STATUS_FRAMEERR: + dev_dbg(dev, "status=%d pipe=%p transaction=%p size=%d\n", + status, pipe, transaction, bytes_transferred); + urb->status = -EPROTO; + break; + } + usb_hcd_unlink_urb_from_ep(octeon_to_hcd(usb), urb); + spin_unlock(&usb->lock); + usb_hcd_giveback_urb(octeon_to_hcd(usb), urb, urb->status); + spin_lock(&usb->lock); +} + +/** + * Signal the completion of a transaction and free it. The + * transaction will be removed from the pipe transaction list. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe the transaction is on + * @transaction: + * Transaction that completed + * @complete_code: + * Completion code + */ +static void cvmx_usb_complete(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + enum cvmx_usb_status complete_code) +{ + /* If this was a split then clear our split in progress marker */ + if (usb->active_split == transaction) + usb->active_split = NULL; + + /* + * Isochronous transactions need extra processing as they might not be + * done after a single data transfer + */ + if (unlikely(transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS)) { + /* Update the number of bytes transferred in this ISO packet */ + transaction->iso_packets[0].length = transaction->actual_bytes; + transaction->iso_packets[0].status = complete_code; + + /* + * If there are more ISOs pending and we succeeded, schedule the + * next one + */ + if ((transaction->iso_number_packets > 1) && + (complete_code == CVMX_USB_STATUS_OK)) { + /* No bytes transferred for this packet as of yet */ + transaction->actual_bytes = 0; + /* One less ISO waiting to transfer */ + transaction->iso_number_packets--; + /* Increment to the next location in our packet array */ + transaction->iso_packets++; + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + return; + } + } + + /* Remove the transaction from the pipe list */ + list_del(&transaction->node); + if (list_empty(&pipe->transactions)) + list_move_tail(&pipe->node, &usb->idle_pipes); + octeon_usb_urb_complete_callback(usb, complete_code, pipe, + transaction, + transaction->actual_bytes, + transaction->urb); + kfree(transaction); +} + +/** + * Submit a usb transaction to a pipe. Called for all types + * of transactions. + * + * @usb: + * @pipe: Which pipe to submit to. + * @type: Transaction type + * @buffer: User buffer for the transaction + * @buffer_length: + * User buffer's length in bytes + * @control_header: + * For control transactions, the 8 byte standard header + * @iso_start_frame: + * For ISO transactions, the start frame + * @iso_number_packets: + * For ISO, the number of packet in the transaction. + * @iso_packets: + * A description of each ISO packet + * @urb: URB for the callback + * + * Returns: Transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_transaction( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + enum cvmx_usb_transfer type, + u64 buffer, + int buffer_length, + u64 control_header, + int iso_start_frame, + int iso_number_packets, + struct cvmx_usb_iso_packet *iso_packets, + struct urb *urb) +{ + struct cvmx_usb_transaction *transaction; + + if (unlikely(pipe->transfer_type != type)) + return NULL; + + transaction = kzalloc(sizeof(*transaction), GFP_ATOMIC); + if (unlikely(!transaction)) + return NULL; + + transaction->type = type; + transaction->buffer = buffer; + transaction->buffer_length = buffer_length; + transaction->control_header = control_header; + /* FIXME: This is not used, implement it. */ + transaction->iso_start_frame = iso_start_frame; + transaction->iso_number_packets = iso_number_packets; + transaction->iso_packets = iso_packets; + transaction->urb = urb; + if (transaction->type == CVMX_USB_TRANSFER_CONTROL) + transaction->stage = CVMX_USB_STAGE_SETUP; + else + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + + if (!list_empty(&pipe->transactions)) { + list_add_tail(&transaction->node, &pipe->transactions); + } else { + list_add_tail(&transaction->node, &pipe->transactions); + list_move_tail(&pipe->node, + &usb->active_pipes[pipe->transfer_type]); + + /* + * We may need to schedule the pipe if this was the head of the + * pipe. + */ + cvmx_usb_schedule(usb, 0); + } + + return transaction; +} + +/** + * Call to submit a USB Bulk transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_bulk( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + return cvmx_usb_submit_transaction(usb, pipe, CVMX_USB_TRANSFER_BULK, + urb->transfer_dma, + urb->transfer_buffer_length, + 0, /* control_header */ + 0, /* iso_start_frame */ + 0, /* iso_number_packets */ + NULL, /* iso_packets */ + urb); +} + +/** + * Call to submit a USB Interrupt transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB returned when the callback is called. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_interrupt( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + return cvmx_usb_submit_transaction(usb, pipe, + CVMX_USB_TRANSFER_INTERRUPT, + urb->transfer_dma, + urb->transfer_buffer_length, + 0, /* control_header */ + 0, /* iso_start_frame */ + 0, /* iso_number_packets */ + NULL, /* iso_packets */ + urb); +} + +/** + * Call to submit a USB Control transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_control( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + int buffer_length = urb->transfer_buffer_length; + u64 control_header = urb->setup_dma; + struct usb_ctrlrequest *header = cvmx_phys_to_ptr(control_header); + + if ((header->bRequestType & USB_DIR_IN) == 0) + buffer_length = le16_to_cpu(header->wLength); + + return cvmx_usb_submit_transaction(usb, pipe, + CVMX_USB_TRANSFER_CONTROL, + urb->transfer_dma, buffer_length, + control_header, + 0, /* iso_start_frame */ + 0, /* iso_number_packets */ + NULL, /* iso_packets */ + urb); +} + +/** + * Call to submit a USB Isochronous transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB returned when the callback is called. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_isochronous( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + struct cvmx_usb_iso_packet *packets; + + packets = (struct cvmx_usb_iso_packet *)urb->setup_packet; + return cvmx_usb_submit_transaction(usb, pipe, + CVMX_USB_TRANSFER_ISOCHRONOUS, + urb->transfer_dma, + urb->transfer_buffer_length, + 0, /* control_header */ + urb->start_frame, + urb->number_of_packets, + packets, urb); +} + +/** + * Cancel one outstanding request in a pipe. Canceling a request + * can fail if the transaction has already completed before cancel + * is called. Even after a successful cancel call, it may take + * a frame or two for the cvmx_usb_poll() function to call the + * associated callback. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe to cancel requests in. + * @transaction: Transaction to cancel, returned by the submit function. + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_cancel(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction) +{ + /* + * If the transaction is the HEAD of the queue and scheduled. We need to + * treat it special + */ + if (list_first_entry(&pipe->transactions, typeof(*transaction), node) == + transaction && (pipe->flags & CVMX_USB_PIPE_FLAGS_SCHEDULED)) { + union cvmx_usbcx_hccharx usbc_hcchar; + + usb->pipe_for_channel[pipe->channel] = NULL; + pipe->flags &= ~CVMX_USB_PIPE_FLAGS_SCHEDULED; + + CVMX_SYNCW; + + usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(pipe->channel, + usb->index)); + /* + * If the channel isn't enabled then the transaction already + * completed. + */ + if (usbc_hcchar.s.chena) { + usbc_hcchar.s.chdis = 1; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(pipe->channel, + usb->index), + usbc_hcchar.u32); + } + } + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_CANCEL); + return 0; +} + +/** + * Cancel all outstanding requests in a pipe. Logically all this + * does is call cvmx_usb_cancel() in a loop. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe to cancel requests in. + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_cancel_all(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe) +{ + struct cvmx_usb_transaction *transaction, *next; + + /* Simply loop through and attempt to cancel each transaction */ + list_for_each_entry_safe(transaction, next, &pipe->transactions, node) { + int result = cvmx_usb_cancel(usb, pipe, transaction); + + if (unlikely(result != 0)) + return result; + } + return 0; +} + +/** + * Close a pipe created with cvmx_usb_open_pipe(). + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe to close. + * + * Returns: 0 or a negative error code. EBUSY is returned if the pipe has + * outstanding transfers. + */ +static int cvmx_usb_close_pipe(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe) +{ + /* Fail if the pipe has pending transactions */ + if (!list_empty(&pipe->transactions)) + return -EBUSY; + + list_del(&pipe->node); + kfree(pipe); + + return 0; +} + +/** + * Get the current USB protocol level frame number. The frame + * number is always in the range of 0-0x7ff. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: USB frame number + */ +static int cvmx_usb_get_frame_number(struct octeon_hcd *usb) +{ + union cvmx_usbcx_hfnum usbc_hfnum; + + usbc_hfnum.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); + + return usbc_hfnum.s.frnum; +} + +static void cvmx_usb_transfer_control(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + union cvmx_usbcx_hccharx usbc_hcchar, + int buffer_space_left, + int bytes_in_last_packet) +{ + switch (transaction->stage) { + case CVMX_USB_STAGE_NON_CONTROL: + case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: + /* This should be impossible */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_ERROR); + break; + case CVMX_USB_STAGE_SETUP: + pipe->pid_toggle = 1; + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + transaction->stage = + CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE; + } else { + struct usb_ctrlrequest *header = + cvmx_phys_to_ptr(transaction->control_header); + if (header->wLength) + transaction->stage = CVMX_USB_STAGE_DATA; + else + transaction->stage = CVMX_USB_STAGE_STATUS; + } + break; + case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: + { + struct usb_ctrlrequest *header = + cvmx_phys_to_ptr(transaction->control_header); + if (header->wLength) + transaction->stage = CVMX_USB_STAGE_DATA; + else + transaction->stage = CVMX_USB_STAGE_STATUS; + } + break; + case CVMX_USB_STAGE_DATA: + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + transaction->stage = CVMX_USB_STAGE_DATA_SPLIT_COMPLETE; + /* + * For setup OUT data that are splits, + * the hardware doesn't appear to count + * transferred data. Here we manually + * update the data transferred + */ + if (!usbc_hcchar.s.epdir) { + if (buffer_space_left < pipe->max_packet) + transaction->actual_bytes += + buffer_space_left; + else + transaction->actual_bytes += + pipe->max_packet; + } + } else if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->pid_toggle = 1; + transaction->stage = CVMX_USB_STAGE_STATUS; + } + break; + case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: + if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->pid_toggle = 1; + transaction->stage = CVMX_USB_STAGE_STATUS; + } else { + transaction->stage = CVMX_USB_STAGE_DATA; + } + break; + case CVMX_USB_STAGE_STATUS: + if (cvmx_usb_pipe_needs_split(usb, pipe)) + transaction->stage = + CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE; + else + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + break; + case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); + break; + } +} + +static void cvmx_usb_transfer_bulk(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + union cvmx_usbcx_hcintx usbc_hcint, + int buffer_space_left, + int bytes_in_last_packet) +{ + /* + * The only time a bulk transfer isn't complete when it finishes with + * an ACK is during a split transaction. For splits we need to continue + * the transfer if more data is needed. + */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) + transaction->stage = + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; + else if (buffer_space_left && + (bytes_in_last_packet == pipe->max_packet)) + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + else + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } else { + if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && + (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && + (usbc_hcint.s.nak)) + pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; + if (!buffer_space_left || + (bytes_in_last_packet < pipe->max_packet)) + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } +} + +static void cvmx_usb_transfer_intr(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + int buffer_space_left, + int bytes_in_last_packet) +{ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) { + transaction->stage = + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; + } else if (buffer_space_left && + (bytes_in_last_packet == pipe->max_packet)) { + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + } else { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } + } else if (!buffer_space_left || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); + } +} + +static void cvmx_usb_transfer_isoc(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + int buffer_space_left, + int bytes_in_last_packet, + int bytes_this_transfer) +{ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * ISOCHRONOUS OUT splits don't require a complete split stage. + * Instead they use a sequence of begin OUT splits to transfer + * the data 188 bytes at a time. Once the transfer is complete, + * the pipe sleeps until the next schedule interval. + */ + if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { + /* + * If no space left or this wasn't a max size packet + * then this transfer is complete. Otherwise start it + * again to send the next 188 bytes + */ + if (!buffer_space_left || (bytes_this_transfer < 188)) { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } + return; + } + if (transaction->stage == + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE) { + /* + * We are in the incoming data phase. Keep getting data + * until we run out of space or get a small packet + */ + if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } + } else { + transaction->stage = + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; + } + } else { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); + } +} + +/** + * Poll a channel for status + * + * @usb: USB device + * @channel: Channel to poll + * + * Returns: Zero on success + */ +static int cvmx_usb_poll_channel(struct octeon_hcd *usb, int channel) +{ + struct usb_hcd *hcd = octeon_to_hcd(usb); + struct device *dev = hcd->self.controller; + union cvmx_usbcx_hcintx usbc_hcint; + union cvmx_usbcx_hctsizx usbc_hctsiz; + union cvmx_usbcx_hccharx usbc_hcchar; + struct cvmx_usb_pipe *pipe; + struct cvmx_usb_transaction *transaction; + int bytes_this_transfer; + int bytes_in_last_packet; + int packets_processed; + int buffer_space_left; + + /* Read the interrupt status bits for the channel */ + usbc_hcint.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCINTX(channel, usb->index)); + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { + usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(channel, + usb->index)); + + if (usbc_hcchar.s.chena && usbc_hcchar.s.chdis) { + /* + * There seems to be a bug in CN31XX which can cause + * interrupt IN transfers to get stuck until we do a + * write of HCCHARX without changing things + */ + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(channel, + usb->index), + usbc_hcchar.u32); + return 0; + } + + /* + * In non DMA mode the channels don't halt themselves. We need + * to manually disable channels that are left running + */ + if (!usbc_hcint.s.chhltd) { + if (usbc_hcchar.s.chena) { + union cvmx_usbcx_hcintmskx hcintmsk; + /* Disable all interrupts except CHHLTD */ + hcintmsk.u32 = 0; + hcintmsk.s.chhltdmsk = 1; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTMSKX(channel, usb->index), + hcintmsk.u32); + usbc_hcchar.s.chdis = 1; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index), + usbc_hcchar.u32); + return 0; + } else if (usbc_hcint.s.xfercompl) { + /* + * Successful IN/OUT with transfer complete. + * Channel halt isn't needed. + */ + } else { + dev_err(dev, "USB%d: Channel %d interrupt without halt\n", + usb->index, channel); + return 0; + } + } + } else { + /* + * There is are no interrupts that we need to process when the + * channel is still running + */ + if (!usbc_hcint.s.chhltd) + return 0; + } + + /* Disable the channel interrupts now that it is done */ + cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), 0); + usb->idle_hardware_channels |= (1 << channel); + + /* Make sure this channel is tied to a valid pipe */ + pipe = usb->pipe_for_channel[channel]; + prefetch(pipe); + if (!pipe) + return 0; + transaction = list_first_entry(&pipe->transactions, + typeof(*transaction), + node); + prefetch(transaction); + + /* + * Disconnect this pipe from the HW channel. Later the schedule + * function will figure out which pipe needs to go + */ + usb->pipe_for_channel[channel] = NULL; + pipe->flags &= ~CVMX_USB_PIPE_FLAGS_SCHEDULED; + + /* + * Read the channel config info so we can figure out how much data + * transferred + */ + usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index)); + usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index)); + + /* + * Calculating the number of bytes successfully transferred is dependent + * on the transfer direction + */ + packets_processed = transaction->pktcnt - usbc_hctsiz.s.pktcnt; + if (usbc_hcchar.s.epdir) { + /* + * IN transactions are easy. For every byte received the + * hardware decrements xfersize. All we need to do is subtract + * the current value of xfersize from its starting value and we + * know how many bytes were written to the buffer + */ + bytes_this_transfer = transaction->xfersize - + usbc_hctsiz.s.xfersize; + } else { + /* + * OUT transaction don't decrement xfersize. Instead pktcnt is + * decremented on every successful packet send. The hardware + * does this when it receives an ACK, or NYET. If it doesn't + * receive one of these responses pktcnt doesn't change + */ + bytes_this_transfer = packets_processed * usbc_hcchar.s.mps; + /* + * The last packet may not be a full transfer if we didn't have + * enough data + */ + if (bytes_this_transfer > transaction->xfersize) + bytes_this_transfer = transaction->xfersize; + } + /* Figure out how many bytes were in the last packet of the transfer */ + if (packets_processed) + bytes_in_last_packet = bytes_this_transfer - + (packets_processed - 1) * usbc_hcchar.s.mps; + else + bytes_in_last_packet = bytes_this_transfer; + + /* + * As a special case, setup transactions output the setup header, not + * the user's data. For this reason we don't count setup data as bytes + * transferred + */ + if ((transaction->stage == CVMX_USB_STAGE_SETUP) || + (transaction->stage == CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE)) + bytes_this_transfer = 0; + + /* + * Add the bytes transferred to the running total. It is important that + * bytes_this_transfer doesn't count any data that needs to be + * retransmitted + */ + transaction->actual_bytes += bytes_this_transfer; + if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) + buffer_space_left = transaction->iso_packets[0].length - + transaction->actual_bytes; + else + buffer_space_left = transaction->buffer_length - + transaction->actual_bytes; + + /* + * We need to remember the PID toggle state for the next transaction. + * The hardware already updated it for the next transaction + */ + pipe->pid_toggle = !(usbc_hctsiz.s.pid == 0); + + /* + * For high speed bulk out, assume the next transaction will need to do + * a ping before proceeding. If this isn't true the ACK processing below + * will clear this flag + */ + if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && + (pipe->transfer_type == CVMX_USB_TRANSFER_BULK) && + (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT)) + pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; + + if (WARN_ON_ONCE(bytes_this_transfer < 0)) { + /* + * In some rare cases the DMA engine seems to get stuck and + * keeps substracting same byte count over and over again. In + * such case we just need to fail every transaction. + */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_ERROR); + return 0; + } + + if (usbc_hcint.s.stall) { + /* + * STALL as a response means this transaction cannot be + * completed because the device can't process transactions. Tell + * the user. Any data that was transferred will be counted on + * the actual bytes transferred + */ + pipe->pid_toggle = 0; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_STALL); + } else if (usbc_hcint.s.xacterr) { + /* + * XactErr as a response means the device signaled + * something wrong with the transfer. For example, PID + * toggle errors cause these. + */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_XACTERR); + } else if (usbc_hcint.s.bblerr) { + /* Babble Error (BblErr) */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_BABBLEERR); + } else if (usbc_hcint.s.datatglerr) { + /* Data toggle error */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_DATATGLERR); + } else if (usbc_hcint.s.nyet) { + /* + * NYET as a response is only allowed in three cases: as a + * response to a ping, as a response to a split transaction, and + * as a response to a bulk out. The ping case is handled by + * hardware, so we only have splits and bulk out + */ + if (!cvmx_usb_pipe_needs_split(usb, pipe)) { + transaction->retries = 0; + /* + * If there is more data to go then we need to try + * again. Otherwise this transaction is complete + */ + if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) + cvmx_usb_complete(usb, pipe, + transaction, + CVMX_USB_STATUS_OK); + } else { + /* + * Split transactions retry the split complete 4 times + * then rewind to the start split and do the entire + * transactions again + */ + transaction->retries++; + if ((transaction->retries & 0x3) == 0) { + /* + * Rewind to the beginning of the transaction by + * anding off the split complete bit + */ + transaction->stage &= ~1; + pipe->split_sc_frame = -1; + } + } + } else if (usbc_hcint.s.ack) { + transaction->retries = 0; + /* + * The ACK bit can only be checked after the other error bits. + * This is because a multi packet transfer may succeed in a + * number of packets and then get a different response on the + * last packet. In this case both ACK and the last response bit + * will be set. If none of the other response bits is set, then + * the last packet must have been an ACK + * + * Since we got an ACK, we know we don't need to do a ping on + * this pipe + */ + pipe->flags &= ~CVMX_USB_PIPE_FLAGS_NEED_PING; + + switch (transaction->type) { + case CVMX_USB_TRANSFER_CONTROL: + cvmx_usb_transfer_control(usb, pipe, transaction, + usbc_hcchar, + buffer_space_left, + bytes_in_last_packet); + break; + case CVMX_USB_TRANSFER_BULK: + cvmx_usb_transfer_bulk(usb, pipe, transaction, + usbc_hcint, buffer_space_left, + bytes_in_last_packet); + break; + case CVMX_USB_TRANSFER_INTERRUPT: + cvmx_usb_transfer_intr(usb, pipe, transaction, + buffer_space_left, + bytes_in_last_packet); + break; + case CVMX_USB_TRANSFER_ISOCHRONOUS: + cvmx_usb_transfer_isoc(usb, pipe, transaction, + buffer_space_left, + bytes_in_last_packet, + bytes_this_transfer); + break; + } + } else if (usbc_hcint.s.nak) { + /* + * If this was a split then clear our split in progress marker. + */ + if (usb->active_split == transaction) + usb->active_split = NULL; + /* + * NAK as a response means the device couldn't accept the + * transaction, but it should be retried in the future. Rewind + * to the beginning of the transaction by anding off the split + * complete bit. Retry in the next interval + */ + transaction->retries = 0; + transaction->stage &= ~1; + pipe->next_tx_frame += pipe->interval; + if (pipe->next_tx_frame < usb->frame_number) + pipe->next_tx_frame = usb->frame_number + + pipe->interval - + (usb->frame_number - pipe->next_tx_frame) % + pipe->interval; + } else { + struct cvmx_usb_port_status port; + + port = cvmx_usb_get_status(usb); + if (port.port_enabled) { + /* We'll retry the exact same transaction again */ + transaction->retries++; + } else { + /* + * We get channel halted interrupts with no result bits + * sets when the cable is unplugged + */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_ERROR); + } + } + return 0; +} + +static void octeon_usb_port_callback(struct octeon_hcd *usb) +{ + spin_unlock(&usb->lock); + usb_hcd_poll_rh_status(octeon_to_hcd(usb)); + spin_lock(&usb->lock); +} + +/** + * Poll the USB block for status and call all needed callback + * handlers. This function is meant to be called in the interrupt + * handler for the USB controller. It can also be called + * periodically in a loop for non-interrupt based operation. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_poll(struct octeon_hcd *usb) +{ + union cvmx_usbcx_hfnum usbc_hfnum; + union cvmx_usbcx_gintsts usbc_gintsts; + + prefetch_range(usb, sizeof(*usb)); + + /* Update the frame counter */ + usbc_hfnum.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); + if ((usb->frame_number & 0x3fff) > usbc_hfnum.s.frnum) + usb->frame_number += 0x4000; + usb->frame_number &= ~0x3fffull; + usb->frame_number |= usbc_hfnum.s.frnum; + + /* Read the pending interrupts */ + usbc_gintsts.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GINTSTS(usb->index)); + + /* Clear the interrupts now that we know about them */ + cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), + usbc_gintsts.u32); + + if (usbc_gintsts.s.rxflvl) { + /* + * RxFIFO Non-Empty (RxFLvl) + * Indicates that there is at least one packet pending to be + * read from the RxFIFO. + * + * In DMA mode this is handled by hardware + */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + cvmx_usb_poll_rx_fifo(usb); + } + if (usbc_gintsts.s.ptxfemp || usbc_gintsts.s.nptxfemp) { + /* Fill the Tx FIFOs when not in DMA mode */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + cvmx_usb_poll_tx_fifo(usb); + } + if (usbc_gintsts.s.disconnint || usbc_gintsts.s.prtint) { + union cvmx_usbcx_hprt usbc_hprt; + /* + * Disconnect Detected Interrupt (DisconnInt) + * Asserted when a device disconnect is detected. + * + * Host Port Interrupt (PrtInt) + * The core sets this bit to indicate a change in port status of + * one of the O2P USB core ports in Host mode. The application + * must read the Host Port Control and Status (HPRT) register to + * determine the exact event that caused this interrupt. The + * application must clear the appropriate status bit in the Host + * Port Control and Status register to clear this bit. + * + * Call the user's port callback + */ + octeon_usb_port_callback(usb); + /* Clear the port change bits */ + usbc_hprt.u32 = + cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); + usbc_hprt.s.prtena = 0; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HPRT(usb->index), + usbc_hprt.u32); + } + if (usbc_gintsts.s.hchint) { + /* + * Host Channels Interrupt (HChInt) + * The core sets this bit to indicate that an interrupt is + * pending on one of the channels of the core (in Host mode). + * The application must read the Host All Channels Interrupt + * (HAINT) register to determine the exact number of the channel + * on which the interrupt occurred, and then read the + * corresponding Host Channel-n Interrupt (HCINTn) register to + * determine the exact cause of the interrupt. The application + * must clear the appropriate status bit in the HCINTn register + * to clear this bit. + */ + union cvmx_usbcx_haint usbc_haint; + + usbc_haint.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HAINT(usb->index)); + while (usbc_haint.u32) { + int channel; + + channel = __fls(usbc_haint.u32); + cvmx_usb_poll_channel(usb, channel); + usbc_haint.u32 ^= 1 << channel; + } + } + + cvmx_usb_schedule(usb, usbc_gintsts.s.sof); + + return 0; +} + +/* convert between an HCD pointer and the corresponding struct octeon_hcd */ +static inline struct octeon_hcd *hcd_to_octeon(struct usb_hcd *hcd) +{ + return (struct octeon_hcd *)(hcd->hcd_priv); +} + +static irqreturn_t octeon_usb_irq(struct usb_hcd *hcd) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_poll(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return IRQ_HANDLED; +} + +static int octeon_usb_start(struct usb_hcd *hcd) +{ + hcd->state = HC_STATE_RUNNING; + return 0; +} + +static void octeon_usb_stop(struct usb_hcd *hcd) +{ + hcd->state = HC_STATE_HALT; +} + +static int octeon_usb_get_frame_number(struct usb_hcd *hcd) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + + return cvmx_usb_get_frame_number(usb); +} + +static int octeon_usb_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct device *dev = hcd->self.controller; + struct cvmx_usb_transaction *transaction = NULL; + struct cvmx_usb_pipe *pipe; + unsigned long flags; + struct cvmx_usb_iso_packet *iso_packet; + struct usb_host_endpoint *ep = urb->ep; + int rc; + + urb->status = 0; + spin_lock_irqsave(&usb->lock, flags); + + rc = usb_hcd_link_urb_to_ep(hcd, urb); + if (rc) { + spin_unlock_irqrestore(&usb->lock, flags); + return rc; + } + + if (!ep->hcpriv) { + enum cvmx_usb_transfer transfer_type; + enum cvmx_usb_speed speed; + int split_device = 0; + int split_port = 0; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + transfer_type = CVMX_USB_TRANSFER_ISOCHRONOUS; + break; + case PIPE_INTERRUPT: + transfer_type = CVMX_USB_TRANSFER_INTERRUPT; + break; + case PIPE_CONTROL: + transfer_type = CVMX_USB_TRANSFER_CONTROL; + break; + default: + transfer_type = CVMX_USB_TRANSFER_BULK; + break; + } + switch (urb->dev->speed) { + case USB_SPEED_LOW: + speed = CVMX_USB_SPEED_LOW; + break; + case USB_SPEED_FULL: + speed = CVMX_USB_SPEED_FULL; + break; + default: + speed = CVMX_USB_SPEED_HIGH; + break; + } + /* + * For slow devices on high speed ports we need to find the hub + * that does the speed translation so we know where to send the + * split transactions. + */ + if (speed != CVMX_USB_SPEED_HIGH) { + /* + * Start at this device and work our way up the usb + * tree. + */ + struct usb_device *dev = urb->dev; + + while (dev->parent) { + /* + * If our parent is high speed then he'll + * receive the splits. + */ + if (dev->parent->speed == USB_SPEED_HIGH) { + split_device = dev->parent->devnum; + split_port = dev->portnum; + break; + } + /* + * Move up the tree one level. If we make it all + * the way up the tree, then the port must not + * be in high speed mode and we don't need a + * split. + */ + dev = dev->parent; + } + } + pipe = cvmx_usb_open_pipe(usb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), speed, + le16_to_cpu(ep->desc.wMaxPacketSize) + & 0x7ff, + transfer_type, + usb_pipein(urb->pipe) ? + CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT, + urb->interval, + (le16_to_cpu(ep->desc.wMaxPacketSize) + >> 11) & 0x3, + split_device, split_port); + if (!pipe) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&usb->lock, flags); + dev_dbg(dev, "Failed to create pipe\n"); + return -ENOMEM; + } + ep->hcpriv = pipe; + } else { + pipe = ep->hcpriv; + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + dev_dbg(dev, "Submit isochronous to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + /* + * Allocate a structure to use for our private list of + * isochronous packets. + */ + iso_packet = kmalloc_array(urb->number_of_packets, + sizeof(struct cvmx_usb_iso_packet), + GFP_ATOMIC); + if (iso_packet) { + int i; + /* Fill the list with the data from the URB */ + for (i = 0; i < urb->number_of_packets; i++) { + iso_packet[i].offset = + urb->iso_frame_desc[i].offset; + iso_packet[i].length = + urb->iso_frame_desc[i].length; + iso_packet[i].status = CVMX_USB_STATUS_ERROR; + } + /* + * Store a pointer to the list in the URB setup_packet + * field. We know this currently isn't being used and + * this saves us a bunch of logic. + */ + urb->setup_packet = (char *)iso_packet; + transaction = cvmx_usb_submit_isochronous(usb, + pipe, urb); + /* + * If submit failed we need to free our private packet + * list. + */ + if (!transaction) { + urb->setup_packet = NULL; + kfree(iso_packet); + } + } + break; + case PIPE_INTERRUPT: + dev_dbg(dev, "Submit interrupt to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + transaction = cvmx_usb_submit_interrupt(usb, pipe, urb); + break; + case PIPE_CONTROL: + dev_dbg(dev, "Submit control to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + transaction = cvmx_usb_submit_control(usb, pipe, urb); + break; + case PIPE_BULK: + dev_dbg(dev, "Submit bulk to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + transaction = cvmx_usb_submit_bulk(usb, pipe, urb); + break; + } + if (!transaction) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&usb->lock, flags); + dev_dbg(dev, "Failed to submit\n"); + return -ENOMEM; + } + urb->hcpriv = transaction; + spin_unlock_irqrestore(&usb->lock, flags); + return 0; +} + +static int octeon_usb_urb_dequeue(struct usb_hcd *hcd, + struct urb *urb, + int status) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + unsigned long flags; + int rc; + + if (!urb->dev) + return -EINVAL; + + spin_lock_irqsave(&usb->lock, flags); + + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto out; + + urb->status = status; + cvmx_usb_cancel(usb, urb->ep->hcpriv, urb->hcpriv); + +out: + spin_unlock_irqrestore(&usb->lock, flags); + + return rc; +} + +static void octeon_usb_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct device *dev = hcd->self.controller; + + if (ep->hcpriv) { + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct cvmx_usb_pipe *pipe = ep->hcpriv; + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_cancel_all(usb, pipe); + if (cvmx_usb_close_pipe(usb, pipe)) + dev_dbg(dev, "Closing pipe %p failed\n", pipe); + spin_unlock_irqrestore(&usb->lock, flags); + ep->hcpriv = NULL; + } +} + +static int octeon_usb_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct cvmx_usb_port_status port_status; + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + buf[0] = port_status.connect_change << 1; + + return buf[0] != 0; +} + +static int octeon_usb_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct device *dev = hcd->self.controller; + struct cvmx_usb_port_status usb_port_status; + int port_status; + struct usb_hub_descriptor *desc; + unsigned long flags; + + switch (typeReq) { + case ClearHubFeature: + dev_dbg(dev, "ClearHubFeature\n"); + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + default: + return -EINVAL; + } + break; + case ClearPortFeature: + dev_dbg(dev, "ClearPortFeature\n"); + if (wIndex != 1) { + dev_dbg(dev, " INVALID\n"); + return -EINVAL; + } + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + dev_dbg(dev, " ENABLE\n"); + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_disable(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_SUSPEND: + dev_dbg(dev, " SUSPEND\n"); + /* Not supported on Octeon */ + break; + case USB_PORT_FEAT_POWER: + dev_dbg(dev, " POWER\n"); + /* Not supported on Octeon */ + break; + case USB_PORT_FEAT_INDICATOR: + dev_dbg(dev, " INDICATOR\n"); + /* Port inidicator not supported */ + break; + case USB_PORT_FEAT_C_CONNECTION: + dev_dbg(dev, " C_CONNECTION\n"); + /* Clears drivers internal connect status change flag */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_C_RESET: + dev_dbg(dev, " C_RESET\n"); + /* + * Clears the driver's internal Port Reset Change flag. + */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_C_ENABLE: + dev_dbg(dev, " C_ENABLE\n"); + /* + * Clears the driver's internal Port Enable/Disable + * Change flag. + */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_C_SUSPEND: + dev_dbg(dev, " C_SUSPEND\n"); + /* + * Clears the driver's internal Port Suspend Change + * flag, which is set when resume signaling on the host + * port is complete. + */ + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(dev, " C_OVER_CURRENT\n"); + /* Clears the driver's overcurrent Change flag */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + default: + dev_dbg(dev, " UNKNOWN\n"); + return -EINVAL; + } + break; + case GetHubDescriptor: + dev_dbg(dev, "GetHubDescriptor\n"); + desc = (struct usb_hub_descriptor *)buf; + desc->bDescLength = 9; + desc->bDescriptorType = 0x29; + desc->bNbrPorts = 1; + desc->wHubCharacteristics = cpu_to_le16(0x08); + desc->bPwrOn2PwrGood = 1; + desc->bHubContrCurrent = 0; + desc->u.hs.DeviceRemovable[0] = 0; + desc->u.hs.DeviceRemovable[1] = 0xff; + break; + case GetHubStatus: + dev_dbg(dev, "GetHubStatus\n"); + *(__le32 *)buf = 0; + break; + case GetPortStatus: + dev_dbg(dev, "GetPortStatus\n"); + if (wIndex != 1) { + dev_dbg(dev, " INVALID\n"); + return -EINVAL; + } + + spin_lock_irqsave(&usb->lock, flags); + usb_port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + port_status = 0; + + if (usb_port_status.connect_change) { + port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); + dev_dbg(dev, " C_CONNECTION\n"); + } + + if (usb_port_status.port_enabled) { + port_status |= (1 << USB_PORT_FEAT_C_ENABLE); + dev_dbg(dev, " C_ENABLE\n"); + } + + if (usb_port_status.connected) { + port_status |= (1 << USB_PORT_FEAT_CONNECTION); + dev_dbg(dev, " CONNECTION\n"); + } + + if (usb_port_status.port_enabled) { + port_status |= (1 << USB_PORT_FEAT_ENABLE); + dev_dbg(dev, " ENABLE\n"); + } + + if (usb_port_status.port_over_current) { + port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT); + dev_dbg(dev, " OVER_CURRENT\n"); + } + + if (usb_port_status.port_powered) { + port_status |= (1 << USB_PORT_FEAT_POWER); + dev_dbg(dev, " POWER\n"); + } + + if (usb_port_status.port_speed == CVMX_USB_SPEED_HIGH) { + port_status |= USB_PORT_STAT_HIGH_SPEED; + dev_dbg(dev, " HIGHSPEED\n"); + } else if (usb_port_status.port_speed == CVMX_USB_SPEED_LOW) { + port_status |= (1 << USB_PORT_FEAT_LOWSPEED); + dev_dbg(dev, " LOWSPEED\n"); + } + + *((__le32 *)buf) = cpu_to_le32(port_status); + break; + case SetHubFeature: + dev_dbg(dev, "SetHubFeature\n"); + /* No HUB features supported */ + break; + case SetPortFeature: + dev_dbg(dev, "SetPortFeature\n"); + if (wIndex != 1) { + dev_dbg(dev, " INVALID\n"); + return -EINVAL; + } + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + dev_dbg(dev, " SUSPEND\n"); + return -EINVAL; + case USB_PORT_FEAT_POWER: + dev_dbg(dev, " POWER\n"); + /* + * Program the port power bit to drive VBUS on the USB. + */ + spin_lock_irqsave(&usb->lock, flags); + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), + cvmx_usbcx_hprt, prtpwr, 1); + spin_unlock_irqrestore(&usb->lock, flags); + return 0; + case USB_PORT_FEAT_RESET: + dev_dbg(dev, " RESET\n"); + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_reset_port(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return 0; + case USB_PORT_FEAT_INDICATOR: + dev_dbg(dev, " INDICATOR\n"); + /* Not supported */ + break; + default: + dev_dbg(dev, " UNKNOWN\n"); + return -EINVAL; + } + break; + default: + dev_dbg(dev, "Unknown root hub request\n"); + return -EINVAL; + } + return 0; +} + +static const struct hc_driver octeon_hc_driver = { + .description = "Octeon USB", + .product_desc = "Octeon Host Controller", + .hcd_priv_size = sizeof(struct octeon_hcd), + .irq = octeon_usb_irq, + .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, + .start = octeon_usb_start, + .stop = octeon_usb_stop, + .urb_enqueue = octeon_usb_urb_enqueue, + .urb_dequeue = octeon_usb_urb_dequeue, + .endpoint_disable = octeon_usb_endpoint_disable, + .get_frame_number = octeon_usb_get_frame_number, + .hub_status_data = octeon_usb_hub_status_data, + .hub_control = octeon_usb_hub_control, + .map_urb_for_dma = octeon_map_urb_for_dma, + .unmap_urb_for_dma = octeon_unmap_urb_for_dma, +}; + +static int octeon_usb_probe(struct platform_device *pdev) +{ + int status; + int initialize_flags; + int usb_num; + struct resource *res_mem; + struct device_node *usbn_node; + int irq = platform_get_irq(pdev, 0); + struct device *dev = &pdev->dev; + struct octeon_hcd *usb; + struct usb_hcd *hcd; + u32 clock_rate = 48000000; + bool is_crystal_clock = false; + const char *clock_type; + int i; + + if (!dev->of_node) { + dev_err(dev, "Error: empty of_node\n"); + return -ENXIO; + } + usbn_node = dev->of_node->parent; + + i = of_property_read_u32(usbn_node, + "clock-frequency", &clock_rate); + if (i) + i = of_property_read_u32(usbn_node, + "refclk-frequency", &clock_rate); + if (i) { + dev_err(dev, "No USBN \"clock-frequency\"\n"); + return -ENXIO; + } + switch (clock_rate) { + case 12000000: + initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ; + break; + case 24000000: + initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ; + break; + case 48000000: + initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ; + break; + default: + dev_err(dev, "Illegal USBN \"clock-frequency\" %u\n", + clock_rate); + return -ENXIO; + } + + i = of_property_read_string(usbn_node, + "cavium,refclk-type", &clock_type); + if (i) + i = of_property_read_string(usbn_node, + "refclk-type", &clock_type); + + if (!i && strcmp("crystal", clock_type) == 0) + is_crystal_clock = true; + + if (is_crystal_clock) + initialize_flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI; + else + initialize_flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_mem) { + dev_err(dev, "found no memory resource\n"); + return -ENXIO; + } + usb_num = (res_mem->start >> 44) & 1; + + if (irq < 0) { + /* Defective device tree, but we know how to fix it. */ + irq_hw_number_t hwirq = usb_num ? (1 << 6) + 17 : 56; + + irq = irq_create_mapping(NULL, hwirq); + } + + /* + * Set the DMA mask to 64bits so we get buffers already translated for + * DMA. + */ + i = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (i) + return i; + + /* + * Only cn52XX and cn56XX have DWC_OTG USB hardware and the + * IOB priority registers. Under heavy network load USB + * hardware can be starved by the IOB causing a crash. Give + * it a priority boost if it has been waiting more than 400 + * cycles to avoid this situation. + * + * Testing indicates that a cnt_val of 8192 is not sufficient, + * but no failures are seen with 4096. We choose a value of + * 400 to give a safety factor of 10. + */ + if (OCTEON_IS_MODEL(OCTEON_CN52XX) || OCTEON_IS_MODEL(OCTEON_CN56XX)) { + union cvmx_iob_n2c_l2c_pri_cnt pri_cnt; + + pri_cnt.u64 = 0; + pri_cnt.s.cnt_enb = 1; + pri_cnt.s.cnt_val = 400; + cvmx_write_csr(CVMX_IOB_N2C_L2C_PRI_CNT, pri_cnt.u64); + } + + hcd = usb_create_hcd(&octeon_hc_driver, dev, dev_name(dev)); + if (!hcd) { + dev_dbg(dev, "Failed to allocate memory for HCD\n"); + return -1; + } + hcd->uses_new_polling = 1; + usb = (struct octeon_hcd *)hcd->hcd_priv; + + spin_lock_init(&usb->lock); + + usb->init_flags = initialize_flags; + + /* Initialize the USB state structure */ + usb->index = usb_num; + INIT_LIST_HEAD(&usb->idle_pipes); + for (i = 0; i < ARRAY_SIZE(usb->active_pipes); i++) + INIT_LIST_HEAD(&usb->active_pipes[i]); + + /* Due to an errata, CN31XX doesn't support DMA */ + if (OCTEON_IS_MODEL(OCTEON_CN31XX)) { + usb->init_flags |= CVMX_USB_INITIALIZE_FLAGS_NO_DMA; + /* Only use one channel with non DMA */ + usb->idle_hardware_channels = 0x1; + } else if (OCTEON_IS_MODEL(OCTEON_CN5XXX)) { + /* CN5XXX have an errata with channel 3 */ + usb->idle_hardware_channels = 0xf7; + } else { + usb->idle_hardware_channels = 0xff; + } + + status = cvmx_usb_initialize(dev, usb); + if (status) { + dev_dbg(dev, "USB initialization failed with %d\n", status); + usb_put_hcd(hcd); + return -1; + } + + status = usb_add_hcd(hcd, irq, 0); + if (status) { + dev_dbg(dev, "USB add HCD failed with %d\n", status); + usb_put_hcd(hcd); + return -1; + } + device_wakeup_enable(hcd->self.controller); + + dev_info(dev, "Registered HCD for port %d on irq %d\n", usb_num, irq); + + return 0; +} + +static int octeon_usb_remove(struct platform_device *pdev) +{ + int status; + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct octeon_hcd *usb = hcd_to_octeon(hcd); + unsigned long flags; + + usb_remove_hcd(hcd); + spin_lock_irqsave(&usb->lock, flags); + status = cvmx_usb_shutdown(usb); + spin_unlock_irqrestore(&usb->lock, flags); + if (status) + dev_dbg(dev, "USB shutdown failed with %d\n", status); + + usb_put_hcd(hcd); + + return 0; +} + +static const struct of_device_id octeon_usb_match[] = { + { + .compatible = "cavium,octeon-5750-usbc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, octeon_usb_match); + +static struct platform_driver octeon_usb_driver = { + .driver = { + .name = "octeon-hcd", + .of_match_table = octeon_usb_match, + }, + .probe = octeon_usb_probe, + .remove = octeon_usb_remove, +}; + +static int __init octeon_usb_driver_init(void) +{ + if (usb_disabled()) + return 0; + + return platform_driver_register(&octeon_usb_driver); +} +module_init(octeon_usb_driver_init); + +static void __exit octeon_usb_driver_exit(void) +{ + if (usb_disabled()) + return; + + platform_driver_unregister(&octeon_usb_driver); +} +module_exit(octeon_usb_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cavium, Inc. "); +MODULE_DESCRIPTION("Cavium Inc. OCTEON USB Host driver."); diff --git a/drivers/usb/host/octeon-hcd.h b/drivers/usb/host/octeon-hcd.h new file mode 100644 index 000000000000..9ed619c93a4e --- /dev/null +++ b/drivers/usb/host/octeon-hcd.h @@ -0,0 +1,1847 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Octeon HCD hardware register definitions. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Some parts of the code were originally released under BSD license: + * + * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * Neither the name of Cavium Networks nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * This Software, including technical data, may be subject to U.S. export + * control laws, including the U.S. Export Administration Act and its associated + * regulations, and may be subject to export or import regulations in other + * countries. + * + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" + * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR + * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO + * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION + * OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM + * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, + * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF + * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR + * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR + * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. + */ + +#ifndef __OCTEON_HCD_H__ +#define __OCTEON_HCD_H__ + +#include + +#define CVMX_USBCXBASE 0x00016F0010000000ull +#define CVMX_USBCXREG1(reg, bid) \ + (CVMX_ADD_IO_SEG(CVMX_USBCXBASE | reg) + \ + ((bid) & 1) * 0x100000000000ull) +#define CVMX_USBCXREG2(reg, bid, off) \ + (CVMX_ADD_IO_SEG(CVMX_USBCXBASE | reg) + \ + (((off) & 7) + ((bid) & 1) * 0x8000000000ull) * 32) + +#define CVMX_USBCX_GAHBCFG(bid) CVMX_USBCXREG1(0x008, bid) +#define CVMX_USBCX_GHWCFG3(bid) CVMX_USBCXREG1(0x04c, bid) +#define CVMX_USBCX_GINTMSK(bid) CVMX_USBCXREG1(0x018, bid) +#define CVMX_USBCX_GINTSTS(bid) CVMX_USBCXREG1(0x014, bid) +#define CVMX_USBCX_GNPTXFSIZ(bid) CVMX_USBCXREG1(0x028, bid) +#define CVMX_USBCX_GNPTXSTS(bid) CVMX_USBCXREG1(0x02c, bid) +#define CVMX_USBCX_GOTGCTL(bid) CVMX_USBCXREG1(0x000, bid) +#define CVMX_USBCX_GRSTCTL(bid) CVMX_USBCXREG1(0x010, bid) +#define CVMX_USBCX_GRXFSIZ(bid) CVMX_USBCXREG1(0x024, bid) +#define CVMX_USBCX_GRXSTSPH(bid) CVMX_USBCXREG1(0x020, bid) +#define CVMX_USBCX_GUSBCFG(bid) CVMX_USBCXREG1(0x00c, bid) +#define CVMX_USBCX_HAINT(bid) CVMX_USBCXREG1(0x414, bid) +#define CVMX_USBCX_HAINTMSK(bid) CVMX_USBCXREG1(0x418, bid) +#define CVMX_USBCX_HCCHARX(off, bid) CVMX_USBCXREG2(0x500, bid, off) +#define CVMX_USBCX_HCFG(bid) CVMX_USBCXREG1(0x400, bid) +#define CVMX_USBCX_HCINTMSKX(off, bid) CVMX_USBCXREG2(0x50c, bid, off) +#define CVMX_USBCX_HCINTX(off, bid) CVMX_USBCXREG2(0x508, bid, off) +#define CVMX_USBCX_HCSPLTX(off, bid) CVMX_USBCXREG2(0x504, bid, off) +#define CVMX_USBCX_HCTSIZX(off, bid) CVMX_USBCXREG2(0x510, bid, off) +#define CVMX_USBCX_HFIR(bid) CVMX_USBCXREG1(0x404, bid) +#define CVMX_USBCX_HFNUM(bid) CVMX_USBCXREG1(0x408, bid) +#define CVMX_USBCX_HPRT(bid) CVMX_USBCXREG1(0x440, bid) +#define CVMX_USBCX_HPTXFSIZ(bid) CVMX_USBCXREG1(0x100, bid) +#define CVMX_USBCX_HPTXSTS(bid) CVMX_USBCXREG1(0x410, bid) + +#define CVMX_USBNXBID1(bid) (((bid) & 1) * 0x10000000ull) +#define CVMX_USBNXBID2(bid) (((bid) & 1) * 0x100000000000ull) + +#define CVMX_USBNXREG1(reg, bid) \ + (CVMX_ADD_IO_SEG(0x0001180068000000ull | reg) + CVMX_USBNXBID1(bid)) +#define CVMX_USBNXREG2(reg, bid) \ + (CVMX_ADD_IO_SEG(0x00016F0000000000ull | reg) + CVMX_USBNXBID2(bid)) + +#define CVMX_USBNX_CLK_CTL(bid) CVMX_USBNXREG1(0x10, bid) +#define CVMX_USBNX_DMA0_INB_CHN0(bid) CVMX_USBNXREG2(0x818, bid) +#define CVMX_USBNX_DMA0_OUTB_CHN0(bid) CVMX_USBNXREG2(0x858, bid) +#define CVMX_USBNX_USBP_CTL_STATUS(bid) CVMX_USBNXREG1(0x18, bid) + +/** + * cvmx_usbc#_gahbcfg + * + * Core AHB Configuration Register (GAHBCFG) + * + * This register can be used to configure the core after power-on or a change in + * mode of operation. This register mainly contains AHB system-related + * configuration parameters. The AHB is the processor interface to the O2P USB + * core. In general, software need not know about this interface except to + * program the values as specified. + * + * The application must program this register as part of the O2P USB core + * initialization. Do not change this register after the initial programming. + */ +union cvmx_usbcx_gahbcfg { + u32 u32; + /** + * struct cvmx_usbcx_gahbcfg_s + * @ptxfemplvl: Periodic TxFIFO Empty Level (PTxFEmpLvl) + * Software should set this bit to 0x1. + * Indicates when the Periodic TxFIFO Empty Interrupt bit in the + * Core Interrupt register (GINTSTS.PTxFEmp) is triggered. This + * bit is used only in Slave mode. + * * 1'b0: GINTSTS.PTxFEmp interrupt indicates that the Periodic + * TxFIFO is half empty + * * 1'b1: GINTSTS.PTxFEmp interrupt indicates that the Periodic + * TxFIFO is completely empty + * @nptxfemplvl: Non-Periodic TxFIFO Empty Level (NPTxFEmpLvl) + * Software should set this bit to 0x1. + * Indicates when the Non-Periodic TxFIFO Empty Interrupt bit in + * the Core Interrupt register (GINTSTS.NPTxFEmp) is triggered. + * This bit is used only in Slave mode. + * * 1'b0: GINTSTS.NPTxFEmp interrupt indicates that the Non- + * Periodic TxFIFO is half empty + * * 1'b1: GINTSTS.NPTxFEmp interrupt indicates that the Non- + * Periodic TxFIFO is completely empty + * @dmaen: DMA Enable (DMAEn) + * * 1'b0: Core operates in Slave mode + * * 1'b1: Core operates in a DMA mode + * @hbstlen: Burst Length/Type (HBstLen) + * This field has not effect and should be left as 0x0. + * @glblintrmsk: Global Interrupt Mask (GlblIntrMsk) + * Software should set this field to 0x1. + * The application uses this bit to mask or unmask the interrupt + * line assertion to itself. Irrespective of this bit's setting, + * the interrupt status registers are updated by the core. + * * 1'b0: Mask the interrupt assertion to the application. + * * 1'b1: Unmask the interrupt assertion to the application. + */ + struct cvmx_usbcx_gahbcfg_s { + __BITFIELD_FIELD(u32 reserved_9_31 : 23, + __BITFIELD_FIELD(u32 ptxfemplvl : 1, + __BITFIELD_FIELD(u32 nptxfemplvl : 1, + __BITFIELD_FIELD(u32 reserved_6_6 : 1, + __BITFIELD_FIELD(u32 dmaen : 1, + __BITFIELD_FIELD(u32 hbstlen : 4, + __BITFIELD_FIELD(u32 glblintrmsk : 1, + ;))))))) + } s; +}; + +/** + * cvmx_usbc#_ghwcfg3 + * + * User HW Config3 Register (GHWCFG3) + * + * This register contains the configuration options of the O2P USB core. + */ +union cvmx_usbcx_ghwcfg3 { + u32 u32; + /** + * struct cvmx_usbcx_ghwcfg3_s + * @dfifodepth: DFIFO Depth (DfifoDepth) + * This value is in terms of 32-bit words. + * * Minimum value is 32 + * * Maximum value is 32768 + * @ahbphysync: AHB and PHY Synchronous (AhbPhySync) + * Indicates whether AHB and PHY clocks are synchronous to + * each other. + * * 1'b0: No + * * 1'b1: Yes + * This bit is tied to 1. + * @rsttype: Reset Style for Clocked always Blocks in RTL (RstType) + * * 1'b0: Asynchronous reset is used in the core + * * 1'b1: Synchronous reset is used in the core + * @optfeature: Optional Features Removed (OptFeature) + * Indicates whether the User ID register, GPIO interface ports, + * and SOF toggle and counter ports were removed for gate count + * optimization. + * @vendor_control_interface_support: Vendor Control Interface Support + * * 1'b0: Vendor Control Interface is not available on the core. + * * 1'b1: Vendor Control Interface is available. + * @i2c_selection: I2C Selection + * * 1'b0: I2C Interface is not available on the core. + * * 1'b1: I2C Interface is available on the core. + * @otgen: OTG Function Enabled (OtgEn) + * The application uses this bit to indicate the O2P USB core's + * OTG capabilities. + * * 1'b0: Not OTG capable + * * 1'b1: OTG Capable + * @pktsizewidth: Width of Packet Size Counters (PktSizeWidth) + * * 3'b000: 4 bits + * * 3'b001: 5 bits + * * 3'b010: 6 bits + * * 3'b011: 7 bits + * * 3'b100: 8 bits + * * 3'b101: 9 bits + * * 3'b110: 10 bits + * * Others: Reserved + * @xfersizewidth: Width of Transfer Size Counters (XferSizeWidth) + * * 4'b0000: 11 bits + * * 4'b0001: 12 bits + * - ... + * * 4'b1000: 19 bits + * * Others: Reserved + */ + struct cvmx_usbcx_ghwcfg3_s { + __BITFIELD_FIELD(u32 dfifodepth : 16, + __BITFIELD_FIELD(u32 reserved_13_15 : 3, + __BITFIELD_FIELD(u32 ahbphysync : 1, + __BITFIELD_FIELD(u32 rsttype : 1, + __BITFIELD_FIELD(u32 optfeature : 1, + __BITFIELD_FIELD(u32 vendor_control_interface_support : 1, + __BITFIELD_FIELD(u32 i2c_selection : 1, + __BITFIELD_FIELD(u32 otgen : 1, + __BITFIELD_FIELD(u32 pktsizewidth : 3, + __BITFIELD_FIELD(u32 xfersizewidth : 4, + ;)))))))))) + } s; +}; + +/** + * cvmx_usbc#_gintmsk + * + * Core Interrupt Mask Register (GINTMSK) + * + * This register works with the Core Interrupt register to interrupt the + * application. When an interrupt bit is masked, the interrupt associated with + * that bit will not be generated. However, the Core Interrupt (GINTSTS) + * register bit corresponding to that interrupt will still be set. + * Mask interrupt: 1'b0, Unmask interrupt: 1'b1 + */ +union cvmx_usbcx_gintmsk { + u32 u32; + /** + * struct cvmx_usbcx_gintmsk_s + * @wkupintmsk: Resume/Remote Wakeup Detected Interrupt Mask + * (WkUpIntMsk) + * @sessreqintmsk: Session Request/New Session Detected Interrupt Mask + * (SessReqIntMsk) + * @disconnintmsk: Disconnect Detected Interrupt Mask (DisconnIntMsk) + * @conidstschngmsk: Connector ID Status Change Mask (ConIDStsChngMsk) + * @ptxfempmsk: Periodic TxFIFO Empty Mask (PTxFEmpMsk) + * @hchintmsk: Host Channels Interrupt Mask (HChIntMsk) + * @prtintmsk: Host Port Interrupt Mask (PrtIntMsk) + * @fetsuspmsk: Data Fetch Suspended Mask (FetSuspMsk) + * @incomplpmsk: Incomplete Periodic Transfer Mask (incomplPMsk) + * Incomplete Isochronous OUT Transfer Mask + * (incompISOOUTMsk) + * @incompisoinmsk: Incomplete Isochronous IN Transfer Mask + * (incompISOINMsk) + * @oepintmsk: OUT Endpoints Interrupt Mask (OEPIntMsk) + * @inepintmsk: IN Endpoints Interrupt Mask (INEPIntMsk) + * @epmismsk: Endpoint Mismatch Interrupt Mask (EPMisMsk) + * @eopfmsk: End of Periodic Frame Interrupt Mask (EOPFMsk) + * @isooutdropmsk: Isochronous OUT Packet Dropped Interrupt Mask + * (ISOOutDropMsk) + * @enumdonemsk: Enumeration Done Mask (EnumDoneMsk) + * @usbrstmsk: USB Reset Mask (USBRstMsk) + * @usbsuspmsk: USB Suspend Mask (USBSuspMsk) + * @erlysuspmsk: Early Suspend Mask (ErlySuspMsk) + * @i2cint: I2C Interrupt Mask (I2CINT) + * @ulpickintmsk: ULPI Carkit Interrupt Mask (ULPICKINTMsk) + * I2C Carkit Interrupt Mask (I2CCKINTMsk) + * @goutnakeffmsk: Global OUT NAK Effective Mask (GOUTNakEffMsk) + * @ginnakeffmsk: Global Non-Periodic IN NAK Effective Mask + * (GINNakEffMsk) + * @nptxfempmsk: Non-Periodic TxFIFO Empty Mask (NPTxFEmpMsk) + * @rxflvlmsk: Receive FIFO Non-Empty Mask (RxFLvlMsk) + * @sofmsk: Start of (micro)Frame Mask (SofMsk) + * @otgintmsk: OTG Interrupt Mask (OTGIntMsk) + * @modemismsk: Mode Mismatch Interrupt Mask (ModeMisMsk) + */ + struct cvmx_usbcx_gintmsk_s { + __BITFIELD_FIELD(u32 wkupintmsk : 1, + __BITFIELD_FIELD(u32 sessreqintmsk : 1, + __BITFIELD_FIELD(u32 disconnintmsk : 1, + __BITFIELD_FIELD(u32 conidstschngmsk : 1, + __BITFIELD_FIELD(u32 reserved_27_27 : 1, + __BITFIELD_FIELD(u32 ptxfempmsk : 1, + __BITFIELD_FIELD(u32 hchintmsk : 1, + __BITFIELD_FIELD(u32 prtintmsk : 1, + __BITFIELD_FIELD(u32 reserved_23_23 : 1, + __BITFIELD_FIELD(u32 fetsuspmsk : 1, + __BITFIELD_FIELD(u32 incomplpmsk : 1, + __BITFIELD_FIELD(u32 incompisoinmsk : 1, + __BITFIELD_FIELD(u32 oepintmsk : 1, + __BITFIELD_FIELD(u32 inepintmsk : 1, + __BITFIELD_FIELD(u32 epmismsk : 1, + __BITFIELD_FIELD(u32 reserved_16_16 : 1, + __BITFIELD_FIELD(u32 eopfmsk : 1, + __BITFIELD_FIELD(u32 isooutdropmsk : 1, + __BITFIELD_FIELD(u32 enumdonemsk : 1, + __BITFIELD_FIELD(u32 usbrstmsk : 1, + __BITFIELD_FIELD(u32 usbsuspmsk : 1, + __BITFIELD_FIELD(u32 erlysuspmsk : 1, + __BITFIELD_FIELD(u32 i2cint : 1, + __BITFIELD_FIELD(u32 ulpickintmsk : 1, + __BITFIELD_FIELD(u32 goutnakeffmsk : 1, + __BITFIELD_FIELD(u32 ginnakeffmsk : 1, + __BITFIELD_FIELD(u32 nptxfempmsk : 1, + __BITFIELD_FIELD(u32 rxflvlmsk : 1, + __BITFIELD_FIELD(u32 sofmsk : 1, + __BITFIELD_FIELD(u32 otgintmsk : 1, + __BITFIELD_FIELD(u32 modemismsk : 1, + __BITFIELD_FIELD(u32 reserved_0_0 : 1, + ;)))))))))))))))))))))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_gintsts + * + * Core Interrupt Register (GINTSTS) + * + * This register interrupts the application for system-level events in the + * current mode of operation (Device mode or Host mode). It is shown in + * Interrupt. Some of the bits in this register are valid only in Host mode, + * while others are valid in Device mode only. This register also indicates the + * current mode of operation. In order to clear the interrupt status bits of + * type R_SS_WC, the application must write 1'b1 into the bit. The FIFO status + * interrupts are read only; once software reads from or writes to the FIFO + * while servicing these interrupts, FIFO interrupt conditions are cleared + * automatically. + */ +union cvmx_usbcx_gintsts { + u32 u32; + /** + * struct cvmx_usbcx_gintsts_s + * @wkupint: Resume/Remote Wakeup Detected Interrupt (WkUpInt) + * In Device mode, this interrupt is asserted when a resume is + * detected on the USB. In Host mode, this interrupt is asserted + * when a remote wakeup is detected on the USB. + * For more information on how to use this interrupt, see "Partial + * Power-Down and Clock Gating Programming Model" on + * page 353. + * @sessreqint: Session Request/New Session Detected Interrupt + * (SessReqInt) + * In Host mode, this interrupt is asserted when a session request + * is detected from the device. In Device mode, this interrupt is + * asserted when the utmiotg_bvalid signal goes high. + * For more information on how to use this interrupt, see "Partial + * Power-Down and Clock Gating Programming Model" on + * page 353. + * @disconnint: Disconnect Detected Interrupt (DisconnInt) + * Asserted when a device disconnect is detected. + * @conidstschng: Connector ID Status Change (ConIDStsChng) + * The core sets this bit when there is a change in connector ID + * status. + * @ptxfemp: Periodic TxFIFO Empty (PTxFEmp) + * Asserted when the Periodic Transmit FIFO is either half or + * completely empty and there is space for at least one entry to be + * written in the Periodic Request Queue. The half or completely + * empty status is determined by the Periodic TxFIFO Empty Level + * bit in the Core AHB Configuration register + * (GAHBCFG.PTxFEmpLvl). + * @hchint: Host Channels Interrupt (HChInt) + * The core sets this bit to indicate that an interrupt is pending + * on one of the channels of the core (in Host mode). The + * application must read the Host All Channels Interrupt (HAINT) + * register to determine the exact number of the channel on which + * the interrupt occurred, and then read the corresponding Host + * Channel-n Interrupt (HCINTn) register to determine the exact + * cause of the interrupt. The application must clear the + * appropriate status bit in the HCINTn register to clear this bit. + * @prtint: Host Port Interrupt (PrtInt) + * The core sets this bit to indicate a change in port status of + * one of the O2P USB core ports in Host mode. The application must + * read the Host Port Control and Status (HPRT) register to + * determine the exact event that caused this interrupt. The + * application must clear the appropriate status bit in the Host + * Port Control and Status register to clear this bit. + * @fetsusp: Data Fetch Suspended (FetSusp) + * This interrupt is valid only in DMA mode. This interrupt + * indicates that the core has stopped fetching data for IN + * endpoints due to the unavailability of TxFIFO space or Request + * Queue space. This interrupt is used by the application for an + * endpoint mismatch algorithm. + * @incomplp: Incomplete Periodic Transfer (incomplP) + * In Host mode, the core sets this interrupt bit when there are + * incomplete periodic transactions still pending which are + * scheduled for the current microframe. + * Incomplete Isochronous OUT Transfer (incompISOOUT) + * The Device mode, the core sets this interrupt to indicate that + * there is at least one isochronous OUT endpoint on which the + * transfer is not completed in the current microframe. This + * interrupt is asserted along with the End of Periodic Frame + * Interrupt (EOPF) bit in this register. + * @incompisoin: Incomplete Isochronous IN Transfer (incompISOIN) + * The core sets this interrupt to indicate that there is at least + * one isochronous IN endpoint on which the transfer is not + * completed in the current microframe. This interrupt is asserted + * along with the End of Periodic Frame Interrupt (EOPF) bit in + * this register. + * @oepint: OUT Endpoints Interrupt (OEPInt) + * The core sets this bit to indicate that an interrupt is pending + * on one of the OUT endpoints of the core (in Device mode). The + * application must read the Device All Endpoints Interrupt + * (DAINT) register to determine the exact number of the OUT + * endpoint on which the interrupt occurred, and then read the + * corresponding Device OUT Endpoint-n Interrupt (DOEPINTn) + * register to determine the exact cause of the interrupt. The + * application must clear the appropriate status bit in the + * corresponding DOEPINTn register to clear this bit. + * @iepint: IN Endpoints Interrupt (IEPInt) + * The core sets this bit to indicate that an interrupt is pending + * on one of the IN endpoints of the core (in Device mode). The + * application must read the Device All Endpoints Interrupt + * (DAINT) register to determine the exact number of the IN + * endpoint on which the interrupt occurred, and then read the + * corresponding Device IN Endpoint-n Interrupt (DIEPINTn) + * register to determine the exact cause of the interrupt. The + * application must clear the appropriate status bit in the + * corresponding DIEPINTn register to clear this bit. + * @epmis: Endpoint Mismatch Interrupt (EPMis) + * Indicates that an IN token has been received for a non-periodic + * endpoint, but the data for another endpoint is present in the + * top of the Non-Periodic Transmit FIFO and the IN endpoint + * mismatch count programmed by the application has expired. + * @eopf: End of Periodic Frame Interrupt (EOPF) + * Indicates that the period specified in the Periodic Frame + * Interval field of the Device Configuration register + * (DCFG.PerFrInt) has been reached in the current microframe. + * @isooutdrop: Isochronous OUT Packet Dropped Interrupt (ISOOutDrop) + * The core sets this bit when it fails to write an isochronous OUT + * packet into the RxFIFO because the RxFIFO doesn't have + * enough space to accommodate a maximum packet size packet + * for the isochronous OUT endpoint. + * @enumdone: Enumeration Done (EnumDone) + * The core sets this bit to indicate that speed enumeration is + * complete. The application must read the Device Status (DSTS) + * register to obtain the enumerated speed. + * @usbrst: USB Reset (USBRst) + * The core sets this bit to indicate that a reset is detected on + * the USB. + * @usbsusp: USB Suspend (USBSusp) + * The core sets this bit to indicate that a suspend was detected + * on the USB. The core enters the Suspended state when there + * is no activity on the phy_line_state_i signal for an extended + * period of time. + * @erlysusp: Early Suspend (ErlySusp) + * The core sets this bit to indicate that an Idle state has been + * detected on the USB for 3 ms. + * @i2cint: I2C Interrupt (I2CINT) + * This bit is always 0x0. + * @ulpickint: ULPI Carkit Interrupt (ULPICKINT) + * This bit is always 0x0. + * @goutnakeff: Global OUT NAK Effective (GOUTNakEff) + * Indicates that the Set Global OUT NAK bit in the Device Control + * register (DCTL.SGOUTNak), set by the application, has taken + * effect in the core. This bit can be cleared by writing the Clear + * Global OUT NAK bit in the Device Control register + * (DCTL.CGOUTNak). + * @ginnakeff: Global IN Non-Periodic NAK Effective (GINNakEff) + * Indicates that the Set Global Non-Periodic IN NAK bit in the + * Device Control register (DCTL.SGNPInNak), set by the + * application, has taken effect in the core. That is, the core has + * sampled the Global IN NAK bit set by the application. This bit + * can be cleared by clearing the Clear Global Non-Periodic IN + * NAK bit in the Device Control register (DCTL.CGNPInNak). + * This interrupt does not necessarily mean that a NAK handshake + * is sent out on the USB. The STALL bit takes precedence over + * the NAK bit. + * @nptxfemp: Non-Periodic TxFIFO Empty (NPTxFEmp) + * This interrupt is asserted when the Non-Periodic TxFIFO is + * either half or completely empty, and there is space for at least + * one entry to be written to the Non-Periodic Transmit Request + * Queue. The half or completely empty status is determined by + * the Non-Periodic TxFIFO Empty Level bit in the Core AHB + * Configuration register (GAHBCFG.NPTxFEmpLvl). + * @rxflvl: RxFIFO Non-Empty (RxFLvl) + * Indicates that there is at least one packet pending to be read + * from the RxFIFO. + * @sof: Start of (micro)Frame (Sof) + * In Host mode, the core sets this bit to indicate that an SOF + * (FS), micro-SOF (HS), or Keep-Alive (LS) is transmitted on the + * USB. The application must write a 1 to this bit to clear the + * interrupt. + * In Device mode, in the core sets this bit to indicate that an + * SOF token has been received on the USB. The application can read + * the Device Status register to get the current (micro)frame + * number. This interrupt is seen only when the core is operating + * at either HS or FS. + * @otgint: OTG Interrupt (OTGInt) + * The core sets this bit to indicate an OTG protocol event. The + * application must read the OTG Interrupt Status (GOTGINT) + * register to determine the exact event that caused this + * interrupt. The application must clear the appropriate status bit + * in the GOTGINT register to clear this bit. + * @modemis: Mode Mismatch Interrupt (ModeMis) + * The core sets this bit when the application is trying to access: + * * A Host mode register, when the core is operating in Device + * mode + * * A Device mode register, when the core is operating in Host + * mode + * The register access is completed on the AHB with an OKAY + * response, but is ignored by the core internally and doesn't + * affect the operation of the core. + * @curmod: Current Mode of Operation (CurMod) + * Indicates the current mode of operation. + * * 1'b0: Device mode + * * 1'b1: Host mode + */ + struct cvmx_usbcx_gintsts_s { + __BITFIELD_FIELD(u32 wkupint : 1, + __BITFIELD_FIELD(u32 sessreqint : 1, + __BITFIELD_FIELD(u32 disconnint : 1, + __BITFIELD_FIELD(u32 conidstschng : 1, + __BITFIELD_FIELD(u32 reserved_27_27 : 1, + __BITFIELD_FIELD(u32 ptxfemp : 1, + __BITFIELD_FIELD(u32 hchint : 1, + __BITFIELD_FIELD(u32 prtint : 1, + __BITFIELD_FIELD(u32 reserved_23_23 : 1, + __BITFIELD_FIELD(u32 fetsusp : 1, + __BITFIELD_FIELD(u32 incomplp : 1, + __BITFIELD_FIELD(u32 incompisoin : 1, + __BITFIELD_FIELD(u32 oepint : 1, + __BITFIELD_FIELD(u32 iepint : 1, + __BITFIELD_FIELD(u32 epmis : 1, + __BITFIELD_FIELD(u32 reserved_16_16 : 1, + __BITFIELD_FIELD(u32 eopf : 1, + __BITFIELD_FIELD(u32 isooutdrop : 1, + __BITFIELD_FIELD(u32 enumdone : 1, + __BITFIELD_FIELD(u32 usbrst : 1, + __BITFIELD_FIELD(u32 usbsusp : 1, + __BITFIELD_FIELD(u32 erlysusp : 1, + __BITFIELD_FIELD(u32 i2cint : 1, + __BITFIELD_FIELD(u32 ulpickint : 1, + __BITFIELD_FIELD(u32 goutnakeff : 1, + __BITFIELD_FIELD(u32 ginnakeff : 1, + __BITFIELD_FIELD(u32 nptxfemp : 1, + __BITFIELD_FIELD(u32 rxflvl : 1, + __BITFIELD_FIELD(u32 sof : 1, + __BITFIELD_FIELD(u32 otgint : 1, + __BITFIELD_FIELD(u32 modemis : 1, + __BITFIELD_FIELD(u32 curmod : 1, + ;)))))))))))))))))))))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_gnptxfsiz + * + * Non-Periodic Transmit FIFO Size Register (GNPTXFSIZ) + * + * The application can program the RAM size and the memory start address for the + * Non-Periodic TxFIFO. + */ +union cvmx_usbcx_gnptxfsiz { + u32 u32; + /** + * struct cvmx_usbcx_gnptxfsiz_s + * @nptxfdep: Non-Periodic TxFIFO Depth (NPTxFDep) + * This value is in terms of 32-bit words. + * Minimum value is 16 + * Maximum value is 32768 + * @nptxfstaddr: Non-Periodic Transmit RAM Start Address (NPTxFStAddr) + * This field contains the memory start address for Non-Periodic + * Transmit FIFO RAM. + */ + struct cvmx_usbcx_gnptxfsiz_s { + __BITFIELD_FIELD(u32 nptxfdep : 16, + __BITFIELD_FIELD(u32 nptxfstaddr : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_gnptxsts + * + * Non-Periodic Transmit FIFO/Queue Status Register (GNPTXSTS) + * + * This read-only register contains the free space information for the + * Non-Periodic TxFIFO and the Non-Periodic Transmit Request Queue. + */ +union cvmx_usbcx_gnptxsts { + u32 u32; + /** + * struct cvmx_usbcx_gnptxsts_s + * @nptxqtop: Top of the Non-Periodic Transmit Request Queue (NPTxQTop) + * Entry in the Non-Periodic Tx Request Queue that is currently + * being processed by the MAC. + * * Bits [30:27]: Channel/endpoint number + * * Bits [26:25]: + * - 2'b00: IN/OUT token + * - 2'b01: Zero-length transmit packet (device IN/host OUT) + * - 2'b10: PING/CSPLIT token + * - 2'b11: Channel halt command + * * Bit [24]: Terminate (last entry for selected channel/endpoint) + * @nptxqspcavail: Non-Periodic Transmit Request Queue Space Available + * (NPTxQSpcAvail) + * Indicates the amount of free space available in the Non- + * Periodic Transmit Request Queue. This queue holds both IN + * and OUT requests in Host mode. Device mode has only IN + * requests. + * * 8'h0: Non-Periodic Transmit Request Queue is full + * * 8'h1: 1 location available + * * 8'h2: 2 locations available + * * n: n locations available (0..8) + * * Others: Reserved + * @nptxfspcavail: Non-Periodic TxFIFO Space Avail (NPTxFSpcAvail) + * Indicates the amount of free space available in the Non- + * Periodic TxFIFO. + * Values are in terms of 32-bit words. + * * 16'h0: Non-Periodic TxFIFO is full + * * 16'h1: 1 word available + * * 16'h2: 2 words available + * * 16'hn: n words available (where 0..32768) + * * 16'h8000: 32768 words available + * * Others: Reserved + */ + struct cvmx_usbcx_gnptxsts_s { + __BITFIELD_FIELD(u32 reserved_31_31 : 1, + __BITFIELD_FIELD(u32 nptxqtop : 7, + __BITFIELD_FIELD(u32 nptxqspcavail : 8, + __BITFIELD_FIELD(u32 nptxfspcavail : 16, + ;)))) + } s; +}; + +/** + * cvmx_usbc#_grstctl + * + * Core Reset Register (GRSTCTL) + * + * The application uses this register to reset various hardware features inside + * the core. + */ +union cvmx_usbcx_grstctl { + u32 u32; + /** + * struct cvmx_usbcx_grstctl_s + * @ahbidle: AHB Master Idle (AHBIdle) + * Indicates that the AHB Master State Machine is in the IDLE + * condition. + * @dmareq: DMA Request Signal (DMAReq) + * Indicates that the DMA request is in progress. Used for debug. + * @txfnum: TxFIFO Number (TxFNum) + * This is the FIFO number that must be flushed using the TxFIFO + * Flush bit. This field must not be changed until the core clears + * the TxFIFO Flush bit. + * * 5'h0: Non-Periodic TxFIFO flush + * * 5'h1: Periodic TxFIFO 1 flush in Device mode or Periodic + * TxFIFO flush in Host mode + * * 5'h2: Periodic TxFIFO 2 flush in Device mode + * - ... + * * 5'hF: Periodic TxFIFO 15 flush in Device mode + * * 5'h10: Flush all the Periodic and Non-Periodic TxFIFOs in the + * core + * @txfflsh: TxFIFO Flush (TxFFlsh) + * This bit selectively flushes a single or all transmit FIFOs, but + * cannot do so if the core is in the midst of a transaction. + * The application must only write this bit after checking that the + * core is neither writing to the TxFIFO nor reading from the + * TxFIFO. + * The application must wait until the core clears this bit before + * performing any operations. This bit takes 8 clocks (of phy_clk + * or hclk, whichever is slower) to clear. + * @rxfflsh: RxFIFO Flush (RxFFlsh) + * The application can flush the entire RxFIFO using this bit, but + * must first ensure that the core is not in the middle of a + * transaction. + * The application must only write to this bit after checking that + * the core is neither reading from the RxFIFO nor writing to the + * RxFIFO. + * The application must wait until the bit is cleared before + * performing any other operations. This bit will take 8 clocks + * (slowest of PHY or AHB clock) to clear. + * @intknqflsh: IN Token Sequence Learning Queue Flush (INTknQFlsh) + * The application writes this bit to flush the IN Token Sequence + * Learning Queue. + * @frmcntrrst: Host Frame Counter Reset (FrmCntrRst) + * The application writes this bit to reset the (micro)frame number + * counter inside the core. When the (micro)frame counter is reset, + * the subsequent SOF sent out by the core will have a + * (micro)frame number of 0. + * @hsftrst: HClk Soft Reset (HSftRst) + * The application uses this bit to flush the control logic in the + * AHB Clock domain. Only AHB Clock Domain pipelines are reset. + * * FIFOs are not flushed with this bit. + * * All state machines in the AHB clock domain are reset to the + * Idle state after terminating the transactions on the AHB, + * following the protocol. + * * CSR control bits used by the AHB clock domain state + * machines are cleared. + * * To clear this interrupt, status mask bits that control the + * interrupt status and are generated by the AHB clock domain + * state machine are cleared. + * * Because interrupt status bits are not cleared, the application + * can get the status of any core events that occurred after it set + * this bit. + * This is a self-clearing bit that the core clears after all + * necessary logic is reset in the core. This may take several + * clocks, depending on the core's current state. + * @csftrst: Core Soft Reset (CSftRst) + * Resets the hclk and phy_clock domains as follows: + * * Clears the interrupts and all the CSR registers except the + * following register bits: + * - PCGCCTL.RstPdwnModule + * - PCGCCTL.GateHclk + * - PCGCCTL.PwrClmp + * - PCGCCTL.StopPPhyLPwrClkSelclk + * - GUSBCFG.PhyLPwrClkSel + * - GUSBCFG.DDRSel + * - GUSBCFG.PHYSel + * - GUSBCFG.FSIntf + * - GUSBCFG.ULPI_UTMI_Sel + * - GUSBCFG.PHYIf + * - HCFG.FSLSPclkSel + * - DCFG.DevSpd + * * All module state machines (except the AHB Slave Unit) are + * reset to the IDLE state, and all the transmit FIFOs and the + * receive FIFO are flushed. + * * Any transactions on the AHB Master are terminated as soon + * as possible, after gracefully completing the last data phase of + * an AHB transfer. Any transactions on the USB are terminated + * immediately. + * The application can write to this bit any time it wants to reset + * the core. This is a self-clearing bit and the core clears this + * bit after all the necessary logic is reset in the core, which + * may take several clocks, depending on the current state of the + * core. Once this bit is cleared software should wait at least 3 + * PHY clocks before doing any access to the PHY domain + * (synchronization delay). Software should also should check that + * bit 31 of this register is 1 (AHB Master is IDLE) before + * starting any operation. + * Typically software reset is used during software development + * and also when you dynamically change the PHY selection bits + * in the USB configuration registers listed above. When you + * change the PHY, the corresponding clock for the PHY is + * selected and used in the PHY domain. Once a new clock is + * selected, the PHY domain has to be reset for proper operation. + */ + struct cvmx_usbcx_grstctl_s { + __BITFIELD_FIELD(u32 ahbidle : 1, + __BITFIELD_FIELD(u32 dmareq : 1, + __BITFIELD_FIELD(u32 reserved_11_29 : 19, + __BITFIELD_FIELD(u32 txfnum : 5, + __BITFIELD_FIELD(u32 txfflsh : 1, + __BITFIELD_FIELD(u32 rxfflsh : 1, + __BITFIELD_FIELD(u32 intknqflsh : 1, + __BITFIELD_FIELD(u32 frmcntrrst : 1, + __BITFIELD_FIELD(u32 hsftrst : 1, + __BITFIELD_FIELD(u32 csftrst : 1, + ;)))))))))) + } s; +}; + +/** + * cvmx_usbc#_grxfsiz + * + * Receive FIFO Size Register (GRXFSIZ) + * + * The application can program the RAM size that must be allocated to the + * RxFIFO. + */ +union cvmx_usbcx_grxfsiz { + u32 u32; + /** + * struct cvmx_usbcx_grxfsiz_s + * @rxfdep: RxFIFO Depth (RxFDep) + * This value is in terms of 32-bit words. + * * Minimum value is 16 + * * Maximum value is 32768 + */ + struct cvmx_usbcx_grxfsiz_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 rxfdep : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_grxstsph + * + * Receive Status Read and Pop Register, Host Mode (GRXSTSPH) + * + * A read to the Receive Status Read and Pop register returns and additionally + * pops the top data entry out of the RxFIFO. + * This Description is only valid when the core is in Host Mode. For Device Mode + * use USBC_GRXSTSPD instead. + * NOTE: GRXSTSPH and GRXSTSPD are physically the same register and share the + * same offset in the O2P USB core. The offset difference shown in this + * document is for software clarity and is actually ignored by the + * hardware. + */ +union cvmx_usbcx_grxstsph { + u32 u32; + /** + * struct cvmx_usbcx_grxstsph_s + * @pktsts: Packet Status (PktSts) + * Indicates the status of the received packet + * * 4'b0010: IN data packet received + * * 4'b0011: IN transfer completed (triggers an interrupt) + * * 4'b0101: Data toggle error (triggers an interrupt) + * * 4'b0111: Channel halted (triggers an interrupt) + * * Others: Reserved + * @dpid: Data PID (DPID) + * * 2'b00: DATA0 + * * 2'b10: DATA1 + * * 2'b01: DATA2 + * * 2'b11: MDATA + * @bcnt: Byte Count (BCnt) + * Indicates the byte count of the received IN data packet + * @chnum: Channel Number (ChNum) + * Indicates the channel number to which the current received + * packet belongs. + */ + struct cvmx_usbcx_grxstsph_s { + __BITFIELD_FIELD(u32 reserved_21_31 : 11, + __BITFIELD_FIELD(u32 pktsts : 4, + __BITFIELD_FIELD(u32 dpid : 2, + __BITFIELD_FIELD(u32 bcnt : 11, + __BITFIELD_FIELD(u32 chnum : 4, + ;))))) + } s; +}; + +/** + * cvmx_usbc#_gusbcfg + * + * Core USB Configuration Register (GUSBCFG) + * + * This register can be used to configure the core after power-on or a changing + * to Host mode or Device mode. It contains USB and USB-PHY related + * configuration parameters. The application must program this register before + * starting any transactions on either the AHB or the USB. Do not make changes + * to this register after the initial programming. + */ +union cvmx_usbcx_gusbcfg { + u32 u32; + /** + * struct cvmx_usbcx_gusbcfg_s + * @otgi2csel: UTMIFS or I2C Interface Select (OtgI2CSel) + * This bit is always 0x0. + * @phylpwrclksel: PHY Low-Power Clock Select (PhyLPwrClkSel) + * Software should set this bit to 0x0. + * Selects either 480-MHz or 48-MHz (low-power) PHY mode. In + * FS and LS modes, the PHY can usually operate on a 48-MHz + * clock to save power. + * * 1'b0: 480-MHz Internal PLL clock + * * 1'b1: 48-MHz External Clock + * In 480 MHz mode, the UTMI interface operates at either 60 or + * 30-MHz, depending upon whether 8- or 16-bit data width is + * selected. In 48-MHz mode, the UTMI interface operates at 48 + * MHz in FS mode and at either 48 or 6 MHz in LS mode + * (depending on the PHY vendor). + * This bit drives the utmi_fsls_low_power core output signal, and + * is valid only for UTMI+ PHYs. + * @usbtrdtim: USB Turnaround Time (USBTrdTim) + * Sets the turnaround time in PHY clocks. + * Specifies the response time for a MAC request to the Packet + * FIFO Controller (PFC) to fetch data from the DFIFO (SPRAM). + * This must be programmed to 0x5. + * @hnpcap: HNP-Capable (HNPCap) + * This bit is always 0x0. + * @srpcap: SRP-Capable (SRPCap) + * This bit is always 0x0. + * @ddrsel: ULPI DDR Select (DDRSel) + * Software should set this bit to 0x0. + * @physel: USB 2.0 High-Speed PHY or USB 1.1 Full-Speed Serial + * Software should set this bit to 0x0. + * @fsintf: Full-Speed Serial Interface Select (FSIntf) + * Software should set this bit to 0x0. + * @ulpi_utmi_sel: ULPI or UTMI+ Select (ULPI_UTMI_Sel) + * This bit is always 0x0. + * @phyif: PHY Interface (PHYIf) + * This bit is always 0x1. + * @toutcal: HS/FS Timeout Calibration (TOutCal) + * The number of PHY clocks that the application programs in this + * field is added to the high-speed/full-speed interpacket timeout + * duration in the core to account for any additional delays + * introduced by the PHY. This may be required, since the delay + * introduced by the PHY in generating the linestate condition may + * vary from one PHY to another. + * The USB standard timeout value for high-speed operation is + * 736 to 816 (inclusive) bit times. The USB standard timeout + * value for full-speed operation is 16 to 18 (inclusive) bit + * times. The application must program this field based on the + * speed of enumeration. The number of bit times added per PHY + * clock are: + * High-speed operation: + * * One 30-MHz PHY clock = 16 bit times + * * One 60-MHz PHY clock = 8 bit times + * Full-speed operation: + * * One 30-MHz PHY clock = 0.4 bit times + * * One 60-MHz PHY clock = 0.2 bit times + * * One 48-MHz PHY clock = 0.25 bit times + */ + struct cvmx_usbcx_gusbcfg_s { + __BITFIELD_FIELD(u32 reserved_17_31 : 15, + __BITFIELD_FIELD(u32 otgi2csel : 1, + __BITFIELD_FIELD(u32 phylpwrclksel : 1, + __BITFIELD_FIELD(u32 reserved_14_14 : 1, + __BITFIELD_FIELD(u32 usbtrdtim : 4, + __BITFIELD_FIELD(u32 hnpcap : 1, + __BITFIELD_FIELD(u32 srpcap : 1, + __BITFIELD_FIELD(u32 ddrsel : 1, + __BITFIELD_FIELD(u32 physel : 1, + __BITFIELD_FIELD(u32 fsintf : 1, + __BITFIELD_FIELD(u32 ulpi_utmi_sel : 1, + __BITFIELD_FIELD(u32 phyif : 1, + __BITFIELD_FIELD(u32 toutcal : 3, + ;))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_haint + * + * Host All Channels Interrupt Register (HAINT) + * + * When a significant event occurs on a channel, the Host All Channels Interrupt + * register interrupts the application using the Host Channels Interrupt bit of + * the Core Interrupt register (GINTSTS.HChInt). This is shown in Interrupt. + * There is one interrupt bit per channel, up to a maximum of 16 bits. Bits in + * this register are set and cleared when the application sets and clears bits + * in the corresponding Host Channel-n Interrupt register. + */ +union cvmx_usbcx_haint { + u32 u32; + /** + * struct cvmx_usbcx_haint_s + * @haint: Channel Interrupts (HAINT) + * One bit per channel: Bit 0 for Channel 0, bit 15 for Channel 15 + */ + struct cvmx_usbcx_haint_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 haint : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_haintmsk + * + * Host All Channels Interrupt Mask Register (HAINTMSK) + * + * The Host All Channel Interrupt Mask register works with the Host All Channel + * Interrupt register to interrupt the application when an event occurs on a + * channel. There is one interrupt mask bit per channel, up to a maximum of 16 + * bits. + * Mask interrupt: 1'b0 Unmask interrupt: 1'b1 + */ +union cvmx_usbcx_haintmsk { + u32 u32; + /** + * struct cvmx_usbcx_haintmsk_s + * @haintmsk: Channel Interrupt Mask (HAINTMsk) + * One bit per channel: Bit 0 for channel 0, bit 15 for channel 15 + */ + struct cvmx_usbcx_haintmsk_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 haintmsk : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hcchar# + * + * Host Channel-n Characteristics Register (HCCHAR) + * + */ +union cvmx_usbcx_hccharx { + u32 u32; + /** + * struct cvmx_usbcx_hccharx_s + * @chena: Channel Enable (ChEna) + * This field is set by the application and cleared by the OTG + * host. + * * 1'b0: Channel disabled + * * 1'b1: Channel enabled + * @chdis: Channel Disable (ChDis) + * The application sets this bit to stop transmitting/receiving + * data on a channel, even before the transfer for that channel is + * complete. The application must wait for the Channel Disabled + * interrupt before treating the channel as disabled. + * @oddfrm: Odd Frame (OddFrm) + * This field is set (reset) by the application to indicate that + * the OTG host must perform a transfer in an odd (micro)frame. + * This field is applicable for only periodic (isochronous and + * interrupt) transactions. + * * 1'b0: Even (micro)frame + * * 1'b1: Odd (micro)frame + * @devaddr: Device Address (DevAddr) + * This field selects the specific device serving as the data + * source or sink. + * @ec: Multi Count (MC) / Error Count (EC) + * When the Split Enable bit of the Host Channel-n Split Control + * register (HCSPLTn.SpltEna) is reset (1'b0), this field indicates + * to the host the number of transactions that should be executed + * per microframe for this endpoint. + * * 2'b00: Reserved. This field yields undefined results. + * * 2'b01: 1 transaction + * * 2'b10: 2 transactions to be issued for this endpoint per + * microframe + * * 2'b11: 3 transactions to be issued for this endpoint per + * microframe + * When HCSPLTn.SpltEna is set (1'b1), this field indicates the + * number of immediate retries to be performed for a periodic split + * transactions on transaction errors. This field must be set to at + * least 2'b01. + * @eptype: Endpoint Type (EPType) + * Indicates the transfer type selected. + * * 2'b00: Control + * * 2'b01: Isochronous + * * 2'b10: Bulk + * * 2'b11: Interrupt + * @lspddev: Low-Speed Device (LSpdDev) + * This field is set by the application to indicate that this + * channel is communicating to a low-speed device. + * @epdir: Endpoint Direction (EPDir) + * Indicates whether the transaction is IN or OUT. + * * 1'b0: OUT + * * 1'b1: IN + * @epnum: Endpoint Number (EPNum) + * Indicates the endpoint number on the device serving as the + * data source or sink. + * @mps: Maximum Packet Size (MPS) + * Indicates the maximum packet size of the associated endpoint. + */ + struct cvmx_usbcx_hccharx_s { + __BITFIELD_FIELD(u32 chena : 1, + __BITFIELD_FIELD(u32 chdis : 1, + __BITFIELD_FIELD(u32 oddfrm : 1, + __BITFIELD_FIELD(u32 devaddr : 7, + __BITFIELD_FIELD(u32 ec : 2, + __BITFIELD_FIELD(u32 eptype : 2, + __BITFIELD_FIELD(u32 lspddev : 1, + __BITFIELD_FIELD(u32 reserved_16_16 : 1, + __BITFIELD_FIELD(u32 epdir : 1, + __BITFIELD_FIELD(u32 epnum : 4, + __BITFIELD_FIELD(u32 mps : 11, + ;))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hcfg + * + * Host Configuration Register (HCFG) + * + * This register configures the core after power-on. Do not make changes to this + * register after initializing the host. + */ +union cvmx_usbcx_hcfg { + u32 u32; + /** + * struct cvmx_usbcx_hcfg_s + * @fslssupp: FS- and LS-Only Support (FSLSSupp) + * The application uses this bit to control the core's enumeration + * speed. Using this bit, the application can make the core + * enumerate as a FS host, even if the connected device supports + * HS traffic. Do not make changes to this field after initial + * programming. + * * 1'b0: HS/FS/LS, based on the maximum speed supported by + * the connected device + * * 1'b1: FS/LS-only, even if the connected device can support HS + * @fslspclksel: FS/LS PHY Clock Select (FSLSPclkSel) + * When the core is in FS Host mode + * * 2'b00: PHY clock is running at 30/60 MHz + * * 2'b01: PHY clock is running at 48 MHz + * * Others: Reserved + * When the core is in LS Host mode + * * 2'b00: PHY clock is running at 30/60 MHz. When the + * UTMI+/ULPI PHY Low Power mode is not selected, use + * 30/60 MHz. + * * 2'b01: PHY clock is running at 48 MHz. When the UTMI+ + * PHY Low Power mode is selected, use 48MHz if the PHY + * supplies a 48 MHz clock during LS mode. + * * 2'b10: PHY clock is running at 6 MHz. In USB 1.1 FS mode, + * use 6 MHz when the UTMI+ PHY Low Power mode is + * selected and the PHY supplies a 6 MHz clock during LS + * mode. If you select a 6 MHz clock during LS mode, you must + * do a soft reset. + * * 2'b11: Reserved + */ + struct cvmx_usbcx_hcfg_s { + __BITFIELD_FIELD(u32 reserved_3_31 : 29, + __BITFIELD_FIELD(u32 fslssupp : 1, + __BITFIELD_FIELD(u32 fslspclksel : 2, + ;))) + } s; +}; + +/** + * cvmx_usbc#_hcint# + * + * Host Channel-n Interrupt Register (HCINT) + * + * This register indicates the status of a channel with respect to USB- and + * AHB-related events. The application must read this register when the Host + * Channels Interrupt bit of the Core Interrupt register (GINTSTS.HChInt) is + * set. Before the application can read this register, it must first read + * the Host All Channels Interrupt (HAINT) register to get the exact channel + * number for the Host Channel-n Interrupt register. The application must clear + * the appropriate bit in this register to clear the corresponding bits in the + * HAINT and GINTSTS registers. + */ +union cvmx_usbcx_hcintx { + u32 u32; + /** + * struct cvmx_usbcx_hcintx_s + * @datatglerr: Data Toggle Error (DataTglErr) + * @frmovrun: Frame Overrun (FrmOvrun) + * @bblerr: Babble Error (BblErr) + * @xacterr: Transaction Error (XactErr) + * @nyet: NYET Response Received Interrupt (NYET) + * @ack: ACK Response Received Interrupt (ACK) + * @nak: NAK Response Received Interrupt (NAK) + * @stall: STALL Response Received Interrupt (STALL) + * @ahberr: This bit is always 0x0. + * @chhltd: Channel Halted (ChHltd) + * Indicates the transfer completed abnormally either because of + * any USB transaction error or in response to disable request by + * the application. + * @xfercompl: Transfer Completed (XferCompl) + * Transfer completed normally without any errors. + */ + struct cvmx_usbcx_hcintx_s { + __BITFIELD_FIELD(u32 reserved_11_31 : 21, + __BITFIELD_FIELD(u32 datatglerr : 1, + __BITFIELD_FIELD(u32 frmovrun : 1, + __BITFIELD_FIELD(u32 bblerr : 1, + __BITFIELD_FIELD(u32 xacterr : 1, + __BITFIELD_FIELD(u32 nyet : 1, + __BITFIELD_FIELD(u32 ack : 1, + __BITFIELD_FIELD(u32 nak : 1, + __BITFIELD_FIELD(u32 stall : 1, + __BITFIELD_FIELD(u32 ahberr : 1, + __BITFIELD_FIELD(u32 chhltd : 1, + __BITFIELD_FIELD(u32 xfercompl : 1, + ;)))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hcintmsk# + * + * Host Channel-n Interrupt Mask Register (HCINTMSKn) + * + * This register reflects the mask for each channel status described in the + * previous section. + * Mask interrupt: 1'b0 Unmask interrupt: 1'b1 + */ +union cvmx_usbcx_hcintmskx { + u32 u32; + /** + * struct cvmx_usbcx_hcintmskx_s + * @datatglerrmsk: Data Toggle Error Mask (DataTglErrMsk) + * @frmovrunmsk: Frame Overrun Mask (FrmOvrunMsk) + * @bblerrmsk: Babble Error Mask (BblErrMsk) + * @xacterrmsk: Transaction Error Mask (XactErrMsk) + * @nyetmsk: NYET Response Received Interrupt Mask (NyetMsk) + * @ackmsk: ACK Response Received Interrupt Mask (AckMsk) + * @nakmsk: NAK Response Received Interrupt Mask (NakMsk) + * @stallmsk: STALL Response Received Interrupt Mask (StallMsk) + * @ahberrmsk: AHB Error Mask (AHBErrMsk) + * @chhltdmsk: Channel Halted Mask (ChHltdMsk) + * @xfercomplmsk: Transfer Completed Mask (XferComplMsk) + */ + struct cvmx_usbcx_hcintmskx_s { + __BITFIELD_FIELD(u32 reserved_11_31 : 21, + __BITFIELD_FIELD(u32 datatglerrmsk : 1, + __BITFIELD_FIELD(u32 frmovrunmsk : 1, + __BITFIELD_FIELD(u32 bblerrmsk : 1, + __BITFIELD_FIELD(u32 xacterrmsk : 1, + __BITFIELD_FIELD(u32 nyetmsk : 1, + __BITFIELD_FIELD(u32 ackmsk : 1, + __BITFIELD_FIELD(u32 nakmsk : 1, + __BITFIELD_FIELD(u32 stallmsk : 1, + __BITFIELD_FIELD(u32 ahberrmsk : 1, + __BITFIELD_FIELD(u32 chhltdmsk : 1, + __BITFIELD_FIELD(u32 xfercomplmsk : 1, + ;)))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hcsplt# + * + * Host Channel-n Split Control Register (HCSPLT) + * + */ +union cvmx_usbcx_hcspltx { + u32 u32; + /** + * struct cvmx_usbcx_hcspltx_s + * @spltena: Split Enable (SpltEna) + * The application sets this field to indicate that this channel is + * enabled to perform split transactions. + * @compsplt: Do Complete Split (CompSplt) + * The application sets this field to request the OTG host to + * perform a complete split transaction. + * @xactpos: Transaction Position (XactPos) + * This field is used to determine whether to send all, first, + * middle, or last payloads with each OUT transaction. + * * 2'b11: All. This is the entire data payload is of this + * transaction (which is less than or equal to 188 bytes). + * * 2'b10: Begin. This is the first data payload of this + * transaction (which is larger than 188 bytes). + * * 2'b00: Mid. This is the middle payload of this transaction + * (which is larger than 188 bytes). + * * 2'b01: End. This is the last payload of this transaction + * (which is larger than 188 bytes). + * @hubaddr: Hub Address (HubAddr) + * This field holds the device address of the transaction + * translator's hub. + * @prtaddr: Port Address (PrtAddr) + * This field is the port number of the recipient transaction + * translator. + */ + struct cvmx_usbcx_hcspltx_s { + __BITFIELD_FIELD(u32 spltena : 1, + __BITFIELD_FIELD(u32 reserved_17_30 : 14, + __BITFIELD_FIELD(u32 compsplt : 1, + __BITFIELD_FIELD(u32 xactpos : 2, + __BITFIELD_FIELD(u32 hubaddr : 7, + __BITFIELD_FIELD(u32 prtaddr : 7, + ;)))))) + } s; +}; + +/** + * cvmx_usbc#_hctsiz# + * + * Host Channel-n Transfer Size Register (HCTSIZ) + * + */ +union cvmx_usbcx_hctsizx { + u32 u32; + /** + * struct cvmx_usbcx_hctsizx_s + * @dopng: Do Ping (DoPng) + * Setting this field to 1 directs the host to do PING protocol. + * @pid: PID (Pid) + * The application programs this field with the type of PID to use + * for the initial transaction. The host will maintain this field + * for the rest of the transfer. + * * 2'b00: DATA0 + * * 2'b01: DATA2 + * * 2'b10: DATA1 + * * 2'b11: MDATA (non-control)/SETUP (control) + * @pktcnt: Packet Count (PktCnt) + * This field is programmed by the application with the expected + * number of packets to be transmitted (OUT) or received (IN). + * The host decrements this count on every successful + * transmission or reception of an OUT/IN packet. Once this count + * reaches zero, the application is interrupted to indicate normal + * completion. + * @xfersize: Transfer Size (XferSize) + * For an OUT, this field is the number of data bytes the host will + * send during the transfer. + * For an IN, this field is the buffer size that the application + * has reserved for the transfer. The application is expected to + * program this field as an integer multiple of the maximum packet + * size for IN transactions (periodic and non-periodic). + */ + struct cvmx_usbcx_hctsizx_s { + __BITFIELD_FIELD(u32 dopng : 1, + __BITFIELD_FIELD(u32 pid : 2, + __BITFIELD_FIELD(u32 pktcnt : 10, + __BITFIELD_FIELD(u32 xfersize : 19, + ;)))) + } s; +}; + +/** + * cvmx_usbc#_hfir + * + * Host Frame Interval Register (HFIR) + * + * This register stores the frame interval information for the current speed to + * which the O2P USB core has enumerated. + */ +union cvmx_usbcx_hfir { + u32 u32; + /** + * struct cvmx_usbcx_hfir_s + * @frint: Frame Interval (FrInt) + * The value that the application programs to this field specifies + * the interval between two consecutive SOFs (FS) or micro- + * SOFs (HS) or Keep-Alive tokens (HS). This field contains the + * number of PHY clocks that constitute the required frame + * interval. The default value set in this field for a FS operation + * when the PHY clock frequency is 60 MHz. The application can + * write a value to this register only after the Port Enable bit of + * the Host Port Control and Status register (HPRT.PrtEnaPort) + * has been set. If no value is programmed, the core calculates + * the value based on the PHY clock specified in the FS/LS PHY + * Clock Select field of the Host Configuration register + * (HCFG.FSLSPclkSel). Do not change the value of this field + * after the initial configuration. + * * 125 us (PHY clock frequency for HS) + * * 1 ms (PHY clock frequency for FS/LS) + */ + struct cvmx_usbcx_hfir_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 frint : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hfnum + * + * Host Frame Number/Frame Time Remaining Register (HFNUM) + * + * This register indicates the current frame number. + * It also indicates the time remaining (in terms of the number of PHY clocks) + * in the current (micro)frame. + */ +union cvmx_usbcx_hfnum { + u32 u32; + /** + * struct cvmx_usbcx_hfnum_s + * @frrem: Frame Time Remaining (FrRem) + * Indicates the amount of time remaining in the current + * microframe (HS) or frame (FS/LS), in terms of PHY clocks. + * This field decrements on each PHY clock. When it reaches + * zero, this field is reloaded with the value in the Frame + * Interval register and a new SOF is transmitted on the USB. + * @frnum: Frame Number (FrNum) + * This field increments when a new SOF is transmitted on the + * USB, and is reset to 0 when it reaches 16'h3FFF. + */ + struct cvmx_usbcx_hfnum_s { + __BITFIELD_FIELD(u32 frrem : 16, + __BITFIELD_FIELD(u32 frnum : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hprt + * + * Host Port Control and Status Register (HPRT) + * + * This register is available in both Host and Device modes. + * Currently, the OTG Host supports only one port. + * A single register holds USB port-related information such as USB reset, + * enable, suspend, resume, connect status, and test mode for each port. The + * R_SS_WC bits in this register can trigger an interrupt to the application + * through the Host Port Interrupt bit of the Core Interrupt register + * (GINTSTS.PrtInt). On a Port Interrupt, the application must read this + * register and clear the bit that caused the interrupt. For the R_SS_WC bits, + * the application must write a 1 to the bit to clear the interrupt. + */ +union cvmx_usbcx_hprt { + u32 u32; + /** + * struct cvmx_usbcx_hprt_s + * @prtspd: Port Speed (PrtSpd) + * Indicates the speed of the device attached to this port. + * * 2'b00: High speed + * * 2'b01: Full speed + * * 2'b10: Low speed + * * 2'b11: Reserved + * @prttstctl: Port Test Control (PrtTstCtl) + * The application writes a nonzero value to this field to put + * the port into a Test mode, and the corresponding pattern is + * signaled on the port. + * * 4'b0000: Test mode disabled + * * 4'b0001: Test_J mode + * * 4'b0010: Test_K mode + * * 4'b0011: Test_SE0_NAK mode + * * 4'b0100: Test_Packet mode + * * 4'b0101: Test_Force_Enable + * * Others: Reserved + * PrtSpd must be zero (i.e. the interface must be in high-speed + * mode) to use the PrtTstCtl test modes. + * @prtpwr: Port Power (PrtPwr) + * The application uses this field to control power to this port, + * and the core clears this bit on an overcurrent condition. + * * 1'b0: Power off + * * 1'b1: Power on + * @prtlnsts: Port Line Status (PrtLnSts) + * Indicates the current logic level USB data lines + * * Bit [10]: Logic level of D- + * * Bit [11]: Logic level of D+ + * @prtrst: Port Reset (PrtRst) + * When the application sets this bit, a reset sequence is + * started on this port. The application must time the reset + * period and clear this bit after the reset sequence is + * complete. + * * 1'b0: Port not in reset + * * 1'b1: Port in reset + * The application must leave this bit set for at least a + * minimum duration mentioned below to start a reset on the + * port. The application can leave it set for another 10 ms in + * addition to the required minimum duration, before clearing + * the bit, even though there is no maximum limit set by the + * USB standard. + * * High speed: 50 ms + * * Full speed/Low speed: 10 ms + * @prtsusp: Port Suspend (PrtSusp) + * The application sets this bit to put this port in Suspend + * mode. The core only stops sending SOFs when this is set. + * To stop the PHY clock, the application must set the Port + * Clock Stop bit, which will assert the suspend input pin of + * the PHY. + * The read value of this bit reflects the current suspend + * status of the port. This bit is cleared by the core after a + * remote wakeup signal is detected or the application sets + * the Port Reset bit or Port Resume bit in this register or the + * Resume/Remote Wakeup Detected Interrupt bit or + * Disconnect Detected Interrupt bit in the Core Interrupt + * register (GINTSTS.WkUpInt or GINTSTS.DisconnInt, + * respectively). + * * 1'b0: Port not in Suspend mode + * * 1'b1: Port in Suspend mode + * @prtres: Port Resume (PrtRes) + * The application sets this bit to drive resume signaling on + * the port. The core continues to drive the resume signal + * until the application clears this bit. + * If the core detects a USB remote wakeup sequence, as + * indicated by the Port Resume/Remote Wakeup Detected + * Interrupt bit of the Core Interrupt register + * (GINTSTS.WkUpInt), the core starts driving resume + * signaling without application intervention and clears this bit + * when it detects a disconnect condition. The read value of + * this bit indicates whether the core is currently driving + * resume signaling. + * * 1'b0: No resume driven + * * 1'b1: Resume driven + * @prtovrcurrchng: Port Overcurrent Change (PrtOvrCurrChng) + * The core sets this bit when the status of the Port + * Overcurrent Active bit (bit 4) in this register changes. + * @prtovrcurract: Port Overcurrent Active (PrtOvrCurrAct) + * Indicates the overcurrent condition of the port. + * * 1'b0: No overcurrent condition + * * 1'b1: Overcurrent condition + * @prtenchng: Port Enable/Disable Change (PrtEnChng) + * The core sets this bit when the status of the Port Enable bit + * [2] of this register changes. + * @prtena: Port Enable (PrtEna) + * A port is enabled only by the core after a reset sequence, + * and is disabled by an overcurrent condition, a disconnect + * condition, or by the application clearing this bit. The + * application cannot set this bit by a register write. It can only + * clear it to disable the port. This bit does not trigger any + * interrupt to the application. + * * 1'b0: Port disabled + * * 1'b1: Port enabled + * @prtconndet: Port Connect Detected (PrtConnDet) + * The core sets this bit when a device connection is detected + * to trigger an interrupt to the application using the Host Port + * Interrupt bit of the Core Interrupt register (GINTSTS.PrtInt). + * The application must write a 1 to this bit to clear the + * interrupt. + * @prtconnsts: Port Connect Status (PrtConnSts) + * * 0: No device is attached to the port. + * * 1: A device is attached to the port. + */ + struct cvmx_usbcx_hprt_s { + __BITFIELD_FIELD(u32 reserved_19_31 : 13, + __BITFIELD_FIELD(u32 prtspd : 2, + __BITFIELD_FIELD(u32 prttstctl : 4, + __BITFIELD_FIELD(u32 prtpwr : 1, + __BITFIELD_FIELD(u32 prtlnsts : 2, + __BITFIELD_FIELD(u32 reserved_9_9 : 1, + __BITFIELD_FIELD(u32 prtrst : 1, + __BITFIELD_FIELD(u32 prtsusp : 1, + __BITFIELD_FIELD(u32 prtres : 1, + __BITFIELD_FIELD(u32 prtovrcurrchng : 1, + __BITFIELD_FIELD(u32 prtovrcurract : 1, + __BITFIELD_FIELD(u32 prtenchng : 1, + __BITFIELD_FIELD(u32 prtena : 1, + __BITFIELD_FIELD(u32 prtconndet : 1, + __BITFIELD_FIELD(u32 prtconnsts : 1, + ;))))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hptxfsiz + * + * Host Periodic Transmit FIFO Size Register (HPTXFSIZ) + * + * This register holds the size and the memory start address of the Periodic + * TxFIFO, as shown in Figures 310 and 311. + */ +union cvmx_usbcx_hptxfsiz { + u32 u32; + /** + * struct cvmx_usbcx_hptxfsiz_s + * @ptxfsize: Host Periodic TxFIFO Depth (PTxFSize) + * This value is in terms of 32-bit words. + * * Minimum value is 16 + * * Maximum value is 32768 + * @ptxfstaddr: Host Periodic TxFIFO Start Address (PTxFStAddr) + */ + struct cvmx_usbcx_hptxfsiz_s { + __BITFIELD_FIELD(u32 ptxfsize : 16, + __BITFIELD_FIELD(u32 ptxfstaddr : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hptxsts + * + * Host Periodic Transmit FIFO/Queue Status Register (HPTXSTS) + * + * This read-only register contains the free space information for the Periodic + * TxFIFO and the Periodic Transmit Request Queue + */ +union cvmx_usbcx_hptxsts { + u32 u32; + /** + * struct cvmx_usbcx_hptxsts_s + * @ptxqtop: Top of the Periodic Transmit Request Queue (PTxQTop) + * This indicates the entry in the Periodic Tx Request Queue that + * is currently being processes by the MAC. + * This register is used for debugging. + * * Bit [31]: Odd/Even (micro)frame + * - 1'b0: send in even (micro)frame + * - 1'b1: send in odd (micro)frame + * * Bits [30:27]: Channel/endpoint number + * * Bits [26:25]: Type + * - 2'b00: IN/OUT + * - 2'b01: Zero-length packet + * - 2'b10: CSPLIT + * - 2'b11: Disable channel command + * * Bit [24]: Terminate (last entry for the selected + * channel/endpoint) + * @ptxqspcavail: Periodic Transmit Request Queue Space Available + * (PTxQSpcAvail) + * Indicates the number of free locations available to be written + * in the Periodic Transmit Request Queue. This queue holds both + * IN and OUT requests. + * * 8'h0: Periodic Transmit Request Queue is full + * * 8'h1: 1 location available + * * 8'h2: 2 locations available + * * n: n locations available (0..8) + * * Others: Reserved + * @ptxfspcavail: Periodic Transmit Data FIFO Space Available + * (PTxFSpcAvail) + * Indicates the number of free locations available to be written + * to in the Periodic TxFIFO. + * Values are in terms of 32-bit words + * * 16'h0: Periodic TxFIFO is full + * * 16'h1: 1 word available + * * 16'h2: 2 words available + * * 16'hn: n words available (where 0..32768) + * * 16'h8000: 32768 words available + * * Others: Reserved + */ + struct cvmx_usbcx_hptxsts_s { + __BITFIELD_FIELD(u32 ptxqtop : 8, + __BITFIELD_FIELD(u32 ptxqspcavail : 8, + __BITFIELD_FIELD(u32 ptxfspcavail : 16, + ;))) + } s; +}; + +/** + * cvmx_usbn#_clk_ctl + * + * USBN_CLK_CTL = USBN's Clock Control + * + * This register is used to control the frequency of the hclk and the + * hreset and phy_rst signals. + */ +union cvmx_usbnx_clk_ctl { + u64 u64; + /** + * struct cvmx_usbnx_clk_ctl_s + * @divide2: The 'hclk' used by the USB subsystem is derived + * from the eclk. + * Also see the field DIVIDE. DIVIDE2<1> must currently + * be zero because it is not implemented, so the maximum + * ratio of eclk/hclk is currently 16. + * The actual divide number for hclk is: + * (DIVIDE2 + 1) * (DIVIDE + 1) + * @hclk_rst: When this field is '0' the HCLK-DIVIDER used to + * generate the hclk in the USB Subsystem is held + * in reset. This bit must be set to '0' before + * changing the value os DIVIDE in this register. + * The reset to the HCLK_DIVIDERis also asserted + * when core reset is asserted. + * @p_x_on: Force USB-PHY on during suspend. + * '1' USB-PHY XO block is powered-down during + * suspend. + * '0' USB-PHY XO block is powered-up during + * suspend. + * The value of this field must be set while POR is + * active. + * @p_rtype: PHY reference clock type + * On CN50XX/CN52XX/CN56XX the values are: + * '0' The USB-PHY uses a 12MHz crystal as a clock source + * at the USB_XO and USB_XI pins. + * '1' Reserved. + * '2' The USB_PHY uses 12/24/48MHz 2.5V board clock at the + * USB_XO pin. USB_XI should be tied to ground in this + * case. + * '3' Reserved. + * On CN3xxx bits 14 and 15 are p_xenbn and p_rclk and values are: + * '0' Reserved. + * '1' Reserved. + * '2' The PHY PLL uses the XO block output as a reference. + * The XO block uses an external clock supplied on the + * XO pin. USB_XI should be tied to ground for this + * usage. + * '3' The XO block uses the clock from a crystal. + * @p_com_on: '0' Force USB-PHY XO Bias, Bandgap and PLL to + * remain powered in Suspend Mode. + * '1' The USB-PHY XO Bias, Bandgap and PLL are + * powered down in suspend mode. + * The value of this field must be set while POR is + * active. + * @p_c_sel: Phy clock speed select. + * Selects the reference clock / crystal frequency. + * '11': Reserved + * '10': 48 MHz (reserved when a crystal is used) + * '01': 24 MHz (reserved when a crystal is used) + * '00': 12 MHz + * The value of this field must be set while POR is + * active. + * NOTE: if a crystal is used as a reference clock, + * this field must be set to 12 MHz. + * @cdiv_byp: Used to enable the bypass input to the USB_CLK_DIV. + * @sd_mode: Scaledown mode for the USBC. Control timing events + * in the USBC, for normal operation this must be '0'. + * @s_bist: Starts bist on the hclk memories, during the '0' + * to '1' transition. + * @por: Power On Reset for the PHY. + * Resets all the PHYS registers and state machines. + * @enable: When '1' allows the generation of the hclk. When + * '0' the hclk will not be generated. SEE DIVIDE + * field of this register. + * @prst: When this field is '0' the reset associated with + * the phy_clk functionality in the USB Subsystem is + * help in reset. This bit should not be set to '1' + * until the time it takes 6 clocks (hclk or phy_clk, + * whichever is slower) has passed. Under normal + * operation once this bit is set to '1' it should not + * be set to '0'. + * @hrst: When this field is '0' the reset associated with + * the hclk functioanlity in the USB Subsystem is + * held in reset.This bit should not be set to '1' + * until 12ms after phy_clk is stable. Under normal + * operation, once this bit is set to '1' it should + * not be set to '0'. + * @divide: The frequency of 'hclk' used by the USB subsystem + * is the eclk frequency divided by the value of + * (DIVIDE2 + 1) * (DIVIDE + 1), also see the field + * DIVIDE2 of this register. + * The hclk frequency should be less than 125Mhz. + * After writing a value to this field the SW should + * read the field for the value written. + * The ENABLE field of this register should not be set + * until AFTER this field is set and then read. + */ + struct cvmx_usbnx_clk_ctl_s { + __BITFIELD_FIELD(u64 reserved_20_63 : 44, + __BITFIELD_FIELD(u64 divide2 : 2, + __BITFIELD_FIELD(u64 hclk_rst : 1, + __BITFIELD_FIELD(u64 p_x_on : 1, + __BITFIELD_FIELD(u64 p_rtype : 2, + __BITFIELD_FIELD(u64 p_com_on : 1, + __BITFIELD_FIELD(u64 p_c_sel : 2, + __BITFIELD_FIELD(u64 cdiv_byp : 1, + __BITFIELD_FIELD(u64 sd_mode : 2, + __BITFIELD_FIELD(u64 s_bist : 1, + __BITFIELD_FIELD(u64 por : 1, + __BITFIELD_FIELD(u64 enable : 1, + __BITFIELD_FIELD(u64 prst : 1, + __BITFIELD_FIELD(u64 hrst : 1, + __BITFIELD_FIELD(u64 divide : 3, + ;))))))))))))))) + } s; +}; + +/** + * cvmx_usbn#_usbp_ctl_status + * + * USBN_USBP_CTL_STATUS = USBP Control And Status Register + * + * Contains general control and status information for the USBN block. + */ +union cvmx_usbnx_usbp_ctl_status { + u64 u64; + /** + * struct cvmx_usbnx_usbp_ctl_status_s + * @txrisetune: HS Transmitter Rise/Fall Time Adjustment + * @txvreftune: HS DC Voltage Level Adjustment + * @txfslstune: FS/LS Source Impedance Adjustment + * @txhsxvtune: Transmitter High-Speed Crossover Adjustment + * @sqrxtune: Squelch Threshold Adjustment + * @compdistune: Disconnect Threshold Adjustment + * @otgtune: VBUS Valid Threshold Adjustment + * @otgdisable: OTG Block Disable + * @portreset: Per_Port Reset + * @drvvbus: Drive VBUS + * @lsbist: Low-Speed BIST Enable. + * @fsbist: Full-Speed BIST Enable. + * @hsbist: High-Speed BIST Enable. + * @bist_done: PHY Bist Done. + * Asserted at the end of the PHY BIST sequence. + * @bist_err: PHY Bist Error. + * Indicates an internal error was detected during + * the BIST sequence. + * @tdata_out: PHY Test Data Out. + * Presents either internally generated signals or + * test register contents, based upon the value of + * test_data_out_sel. + * @siddq: Drives the USBP (USB-PHY) SIDDQ input. + * Normally should be set to zero. + * When customers have no intent to use USB PHY + * interface, they should: + * - still provide 3.3V to USB_VDD33, and + * - tie USB_REXT to 3.3V supply, and + * - set USBN*_USBP_CTL_STATUS[SIDDQ]=1 + * @txpreemphasistune: HS Transmitter Pre-Emphasis Enable + * @dma_bmode: When set to 1 the L2C DMA address will be updated + * with byte-counts between packets. When set to 0 + * the L2C DMA address is incremented to the next + * 4-byte aligned address after adding byte-count. + * @usbc_end: Bigendian input to the USB Core. This should be + * set to '0' for operation. + * @usbp_bist: PHY, This is cleared '0' to run BIST on the USBP. + * @tclk: PHY Test Clock, used to load TDATA_IN to the USBP. + * @dp_pulld: PHY DP_PULLDOWN input to the USB-PHY. + * This signal enables the pull-down resistance on + * the D+ line. '1' pull down-resistance is connected + * to D+/ '0' pull down resistance is not connected + * to D+. When an A/B device is acting as a host + * (downstream-facing port), dp_pulldown and + * dm_pulldown are enabled. This must not toggle + * during normal operation. + * @dm_pulld: PHY DM_PULLDOWN input to the USB-PHY. + * This signal enables the pull-down resistance on + * the D- line. '1' pull down-resistance is connected + * to D-. '0' pull down resistance is not connected + * to D-. When an A/B device is acting as a host + * (downstream-facing port), dp_pulldown and + * dm_pulldown are enabled. This must not toggle + * during normal operation. + * @hst_mode: When '0' the USB is acting as HOST, when '1' + * USB is acting as device. This field needs to be + * set while the USB is in reset. + * @tuning: Transmitter Tuning for High-Speed Operation. + * Tunes the current supply and rise/fall output + * times for high-speed operation. + * [20:19] == 11: Current supply increased + * approximately 9% + * [20:19] == 10: Current supply increased + * approximately 4.5% + * [20:19] == 01: Design default. + * [20:19] == 00: Current supply decreased + * approximately 4.5% + * [22:21] == 11: Rise and fall times are increased. + * [22:21] == 10: Design default. + * [22:21] == 01: Rise and fall times are decreased. + * [22:21] == 00: Rise and fall times are decreased + * further as compared to the 01 setting. + * @tx_bs_enh: Transmit Bit Stuffing on [15:8]. + * Enables or disables bit stuffing on data[15:8] + * when bit-stuffing is enabled. + * @tx_bs_en: Transmit Bit Stuffing on [7:0]. + * Enables or disables bit stuffing on data[7:0] + * when bit-stuffing is enabled. + * @loop_enb: PHY Loopback Test Enable. + * '1': During data transmission the receive is + * enabled. + * '0': During data transmission the receive is + * disabled. + * Must be '0' for normal operation. + * @vtest_enb: Analog Test Pin Enable. + * '1' The PHY's analog_test pin is enabled for the + * input and output of applicable analog test signals. + * '0' THe analog_test pin is disabled. + * @bist_enb: Built-In Self Test Enable. + * Used to activate BIST in the PHY. + * @tdata_sel: Test Data Out Select. + * '1' test_data_out[3:0] (PHY) register contents + * are output. '0' internally generated signals are + * output. + * @taddr_in: Mode Address for Test Interface. + * Specifies the register address for writing to or + * reading from the PHY test interface register. + * @tdata_in: Internal Testing Register Input Data and Select + * This is a test bus. Data is present on [3:0], + * and its corresponding select (enable) is present + * on bits [7:4]. + * @ate_reset: Reset input from automatic test equipment. + * This is a test signal. When the USB Core is + * powered up (not in Susned Mode), an automatic + * tester can use this to disable phy_clock and + * free_clk, then re-enable them with an aligned + * phase. + * '1': The phy_clk and free_clk outputs are + * disabled. "0": The phy_clock and free_clk outputs + * are available within a specific period after the + * de-assertion. + */ + struct cvmx_usbnx_usbp_ctl_status_s { + __BITFIELD_FIELD(u64 txrisetune : 1, + __BITFIELD_FIELD(u64 txvreftune : 4, + __BITFIELD_FIELD(u64 txfslstune : 4, + __BITFIELD_FIELD(u64 txhsxvtune : 2, + __BITFIELD_FIELD(u64 sqrxtune : 3, + __BITFIELD_FIELD(u64 compdistune : 3, + __BITFIELD_FIELD(u64 otgtune : 3, + __BITFIELD_FIELD(u64 otgdisable : 1, + __BITFIELD_FIELD(u64 portreset : 1, + __BITFIELD_FIELD(u64 drvvbus : 1, + __BITFIELD_FIELD(u64 lsbist : 1, + __BITFIELD_FIELD(u64 fsbist : 1, + __BITFIELD_FIELD(u64 hsbist : 1, + __BITFIELD_FIELD(u64 bist_done : 1, + __BITFIELD_FIELD(u64 bist_err : 1, + __BITFIELD_FIELD(u64 tdata_out : 4, + __BITFIELD_FIELD(u64 siddq : 1, + __BITFIELD_FIELD(u64 txpreemphasistune : 1, + __BITFIELD_FIELD(u64 dma_bmode : 1, + __BITFIELD_FIELD(u64 usbc_end : 1, + __BITFIELD_FIELD(u64 usbp_bist : 1, + __BITFIELD_FIELD(u64 tclk : 1, + __BITFIELD_FIELD(u64 dp_pulld : 1, + __BITFIELD_FIELD(u64 dm_pulld : 1, + __BITFIELD_FIELD(u64 hst_mode : 1, + __BITFIELD_FIELD(u64 tuning : 4, + __BITFIELD_FIELD(u64 tx_bs_enh : 1, + __BITFIELD_FIELD(u64 tx_bs_en : 1, + __BITFIELD_FIELD(u64 loop_enb : 1, + __BITFIELD_FIELD(u64 vtest_enb : 1, + __BITFIELD_FIELD(u64 bist_enb : 1, + __BITFIELD_FIELD(u64 tdata_sel : 1, + __BITFIELD_FIELD(u64 taddr_in : 4, + __BITFIELD_FIELD(u64 tdata_in : 8, + __BITFIELD_FIELD(u64 ate_reset : 1, + ;))))))))))))))))))))))))))))))))))) + } s; +}; + +#endif /* __OCTEON_HCD_H__ */ -- cgit v1.2.3 From 281aec87d09b6eb343632e1d6a5a4587569c1ac1 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Tue, 21 Jun 2022 17:23:47 +0200 Subject: usb: host: ohci-platform: add TPL support The Target Peripheral List (TPL) is used to identify targeted devices during Embedded Host compliance testing. The user can add "tpl-support" in the device tree to enable it. Acked-by: Alan Stern Signed-off-by: Amelie Delaunay Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20220621152350.145745-2-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-platform.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 47dfbfe9e519..0adae6265127 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "ohci.h" @@ -210,6 +211,8 @@ static int ohci_platform_probe(struct platform_device *dev) hcd->rsrc_start = res_mem->start; hcd->rsrc_len = resource_size(res_mem); + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) goto err_power; -- cgit v1.2.3 From 401e9d73225a3a382d475de2234500f038434664 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Tue, 21 Jun 2022 17:23:48 +0200 Subject: usb: host: ehci-platform: add TPL support The Target Peripheral List (TPL) is used to identify targeted devices during Embedded Host compliance testing. The user can add "tpl-support" in the device tree to enable it. Acked-by: Alan Stern Signed-off-by: Amelie Delaunay Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20220621152350.145745-3-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-platform.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index f343967443e2..6924f0316e9a 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -370,6 +370,8 @@ static int ehci_platform_probe(struct platform_device *dev) hcd->rsrc_start = res_mem->start; hcd->rsrc_len = resource_size(res_mem); + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) goto err_power; -- cgit v1.2.3 From 2c8845fe9342b8801185b4c1b9261c532901fafd Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Tue, 21 Jun 2022 17:23:50 +0200 Subject: usb: dwc2: host: add TPL support The Target Peripheral List (TPL) is used to identify targeted devices during Embedded Host compliance testing. The user can add "tpl-support" in the device tree to enable it. Signed-off-by: Amelie Delaunay Signed-off-by: Fabrice Gasnier Acked-by: Minas Harutyunyan Link: https://lore.kernel.org/r/20220621152350.145745-5-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 3f107a06817d..2c9613d0d395 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -52,6 +52,7 @@ #include #include +#include #include "core.h" #include "hcd.h" @@ -5339,6 +5340,8 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) /* Don't support SG list at this point */ hcd->self.sg_tablesize = 0; + hcd->tpl_support = of_usb_host_tpl_support(hsotg->dev->of_node); + if (!IS_ERR_OR_NULL(hsotg->uphy)) otg_set_host(hsotg->uphy->otg, &hcd->self); -- cgit v1.2.3 From 7828466cff6b38c2a8ea7cc43958e3abe04342c2 Mon Sep 17 00:00:00 2001 From: Slark Xiao Date: Wed, 22 Jun 2022 14:21:13 +0800 Subject: USB: serial: use kmemdup instead of kmalloc + memcpy For code neat purpose, we can use kmemdup to replace kmalloc + memcpy. Signed-off-by: Slark Xiao Signed-off-by: Johan Hovold --- drivers/usb/serial/garmin_gps.c | 4 +--- drivers/usb/serial/opticon.c | 4 +--- drivers/usb/serial/sierra.c | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index e5c75944ebb7..f1a8d8343623 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -988,7 +988,7 @@ static int garmin_write_bulk(struct usb_serial_port *port, garmin_data_p->flags &= ~FLAGS_DROP_DATA; spin_unlock_irqrestore(&garmin_data_p->lock, flags); - buffer = kmalloc(count, GFP_ATOMIC); + buffer = kmemdup(buf, count, GFP_ATOMIC); if (!buffer) return -ENOMEM; @@ -998,8 +998,6 @@ static int garmin_write_bulk(struct usb_serial_port *port, return -ENOMEM; } - memcpy(buffer, buf, count); - usb_serial_debug_data(&port->dev, __func__, count, buffer); usb_fill_bulk_urb(urb, serial->dev, diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index aed28c35caff..e31a6d77da3a 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -208,7 +208,7 @@ static int opticon_write(struct tty_struct *tty, struct usb_serial_port *port, priv->outstanding_bytes += count; spin_unlock_irqrestore(&priv->lock, flags); - buffer = kmalloc(count, GFP_ATOMIC); + buffer = kmemdup(buf, count, GFP_ATOMIC); if (!buffer) goto error_no_buffer; @@ -216,8 +216,6 @@ static int opticon_write(struct tty_struct *tty, struct usb_serial_port *port, if (!urb) goto error_no_urb; - memcpy(buffer, buf, count); - usb_serial_debug_data(&port->dev, __func__, count, buffer); /* The connected devices do not have a bulk write endpoint, diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 9d56138133a9..525c7f888c90 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -453,7 +453,7 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, goto error_simple; } - buffer = kmalloc(writesize, GFP_ATOMIC); + buffer = kmemdup(buf, writesize, GFP_ATOMIC); if (!buffer) { retval = -ENOMEM; goto error_no_buffer; @@ -465,8 +465,6 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, goto error_no_urb; } - memcpy(buffer, buf, writesize); - usb_serial_debug_data(&port->dev, __func__, writesize, buffer); usb_fill_bulk_urb(urb, serial->dev, -- cgit v1.2.3 From 21cdd6a0e8441461e549f0c25af9696c6c4de22c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 24 Jun 2022 10:22:20 +0300 Subject: usb: musb: mpfs: Fix error codes in probe() These error paths return success but they need to return a negative error code. Fixes: 7a96b6ea90a4 ("usb: musb: Add support for PolarFire SoC's musb controller") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/YrVmLEc/FOEzNdzj@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/mpfs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/musb/mpfs.c b/drivers/usb/musb/mpfs.c index 99666ef8af06..a69ca338eace 100644 --- a/drivers/usb/musb/mpfs.c +++ b/drivers/usb/musb/mpfs.c @@ -181,8 +181,10 @@ static int mpfs_probe(struct platform_device *pdev) glue->clk = clk; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) + if (!pdata) { + ret = -ENOMEM; goto err_clk_disable; + } pdata->config = &mpfs_musb_hdrc_config; pdata->platform_ops = &mpfs_ops; @@ -197,6 +199,7 @@ static int mpfs_probe(struct platform_device *pdev) if (IS_ERR(glue->phy)) { dev_err(dev, "failed to register usb-phy %ld\n", PTR_ERR(glue->phy)); + ret = PTR_ERR(glue->phy); goto err_clk_disable; } -- cgit v1.2.3 From db638c6500abaffb8f7770b2a69c40d003d54ae1 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Wed, 22 Jun 2022 18:07:17 +0200 Subject: usb: dwc2: gadget: remove D+ pull-up while no vbus with usb-role-switch When using usb-role-switch, D+ pull-up is set as soon as DTCL_SFTDISCON is cleared, whatever the vbus valid signal state is. The pull-up should not be set when vbus isn't present (this is determined by the drd controller). This patch ensures that B-Session (so Peripheral role + vbus valid signal) is valid before clearing the DCTL_SFTDISCON bit when role switch is used. Keep original behavior when usb-role-switch isn't used. Acked-by: Minas Harutyunyan Signed-off-by: Amelie Delaunay Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20220622160717.314580-1-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index fe2a58c75861..8b15742d9e8a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3594,7 +3594,8 @@ void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) { /* remove the soft-disconnect and let's go */ - dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON); + if (!hsotg->role_sw || (dwc2_readl(hsotg, GOTGCTL) & GOTGCTL_BSESVLD)) + dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON); } /** -- cgit v1.2.3 From 2958d494b7577037a257461bae0bfe723f192910 Mon Sep 17 00:00:00 2001 From: Jiang Jian Date: Wed, 22 Jun 2022 18:35:58 +0800 Subject: usb: dwc2: drop unexpected word "the" in the comments there is an unexpected word "the" in the comments that need to be dropped file: ./drivers/usb/dwc2/hcd.c line: 1002 * even and the current frame number is even the the transfer changed to: * even and the current frame number is even the transfer Signed-off-by: Jiang Jian Link: https://lore.kernel.org/r/20220622103558.6647-1-jiangjian@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 2c9613d0d395..aaf7b9fc4d34 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1000,7 +1000,7 @@ static void dwc2_hc_set_even_odd_frame(struct dwc2_hsotg *hsotg, /* * Try to figure out if we're an even or odd frame. If we set - * even and the current frame number is even the the transfer + * even and the current frame number is even the transfer * will happen immediately. Similar if both are odd. If one is * even and the other is odd then the transfer will happen when * the frame number ticks. -- cgit v1.2.3 From d46b4343e13246ab1d2b057b972657886e2a5f14 Mon Sep 17 00:00:00 2001 From: Jiang Jian Date: Wed, 22 Jun 2022 18:30:03 +0800 Subject: usb: max-3421: drop unexpected word "the" in the comments there is an unexpected word "the" in the comments that need to be dropped file: ./drivers/usb/host/max3421-hcd.c line: 315 * reasonable overview of how control transfers use the the IN/OUT changed to: * reasonable overview of how control transfers use the IN/OUT Signed-off-by: Jiang Jian Link: https://lore.kernel.org/r/20220622103003.5420-1-jiangjian@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/max3421-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 502a3ac5e35b..352e3ac2b377 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -312,7 +312,7 @@ static const int hrsl_to_error[] = { /* * See https://www.beyondlogic.org/usbnutshell/usb4.shtml#Control for a - * reasonable overview of how control transfers use the the IN/OUT + * reasonable overview of how control transfers use the IN/OUT * tokens. */ #define MAX3421_HXFR_BULK_IN(ep) (0x00 | (ep)) /* bulk or interrupt */ -- cgit v1.2.3 From 508aeb54e4f0225f4ff3da9b7ec8ac44ce30aad8 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Thu, 23 Jun 2022 09:08:08 +0800 Subject: usb: gadget: u_ether: Remove duplicated include in u_ether.c Fix following includecheck warning: ./drivers/usb/gadget/function/u_ether.c: linux/etherdevice.h is included more than once. Signed-off-by: Yang Li Link: https://lore.kernel.org/r/20220623010808.9816-1-yang.lee@linux.alibaba.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_ether.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index f51694f29de9..7887def05dc2 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -17,7 +17,6 @@ #include #include #include -#include #include "u_ether.h" -- cgit v1.2.3 From 6569689e78299ff91002960a163012b576f2b21a Mon Sep 17 00:00:00 2001 From: Xuezhi Zhang Date: Fri, 24 Jun 2022 20:12:38 +0800 Subject: usb: core: sysfs: convert sysfs snprintf to sysfs_emit Fix up all sysfs show entries to use sysfs_emit Signed-off-by: Xuezhi Zhang Link: https://lore.kernel.org/r/20220624121238.134256-1-zhangxuezhi1@coolpad.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/sysfs.c | 79 ++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index fa2e49d432ff..631574718d8a 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -35,7 +35,7 @@ static ssize_t field##_show(struct device *dev, \ return -EINTR; \ actconfig = udev->actconfig; \ if (actconfig) \ - rc = sprintf(buf, format_string, \ + rc = sysfs_emit(buf, format_string, \ actconfig->desc.field); \ usb_unlock_device(udev); \ return rc; \ @@ -61,7 +61,7 @@ static ssize_t bMaxPower_show(struct device *dev, return -EINTR; actconfig = udev->actconfig; if (actconfig) - rc = sprintf(buf, "%dmA\n", usb_get_max_power(udev, actconfig)); + rc = sysfs_emit(buf, "%dmA\n", usb_get_max_power(udev, actconfig)); usb_unlock_device(udev); return rc; } @@ -80,7 +80,7 @@ static ssize_t configuration_show(struct device *dev, return -EINTR; actconfig = udev->actconfig; if (actconfig && actconfig->string) - rc = sprintf(buf, "%s\n", actconfig->string); + rc = sysfs_emit(buf, "%s\n", actconfig->string); usb_unlock_device(udev); return rc; } @@ -114,7 +114,7 @@ static ssize_t devspec_show(struct device *dev, struct device_attribute *attr, { struct device_node *of_node = dev->of_node; - return sprintf(buf, "%pOF\n", of_node); + return sysfs_emit(buf, "%pOF\n", of_node); } static DEVICE_ATTR_RO(devspec); #endif @@ -131,7 +131,7 @@ static ssize_t name##_show(struct device *dev, \ retval = usb_lock_device_interruptible(udev); \ if (retval < 0) \ return -EINTR; \ - retval = sprintf(buf, "%s\n", udev->name); \ + retval = sysfs_emit(buf, "%s\n", udev->name); \ usb_unlock_device(udev); \ return retval; \ } \ @@ -175,7 +175,7 @@ static ssize_t speed_show(struct device *dev, struct device_attribute *attr, default: speed = "unknown"; } - return sprintf(buf, "%s\n", speed); + return sysfs_emit(buf, "%s\n", speed); } static DEVICE_ATTR_RO(speed); @@ -185,7 +185,7 @@ static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->rx_lanes); + return sysfs_emit(buf, "%d\n", udev->rx_lanes); } static DEVICE_ATTR_RO(rx_lanes); @@ -195,7 +195,7 @@ static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->tx_lanes); + return sysfs_emit(buf, "%d\n", udev->tx_lanes); } static DEVICE_ATTR_RO(tx_lanes); @@ -205,7 +205,7 @@ static ssize_t busnum_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->bus->busnum); + return sysfs_emit(buf, "%d\n", udev->bus->busnum); } static DEVICE_ATTR_RO(busnum); @@ -215,7 +215,7 @@ static ssize_t devnum_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->devnum); + return sysfs_emit(buf, "%d\n", udev->devnum); } static DEVICE_ATTR_RO(devnum); @@ -225,7 +225,7 @@ static ssize_t devpath_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%s\n", udev->devpath); + return sysfs_emit(buf, "%s\n", udev->devpath); } static DEVICE_ATTR_RO(devpath); @@ -237,7 +237,7 @@ static ssize_t version_show(struct device *dev, struct device_attribute *attr, udev = to_usb_device(dev); bcdUSB = le16_to_cpu(udev->descriptor.bcdUSB); - return sprintf(buf, "%2x.%02x\n", bcdUSB >> 8, bcdUSB & 0xff); + return sysfs_emit(buf, "%2x.%02x\n", bcdUSB >> 8, bcdUSB & 0xff); } static DEVICE_ATTR_RO(version); @@ -247,7 +247,7 @@ static ssize_t maxchild_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->maxchild); + return sysfs_emit(buf, "%d\n", udev->maxchild); } static DEVICE_ATTR_RO(maxchild); @@ -257,7 +257,7 @@ static ssize_t quirks_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "0x%x\n", udev->quirks); + return sysfs_emit(buf, "0x%x\n", udev->quirks); } static DEVICE_ATTR_RO(quirks); @@ -267,7 +267,7 @@ static ssize_t avoid_reset_quirk_show(struct device *dev, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", !!(udev->quirks & USB_QUIRK_RESET)); + return sysfs_emit(buf, "%d\n", !!(udev->quirks & USB_QUIRK_RESET)); } static ssize_t avoid_reset_quirk_store(struct device *dev, @@ -297,7 +297,7 @@ static ssize_t urbnum_show(struct device *dev, struct device_attribute *attr, struct usb_device *udev; udev = to_usb_device(dev); - return sprintf(buf, "%d\n", atomic_read(&udev->urbnum)); + return sysfs_emit(buf, "%d\n", atomic_read(&udev->urbnum)); } static DEVICE_ATTR_RO(urbnum); @@ -305,8 +305,8 @@ static ssize_t ltm_capable_show(struct device *dev, struct device_attribute *attr, char *buf) { if (usb_device_supports_ltm(to_usb_device(dev))) - return sprintf(buf, "%s\n", "yes"); - return sprintf(buf, "%s\n", "no"); + return sysfs_emit(buf, "%s\n", "yes"); + return sysfs_emit(buf, "%s\n", "no"); } static DEVICE_ATTR_RO(ltm_capable); @@ -317,7 +317,7 @@ static ssize_t persist_show(struct device *dev, struct device_attribute *attr, { struct usb_device *udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->persist_enabled); + return sysfs_emit(buf, "%d\n", udev->persist_enabled); } static ssize_t persist_store(struct device *dev, struct device_attribute *attr, @@ -372,7 +372,7 @@ static ssize_t connected_duration_show(struct device *dev, { struct usb_device *udev = to_usb_device(dev); - return sprintf(buf, "%u\n", + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(jiffies - udev->connect_time)); } static DEVICE_ATTR_RO(connected_duration); @@ -394,14 +394,14 @@ static ssize_t active_duration_show(struct device *dev, duration = jiffies_to_msecs(jiffies + udev->active_duration); else duration = jiffies_to_msecs(udev->active_duration); - return sprintf(buf, "%u\n", duration); + return sysfs_emit(buf, "%u\n", duration); } static DEVICE_ATTR_RO(active_duration); static ssize_t autosuspend_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", dev->power.autosuspend_delay / 1000); + return sysfs_emit(buf, "%d\n", dev->power.autosuspend_delay / 1000); } static ssize_t autosuspend_store(struct device *dev, @@ -442,7 +442,7 @@ static ssize_t level_show(struct device *dev, struct device_attribute *attr, warn_level(); if (udev->state != USB_STATE_SUSPENDED && !udev->dev.power.runtime_auto) p = on_string; - return sprintf(buf, "%s\n", p); + return sysfs_emit(buf, "%s\n", p); } static ssize_t level_store(struct device *dev, struct device_attribute *attr, @@ -490,7 +490,7 @@ static ssize_t usb2_hardware_lpm_show(struct device *dev, else p = "disabled"; - return sprintf(buf, "%s\n", p); + return sysfs_emit(buf, "%s\n", p); } static ssize_t usb2_hardware_lpm_store(struct device *dev, @@ -529,7 +529,7 @@ static ssize_t usb2_lpm_l1_timeout_show(struct device *dev, char *buf) { struct usb_device *udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->l1_params.timeout); + return sysfs_emit(buf, "%d\n", udev->l1_params.timeout); } static ssize_t usb2_lpm_l1_timeout_store(struct device *dev, @@ -552,7 +552,7 @@ static ssize_t usb2_lpm_besl_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_device *udev = to_usb_device(dev); - return sprintf(buf, "%d\n", udev->l1_params.besl); + return sysfs_emit(buf, "%d\n", udev->l1_params.besl); } static ssize_t usb2_lpm_besl_store(struct device *dev, @@ -589,7 +589,7 @@ static ssize_t usb3_hardware_lpm_u1_show(struct device *dev, usb_unlock_device(udev); - return sprintf(buf, "%s\n", p); + return sysfs_emit(buf, "%s\n", p); } static DEVICE_ATTR_RO(usb3_hardware_lpm_u1); @@ -611,7 +611,7 @@ static ssize_t usb3_hardware_lpm_u2_show(struct device *dev, usb_unlock_device(udev); - return sprintf(buf, "%s\n", p); + return sysfs_emit(buf, "%s\n", p); } static DEVICE_ATTR_RO(usb3_hardware_lpm_u2); @@ -694,7 +694,7 @@ field##_show(struct device *dev, struct device_attribute *attr, \ struct usb_device *udev; \ \ udev = to_usb_device(dev); \ - return sprintf(buf, format_string, \ + return sysfs_emit(buf, format_string, \ le16_to_cpu(udev->descriptor.field)); \ } \ static DEVICE_ATTR_RO(field) @@ -711,7 +711,7 @@ field##_show(struct device *dev, struct device_attribute *attr, \ struct usb_device *udev; \ \ udev = to_usb_device(dev); \ - return sprintf(buf, format_string, udev->descriptor.field); \ + return sysfs_emit(buf, format_string, udev->descriptor.field); \ } \ static DEVICE_ATTR_RO(field) @@ -727,7 +727,7 @@ static ssize_t authorized_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_device *usb_dev = to_usb_device(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", usb_dev->authorized); + return sysfs_emit(buf, "%u\n", usb_dev->authorized); } /* @@ -918,7 +918,7 @@ static ssize_t authorized_default_show(struct device *dev, struct usb_hcd *hcd; hcd = bus_to_hcd(usb_bus); - return snprintf(buf, PAGE_SIZE, "%u\n", hcd->dev_policy); + return sysfs_emit(buf, "%u\n", hcd->dev_policy); } static ssize_t authorized_default_store(struct device *dev, @@ -957,7 +957,7 @@ static ssize_t interface_authorized_default_show(struct device *dev, struct usb_device *usb_dev = to_usb_device(dev); struct usb_hcd *hcd = bus_to_hcd(usb_dev->bus); - return sprintf(buf, "%u\n", !!HCD_INTF_AUTHORIZED(hcd)); + return sysfs_emit(buf, "%u\n", !!HCD_INTF_AUTHORIZED(hcd)); } /* @@ -1066,7 +1066,7 @@ iad_##field##_show(struct device *dev, struct device_attribute *attr, \ { \ struct usb_interface *intf = to_usb_interface(dev); \ \ - return sprintf(buf, format_string, \ + return sysfs_emit(buf, format_string, \ intf->intf_assoc->field); \ } \ static DEVICE_ATTR_RO(iad_##field) @@ -1085,7 +1085,7 @@ field##_show(struct device *dev, struct device_attribute *attr, \ { \ struct usb_interface *intf = to_usb_interface(dev); \ \ - return sprintf(buf, format_string, \ + return sysfs_emit(buf, format_string, \ intf->cur_altsetting->desc.field); \ } \ static DEVICE_ATTR_RO(field) @@ -1107,7 +1107,7 @@ static ssize_t interface_show(struct device *dev, struct device_attribute *attr, string = READ_ONCE(intf->cur_altsetting->string); if (!string) return 0; - return sprintf(buf, "%s\n", string); + return sysfs_emit(buf, "%s\n", string); } static DEVICE_ATTR_RO(interface); @@ -1122,7 +1122,8 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, udev = interface_to_usbdev(intf); alt = READ_ONCE(intf->cur_altsetting); - return sprintf(buf, "usb:v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02X" + return sysfs_emit(buf, + "usb:v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02X" "ic%02Xisc%02Xip%02Xin%02X\n", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct), @@ -1150,7 +1151,7 @@ static ssize_t supports_autosuspend_show(struct device *dev, s = (!dev->driver || to_usb_driver(dev->driver)->supports_autosuspend); device_unlock(dev); - return sprintf(buf, "%u\n", s); + return sysfs_emit(buf, "%u\n", s); } static DEVICE_ATTR_RO(supports_autosuspend); @@ -1163,7 +1164,7 @@ static ssize_t interface_authorized_show(struct device *dev, { struct usb_interface *intf = to_usb_interface(dev); - return sprintf(buf, "%u\n", intf->authorized); + return sysfs_emit(buf, "%u\n", intf->authorized); } /* -- cgit v1.2.3 From ad44cf402486a0c84e65e6f0a65c20ce58a65a96 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 28 Jun 2022 10:46:25 +0100 Subject: usb: dwc3: document async_callbacks field Avoid a kernel-doc warning by documenting it: drivers/usb/dwc3/core.h:1328: warning: Function parameter or member 'async_callbacks' not described in 'dwc3' Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/2c3de5935934baec097286f525ce4beff0a31ec0.1656409369.git.mchehab@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 722808d8c0af..4fe4287dc934 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1087,6 +1087,8 @@ struct dwc3_scratchpad_array { * @dis_u1_entry_quirk: set if link entering into U1 state needs to be disabled. * @dis_u2_entry_quirk: set if link entering into U2 state needs to be disabled. * @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3 + * @async_callbacks: if set, indicate that async callbacks will be used. + * * @dis_u2_freeclk_exists_quirk : set if we clear u2_freeclk_exists * in GUSB2PHYCFG, specify that USB2 PHY doesn't * provide a free-running PHY clock. -- cgit v1.2.3 From babfcd947eba6e4c80a023683a37723ad43816e2 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 28 Jun 2022 10:46:26 +0100 Subject: usb: dwc3: gadget: fix a kernel-doc warning The multiplier parameter of dwc3_gadget_calc_tx_fifo_size() was not documented: drivers/usb/dwc3/gadget.c:675: warning: Function parameter or member 'mult' not described in 'dwc3_gadget_calc_tx_fifo_size' Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/bd599a18cea45c57d91c69d3e30d8b1e8ea69dd1.1656409369.git.mchehab@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 8716bece1072..a944c7a6c83a 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -657,6 +657,7 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) /** * dwc3_gadget_calc_tx_fifo_size - calculates the txfifo size value * @dwc: pointer to the DWC3 context + * @mult: multiplier to be used when calculating the fifo_size * * Calculates the size value based on the equation below: * -- cgit v1.2.3 From 70cdb930f7e97e0abe4ec4ac30e63ada490ef375 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 28 Jun 2022 11:21:43 +0300 Subject: usb: gadget: aspeed_udc: fix handling of tx_len == 0 The bug is that we should still enter this loop if "tx_len" is zero. After adding the "last" variable, then the "chunk >= 0" condition is no longer required but I left it for readability. Fixes: c09b1f372e74 ("usb: gadget: aspeed_udc: cleanup loop in ast_dma_descriptor_setup()") Reported-by: Neal Liu Reviewed-by: Neal Liu Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Yrq6F5okoX1y05rT@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed_udc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index d75a4e070bf7..01968e2167f9 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -476,6 +476,7 @@ static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, { struct ast_udc_dev *udc = ep->udc; struct device *dev = &udc->pdev->dev; + bool last = false; int chunk, count; u32 offset; @@ -493,14 +494,16 @@ static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, "tx_len", tx_len); /* Create Descriptor Lists */ - while (chunk > 0 && count < AST_UDC_DESCS_COUNT) { + while (chunk >= 0 && !last && count < AST_UDC_DESCS_COUNT) { ep->descs[ep->descs_wptr].des_0 = dma_buf + offset; - if (chunk > ep->chunk_max) + if (chunk > ep->chunk_max) { ep->descs[ep->descs_wptr].des_1 = ep->chunk_max; - else + } else { ep->descs[ep->descs_wptr].des_1 = chunk; + last = true; + } chunk -= ep->chunk_max; -- cgit v1.2.3 From 98ceba7559280cdd09feccc7af4406d93187fbfb Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Tue, 28 Jun 2022 22:05:27 +0800 Subject: usb: musb: mpfs: add missing clk_disable_unprepare() in mpfs_remove() clock source is prepared and enabled by clk_prepare_enable() in probe function, but no disable or unprepare in remove. Fixes: 7a96b6ea90a4 ("usb: musb: Add support for PolarFire SoC's musb controller") Reported-by: Hulk Robot Reviewed-by: Conor Dooley Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20220628140527.1404439-1-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/mpfs.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/musb/mpfs.c b/drivers/usb/musb/mpfs.c index a69ca338eace..cea2e8108867 100644 --- a/drivers/usb/musb/mpfs.c +++ b/drivers/usb/musb/mpfs.c @@ -239,6 +239,7 @@ static int mpfs_remove(struct platform_device *pdev) { struct mpfs_glue *glue = platform_get_drvdata(pdev); + clk_disable_unprepare(glue->clk); platform_device_unregister(glue->musb); usb_phy_generic_unregister(pdev); -- cgit v1.2.3 From df574080fad0d73f7aaf472a0fa45ea3f18230e0 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 29 Jun 2022 15:26:38 +0200 Subject: USB: cdc-acm: use define for timeout We have a symbolic name for the standard timeout. Use it. No functional change intended. Signed-off-by: Oliver Neukum Link: https://lore.kernel.org/r/20220629132638.31810-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 9b9aea24d58c..fedf3065670e 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -119,7 +119,7 @@ static int acm_ctrl_msg(struct acm *acm, int request, int value, retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), request, USB_RT_ACM, value, acm->control->altsetting[0].desc.bInterfaceNumber, - buf, len, 5000); + buf, len, USB_CTRL_SET_TIMEOUT); dev_dbg(&acm->control->dev, "%s - rq 0x%02x, val %#x, len %#x, result %d\n", -- cgit v1.2.3 From 3b91edd624ab1ab694deef513a45eb9e9d49d75f Mon Sep 17 00:00:00 2001 From: Neal Liu Date: Tue, 28 Jun 2022 10:14:36 +0800 Subject: usb: gadget: f_mass_storage: Make CD-ROM emulation works with Windows OS Add read TOC with format 1 to support CD-ROM emulation with Windows OS. This patch is tested on Windows OS Server 2019. Fixes: 89ada0fe669a ("usb: gadget: f_mass_storage: Make CD-ROM emulation work with Mac OS-X") Reviewed-by: Alan Stern Signed-off-by: Neal Liu Link: https://lore.kernel.org/r/20220628021436.3252262-1-neal_liu@aspeedtech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 3a77bca0ebe1..e884f295504f 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -1192,13 +1192,14 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) u8 format; int i, len; + format = common->cmnd[2] & 0xf; + if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ - start_track > 1) { + (start_track > 1 && format != 0x1)) { curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } - format = common->cmnd[2] & 0xf; /* * Check if CDB is old style SFF-8020i * i.e. format is in 2 MSBs of byte 9 @@ -1208,8 +1209,8 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) format = (common->cmnd[9] >> 6) & 0x3; switch (format) { - case 0: - /* Formatted TOC */ + case 0: /* Formatted TOC */ + case 1: /* Multi-session info */ len = 4 + 2*8; /* 4 byte header + 2 descriptors */ memset(buf, 0, len); buf[1] = len - 2; /* TOC Length excludes length field */ @@ -1250,7 +1251,7 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) return len; default: - /* Multi-session, PMA, ATIP, CD-TEXT not supported/required */ + /* PMA, ATIP, CD-TEXT not supported/required */ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } -- cgit v1.2.3 From 1ce69c35b86038dd11d3a6115a04501c5b89a940 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Thu, 30 Jun 2022 15:46:45 +0300 Subject: usb: host: xhci: use snprintf() in xhci_decode_trb() Commit cbf286e8ef83 ("xhci: fix unsafe memory usage in xhci tracing") apparently missed one sprintf() call in xhci_decode_trb() -- replace it with the snprintf() call as well... Found by Linux Verification Center (linuxtesting.org) with the SVACE static analysis tool. Fixes: cbf286e8ef83 ("xhci: fix unsafe memory usage in xhci tracing") Signed-off-by: Sergey Shtylyov Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220630124645.1805902-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 28aaf031f9a8..1960b47acfb2 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -2417,7 +2417,7 @@ static inline const char *xhci_decode_trb(char *str, size_t size, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_STOP_RING: - sprintf(str, + snprintf(str, size, "%s: slot %d sp %d ep %d flags %c", xhci_trb_type_string(type), TRB_TO_SLOT_ID(field3), -- cgit v1.2.3 From 14a6043379e2132c11b815972c0e2201e68d7467 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Sun, 3 Jul 2022 09:14:49 -0400 Subject: USB: gadget: udc: tracing: Do not open code __string() with __dynamic_array() The event classes udc_log_ep and udc_log_req both declare: __dynamic_array(char, name, UDC_TRACE_STR_MAX) Which will reserve UDC_TRACE_STR_MAX bytes on the ring buffer for the event to write in name. It then uses snprintf() to write into that space. Assuming that the string being copied is nul terminated, it is better to just use the __string() helper. That way only the size of the string is saved into the ring buffer and not the max size (yes, the entire UDC_TRACE_STR_MAX is used in the trace event, and anything not used is just junk in the ring buffer). Worse, there's also meta data saved into the event that denotes where the string is stored in the event and also saves its size, which is always going to be UDC_TRACE_STR_MAX. Convert both to use the __string() and __assign_str() helpers that are for this kind of use case. Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Signed-off-by: Steven Rostedt (Google) Link: https://lore.kernel.org/r/20220703091449.317f94b1@rorschach.local.home Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/trace.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/trace.h b/drivers/usb/gadget/udc/trace.h index 98584f6b6c66..abdbcb1bacb0 100644 --- a/drivers/usb/gadget/udc/trace.h +++ b/drivers/usb/gadget/udc/trace.h @@ -140,7 +140,7 @@ DECLARE_EVENT_CLASS(udc_log_ep, TP_PROTO(struct usb_ep *ep, int ret), TP_ARGS(ep, ret), TP_STRUCT__entry( - __dynamic_array(char, name, UDC_TRACE_STR_MAX) + __string(name, ep->name) __field(unsigned, maxpacket) __field(unsigned, maxpacket_limit) __field(unsigned, max_streams) @@ -152,7 +152,7 @@ DECLARE_EVENT_CLASS(udc_log_ep, __field(int, ret) ), TP_fast_assign( - snprintf(__get_str(name), UDC_TRACE_STR_MAX, "%s", ep->name); + __assign_str(name, ep->name); __entry->maxpacket = ep->maxpacket; __entry->maxpacket_limit = ep->maxpacket_limit; __entry->max_streams = ep->max_streams; @@ -214,7 +214,7 @@ DECLARE_EVENT_CLASS(udc_log_req, TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), TP_ARGS(ep, req, ret), TP_STRUCT__entry( - __dynamic_array(char, name, UDC_TRACE_STR_MAX) + __string(name, ep->name) __field(unsigned, length) __field(unsigned, actual) __field(unsigned, num_sgs) @@ -228,7 +228,7 @@ DECLARE_EVENT_CLASS(udc_log_req, __field(struct usb_request *, req) ), TP_fast_assign( - snprintf(__get_str(name), UDC_TRACE_STR_MAX, "%s", ep->name); + __assign_str(name, ep->name); __entry->length = req->length; __entry->actual = req->actual; __entry->num_sgs = req->num_sgs; -- cgit v1.2.3 From e89676f65ef372fda6c392b3f99fcc7175b206e6 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 5 Jul 2022 15:19:50 +0200 Subject: usb: gadget: udc: atmel: check rc of devm_gpiod_get_optional() devm_gpiod_get_optional() might still return an error code, esp. EPROBE_DEFER. Return any errors. Signed-off-by: Michael Walle Link: https://lore.kernel.org/r/20220705131951.1388968-1-michael@walle.cc Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/atmel_usba_udc.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index ae2bfbac603e..48355e0cee76 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -2165,6 +2165,8 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, udc->vbus_pin = devm_gpiod_get_optional(&pdev->dev, "atmel,vbus", GPIOD_IN); + if (IS_ERR(udc->vbus_pin)) + return ERR_CAST(udc->vbus_pin); if (fifo_mode == 0) { udc->num_ep = udc_config->num_ep; -- cgit v1.2.3 From 1bd71816643a9e279c2a7b7a02c10de4a07bf9eb Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 5 Jul 2022 15:19:51 +0200 Subject: usb: gadget: udc: atmel: convert to platform driver The driver won't probe on a LAN9668 because the pinctrl driver isn't ready yet. Probe deferral is not supported because the init section is already discarded. With fw_devlink enabled, the probe won't even be called. Convert the driver to a proper platform driver. Signed-off-by: Michael Walle Link: https://lore.kernel.org/r/20220705131951.1388968-2-michael@walle.cc Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/atmel_usba_udc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 48355e0cee76..53ca38c4b3ec 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -2060,7 +2060,7 @@ static const struct usba_udc_errata at91sam9g45_errata = { .pulse_bias = at91sam9g45_pulse_bias, }; -static const struct usba_ep_config ep_config_sam9[] __initconst = { +static const struct usba_ep_config ep_config_sam9[] = { { .nr_banks = 1 }, /* ep 0 */ { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 1 */ { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 2 */ @@ -2070,7 +2070,7 @@ static const struct usba_ep_config ep_config_sam9[] __initconst = { { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 6 */ }; -static const struct usba_ep_config ep_config_sama5[] __initconst = { +static const struct usba_ep_config ep_config_sama5[] = { { .nr_banks = 1 }, /* ep 0 */ { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 1 */ { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 2 */ @@ -2449,6 +2449,7 @@ static int usba_udc_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(usba_udc_pm_ops, usba_udc_suspend, usba_udc_resume); static struct platform_driver udc_driver = { + .probe = usba_udc_probe, .remove = usba_udc_remove, .driver = { .name = "atmel_usba_udc", @@ -2456,8 +2457,7 @@ static struct platform_driver udc_driver = { .of_match_table = atmel_udc_dt_ids, }, }; - -module_platform_driver_probe(udc_driver, usba_udc_probe); +module_platform_driver(udc_driver); MODULE_DESCRIPTION("Atmel USBA UDC driver"); MODULE_AUTHOR("Haavard Skinnemoen (Atmel)"); -- cgit v1.2.3 From 04cb742d4d8f30dc2e83b46ac317eec09191c68e Mon Sep 17 00:00:00 2001 From: Jozef Martiniak Date: Fri, 8 Jul 2022 09:06:44 +0200 Subject: gadgetfs: ep_io - wait until IRQ finishes after usb_ep_queue() if wait_for_completion_interruptible() is interrupted we need to wait until IRQ gets finished. Otherwise complete() from epio_complete() can corrupt stack. Signed-off-by: Jozef Martiniak Link: https://lore.kernel.org/r/20220708070645.6130-1-jomajm@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index 79990597c39f..01c3ead7d1b4 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -362,6 +362,7 @@ ep_io (struct ep_data *epdata, void *buf, unsigned len) spin_unlock_irq (&epdata->dev->lock); DBG (epdata->dev, "endpoint gone\n"); + wait_for_completion(&done); epdata->status = -ENODEV; } } -- cgit v1.2.3 From 7963d4d710112bc457f99bdb56608211e561190e Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Wed, 6 Jul 2022 16:34:31 +0800 Subject: usb: typec: tcpci: move tcpci.h to include/linux/usb/ USB PD controllers which consisting of a microcontroller (acting as the TCPM) and a port controller (TCPC) - may require that the driver for the PD controller accesses directly also the on-chip port controller in some cases. Move tcpci.h to include/linux/usb/ is convenience access TCPC registers. Reviewed-by: Heikki Krogerus Signed-off-by: Xin Ji Link: https://lore.kernel.org/r/20220706083433.2415524-1-xji@analogixsemi.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 3 +- drivers/usb/typec/tcpm/tcpci.h | 209 -------------------------------- drivers/usb/typec/tcpm/tcpci_maxim.c | 3 +- drivers/usb/typec/tcpm/tcpci_mt6360.c | 3 +- drivers/usb/typec/tcpm/tcpci_rt1711h.c | 2 +- include/linux/usb/tcpci.h | 210 +++++++++++++++++++++++++++++++++ 6 files changed, 214 insertions(+), 216 deletions(-) delete mode 100644 drivers/usb/typec/tcpm/tcpci.h create mode 100644 include/linux/usb/tcpci.h (limited to 'drivers') diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index f33e08eb7670..812784702d53 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -13,11 +13,10 @@ #include #include #include +#include #include #include -#include "tcpci.h" - #define PD_RETRY_COUNT_DEFAULT 3 #define PD_RETRY_COUNT_3_0_OR_HIGHER 2 #define AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV 3500 diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h deleted file mode 100644 index b2edd45f13c6..000000000000 --- a/drivers/usb/typec/tcpm/tcpci.h +++ /dev/null @@ -1,209 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright 2015-2017 Google, Inc - * - * USB Type-C Port Controller Interface. - */ - -#ifndef __LINUX_USB_TCPCI_H -#define __LINUX_USB_TCPCI_H - -#include - -#define TCPC_VENDOR_ID 0x0 -#define TCPC_PRODUCT_ID 0x2 -#define TCPC_BCD_DEV 0x4 -#define TCPC_TC_REV 0x6 -#define TCPC_PD_REV 0x8 -#define TCPC_PD_INT_REV 0xa - -#define TCPC_ALERT 0x10 -#define TCPC_ALERT_EXTND BIT(14) -#define TCPC_ALERT_EXTENDED_STATUS BIT(13) -#define TCPC_ALERT_VBUS_DISCNCT BIT(11) -#define TCPC_ALERT_RX_BUF_OVF BIT(10) -#define TCPC_ALERT_FAULT BIT(9) -#define TCPC_ALERT_V_ALARM_LO BIT(8) -#define TCPC_ALERT_V_ALARM_HI BIT(7) -#define TCPC_ALERT_TX_SUCCESS BIT(6) -#define TCPC_ALERT_TX_DISCARDED BIT(5) -#define TCPC_ALERT_TX_FAILED BIT(4) -#define TCPC_ALERT_RX_HARD_RST BIT(3) -#define TCPC_ALERT_RX_STATUS BIT(2) -#define TCPC_ALERT_POWER_STATUS BIT(1) -#define TCPC_ALERT_CC_STATUS BIT(0) - -#define TCPC_ALERT_MASK 0x12 -#define TCPC_POWER_STATUS_MASK 0x14 -#define TCPC_FAULT_STATUS_MASK 0x15 - -#define TCPC_EXTENDED_STATUS_MASK 0x16 -#define TCPC_EXTENDED_STATUS_MASK_VSAFE0V BIT(0) - -#define TCPC_ALERT_EXTENDED_MASK 0x17 -#define TCPC_SINK_FAST_ROLE_SWAP BIT(0) - -#define TCPC_CONFIG_STD_OUTPUT 0x18 - -#define TCPC_TCPC_CTRL 0x19 -#define TCPC_TCPC_CTRL_ORIENTATION BIT(0) -#define PLUG_ORNT_CC1 0 -#define PLUG_ORNT_CC2 1 -#define TCPC_TCPC_CTRL_BIST_TM BIT(1) -#define TCPC_TCPC_CTRL_EN_LK4CONN_ALRT BIT(6) - -#define TCPC_EXTENDED_STATUS 0x20 -#define TCPC_EXTENDED_STATUS_VSAFE0V BIT(0) - -#define TCPC_ROLE_CTRL 0x1a -#define TCPC_ROLE_CTRL_DRP BIT(6) -#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 -#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3 -#define TCPC_ROLE_CTRL_RP_VAL_DEF 0x0 -#define TCPC_ROLE_CTRL_RP_VAL_1_5 0x1 -#define TCPC_ROLE_CTRL_RP_VAL_3_0 0x2 -#define TCPC_ROLE_CTRL_CC2_SHIFT 2 -#define TCPC_ROLE_CTRL_CC2_MASK 0x3 -#define TCPC_ROLE_CTRL_CC1_SHIFT 0 -#define TCPC_ROLE_CTRL_CC1_MASK 0x3 -#define TCPC_ROLE_CTRL_CC_RA 0x0 -#define TCPC_ROLE_CTRL_CC_RP 0x1 -#define TCPC_ROLE_CTRL_CC_RD 0x2 -#define TCPC_ROLE_CTRL_CC_OPEN 0x3 - -#define TCPC_FAULT_CTRL 0x1b - -#define TCPC_POWER_CTRL 0x1c -#define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) -#define TCPC_POWER_CTRL_BLEED_DISCHARGE BIT(3) -#define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4) -#define TCPC_DIS_VOLT_ALRM BIT(5) -#define TCPC_POWER_CTRL_VBUS_VOLT_MON BIT(6) -#define TCPC_FAST_ROLE_SWAP_EN BIT(7) - -#define TCPC_CC_STATUS 0x1d -#define TCPC_CC_STATUS_TOGGLING BIT(5) -#define TCPC_CC_STATUS_TERM BIT(4) -#define TCPC_CC_STATUS_TERM_RP 0 -#define TCPC_CC_STATUS_TERM_RD 1 -#define TCPC_CC_STATE_SRC_OPEN 0 -#define TCPC_CC_STATUS_CC2_SHIFT 2 -#define TCPC_CC_STATUS_CC2_MASK 0x3 -#define TCPC_CC_STATUS_CC1_SHIFT 0 -#define TCPC_CC_STATUS_CC1_MASK 0x3 - -#define TCPC_POWER_STATUS 0x1e -#define TCPC_POWER_STATUS_DBG_ACC_CON BIT(7) -#define TCPC_POWER_STATUS_UNINIT BIT(6) -#define TCPC_POWER_STATUS_SOURCING_VBUS BIT(4) -#define TCPC_POWER_STATUS_VBUS_DET BIT(3) -#define TCPC_POWER_STATUS_VBUS_PRES BIT(2) -#define TCPC_POWER_STATUS_VCONN_PRES BIT(1) -#define TCPC_POWER_STATUS_SINKING_VBUS BIT(0) - -#define TCPC_FAULT_STATUS 0x1f - -#define TCPC_ALERT_EXTENDED 0x21 - -#define TCPC_COMMAND 0x23 -#define TCPC_CMD_WAKE_I2C 0x11 -#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 -#define TCPC_CMD_ENABLE_VBUS_DETECT 0x33 -#define TCPC_CMD_DISABLE_SINK_VBUS 0x44 -#define TCPC_CMD_SINK_VBUS 0x55 -#define TCPC_CMD_DISABLE_SRC_VBUS 0x66 -#define TCPC_CMD_SRC_VBUS_DEFAULT 0x77 -#define TCPC_CMD_SRC_VBUS_HIGH 0x88 -#define TCPC_CMD_LOOK4CONNECTION 0x99 -#define TCPC_CMD_RXONEMORE 0xAA -#define TCPC_CMD_I2C_IDLE 0xFF - -#define TCPC_DEV_CAP_1 0x24 -#define TCPC_DEV_CAP_2 0x26 -#define TCPC_STD_INPUT_CAP 0x28 -#define TCPC_STD_OUTPUT_CAP 0x29 - -#define TCPC_MSG_HDR_INFO 0x2e -#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3) -#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0) -#define TCPC_MSG_HDR_INFO_REV_SHIFT 1 -#define TCPC_MSG_HDR_INFO_REV_MASK 0x3 - -#define TCPC_RX_DETECT 0x2f -#define TCPC_RX_DETECT_HARD_RESET BIT(5) -#define TCPC_RX_DETECT_SOP BIT(0) -#define TCPC_RX_DETECT_SOP1 BIT(1) -#define TCPC_RX_DETECT_SOP2 BIT(2) -#define TCPC_RX_DETECT_DBG1 BIT(3) -#define TCPC_RX_DETECT_DBG2 BIT(4) - -#define TCPC_RX_BYTE_CNT 0x30 -#define TCPC_RX_BUF_FRAME_TYPE 0x31 -#define TCPC_RX_BUF_FRAME_TYPE_SOP 0 -#define TCPC_RX_HDR 0x32 -#define TCPC_RX_DATA 0x34 /* through 0x4f */ - -#define TCPC_TRANSMIT 0x50 -#define TCPC_TRANSMIT_RETRY_SHIFT 4 -#define TCPC_TRANSMIT_RETRY_MASK 0x3 -#define TCPC_TRANSMIT_TYPE_SHIFT 0 -#define TCPC_TRANSMIT_TYPE_MASK 0x7 - -#define TCPC_TX_BYTE_CNT 0x51 -#define TCPC_TX_HDR 0x52 -#define TCPC_TX_DATA 0x54 /* through 0x6f */ - -#define TCPC_VBUS_VOLTAGE 0x70 -#define TCPC_VBUS_VOLTAGE_MASK 0x3ff -#define TCPC_VBUS_VOLTAGE_LSB_MV 25 -#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 -#define TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV 25 -#define TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX 0x3ff -#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 -#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 -#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 - -/* I2C_WRITE_BYTE_COUNT + 1 when TX_BUF_BYTE_x is only accessible I2C_WRITE_BYTE_COUNT */ -#define TCPC_TRANSMIT_BUFFER_MAX_LEN 31 - -struct tcpci; - -/* - * @TX_BUF_BYTE_x_hidden: - * optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT. - * @frs_sourcing_vbus: - * Optional; Callback to perform chip specific operations when FRS - * is sourcing vbus. - * @auto_discharge_disconnect: - * Optional; Enables TCPC to autonously discharge vbus on disconnect. - * @vbus_vsafe0v: - * optional; Set when TCPC can detect whether vbus is at VSAFE0V. - * @set_partner_usb_comm_capable: - * Optional; The USB Communications Capable bit indicates if port - * partner is capable of communication over the USB data lines - * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. - */ -struct tcpci_data { - struct regmap *regmap; - unsigned char TX_BUF_BYTE_x_hidden:1; - unsigned char auto_discharge_disconnect:1; - unsigned char vbus_vsafe0v:1; - - int (*init)(struct tcpci *tcpci, struct tcpci_data *data); - int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, - bool enable); - int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, - enum typec_cc_status cc); - int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink); - void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data); - void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data, - bool capable); -}; - -struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); -void tcpci_unregister_port(struct tcpci *tcpci); -irqreturn_t tcpci_irq(struct tcpci *tcpci); - -struct tcpm_port; -struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci); -#endif /* __LINUX_USB_TCPCI_H */ diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index df2505570f07..4b6705f3d7b7 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -11,11 +11,10 @@ #include #include #include +#include #include #include -#include "tcpci.h" - #define PD_ACTIVITY_TIMEOUT_MS 10000 #define TCPC_VENDOR_ALERT 0x80 diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c index 8a952eaf9016..1b7c31278ebb 100644 --- a/drivers/usb/typec/tcpm/tcpci_mt6360.c +++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c @@ -11,10 +11,9 @@ #include #include #include +#include #include -#include "tcpci.h" - #define MT6360_REG_PHYCTRL1 0x80 #define MT6360_REG_PHYCTRL3 0x82 #define MT6360_REG_PHYCTRL7 0x86 diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index b56a0880a044..3291ca4948da 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -10,9 +10,9 @@ #include #include #include +#include #include #include -#include "tcpci.h" #define RT1711H_VID 0x29CF #define RT1711H_PID 0x1711 diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h new file mode 100644 index 000000000000..20c0bedb8ec8 --- /dev/null +++ b/include/linux/usb/tcpci.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2015-2017 Google, Inc + * + * USB Type-C Port Controller Interface. + */ + +#ifndef __LINUX_USB_TCPCI_H +#define __LINUX_USB_TCPCI_H + +#include +#include + +#define TCPC_VENDOR_ID 0x0 +#define TCPC_PRODUCT_ID 0x2 +#define TCPC_BCD_DEV 0x4 +#define TCPC_TC_REV 0x6 +#define TCPC_PD_REV 0x8 +#define TCPC_PD_INT_REV 0xa + +#define TCPC_ALERT 0x10 +#define TCPC_ALERT_EXTND BIT(14) +#define TCPC_ALERT_EXTENDED_STATUS BIT(13) +#define TCPC_ALERT_VBUS_DISCNCT BIT(11) +#define TCPC_ALERT_RX_BUF_OVF BIT(10) +#define TCPC_ALERT_FAULT BIT(9) +#define TCPC_ALERT_V_ALARM_LO BIT(8) +#define TCPC_ALERT_V_ALARM_HI BIT(7) +#define TCPC_ALERT_TX_SUCCESS BIT(6) +#define TCPC_ALERT_TX_DISCARDED BIT(5) +#define TCPC_ALERT_TX_FAILED BIT(4) +#define TCPC_ALERT_RX_HARD_RST BIT(3) +#define TCPC_ALERT_RX_STATUS BIT(2) +#define TCPC_ALERT_POWER_STATUS BIT(1) +#define TCPC_ALERT_CC_STATUS BIT(0) + +#define TCPC_ALERT_MASK 0x12 +#define TCPC_POWER_STATUS_MASK 0x14 +#define TCPC_FAULT_STATUS_MASK 0x15 + +#define TCPC_EXTENDED_STATUS_MASK 0x16 +#define TCPC_EXTENDED_STATUS_MASK_VSAFE0V BIT(0) + +#define TCPC_ALERT_EXTENDED_MASK 0x17 +#define TCPC_SINK_FAST_ROLE_SWAP BIT(0) + +#define TCPC_CONFIG_STD_OUTPUT 0x18 + +#define TCPC_TCPC_CTRL 0x19 +#define TCPC_TCPC_CTRL_ORIENTATION BIT(0) +#define PLUG_ORNT_CC1 0 +#define PLUG_ORNT_CC2 1 +#define TCPC_TCPC_CTRL_BIST_TM BIT(1) +#define TCPC_TCPC_CTRL_EN_LK4CONN_ALRT BIT(6) + +#define TCPC_EXTENDED_STATUS 0x20 +#define TCPC_EXTENDED_STATUS_VSAFE0V BIT(0) + +#define TCPC_ROLE_CTRL 0x1a +#define TCPC_ROLE_CTRL_DRP BIT(6) +#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 +#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3 +#define TCPC_ROLE_CTRL_RP_VAL_DEF 0x0 +#define TCPC_ROLE_CTRL_RP_VAL_1_5 0x1 +#define TCPC_ROLE_CTRL_RP_VAL_3_0 0x2 +#define TCPC_ROLE_CTRL_CC2_SHIFT 2 +#define TCPC_ROLE_CTRL_CC2_MASK 0x3 +#define TCPC_ROLE_CTRL_CC1_SHIFT 0 +#define TCPC_ROLE_CTRL_CC1_MASK 0x3 +#define TCPC_ROLE_CTRL_CC_RA 0x0 +#define TCPC_ROLE_CTRL_CC_RP 0x1 +#define TCPC_ROLE_CTRL_CC_RD 0x2 +#define TCPC_ROLE_CTRL_CC_OPEN 0x3 + +#define TCPC_FAULT_CTRL 0x1b + +#define TCPC_POWER_CTRL 0x1c +#define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) +#define TCPC_POWER_CTRL_BLEED_DISCHARGE BIT(3) +#define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4) +#define TCPC_DIS_VOLT_ALRM BIT(5) +#define TCPC_POWER_CTRL_VBUS_VOLT_MON BIT(6) +#define TCPC_FAST_ROLE_SWAP_EN BIT(7) + +#define TCPC_CC_STATUS 0x1d +#define TCPC_CC_STATUS_TOGGLING BIT(5) +#define TCPC_CC_STATUS_TERM BIT(4) +#define TCPC_CC_STATUS_TERM_RP 0 +#define TCPC_CC_STATUS_TERM_RD 1 +#define TCPC_CC_STATE_SRC_OPEN 0 +#define TCPC_CC_STATUS_CC2_SHIFT 2 +#define TCPC_CC_STATUS_CC2_MASK 0x3 +#define TCPC_CC_STATUS_CC1_SHIFT 0 +#define TCPC_CC_STATUS_CC1_MASK 0x3 + +#define TCPC_POWER_STATUS 0x1e +#define TCPC_POWER_STATUS_DBG_ACC_CON BIT(7) +#define TCPC_POWER_STATUS_UNINIT BIT(6) +#define TCPC_POWER_STATUS_SOURCING_VBUS BIT(4) +#define TCPC_POWER_STATUS_VBUS_DET BIT(3) +#define TCPC_POWER_STATUS_VBUS_PRES BIT(2) +#define TCPC_POWER_STATUS_VCONN_PRES BIT(1) +#define TCPC_POWER_STATUS_SINKING_VBUS BIT(0) + +#define TCPC_FAULT_STATUS 0x1f + +#define TCPC_ALERT_EXTENDED 0x21 + +#define TCPC_COMMAND 0x23 +#define TCPC_CMD_WAKE_I2C 0x11 +#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 +#define TCPC_CMD_ENABLE_VBUS_DETECT 0x33 +#define TCPC_CMD_DISABLE_SINK_VBUS 0x44 +#define TCPC_CMD_SINK_VBUS 0x55 +#define TCPC_CMD_DISABLE_SRC_VBUS 0x66 +#define TCPC_CMD_SRC_VBUS_DEFAULT 0x77 +#define TCPC_CMD_SRC_VBUS_HIGH 0x88 +#define TCPC_CMD_LOOK4CONNECTION 0x99 +#define TCPC_CMD_RXONEMORE 0xAA +#define TCPC_CMD_I2C_IDLE 0xFF + +#define TCPC_DEV_CAP_1 0x24 +#define TCPC_DEV_CAP_2 0x26 +#define TCPC_STD_INPUT_CAP 0x28 +#define TCPC_STD_OUTPUT_CAP 0x29 + +#define TCPC_MSG_HDR_INFO 0x2e +#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3) +#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0) +#define TCPC_MSG_HDR_INFO_REV_SHIFT 1 +#define TCPC_MSG_HDR_INFO_REV_MASK 0x3 + +#define TCPC_RX_DETECT 0x2f +#define TCPC_RX_DETECT_HARD_RESET BIT(5) +#define TCPC_RX_DETECT_SOP BIT(0) +#define TCPC_RX_DETECT_SOP1 BIT(1) +#define TCPC_RX_DETECT_SOP2 BIT(2) +#define TCPC_RX_DETECT_DBG1 BIT(3) +#define TCPC_RX_DETECT_DBG2 BIT(4) + +#define TCPC_RX_BYTE_CNT 0x30 +#define TCPC_RX_BUF_FRAME_TYPE 0x31 +#define TCPC_RX_BUF_FRAME_TYPE_SOP 0 +#define TCPC_RX_HDR 0x32 +#define TCPC_RX_DATA 0x34 /* through 0x4f */ + +#define TCPC_TRANSMIT 0x50 +#define TCPC_TRANSMIT_RETRY_SHIFT 4 +#define TCPC_TRANSMIT_RETRY_MASK 0x3 +#define TCPC_TRANSMIT_TYPE_SHIFT 0 +#define TCPC_TRANSMIT_TYPE_MASK 0x7 + +#define TCPC_TX_BYTE_CNT 0x51 +#define TCPC_TX_HDR 0x52 +#define TCPC_TX_DATA 0x54 /* through 0x6f */ + +#define TCPC_VBUS_VOLTAGE 0x70 +#define TCPC_VBUS_VOLTAGE_MASK 0x3ff +#define TCPC_VBUS_VOLTAGE_LSB_MV 25 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV 25 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX 0x3ff +#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 +#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 +#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 + +/* I2C_WRITE_BYTE_COUNT + 1 when TX_BUF_BYTE_x is only accessible I2C_WRITE_BYTE_COUNT */ +#define TCPC_TRANSMIT_BUFFER_MAX_LEN 31 + +struct tcpci; + +/* + * @TX_BUF_BYTE_x_hidden: + * optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT. + * @frs_sourcing_vbus: + * Optional; Callback to perform chip specific operations when FRS + * is sourcing vbus. + * @auto_discharge_disconnect: + * Optional; Enables TCPC to autonously discharge vbus on disconnect. + * @vbus_vsafe0v: + * optional; Set when TCPC can detect whether vbus is at VSAFE0V. + * @set_partner_usb_comm_capable: + * Optional; The USB Communications Capable bit indicates if port + * partner is capable of communication over the USB data lines + * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. + */ +struct tcpci_data { + struct regmap *regmap; + unsigned char TX_BUF_BYTE_x_hidden:1; + unsigned char auto_discharge_disconnect:1; + unsigned char vbus_vsafe0v:1; + + int (*init)(struct tcpci *tcpci, struct tcpci_data *data); + int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, + bool enable); + int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, + enum typec_cc_status cc); + int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink); + void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data); + void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data, + bool capable); +}; + +struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); +void tcpci_unregister_port(struct tcpci *tcpci); +irqreturn_t tcpci_irq(struct tcpci *tcpci); + +struct tcpm_port; +struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci); +#endif /* __LINUX_USB_TCPCI_H */ -- cgit v1.2.3 From 3cb7982207260e91cd7ba77120ba2e0613c8dbf2 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 8 Jul 2022 15:19:00 +0800 Subject: usb: mtu3: print endpoint type as string Print endpoint type as string instead of decimal value to make the log more readable. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220708071903.25752-2-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_debugfs.c | 8 ++++---- drivers/usb/mtu3/mtu3_trace.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_debugfs.c b/drivers/usb/mtu3/mtu3_debugfs.c index d27de647c86a..f0de99858353 100644 --- a/drivers/usb/mtu3/mtu3_debugfs.c +++ b/drivers/usb/mtu3/mtu3_debugfs.c @@ -101,13 +101,13 @@ static int mtu3_ep_used_show(struct seq_file *sf, void *unused) for (i = 0; i < mtu->num_eps; i++) { mep = mtu->in_eps + i; if (mep->flags & MTU3_EP_ENABLED) { - seq_printf(sf, "%s - type: %d\n", mep->name, mep->type); + seq_printf(sf, "%s - type: %s\n", mep->name, usb_ep_type_string(mep->type)); used++; } mep = mtu->out_eps + i; if (mep->flags & MTU3_EP_ENABLED) { - seq_printf(sf, "%s - type: %d\n", mep->name, mep->type); + seq_printf(sf, "%s - type: %s\n", mep->name, usb_ep_type_string(mep->type)); used++; } } @@ -177,8 +177,8 @@ static int mtu3_ep_info_show(struct seq_file *sf, void *unused) unsigned long flags; spin_lock_irqsave(&mtu->lock, flags); - seq_printf(sf, "ep - type:%d, maxp:%d, slot:%d, flags:%x\n", - mep->type, mep->maxp, mep->slot, mep->flags); + seq_printf(sf, "ep - type:%s, maxp:%d, slot:%d, flags:%x\n", + usb_ep_type_string(mep->type), mep->maxp, mep->slot, mep->flags); spin_unlock_irqrestore(&mtu->lock, flags); return 0; diff --git a/drivers/usb/mtu3/mtu3_trace.h b/drivers/usb/mtu3/mtu3_trace.h index 1b897636daf2..a09deae1146f 100644 --- a/drivers/usb/mtu3/mtu3_trace.h +++ b/drivers/usb/mtu3/mtu3_trace.h @@ -238,8 +238,8 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __entry->direction = mep->is_in; __entry->gpd_ring = &mep->gpd_ring; ), - TP_printk("%s: type %d maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c", - __get_str(name), __entry->type, + TP_printk("%s: type %s maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c", + __get_str(name), usb_ep_type_string(__entry->type), __entry->maxp, __entry->slot, __entry->mult, __entry->maxburst, __entry->gpd_ring, &__entry->gpd_ring->dma, -- cgit v1.2.3 From 269f49ff381aade6cfd20deeae86a8af7ae35b5a Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 8 Jul 2022 15:19:01 +0800 Subject: usb: mtu3: add feature to disable device's usb3 port We may want to disable device's usb3 port when the combo phy is switched from usb3 mode to pcie mode for some projects. Meanwhile rename member @is_u3_ip to @u3_capable to avoid misleading, due to the member's value may be changed when disable usb3 port, but the controller is still a usb3 IP which also tells us how to manage the fifo. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220708071903.25752-3-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 4 +++- drivers/usb/mtu3/mtu3_core.c | 32 +++++++++++++++++++------------- drivers/usb/mtu3/mtu3_plat.c | 4 ++-- 3 files changed, 24 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 8408e1b1a24a..2d7b57e07eee 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -317,6 +317,7 @@ static inline struct ssusb_mtk *dev_to_ssusb(struct device *dev) * @ep0_req: dummy request used while handling standard USB requests * for GET_STATUS and SET_SEL * @setup_buf: ep0 response buffer for GET_STATUS and SET_SEL requests + * @u3_capable: is capable of supporting USB3 */ struct mtu3 { spinlock_t lock; @@ -353,11 +354,12 @@ struct mtu3 { unsigned softconnect:1; unsigned u1_enable:1; unsigned u2_enable:1; - unsigned is_u3_ip:1; + unsigned u3_capable:1; unsigned delayed_status:1; unsigned gen2cp:1; unsigned connected:1; unsigned async_callbacks:1; + unsigned separate_fifo:1; u8 address; u8 test_mode_nr; diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index 3c6a670efafa..0ca173af87bb 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -100,7 +100,7 @@ static int mtu3_device_enable(struct mtu3 *mtu) mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); - if (mtu->is_u3_ip) { + if (mtu->u3_capable) { check_clk = SSUSB_U3_MAC_RST_B_STS; mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), (SSUSB_U3_PORT_DIS | SSUSB_U3_PORT_PDN | @@ -112,7 +112,7 @@ static int mtu3_device_enable(struct mtu3 *mtu) if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG) { mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_OTG_SEL); - if (mtu->is_u3_ip) + if (mtu->u3_capable) mtu3_setbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_DUAL_MODE); } @@ -124,7 +124,7 @@ static void mtu3_device_disable(struct mtu3 *mtu) { void __iomem *ibase = mtu->ippc_base; - if (mtu->is_u3_ip) + if (mtu->u3_capable) mtu3_setbits(ibase, SSUSB_U3_CTRL(0), (SSUSB_U3_PORT_DIS | SSUSB_U3_PORT_PDN)); @@ -133,7 +133,7 @@ static void mtu3_device_disable(struct mtu3 *mtu) if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG) { mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_OTG_SEL); - if (mtu->is_u3_ip) + if (mtu->u3_capable) mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_DUAL_MODE); } @@ -146,7 +146,7 @@ static void mtu3_dev_power_on(struct mtu3 *mtu) void __iomem *ibase = mtu->ippc_base; mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); - if (mtu->is_u3_ip) + if (mtu->u3_capable) mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); @@ -156,7 +156,7 @@ static void mtu3_dev_power_down(struct mtu3 *mtu) { void __iomem *ibase = mtu->ippc_base; - if (mtu->is_u3_ip) + if (mtu->u3_capable) mtu3_setbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); @@ -213,7 +213,7 @@ static void mtu3_intr_enable(struct mtu3 *mtu) value = SUSPEND_INTR | RESUME_INTR | RESET_INTR; mtu3_writel(mbase, U3D_COMMON_USB_INTR_ENABLE, value); - if (mtu->is_u3_ip) { + if (mtu->u3_capable) { /* Enable U3 LTSSM interrupts */ value = HOT_RST_INTR | WARM_RST_INTR | ENTER_U3_INTR | EXIT_U3_INTR; @@ -273,7 +273,7 @@ static void mtu3_csr_init(struct mtu3 *mtu) { void __iomem *mbase = mtu->mac_base; - if (mtu->is_u3_ip) { + if (mtu->u3_capable) { /* disable LGO_U1/U2 by default */ mtu3_clrbits(mbase, U3D_LINK_POWER_CONTROL, SW_U1_REQUEST_ENABLE | SW_U2_REQUEST_ENABLE); @@ -341,7 +341,7 @@ void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set) void mtu3_dev_on_off(struct mtu3 *mtu, int is_on) { - if (mtu->is_u3_ip && mtu->speed >= USB_SPEED_SUPER) + if (mtu->u3_capable && mtu->speed >= USB_SPEED_SUPER) mtu3_ss_func_set(mtu, is_on); else mtu3_hs_softconn_set(mtu, is_on); @@ -544,7 +544,7 @@ static void get_ep_fifo_config(struct mtu3 *mtu) struct mtu3_fifo_info *rx_fifo; u32 fifosize; - if (mtu->is_u3_ip) { + if (mtu->separate_fifo) { fifosize = mtu3_readl(mtu->mac_base, U3D_CAP_EPNTXFFSZ); tx_fifo = &mtu->tx_fifo; tx_fifo->base = 0; @@ -821,6 +821,10 @@ static irqreturn_t mtu3_irq(int irq, void *data) static void mtu3_check_params(struct mtu3 *mtu) { + /* device's u3 port (port0) is disabled */ + if (mtu->u3_capable && (mtu->ssusb->u3p_dis_msk & BIT(0))) + mtu->u3_capable = 0; + /* check the max_speed parameter */ switch (mtu->max_speed) { case USB_SPEED_FULL: @@ -838,7 +842,7 @@ static void mtu3_check_params(struct mtu3 *mtu) break; } - if (!mtu->is_u3_ip && (mtu->max_speed > USB_SPEED_HIGH)) + if (!mtu->u3_capable && (mtu->max_speed > USB_SPEED_HIGH)) mtu->max_speed = USB_SPEED_HIGH; mtu->speed = mtu->max_speed; @@ -857,10 +861,12 @@ static int mtu3_hw_init(struct mtu3 *mtu) mtu->gen2cp = !!(mtu->hw_version >= MTU3_TRUNK_VERS_1003); value = mtu3_readl(mtu->ippc_base, U3D_SSUSB_IP_DEV_CAP); - mtu->is_u3_ip = !!SSUSB_IP_DEV_U3_PORT_NUM(value); + mtu->u3_capable = !!SSUSB_IP_DEV_U3_PORT_NUM(value); + /* usb3 ip uses separate fifo */ + mtu->separate_fifo = mtu->u3_capable; dev_info(mtu->dev, "IP version 0x%x(%s IP)\n", mtu->hw_version, - mtu->is_u3_ip ? "U3" : "U2"); + mtu->u3_capable ? "U3" : "U2"); mtu3_check_params(mtu); diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index d14494b30064..42987d08ce8c 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -244,6 +244,8 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN) ssusb->dr_mode = USB_DR_MODE_OTG; + of_property_read_u32(node, "mediatek,u3p-dis-msk", &ssusb->u3p_dis_msk); + if (ssusb->dr_mode == USB_DR_MODE_PERIPHERAL) goto out; @@ -255,8 +257,6 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) } /* optional property, ignore the error if it does not exist */ - of_property_read_u32(node, "mediatek,u3p-dis-msk", - &ssusb->u3p_dis_msk); of_property_read_u32(node, "mediatek,u2p-dis-msk", &ssusb->u2p_dis_msk); -- cgit v1.2.3 From 683ff6e485f1d16fe3ad4f5fbd04af11ce917caf Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 8 Jul 2022 15:19:02 +0800 Subject: usb: mtu3: check capability of usb3 dual role It is capable of supporting usb3 dual role if there is at least one usb3 port for device and xhci controller, we can check it from the controller's capability, so no need the property "mediatek,usb3-drd" anymore, but I find the property is not decribed in dt-binding. Meanwhile, also take into account if the u3 port is disabled when the u3 phy is shared with pcie. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220708071903.25752-4-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_plat.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 42987d08ce8c..4cb65346789d 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -190,6 +190,31 @@ static void ssusb_ip_sw_reset(struct ssusb_mtk *ssusb) mtu3_setbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); } +static void ssusb_u3_drd_check(struct ssusb_mtk *ssusb) +{ + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + u32 dev_u3p_num; + u32 host_u3p_num; + u32 value; + + /* u3 port0 is disabled */ + if (ssusb->u3p_dis_msk & BIT(0)) { + otg_sx->is_u3_drd = false; + goto out; + } + + value = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_DEV_CAP); + dev_u3p_num = SSUSB_IP_DEV_U3_PORT_NUM(value); + + value = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_XHCI_CAP); + host_u3p_num = SSUSB_IP_XHCI_U3_PORT_NUM(value); + + otg_sx->is_u3_drd = !!(dev_u3p_num && host_u3p_num); + +out: + dev_info(ssusb->dev, "usb3-drd: %d\n", otg_sx->is_u3_drd); +} + static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) { struct device_node *node = pdev->dev.of_node; @@ -270,7 +295,6 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) goto out; /* if dual-role mode is supported */ - otg_sx->is_u3_drd = of_property_read_bool(node, "mediatek,usb3-drd"); otg_sx->manual_drd_enabled = of_property_read_bool(node, "enable-manual-drd"); otg_sx->role_sw_used = of_property_read_bool(node, "usb-role-switch"); @@ -290,9 +314,8 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) } out: - dev_info(dev, "dr_mode: %d, is_u3_dr: %d, drd: %s\n", - ssusb->dr_mode, otg_sx->is_u3_drd, - otg_sx->manual_drd_enabled ? "manual" : "auto"); + dev_info(dev, "dr_mode: %d, drd: %s\n", ssusb->dr_mode, + otg_sx->manual_drd_enabled ? "manual" : "auto"); dev_info(dev, "u2p_dis_msk: %x, u3p_dis_msk: %x\n", ssusb->u2p_dis_msk, ssusb->u3p_dis_msk); @@ -353,6 +376,7 @@ static int mtu3_probe(struct platform_device *pdev) } ssusb_ip_sw_reset(ssusb); + ssusb_u3_drd_check(ssusb); if (IS_ENABLED(CONFIG_USB_MTU3_HOST)) ssusb->dr_mode = USB_DR_MODE_HOST; -- cgit v1.2.3 From 1742b765982cd6d9d5e76ba040598981d53e0a0f Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 8 Jul 2022 15:19:03 +0800 Subject: usb: mtu3: support function remote wakeup Add function wake notification to support function remote wakeup, currently assume the composite device only enable function wake for the first interface. Forward request to function driver when the recipient is an interface, including: GetStatus() request to the first interface in a function returns the information about 'function remote wakeup' and 'function remote wakeup capalbe'; SetFeature request of FUNCTION_SUSPEND to an interface recipient, the controller driver saves the suspend option; Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20220708071903.25752-5-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_gadget.c | 18 ++++++++++++++++++ drivers/usb/mtu3/mtu3_gadget_ep0.c | 8 ++++---- drivers/usb/mtu3/mtu3_hw_regs.h | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 30999b4debb8..80236e7b0895 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -433,6 +433,13 @@ static int mtu3_gadget_get_frame(struct usb_gadget *gadget) return (int)mtu3_readl(mtu->mac_base, U3D_USB20_FRAME_NUM); } +static void function_wake_notif(struct mtu3 *mtu, u8 intf) +{ + mtu3_writel(mtu->mac_base, U3D_DEV_NOTIF_0, + TYPE_FUNCTION_WAKE | DEV_NOTIF_VAL_FW(intf)); + mtu3_setbits(mtu->mac_base, U3D_DEV_NOTIF_0, SEND_DEV_NOTIF); +} + static int mtu3_gadget_wakeup(struct usb_gadget *gadget) { struct mtu3 *mtu = gadget_to_mtu3(gadget); @@ -446,7 +453,18 @@ static int mtu3_gadget_wakeup(struct usb_gadget *gadget) spin_lock_irqsave(&mtu->lock, flags); if (mtu->g.speed >= USB_SPEED_SUPER) { + /* + * class driver may do function wakeup even UFP is in U0, + * and UX_EXIT only takes effect in U1/U2/U3; + */ mtu3_setbits(mtu->mac_base, U3D_LINK_POWER_CONTROL, UX_EXIT); + /* + * Assume there's only one function on the composite device + * and enable remote wake for the first interface. + * FIXME if the IAD (interface association descriptor) shows + * there is more than one function. + */ + function_wake_notif(mtu, 0); } else { mtu3_setbits(mtu->mac_base, U3D_POWER_MANAGEMENT, RESUME); spin_unlock_irqrestore(&mtu->lock, flags); diff --git a/drivers/usb/mtu3/mtu3_gadget_ep0.c b/drivers/usb/mtu3/mtu3_gadget_ep0.c index 7e7013508af6..e4fd1bb14a55 100644 --- a/drivers/usb/mtu3/mtu3_gadget_ep0.c +++ b/drivers/usb/mtu3/mtu3_gadget_ep0.c @@ -226,6 +226,8 @@ ep0_get_status(struct mtu3 *mtu, const struct usb_ctrlrequest *setup) break; case USB_RECIP_INTERFACE: + /* status of function remote wakeup, forward request */ + handled = 0; break; case USB_RECIP_ENDPOINT: epnum = (u8) le16_to_cpu(setup->wIndex); @@ -397,10 +399,8 @@ static int ep0_handle_feature(struct mtu3 *mtu, /* superspeed only */ if (value == USB_INTRF_FUNC_SUSPEND && mtu->g.speed >= USB_SPEED_SUPER) { - /* - * forward the request because function drivers - * should handle it - */ + /* forward the request for function suspend */ + mtu->may_wakeup = !!(index & USB_INTRF_FUNC_SUSPEND_RW); handled = 0; } break; diff --git a/drivers/usb/mtu3/mtu3_hw_regs.h b/drivers/usb/mtu3/mtu3_hw_regs.h index 072db1f6470e..519a58301f45 100644 --- a/drivers/usb/mtu3/mtu3_hw_regs.h +++ b/drivers/usb/mtu3/mtu3_hw_regs.h @@ -341,6 +341,8 @@ #define U3D_LINK_UX_INACT_TIMER (SSUSB_USB3_SYS_CSR_BASE + 0x020C) #define U3D_LINK_POWER_CONTROL (SSUSB_USB3_SYS_CSR_BASE + 0x0210) #define U3D_LINK_ERR_COUNT (SSUSB_USB3_SYS_CSR_BASE + 0x0214) +#define U3D_DEV_NOTIF_0 (SSUSB_USB3_SYS_CSR_BASE + 0x0290) +#define U3D_DEV_NOTIF_1 (SSUSB_USB3_SYS_CSR_BASE + 0x0294) /*---------------- SSUSB_USB3_SYS_CSR FIELD DEFINITION ----------------*/ @@ -365,6 +367,20 @@ #define CLR_LINK_ERR_CNT BIT(16) #define LINK_ERROR_COUNT GENMASK(15, 0) +/* U3D_DEV_NOTIF_0 */ +#define DEV_NOTIF_TYPE_SPECIFIC_LOW_MSK GENMASK(31, 8) +#define DEV_NOTIF_VAL_FW(x) (((x) & 0xff) << 8) +#define DEV_NOTIF_VAL_LTM(x) (((x) & 0xfff) << 8) +#define DEV_NOTIF_VAL_IAM(x) (((x) & 0xffff) << 8) +#define DEV_NOTIF_TYPE_MSK GENMASK(7, 4) +/* Notification Type */ +#define TYPE_FUNCTION_WAKE (0x1 << 4) +#define TYPE_LATENCY_TOLERANCE_MESSAGE (0x2 << 4) +#define TYPE_BUS_INTERVAL_ADJUST_MESSAGE (0x3 << 4) +#define TYPE_HOST_ROLE_REQUEST (0x4 << 4) +#define TYPE_SUBLINK_SPEED (0x5 << 4) +#define SEND_DEV_NOTIF BIT(0) + /*---------------- SSUSB_USB2_CSR REGISTER DEFINITION ----------------*/ #define U3D_POWER_MANAGEMENT (SSUSB_USB2_CSR_BASE + 0x0004) -- cgit v1.2.3 From 8bc063641cebf9d555e41d135db2b5035b521768 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Thu, 30 Jun 2022 12:35:29 -0700 Subject: usb: misc: Add onboard_usb_hub driver The main issue this driver addresses is that a USB hub needs to be powered before it can be discovered. For discrete onboard hubs (an example for such a hub is the Realtek RTS5411) this is often solved by supplying the hub with an 'always-on' regulator, which is kind of a hack. Some onboard hubs may require further initialization steps, like changing the state of a GPIO or enabling a clock, which requires even more hacks. This driver creates a platform device representing the hub which performs the necessary initialization. Currently it only supports switching on a single regulator, support for multiple regulators or other actions can be added as needed. Different initialization sequences can be supported based on the compatible string. Besides performing the initialization the driver can be configured to power the hub off during system suspend. This can help to extend battery life on battery powered devices which have no requirements to keep the hub powered during suspend. The driver can also be configured to leave the hub powered when a wakeup capable USB device is connected when suspending, and power it off otherwise. Technically the driver consists of two drivers, the platform driver described above and a very thin USB driver that subclasses the generic driver. The purpose of this driver is to provide the platform driver with the USB devices corresponding to the hub(s) (a hub controller may provide multiple 'logical' hubs, e.g. one to support USB 2.0 and another for USB 3.x). Co-developed-by: Ravi Chandra Sadineni Reviewed-by: Douglas Anderson Signed-off-by: Ravi Chandra Sadineni Signed-off-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20220630123445.v24.3.I7c9a1f1d6ced41dd8310e8a03da666a32364e790@changeid Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-platform-onboard-usb-hub | 8 + MAINTAINERS | 7 + drivers/usb/core/Makefile | 4 + drivers/usb/misc/Kconfig | 16 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/onboard_usb_hub.c | 428 +++++++++++++++++++++ drivers/usb/misc/onboard_usb_hub.h | 17 + drivers/usb/misc/onboard_usb_hub_pdevs.c | 142 +++++++ include/linux/usb/onboard_hub.h | 18 + 9 files changed, 641 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub create mode 100644 drivers/usb/misc/onboard_usb_hub.c create mode 100644 drivers/usb/misc/onboard_usb_hub.h create mode 100644 drivers/usb/misc/onboard_usb_hub_pdevs.c create mode 100644 include/linux/usb/onboard_hub.h (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub new file mode 100644 index 000000000000..42deb0552065 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub @@ -0,0 +1,8 @@ +What: /sys/bus/platform/devices//always_powered_in_suspend +Date: June 2022 +KernelVersion: 5.20 +Contact: Matthias Kaehlcke + linux-usb@vger.kernel.org +Description: + (RW) Controls whether the USB hub remains always powered + during system suspend or not. \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index 7533cb27adc0..f43b126eaf59 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14848,6 +14848,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov9734.c +ONBOARD USB HUB DRIVER +M: Matthias Kaehlcke +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub +F: drivers/usb/misc/onboard_usb_hub.c + ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index 18e874b0441e..7d338e9c0657 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -12,6 +12,10 @@ usbcore-$(CONFIG_OF) += of.o usbcore-$(CONFIG_USB_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o +ifdef CONFIG_USB_ONBOARD_HUB +usbcore-y += ../misc/onboard_usb_hub_pdevs.o +endif + obj-$(CONFIG_USB) += usbcore.o obj-$(CONFIG_USB_LEDS_TRIGGER_USBPORT) += ledtrig-usbport.o diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 4c5ddbd75b7e..9367c12c7e6f 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -295,3 +295,19 @@ config BRCM_USB_PINMAP This option enables support for remapping some USB external signals, which are typically on dedicated pins on the chip, to any gpio. + +config USB_ONBOARD_HUB + tristate "Onboard USB hub support" + depends on OF || COMPILE_TEST + help + Say Y here if you want to support discrete onboard USB hubs that + don't require an additional control bus for initialization, but + need some non-trivial form of initialization, such as enabling a + power regulator. An example for such a hub is the Realtek + RTS5411. + + This driver can be used as a module but its state (module vs + builtin) must match the state of the USB subsystem. Enabling + this config will enable the driver and it will automatically + match the state of the USB subsystem. If this driver is a + module it will be called onboard_usb_hub. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 35bdb4b6c3b6..93581baec3a8 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -33,3 +33,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o +obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c new file mode 100644 index 000000000000..6b9b949d17d3 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (c) 2022, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "onboard_usb_hub.h" + +static struct usb_device_driver onboard_hub_usbdev_driver; + +/************************** Platform driver **************************/ + +struct usbdev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_hub { + struct regulator *vdd; + struct device *dev; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct work_struct attach_usb_driver_work; + struct mutex lock; +}; + +static int onboard_hub_power_on(struct onboard_hub *hub) +{ + int err; + + err = regulator_enable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to enable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = true; + + return 0; +} + +static int onboard_hub_power_off(struct onboard_hub *hub) +{ + int err; + + err = regulator_disable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to disable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_hub_suspend(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + struct usbdev_node *node; + bool power_off = true; + + if (hub->always_powered_in_suspend) + return 0; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&hub->lock); + + if (!power_off) + return 0; + + return onboard_hub_power_off(hub); +} + +static int __maybe_unused onboard_hub_resume(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + + if (hub->is_powered_on) + return 0; + + return onboard_hub_power_on(hub); +} + +static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) +{ + snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); +} + +static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + int err; + + mutex_lock(&hub->lock); + + if (hub->going_away) { + err = -EINVAL; + goto error; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + err = -ENOMEM; + goto error; + } + + node->udev = udev; + + list_add(&node->list, &hub->udev_list); + + mutex_unlock(&hub->lock); + + get_udev_link_name(udev, link_name, sizeof(link_name)); + WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); + + return 0; + +error: + mutex_unlock(&hub->lock); + + return err; +} + +static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + + get_udev_link_name(udev, link_name, sizeof(link_name)); + sysfs_remove_link(&hub->dev->kobj, link_name); + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + kfree(node); + break; + } + } + + mutex_unlock(&hub->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const struct onboard_hub *hub = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + hub->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_hub_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; +ATTRIBUTE_GROUPS(onboard_hub); + +static void onboard_hub_attach_usb_driver(struct work_struct *work) +{ + int err; + + err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); + if (err) + pr_err("Failed to attach USB driver: %d\n", err); +} + +static int onboard_hub_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct onboard_hub *hub; + int err; + + hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + hub->dev = dev; + mutex_init(&hub->lock); + INIT_LIST_HEAD(&hub->udev_list); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_power_on(hub); + if (err) + return err; + + /* + * The USB driver might have been detached from the USB devices by + * onboard_hub_remove() (e.g. through an 'unbind' by userspace), + * make sure to re-attach it if needed. + * + * This needs to be done deferred to avoid self-deadlocks on systems + * with nested onboard hubs. + */ + INIT_WORK(&hub->attach_usb_driver_work, onboard_hub_attach_usb_driver); + schedule_work(&hub->attach_usb_driver_work); + + return 0; +} + +static int onboard_hub_remove(struct platform_device *pdev) +{ + struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); + struct usbdev_node *node; + struct usb_device *udev; + + hub->going_away = true; + + if (&hub->attach_usb_driver_work != current_work()) + cancel_work_sync(&hub->attach_usb_driver_work); + + mutex_lock(&hub->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&hub->udev_list)) { + node = list_first_entry(&hub->udev_list, struct usbdev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_hub_remove_usbdev(), + * which acquires hub->lock. We must release the lock first. + */ + get_device(&udev->dev); + mutex_unlock(&hub->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&hub->lock); + } + + mutex_unlock(&hub->lock); + + return onboard_hub_power_off(hub); +} + +MODULE_DEVICE_TABLE(of, onboard_hub_match); + +static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) +}; + +static struct platform_driver onboard_hub_driver = { + .probe = onboard_hub_probe, + .remove = onboard_hub_remove, + + .driver = { + .name = "onboard-usb-hub", + .of_match_table = onboard_hub_match, + .pm = pm_ptr(&onboard_hub_pm_ops), + .dev_groups = onboard_hub_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_REALTEK 0x0bda + +/* + * Returns the onboard_hub platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_hub *_find_onboard_hub(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct onboard_hub *hub; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + np = of_parse_phandle(dev->of_node, "peer-hub", 0); + if (!np) { + dev_err(dev, "failed to find device node for peer hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + } + + hub = dev_get_drvdata(&pdev->dev); + put_device(&pdev->dev); + + /* + * The presence of drvdata ('hub') indicates that the platform driver + * finished probing. This handles the case where (conceivably) we could + * be running at the exact same time as the platform driver's probe. If + * we detect the race we request probe deferral and we'll come back and + * try again. + */ + if (!hub) + return ERR_PTR(-EPROBE_DEFER); + + return hub; +} + +static int onboard_hub_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_hub *hub; + int err; + + /* ignore supported hubs without device tree node */ + if (!dev->of_node) + return -ENODEV; + + hub = _find_onboard_hub(dev); + if (IS_ERR(hub)) + return PTR_ERR(hub); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_add_usbdev(hub, udev); + if (err) + return err; + + return 0; +} + +static void onboard_hub_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_hub *hub = dev_get_drvdata(&udev->dev); + + onboard_hub_remove_usbdev(hub, udev); +} + +static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ + {} +}; +MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); + +static struct usb_device_driver onboard_hub_usbdev_driver = { + .name = "onboard-usb-hub", + .probe = onboard_hub_usbdev_probe, + .disconnect = onboard_hub_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_hub_id_table, +}; + +static int __init onboard_hub_init(void) +{ + int ret; + + ret = platform_driver_register(&onboard_hub_driver); + if (ret) + return ret; + + ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); + if (ret) + platform_driver_unregister(&onboard_hub_driver); + + return ret; +} +module_init(onboard_hub_init); + +static void __exit onboard_hub_exit(void) +{ + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + platform_driver_unregister(&onboard_hub_driver); +} +module_exit(onboard_hub_exit); + +MODULE_AUTHOR("Matthias Kaehlcke "); +MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h new file mode 100644 index 000000000000..d3a5b6938582 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2022, Google LLC + */ + +#ifndef _USB_MISC_ONBOARD_USB_HUB_H +#define _USB_MISC_ONBOARD_USB_HUB_H + +static const struct of_device_id onboard_hub_match[] = { + { .compatible = "usbbda,411" }, + { .compatible = "usbbda,5411" }, + { .compatible = "usbbda,414" }, + { .compatible = "usbbda,5414" }, + {} +}; + +#endif /* _USB_MISC_ONBOARD_USB_HUB_H */ diff --git a/drivers/usb/misc/onboard_usb_hub_pdevs.c b/drivers/usb/misc/onboard_usb_hub_pdevs.c new file mode 100644 index 000000000000..a0a5f719129f --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub_pdevs.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * API for creating and destroying USB onboard hub platform devices + * + * Copyright (c) 2022, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "onboard_usb_hub.h" + +struct pdev_list_entry { + struct platform_device *pdev; + struct list_head node; +}; + +static bool of_is_onboard_usb_hub(const struct device_node *np) +{ + return !!of_match_node(onboard_hub_match, np); +} + +/** + * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs + * @parent_hub : parent hub to scan for connected onboard hubs + * @pdev_list : list of onboard hub platform devices owned by the parent hub + * + * Creates a platform device for each supported onboard hub that is connected to + * the given parent hub. The platform device is in charge of initializing the + * hub (enable regulators, take the hub out of reset, ...) and can optionally + * control whether the hub remains powered during system suspend or not. + * + * To keep track of the platform devices they are added to a list that is owned + * by the parent hub. + * + * Some background about the logic in this function, which can be a bit hard + * to follow: + * + * Root hubs don't have dedicated device tree nodes, but use the node of their + * HCD. The primary and secondary HCD are usually represented by a single DT + * node. That means the root hubs of the primary and secondary HCD share the + * same device tree node (the HCD node). As a result this function can be called + * twice with the same DT node for root hubs. We only want to create a single + * platform device for each physical onboard hub, hence for root hubs the loop + * is only executed for the root hub of the primary HCD. Since the function + * scans through all child nodes it still creates pdevs for onboard hubs + * connected to the root hub of the secondary HCD if needed. + * + * Further there must be only one platform device for onboard hubs with a peer + * hub (the hub is a single physical device). To achieve this two measures are + * taken: pdevs for onboard hubs with a peer are only created when the function + * is called on behalf of the parent hub that is connected to the primary HCD + * (directly or through other hubs). For onboard hubs connected to root hubs + * the function processes the nodes of both peers. A platform device is only + * created if the peer hub doesn't have one already. + */ +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +{ + int i; + struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus); + struct device_node *np, *npc; + struct platform_device *pdev; + struct pdev_list_entry *pdle; + + if (!parent_hub->dev.of_node) + return; + + if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd)) + return; + + for (i = 1; i <= parent_hub->maxchild; i++) { + np = usb_of_get_device_node(parent_hub, i); + if (!np) + continue; + + if (!of_is_onboard_usb_hub(np)) + goto node_put; + + npc = of_parse_phandle(np, "peer-hub", 0); + if (npc) { + if (!usb_hcd_is_primary_hcd(hcd)) { + of_node_put(npc); + goto node_put; + } + + pdev = of_find_device_by_node(npc); + of_node_put(npc); + + if (pdev) { + put_device(&pdev->dev); + goto node_put; + } + } + + pdev = of_platform_device_create(np, NULL, &parent_hub->dev); + if (!pdev) { + dev_err(&parent_hub->dev, + "failed to create platform device for onboard hub '%pOF'\n", np); + goto node_put; + } + + pdle = kzalloc(sizeof(*pdle), GFP_KERNEL); + if (!pdle) { + of_platform_device_destroy(&pdev->dev, NULL); + goto node_put; + } + + pdle->pdev = pdev; + list_add(&pdle->node, pdev_list); + +node_put: + of_node_put(np); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); + +/** + * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices + * @pdev_list : list of onboard hub platform devices + * + * Destroys the platform devices in the given list and frees the memory associated + * with the list entry. + */ +void onboard_hub_destroy_pdevs(struct list_head *pdev_list) +{ + struct pdev_list_entry *pdle, *tmp; + + list_for_each_entry_safe(pdle, tmp, pdev_list, node) { + list_del(&pdle->node); + of_platform_device_destroy(&pdle->pdev->dev, NULL); + kfree(pdle); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); diff --git a/include/linux/usb/onboard_hub.h b/include/linux/usb/onboard_hub.h new file mode 100644 index 000000000000..d9373230556e --- /dev/null +++ b/include/linux/usb/onboard_hub.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LINUX_USB_ONBOARD_HUB_H +#define __LINUX_USB_ONBOARD_HUB_H + +struct usb_device; +struct list_head; + +#if IS_ENABLED(CONFIG_USB_ONBOARD_HUB) +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list); +void onboard_hub_destroy_pdevs(struct list_head *pdev_list); +#else +static inline void onboard_hub_create_pdevs(struct usb_device *parent_hub, + struct list_head *pdev_list) {} +static inline void onboard_hub_destroy_pdevs(struct list_head *pdev_list) {} +#endif + +#endif /* __LINUX_USB_ONBOARD_HUB_H */ -- cgit v1.2.3 From 3a6bf4a08142826698121bef16b244dcf50a6431 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Thu, 30 Jun 2022 12:35:30 -0700 Subject: usb: core: hub: Create platform devices for onboard hubs in hub_probe() Call onboard_hub_create/destroy_pdevs() from hub_probe/disconnect() to create/destroy platform devices for onboard USB hubs that may be connected to the hub. The onboard hubs must have nodes in the device tree. onboard_hub_create/destroy_pdevs() are NOPs unless CONFIG_USB_ONBOARD_HUB=y/m. Also add a field to struct usb_hub to keep track of the onboard hub platform devices that are owned by the hub. Reviewed-by: Douglas Anderson Signed-off-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20220630123445.v24.4.Ic9dd36078f9d803de82ca01a6700c58b8e4de27e@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 9 ++++++++- drivers/usb/core/hub.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b7f66dcd1fe0..2633acde7ac1 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -1752,6 +1753,8 @@ static void hub_disconnect(struct usb_interface *intf) if (hub->quirk_disable_autosuspend) usb_autopm_put_interface(intf); + onboard_hub_destroy_pdevs(&hub->onboard_hub_devs); + kref_put(&hub->kref, hub_release); } @@ -1869,6 +1872,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) INIT_DELAYED_WORK(&hub->leds, led_work); INIT_DELAYED_WORK(&hub->init_work, NULL); INIT_WORK(&hub->events, hub_event); + INIT_LIST_HEAD(&hub->onboard_hub_devs); spin_lock_init(&hub->irq_urb_lock); timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0); usb_get_intf(intf); @@ -1889,8 +1893,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) usb_autopm_get_interface_no_resume(intf); } - if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) + if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) { + onboard_hub_create_pdevs(hdev, &hub->onboard_hub_devs); + return 0; + } hub_disconnect(intf); return -ENODEV; diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 3fcb38099ce3..b2925856b4cb 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -73,6 +73,7 @@ struct usb_hub { spinlock_t irq_urb_lock; struct timer_list irq_urb_retry; struct usb_port **ports; + struct list_head onboard_hub_devs; }; /** -- cgit v1.2.3 From 23385cec5f354794dadced7f28c31da7ae3eb54c Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 4 Jul 2022 16:18:11 +0200 Subject: usb: dwc3: gadget: refactor dwc3_repare_one_trb The function __dwc3_prepare_one_trb has many parameters. Since it is only used in dwc3_prepare_one_trb there is no point in keeping the function. We merge both functions and get rid of the big list of parameters. Fixes: 40d829fb2ec6 ("usb: dwc3: gadget: Correct ISOC DATA PIDs for short packets") Cc: stable Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220704141812.1532306-2-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 92 +++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 52 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index a944c7a6c83a..dcd8fc209ccd 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1183,17 +1183,49 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) return trbs_left; } -static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, - dma_addr_t dma, unsigned int length, unsigned int chain, - unsigned int node, unsigned int stream_id, - unsigned int short_not_ok, unsigned int no_interrupt, - unsigned int is_last, bool must_interrupt) +/** + * dwc3_prepare_one_trb - setup one TRB from one request + * @dep: endpoint for which this request is prepared + * @req: dwc3_request pointer + * @trb_length: buffer size of the TRB + * @chain: should this TRB be chained to the next? + * @node: only for isochronous endpoints. First TRB needs different type. + * @use_bounce_buffer: set to use bounce buffer + * @must_interrupt: set to interrupt on TRB completion + */ +static void dwc3_prepare_one_trb(struct dwc3_ep *dep, + struct dwc3_request *req, unsigned int trb_length, + unsigned int chain, unsigned int node, bool use_bounce_buffer, + bool must_interrupt) { + struct dwc3_trb *trb; + dma_addr_t dma; + unsigned int stream_id = req->request.stream_id; + unsigned int short_not_ok = req->request.short_not_ok; + unsigned int no_interrupt = req->request.no_interrupt; + unsigned int is_last = req->request.is_last; struct dwc3 *dwc = dep->dwc; struct usb_gadget *gadget = dwc->gadget; enum usb_device_speed speed = gadget->speed; - trb->size = DWC3_TRB_SIZE_LENGTH(length); + if (use_bounce_buffer) + dma = dep->dwc->bounce_addr; + else if (req->request.num_sgs > 0) + dma = sg_dma_address(req->start_sg); + else + dma = req->request.dma; + + trb = &dep->trb_pool[dep->trb_enqueue]; + + if (!req->trb) { + dwc3_gadget_move_started_request(req); + req->trb = trb; + req->trb_dma = dwc3_trb_dma_offset(dep, trb); + } + + req->num_trbs++; + + trb->size = DWC3_TRB_SIZE_LENGTH(trb_length); trb->bpl = lower_32_bits(dma); trb->bph = upper_32_bits(dma); @@ -1233,10 +1265,10 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, unsigned int mult = 2; unsigned int maxp = usb_endpoint_maxp(ep->desc); - if (length <= (2 * maxp)) + if (trb_length <= (2 * maxp)) mult--; - if (length <= maxp) + if (trb_length <= maxp) mult--; trb->size |= DWC3_TRB_SIZE_PCM1(mult); @@ -1310,50 +1342,6 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, trace_dwc3_prepare_trb(dep, trb); } -/** - * dwc3_prepare_one_trb - setup one TRB from one request - * @dep: endpoint for which this request is prepared - * @req: dwc3_request pointer - * @trb_length: buffer size of the TRB - * @chain: should this TRB be chained to the next? - * @node: only for isochronous endpoints. First TRB needs different type. - * @use_bounce_buffer: set to use bounce buffer - * @must_interrupt: set to interrupt on TRB completion - */ -static void dwc3_prepare_one_trb(struct dwc3_ep *dep, - struct dwc3_request *req, unsigned int trb_length, - unsigned int chain, unsigned int node, bool use_bounce_buffer, - bool must_interrupt) -{ - struct dwc3_trb *trb; - dma_addr_t dma; - unsigned int stream_id = req->request.stream_id; - unsigned int short_not_ok = req->request.short_not_ok; - unsigned int no_interrupt = req->request.no_interrupt; - unsigned int is_last = req->request.is_last; - - if (use_bounce_buffer) - dma = dep->dwc->bounce_addr; - else if (req->request.num_sgs > 0) - dma = sg_dma_address(req->start_sg); - else - dma = req->request.dma; - - trb = &dep->trb_pool[dep->trb_enqueue]; - - if (!req->trb) { - dwc3_gadget_move_started_request(req); - req->trb = trb; - req->trb_dma = dwc3_trb_dma_offset(dep, trb); - } - - req->num_trbs++; - - __dwc3_prepare_one_trb(dep, trb, dma, trb_length, chain, node, - stream_id, short_not_ok, no_interrupt, is_last, - must_interrupt); -} - static bool dwc3_needs_extra_trb(struct dwc3_ep *dep, struct dwc3_request *req) { unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); -- cgit v1.2.3 From 8affe37c525d800a2628c4ecfaed13b77dc5634a Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 4 Jul 2022 16:18:12 +0200 Subject: usb: dwc3: gadget: fix high speed multiplier setting For High-Speed Transfers the prepare_one_trb function is calculating the multiplier setting for the trb based on the length parameter of the trb currently prepared. This assumption is wrong. For trbs with a sg list, the length of the actual request has to be taken instead. Fixes: 40d829fb2ec6 ("usb: dwc3: gadget: Correct ISOC DATA PIDs for short packets") Cc: stable Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220704141812.1532306-3-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index dcd8fc209ccd..4366c45c28cf 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1265,10 +1265,10 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, unsigned int mult = 2; unsigned int maxp = usb_endpoint_maxp(ep->desc); - if (trb_length <= (2 * maxp)) + if (req->request.length <= (2 * maxp)) mult--; - if (trb_length <= maxp) + if (req->request.length <= maxp) mult--; trb->size |= DWC3_TRB_SIZE_PCM1(mult); -- cgit v1.2.3 From f41e16ac606216c7ad01c21e01a1004ca5650a8f Mon Sep 17 00:00:00 2001 From: Zhang Jiaming Date: Wed, 29 Jun 2022 14:41:53 +0800 Subject: USB: serial: io_edgeport: fix spelling mistakes Change 'paramater' to 'parameter'. Change 'timedout' to 'timeout'. Signed-off-by: Zhang Jiaming Signed-off-by: Johan Hovold --- drivers/usb/serial/io_edgeport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index bdee78cc4a07..ffa622539a25 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -220,7 +220,7 @@ struct edgeport_serial { __u8 rxHeader3; /* receive header byte 3 */ __u8 rxPort; /* the port that we are currently receiving data for */ __u8 rxStatusCode; /* the receive status code */ - __u8 rxStatusParam; /* the receive status paramater */ + __u8 rxStatusParam; /* the receive status parameter */ __s16 rxBytesRemaining; /* the number of port bytes left to read */ struct usb_serial *serial; /* loop back to the owner of this object */ }; @@ -901,7 +901,7 @@ static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) if (!edge_port->open) { /* open timed out */ - dev_dbg(dev, "%s - open timedout\n", __func__); + dev_dbg(dev, "%s - open timeout\n", __func__); edge_port->openPending = false; return -ENODEV; } -- cgit v1.2.3 From 9ec7e8d5fae34b3da52b4b0a7a47877bc6aa8416 Mon Sep 17 00:00:00 2001 From: Jiang Jian Date: Wed, 22 Jun 2022 18:42:17 +0800 Subject: USB: serial: fix repeated word "the" in comments There is a repeated word "the" in two comments that should be replaced or removed. Signed-off-by: Jiang Jian [ johan: replace one "the" with "that", merge the two cleanups, amend commit message ] Signed-off-by: Johan Hovold --- drivers/usb/serial/cypress_m8.c | 2 +- drivers/usb/serial/mos7720.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index 6924fa95f6bd..5fbcc155e8f5 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -256,7 +256,7 @@ static int analyze_baud_rate(struct usb_serial_port *port, speed_t new_rate) /* * Mike Isely 2-Feb-2008: The * Cypress app note that describes this mechanism - * states the the low-speed part can't handle more + * states that the low-speed part can't handle more * than 800 bytes/sec, in which case 4800 baud is the * safest speed for a part like that. */ diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 1e12b5f30dcc..23ccbba716c7 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -826,7 +826,7 @@ static int mos77xx_calc_num_ports(struct usb_serial *serial, /* * The 7715 uses the first bulk in/out endpoint pair for the * parallel port, and the second for the serial port. We swap - * the endpoint descriptors here so that the the first and + * the endpoint descriptors here so that the first and * only registered port structure uses the serial-port * endpoints. */ -- cgit v1.2.3 From 8097cf2fb3b2205257f1c76f4808e3398d66b6d9 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 8 Jul 2022 18:36:01 -0700 Subject: usb: gadget: udc: amd5536 depends on HAS_DMA USB_AMD5536UDC should depend on HAS_DMA since it selects USB_SNP_CORE, which depends on HAS_DMA and since 'select' does not follow any dependency chains. Fixes this kconfig warning: WARNING: unmet direct dependencies detected for USB_SNP_CORE Depends on [n]: USB_SUPPORT [=y] && USB_GADGET [=y] && (USB_AMD5536UDC [=y] || USB_SNP_UDC_PLAT [=n]) && HAS_DMA [=n] Selected by [y]: - USB_AMD5536UDC [=y] && USB_SUPPORT [=y] && USB_GADGET [=y] && USB_PCI [=y] Fixes: 97b3ffa233b9 ("usb: gadget: udc: amd5536: split core and PCI layer") Cc: Raviteja Garimella Cc: Felipe Balbi Cc: linux-usb@vger.kernel.org Cc: Greg Kroah-Hartman Signed-off-by: Randy Dunlap Link: https://lore.kernel.org/r/20220709013601.7536-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 03535f33511b..43130110a0b4 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -311,7 +311,7 @@ source "drivers/usb/gadget/udc/bdc/Kconfig" config USB_AMD5536UDC tristate "AMD5536 UDC" - depends on USB_PCI + depends on USB_PCI && HAS_DMA select USB_SNP_CORE help The AMD5536 UDC is part of the AMD Geode CS5536, an x86 southbridge. -- cgit v1.2.3 From 421c8d9a20da92deed2dac227e7ebdee7eb3e88f Mon Sep 17 00:00:00 2001 From: Maxim Devaev Date: Mon, 11 Jul 2022 13:29:57 +0300 Subject: usb: gadget: f_mass_storage: forced_eject attribute It allows to reset prevent_medium_removal flag and "eject" the image. This can be useful to free the drive from a hunging host or if the host continues to use the drive even after unmounting (Linux does this). It's also a bit like using an unfolded paperclip on an optical drive. Previously, the undocumented method of sending SIGUSR1 to a special "file-storage" kernel thread could be used for these purposes, but when using multiple storages there was no way to distinguish one from the other, so we had to send a signal to everyone. Reviewed-by: Alan Stern Signed-off-by: Maxim Devaev Link: https://lore.kernel.org/r/20220711102956.19642-1-mdevaev@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/configfs-usb-gadget-mass-storage | 6 ++++++ Documentation/usb/gadget-testing.rst | 6 ++++++ Documentation/usb/mass-storage.rst | 9 ++++++++ drivers/usb/gadget/function/f_mass_storage.c | 25 ++++++++++++++++++++++ drivers/usb/gadget/function/storage_common.c | 15 +++++++++++++ drivers/usb/gadget/function/storage_common.h | 2 ++ 6 files changed, 63 insertions(+) (limited to 'drivers') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-mass-storage b/Documentation/ABI/testing/configfs-usb-gadget-mass-storage index c86b63a7bb43..d899adb57e81 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-mass-storage +++ b/Documentation/ABI/testing/configfs-usb-gadget-mass-storage @@ -32,4 +32,10 @@ Description: being a CD-ROM. nofua Flag specifying that FUA flag in SCSI WRITE(10,12) + forced_eject This write-only file is useful only when + the function is active. It causes the backing + file to be forcibly detached from the LUN, + regardless of whether the host has allowed it. + Any non-zero number of bytes written will + result in ejection. =========== ============================================== diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 1c37159fa171..2278c9ffb74a 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -333,6 +333,12 @@ In each lun directory there are the following attribute files: being a CD-ROM. nofua Flag specifying that FUA flag in SCSI WRITE(10,12) + forced_eject This write-only file is useful only when + the function is active. It causes the backing + file to be forcibly detached from the LUN, + regardless of whether the host has allowed it. + Any non-zero number of bytes written will + result in ejection. =============== ============================================== Testing the MASS STORAGE function diff --git a/Documentation/usb/mass-storage.rst b/Documentation/usb/mass-storage.rst index d181b47c3cb6..f399ec631599 100644 --- a/Documentation/usb/mass-storage.rst +++ b/Documentation/usb/mass-storage.rst @@ -181,6 +181,15 @@ sysfs entries Reflects the state of nofua flag for given logical unit. It can be read and written. + - forced_eject + + When written into, it causes the backing file to be forcibly + detached from the LUN, regardless of whether the host has allowed + it. The content doesn't matter, any non-zero number of bytes + written will result in ejection. + + Can not be read. + Other then those, as usual, the values of module parameters can be read from /sys/module/g_mass_storage/parameters/* files. diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index e884f295504f..925e99f9775c 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -2651,10 +2651,21 @@ static ssize_t file_store(struct device *dev, struct device_attribute *attr, return fsg_store_file(curlun, filesem, buf, count); } +static ssize_t forced_eject_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + + return fsg_store_forced_eject(curlun, filesem, buf, count); +} + static DEVICE_ATTR_RW(nofua); /* mode wil be set in fsg_lun_attr_is_visible() */ static DEVICE_ATTR(ro, 0, ro_show, ro_store); static DEVICE_ATTR(file, 0, file_show, file_store); +static DEVICE_ATTR_WO(forced_eject); /****************************** FSG COMMON ******************************/ @@ -2808,6 +2819,7 @@ static struct attribute *fsg_lun_dev_attrs[] = { &dev_attr_ro.attr, &dev_attr_file.attr, &dev_attr_nofua.attr, + &dev_attr_forced_eject.attr, NULL }; @@ -3221,6 +3233,18 @@ static ssize_t fsg_lun_opts_inquiry_string_store(struct config_item *item, CONFIGFS_ATTR(fsg_lun_opts_, inquiry_string); +static ssize_t fsg_lun_opts_forced_eject_store(struct config_item *item, + const char *page, size_t len) +{ + struct fsg_lun_opts *opts = to_fsg_lun_opts(item); + struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent); + + return fsg_store_forced_eject(opts->lun, &fsg_opts->common->filesem, + page, len); +} + +CONFIGFS_ATTR_WO(fsg_lun_opts_, forced_eject); + static struct configfs_attribute *fsg_lun_attrs[] = { &fsg_lun_opts_attr_file, &fsg_lun_opts_attr_ro, @@ -3228,6 +3252,7 @@ static struct configfs_attribute *fsg_lun_attrs[] = { &fsg_lun_opts_attr_cdrom, &fsg_lun_opts_attr_nofua, &fsg_lun_opts_attr_inquiry_string, + &fsg_lun_opts_attr_forced_eject, NULL, }; diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c index b859a158a414..03035dbbe97b 100644 --- a/drivers/usb/gadget/function/storage_common.c +++ b/drivers/usb/gadget/function/storage_common.c @@ -519,4 +519,19 @@ ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf, } EXPORT_SYMBOL_GPL(fsg_store_inquiry_string); +ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count) +{ + int ret; + + /* + * Forcibly detach the backing file from the LUN + * regardless of whether the host has allowed it. + */ + curlun->prevent_medium_removal = 0; + ret = fsg_store_file(curlun, filesem, "", 0); + return ret < 0 ? ret : count; +} +EXPORT_SYMBOL_GPL(fsg_store_forced_eject); + MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h index bdeb1e233fc9..0a544a82cbf8 100644 --- a/drivers/usb/gadget/function/storage_common.h +++ b/drivers/usb/gadget/function/storage_common.h @@ -219,5 +219,7 @@ ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf, size_t count); ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf, size_t count); +ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count); #endif /* USB_STORAGE_COMMON_H */ -- cgit v1.2.3 From 2d937c64e8bf3d9b11b1d62d37fbe97b3cd5dc8d Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Mon, 11 Jul 2022 09:25:05 -0700 Subject: usb: misc: onboard_hub: Fix 'missing prototype' warning When building with 'W=1' the compiler complains about missing prototypes for onboard_hub_create/destroy_pdevs(). Include the header with the prototypes to fix this. Reported-by: kernel test robot Reviewed-by: Douglas Anderson Signed-off-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20220711092431.1.I4016c759fd7fe2b32dd482994a20661f36e2cae3@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub_pdevs.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/misc/onboard_usb_hub_pdevs.c b/drivers/usb/misc/onboard_usb_hub_pdevs.c index a0a5f719129f..ed22a18f4ab7 100644 --- a/drivers/usb/misc/onboard_usb_hub_pdevs.c +++ b/drivers/usb/misc/onboard_usb_hub_pdevs.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "onboard_usb_hub.h" -- cgit v1.2.3 From 5e76ee96be8f7bbf9416a5edddc8c064e7e7c6ac Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Mon, 11 Jul 2022 18:44:03 -0700 Subject: usb: dwc3: ep0: Properly handle setup_packet_pending scenario in data stage During a 3 stage SETUP transfer, if the host sends another SETUP token before completing the status phase, it signifies that the host has aborted the current control transfer. Currently, if a setup_packet_pending is received, there are no subsequent calls to dwc3_ep0_out_start() to fetch the new SETUP packet. This leads to a stall on EP0, as host does not expect another STATUS phase as it has aborted the current transfer. Fix this issue by explicitly stalling and restarting EP0, as well as resetting the trb_enqueue indexes. (without this, there is a chance the SETUP TRB is set up on trb_endqueue == 1) Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/20220712014403.2977-1-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/ep0.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 2a510e84eef4..197af63f8d05 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -239,6 +239,8 @@ void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) dwc3_gadget_giveback(dep, req, -ECONNRESET); } + dwc->eps[0]->trb_enqueue = 0; + dwc->eps[1]->trb_enqueue = 0; dwc->ep0state = EP0_SETUP_PHASE; dwc3_ep0_out_start(dwc); } @@ -1140,6 +1142,11 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc, if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS) return; + if (dwc->setup_packet_pending) { + dwc3_ep0_stall_and_restart(dwc); + return; + } + dwc->ep0state = EP0_STATUS_PHASE; if (dwc->delayed_status) { -- cgit v1.2.3 From efa2bebf2a9fde638644d6fb0fb776345ac6fcc1 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Wed, 13 Jul 2022 15:02:05 +0800 Subject: usb: ldusb: replace ternary operator with max_t() Fix the following coccicheck warning: drivers/usb/misc/ldusb.c:719: WARNING opportunity for max(). drivers/usb/misc/ldusb.c:721: WARNING opportunity for max(). max_t() macro is defined in include/linux/minmax.h. It avoids multiple evaluations of the arguments when non-constant and performs strict type-checking. Signed-off-by: Jiangshan Yi Link: https://lore.kernel.org/r/20220713070205.3047256-1-13667453960@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/ldusb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c index dcc88df72df4..7cbef74dfc9a 100644 --- a/drivers/usb/misc/ldusb.c +++ b/drivers/usb/misc/ldusb.c @@ -716,9 +716,11 @@ static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id * dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->interrupt_out_urb) goto error; - dev->interrupt_in_interval = min_interrupt_in_interval > dev->interrupt_in_endpoint->bInterval ? min_interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; + dev->interrupt_in_interval = max_t(int, min_interrupt_in_interval, + dev->interrupt_in_endpoint->bInterval); if (dev->interrupt_out_endpoint) - dev->interrupt_out_interval = min_interrupt_out_interval > dev->interrupt_out_endpoint->bInterval ? min_interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + dev->interrupt_out_interval = max_t(int, min_interrupt_out_interval, + dev->interrupt_out_endpoint->bInterval); /* we can register the device now, as it is ready */ usb_set_intfdata(intf, dev); -- cgit v1.2.3 From 220fafb4ed04187e9c17be4152da5a7f2ffbdd8c Mon Sep 17 00:00:00 2001 From: Liang He Date: Wed, 13 Jul 2022 20:05:28 +0800 Subject: usb: aspeed-vhub: Fix refcount leak bug in ast_vhub_init_desc() We should call of_node_put() for the reference returned by of_get_child_by_name() which has increased the refcount. Fixes: 30d2617fd7ed ("usb: gadget: aspeed: allow to set usb strings in device tree") Signed-off-by: Liang He Link: https://lore.kernel.org/r/20220713120528.368168-1-windhl@126.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed-vhub/hub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 65cd4e46f031..e2207d014620 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -1059,8 +1059,10 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) /* Initialize vhub String Descriptors. */ INIT_LIST_HEAD(&vhub->vhub_str_desc); desc_np = of_get_child_by_name(vhub_np, "vhub-strings"); - if (desc_np) + if (desc_np) { ret = ast_vhub_of_parse_str_desc(vhub, desc_np); + of_node_put(desc_np); + } else ret = ast_vhub_str_alloc_add(vhub, &ast_vhub_strings); -- cgit v1.2.3 From 07903626d98853e605fe63e5ce149f1b7314bbea Mon Sep 17 00:00:00 2001 From: Rohith Kollalsi Date: Thu, 14 Jul 2022 10:26:25 +0530 Subject: usb: dwc3: core: Do not perform GCTL_CORE_SOFTRESET during bootup According to the programming guide, it is recommended to perform a GCTL_CORE_SOFTRESET only when switching the mode from device to host or host to device. However, it is found that during bootup when __dwc3_set_mode() is called for the first time, GCTL_CORESOFTRESET is done with suspendable bit(BIT 17) of DWC3_GUSB3PIPECTL set. This some times leads to issues like controller going into bad state and controller registers reading value zero. Until GCTL_CORESOFTRESET is done and run/stop bit is set core initialization is not complete. Setting suspendable bit of DWC3_GUSB3PIPECTL and then performing GCTL_CORESOFTRESET is therefore not recommended. Avoid this by only performing the reset if current_dr_role is set, that is, when doing subsequent role switching. Fixes: f88359e1588b ("usb: dwc3: core: Do core softreset when switch mode") Signed-off-by: Rohith Kollalsi Link: https://lore.kernel.org/r/20220714045625.20377-1-quic_rkollals@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 050b2ba5986d..c5c238ab3083 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -158,9 +158,13 @@ static void __dwc3_set_mode(struct work_struct *work) break; } - /* For DRD host or device mode only */ - if ((DWC3_IP_IS(DWC3) || DWC3_VER_IS_PRIOR(DWC31, 190A)) && - dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) { + /* + * When current_dr_role is not set, there's no role switching. + * Only perform GCTL.CoreSoftReset when there's DRD role switching. + */ + if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) || + DWC3_VER_IS_PRIOR(DWC31, 190A)) && + dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) { reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg |= DWC3_GCTL_CORESOFTRESET; dwc3_writel(dwc->regs, DWC3_GCTL, reg); -- cgit v1.2.3 From fe6d8a9c8e6456f8e7ba6b4ee528460beaf65a71 Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Thu, 14 Jul 2022 16:13:50 +0800 Subject: usb: typec: anx7411: Add Analogix PD ANX7411 support Add driver for analogix ANX7411 USB Type-C DRP port controller. Acked-by: Heikki Krogerus Signed-off-by: Xin Ji Link: https://lore.kernel.org/r/20220714081350.36447-2-xji@analogixsemi.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Kconfig | 11 + drivers/usb/typec/Makefile | 1 + drivers/usb/typec/anx7411.c | 1596 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1608 insertions(+) create mode 100644 drivers/usb/typec/anx7411.c (limited to 'drivers') diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index ba24847fb245..5defdfead653 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -52,6 +52,17 @@ source "drivers/usb/typec/ucsi/Kconfig" source "drivers/usb/typec/tipd/Kconfig" +config TYPEC_ANX7411 + tristate "Analogix ANX7411 Type-C DRP Port controller driver" + depends on I2C + depends on USB_ROLE_SWITCH + help + Say Y or M here if your system has Analogix ANX7411 Type-C DRP Port + controller driver. + + If you choose to build this driver as a dynamically linked module, the + module will be called anx7411.ko. + config TYPEC_RT1719 tristate "Richtek RT1719 Sink Only Type-C controller driver" depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 2f174cd3e5df..dac5c11a3234 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_TYPEC) += altmodes/ obj-$(CONFIG_TYPEC_TCPM) += tcpm/ obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) += tipd/ +obj-$(CONFIG_TYPEC_ANX7411) += anx7411.o obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c new file mode 100644 index 000000000000..b990376991f8 --- /dev/null +++ b/drivers/usb/typec/anx7411.c @@ -0,0 +1,1596 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Driver for Analogix ANX7411 USB Type-C and PD controller + * + * Copyright(c) 2022, Analogix Semiconductor. All rights reserved. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TCPC_ADDRESS1 0x58 +#define TCPC_ADDRESS2 0x56 +#define TCPC_ADDRESS3 0x54 +#define TCPC_ADDRESS4 0x52 +#define SPI_ADDRESS1 0x7e +#define SPI_ADDRESS2 0x6e +#define SPI_ADDRESS3 0x64 +#define SPI_ADDRESS4 0x62 + +struct anx7411_i2c_select { + u8 tcpc_address; + u8 spi_address; +}; + +#define VID_ANALOGIX 0x1F29 +#define PID_ANALOGIX 0x7411 + +/* TCPC register define */ + +#define ANALOG_CTRL_10 0xAA + +#define STATUS_LEN 2 +#define ALERT_0 0xCB +#define RECEIVED_MSG BIT(7) +#define SOFTWARE_INT BIT(6) +#define MSG_LEN 32 +#define HEADER_LEN 2 +#define MSG_HEADER 0x00 +#define MSG_TYPE 0x01 +#define MSG_RAWDATA 0x02 +#define MSG_LEN_MASK 0x1F + +#define ALERT_1 0xCC +#define INTP_POW_ON BIT(7) +#define INTP_POW_OFF BIT(6) + +#define VBUS_THRESHOLD_H 0xDD +#define VBUS_THRESHOLD_L 0xDE + +#define FW_CTRL_0 0xF0 +#define UNSTRUCT_VDM_EN BIT(0) +#define DELAY_200MS BIT(1) +#define VSAFE0 0 +#define VSAFE1 BIT(2) +#define VSAFE2 BIT(3) +#define VSAFE3 (BIT(2) | BIT(3)) +#define FRS_EN BIT(7) + +#define FW_PARAM 0xF1 +#define DONGLE_IOP BIT(0) + +#define FW_CTRL_2 0xF7 +#define SINK_CTRL_DIS_FLAG BIT(5) + +/* SPI register define */ +#define OCM_CTRL_0 0x6E +#define OCM_RESET BIT(6) + +#define MAX_VOLTAGE 0xAC +#define MAX_POWER 0xAD +#define MIN_POWER 0xAE + +#define REQUEST_VOLTAGE 0xAF +#define VOLTAGE_UNIT 100 /* mV per unit */ + +#define REQUEST_CURRENT 0xB1 +#define CURRENT_UNIT 50 /* mA per unit */ + +#define CMD_SEND_BUF 0xC0 +#define CMD_RECV_BUF 0xE0 + +#define REQ_VOL_20V_IN_100MV 0xC8 +#define REQ_CUR_2_25A_IN_50MA 0x2D +#define REQ_CUR_3_25A_IN_50MA 0x41 + +#define DEF_5V 5000 +#define DEF_1_5A 1500 + +#define LOBYTE(w) ((u8)((w) & 0xFF)) +#define HIBYTE(w) ((u8)(((u16)(w) >> 8) & 0xFF)) + +enum anx7411_typec_message_type { + TYPE_SRC_CAP = 0x00, + TYPE_SNK_CAP = 0x01, + TYPE_SNK_IDENTITY = 0x02, + TYPE_SVID = 0x03, + TYPE_SET_SNK_DP_CAP = 0x08, + TYPE_PSWAP_REQ = 0x10, + TYPE_DSWAP_REQ = 0x11, + TYPE_VDM = 0x14, + TYPE_OBJ_REQ = 0x16, + TYPE_DP_ALT_ENTER = 0x19, + TYPE_DP_DISCOVER_MODES_INFO = 0x27, + TYPE_GET_DP_CONFIG = 0x29, + TYPE_DP_CONFIGURE = 0x2A, + TYPE_GET_DP_DISCOVER_MODES_INFO = 0x2E, + TYPE_GET_DP_ALT_ENTER = 0x2F, +}; + +#define FW_CTRL_1 0xB2 +#define AUTO_PD_EN BIT(1) +#define TRYSRC_EN BIT(2) +#define TRYSNK_EN BIT(3) +#define FORCE_SEND_RDO BIT(6) + +#define FW_VER 0xB4 +#define FW_SUBVER 0xB5 + +#define INT_MASK 0xB6 +#define INT_STS 0xB7 +#define OCM_BOOT_UP BIT(0) +#define OC_OV_EVENT BIT(1) +#define VCONN_CHANGE BIT(2) +#define VBUS_CHANGE BIT(3) +#define CC_STATUS_CHANGE BIT(4) +#define DATA_ROLE_CHANGE BIT(5) +#define PR_CONSUMER_GOT_POWER BIT(6) +#define HPD_STATUS_CHANGE BIT(7) + +#define SYSTEM_STSTUS 0xB8 +/* 0: SINK off; 1: SINK on */ +#define SINK_STATUS BIT(1) +/* 0: VCONN off; 1: VCONN on*/ +#define VCONN_STATUS BIT(2) +/* 0: vbus off; 1: vbus on*/ +#define VBUS_STATUS BIT(3) +/* 1: host; 0:device*/ +#define DATA_ROLE BIT(5) +/* 0: Chunking; 1: Unchunked*/ +#define SUPPORT_UNCHUNKING BIT(6) +/* 0: HPD low; 1: HPD high*/ +#define HPD_STATUS BIT(7) + +#define DATA_DFP 1 +#define DATA_UFP 2 +#define POWER_SOURCE 1 +#define POWER_SINK 2 + +#define CC_STATUS 0xB9 +#define CC1_RD BIT(0) +#define CC2_RD BIT(4) +#define CC1_RA BIT(1) +#define CC2_RA BIT(5) +#define CC1_RD BIT(0) +#define CC1_RP(cc) (((cc) >> 2) & 0x03) +#define CC2_RP(cc) (((cc) >> 6) & 0x03) + +#define PD_REV_INIT 0xBA + +#define PD_EXT_MSG_CTRL 0xBB +#define SRC_CAP_EXT_REPLY BIT(0) +#define MANUFACTURER_INFO_REPLY BIT(1) +#define BATTERY_STS_REPLY BIT(2) +#define BATTERY_CAP_REPLY BIT(3) +#define ALERT_REPLY BIT(4) +#define STATUS_REPLY BIT(5) +#define PPS_STATUS_REPLY BIT(6) +#define SNK_CAP_EXT_REPLY BIT(7) + +#define NO_CONNECT 0x00 +#define USB3_1_CONNECTED 0x01 +#define DP_ALT_4LANES 0x02 +#define USB3_1_DP_2LANES 0x03 +#define CC1_CONNECTED 0x01 +#define CC2_CONNECTED 0x02 +#define SELECT_PIN_ASSIGMENT_C 0x04 +#define SELECT_PIN_ASSIGMENT_D 0x08 +#define SELECT_PIN_ASSIGMENT_E 0x10 +#define SELECT_PIN_ASSIGMENT_U 0x00 +#define REDRIVER_ADDRESS 0x20 +#define REDRIVER_OFFSET 0x00 + +#define DP_SVID 0xFF01 +#define VDM_ACK 0x40 +#define VDM_CMD_RES 0x00 +#define VDM_CMD_DIS_ID 0x01 +#define VDM_CMD_DIS_SVID 0x02 +#define VDM_CMD_DIS_MOD 0x03 +#define VDM_CMD_ENTER_MODE 0x04 +#define VDM_CMD_EXIT_MODE 0x05 +#define VDM_CMD_ATTENTION 0x06 +#define VDM_CMD_GET_STS 0x10 +#define VDM_CMD_AND_ACK_MASK 0x5F + +#define MAX_ALTMODE 2 + +#define HAS_SOURCE_CAP BIT(0) +#define HAS_SINK_CAP BIT(1) +#define HAS_SINK_WATT BIT(2) + +enum anx7411_psy_state { + /* copy from drivers/usb/typec/tcpm */ + ANX7411_PSY_OFFLINE = 0, + ANX7411_PSY_FIXED_ONLINE, + + /* private */ + /* PD keep in, but disconnct power to bq25700, + * this state can be active when higher capacity adapter plug in, + * and change to ONLINE state when higher capacity adapter plug out + */ + ANX7411_PSY_HANG = 0xff, +}; + +struct typec_params { + int request_current; /* ma */ + int request_voltage; /* mv */ + int cc_connect; + int cc_orientation_valid; + int cc_status; + int data_role; + int power_role; + int vconn_role; + int dp_altmode_enter; + int cust_altmode_enter; + struct usb_role_switch *role_sw; + struct typec_port *port; + struct typec_partner *partner; + struct typec_mux_dev *typec_mux; + struct typec_switch_dev *typec_switch; + struct typec_altmode *amode[MAX_ALTMODE]; + struct typec_altmode *port_amode[MAX_ALTMODE]; + struct typec_displayport_data data; + int pin_assignment; + struct typec_capability caps; + u32 src_pdo[PDO_MAX_OBJECTS]; + u32 sink_pdo[PDO_MAX_OBJECTS]; + u8 caps_flags; + u8 src_pdo_nr; + u8 sink_pdo_nr; + u8 sink_watt; + u8 sink_voltage; +}; + +#define MAX_BUF_LEN 30 +struct fw_msg { + u8 msg_len; + u8 msg_type; + u8 buf[MAX_BUF_LEN]; +} __packed; + +struct anx7411_data { + int fw_version; + int fw_subversion; + struct i2c_client *tcpc_client; + struct i2c_client *spi_client; + struct fw_msg send_msg; + struct fw_msg recv_msg; + struct gpio_desc *intp_gpiod; + struct fwnode_handle *connector_fwnode; + struct typec_params typec; + int intp_irq; + struct work_struct work; + struct workqueue_struct *workqueue; + /* Lock for interrupt work queue */ + struct mutex lock; + + enum anx7411_psy_state psy_online; + enum power_supply_usb_type usb_type; + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct device *dev; +}; + +static u8 snk_identity[] = { + LOBYTE(VID_ANALOGIX), HIBYTE(VID_ANALOGIX), 0x00, 0x82, /* snk_id_hdr */ + 0x00, 0x00, 0x00, 0x00, /* snk_cert */ + 0x00, 0x00, LOBYTE(PID_ANALOGIX), HIBYTE(PID_ANALOGIX), /* 5snk_ama */ +}; + +static u8 dp_caps[4] = {0xC6, 0x00, 0x00, 0x00}; + +static int anx7411_reg_read(struct i2c_client *client, + u8 reg_addr) +{ + return i2c_smbus_read_byte_data(client, reg_addr); +} + +static int anx7411_reg_block_read(struct i2c_client *client, + u8 reg_addr, u8 len, u8 *buf) +{ + return i2c_smbus_read_i2c_block_data(client, reg_addr, len, buf); +} + +static int anx7411_reg_write(struct i2c_client *client, + u8 reg_addr, u8 reg_val) +{ + return i2c_smbus_write_byte_data(client, reg_addr, reg_val); +} + +static int anx7411_reg_block_write(struct i2c_client *client, + u8 reg_addr, u8 len, u8 *buf) +{ + return i2c_smbus_write_i2c_block_data(client, reg_addr, len, buf); +} + +static struct anx7411_i2c_select anx7411_i2c_addr[] = { + {TCPC_ADDRESS1, SPI_ADDRESS1}, + {TCPC_ADDRESS2, SPI_ADDRESS2}, + {TCPC_ADDRESS3, SPI_ADDRESS3}, + {TCPC_ADDRESS4, SPI_ADDRESS4}, +}; + +static int anx7411_detect_power_mode(struct anx7411_data *ctx) +{ + int ret; + int mode; + + ret = anx7411_reg_read(ctx->spi_client, REQUEST_CURRENT); + if (ret < 0) + return ret; + + ctx->typec.request_current = ret * CURRENT_UNIT; /* 50ma per unit */ + + ret = anx7411_reg_read(ctx->spi_client, REQUEST_VOLTAGE); + if (ret < 0) + return ret; + + ctx->typec.request_voltage = ret * VOLTAGE_UNIT; /* 100mv per unit */ + + if (ctx->psy_online == ANX7411_PSY_OFFLINE) { + ctx->psy_online = ANX7411_PSY_FIXED_ONLINE; + ctx->usb_type = POWER_SUPPLY_USB_TYPE_PD; + power_supply_changed(ctx->psy); + } + + if (!ctx->typec.cc_orientation_valid) + return 0; + + if (ctx->typec.cc_connect == CC1_CONNECTED) + mode = CC1_RP(ctx->typec.cc_status); + else + mode = CC2_RP(ctx->typec.cc_status); + if (mode) { + typec_set_pwr_opmode(ctx->typec.port, mode - 1); + return 0; + } + + typec_set_pwr_opmode(ctx->typec.port, TYPEC_PWR_MODE_PD); + + return 0; +} + +static int anx7411_register_partner(struct anx7411_data *ctx, + int pd, int accessory) +{ + struct typec_partner_desc desc; + + if (ctx->typec.partner) + return 0; + + desc.usb_pd = pd; + desc.accessory = accessory; + desc.identity = NULL; + ctx->typec.partner = typec_register_partner(ctx->typec.port, &desc); + if (IS_ERR(ctx->typec.partner)) { + ctx->typec.partner = NULL; + return PTR_ERR(ctx->typec.partner); + } + + return 0; +} + +static int anx7411_detect_cc_orientation(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->spi_client->dev; + int ret; + int cc1_rd, cc2_rd; + int cc1_ra, cc2_ra; + int cc1_rp, cc2_rp; + + ret = anx7411_reg_read(ctx->spi_client, CC_STATUS); + if (ret < 0) + return ret; + + ctx->typec.cc_status = ret; + + cc1_rd = ret & CC1_RD ? 1 : 0; + cc2_rd = ret & CC2_RD ? 1 : 0; + cc1_ra = ret & CC1_RA ? 1 : 0; + cc2_ra = ret & CC2_RA ? 1 : 0; + cc1_rp = CC1_RP(ret); + cc2_rp = CC2_RP(ret); + + /* Debug cable, nothing to do */ + if (cc1_rd && cc2_rd) { + ctx->typec.cc_orientation_valid = 0; + return anx7411_register_partner(ctx, 0, TYPEC_ACCESSORY_DEBUG); + } + + if (cc1_ra && cc2_ra) { + ctx->typec.cc_orientation_valid = 0; + return anx7411_register_partner(ctx, 0, TYPEC_ACCESSORY_AUDIO); + } + + ctx->typec.cc_orientation_valid = 1; + + ret = anx7411_register_partner(ctx, 1, TYPEC_ACCESSORY_NONE); + if (ret) { + dev_err(dev, "register partner\n"); + return ret; + } + + if (cc1_rd || cc1_rp) { + typec_set_orientation(ctx->typec.port, TYPEC_ORIENTATION_NORMAL); + ctx->typec.cc_connect = CC1_CONNECTED; + } + + if (cc2_rd || cc2_rp) { + typec_set_orientation(ctx->typec.port, TYPEC_ORIENTATION_REVERSE); + ctx->typec.cc_connect = CC2_CONNECTED; + } + + return 0; +} + +static int anx7411_set_mux(struct anx7411_data *ctx, int pin_assignment) +{ + int mode = TYPEC_STATE_SAFE; + + switch (pin_assignment) { + case SELECT_PIN_ASSIGMENT_U: + /* default 4 line USB 3.1 */ + mode = TYPEC_STATE_MODAL; + break; + case SELECT_PIN_ASSIGMENT_C: + case SELECT_PIN_ASSIGMENT_E: + /* 4 line DP */ + mode = TYPEC_STATE_SAFE; + break; + case SELECT_PIN_ASSIGMENT_D: + /* 2 line DP, 2 line USB */ + mode = TYPEC_MODE_USB3; + break; + default: + mode = TYPEC_STATE_SAFE; + break; + } + + ctx->typec.pin_assignment = pin_assignment; + + return typec_set_mode(ctx->typec.port, mode); +} + +static int anx7411_set_usb_role(struct anx7411_data *ctx, enum usb_role role) +{ + if (!ctx->typec.role_sw) + return 0; + + return usb_role_switch_set_role(ctx->typec.role_sw, role); +} + +static int anx7411_data_role_detect(struct anx7411_data *ctx) +{ + int ret; + + ret = anx7411_reg_read(ctx->spi_client, SYSTEM_STSTUS); + if (ret < 0) + return ret; + + ctx->typec.data_role = (ret & DATA_ROLE) ? TYPEC_HOST : TYPEC_DEVICE; + ctx->typec.vconn_role = (ret & VCONN_STATUS) ? TYPEC_SOURCE : TYPEC_SINK; + + typec_set_data_role(ctx->typec.port, ctx->typec.data_role); + + typec_set_vconn_role(ctx->typec.port, ctx->typec.vconn_role); + + if (ctx->typec.data_role == TYPEC_HOST) + return anx7411_set_usb_role(ctx, USB_ROLE_HOST); + + return anx7411_set_usb_role(ctx, USB_ROLE_DEVICE); +} + +static int anx7411_power_role_detect(struct anx7411_data *ctx) +{ + int ret; + + ret = anx7411_reg_read(ctx->spi_client, SYSTEM_STSTUS); + if (ret < 0) + return ret; + + ctx->typec.power_role = (ret & SINK_STATUS) ? TYPEC_SINK : TYPEC_SOURCE; + + if (ctx->typec.power_role == TYPEC_SOURCE) { + ctx->typec.request_current = DEF_1_5A; + ctx->typec.request_voltage = DEF_5V; + } + + typec_set_pwr_role(ctx->typec.port, ctx->typec.power_role); + + return 0; +} + +static int anx7411_cc_status_detect(struct anx7411_data *ctx) +{ + anx7411_detect_cc_orientation(ctx); + anx7411_detect_power_mode(ctx); + + return 0; +} + +static void anx7411_partner_unregister_altmode(struct anx7411_data *ctx) +{ + int i; + + ctx->typec.dp_altmode_enter = 0; + ctx->typec.cust_altmode_enter = 0; + + for (i = 0; i < MAX_ALTMODE; i++) + if (ctx->typec.amode[i]) { + typec_unregister_altmode(ctx->typec.amode[i]); + ctx->typec.amode[i] = NULL; + } + + ctx->typec.pin_assignment = 0; +} + +static int anx7411_typec_register_altmode(struct anx7411_data *ctx, + int svid, int vdo) +{ + struct device *dev = &ctx->spi_client->dev; + struct typec_altmode_desc desc; + int i; + + desc.svid = svid; + desc.vdo = vdo; + + for (i = 0; i < MAX_ALTMODE; i++) + if (!ctx->typec.amode[i]) + break; + + desc.mode = i + 1; /* start with 1 */ + + if (i >= MAX_ALTMODE) { + dev_err(dev, "no altmode space for registering\n"); + return -ENOMEM; + } + + ctx->typec.amode[i] = typec_partner_register_altmode(ctx->typec.partner, + &desc); + if (IS_ERR(ctx->typec.amode[i])) { + dev_err(dev, "failed to register altmode\n"); + ctx->typec.amode[i] = NULL; + return PTR_ERR(ctx->typec.amode); + } + + return 0; +} + +static void anx7411_unregister_partner(struct anx7411_data *ctx) +{ + if (ctx->typec.partner) { + typec_unregister_partner(ctx->typec.partner); + ctx->typec.partner = NULL; + } +} + +static int anx7411_update_altmode(struct anx7411_data *ctx, int svid) +{ + int i; + + if (svid == DP_SVID) + ctx->typec.dp_altmode_enter = 1; + else + ctx->typec.cust_altmode_enter = 1; + + for (i = 0; i < MAX_ALTMODE; i++) { + if (!ctx->typec.amode[i]) + continue; + + if (ctx->typec.amode[i]->svid == svid) { + typec_altmode_update_active(ctx->typec.amode[i], true); + typec_altmode_notify(ctx->typec.amode[i], + ctx->typec.pin_assignment, + &ctx->typec.data); + break; + } + } + + return 0; +} + +static int anx7411_register_altmode(struct anx7411_data *ctx, + bool dp_altmode, u8 *buf) +{ + int ret; + int svid; + int mid; + + if (!ctx->typec.partner) + return 0; + + svid = DP_SVID; + if (dp_altmode) { + mid = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + return anx7411_typec_register_altmode(ctx, svid, mid); + } + + svid = (buf[3] << 8) | buf[2]; + if ((buf[0] & VDM_CMD_AND_ACK_MASK) != (VDM_ACK | VDM_CMD_ENTER_MODE)) + return anx7411_update_altmode(ctx, svid); + + if ((buf[0] & VDM_CMD_AND_ACK_MASK) != (VDM_ACK | VDM_CMD_DIS_MOD)) + return 0; + + mid = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); + + ret = anx7411_typec_register_altmode(ctx, svid, mid); + if (ctx->typec.cust_altmode_enter) + ret |= anx7411_update_altmode(ctx, svid); + + return ret; +} + +static int anx7411_parse_cmd(struct anx7411_data *ctx, u8 type, u8 *buf, u8 len) +{ + struct device *dev = &ctx->spi_client->dev; + u8 cur_50ma, vol_100mv; + + switch (type) { + case TYPE_SRC_CAP: + cur_50ma = anx7411_reg_read(ctx->spi_client, REQUEST_CURRENT); + vol_100mv = anx7411_reg_read(ctx->spi_client, REQUEST_VOLTAGE); + + ctx->typec.request_voltage = vol_100mv * VOLTAGE_UNIT; + ctx->typec.request_current = cur_50ma * CURRENT_UNIT; + + ctx->psy_online = ANX7411_PSY_FIXED_ONLINE; + ctx->usb_type = POWER_SUPPLY_USB_TYPE_PD; + power_supply_changed(ctx->psy); + break; + case TYPE_SNK_CAP: + break; + case TYPE_SVID: + break; + case TYPE_SNK_IDENTITY: + break; + case TYPE_GET_DP_ALT_ENTER: + /* DP alt mode enter success */ + if (buf[0]) + anx7411_update_altmode(ctx, DP_SVID); + break; + case TYPE_DP_ALT_ENTER: + /* Update DP altmode */ + anx7411_update_altmode(ctx, DP_SVID); + break; + case TYPE_OBJ_REQ: + anx7411_detect_power_mode(ctx); + break; + case TYPE_DP_CONFIGURE: + anx7411_set_mux(ctx, buf[1]); + break; + case TYPE_DP_DISCOVER_MODES_INFO: + /* Make sure discover modes valid */ + if (buf[0] | buf[1]) + /* Register DP Altmode */ + anx7411_register_altmode(ctx, 1, buf); + break; + case TYPE_VDM: + /* Register other altmode */ + anx7411_register_altmode(ctx, 0, buf); + break; + default: + dev_err(dev, "ignore message(0x%.02x).\n", type); + break; + } + + return 0; +} + +static u8 checksum(struct device *dev, u8 *buf, u8 len) +{ + u8 ret = 0; + u8 i; + + for (i = 0; i < len; i++) + ret += buf[i]; + + return ret; +} + +static int anx7411_read_msg_ctrl_status(struct i2c_client *client) +{ + return anx7411_reg_read(client, CMD_SEND_BUF); +} + +static int anx7411_wait_msg_empty(struct i2c_client *client) +{ + int val; + + return readx_poll_timeout(anx7411_read_msg_ctrl_status, + client, val, (val < 0) || (val == 0), + 2000, 2000 * 150); +} + +static int anx7411_send_msg(struct anx7411_data *ctx, u8 type, u8 *buf, u8 size) +{ + struct device *dev = &ctx->spi_client->dev; + struct fw_msg *msg = &ctx->send_msg; + u8 crc; + int ret; + + size = min_t(u8, size, (u8)MAX_BUF_LEN); + memcpy(msg->buf, buf, size); + msg->msg_type = type; + /* msg len equals buffer length + msg_type */ + msg->msg_len = size + 1; + + /* Do CRC check for all buffer data and msg_len and msg_type */ + crc = checksum(dev, (u8 *)msg, size + HEADER_LEN); + msg->buf[size] = 0 - crc; + + ret = anx7411_wait_msg_empty(ctx->spi_client); + if (ret) + return ret; + + ret = anx7411_reg_block_write(ctx->spi_client, + CMD_SEND_BUF + 1, size + HEADER_LEN, + &msg->msg_type); + ret |= anx7411_reg_write(ctx->spi_client, CMD_SEND_BUF, + msg->msg_len); + return ret; +} + +static int anx7411_process_cmd(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->spi_client->dev; + struct fw_msg *msg = &ctx->recv_msg; + u8 len; + u8 crc; + int ret; + + /* Read message from firmware */ + ret = anx7411_reg_block_read(ctx->spi_client, CMD_RECV_BUF, + MSG_LEN, (u8 *)msg); + if (ret < 0) + return 0; + + if (!msg->msg_len) + return 0; + + ret = anx7411_reg_write(ctx->spi_client, CMD_RECV_BUF, 0); + if (ret) + return ret; + + len = msg->msg_len & MSG_LEN_MASK; + crc = checksum(dev, (u8 *)msg, len + HEADER_LEN); + if (crc) { + dev_err(dev, "message error crc(0x%.02x)\n", crc); + return -ERANGE; + } + + return anx7411_parse_cmd(ctx, msg->msg_type, msg->buf, len - 1); +} + +static void anx7411_translate_payload(struct device *dev, __le32 *payload, + u32 *pdo, int nr, const char *type) +{ + int i; + + if (nr > PDO_MAX_OBJECTS) { + dev_err(dev, "nr(%d) exceed PDO_MAX_OBJECTS(%d)\n", + nr, PDO_MAX_OBJECTS); + + return; + } + + for (i = 0; i < nr; i++) + payload[i] = cpu_to_le32(pdo[i]); +} + +static int anx7411_config(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->spi_client->dev; + struct typec_params *typecp = &ctx->typec; + __le32 payload[PDO_MAX_OBJECTS]; + int ret; + + /* Config PD FW work under PD 2.0 */ + ret = anx7411_reg_write(ctx->spi_client, PD_REV_INIT, PD_REV20); + ret |= anx7411_reg_write(ctx->tcpc_client, FW_CTRL_0, + UNSTRUCT_VDM_EN | DELAY_200MS | + VSAFE1 | FRS_EN); + ret |= anx7411_reg_write(ctx->spi_client, FW_CTRL_1, + AUTO_PD_EN | FORCE_SEND_RDO); + + /* Set VBUS current threshold */ + ret |= anx7411_reg_write(ctx->tcpc_client, VBUS_THRESHOLD_H, 0xff); + ret |= anx7411_reg_write(ctx->tcpc_client, VBUS_THRESHOLD_L, 0x03); + + /* Fix dongle compatible issue */ + ret |= anx7411_reg_write(ctx->tcpc_client, FW_PARAM, + anx7411_reg_read(ctx->tcpc_client, FW_PARAM) | + DONGLE_IOP); + ret |= anx7411_reg_write(ctx->spi_client, INT_MASK, 0); + + ret |= anx7411_reg_write(ctx->spi_client, PD_EXT_MSG_CTRL, 0xFF); + if (ret) + return ret; + + if (typecp->caps_flags & HAS_SOURCE_CAP) { + anx7411_translate_payload(dev, payload, typecp->src_pdo, + typecp->src_pdo_nr, "source"); + anx7411_send_msg(ctx, TYPE_SRC_CAP, (u8 *)&payload, + typecp->src_pdo_nr * 4); + anx7411_send_msg(ctx, TYPE_SNK_IDENTITY, snk_identity, + sizeof(snk_identity)); + anx7411_send_msg(ctx, TYPE_SET_SNK_DP_CAP, dp_caps, + sizeof(dp_caps)); + } + + if (typecp->caps_flags & HAS_SINK_CAP) { + anx7411_translate_payload(dev, payload, typecp->sink_pdo, + typecp->sink_pdo_nr, "sink"); + anx7411_send_msg(ctx, TYPE_SNK_CAP, (u8 *)&payload, + typecp->sink_pdo_nr * 4); + } + + if (typecp->caps_flags & HAS_SINK_WATT) { + if (typecp->sink_watt) { + ret |= anx7411_reg_write(ctx->spi_client, MAX_POWER, + typecp->sink_watt); + /* Set min power to 1W */ + ret |= anx7411_reg_write(ctx->spi_client, MIN_POWER, 2); + } + + if (typecp->sink_voltage) + ret |= anx7411_reg_write(ctx->spi_client, MAX_VOLTAGE, + typecp->sink_voltage); + if (ret) + return ret; + } + + if (!typecp->caps_flags) + usleep_range(5000, 6000); + + ctx->fw_version = anx7411_reg_read(ctx->spi_client, FW_VER); + ctx->fw_subversion = anx7411_reg_read(ctx->spi_client, FW_SUBVER); + + return 0; +} + +static void anx7411_chip_standby(struct anx7411_data *ctx) +{ + int ret; + u8 cc1, cc2; + struct device *dev = &ctx->spi_client->dev; + + ret = anx7411_reg_write(ctx->spi_client, OCM_CTRL_0, + anx7411_reg_read(ctx->spi_client, OCM_CTRL_0) | + OCM_RESET); + ret |= anx7411_reg_write(ctx->tcpc_client, ANALOG_CTRL_10, 0x80); + /* Set TCPC to RD and DRP enable */ + cc1 = TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT; + cc2 = TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT; + ret |= anx7411_reg_write(ctx->tcpc_client, TCPC_ROLE_CTRL, + TCPC_ROLE_CTRL_DRP | cc1 | cc2); + + /* Send DRP toggle command */ + ret |= anx7411_reg_write(ctx->tcpc_client, TCPC_COMMAND, + TCPC_CMD_LOOK4CONNECTION); + + /* Send TCPC enter standby command */ + ret |= anx7411_reg_write(ctx->tcpc_client, + TCPC_COMMAND, TCPC_CMD_I2C_IDLE); + if (ret) + dev_err(dev, "Chip standby failed\n"); +} + +static void anx7411_work_func(struct work_struct *work) +{ + int ret; + u8 buf[STATUS_LEN]; + u8 int_change; /* Interrupt change */ + u8 int_status; /* Firmware status update */ + u8 alert0, alert1; /* Interrupt alert source */ + struct anx7411_data *ctx = container_of(work, struct anx7411_data, work); + struct device *dev = &ctx->spi_client->dev; + + mutex_lock(&ctx->lock); + + /* Read interrupt change status */ + ret = anx7411_reg_block_read(ctx->spi_client, INT_STS, STATUS_LEN, buf); + if (ret < 0) { + /* Power standby mode, just return */ + goto unlock; + } + int_change = buf[0]; + int_status = buf[1]; + + /* Read alert register */ + ret = anx7411_reg_block_read(ctx->tcpc_client, ALERT_0, STATUS_LEN, buf); + if (ret < 0) + goto unlock; + + alert0 = buf[0]; + alert1 = buf[1]; + + /* Clear interrupt and alert status */ + ret = anx7411_reg_write(ctx->spi_client, INT_STS, 0); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_0, alert0); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_1, alert1); + if (ret) + goto unlock; + + if (alert1 & INTP_POW_OFF) { + anx7411_partner_unregister_altmode(ctx); + if (anx7411_set_usb_role(ctx, USB_ROLE_NONE)) + dev_err(dev, "Set usb role\n"); + anx7411_unregister_partner(ctx); + ctx->psy_online = ANX7411_PSY_OFFLINE; + ctx->usb_type = POWER_SUPPLY_USB_TYPE_C; + ctx->typec.request_voltage = 0; + ctx->typec.request_current = 0; + power_supply_changed(ctx->psy); + anx7411_chip_standby(ctx); + goto unlock; + } + + if ((alert0 & SOFTWARE_INT) && (int_change & OCM_BOOT_UP)) { + if (anx7411_config(ctx)) + dev_err(dev, "Config failed\n"); + if (anx7411_data_role_detect(ctx)) + dev_err(dev, "set PD data role\n"); + if (anx7411_power_role_detect(ctx)) + dev_err(dev, "set PD power role\n"); + anx7411_set_mux(ctx, SELECT_PIN_ASSIGMENT_C); + } + + if (alert0 & RECEIVED_MSG) + anx7411_process_cmd(ctx); + + ret = (int_status & DATA_ROLE) ? TYPEC_HOST : TYPEC_DEVICE; + if (ctx->typec.data_role != ret) + if (anx7411_data_role_detect(ctx)) + dev_err(dev, "set PD data role\n"); + + ret = (int_status & SINK_STATUS) ? TYPEC_SINK : TYPEC_SOURCE; + if (ctx->typec.power_role != ret) + if (anx7411_power_role_detect(ctx)) + dev_err(dev, "set PD power role\n"); + + if ((alert0 & SOFTWARE_INT) && (int_change & CC_STATUS_CHANGE)) + anx7411_cc_status_detect(ctx); + +unlock: + mutex_unlock(&ctx->lock); +} + +static irqreturn_t anx7411_intr_isr(int irq, void *data) +{ + struct anx7411_data *ctx = (struct anx7411_data *)data; + + queue_work(ctx->workqueue, &ctx->work); + + return IRQ_HANDLED; +} + +static int anx7411_register_i2c_dummy_clients(struct anx7411_data *ctx, + struct i2c_client *client) +{ + int i; + u8 spi_addr; + + for (i = 0; i < sizeof(anx7411_i2c_addr); i++) { + if (client->addr == (anx7411_i2c_addr[i].tcpc_address >> 1)) { + spi_addr = anx7411_i2c_addr[i].spi_address >> 1; + ctx->spi_client = i2c_new_dummy_device(client->adapter, + spi_addr); + if (ctx->spi_client) + return 0; + } + } + + dev_err(&client->dev, "unable to get SPI slave\n"); + return -ENOMEM; +} + +static void anx7411_port_unregister_altmodes(struct typec_altmode **adev) +{ + int i; + + for (i = 0; i < MAX_ALTMODE; i++) + if (adev[i]) { + typec_unregister_altmode(adev[i]); + adev[i] = NULL; + } +} + +static int anx7411_usb_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct anx7411_data *ctx = typec_mux_get_drvdata(mux); + struct device *dev = &ctx->spi_client->dev; + int has_dp; + + has_dp = (state->alt && state->alt->svid == USB_TYPEC_DP_SID && + state->alt->mode == USB_TYPEC_DP_MODE); + if (!has_dp) + dev_err(dev, "dp altmode not register\n"); + + return 0; +} + +static int anx7411_usb_set_orientation(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + /* No need set */ + + return 0; +} + +static int anx7411_register_switch(struct anx7411_data *ctx, + struct device *dev, + struct fwnode_handle *fwnode) +{ + struct typec_switch_desc sw_desc = { }; + + sw_desc.fwnode = fwnode; + sw_desc.drvdata = ctx; + sw_desc.name = fwnode_get_name(fwnode); + sw_desc.set = anx7411_usb_set_orientation; + + ctx->typec.typec_switch = typec_switch_register(dev, &sw_desc); + if (IS_ERR(ctx->typec.typec_switch)) { + dev_err(dev, "switch register failed\n"); + return PTR_ERR(ctx->typec.typec_switch); + } + + return 0; +} + +static int anx7411_register_mux(struct anx7411_data *ctx, + struct device *dev, + struct fwnode_handle *fwnode) +{ + struct typec_mux_desc mux_desc = { }; + + mux_desc.fwnode = fwnode; + mux_desc.drvdata = ctx; + mux_desc.name = fwnode_get_name(fwnode); + mux_desc.set = anx7411_usb_mux_set; + + ctx->typec.typec_mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(ctx->typec.typec_mux)) { + dev_err(dev, "mux register failed\n"); + return PTR_ERR(ctx->typec.typec_mux); + } + + return 0; +} + +static void anx7411_unregister_mux(struct anx7411_data *ctx) +{ + if (ctx->typec.typec_mux) { + typec_mux_unregister(ctx->typec.typec_mux); + ctx->typec.typec_mux = NULL; + } +} + +static void anx7411_unregister_switch(struct anx7411_data *ctx) +{ + if (ctx->typec.typec_switch) { + typec_switch_unregister(ctx->typec.typec_switch); + ctx->typec.typec_switch = NULL; + } +} + +static int anx7411_typec_switch_probe(struct anx7411_data *ctx, + struct device *dev) +{ + int ret; + struct device_node *node; + + node = of_find_node_by_name(dev->of_node, "orientation_switch"); + if (!node) + return 0; + + ret = anx7411_register_switch(ctx, dev, &node->fwnode); + if (ret) { + dev_err(dev, "failed register switch"); + return ret; + } + + node = of_find_node_by_name(dev->of_node, "mode_switch"); + if (!node) { + dev_err(dev, "no typec mux exist"); + ret = -ENODEV; + goto unregister_switch; + } + + ret = anx7411_register_mux(ctx, dev, &node->fwnode); + if (ret) { + dev_err(dev, "failed register mode switch"); + ret = -ENODEV; + goto unregister_switch; + } + + return 0; + +unregister_switch: + anx7411_unregister_switch(ctx); + + return ret; +} + +static int anx7411_typec_port_probe(struct anx7411_data *ctx, + struct device *dev) +{ + struct typec_capability *cap = &ctx->typec.caps; + struct typec_params *typecp = &ctx->typec; + struct fwnode_handle *fwnode; + const char *buf; + int ret, i; + + fwnode = device_get_named_child_node(dev, "connector"); + if (!fwnode) + return -EINVAL; + + ret = fwnode_property_read_string(fwnode, "power-role", &buf); + if (ret) { + dev_err(dev, "power-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_port_power_role(buf); + if (ret < 0) + return ret; + cap->type = ret; + + ret = fwnode_property_read_string(fwnode, "data-role", &buf); + if (ret) { + dev_err(dev, "data-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_port_data_role(buf); + if (ret < 0) + return ret; + cap->data = ret; + + ret = fwnode_property_read_string(fwnode, "try-power-role", &buf); + if (ret) { + dev_err(dev, "try-power-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_power_role(buf); + if (ret < 0) + return ret; + cap->prefer_role = ret; + + /* Get source pdos */ + ret = fwnode_property_count_u32(fwnode, "source-pdos"); + if (ret > 0) { + typecp->src_pdo_nr = min_t(u8, ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + typecp->src_pdo, + typecp->src_pdo_nr); + if (ret < 0) { + dev_err(dev, "source cap validate failed: %d\n", ret); + return -EINVAL; + } + + typecp->caps_flags |= HAS_SOURCE_CAP; + } + + ret = fwnode_property_count_u32(fwnode, "sink-pdos"); + if (ret > 0) { + typecp->sink_pdo_nr = min_t(u8, ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", + typecp->sink_pdo, + typecp->sink_pdo_nr); + if (ret < 0) { + dev_err(dev, "sink cap validate failed: %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < typecp->sink_pdo_nr; i++) { + ret = 0; + switch (pdo_type(typecp->sink_pdo[i])) { + case PDO_TYPE_FIXED: + ret = pdo_fixed_voltage(typecp->sink_pdo[i]); + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + ret = pdo_max_voltage(typecp->sink_pdo[i]); + break; + case PDO_TYPE_APDO: + default: + ret = 0; + break; + } + + /* 100mv per unit */ + typecp->sink_voltage = max(5000, ret) / 100; + } + + typecp->caps_flags |= HAS_SINK_CAP; + } + + if (!fwnode_property_read_u32(fwnode, "op-sink-microwatt", &ret)) { + typecp->sink_watt = ret / 500000; /* 500mw per unit */ + typecp->caps_flags |= HAS_SINK_WATT; + } + + cap->fwnode = fwnode; + + ctx->typec.role_sw = usb_role_switch_get(dev); + if (IS_ERR(ctx->typec.role_sw)) { + dev_err(dev, "USB role switch not found.\n"); + ctx->typec.role_sw = NULL; + } + + ctx->typec.port = typec_register_port(dev, cap); + if (IS_ERR(ctx->typec.port)) { + ret = PTR_ERR(ctx->typec.port); + ctx->typec.port = NULL; + dev_err(dev, "Failed to register type c port %d\n", ret); + return ret; + } + + typec_port_register_altmodes(ctx->typec.port, NULL, ctx, + ctx->typec.port_amode, + MAX_ALTMODE); + return 0; +} + +static int anx7411_typec_check_connection(struct anx7411_data *ctx) +{ + int ret; + + ret = anx7411_reg_read(ctx->spi_client, FW_VER); + if (ret < 0) + return 0; /* No device attached in typec port */ + + /* Clear interrupt and alert status */ + ret = anx7411_reg_write(ctx->spi_client, INT_STS, 0); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_0, 0xFF); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_1, 0xFF); + if (ret) + return ret; + + ret = anx7411_cc_status_detect(ctx); + ret |= anx7411_power_role_detect(ctx); + ret |= anx7411_data_role_detect(ctx); + ret |= anx7411_set_mux(ctx, SELECT_PIN_ASSIGMENT_C); + if (ret) + return ret; + + ret = anx7411_send_msg(ctx, TYPE_GET_DP_ALT_ENTER, NULL, 0); + ret |= anx7411_send_msg(ctx, TYPE_GET_DP_DISCOVER_MODES_INFO, NULL, 0); + + return ret; +} + +static int __maybe_unused anx7411_runtime_pm_suspend(struct device *dev) +{ + struct anx7411_data *ctx = dev_get_drvdata(dev); + + mutex_lock(&ctx->lock); + + anx7411_partner_unregister_altmode(ctx); + + if (ctx->typec.partner) + anx7411_unregister_partner(ctx); + + mutex_unlock(&ctx->lock); + + return 0; +} + +static int __maybe_unused anx7411_runtime_pm_resume(struct device *dev) +{ + struct anx7411_data *ctx = dev_get_drvdata(dev); + + mutex_lock(&ctx->lock); + /* Detect PD connection */ + if (anx7411_typec_check_connection(ctx)) + dev_err(dev, "check connection"); + + mutex_unlock(&ctx->lock); + + return 0; +} + +static const struct dev_pm_ops anx7411_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(anx7411_runtime_pm_suspend, + anx7411_runtime_pm_resume, NULL) +}; + +static void anx7411_get_gpio_irq(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->tcpc_client->dev; + + ctx->intp_gpiod = devm_gpiod_get_optional(dev, "interrupt", GPIOD_IN); + if (!ctx->intp_gpiod) { + dev_err(dev, "no interrupt gpio property\n"); + return; + } + + ctx->intp_irq = gpiod_to_irq(ctx->intp_gpiod); + if (!ctx->intp_irq) + dev_err(dev, "failed to get GPIO IRQ\n"); +} + +static enum power_supply_usb_type anx7411_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_PPS, +}; + +static enum power_supply_property anx7411_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int anx7411_psy_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct anx7411_data *ctx = power_supply_get_drvdata(psy); + int ret = 0; + + if (psp == POWER_SUPPLY_PROP_ONLINE) + ctx->psy_online = val->intval; + else + ret = -EINVAL; + + power_supply_changed(ctx->psy); + return ret; +} + +static int anx7411_psy_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_ONLINE; +} + +static int anx7411_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct anx7411_data *ctx = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = ctx->usb_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ctx->psy_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = (ctx->psy_online) ? + ctx->typec.request_voltage * 1000 : 0; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = (ctx->psy_online) ? + ctx->typec.request_current * 1000 : 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int anx7411_psy_register(struct anx7411_data *ctx) +{ + struct power_supply_desc *psy_desc = &ctx->psy_desc; + struct power_supply_config psy_cfg = {}; + char *psy_name; + + psy_name = devm_kasprintf(ctx->dev, GFP_KERNEL, "anx7411-source-psy-%s", + dev_name(ctx->dev)); + if (!psy_name) + return -ENOMEM; + + psy_desc->name = psy_name; + psy_desc->type = POWER_SUPPLY_TYPE_USB; + psy_desc->usb_types = anx7411_psy_usb_types; + psy_desc->num_usb_types = ARRAY_SIZE(anx7411_psy_usb_types); + psy_desc->properties = anx7411_psy_props, + psy_desc->num_properties = ARRAY_SIZE(anx7411_psy_props), + + psy_desc->get_property = anx7411_psy_get_prop, + psy_desc->set_property = anx7411_psy_set_prop, + psy_desc->property_is_writeable = anx7411_psy_prop_writeable, + + ctx->usb_type = POWER_SUPPLY_USB_TYPE_C; + ctx->psy = devm_power_supply_register(ctx->dev, psy_desc, &psy_cfg); + + if (IS_ERR(ctx->psy)) + dev_warn(ctx->dev, "unable to register psy\n"); + + return PTR_ERR_OR_ZERO(ctx->psy); +} + +static int anx7411_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct anx7411_data *plat; + struct device *dev = &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + plat = devm_kzalloc(dev, sizeof(*plat), GFP_KERNEL); + if (!plat) + return -ENOMEM; + + plat->tcpc_client = client; + i2c_set_clientdata(client, plat); + + mutex_init(&plat->lock); + + ret = anx7411_register_i2c_dummy_clients(plat, client); + if (ret) { + dev_err(dev, "fail to reserve I2C bus\n"); + return ret; + } + + ret = anx7411_typec_switch_probe(plat, dev); + if (ret) { + dev_err(dev, "fail to probe typec switch\n"); + goto free_i2c_dummy; + } + + ret = anx7411_typec_port_probe(plat, dev); + if (ret) { + dev_err(dev, "fail to probe typec property.\n"); + ret = -ENODEV; + goto free_typec_switch; + } + + plat->intp_irq = client->irq; + if (!client->irq) + anx7411_get_gpio_irq(plat); + + if (!plat->intp_irq) { + dev_err(dev, "fail to get interrupt IRQ\n"); + goto free_typec_port; + } + + plat->dev = dev; + plat->psy_online = ANX7411_PSY_OFFLINE; + if (anx7411_psy_register(plat)) { + dev_err(dev, "register psy\n"); + goto free_typec_port; + } + + INIT_WORK(&plat->work, anx7411_work_func); + plat->workqueue = alloc_workqueue("anx7411_work", + WQ_FREEZABLE | + WQ_MEM_RECLAIM, + 1); + if (!plat->workqueue) { + dev_err(dev, "fail to create work queue\n"); + ret = -ENOMEM; + goto free_typec_port; + } + + ret = devm_request_threaded_irq(dev, plat->intp_irq, + NULL, anx7411_intr_isr, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "anx7411-intp", plat); + if (ret) { + dev_err(dev, "fail to request irq\n"); + goto free_wq; + } + + if (anx7411_typec_check_connection(plat)) + dev_err(dev, "check status\n"); + + pm_runtime_enable(dev); + + return 0; + +free_wq: + destroy_workqueue(plat->workqueue); + +free_typec_port: + typec_unregister_port(plat->typec.port); + anx7411_port_unregister_altmodes(plat->typec.port_amode); + +free_typec_switch: + anx7411_unregister_switch(plat); + anx7411_unregister_mux(plat); + +free_i2c_dummy: + i2c_unregister_device(plat->spi_client); + + return ret; +} + +static int anx7411_i2c_remove(struct i2c_client *client) +{ + struct anx7411_data *plat = i2c_get_clientdata(client); + + anx7411_partner_unregister_altmode(plat); + anx7411_unregister_partner(plat); + + if (plat->workqueue) + destroy_workqueue(plat->workqueue); + + if (plat->spi_client) + i2c_unregister_device(plat->spi_client); + + if (plat->typec.role_sw) + usb_role_switch_put(plat->typec.role_sw); + + anx7411_unregister_mux(plat); + + anx7411_unregister_switch(plat); + + if (plat->typec.port) + typec_unregister_port(plat->typec.port); + + anx7411_port_unregister_altmodes(plat->typec.port_amode); + + return 0; +} + +static const struct i2c_device_id anx7411_id[] = { + {"anx7411", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, anx7411_id); + +static const struct of_device_id anx_match_table[] = { + {.compatible = "analogix,anx7411",}, + {}, +}; + +static struct i2c_driver anx7411_driver = { + .driver = { + .name = "anx7411", + .of_match_table = anx_match_table, + .pm = &anx7411_pm_ops, + }, + .probe = anx7411_i2c_probe, + .remove = anx7411_i2c_remove, + + .id_table = anx7411_id, +}; + +module_i2c_driver(anx7411_driver); + +MODULE_DESCRIPTION("Anx7411 USB Type-C PD driver"); +MODULE_AUTHOR("Xin Ji "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1.5"); -- cgit v1.2.3 From 69bb3520db7cecbccc9e497fc568fa5465c9d43f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 13 Jul 2022 15:13:36 +0200 Subject: usb: dwc3: qcom: fix missing optional irq warnings Not all platforms have all of the four currently supported wakeup interrupts so use the optional irq helpers when looking up interrupts to avoid printing error messages when an optional interrupt is not found: dwc3-qcom a6f8800.usb: error -ENXIO: IRQ hs_phy_irq not found Fixes: a4333c3a6ba9 ("usb: dwc3: Add Qualcomm DWC3 glue driver") Reviewed-by: Andrew Halaney Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20220713131340.29401-4-johan+linaro@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 77036551987a..c5e482f53e9d 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -490,9 +490,9 @@ static int dwc3_qcom_get_irq(struct platform_device *pdev, int ret; if (np) - ret = platform_get_irq_byname(pdev_irq, name); + ret = platform_get_irq_byname_optional(pdev_irq, name); else - ret = platform_get_irq(pdev_irq, num); + ret = platform_get_irq_optional(pdev_irq, num); return ret; } -- cgit v1.2.3 From ddaf8d96f93bccb3f2b1f4f156c098b272440004 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:22:55 +0000 Subject: usb: typec: Add support for retimers Introduce a retimer device class and associated functions that register and use retimer "switch" devices. These operate in a manner similar to the "mode-switch" and help configure retimers that exist between the Type-C connector and host controller(s). Type C ports can be linked to retimers using firmware node device references (again, in a manner similar to "mode-switch"). There are no new sysfs files being created; there is the new retimer class directory, but there are no class-specific files being created there. Signed-off-by: Prashant Malani Acked-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220711072333.2064341-2-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Makefile | 2 +- drivers/usb/typec/class.c | 9 +- drivers/usb/typec/class.h | 1 + drivers/usb/typec/retimer.c | 168 ++++++++++++++++++++++++++++++++++++++ drivers/usb/typec/retimer.h | 15 ++++ include/linux/usb/typec_retimer.h | 45 ++++++++++ 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 drivers/usb/typec/retimer.c create mode 100644 drivers/usb/typec/retimer.h create mode 100644 include/linux/usb/typec_retimer.h (limited to 'drivers') diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index dac5c11a3234..4a83dad51a6c 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o -typec-y := class.o mux.o bus.o pd.o +typec-y := class.o mux.o bus.o pd.o retimer.o typec-$(CONFIG_ACPI) += port-mapper.o obj-$(CONFIG_TYPEC) += altmodes/ obj-$(CONFIG_TYPEC_TCPM) += tcpm/ diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index bbc46b14f99a..9062836bb638 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -2299,10 +2299,14 @@ static int __init typec_init(void) if (ret) goto err_unregister_bus; - ret = class_register(&typec_class); + ret = class_register(&retimer_class); if (ret) goto err_unregister_mux_class; + ret = class_register(&typec_class); + if (ret) + goto err_unregister_retimer_class; + ret = usb_power_delivery_init(); if (ret) goto err_unregister_class; @@ -2312,6 +2316,9 @@ static int __init typec_init(void) err_unregister_class: class_unregister(&typec_class); +err_unregister_retimer_class: + class_unregister(&retimer_class); + err_unregister_mux_class: class_unregister(&typec_mux_class); diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index b531f9853bc0..43fcf9e37a8c 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -76,6 +76,7 @@ extern const struct device_type typec_port_dev_type; #define is_typec_port(dev) ((dev)->type == &typec_port_dev_type) extern struct class typec_mux_class; +extern struct class retimer_class; extern struct class typec_class; #if defined(CONFIG_ACPI) diff --git a/drivers/usb/typec/retimer.c b/drivers/usb/typec/retimer.c new file mode 100644 index 000000000000..051eaa7d2899 --- /dev/null +++ b/drivers/usb/typec/retimer.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2022 Google LLC + * + * USB Type-C Retimer support. + * Author: Prashant Malani + * + */ + +#include +#include +#include +#include +#include +#include + +#include "class.h" +#include "retimer.h" + +static bool dev_name_ends_with(struct device *dev, const char *suffix) +{ + const char *name = dev_name(dev); + const int name_len = strlen(name); + const int suffix_len = strlen(suffix); + + if (suffix_len > name_len) + return false; + + return strcmp(name + (name_len - suffix_len), suffix) == 0; +} + +static int retimer_fwnode_match(struct device *dev, const void *fwnode) +{ + return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-retimer"); +} + +static void *typec_retimer_match(struct fwnode_handle *fwnode, const char *id, void *data) +{ + struct device *dev = class_find_device(&retimer_class, NULL, fwnode, + retimer_fwnode_match); + + return dev ? to_typec_retimer(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * fwnode_typec_retimer_get - Find USB Type-C retimer. + * @fwnode: The caller device node. + * + * Finds a retimer linked to the caller. This function is primarily meant for the + * Type-C drivers. Returns a reference to the retimer on success, NULL if no + * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection + * was found but the retimer has not been enumerated yet. + */ +struct typec_retimer *fwnode_typec_retimer_get(struct fwnode_handle *fwnode) +{ + struct typec_retimer *retimer; + + retimer = fwnode_connection_find_match(fwnode, "retimer-switch", NULL, typec_retimer_match); + if (!IS_ERR_OR_NULL(retimer)) + WARN_ON(!try_module_get(retimer->dev.parent->driver->owner)); + + return retimer; +} +EXPORT_SYMBOL_GPL(fwnode_typec_retimer_get); + +/** + * typec_retimer_put - Release handle to a retimer. + * @retimer: USB Type-C Connector Retimer. + * + * Decrements reference count for @retimer. + */ +void typec_retimer_put(struct typec_retimer *retimer) +{ + if (!IS_ERR_OR_NULL(retimer)) { + module_put(retimer->dev.parent->driver->owner); + put_device(&retimer->dev); + } +} +EXPORT_SYMBOL_GPL(typec_retimer_put); + +int typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + if (IS_ERR_OR_NULL(retimer)) + return 0; + + return retimer->set(retimer, state); +} +EXPORT_SYMBOL_GPL(typec_retimer_set); + +static void typec_retimer_release(struct device *dev) +{ + kfree(to_typec_retimer(dev)); +} + +static const struct device_type typec_retimer_dev_type = { + .name = "typec_retimer", + .release = typec_retimer_release, +}; + +/** + * typec_retimer_register - Register a retimer device. + * @parent: Parent device. + * @desc: Retimer description. + * + * Some USB Type-C connectors have their physical lines routed through retimers before they + * reach muxes or host controllers. In some cases (for example: using alternate modes) + * these retimers need to be reconfigured appropriately. This function registers retimer + * switches which route and potentially modify the signals on the Type C physical lines + * enroute to the host controllers. + */ +struct typec_retimer * +typec_retimer_register(struct device *parent, const struct typec_retimer_desc *desc) +{ + struct typec_retimer *retimer; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + retimer = kzalloc(sizeof(*retimer), GFP_KERNEL); + if (!retimer) + return ERR_PTR(-ENOMEM); + + retimer->set = desc->set; + + device_initialize(&retimer->dev); + retimer->dev.parent = parent; + retimer->dev.fwnode = desc->fwnode; + retimer->dev.class = &retimer_class; + retimer->dev.type = &typec_retimer_dev_type; + retimer->dev.driver_data = desc->drvdata; + dev_set_name(&retimer->dev, "%s-retimer", + desc->name ? desc->name : dev_name(parent)); + + ret = device_add(&retimer->dev); + if (ret) { + dev_err(parent, "failed to register retimer (%d)\n", ret); + put_device(&retimer->dev); + return ERR_PTR(ret); + } + + return retimer; +} +EXPORT_SYMBOL_GPL(typec_retimer_register); + +/** + * typec_retimer_unregister - Unregister retimer device. + * @retimer: USB Type-C Connector retimer. + * + * Unregister retimer that was registered with typec_retimer_register(). + */ +void typec_retimer_unregister(struct typec_retimer *retimer) +{ + if (!IS_ERR_OR_NULL(retimer)) + device_unregister(&retimer->dev); +} +EXPORT_SYMBOL_GPL(typec_retimer_unregister); + +void *typec_retimer_get_drvdata(struct typec_retimer *retimer) +{ + return dev_get_drvdata(&retimer->dev); +} +EXPORT_SYMBOL_GPL(typec_retimer_get_drvdata); + +struct class retimer_class = { + .name = "retimer", + .owner = THIS_MODULE, +}; diff --git a/drivers/usb/typec/retimer.h b/drivers/usb/typec/retimer.h new file mode 100644 index 000000000000..fa15951d4846 --- /dev/null +++ b/drivers/usb/typec/retimer.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_RETIMER__ +#define __USB_TYPEC_RETIMER__ + +#include + +struct typec_retimer { + struct device dev; + typec_retimer_set_fn_t set; +}; + +#define to_typec_retimer(_dev_) container_of(_dev_, struct typec_retimer, dev) + +#endif /* __USB_TYPEC_RETIMER__ */ diff --git a/include/linux/usb/typec_retimer.h b/include/linux/usb/typec_retimer.h new file mode 100644 index 000000000000..5e036b3360e2 --- /dev/null +++ b/include/linux/usb/typec_retimer.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_RETIMER +#define __USB_TYPEC_RETIMER + +#include +#include + +struct device; +struct typec_retimer; +struct typec_altmode; +struct fwnode_handle; + +struct typec_retimer_state { + struct typec_altmode *alt; + unsigned long mode; + void *data; +}; + +typedef int (*typec_retimer_set_fn_t)(struct typec_retimer *retimer, + struct typec_retimer_state *state); + +struct typec_retimer_desc { + struct fwnode_handle *fwnode; + typec_retimer_set_fn_t set; + const char *name; + void *drvdata; +}; + +struct typec_retimer *fwnode_typec_retimer_get(struct fwnode_handle *fwnode); +void typec_retimer_put(struct typec_retimer *retimer); +int typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state); + +static inline struct typec_retimer *typec_retimer_get(struct device *dev) +{ + return fwnode_typec_retimer_get(dev_fwnode(dev)); +} + +struct typec_retimer * +typec_retimer_register(struct device *parent, const struct typec_retimer_desc *desc); +void typec_retimer_unregister(struct typec_retimer *retimer); + +void *typec_retimer_get_drvdata(struct typec_retimer *retimer); + +#endif /* __USB_TYPEC_RETIMER */ -- cgit v1.2.3 From f31a8702cd36f7908bdc3fa1ed7f95b56c10ed35 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:22:56 +0000 Subject: usb: typec: Add retimer handle to port Similar to mux and orientation switch, add a handle for registered retimer to the port, so that it has handles to the various switches connected to it. Signed-off-by: Prashant Malani Acked-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220711072333.2064341-3-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 9 +++++++++ drivers/usb/typec/class.h | 1 + 2 files changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 9062836bb638..f08e32d552b4 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "bus.h" #include "class.h" @@ -1736,6 +1737,7 @@ static void typec_release(struct device *dev) ida_destroy(&port->mode_ids); typec_switch_put(port->sw); typec_mux_put(port->mux); + typec_retimer_put(port->retimer); kfree(port->cap); kfree(port); } @@ -2249,6 +2251,13 @@ struct typec_port *typec_register_port(struct device *parent, return ERR_PTR(ret); } + port->retimer = typec_retimer_get(&port->dev); + if (IS_ERR(port->retimer)) { + ret = PTR_ERR(port->retimer); + put_device(&port->dev); + return ERR_PTR(ret); + } + ret = device_add(&port->dev); if (ret) { dev_err(parent, "failed to register port (%d)\n", ret); diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index 43fcf9e37a8c..673b2952b074 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -55,6 +55,7 @@ struct typec_port { enum typec_orientation orientation; struct typec_switch *sw; struct typec_mux *mux; + struct typec_retimer *retimer; const struct typec_capability *cap; const struct typec_operations *ops; -- cgit v1.2.3 From e54369058f3da181fcc4c893f224a0c5a8a526b6 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:22:58 +0000 Subject: platform/chrome: cros_typec_switch: Add switch driver Introduce a driver to configure USB Type-C mode switches and retimers which are controlled by the Chrome OS EC (Embedded Controller). This allows Type-C port drivers, as well as alternate mode drivers to configure their relevant mode switches and retimers according to the Type-C state they want to achieve. ACPI devices with ID GOOG001A will bind to this driver. Currently, we only register a retimer switch with a stub set function. Subsequent patches will implement the host command set functionality, and introduce mode switches. Reported-by: kernel test robot Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220711072333.2064341-5-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/platform/chrome/Kconfig | 11 ++ drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_typec_switch.c | 170 ++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 drivers/platform/chrome/cros_typec_switch.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index f43b126eaf59..d5cdf81ea2c0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4752,6 +4752,7 @@ M: Prashant Malani L: chrome-platform@lists.linux.dev S: Maintained F: drivers/platform/chrome/cros_ec_typec.c +F: drivers/platform/chrome/cros_typec_switch.c CHROMEOS EC USB PD NOTIFY DRIVER M: Prashant Malani diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 717299cbccac..c62a514a087f 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -265,6 +265,17 @@ config CHROMEOS_PRIVACY_SCREEN this should probably always be built into the kernel to avoid or minimize drm probe deferral. +config CROS_TYPEC_SWITCH + tristate "ChromeOS EC Type-C Switch Control" + depends on MFD_CROS_EC_DEV && TYPEC + default MFD_CROS_EC_DEV + help + If you say Y here, you get support for configuring the Chrome OS EC Type C + muxes and retimers. + + To compile this driver as a module, choose M here: the module will be + called cros_typec_switch. + source "drivers/platform/chrome/wilco_ec/Kconfig" endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 52f5a2dde8b8..0dcaf6a7ed27 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o obj-$(CONFIG_CROS_EC) += cros_ec.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o +obj-$(CONFIG_CROS_TYPEC_SWITCH) += cros_typec_switch.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c new file mode 100644 index 000000000000..0d319e315d57 --- /dev/null +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2022 Google LLC + * + * This driver provides the ability to configure Type C muxes and retimers which are controlled by + * the Chrome OS EC. + */ + +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-typec-switch" + +/* Handles and other relevant data required for each port's switches. */ +struct cros_typec_port { + int port_num; + struct typec_retimer *retimer; + struct cros_typec_switch_data *sdata; +}; + +/* Driver-specific data. */ +struct cros_typec_switch_data { + struct device *dev; + struct cros_ec_device *ec; + struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; +}; + +static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + return 0; +} + +static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) +{ + int i; + + for (i = 0; i < EC_USB_PD_MAX_PORTS; i++) { + if (!sdata->ports[i]) + continue; + typec_retimer_unregister(sdata->ports[i]->retimer); + } +} + +static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) +{ + struct typec_retimer_desc retimer_desc = { + .fwnode = fwnode, + .drvdata = port, + .name = fwnode_get_name(fwnode), + .set = cros_typec_retimer_set, + }; + + port->retimer = typec_retimer_register(port->sdata->dev, &retimer_desc); + if (IS_ERR(port->retimer)) + return PTR_ERR(port->retimer); + + return 0; +} + +static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) +{ + struct cros_typec_port *port = NULL; + struct device *dev = sdata->dev; + struct fwnode_handle *fwnode; + struct acpi_device *adev; + unsigned long long index; + int ret = 0; + int nports; + + nports = device_get_child_node_count(dev); + if (nports == 0) { + dev_err(dev, "No switch devices found.\n"); + return -ENODEV; + } + + device_for_each_child_node(dev, fwnode) { + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) { + ret = -ENOMEM; + goto err_switch; + } + + adev = to_acpi_device_node(fwnode); + if (!adev) { + dev_err(fwnode->dev, "Couldn't get ACPI device handle\n"); + ret = -ENODEV; + goto err_switch; + } + + ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index); + if (ACPI_FAILURE(ret)) { + dev_err(fwnode->dev, "_ADR wasn't evaluated\n"); + ret = -ENODATA; + goto err_switch; + } + + if (index < 0 || index >= EC_USB_PD_MAX_PORTS) { + dev_err(fwnode->dev, "Invalid port index number: %llu", index); + ret = -EINVAL; + goto err_switch; + } + port->sdata = sdata; + port->port_num = index; + sdata->ports[index] = port; + + ret = cros_typec_register_retimer(port, fwnode); + if (ret) { + dev_err(dev, "Retimer switch register failed\n"); + goto err_switch; + } + + dev_dbg(dev, "Retimer switch registered for index %llu\n", index); + } + + return 0; +err_switch: + cros_typec_unregister_switches(sdata); + return ret; +} + +static int cros_typec_switch_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_typec_switch_data *sdata; + + sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL); + if (!sdata) + return -ENOMEM; + + sdata->dev = dev; + sdata->ec = dev_get_drvdata(pdev->dev.parent); + + platform_set_drvdata(pdev, sdata); + + return cros_typec_register_switches(sdata); +} + +static int cros_typec_switch_remove(struct platform_device *pdev) +{ + struct cros_typec_switch_data *sdata = platform_get_drvdata(pdev); + + cros_typec_unregister_switches(sdata); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cros_typec_switch_acpi_id[] = { + { "GOOG001A", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, cros_typec_switch_acpi_id); +#endif + +static struct platform_driver cros_typec_switch_driver = { + .driver = { + .name = DRV_NAME, + .acpi_match_table = ACPI_PTR(cros_typec_switch_acpi_id), + }, + .probe = cros_typec_switch_probe, + .remove = cros_typec_switch_remove, +}; + +module_platform_driver(cros_typec_switch_driver); + +MODULE_AUTHOR("Prashant Malani "); +MODULE_DESCRIPTION("Chrome OS EC Type C Switch control"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 34f375f0fdf67f8804142fa37a28e73426d4c1df Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:22:59 +0000 Subject: platform/chrome: cros_typec_switch: Set EC retimer Invoke Chrome EC host commands to set EC-controlled retimer switches to the state the Type-C framework instructs. Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220711072333.2064341-6-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_typec_switch.c | 56 ++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 0d319e315d57..b50ecedce662 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -9,7 +9,10 @@ #include #include #include +#include #include +#include +#include #include #define DRV_NAME "cros-typec-switch" @@ -28,9 +31,60 @@ struct cros_typec_switch_data { struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; }; +static int cros_typec_cmd_mux_set(struct cros_typec_switch_data *sdata, int port_num, u8 index, + u8 state) +{ + struct typec_usb_mux_set params = { + .mux_index = index, + .mux_flags = state, + }; + + struct ec_params_typec_control req = { + .port = port_num, + .command = TYPEC_CONTROL_COMMAND_USB_MUX_SET, + .mux_params = params, + }; + + return cros_ec_command(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, + sizeof(req), NULL, 0); +} + +static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *alt) +{ + int ret = -EOPNOTSUPP; + + if (mode == TYPEC_STATE_SAFE) + ret = USB_PD_MUX_SAFE_MODE; + else if (mode == TYPEC_STATE_USB) + ret = USB_PD_MUX_USB_ENABLED; + else if (alt && alt->svid == USB_TYPEC_DP_SID) + ret = USB_PD_MUX_DP_ENABLED; + + return ret; +} + +/* + * The Chrome EC treats both mode-switches and retimers as "muxes" for the purposes of the + * host command API. This common function configures and verifies the retimer/mode-switch + * according to the provided setting. + */ +static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index, + unsigned long mode, struct typec_altmode *alt) +{ + int ret = cros_typec_get_mux_state(mode, alt); + + if (ret < 0) + return ret; + + return cros_typec_cmd_mux_set(sdata, port_num, index, (u8)ret); +} + static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { - return 0; + struct cros_typec_port *port = typec_retimer_get_drvdata(retimer); + + /* Retimers have index 1. */ + return cros_typec_configure_mux(port->sdata, port->port_num, 1, state->mode, state->alt); } static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) -- cgit v1.2.3 From bb53ad958012f5a8d88b3b7159c22b3b877601bb Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:23:00 +0000 Subject: platform/chrome: cros_typec_switch: Add event check The Chrome EC updates Type-C status events when mux set requests from the Application Processor (AP) are completed. Add a check to the flow of configuring muxes to look for this status done bit, so that the driver is aware that the mux set completed successfully or not. Reported-by: kernel test robot Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220711072333.2064341-7-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_typec_switch.c | 72 ++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index b50ecedce662..7c01957a032d 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -7,6 +7,8 @@ */ #include +#include +#include #include #include #include @@ -63,6 +65,40 @@ static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *al return ret; } +static int cros_typec_send_clear_event(struct cros_typec_switch_data *sdata, int port_num, + u32 events_mask) +{ + struct ec_params_typec_control req = { + .port = port_num, + .command = TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, + .clear_events_mask = events_mask, + }; + + return cros_ec_command(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, + sizeof(req), NULL, 0); +} + +static bool cros_typec_check_event(struct cros_typec_switch_data *sdata, int port_num, u32 mask) +{ + struct ec_response_typec_status resp; + struct ec_params_typec_status req = { + .port = port_num, + }; + int ret; + + ret = cros_ec_command(sdata->ec, 0, EC_CMD_TYPEC_STATUS, &req, sizeof(req), + &resp, sizeof(resp)); + if (ret < 0) { + dev_warn(sdata->dev, "EC_CMD_TYPEC_STATUS failed for port: %d\n", port_num); + return false; + } + + if (resp.events & mask) + return true; + + return false; +} + /* * The Chrome EC treats both mode-switches and retimers as "muxes" for the purposes of the * host command API. This common function configures and verifies the retimer/mode-switch @@ -71,12 +107,44 @@ static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *al static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index, unsigned long mode, struct typec_altmode *alt) { - int ret = cros_typec_get_mux_state(mode, alt); + unsigned long end; + u32 event_mask; + u8 mux_state; + int ret; + + ret = cros_typec_get_mux_state(mode, alt); + if (ret < 0) + return ret; + mux_state = (u8)ret; + /* Clear any old mux set done event. */ + if (index == 0) + event_mask = PD_STATUS_EVENT_MUX_0_SET_DONE; + else + event_mask = PD_STATUS_EVENT_MUX_1_SET_DONE; + + ret = cros_typec_send_clear_event(sdata, port_num, event_mask); + if (ret < 0) + return ret; + + /* Send the set command. */ + ret = cros_typec_cmd_mux_set(sdata, port_num, index, mux_state); if (ret < 0) return ret; - return cros_typec_cmd_mux_set(sdata, port_num, index, (u8)ret); + /* Check for the mux set done event. */ + end = jiffies + msecs_to_jiffies(1000); + do { + if (cros_typec_check_event(sdata, port_num, event_mask)) + return 0; + + usleep_range(500, 1000); + } while (time_before(jiffies, end)); + + dev_err(sdata->dev, "Timed out waiting for mux set done on index: %d, state: %d\n", + index, mux_state); + + return -ETIMEDOUT; } static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) -- cgit v1.2.3 From f5434e30011e011b24852959365b7cbc61dd8c85 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:23:01 +0000 Subject: platform/chrome: cros_typec_switch: Register mode switches Register mode switch devices for Type C connectors, when they are specified by firmware. These control Type C configuration for any USB Type-C mode switches (sometimes known as "muxes") which are controlled by the Chrome EC. Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220711072333.2064341-8-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_typec_switch.c | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 7c01957a032d..024a2bb146b2 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #define DRV_NAME "cros-typec-switch" @@ -22,6 +23,7 @@ /* Handles and other relevant data required for each port's switches. */ struct cros_typec_port { int port_num; + struct typec_mux_dev *mode_switch; struct typec_retimer *retimer; struct cros_typec_switch_data *sdata; }; @@ -147,6 +149,15 @@ static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int po return -ETIMEDOUT; } +static int cros_typec_mode_switch_set(struct typec_mux_dev *mode_switch, + struct typec_mux_state *state) +{ + struct cros_typec_port *port = typec_mux_get_drvdata(mode_switch); + + /* Mode switches have index 0. */ + return cros_typec_configure_mux(port->sdata, port->port_num, 0, state->mode, state->alt); +} + static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { struct cros_typec_port *port = typec_retimer_get_drvdata(retimer); @@ -163,9 +174,27 @@ static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) if (!sdata->ports[i]) continue; typec_retimer_unregister(sdata->ports[i]->retimer); + typec_mux_unregister(sdata->ports[i]->mode_switch); } } +static int cros_typec_register_mode_switch(struct cros_typec_port *port, + struct fwnode_handle *fwnode) +{ + struct typec_mux_desc mode_switch_desc = { + .fwnode = fwnode, + .drvdata = port, + .name = fwnode_get_name(fwnode), + .set = cros_typec_mode_switch_set, + }; + + port->mode_switch = typec_mux_register(port->sdata->dev, &mode_switch_desc); + if (IS_ERR(port->mode_switch)) + return PTR_ERR(port->mode_switch); + + return 0; +} + static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) { struct typec_retimer_desc retimer_desc = { @@ -235,6 +264,17 @@ static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) } dev_dbg(dev, "Retimer switch registered for index %llu\n", index); + + if (!fwnode_property_read_bool(fwnode, "mode-switch")) + continue; + + ret = cros_typec_register_mode_switch(port, fwnode); + if (ret) { + dev_err(dev, "Mode switch register failed\n"); + goto err_switch; + } + + dev_dbg(dev, "Mode switch registered for index %llu\n", index); } return 0; -- cgit v1.2.3 From 66fe238a9bcc158f75ddecf976d1ce7efe20f713 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:23:02 +0000 Subject: platform/chrome: cros_ec_typec: Cleanup switch handle return paths Some of the return paths for the cros_typec_get_switch_handles() aren't necessary. Clean up the return paths to only undo the handle get's which succeeded. Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220711072333.2064341-9-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_typec.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 7cb2e35c4ded..39e6fd4491a9 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -157,12 +157,10 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, return 0; role_sw_err: - usb_role_switch_put(port->role_sw); -ori_sw_err: typec_switch_put(port->ori_sw); -mux_err: +ori_sw_err: typec_mux_put(port->mux); - +mux_err: return -ENODEV; } -- cgit v1.2.3 From c76d09da77d69d7f737540985912ad2bca654713 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 11 Jul 2022 07:23:03 +0000 Subject: platform/chrome: cros_ec_typec: Get retimer handle Where available, obtain the handle to retimer switch specified via firmware, and update the mux configuration callsites to add retimer support for supported modes. Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220711072333.2064341-10-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_typec.c | 44 ++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 39e6fd4491a9..38c4ac754ea9 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,7 @@ struct cros_typec_port { struct usb_pd_identity c_identity; struct typec_switch *ori_sw; struct typec_mux *mux; + struct typec_retimer *retimer; struct usb_role_switch *role_sw; /* Variables keeping track of switch state. */ @@ -142,6 +144,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, goto mux_err; } + port->retimer = fwnode_typec_retimer_get(fwnode); + if (IS_ERR(port->retimer)) { + dev_dbg(dev, "Retimer handle not found.\n"); + goto retimer_sw_err; + } + port->ori_sw = fwnode_typec_switch_get(fwnode); if (IS_ERR(port->ori_sw)) { dev_dbg(dev, "Orientation switch handle not found.\n"); @@ -159,6 +167,8 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, role_sw_err: typec_switch_put(port->ori_sw); ori_sw_err: + typec_retimer_put(port->retimer); +retimer_sw_err: typec_mux_put(port->mux); mux_err: return -ENODEV; @@ -203,6 +213,21 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po } } +/* + * Map the Type-C Mux state to retimer state and call the retimer set function. We need this + * because we re-use the Type-C mux state for retimers. + */ +static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_mux_state state) +{ + struct typec_retimer_state rstate = { + .alt = state.alt, + .mode = state.mode, + .data = state.data, + }; + + return typec_retimer_set(retimer, &rstate); +} + static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) { port->state.alt = NULL; @@ -211,6 +236,7 @@ static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); + cros_typec_retimer_set(port->retimer, port->state); return typec_mux_set(port->mux, &port->state); } @@ -381,9 +407,14 @@ unregister_ports: static int cros_typec_usb_safe_state(struct cros_typec_port *port) { + int ret; port->state.mode = TYPEC_STATE_SAFE; - return typec_mux_set(port->mux, &port->state); + ret = cros_typec_retimer_set(port->retimer, port->state); + if (!ret) + ret = typec_mux_set(port->mux, &port->state); + + return ret; } /* @@ -480,7 +511,11 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec, port->state.data = &dp_data; port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode)); - return typec_mux_set(port->mux, &port->state); + ret = cros_typec_retimer_set(port->retimer, port->state); + if (!ret) + ret = typec_mux_set(port->mux, &port->state); + + return ret; } static int cros_typec_enable_usb4(struct cros_typec_data *typec, @@ -569,7 +604,10 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, } else if (port->mux_flags & USB_PD_MUX_USB_ENABLED) { port->state.alt = NULL; port->state.mode = TYPEC_STATE_USB; - ret = typec_mux_set(port->mux, &port->state); + + ret = cros_typec_retimer_set(port->retimer, port->state); + if (!ret) + ret = typec_mux_set(port->mux, &port->state); } else { dev_dbg(typec->dev, "Unrecognized mode requested, mux flags: %x\n", -- cgit v1.2.3 From 88a15fbb47db483d06b12b1ae69f114b96361a96 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 18 Jul 2022 18:55:51 +0000 Subject: platform/chrome: cros_typec_switch: Add ACPI Kconfig dep Add the ACPI Kconfig dependency that was missed during the initial driver submission. Fixes the following compiler errors: drivers/platform/chrome/cros_typec_switch.c:93:9: error: call to undeclared function 'acpi_evaluate_integer'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index); drivers/platform/chrome/cros_typec_switch.c:93:35: error: incomplete definition of type 'struct acpi_device' ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index); Fixes: e54369058f3d ("platform/chrome: cros_typec_switch: Add switch driver") Reported-by: Reported-by: kernel test robot Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20220718185551.1025288-1-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index c62a514a087f..9d4fc505fa25 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -267,7 +267,7 @@ config CHROMEOS_PRIVACY_SCREEN config CROS_TYPEC_SWITCH tristate "ChromeOS EC Type-C Switch Control" - depends on MFD_CROS_EC_DEV && TYPEC + depends on MFD_CROS_EC_DEV && TYPEC && ACPI default MFD_CROS_EC_DEV help If you say Y here, you get support for configuring the Chrome OS EC Type C -- cgit v1.2.3 From 829b4c4183c2201d2c3981d55bdaeec201ec4098 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:56:32 +0200 Subject: Revert "platform/chrome: cros_ec_typec: Get retimer handle" This reverts commit c76d09da77d69d7f737540985912ad2bca654713. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_typec.c | 44 +++------------------------------ 1 file changed, 3 insertions(+), 41 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 38c4ac754ea9..39e6fd4491a9 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -54,7 +53,6 @@ struct cros_typec_port { struct usb_pd_identity c_identity; struct typec_switch *ori_sw; struct typec_mux *mux; - struct typec_retimer *retimer; struct usb_role_switch *role_sw; /* Variables keeping track of switch state. */ @@ -144,12 +142,6 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, goto mux_err; } - port->retimer = fwnode_typec_retimer_get(fwnode); - if (IS_ERR(port->retimer)) { - dev_dbg(dev, "Retimer handle not found.\n"); - goto retimer_sw_err; - } - port->ori_sw = fwnode_typec_switch_get(fwnode); if (IS_ERR(port->ori_sw)) { dev_dbg(dev, "Orientation switch handle not found.\n"); @@ -167,8 +159,6 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, role_sw_err: typec_switch_put(port->ori_sw); ori_sw_err: - typec_retimer_put(port->retimer); -retimer_sw_err: typec_mux_put(port->mux); mux_err: return -ENODEV; @@ -213,21 +203,6 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po } } -/* - * Map the Type-C Mux state to retimer state and call the retimer set function. We need this - * because we re-use the Type-C mux state for retimers. - */ -static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_mux_state state) -{ - struct typec_retimer_state rstate = { - .alt = state.alt, - .mode = state.mode, - .data = state.data, - }; - - return typec_retimer_set(retimer, &rstate); -} - static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) { port->state.alt = NULL; @@ -236,7 +211,6 @@ static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); - cros_typec_retimer_set(port->retimer, port->state); return typec_mux_set(port->mux, &port->state); } @@ -407,14 +381,9 @@ unregister_ports: static int cros_typec_usb_safe_state(struct cros_typec_port *port) { - int ret; port->state.mode = TYPEC_STATE_SAFE; - ret = cros_typec_retimer_set(port->retimer, port->state); - if (!ret) - ret = typec_mux_set(port->mux, &port->state); - - return ret; + return typec_mux_set(port->mux, &port->state); } /* @@ -511,11 +480,7 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec, port->state.data = &dp_data; port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode)); - ret = cros_typec_retimer_set(port->retimer, port->state); - if (!ret) - ret = typec_mux_set(port->mux, &port->state); - - return ret; + return typec_mux_set(port->mux, &port->state); } static int cros_typec_enable_usb4(struct cros_typec_data *typec, @@ -604,10 +569,7 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, } else if (port->mux_flags & USB_PD_MUX_USB_ENABLED) { port->state.alt = NULL; port->state.mode = TYPEC_STATE_USB; - - ret = cros_typec_retimer_set(port->retimer, port->state); - if (!ret) - ret = typec_mux_set(port->mux, &port->state); + ret = typec_mux_set(port->mux, &port->state); } else { dev_dbg(typec->dev, "Unrecognized mode requested, mux flags: %x\n", -- cgit v1.2.3 From 9169d2fd1f5778295ec4934acd99dc7b35a83fd5 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:56:40 +0200 Subject: Revert "platform/chrome: cros_ec_typec: Cleanup switch handle return paths" This reverts commit 66fe238a9bcc158f75ddecf976d1ce7efe20f713. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_typec.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 39e6fd4491a9..7cb2e35c4ded 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -157,10 +157,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, return 0; role_sw_err: - typec_switch_put(port->ori_sw); + usb_role_switch_put(port->role_sw); ori_sw_err: - typec_mux_put(port->mux); + typec_switch_put(port->ori_sw); mux_err: + typec_mux_put(port->mux); + return -ENODEV; } -- cgit v1.2.3 From 3838896d374929638bdb143dc33aee21931ef903 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:56:46 +0200 Subject: Revert "platform/chrome: cros_typec_switch: Register mode switches" This reverts commit f5434e30011e011b24852959365b7cbc61dd8c85. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_typec_switch.c | 40 ----------------------------- 1 file changed, 40 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 024a2bb146b2..7c01957a032d 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #define DRV_NAME "cros-typec-switch" @@ -23,7 +22,6 @@ /* Handles and other relevant data required for each port's switches. */ struct cros_typec_port { int port_num; - struct typec_mux_dev *mode_switch; struct typec_retimer *retimer; struct cros_typec_switch_data *sdata; }; @@ -149,15 +147,6 @@ static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int po return -ETIMEDOUT; } -static int cros_typec_mode_switch_set(struct typec_mux_dev *mode_switch, - struct typec_mux_state *state) -{ - struct cros_typec_port *port = typec_mux_get_drvdata(mode_switch); - - /* Mode switches have index 0. */ - return cros_typec_configure_mux(port->sdata, port->port_num, 0, state->mode, state->alt); -} - static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { struct cros_typec_port *port = typec_retimer_get_drvdata(retimer); @@ -174,27 +163,9 @@ static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) if (!sdata->ports[i]) continue; typec_retimer_unregister(sdata->ports[i]->retimer); - typec_mux_unregister(sdata->ports[i]->mode_switch); } } -static int cros_typec_register_mode_switch(struct cros_typec_port *port, - struct fwnode_handle *fwnode) -{ - struct typec_mux_desc mode_switch_desc = { - .fwnode = fwnode, - .drvdata = port, - .name = fwnode_get_name(fwnode), - .set = cros_typec_mode_switch_set, - }; - - port->mode_switch = typec_mux_register(port->sdata->dev, &mode_switch_desc); - if (IS_ERR(port->mode_switch)) - return PTR_ERR(port->mode_switch); - - return 0; -} - static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) { struct typec_retimer_desc retimer_desc = { @@ -264,17 +235,6 @@ static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) } dev_dbg(dev, "Retimer switch registered for index %llu\n", index); - - if (!fwnode_property_read_bool(fwnode, "mode-switch")) - continue; - - ret = cros_typec_register_mode_switch(port, fwnode); - if (ret) { - dev_err(dev, "Mode switch register failed\n"); - goto err_switch; - } - - dev_dbg(dev, "Mode switch registered for index %llu\n", index); } return 0; -- cgit v1.2.3 From e5b25ca94c294545a677905a1fb2c940eee84c67 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:56:53 +0200 Subject: Revert "platform/chrome: cros_typec_switch: Add event check" This reverts commit bb53ad958012f5a8d88b3b7159c22b3b877601bb. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_typec_switch.c | 72 +---------------------------- 1 file changed, 2 insertions(+), 70 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 7c01957a032d..b50ecedce662 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -7,8 +7,6 @@ */ #include -#include -#include #include #include #include @@ -65,40 +63,6 @@ static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *al return ret; } -static int cros_typec_send_clear_event(struct cros_typec_switch_data *sdata, int port_num, - u32 events_mask) -{ - struct ec_params_typec_control req = { - .port = port_num, - .command = TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, - .clear_events_mask = events_mask, - }; - - return cros_ec_command(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, - sizeof(req), NULL, 0); -} - -static bool cros_typec_check_event(struct cros_typec_switch_data *sdata, int port_num, u32 mask) -{ - struct ec_response_typec_status resp; - struct ec_params_typec_status req = { - .port = port_num, - }; - int ret; - - ret = cros_ec_command(sdata->ec, 0, EC_CMD_TYPEC_STATUS, &req, sizeof(req), - &resp, sizeof(resp)); - if (ret < 0) { - dev_warn(sdata->dev, "EC_CMD_TYPEC_STATUS failed for port: %d\n", port_num); - return false; - } - - if (resp.events & mask) - return true; - - return false; -} - /* * The Chrome EC treats both mode-switches and retimers as "muxes" for the purposes of the * host command API. This common function configures and verifies the retimer/mode-switch @@ -107,44 +71,12 @@ static bool cros_typec_check_event(struct cros_typec_switch_data *sdata, int por static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index, unsigned long mode, struct typec_altmode *alt) { - unsigned long end; - u32 event_mask; - u8 mux_state; - int ret; - - ret = cros_typec_get_mux_state(mode, alt); - if (ret < 0) - return ret; - mux_state = (u8)ret; + int ret = cros_typec_get_mux_state(mode, alt); - /* Clear any old mux set done event. */ - if (index == 0) - event_mask = PD_STATUS_EVENT_MUX_0_SET_DONE; - else - event_mask = PD_STATUS_EVENT_MUX_1_SET_DONE; - - ret = cros_typec_send_clear_event(sdata, port_num, event_mask); - if (ret < 0) - return ret; - - /* Send the set command. */ - ret = cros_typec_cmd_mux_set(sdata, port_num, index, mux_state); if (ret < 0) return ret; - /* Check for the mux set done event. */ - end = jiffies + msecs_to_jiffies(1000); - do { - if (cros_typec_check_event(sdata, port_num, event_mask)) - return 0; - - usleep_range(500, 1000); - } while (time_before(jiffies, end)); - - dev_err(sdata->dev, "Timed out waiting for mux set done on index: %d, state: %d\n", - index, mux_state); - - return -ETIMEDOUT; + return cros_typec_cmd_mux_set(sdata, port_num, index, (u8)ret); } static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) -- cgit v1.2.3 From 182da120f21277e36f4de6d510290b112438bb2d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:57:00 +0200 Subject: Revert "platform/chrome: cros_typec_switch: Set EC retimer" This reverts commit 34f375f0fdf67f8804142fa37a28e73426d4c1df. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_typec_switch.c | 56 +---------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index b50ecedce662..0d319e315d57 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -9,10 +9,7 @@ #include #include #include -#include #include -#include -#include #include #define DRV_NAME "cros-typec-switch" @@ -31,60 +28,9 @@ struct cros_typec_switch_data { struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; }; -static int cros_typec_cmd_mux_set(struct cros_typec_switch_data *sdata, int port_num, u8 index, - u8 state) -{ - struct typec_usb_mux_set params = { - .mux_index = index, - .mux_flags = state, - }; - - struct ec_params_typec_control req = { - .port = port_num, - .command = TYPEC_CONTROL_COMMAND_USB_MUX_SET, - .mux_params = params, - }; - - return cros_ec_command(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, - sizeof(req), NULL, 0); -} - -static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *alt) -{ - int ret = -EOPNOTSUPP; - - if (mode == TYPEC_STATE_SAFE) - ret = USB_PD_MUX_SAFE_MODE; - else if (mode == TYPEC_STATE_USB) - ret = USB_PD_MUX_USB_ENABLED; - else if (alt && alt->svid == USB_TYPEC_DP_SID) - ret = USB_PD_MUX_DP_ENABLED; - - return ret; -} - -/* - * The Chrome EC treats both mode-switches and retimers as "muxes" for the purposes of the - * host command API. This common function configures and verifies the retimer/mode-switch - * according to the provided setting. - */ -static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index, - unsigned long mode, struct typec_altmode *alt) -{ - int ret = cros_typec_get_mux_state(mode, alt); - - if (ret < 0) - return ret; - - return cros_typec_cmd_mux_set(sdata, port_num, index, (u8)ret); -} - static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { - struct cros_typec_port *port = typec_retimer_get_drvdata(retimer); - - /* Retimers have index 1. */ - return cros_typec_configure_mux(port->sdata, port->port_num, 1, state->mode, state->alt); + return 0; } static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) -- cgit v1.2.3 From 1583afd4b0ef45ebdddb36f9dd390d96915a7f4f Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:57:25 +0200 Subject: Revert "platform/chrome: cros_typec_switch: Add ACPI Kconfig dep" This reverts commit 88a15fbb47db483d06b12b1ae69f114b96361a96. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 9d4fc505fa25..c62a514a087f 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -267,7 +267,7 @@ config CHROMEOS_PRIVACY_SCREEN config CROS_TYPEC_SWITCH tristate "ChromeOS EC Type-C Switch Control" - depends on MFD_CROS_EC_DEV && TYPEC && ACPI + depends on MFD_CROS_EC_DEV && TYPEC default MFD_CROS_EC_DEV help If you say Y here, you get support for configuring the Chrome OS EC Type C -- cgit v1.2.3 From 3d3e9b0db608608c049d286418f0c62c592ad280 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 19 Jul 2022 13:57:29 +0200 Subject: Revert "platform/chrome: cros_typec_switch: Add switch driver" This reverts commit e54369058f3da181fcc4c893f224a0c5a8a526b6. The chrome platform driver changes need to come in through the platform tree due to some api changes that showed up there that cause build errors in linux-next Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220719160821.5e68e30b@oak.ozlabs.ibm.com Cc: Prashant Malani Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 - drivers/platform/chrome/Kconfig | 11 -- drivers/platform/chrome/Makefile | 1 - drivers/platform/chrome/cros_typec_switch.c | 170 ---------------------------- 4 files changed, 183 deletions(-) delete mode 100644 drivers/platform/chrome/cros_typec_switch.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 1d254110c354..8e44c1fa9b90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4825,7 +4825,6 @@ M: Prashant Malani L: chrome-platform@lists.linux.dev S: Maintained F: drivers/platform/chrome/cros_ec_typec.c -F: drivers/platform/chrome/cros_typec_switch.c CHROMEOS EC USB PD NOTIFY DRIVER M: Prashant Malani diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index c62a514a087f..717299cbccac 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -265,17 +265,6 @@ config CHROMEOS_PRIVACY_SCREEN this should probably always be built into the kernel to avoid or minimize drm probe deferral. -config CROS_TYPEC_SWITCH - tristate "ChromeOS EC Type-C Switch Control" - depends on MFD_CROS_EC_DEV && TYPEC - default MFD_CROS_EC_DEV - help - If you say Y here, you get support for configuring the Chrome OS EC Type C - muxes and retimers. - - To compile this driver as a module, choose M here: the module will be - called cros_typec_switch. - source "drivers/platform/chrome/wilco_ec/Kconfig" endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 0dcaf6a7ed27..52f5a2dde8b8 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -12,7 +12,6 @@ obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o obj-$(CONFIG_CROS_EC) += cros_ec.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o -obj-$(CONFIG_CROS_TYPEC_SWITCH) += cros_typec_switch.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c deleted file mode 100644 index 0d319e315d57..000000000000 --- a/drivers/platform/chrome/cros_typec_switch.c +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright 2022 Google LLC - * - * This driver provides the ability to configure Type C muxes and retimers which are controlled by - * the Chrome OS EC. - */ - -#include -#include -#include -#include -#include - -#define DRV_NAME "cros-typec-switch" - -/* Handles and other relevant data required for each port's switches. */ -struct cros_typec_port { - int port_num; - struct typec_retimer *retimer; - struct cros_typec_switch_data *sdata; -}; - -/* Driver-specific data. */ -struct cros_typec_switch_data { - struct device *dev; - struct cros_ec_device *ec; - struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; -}; - -static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) -{ - return 0; -} - -static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) -{ - int i; - - for (i = 0; i < EC_USB_PD_MAX_PORTS; i++) { - if (!sdata->ports[i]) - continue; - typec_retimer_unregister(sdata->ports[i]->retimer); - } -} - -static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) -{ - struct typec_retimer_desc retimer_desc = { - .fwnode = fwnode, - .drvdata = port, - .name = fwnode_get_name(fwnode), - .set = cros_typec_retimer_set, - }; - - port->retimer = typec_retimer_register(port->sdata->dev, &retimer_desc); - if (IS_ERR(port->retimer)) - return PTR_ERR(port->retimer); - - return 0; -} - -static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) -{ - struct cros_typec_port *port = NULL; - struct device *dev = sdata->dev; - struct fwnode_handle *fwnode; - struct acpi_device *adev; - unsigned long long index; - int ret = 0; - int nports; - - nports = device_get_child_node_count(dev); - if (nports == 0) { - dev_err(dev, "No switch devices found.\n"); - return -ENODEV; - } - - device_for_each_child_node(dev, fwnode) { - port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); - if (!port) { - ret = -ENOMEM; - goto err_switch; - } - - adev = to_acpi_device_node(fwnode); - if (!adev) { - dev_err(fwnode->dev, "Couldn't get ACPI device handle\n"); - ret = -ENODEV; - goto err_switch; - } - - ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index); - if (ACPI_FAILURE(ret)) { - dev_err(fwnode->dev, "_ADR wasn't evaluated\n"); - ret = -ENODATA; - goto err_switch; - } - - if (index < 0 || index >= EC_USB_PD_MAX_PORTS) { - dev_err(fwnode->dev, "Invalid port index number: %llu", index); - ret = -EINVAL; - goto err_switch; - } - port->sdata = sdata; - port->port_num = index; - sdata->ports[index] = port; - - ret = cros_typec_register_retimer(port, fwnode); - if (ret) { - dev_err(dev, "Retimer switch register failed\n"); - goto err_switch; - } - - dev_dbg(dev, "Retimer switch registered for index %llu\n", index); - } - - return 0; -err_switch: - cros_typec_unregister_switches(sdata); - return ret; -} - -static int cros_typec_switch_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct cros_typec_switch_data *sdata; - - sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL); - if (!sdata) - return -ENOMEM; - - sdata->dev = dev; - sdata->ec = dev_get_drvdata(pdev->dev.parent); - - platform_set_drvdata(pdev, sdata); - - return cros_typec_register_switches(sdata); -} - -static int cros_typec_switch_remove(struct platform_device *pdev) -{ - struct cros_typec_switch_data *sdata = platform_get_drvdata(pdev); - - cros_typec_unregister_switches(sdata); - return 0; -} - -#ifdef CONFIG_ACPI -static const struct acpi_device_id cros_typec_switch_acpi_id[] = { - { "GOOG001A", 0 }, - {} -}; -MODULE_DEVICE_TABLE(acpi, cros_typec_switch_acpi_id); -#endif - -static struct platform_driver cros_typec_switch_driver = { - .driver = { - .name = DRV_NAME, - .acpi_match_table = ACPI_PTR(cros_typec_switch_acpi_id), - }, - .probe = cros_typec_switch_probe, - .remove = cros_typec_switch_remove, -}; - -module_platform_driver(cros_typec_switch_driver); - -MODULE_AUTHOR("Prashant Malani "); -MODULE_DESCRIPTION("Chrome OS EC Type C Switch control"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 3fb975e66ce2c6eb50e8ad1963c19bee20302757 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jul 2022 09:58:37 +0200 Subject: USB: cdc-acm: use CDC control-line defines Use the new CDC control-line defines. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20220725075841.1187-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 18 +++++++++--------- drivers/usb/class/cdc-acm.h | 7 ------- 2 files changed, 9 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index fedf3065670e..741c8cd213cc 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -658,7 +658,7 @@ static void acm_port_dtr_rts(struct tty_port *port, int raise) int res; if (raise) - val = ACM_CTRL_DTR | ACM_CTRL_RTS; + val = USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS; else val = 0; @@ -903,8 +903,8 @@ static int acm_tty_tiocmget(struct tty_struct *tty) { struct acm *acm = tty->driver_data; - return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) | - (acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) | + return (acm->ctrlout & USB_CDC_CTRL_DTR ? TIOCM_DTR : 0) | + (acm->ctrlout & USB_CDC_CTRL_RTS ? TIOCM_RTS : 0) | (acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) | (acm->ctrlin & ACM_CTRL_RI ? TIOCM_RI : 0) | (acm->ctrlin & ACM_CTRL_DCD ? TIOCM_CD : 0) | @@ -918,10 +918,10 @@ static int acm_tty_tiocmset(struct tty_struct *tty, unsigned int newctrl; newctrl = acm->ctrlout; - set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | - (set & TIOCM_RTS ? ACM_CTRL_RTS : 0); - clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | - (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0); + set = (set & TIOCM_DTR ? USB_CDC_CTRL_DTR : 0) | + (set & TIOCM_RTS ? USB_CDC_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? USB_CDC_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? USB_CDC_CTRL_RTS : 0); newctrl = (newctrl & ~clear) | set; @@ -1068,9 +1068,9 @@ static void acm_tty_set_termios(struct tty_struct *tty, if (C_BAUD(tty) == B0) { newline.dwDTERate = acm->line.dwDTERate; - newctrl &= ~ACM_CTRL_DTR; + newctrl &= ~USB_CDC_CTRL_DTR; } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { - newctrl |= ACM_CTRL_DTR; + newctrl |= USB_CDC_CTRL_DTR; } if (newctrl != acm->ctrlout) diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index d26ecd15be60..da7e8b8aaf28 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -22,13 +22,6 @@ #define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) -/* - * Output control lines. - */ - -#define ACM_CTRL_DTR 0x01 -#define ACM_CTRL_RTS 0x02 - /* * Input control lines and line errors. */ -- cgit v1.2.3 From 7333c87f7829dee2545fa5ba6476e971973263a0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jul 2022 09:58:38 +0200 Subject: USB: cdc-acm: use CDC serial-state defines Use the new CDC serial-state defines. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20220725075841.1187-5-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 24 ++++++++++++------------ drivers/usb/class/cdc-acm.h | 13 ------------- 2 files changed, 12 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 741c8cd213cc..483bcb1213f7 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -311,7 +311,7 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) dev_dbg(&acm->control->dev, "%s - serial state: 0x%x\n", __func__, newctrl); - if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { + if (!acm->clocal && (acm->ctrlin & ~newctrl & USB_CDC_SERIAL_STATE_DCD)) { dev_dbg(&acm->control->dev, "%s - calling hangup\n", __func__); tty_port_tty_hangup(&acm->port, false); @@ -322,25 +322,25 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) acm->ctrlin = newctrl; acm->oldcount = acm->iocount; - if (difference & ACM_CTRL_DSR) + if (difference & USB_CDC_SERIAL_STATE_DSR) acm->iocount.dsr++; - if (difference & ACM_CTRL_DCD) + if (difference & USB_CDC_SERIAL_STATE_DCD) acm->iocount.dcd++; - if (newctrl & ACM_CTRL_BRK) { + if (newctrl & USB_CDC_SERIAL_STATE_BREAK) { acm->iocount.brk++; tty_insert_flip_char(&acm->port, 0, TTY_BREAK); } - if (newctrl & ACM_CTRL_RI) + if (newctrl & USB_CDC_SERIAL_STATE_RING_SIGNAL) acm->iocount.rng++; - if (newctrl & ACM_CTRL_FRAMING) + if (newctrl & USB_CDC_SERIAL_STATE_FRAMING) acm->iocount.frame++; - if (newctrl & ACM_CTRL_PARITY) + if (newctrl & USB_CDC_SERIAL_STATE_PARITY) acm->iocount.parity++; - if (newctrl & ACM_CTRL_OVERRUN) + if (newctrl & USB_CDC_SERIAL_STATE_OVERRUN) acm->iocount.overrun++; spin_unlock_irqrestore(&acm->read_lock, flags); - if (newctrl & ACM_CTRL_BRK) + if (newctrl & USB_CDC_SERIAL_STATE_BREAK) tty_flip_buffer_push(&acm->port); if (difference) @@ -905,9 +905,9 @@ static int acm_tty_tiocmget(struct tty_struct *tty) return (acm->ctrlout & USB_CDC_CTRL_DTR ? TIOCM_DTR : 0) | (acm->ctrlout & USB_CDC_CTRL_RTS ? TIOCM_RTS : 0) | - (acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) | - (acm->ctrlin & ACM_CTRL_RI ? TIOCM_RI : 0) | - (acm->ctrlin & ACM_CTRL_DCD ? TIOCM_CD : 0) | + (acm->ctrlin & USB_CDC_SERIAL_STATE_DSR ? TIOCM_DSR : 0) | + (acm->ctrlin & USB_CDC_SERIAL_STATE_RING_SIGNAL ? TIOCM_RI : 0) | + (acm->ctrlin & USB_CDC_SERIAL_STATE_DCD ? TIOCM_CD : 0) | TIOCM_CTS; } diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index da7e8b8aaf28..759ac15631d3 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -22,19 +22,6 @@ #define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) -/* - * Input control lines and line errors. - */ - -#define ACM_CTRL_DCD 0x01 -#define ACM_CTRL_DSR 0x02 -#define ACM_CTRL_BRK 0x04 -#define ACM_CTRL_RI 0x08 - -#define ACM_CTRL_FRAMING 0x10 -#define ACM_CTRL_PARITY 0x20 -#define ACM_CTRL_OVERRUN 0x40 - /* * Internal driver structures. */ -- cgit v1.2.3 From 0752670685c47203484731bdfae9607f4da735c4 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jul 2022 09:58:39 +0200 Subject: staging: gdm724x: drop unused CDC defines This driver has a copy of some of the CDC defines but which are currently unused. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20220725075841.1187-6-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/staging/gdm724x/gdm_tty.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers') diff --git a/drivers/staging/gdm724x/gdm_tty.c b/drivers/staging/gdm724x/gdm_tty.c index 04df6f9f5403..cc6d80554c98 100644 --- a/drivers/staging/gdm724x/gdm_tty.c +++ b/drivers/staging/gdm724x/gdm_tty.c @@ -17,12 +17,6 @@ #define GDM_TTY_MAJOR 0 #define GDM_TTY_MINOR 32 -#define ACM_CTRL_DTR 0x01 -#define ACM_CTRL_RTS 0x02 -#define ACM_CTRL_DSR 0x02 -#define ACM_CTRL_RI 0x08 -#define ACM_CTRL_DCD 0x01 - #define WRITE_SIZE 2048 #define MUX_TX_MAX_SIZE 2048 -- cgit v1.2.3 From f4beed1e91326630a4ec3fb2e209f06a7ca2e983 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jul 2022 09:58:40 +0200 Subject: USB: gadget: f_acm: use CDC defines Use the new CDC control-line and serial-state defines. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20220725075841.1187-7-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_acm.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 411eb489e0ff..cb523f118f04 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -57,18 +57,8 @@ struct f_acm { /* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */ u16 port_handshake_bits; -#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ -#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ - /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */ u16 serial_state; -#define ACM_CTRL_OVERRUN (1 << 6) -#define ACM_CTRL_PARITY (1 << 5) -#define ACM_CTRL_FRAMING (1 << 4) -#define ACM_CTRL_RI (1 << 3) -#define ACM_CTRL_BRK (1 << 2) -#define ACM_CTRL_DSR (1 << 1) -#define ACM_CTRL_DCD (1 << 0) }; static inline struct f_acm *func_to_acm(struct usb_function *f) @@ -387,7 +377,7 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) value = 0; /* FIXME we should not allow data to flow until the - * host sets the ACM_CTRL_DTR bit; and when it clears + * host sets the USB_CDC_CTRL_DTR bit; and when it clears * that bit, we should return to that no-flow state. */ acm->port_handshake_bits = w_value; @@ -585,7 +575,7 @@ static void acm_connect(struct gserial *port) { struct f_acm *acm = port_to_acm(port); - acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + acm->serial_state |= USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_DCD; acm_notify_serial_state(acm); } @@ -593,7 +583,7 @@ static void acm_disconnect(struct gserial *port) { struct f_acm *acm = port_to_acm(port); - acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + acm->serial_state &= ~(USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_DCD); acm_notify_serial_state(acm); } @@ -603,9 +593,9 @@ static int acm_send_break(struct gserial *port, int duration) u16 state; state = acm->serial_state; - state &= ~ACM_CTRL_BRK; + state &= ~USB_CDC_SERIAL_STATE_BREAK; if (duration) - state |= ACM_CTRL_BRK; + state |= USB_CDC_SERIAL_STATE_BREAK; acm->serial_state = state; return acm_notify_serial_state(acm); -- cgit v1.2.3 From d5e22360e907dbf570116c3c08be0d3e5be43a23 Mon Sep 17 00:00:00 2001 From: Yan Xinyu Date: Mon, 25 Jul 2022 09:58:41 +0200 Subject: USB: serial: usb_wwan: replace DTR/RTS magic numbers with macros The usb_wwan_send_setup function generates DTR/RTS signals in compliance with CDC ACM standard. This patch changes magic numbers in this function to equivalent macros. Link: https://lore.kernel.org/r/20220722085040.704885-1-sdlyyxy@bupt.edu.cn [ johan: use the new CDC control-line defines ] Signed-off-by: Yan Xinyu Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20220725075841.1187-8-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb_wwan.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index dab38b63eaf7..6129a6e26f2c 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "usb-wwan.h" @@ -48,9 +49,9 @@ static int usb_wwan_send_setup(struct usb_serial_port *port) portdata = usb_get_serial_port_data(port); if (portdata->dtr_state) - val |= 0x01; + val |= USB_CDC_CTRL_DTR; if (portdata->rts_state) - val |= 0x02; + val |= USB_CDC_CTRL_RTS; ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber; @@ -59,8 +60,9 @@ static int usb_wwan_send_setup(struct usb_serial_port *port) return res; res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), - 0x22, 0x21, val, ifnum, NULL, 0, - USB_CTRL_SET_TIMEOUT); + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + val, ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT); usb_autopm_put_interface(port->serial->interface); -- cgit v1.2.3 From 688ee1d1785c1359f9040f615dd8e6054962bce2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jul 2022 10:44:57 +0200 Subject: USB: serial: fix tty-port initialized comments Fix up the tty-port initialized comments which got truncated and obfuscated when replacing the old ASYNCB_INITIALIZED flag. Fixes: d41861ca19c9 ("tty: Replace ASYNC_INITIALIZED bit and update atomically") Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/sierra.c | 3 ++- drivers/usb/serial/usb-serial.c | 2 +- drivers/usb/serial/usb_wwan.c | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 525c7f888c90..353b2549eaa8 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -735,7 +735,8 @@ static void sierra_close(struct usb_serial_port *port) /* * Need to take susp_lock to make sure port is not already being - * resumed, but no need to hold it due to initialized + * resumed, but no need to hold it due to the tty-port initialized + * flag. */ spin_lock_irq(&intfdata->susp_lock); if (--intfdata->open_ports == 0) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 24101bd7fcad..e35bea2235c1 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -295,7 +295,7 @@ static int serial_open(struct tty_struct *tty, struct file *filp) * * Shut down a USB serial port. Serialized against activate by the * tport mutex and kept to matching open/close pairs - * of calls by the initialized flag. + * of calls by the tty-port initialized flag. * * Not called if tty is console. */ diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index dab38b63eaf7..cc81ab7ef4da 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -388,7 +388,8 @@ void usb_wwan_close(struct usb_serial_port *port) /* * Need to take susp_lock to make sure port is not already being - * resumed, but no need to hold it due to initialized + * resumed, but no need to hold it due to the tty-port initialized + * flag. */ spin_lock_irq(&intfdata->susp_lock); if (--intfdata->open_ports == 0) -- cgit v1.2.3 From ff50a91ee5e6db357c900dce280a7129dc9e363c Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 15 Jul 2022 23:56:57 +0100 Subject: usb: phy: remove redundant store to variable var after & operation There is no need to store the result of the & operation back to the variable var. The store is redundant, replace &= with just &. Cleans up clang scan warning: drivers/usb/phy/phy-keystone.c:62:5: warning: Although the value stored to 'val' is used in the enclosing expression, the value is never actually read from 'val' [deadcode.DeadStores] Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20220715225657.353828-1-colin.i.king@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-keystone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/phy/phy-keystone.c b/drivers/usb/phy/phy-keystone.c index 358d05cb643d..f75912279b39 100644 --- a/drivers/usb/phy/phy-keystone.c +++ b/drivers/usb/phy/phy-keystone.c @@ -59,7 +59,7 @@ static void keystone_usbphy_shutdown(struct usb_phy *phy) val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK); keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK, - val &= ~PHY_REF_SSP_EN); + val & ~PHY_REF_SSP_EN); } static int keystone_usbphy_probe(struct platform_device *pdev) -- cgit v1.2.3 From cfed201e2db273562de152d22b74f74dee77e301 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 22 Jul 2022 09:25:42 +0300 Subject: usb: typec: anx7411: Fix an array out of bounds This should be ARRAY_SIZE() instead of sizeof(). ARRAY_SIZE is 4 and sizeof is 8. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Reviewed-by: Xin Ji Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/YtpC5s4/AD8vFz+X@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index b990376991f8..4f7a5cc968d0 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -992,7 +992,7 @@ static int anx7411_register_i2c_dummy_clients(struct anx7411_data *ctx, int i; u8 spi_addr; - for (i = 0; i < sizeof(anx7411_i2c_addr); i++) { + for (i = 0; i < ARRAY_SIZE(anx7411_i2c_addr); i++) { if (client->addr == (anx7411_i2c_addr[i].tcpc_address >> 1)) { spi_addr = anx7411_i2c_addr[i].spi_address >> 1; ctx->spi_client = i2c_new_dummy_device(client->adapter, -- cgit v1.2.3 From 9310bd4bf20ff9ab180a0158f917b1d9af3247dc Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 22 Jul 2022 09:29:07 +0300 Subject: usb: typec: anx7411: fix error checking in anx7411_get_gpio_irq() This is a minor bug which means that certain error messages are not printed. The devm_gpiod_get_optional() function can return either error pointers or NULL. It returns error pointers if there is an allocation failure, or a similar issue. It returns NULL if no GPIO was assigned to the requested function. Print an error in either case. The gpiod_to_irq() function never returns zero. It either returns a positive IRQ number or a negative error code. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Reviewed-by: Xin Ji Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/YtpDs8VsWIbl/Smd@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 4f7a5cc968d0..311b56aaea9f 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1326,13 +1326,13 @@ static void anx7411_get_gpio_irq(struct anx7411_data *ctx) struct device *dev = &ctx->tcpc_client->dev; ctx->intp_gpiod = devm_gpiod_get_optional(dev, "interrupt", GPIOD_IN); - if (!ctx->intp_gpiod) { + if (IS_ERR_OR_NULL(ctx->intp_gpiod)) { dev_err(dev, "no interrupt gpio property\n"); return; } ctx->intp_irq = gpiod_to_irq(ctx->intp_gpiod); - if (!ctx->intp_irq) + if (ctx->intp_irq < 0) dev_err(dev, "failed to get GPIO IRQ\n"); } -- cgit v1.2.3 From 0c25bab1abb43dbe2662e88f56e157ccac76f8c2 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 22 Jul 2022 09:29:52 +0300 Subject: usb: typec: anx7411: use semi-colons instead of commas Semi colons and commas are equivalent in this context but semi-colons are better style. Reviewed-by: Xin Ji Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/YtpD4MKBa43higNc@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 311b56aaea9f..18a6a6a8b9eb 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1421,12 +1421,12 @@ static int anx7411_psy_register(struct anx7411_data *ctx) psy_desc->type = POWER_SUPPLY_TYPE_USB; psy_desc->usb_types = anx7411_psy_usb_types; psy_desc->num_usb_types = ARRAY_SIZE(anx7411_psy_usb_types); - psy_desc->properties = anx7411_psy_props, - psy_desc->num_properties = ARRAY_SIZE(anx7411_psy_props), + psy_desc->properties = anx7411_psy_props; + psy_desc->num_properties = ARRAY_SIZE(anx7411_psy_props); - psy_desc->get_property = anx7411_psy_get_prop, - psy_desc->set_property = anx7411_psy_set_prop, - psy_desc->property_is_writeable = anx7411_psy_prop_writeable, + psy_desc->get_property = anx7411_psy_get_prop; + psy_desc->set_property = anx7411_psy_set_prop; + psy_desc->property_is_writeable = anx7411_psy_prop_writeable; ctx->usb_type = POWER_SUPPLY_USB_TYPE_C; ctx->psy = devm_power_supply_register(ctx->dev, psy_desc, &psy_cfg); -- cgit v1.2.3 From 67fb0cc02f89049e532b008faa35818c82aa0d62 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Thu, 21 Jul 2022 15:11:59 +0800 Subject: usb: typec: anx7411: Fix wrong pointer passed to PTR_ERR() It should be 'ctx->typec.amode[i]' passed to PTR_ERR() when typec_partner_register_altmode() failed. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20220721071201.269344-1-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 18a6a6a8b9eb..e16c2d511e8f 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -549,6 +549,7 @@ static int anx7411_typec_register_altmode(struct anx7411_data *ctx, { struct device *dev = &ctx->spi_client->dev; struct typec_altmode_desc desc; + int err; int i; desc.svid = svid; @@ -569,8 +570,9 @@ static int anx7411_typec_register_altmode(struct anx7411_data *ctx, &desc); if (IS_ERR(ctx->typec.amode[i])) { dev_err(dev, "failed to register altmode\n"); + err = PTR_ERR(ctx->typec.amode[i]); ctx->typec.amode[i] = NULL; - return PTR_ERR(ctx->typec.amode); + return err; } return 0; -- cgit v1.2.3 From 5cda657679f8fcb2896e4ac0aa8e231f12f9fb04 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Thu, 21 Jul 2022 15:12:00 +0800 Subject: usb: typec: anx7411: Fix return value check in anx7411_register_i2c_dummy_clients() If i2c_new_dummy_device() fails, it never return NULL pointer, replace NULL test with IS_ERR() to fix it. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20220721071201.269344-2-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index e16c2d511e8f..7692e26911b7 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -999,7 +999,7 @@ static int anx7411_register_i2c_dummy_clients(struct anx7411_data *ctx, spi_addr = anx7411_i2c_addr[i].spi_address >> 1; ctx->spi_client = i2c_new_dummy_device(client->adapter, spi_addr); - if (ctx->spi_client) + if (!IS_ERR(ctx->spi_client)) return 0; } } -- cgit v1.2.3 From d183a57cad920087a770c45721e98f45feea0fde Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Thu, 21 Jul 2022 15:12:01 +0800 Subject: usb: typec: anx7411: Fix error return code in anx7411_i2c_probe() Add mising error return code when failed to get interrupt or failed to register psy. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20220721071201.269344-3-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 7692e26911b7..f8baa1e189b3 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1483,12 +1483,14 @@ static int anx7411_i2c_probe(struct i2c_client *client, if (!plat->intp_irq) { dev_err(dev, "fail to get interrupt IRQ\n"); + ret = -EINVAL; goto free_typec_port; } plat->dev = dev; plat->psy_online = ANX7411_PSY_OFFLINE; - if (anx7411_psy_register(plat)) { + ret = anx7411_psy_register(plat); + if (ret) { dev_err(dev, "register psy\n"); goto free_typec_port; } -- cgit v1.2.3 From 23bb7b49597139d38f4da9392df28a24229697b6 Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Fri, 22 Jul 2022 16:18:34 +0800 Subject: usb: typec: anx7411: fix passing zero to 'PTR_ERR' Fix anx7411_register_partner() warn: passing zero to 'PTR_ERR' Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Signed-off-by: Xin Ji Link: https://lore.kernel.org/r/20220722081836.3380885-1-xji@analogixsemi.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index f8baa1e189b3..c0f0842d443c 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -374,6 +374,7 @@ static int anx7411_register_partner(struct anx7411_data *ctx, int pd, int accessory) { struct typec_partner_desc desc; + struct typec_partner *partner; if (ctx->typec.partner) return 0; @@ -381,11 +382,11 @@ static int anx7411_register_partner(struct anx7411_data *ctx, desc.usb_pd = pd; desc.accessory = accessory; desc.identity = NULL; - ctx->typec.partner = typec_register_partner(ctx->typec.port, &desc); - if (IS_ERR(ctx->typec.partner)) { - ctx->typec.partner = NULL; - return PTR_ERR(ctx->typec.partner); - } + partner = typec_register_partner(ctx->typec.port, &desc); + if (IS_ERR(partner)) + return PTR_ERR(partner); + + ctx->typec.partner = partner; return 0; } -- cgit v1.2.3 From d7de14d74d6551f0d097430f9893ce82ad17e5b8 Mon Sep 17 00:00:00 2001 From: Alexey Sheplyakov Date: Fri, 22 Jul 2022 18:17:00 +0400 Subject: usb: xhci_plat_remove: avoid NULL dereference Since commit 4736ebd7fcaff1eb8481c140ba494962847d6e0a ("usb: host: xhci-plat: omit shared hcd if either root hub has no ports") xhci->shared_hcd can be NULL, which causes the following Oops on reboot: [ 710.124450] systemd-shutdown[1]: Rebooting. [ 710.298861] xhci-hcd xhci-hcd.2.auto: remove, state 4 [ 710.304217] usb usb3: USB disconnect, device number 1 [ 710.317441] xhci-hcd xhci-hcd.2.auto: USB bus 3 deregistered [ 710.323280] xhci-hcd xhci-hcd.2.auto: remove, state 1 [ 710.328401] usb usb2: USB disconnect, device number 1 [ 710.333515] usb 2-3: USB disconnect, device number 2 [ 710.467649] xhci-hcd xhci-hcd.2.auto: USB bus 2 deregistered [ 710.475450] Unable to handle kernel NULL pointer dereference at virtual address 00000000000003b8 [ 710.484425] Mem abort info: [ 710.487265] ESR = 0x0000000096000004 [ 710.491060] EC = 0x25: DABT (current EL), IL = 32 bits [ 710.496427] SET = 0, FnV = 0 [ 710.499525] EA = 0, S1PTW = 0 [ 710.502716] FSC = 0x04: level 0 translation fault [ 710.507648] Data abort info: [ 710.510577] ISV = 0, ISS = 0x00000004 [ 710.514462] CM = 0, WnR = 0 [ 710.517480] user pgtable: 4k pages, 48-bit VAs, pgdp=00000008b0050000 [ 710.523976] [00000000000003b8] pgd=0000000000000000, p4d=0000000000000000 [ 710.530961] Internal error: Oops: 96000004 [#1] PREEMPT SMP [ 710.536551] Modules linked in: rfkill input_leds snd_soc_simple_card snd_soc_simple_card_utils snd_soc_nau8822 designware_i2s snd_soc_core dw_hdmi_ahb_audio snd_pcm_dmaengine arm_ccn panfrost ac97_bus gpu_sched snd_pcm at24 fuse configfs sdhci_of_dwcmshc sdhci_pltfm sdhci nvme led_class mmc_core nvme_core bt1_pvt polynomial tp_serio snd_seq_midi snd_seq_midi_event snd_seq snd_timer snd_rawmidi snd_seq_device snd soundcore efivarfs ipv6 [ 710.575286] CPU: 7 PID: 1 Comm: systemd-shutdow Not tainted 5.19.0-rc7-00043-gfd8619f4fd54 #1 [ 710.583822] Hardware name: T-Platforms TF307-MB/BM1BM1-A, BIOS 5.6 07/06/2022 [ 710.590972] pstate: 40000005 (nZcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 710.597949] pc : usb_remove_hcd+0x34/0x1e4 [ 710.602067] lr : xhci_plat_remove+0x74/0x140 [ 710.606351] sp : ffff800009f3b7c0 [ 710.609674] x29: ffff800009f3b7c0 x28: ffff000800960040 x27: 0000000000000000 [ 710.616833] x26: ffff800008dc22a0 x25: 0000000000000000 x24: 0000000000000000 [ 710.623992] x23: 0000000000000000 x22: ffff000805465810 x21: ffff000805465800 [ 710.631149] x20: ffff000800f80000 x19: 0000000000000000 x18: ffffffffffffffff [ 710.638307] x17: ffff000805096000 x16: ffff00080633b800 x15: ffff000806537a1c [ 710.645465] x14: 0000000000000001 x13: 0000000000000000 x12: ffff00080378d6f0 [ 710.652621] x11: ffff00080041a900 x10: ffff800009b204e8 x9 : ffff8000088abaa4 [ 710.659779] x8 : ffff000800960040 x7 : ffff800009409000 x6 : 0000000000000001 [ 710.666936] x5 : ffff800009241000 x4 : ffff800009241440 x3 : 0000000000000000 [ 710.674094] x2 : ffff000800960040 x1 : ffff000800960040 x0 : 0000000000000000 [ 710.681251] Call trace: [ 710.683704] usb_remove_hcd+0x34/0x1e4 [ 710.687467] xhci_plat_remove+0x74/0x140 [ 710.691400] platform_remove+0x34/0x70 [ 710.695165] device_remove+0x54/0x90 [ 710.698753] device_release_driver_internal+0x200/0x270 [ 710.703992] device_release_driver+0x24/0x30 [ 710.708273] bus_remove_device+0xe0/0x16c [ 710.712293] device_del+0x178/0x390 [ 710.715797] platform_device_del.part.0+0x24/0x90 [ 710.720514] platform_device_unregister+0x30/0x50 [ 710.725232] dwc3_host_exit+0x20/0x30 [ 710.728907] dwc3_remove+0x174/0x1b0 [ 710.732494] platform_remove+0x34/0x70 [ 710.736254] device_remove+0x54/0x90 [ 710.739840] device_release_driver_internal+0x200/0x270 [ 710.745078] device_release_driver+0x24/0x30 [ 710.749359] bus_remove_device+0xe0/0x16c [ 710.753380] device_del+0x178/0x390 [ 710.756881] platform_device_del.part.0+0x24/0x90 [ 710.761598] platform_device_unregister+0x30/0x50 [ 710.766314] of_platform_device_destroy+0xe8/0x100 [ 710.771119] device_for_each_child_reverse+0x70/0xc0 [ 710.776099] of_platform_depopulate+0x48/0x90 [ 710.780468] __dwc3_of_simple_teardown+0x28/0xe0 [ 710.785099] dwc3_of_simple_shutdown+0x20/0x30 [ 710.789555] platform_shutdown+0x30/0x40 [ 710.793490] device_shutdown+0x138/0x32c [ 710.797425] __do_sys_reboot+0x1c4/0x2ac [ 710.801362] __arm64_sys_reboot+0x30/0x40 [ 710.805383] invoke_syscall+0x50/0x120 [ 710.809146] el0_svc_common.constprop.0+0x68/0x124 [ 710.813950] do_el0_svc+0x3c/0xcc [ 710.817275] el0_svc+0x60/0x12c [ 710.820428] el0t_64_sync_handler+0xc0/0x13c [ 710.824710] el0t_64_sync+0x18c/0x190 [ 710.828386] Code: a9025bf5 f942c420 f9001fe0 d2800000 (b943ba62) [ 710.834498] ---[ end trace 0000000000000000 ]--- [ 710.875958] pstore: crypto_comp_compress failed, ret = -22! [ 710.895047] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b [ 710.902757] Kernel Offset: disabled [ 710.906255] CPU features: 0x800,00004811,00001082 [ 710.910971] Memory Limit: none [ 710.927474] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b ]--- To avoid the problem check for NULL in usb_remove_hcd. Fixes: 4736ebd7fcaf ("usb: host: xhci-plat: omit shared hcd if either root hub has no ports") Signed-off-by: Alexey Sheplyakov Link: https://lore.kernel.org/r/20220722141700.1271439-1-asheplyakov@basealt.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 06eea8848ccc..41dcd41e550c 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -3033,9 +3033,15 @@ EXPORT_SYMBOL_GPL(usb_add_hcd); */ void usb_remove_hcd(struct usb_hcd *hcd) { - struct usb_device *rhdev = hcd->self.root_hub; + struct usb_device *rhdev; bool rh_registered; + if (!hcd) { + pr_debug("%s: hcd is NULL\n", __func__); + return; + } + rhdev = hcd->self.root_hub; + dev_info(hcd->self.controller, "remove, state %x\n", hcd->state); usb_get_dev(rhdev); -- cgit v1.2.3 From 26c6c2f8a907c9e3a2f24990552a4d77235791e6 Mon Sep 17 00:00:00 2001 From: Weitao Wang Date: Tue, 26 Jul 2022 15:49:18 +0800 Subject: USB: HCD: Fix URB giveback issue in tasklet function Usb core introduce the mechanism of giveback of URB in tasklet context to reduce hardware interrupt handling time. On some test situation(such as FIO with 4KB block size), when tasklet callback function called to giveback URB, interrupt handler add URB node to the bh->head list also. If check bh->head list again after finish all URB giveback of local_list, then it may introduce a "dynamic balance" between giveback URB and add URB to bh->head list. This tasklet callback function may not exit for a long time, which will cause other tasklet function calls to be delayed. Some real-time applications(such as KB and Mouse) will see noticeable lag. In order to prevent the tasklet function from occupying the cpu for a long time at a time, new URBS will not be added to the local_list even though the bh->head list is not empty. But also need to ensure the left URB giveback to be processed in time, so add a member high_prio for structure giveback_urb_bh to prioritize tasklet and schelule this tasklet again if bh->head list is not empty. At the same time, we are able to prioritize tasklet through structure member high_prio. So, replace the local high_prio_bh variable with this structure member in usb_hcd_giveback_urb. Fixes: 94dfd7edfd5c ("USB: HCD: support giveback of URB in tasklet context") Cc: stable Reviewed-by: Alan Stern Signed-off-by: Weitao Wang Link: https://lore.kernel.org/r/20220726074918.5114-1-WeitaoWang-oc@zhaoxin.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 26 +++++++++++++++----------- include/linux/usb/hcd.h | 1 + 2 files changed, 16 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 41dcd41e550c..a6a87c5d1b05 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1691,7 +1691,6 @@ static void usb_giveback_urb_bh(struct tasklet_struct *t) spin_lock_irq(&bh->lock); bh->running = true; - restart: list_replace_init(&bh->head, &local_list); spin_unlock_irq(&bh->lock); @@ -1705,10 +1704,17 @@ static void usb_giveback_urb_bh(struct tasklet_struct *t) bh->completing_ep = NULL; } - /* check if there are new URBs to giveback */ + /* + * giveback new URBs next time to prevent this function + * from not exiting for a long time. + */ spin_lock_irq(&bh->lock); - if (!list_empty(&bh->head)) - goto restart; + if (!list_empty(&bh->head)) { + if (bh->high_prio) + tasklet_hi_schedule(&bh->bh); + else + tasklet_schedule(&bh->bh); + } bh->running = false; spin_unlock_irq(&bh->lock); } @@ -1737,7 +1743,7 @@ static void usb_giveback_urb_bh(struct tasklet_struct *t) void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) { struct giveback_urb_bh *bh; - bool running, high_prio_bh; + bool running; /* pass status to tasklet via unlinked */ if (likely(!urb->unlinked)) @@ -1748,13 +1754,10 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) return; } - if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe)) { + if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe)) bh = &hcd->high_prio_bh; - high_prio_bh = true; - } else { + else bh = &hcd->low_prio_bh; - high_prio_bh = false; - } spin_lock(&bh->lock); list_add_tail(&urb->urb_list, &bh->head); @@ -1763,7 +1766,7 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) if (running) ; - else if (high_prio_bh) + else if (bh->high_prio) tasklet_hi_schedule(&bh->bh); else tasklet_schedule(&bh->bh); @@ -2959,6 +2962,7 @@ int usb_add_hcd(struct usb_hcd *hcd, /* initialize tasklets */ init_giveback_urb_bh(&hcd->high_prio_bh); + hcd->high_prio_bh.high_prio = true; init_giveback_urb_bh(&hcd->low_prio_bh); /* enable irqs just before we start the controller, diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 2c1fc9212cf2..98d1921f02b1 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -66,6 +66,7 @@ struct giveback_urb_bh { bool running; + bool high_prio; spinlock_t lock; struct list_head head; struct tasklet_struct bh; -- cgit v1.2.3 From 2191c00855b03aa59c20e698be713d952d51fc18 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 21 Jul 2022 11:07:10 -0400 Subject: USB: gadget: Fix use-after-free Read in usb_udc_uevent() The syzbot fuzzer found a race between uevent callbacks and gadget driver unregistration that can cause a use-after-free bug: --------------------------------------------------------------- BUG: KASAN: use-after-free in usb_udc_uevent+0x11f/0x130 drivers/usb/gadget/udc/core.c:1732 Read of size 8 at addr ffff888078ce2050 by task udevd/2968 CPU: 1 PID: 2968 Comm: udevd Not tainted 5.19.0-rc4-next-20220628-syzkaller #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 06/29/2022 Call Trace: __dump_stack lib/dump_stack.c:88 [inline] dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106 print_address_description mm/kasan/report.c:317 [inline] print_report.cold+0x2ba/0x719 mm/kasan/report.c:433 kasan_report+0xbe/0x1f0 mm/kasan/report.c:495 usb_udc_uevent+0x11f/0x130 drivers/usb/gadget/udc/core.c:1732 dev_uevent+0x290/0x770 drivers/base/core.c:2424 --------------------------------------------------------------- The bug occurs because usb_udc_uevent() dereferences udc->driver but does so without acquiring the udc_lock mutex, which protects this field. If the gadget driver is unbound from the udc concurrently with uevent processing, the driver structure may be accessed after it has been deallocated. To prevent the race, we make sure that the routine holds the mutex around the racing accesses. Link: CC: stable@vger.kernel.org # fc274c1e9973 Reported-and-tested-by: syzbot+b0de012ceb1e2a97891b@syzkaller.appspotmail.com Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/YtlrnhHyrHsSky9m@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 7886497253cc..cafcf260394c 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1728,13 +1728,14 @@ static int usb_udc_uevent(struct device *dev, struct kobj_uevent_env *env) return ret; } - if (udc->driver) { + mutex_lock(&udc_lock); + if (udc->driver) ret = add_uevent_var(env, "USB_UDC_DRIVER=%s", udc->driver->function); - if (ret) { - dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n"); - return ret; - } + mutex_unlock(&udc_lock); + if (ret) { + dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n"); + return ret; } return 0; -- cgit v1.2.3 From 86c4bb4f124eec79423b90ef138402bf0b809bce Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:13:12 +0800 Subject: usb/atm: fix repeated words in comments Delete the redundant word 'was'. Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716131312.31767-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/ueagle-atm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 786299892c7f..5812f7ea7f90 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -9,7 +9,7 @@ * HISTORY : some part of the code was base on ueagle 1.3 BSD driver, * Damien Bergamini agree to put his code under a DUAL GPL/BSD license. * - * The rest of the code was was rewritten from scratch. + * The rest of the code was rewritten from scratch. */ #include -- cgit v1.2.3 From a7a9f4c0060e8f29a5fc2fe610575c3eabfc2253 Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:24:03 +0800 Subject: usb/core: fix repeated words in comments Delete the redundant word 'the'. Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716132403.35270-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 2 +- drivers/usb/core/usb.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index b87452e22835..7e7e119c253f 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1482,7 +1482,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) * @msg: Power Management message describing this state transition * * This is the central routine for resuming USB devices. It calls the - * the resume method for @udev and then calls the resume methods for all + * resume method for @udev and then calls the resume methods for all * the interface drivers in @udev. * * Autoresume requests originating from a child device or an interface diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2f71636af6e1..11b15d7b357a 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -801,7 +801,7 @@ EXPORT_SYMBOL_GPL(usb_intf_get_dma_device); * is simple: * * When locking both a device and its parent, always lock the - * the parent first. + * parent first. */ /** -- cgit v1.2.3 From 973939279a20c1368bbd818b49ad2443689cd926 Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:36:24 +0800 Subject: usb/host: fix repeated words in comments Delete the redundant word 'the'. Delete the redundant word 'to'. Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716133624.41994-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-grlib.c | 2 +- drivers/usb/host/uhci-hcd.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/uhci-grlib.c b/drivers/usb/host/uhci-grlib.c index 0a201a73b196..3ef6d52839e5 100644 --- a/drivers/usb/host/uhci-grlib.c +++ b/drivers/usb/host/uhci-grlib.c @@ -43,7 +43,7 @@ static int uhci_grlib_init(struct usb_hcd *hcd) uhci->rh_numports = uhci_count_ports(hcd); - /* Set up pointers to to generic functions */ + /* Set up pointers to generic functions */ uhci->reset_hc = uhci_generic_reset_hc; uhci->check_and_reset_hc = uhci_generic_check_and_reset_hc; /* No special actions need to be taken for the functions below */ diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 8ae5ccd26753..0688c3e5bfe2 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -314,7 +314,7 @@ struct uhci_td { * * There's a special skeleton QH for Isochronous QHs which never appears * on the schedule. Isochronous TDs go on the schedule before the - * the skeleton QHs. The hardware accesses them directly rather than + * skeleton QHs. The hardware accesses them directly rather than * through their QH, which is used only for bookkeeping purposes. * While the UHCI spec doesn't forbid the use of QHs for Isochronous, * it doesn't use them either. And the spec says that queues never -- cgit v1.2.3 From cd86f367eb6b5154230650c8f8c4003da0c12f54 Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:38:25 +0800 Subject: usb/image: fix repeated words in comments Delete the redundant word 'the'. Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716133825.43161-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/mdc800.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/image/mdc800.c b/drivers/usb/image/mdc800.c index fc0e22cc6fda..67f098579fb4 100644 --- a/drivers/usb/image/mdc800.c +++ b/drivers/usb/image/mdc800.c @@ -38,7 +38,7 @@ * * version 0.7.3 * bugfix : The mdc800->state field gets set to READY after the - * the disconnect function sets it to NOT_CONNECTED. This makes the + * disconnect function sets it to NOT_CONNECTED. This makes the * driver running like the camera is connected and causes some * hang ups. * -- cgit v1.2.3 From 676cb83b11c3648f513e84cf3a8616e9e1539d44 Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:41:05 +0800 Subject: usb/misc: fix repeated words in comments Delete the redundant word 'with'. Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716134105.44710-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbtest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 150090ee4ec1..ac0d75ac2d2f 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -2638,7 +2638,7 @@ usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param) * different busses) to use when testing, and allocate one thread per * test. So discovery is simplified, and we have no device naming issues. * - * Don't use these only as stress/load tests. Use them along with with + * Don't use these only as stress/load tests. Use them along with * other USB bus activity: plugging, unplugging, mousing, mp3 playback, * video capture, and so on. Run different tests at different times, in * different sequences. Nothing here should interact with other devices, -- cgit v1.2.3 From d5851c2480253e6a58965d832d5ffbffba6c4729 Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:44:57 +0800 Subject: usb/musb: fix repeated words in comments Delete the redundant word 'mode'. Delete the redundant word 'than'. Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716134457.46535-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_cppi41.c | 2 +- drivers/usb/musb/tusb6010.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index 7fbb8a307145..c963cb8565f2 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -286,7 +286,7 @@ static void cppi41_dma_callback(void *private_data, * receive a FIFO empty interrupt so the only thing we can do is * to poll for the bit. On HS it usually takes 2us, on FS around * 110us - 150us depending on the transfer size. - * We spin on HS (no longer than than 25us and setup a timer on + * We spin on HS (no longer than 25us and setup a timer on * FS to check for the bit and complete the transfer. */ if (is_host_active(musb)) { diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index 7ed4cc348d99..5609b4e84d40 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -495,7 +495,7 @@ done: } /* - * Maybe put TUSB6010 into idle mode mode depending on USB link status, + * Maybe put TUSB6010 into idle mode depending on USB link status, * like "disconnected" or "suspended". We'll be woken out of it by * connect, resume, or disconnect. * -- cgit v1.2.3 From 908d34aad1ef9214899ec07bfb2a9924f8ed52ff Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:56:42 +0800 Subject: usb/typec/tcpm: fix repeated words in comments Delete the redundant word 'to'. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716135642.52460-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index e1126a6c8e46..ea5a917c51b1 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4520,7 +4520,7 @@ static void run_state_machine(struct tcpm_port *port) * The specification suggests that dual mode ports in sink * mode should transition to state PE_SRC_Transition_to_default. * See USB power delivery specification chapter 8.3.3.6.1.3. - * This would mean to to + * This would mean to * - turn off VCONN, reset power supply * - request hardware reset * - turn on VCONN -- cgit v1.2.3 From 13da6f41fbe01afc4937aabef87950223d52c83f Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sat, 16 Jul 2022 12:17:55 +0800 Subject: USB: xhci: Fix comment typo The double `the' is duplicated in the comment, remove one. Signed-off-by: Jason Wang Link: https://lore.kernel.org/r/20220716041755.34016-1-wangborong@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 46d0b9ad6f74..ad81e9a508b1 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1964,7 +1964,7 @@ static void handle_port_status(struct xhci_hcd *xhci, /* * Check to see if xhci-hub.c is waiting on RExit to U0 transition (or - * RExit to a disconnect state). If so, let the the driver know it's + * RExit to a disconnect state). If so, let the driver know it's * out of the RExit state. */ if (!DEV_SUPERSPEED_ANY(portsc) && hcd->speed < HCD_USB3 && -- cgit v1.2.3 From 0c34043897736a3fac259564a6323a1f045100bb Mon Sep 17 00:00:00 2001 From: "Nícolas F. R. A. Prado" Date: Mon, 25 Jul 2022 16:31:29 -0400 Subject: usb: typec: retimer: Add missing id check in match callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fwnode_connection_find_match() function handles two cases: named references and graph endpoints. In the second case, the match function passed in is called with the id to check for the match. However, the match function for the recently added type-c retimer class assumes the connection has already been matched (which is only true for the first case). The result is that with that change, all type-c nodes with graph endpoints defer probe indefinitely, independently of having a retimer connection or not. Add the missing check, like is done by the type-c mux and usb role switch code, to fix the issue. Fixes: ddaf8d96f93b ("usb: typec: Add support for retimers") Reviewed-by: Prashant Malani Signed-off-by: Nícolas F. R. A. Prado Link: https://lore.kernel.org/r/20220725203129.1973260-1-nfraprado@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/retimer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/retimer.c b/drivers/usb/typec/retimer.c index 051eaa7d2899..2003731f1bee 100644 --- a/drivers/usb/typec/retimer.c +++ b/drivers/usb/typec/retimer.c @@ -36,8 +36,13 @@ static int retimer_fwnode_match(struct device *dev, const void *fwnode) static void *typec_retimer_match(struct fwnode_handle *fwnode, const char *id, void *data) { - struct device *dev = class_find_device(&retimer_class, NULL, fwnode, - retimer_fwnode_match); + struct device *dev; + + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + dev = class_find_device(&retimer_class, NULL, fwnode, + retimer_fwnode_match); return dev ? to_typec_retimer(dev) : ERR_PTR(-EPROBE_DEFER); } -- cgit v1.2.3 From b2d0dd5155c437ec5aad2141d74a8fac97ef755f Mon Sep 17 00:00:00 2001 From: Chen Xingdi Date: Wed, 27 Jul 2022 11:11:46 +0800 Subject: usb: renesas-xhci: Do not print any log while fw verif success When drivers are working properly, they should be quiet. Signed-off-by: Chen Xingdi Link: https://lore.kernel.org/r/20220727031146.19345-1-chenxingdi@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci-renesas.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c index 52599d96634f..93f8b355bc70 100644 --- a/drivers/usb/host/xhci-pci-renesas.c +++ b/drivers/usb/host/xhci-pci-renesas.c @@ -120,7 +120,6 @@ static int renesas_fw_verify(const void *fw_data, size_t length) { u16 fw_version_pointer; - u16 fw_version; /* * The Firmware's Data Format is describe in @@ -150,9 +149,6 @@ static int renesas_fw_verify(const void *fw_data, return -EINVAL; } - fw_version = get_unaligned_le16(fw_data + fw_version_pointer); - pr_err("got firmware version: %02x.", fw_version); - return 0; } -- cgit v1.2.3 From fe3cc0cebe6091675c8bacd94e232a3203b939b8 Mon Sep 17 00:00:00 2001 From: Jilin Yuan Date: Sat, 16 Jul 2022 21:16:30 +0800 Subject: usb/chipidea: fix repeated words in comments Delete the redundant word 'power'. Acked-by: Peter Chen Signed-off-by: Jilin Yuan Link: https://lore.kernel.org/r/20220716131630.33151-1-yuanjilin@cdjrlc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/otg_fsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 6ed4b00dba96..61b157b9c662 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -459,7 +459,7 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); if (on) { - /* Enable power power */ + /* Enable power */ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, PORTSC_PP); if (ci->platdata->reg_vbus) { -- cgit v1.2.3 From c3ffc9c4ca44bfe9562166793d133e1fb0630ea6 Mon Sep 17 00:00:00 2001 From: Andrey Strachuk Date: Mon, 18 Jul 2022 19:00:52 +0300 Subject: usb: cdns3: change place of 'priv_ep' assignment in cdns3_gadget_ep_dequeue(), cdns3_gadget_ep_enable() If 'ep' is NULL, result of ep_to_cdns3_ep(ep) is invalid pointer and its dereference with priv_ep->cdns3_dev may cause panic. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7733f6c32e36 ("usb: cdns3: Add Cadence USB3 DRD Driver") Acked-by: Peter Chen Signed-off-by: Andrey Strachuk Link: https://lore.kernel.org/r/20220718160052.4188-1-strochuk@ispras.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-gadget.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 555caafe4f04..9ac7d0a8c5da 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -2285,14 +2285,15 @@ static int cdns3_gadget_ep_enable(struct usb_ep *ep, int val; priv_ep = ep_to_cdns3_ep(ep); - priv_dev = priv_ep->cdns3_dev; - comp_desc = priv_ep->endpoint.comp_desc; if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { dev_dbg(priv_dev->dev, "usbss: invalid parameters\n"); return -EINVAL; } + comp_desc = priv_ep->endpoint.comp_desc; + priv_dev = priv_ep->cdns3_dev; + if (!desc->wMaxPacketSize) { dev_err(priv_dev->dev, "usbss: missing wMaxPacketSize\n"); return -EINVAL; @@ -2600,7 +2601,7 @@ int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request) { struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_device *priv_dev; struct usb_request *req, *req_temp; struct cdns3_request *priv_req; struct cdns3_trb *link_trb; @@ -2611,6 +2612,8 @@ int cdns3_gadget_ep_dequeue(struct usb_ep *ep, if (!ep || !request || !ep->desc) return -EINVAL; + priv_dev = priv_ep->cdns3_dev; + spin_lock_irqsave(&priv_dev->lock, flags); priv_req = to_cdns3_request(request); -- cgit v1.2.3 From a7dc438b5e446afcd1b3b6651da28271400722f2 Mon Sep 17 00:00:00 2001 From: Linyu Yuan Date: Tue, 26 Jul 2022 14:45:49 +0800 Subject: usb: typec: ucsi: Acknowledge the GET_ERROR_STATUS command completion We found PPM will not send any notification after it report error status and OPM issue GET_ERROR_STATUS command to read the details about error. According UCSI spec, PPM may clear the Error Status Data after the OPM has acknowledged the command completion. This change add operation to acknowledge the command completion from PPM. Fixes: bdc62f2bae8f (usb: typec: ucsi: Simplified registration and I/O API) Cc: # 5.10 Signed-off-by: Jack Pham Signed-off-by: Linyu Yuan Link: https://lore.kernel.org/r/1658817949-4632-1-git-send-email-quic_linyyuan@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index cbd862f9f2a1..1aea46493b85 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -76,6 +76,10 @@ static int ucsi_read_error(struct ucsi *ucsi) if (ret) return ret; + ret = ucsi_acknowledge_command(ucsi); + if (ret) + return ret; + switch (error) { case UCSI_ERROR_INCOMPATIBLE_PARTNER: return -EOPNOTSUPP; -- cgit v1.2.3 From 72849d4fcee7cc9e6b98637738b722f78502525d Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 13 Jul 2022 14:08:41 +0200 Subject: usb: typec: ucsi: stm32g0: add support for stm32g0 controller STM32G0 provides an integrated USB Type-C and power delivery interface. It can be programmed with a firmware to handle UCSI protocol over I2C interface. A GPIO is used as an interrupt line. Type-C connector can be used as a wakeup source (typically to detect changes on the port, like attach or detach). PM suspend / resume routines are used to enable wake irqs, and signal a wakeup event in case the IRQ has fired while in suspend. The i2c core is doing the necessary initialization when the "wakeup-source" flag is provided. Note: the interrupt handler shouldn't be called before the i2c bus resumes. So, the interrupts are disabled during suspend period, and re-enabled upon resume, to avoid i2c transfer while suspended, from the irq handler. Acked-by: Heikki Krogerus Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20220713120842.560902-3-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/Kconfig | 10 ++ drivers/usb/typec/ucsi/Makefile | 1 + drivers/usb/typec/ucsi/ucsi_stm32g0.c | 264 ++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 drivers/usb/typec/ucsi/ucsi_stm32g0.c (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index 5e9b37b3f25e..8f9c4b9f31f7 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -48,4 +48,14 @@ config UCSI_ACPI To compile the driver as a module, choose M here: the module will be called ucsi_acpi +config UCSI_STM32G0 + tristate "UCSI Interface Driver for STM32G0" + depends on I2C + help + This driver enables UCSI support on platforms that expose a STM32G0 + Type-C controller over I2C interface. + + To compile the driver as a module, choose M here: the module will be + called ucsi_stm32g0. + endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index 8a8eb5cb8e0f..480d533d762f 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -17,3 +17,4 @@ endif obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o +obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c new file mode 100644 index 000000000000..e3965cbff058 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * UCSI driver for STMicroelectronics STM32G0 Type-C PD controller + * + * Copyright (C) 2022, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + */ + +#include +#include +#include +#include + +#include "ucsi.h" + +struct ucsi_stm32g0 { + struct i2c_client *client; + struct completion complete; + struct device *dev; + unsigned long flags; + struct ucsi *ucsi; + bool suspended; + bool wakeup_event; +}; + +static int ucsi_stm32g0_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + u8 reg = offset; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c read %02x, %02x error: %d\n", client->addr, reg, ret); + + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int ucsi_stm32g0_async_write(struct ucsi *ucsi, unsigned int offset, const void *val, + size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + } + }; + unsigned char *buf; + int ret; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = offset; + memcpy(&buf[1], val, len); + msg[0].len = len + 1; + msg[0].buf = buf; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + kfree(buf); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c write %02x, %02x error: %d\n", client->addr, offset, ret); + + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int ucsi_stm32g0_sync_write(struct ucsi *ucsi, unsigned int offset, const void *val, + size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + int ret; + + set_bit(COMMAND_PENDING, &g0->flags); + + ret = ucsi_stm32g0_async_write(ucsi, offset, val, len); + if (ret) + goto out_clear_bit; + + if (!wait_for_completion_timeout(&g0->complete, msecs_to_jiffies(5000))) + ret = -ETIMEDOUT; + +out_clear_bit: + clear_bit(COMMAND_PENDING, &g0->flags); + + return ret; +} + +static irqreturn_t ucsi_stm32g0_irq_handler(int irq, void *data) +{ + struct ucsi_stm32g0 *g0 = data; + u32 cci; + int ret; + + if (g0->suspended) + g0->wakeup_event = true; + + ret = ucsi_stm32g0_read(g0->ucsi, UCSI_CCI, &cci, sizeof(cci)); + if (ret) + return IRQ_NONE; + + if (UCSI_CCI_CONNECTOR(cci)) + ucsi_connector_change(g0->ucsi, UCSI_CCI_CONNECTOR(cci)); + + if (test_bit(COMMAND_PENDING, &g0->flags) && + cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) + complete(&g0->complete); + + return IRQ_HANDLED; +} + +static const struct ucsi_operations ucsi_stm32g0_ops = { + .read = ucsi_stm32g0_read, + .sync_write = ucsi_stm32g0_sync_write, + .async_write = ucsi_stm32g0_async_write, +}; + +static int ucsi_stm32g0_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ucsi_stm32g0 *g0; + int ret; + + g0 = devm_kzalloc(dev, sizeof(*g0), GFP_KERNEL); + if (!g0) + return -ENOMEM; + + g0->dev = dev; + g0->client = client; + init_completion(&g0->complete); + i2c_set_clientdata(client, g0); + + g0->ucsi = ucsi_create(dev, &ucsi_stm32g0_ops); + if (IS_ERR(g0->ucsi)) + return PTR_ERR(g0->ucsi); + + ucsi_set_drvdata(g0->ucsi, g0); + + /* Request alert interrupt */ + ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, + dev_name(&client->dev), g0); + if (ret) { + dev_err_probe(dev, ret, "request IRQ failed\n"); + goto destroy; + } + + ret = ucsi_register(g0->ucsi); + if (ret) { + dev_err_probe(dev, ret, "ucsi_register failed\n"); + goto freeirq; + } + + return 0; + +freeirq: + free_irq(client->irq, g0); +destroy: + ucsi_destroy(g0->ucsi); + + return ret; +} + +static int ucsi_stm32g0_remove(struct i2c_client *client) +{ + struct ucsi_stm32g0 *g0 = i2c_get_clientdata(client); + + ucsi_unregister(g0->ucsi); + free_irq(client->irq, g0); + ucsi_destroy(g0->ucsi); + + return 0; +} + +static int ucsi_stm32g0_suspend(struct device *dev) +{ + struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); + struct i2c_client *client = g0->client; + + /* Keep the interrupt disabled until the i2c bus has been resumed */ + disable_irq(client->irq); + + g0->suspended = true; + g0->wakeup_event = false; + + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int ucsi_stm32g0_resume(struct device *dev) +{ + struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); + struct i2c_client *client = g0->client; + + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + disable_irq_wake(client->irq); + + enable_irq(client->irq); + + /* Enforce any pending handler gets called to signal a wakeup_event */ + synchronize_irq(client->irq); + + if (g0->wakeup_event) + pm_wakeup_event(g0->dev, 0); + + g0->suspended = false; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_stm32g0_pm_ops, ucsi_stm32g0_suspend, ucsi_stm32g0_resume); + +static const struct of_device_id __maybe_unused ucsi_stm32g0_typec_of_match[] = { + { .compatible = "st,stm32g0-typec" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ucsi_stm32g0_typec_of_match); + +static const struct i2c_device_id ucsi_stm32g0_typec_i2c_devid[] = { + {"stm32g0-typec", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ucsi_stm32g0_typec_i2c_devid); + +static struct i2c_driver ucsi_stm32g0_i2c_driver = { + .driver = { + .name = "ucsi-stm32g0-i2c", + .of_match_table = of_match_ptr(ucsi_stm32g0_typec_of_match), + .pm = pm_sleep_ptr(&ucsi_stm32g0_pm_ops), + }, + .probe = ucsi_stm32g0_probe, + .remove = ucsi_stm32g0_remove, + .id_table = ucsi_stm32g0_typec_i2c_devid +}; +module_i2c_driver(ucsi_stm32g0_i2c_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_DESCRIPTION("STMicroelectronics STM32G0 Type-C controller"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:ucsi-stm32g0"); -- cgit v1.2.3 From 2d945194cce11ce2800949c8746bc3e788d89067 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 13 Jul 2022 14:08:42 +0200 Subject: usb: typec: ucsi: stm32g0: add bootloader support STM32G0 comes with STM32 bootloader in its system memory. Add support for some I2C bootloader commands as described in application notes AN2606 and AN4221, to enable STM32G0 UCSI firmware update. Upon probing, the driver needs to know the STM32G0 state: - In bootloader mode, STM32 G0 answers at i2c addr 0x51. - In running mode, STM32 G0 firmware may answer at two address. - The main address specified in DT is used for UCSI. - 0x51 addr can be re-used for FW controls like getting software version or jump to booloader request. So probe using the main firmware i2c address first, before attempting bootloader address (e.g. check for blank, erased or previously aborted firmware update). Acked-by: Heikki Krogerus Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20220713120842.560902-4-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_stm32g0.c | 539 +++++++++++++++++++++++++++++++++- 1 file changed, 526 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index e3965cbff058..061551d464f1 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -6,23 +6,326 @@ * Author: Fabrice Gasnier . */ +#include +#include #include #include #include #include +#include #include "ucsi.h" +/* STM32G0 I2C bootloader addr: 0b1010001x (See AN2606) */ +#define STM32G0_I2C_BL_ADDR (0xa2 >> 1) + +/* STM32G0 I2C bootloader max data size */ +#define STM32G0_I2C_BL_SZ 256 + +/* STM32 I2C bootloader commands (See AN4221) */ +#define STM32_CMD_GVR 0x01 /* Gets the bootloader version */ +#define STM32_CMD_GVR_LEN 1 +#define STM32_CMD_RM 0x11 /* Reag memory */ +#define STM32_CMD_WM 0x31 /* Write memory */ +#define STM32_CMD_ADDR_LEN 5 /* Address len for go, mem write... */ +#define STM32_CMD_ERASE 0x44 /* Erase page, bank or all */ +#define STM32_CMD_ERASE_SPECIAL_LEN 3 +#define STM32_CMD_GLOBAL_MASS_ERASE 0xffff /* All-bank erase */ + +/* STM32 I2C bootloader answer status */ +#define STM32G0_I2C_BL_ACK 0x79 +#define STM32G0_I2C_BL_NACK 0x1f +#define STM32G0_I2C_BL_BUSY 0x76 + +/* STM32G0 flash definitions */ +#define STM32G0_USER_OPTION_BYTES 0x1fff7800 +#define STM32G0_USER_OB_NBOOT0 BIT(26) +#define STM32G0_USER_OB_NBOOT_SEL BIT(24) +#define STM32G0_USER_OB_BOOT_MAIN (STM32G0_USER_OB_NBOOT0 | STM32G0_USER_OB_NBOOT_SEL) +#define STM32G0_MAIN_MEM_ADDR 0x08000000 + +/* STM32 Firmware definitions: additional commands */ +#define STM32G0_FW_GETVER 0x00 /* Gets the firmware version */ +#define STM32G0_FW_GETVER_LEN 4 +#define STM32G0_FW_RSTGOBL 0x21 /* Reset and go to bootloader */ +#define STM32G0_FW_KEYWORD 0xa56959a6 + +/* ucsi_stm32g0_fw_info located at the end of the firmware */ +struct ucsi_stm32g0_fw_info { + u32 version; + u32 keyword; +}; + struct ucsi_stm32g0 { struct i2c_client *client; + struct i2c_client *i2c_bl; + bool in_bootloader; + u8 bl_version; struct completion complete; struct device *dev; unsigned long flags; + const char *fw_name; struct ucsi *ucsi; bool suspended; bool wakeup_event; }; +/* + * Bootloader commands helpers: + * - send command (2 bytes) + * - check ack + * Then either: + * - receive data + * - receive data + check ack + * - send data + check ack + * These operations depends on the command and have various length. + */ +static int ucsi_stm32g0_bl_check_ack(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + unsigned char ack; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = &ack, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl ack (%02x), error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + /* The 'ack' byte should contain bootloader answer: ack/nack/busy */ + switch (ack) { + case STM32G0_I2C_BL_ACK: + return 0; + case STM32G0_I2C_BL_NACK: + return -ENOENT; + case STM32G0_I2C_BL_BUSY: + return -EBUSY; + default: + dev_err(g0->dev, "i2c bl ack (%02x), invalid byte: %02x\n", + client->addr, ack); + return -EINVAL; + } +} + +static int ucsi_stm32g0_bl_cmd_check_ack(struct ucsi *ucsi, unsigned int cmd, bool check_ack) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + unsigned char buf[2]; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + }, + }; + int ret; + + /* + * Send STM32 bootloader command format is two bytes: + * - command code + * - XOR'ed command code + */ + buf[0] = cmd; + buf[1] = cmd ^ 0xff; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_dbg(g0->dev, "i2c bl cmd %d (%02x), error: %d\n", cmd, client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + if (check_ack) + return ucsi_stm32g0_bl_check_ack(ucsi); + + return 0; +} + +static int ucsi_stm32g0_bl_cmd(struct ucsi *ucsi, unsigned int cmd) +{ + return ucsi_stm32g0_bl_cmd_check_ack(ucsi, cmd, true); +} + +static int ucsi_stm32g0_bl_rcv_check_ack(struct ucsi *ucsi, void *data, size_t len, bool check_ack) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = data, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl rcv %02x, error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + if (check_ack) + return ucsi_stm32g0_bl_check_ack(ucsi); + + return 0; +} + +static int ucsi_stm32g0_bl_rcv(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_check_ack(ucsi, data, len, true); +} + +static int ucsi_stm32g0_bl_rcv_woack(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_check_ack(ucsi, data, len, false); +} + +static int ucsi_stm32g0_bl_send(struct ucsi *ucsi, void *data, size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = len, + .buf = data, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl send %02x, error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + return ucsi_stm32g0_bl_check_ack(ucsi); +} + +/* Bootloader commands */ +static int ucsi_stm32g0_bl_get_version(struct ucsi *ucsi, u8 *bl_version) +{ + int ret; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_GVR); + if (ret) + return ret; + + return ucsi_stm32g0_bl_rcv(ucsi, bl_version, STM32_CMD_GVR_LEN); +} + +static int ucsi_stm32g0_bl_send_addr(struct ucsi *ucsi, u32 addr) +{ + u8 data8[STM32_CMD_ADDR_LEN]; + + /* Address format: 4 bytes addr (MSB first) + XOR'ed addr bytes */ + put_unaligned_be32(addr, data8); + data8[4] = data8[0] ^ data8[1] ^ data8[2] ^ data8[3]; + + return ucsi_stm32g0_bl_send(ucsi, data8, STM32_CMD_ADDR_LEN); +} + +static int ucsi_stm32g0_bl_global_mass_erase(struct ucsi *ucsi) +{ + u8 data8[4]; + u16 *data16 = (u16 *)&data8[0]; + int ret; + + data16[0] = STM32_CMD_GLOBAL_MASS_ERASE; + data8[2] = data8[0] ^ data8[1]; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_ERASE); + if (ret) + return ret; + + return ucsi_stm32g0_bl_send(ucsi, data8, STM32_CMD_ERASE_SPECIAL_LEN); +} + +static int ucsi_stm32g0_bl_write(struct ucsi *ucsi, u32 addr, const void *data, size_t len) +{ + u8 *data8; + int i, ret; + + if (!len || len > STM32G0_I2C_BL_SZ) + return -EINVAL; + + /* Write memory: len bytes -1, data up to 256 bytes + XOR'ed bytes */ + data8 = kmalloc(STM32G0_I2C_BL_SZ + 2, GFP_KERNEL); + if (!data8) + return -ENOMEM; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_WM); + if (ret) + goto free; + + ret = ucsi_stm32g0_bl_send_addr(ucsi, addr); + if (ret) + goto free; + + data8[0] = len - 1; + memcpy(data8 + 1, data, len); + data8[len + 1] = data8[0]; + for (i = 1; i <= len; i++) + data8[len + 1] ^= data8[i]; + + ret = ucsi_stm32g0_bl_send(ucsi, data8, len + 2); +free: + kfree(data8); + + return ret; +} + +static int ucsi_stm32g0_bl_read(struct ucsi *ucsi, u32 addr, void *data, size_t len) +{ + int ret; + + if (!len || len > STM32G0_I2C_BL_SZ) + return -EINVAL; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_RM); + if (ret) + return ret; + + ret = ucsi_stm32g0_bl_send_addr(ucsi, addr); + if (ret) + return ret; + + ret = ucsi_stm32g0_bl_cmd(ucsi, len - 1); + if (ret) + return ret; + + return ucsi_stm32g0_bl_rcv_woack(ucsi, data, len); +} + +/* Firmware commands (the same address as the bootloader) */ +static int ucsi_stm32g0_fw_cmd(struct ucsi *ucsi, unsigned int cmd) +{ + return ucsi_stm32g0_bl_cmd_check_ack(ucsi, cmd, false); +} + +static int ucsi_stm32g0_fw_rcv(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_woack(ucsi, data, len); +} + +/* UCSI ops */ static int ucsi_stm32g0_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t len) { struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); @@ -138,6 +441,191 @@ static const struct ucsi_operations ucsi_stm32g0_ops = { .async_write = ucsi_stm32g0_async_write, }; +static int ucsi_stm32g0_register(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + int ret; + + /* Request alert interrupt */ + ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, + dev_name(g0->dev), g0); + if (ret) { + dev_err(g0->dev, "request IRQ failed: %d\n", ret); + return ret; + } + + ret = ucsi_register(ucsi); + if (ret) { + dev_err_probe(g0->dev, ret, "ucsi_register failed\n"); + free_irq(client->irq, g0); + return ret; + } + + return 0; +} + +static void ucsi_stm32g0_unregister(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + + ucsi_unregister(ucsi); + free_irq(client->irq, g0); +} + +static void ucsi_stm32g0_fw_cb(const struct firmware *fw, void *context) +{ + struct ucsi_stm32g0 *g0; + const u8 *data, *end; + const struct ucsi_stm32g0_fw_info *fw_info; + u32 addr = STM32G0_MAIN_MEM_ADDR, ob, fw_version; + int ret, size; + + if (!context) + return; + + g0 = ucsi_get_drvdata(context); + + if (!fw) + goto fw_release; + + fw_info = (struct ucsi_stm32g0_fw_info *)(fw->data + fw->size - sizeof(*fw_info)); + + if (!g0->in_bootloader) { + /* Read running firmware version */ + ret = ucsi_stm32g0_fw_cmd(g0->ucsi, STM32G0_FW_GETVER); + if (ret) { + dev_err(g0->dev, "Get version cmd failed %d\n", ret); + goto fw_release; + } + ret = ucsi_stm32g0_fw_rcv(g0->ucsi, &fw_version, + STM32G0_FW_GETVER_LEN); + if (ret) { + dev_err(g0->dev, "Get version failed %d\n", ret); + goto fw_release; + } + + /* Sanity check on keyword and firmware version */ + if (fw_info->keyword != STM32G0_FW_KEYWORD || fw_info->version == fw_version) + goto fw_release; + + dev_info(g0->dev, "Flashing FW: %08x (%08x cur)\n", fw_info->version, fw_version); + + /* Switch to bootloader mode */ + ucsi_stm32g0_unregister(g0->ucsi); + ret = ucsi_stm32g0_fw_cmd(g0->ucsi, STM32G0_FW_RSTGOBL); + if (ret) { + dev_err(g0->dev, "bootloader cmd failed %d\n", ret); + goto fw_release; + } + g0->in_bootloader = true; + + /* STM32G0 reboot delay */ + msleep(100); + } + + ret = ucsi_stm32g0_bl_global_mass_erase(g0->ucsi); + if (ret) { + dev_err(g0->dev, "Erase failed %d\n", ret); + goto fw_release; + } + + data = fw->data; + end = fw->data + fw->size; + while (data < end) { + if ((end - data) < STM32G0_I2C_BL_SZ) + size = end - data; + else + size = STM32G0_I2C_BL_SZ; + + ret = ucsi_stm32g0_bl_write(g0->ucsi, addr, data, size); + if (ret) { + dev_err(g0->dev, "Write failed %d\n", ret); + goto fw_release; + } + addr += size; + data += size; + } + + dev_dbg(g0->dev, "Configure to boot from main flash\n"); + + ret = ucsi_stm32g0_bl_read(g0->ucsi, STM32G0_USER_OPTION_BYTES, &ob, sizeof(ob)); + if (ret) { + dev_err(g0->dev, "read user option bytes failed %d\n", ret); + goto fw_release; + } + + dev_dbg(g0->dev, "STM32G0_USER_OPTION_BYTES 0x%08x\n", ob); + + /* Configure user option bytes to boot from main flash next time */ + ob |= STM32G0_USER_OB_BOOT_MAIN; + + /* Writing option bytes will also reset G0 for updates to be loaded */ + ret = ucsi_stm32g0_bl_write(g0->ucsi, STM32G0_USER_OPTION_BYTES, &ob, sizeof(ob)); + if (ret) { + dev_err(g0->dev, "write user option bytes failed %d\n", ret); + goto fw_release; + } + + dev_info(g0->dev, "Starting, option bytes:0x%08x\n", ob); + + /* STM32G0 FW boot delay */ + msleep(500); + + /* Register UCSI interface */ + if (!ucsi_stm32g0_register(g0->ucsi)) + g0->in_bootloader = false; + +fw_release: + release_firmware(fw); +} + +static int ucsi_stm32g0_probe_bootloader(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + int ret; + u16 ucsi_version; + + /* firmware-name is optional */ + if (device_property_present(g0->dev, "firmware-name")) { + ret = device_property_read_string(g0->dev, "firmware-name", &g0->fw_name); + if (ret < 0) + return dev_err_probe(g0->dev, ret, "Error reading firmware-name\n"); + } + + if (g0->fw_name) { + /* STM32G0 in bootloader mode communicates at reserved address 0x51 */ + g0->i2c_bl = i2c_new_dummy_device(g0->client->adapter, STM32G0_I2C_BL_ADDR); + if (IS_ERR(g0->i2c_bl)) { + ret = dev_err_probe(g0->dev, PTR_ERR(g0->i2c_bl), + "Failed to register booloader I2C address\n"); + return ret; + } + } + + /* + * Try to guess if the STM32G0 is running a UCSI firmware. First probe the UCSI FW at its + * i2c address. Fallback to bootloader i2c address only if firmware-name is specified. + */ + ret = ucsi_stm32g0_read(ucsi, UCSI_VERSION, &ucsi_version, sizeof(ucsi_version)); + if (!ret || !g0->fw_name) + return ret; + + /* Speculatively read the bootloader version that has a known length. */ + ret = ucsi_stm32g0_bl_get_version(ucsi, &g0->bl_version); + if (ret < 0) { + i2c_unregister_device(g0->i2c_bl); + return ret; + } + + /* Device in bootloader mode */ + g0->in_bootloader = true; + dev_info(g0->dev, "Bootloader Version 0x%02x\n", g0->bl_version); + + return 0; +} + static int ucsi_stm32g0_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; @@ -159,24 +647,41 @@ static int ucsi_stm32g0_probe(struct i2c_client *client, const struct i2c_device ucsi_set_drvdata(g0->ucsi, g0); - /* Request alert interrupt */ - ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, - dev_name(&client->dev), g0); - if (ret) { - dev_err_probe(dev, ret, "request IRQ failed\n"); + ret = ucsi_stm32g0_probe_bootloader(g0->ucsi); + if (ret < 0) goto destroy; + + /* + * Don't register in bootloader mode: wait for the firmware to be loaded and started before + * registering UCSI device. + */ + if (!g0->in_bootloader) { + ret = ucsi_stm32g0_register(g0->ucsi); + if (ret < 0) + goto freei2c; } - ret = ucsi_register(g0->ucsi); - if (ret) { - dev_err_probe(dev, ret, "ucsi_register failed\n"); - goto freeirq; + if (g0->fw_name) { + /* + * Asynchronously flash (e.g. bootloader mode) or update the running firmware, + * not to hang the boot process + */ + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, g0->fw_name, g0->dev, + GFP_KERNEL, g0->ucsi, ucsi_stm32g0_fw_cb); + if (ret < 0) { + dev_err_probe(dev, ret, "firmware request failed\n"); + goto unregister; + } } return 0; -freeirq: - free_irq(client->irq, g0); +unregister: + if (!g0->in_bootloader) + ucsi_stm32g0_unregister(g0->ucsi); +freei2c: + if (g0->fw_name) + i2c_unregister_device(g0->i2c_bl); destroy: ucsi_destroy(g0->ucsi); @@ -187,8 +692,10 @@ static int ucsi_stm32g0_remove(struct i2c_client *client) { struct ucsi_stm32g0 *g0 = i2c_get_clientdata(client); - ucsi_unregister(g0->ucsi); - free_irq(client->irq, g0); + if (!g0->in_bootloader) + ucsi_stm32g0_unregister(g0->ucsi); + if (g0->fw_name) + i2c_unregister_device(g0->i2c_bl); ucsi_destroy(g0->ucsi); return 0; @@ -199,6 +706,9 @@ static int ucsi_stm32g0_suspend(struct device *dev) struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); struct i2c_client *client = g0->client; + if (g0->in_bootloader) + return 0; + /* Keep the interrupt disabled until the i2c bus has been resumed */ disable_irq(client->irq); @@ -216,6 +726,9 @@ static int ucsi_stm32g0_resume(struct device *dev) struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); struct i2c_client *client = g0->client; + if (g0->in_bootloader) + return 0; + if (device_may_wakeup(dev) || device_wakeup_path(dev)) disable_irq_wake(client->irq); -- cgit v1.2.3 From 43993626de00f8faea2cf4d54aaea8f607331fcf Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Tue, 26 Jul 2022 10:07:06 +0200 Subject: usb: misc: onboard-hub: add support for Microchip USB2514B USB 2.0 hub Add support for Microchip USB2514B USB 2.0 hub to the onboard usb hub driver. Adopt the generic usb-device compatible ("usbVID,PID"). Some STM32MP1 boards have this hub on-board, with a supply that needs to be enabled for proper operation. Acked-by: Matthias Kaehlcke Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20220726080708.162547-3-fabrice.gasnier@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub.c | 2 ++ drivers/usb/misc/onboard_usb_hub.h | 1 + 2 files changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index 6b9b949d17d3..de3627af3c84 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -309,6 +309,7 @@ static struct platform_driver onboard_hub_driver = { /************************** USB driver **************************/ +#define VENDOR_ID_MICROCHIP 0x0424 #define VENDOR_ID_REALTEK 0x0bda /* @@ -383,6 +384,7 @@ static void onboard_hub_usbdev_disconnect(struct usb_device *udev) } static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h index d3a5b6938582..3820669eb98c 100644 --- a/drivers/usb/misc/onboard_usb_hub.h +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -7,6 +7,7 @@ #define _USB_MISC_ONBOARD_USB_HUB_H static const struct of_device_id onboard_hub_match[] = { + { .compatible = "usb424,2514" }, { .compatible = "usbbda,411" }, { .compatible = "usbbda,5411" }, { .compatible = "usbbda,414" }, -- cgit v1.2.3 From e0c6b1f3d7574dea3bec7deb18578e896d2cfaa2 Mon Sep 17 00:00:00 2001 From: Xuezhi Zhang Date: Wed, 27 Jul 2022 20:44:15 +0800 Subject: USB: usbsevseg: convert sysfs snprintf to sysfs_emit Fix the following coccincheck warning: drivers/usb/misc/usbsevseg.c:170:8-16: WARNING: use scnprintf or sprintf Signed-off-by: Xuezhi Zhang Link: https://lore.kernel.org/r/20220727124415.8340-1-zhangxuezhi3@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbsevseg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c index 4bc816bb09bb..c3114d9bd128 100644 --- a/drivers/usb/misc/usbsevseg.c +++ b/drivers/usb/misc/usbsevseg.c @@ -167,7 +167,7 @@ static ssize_t text_show(struct device *dev, struct usb_interface *intf = to_usb_interface(dev); struct usb_sevsegdev *mydev = usb_get_intfdata(intf); - return snprintf(buf, mydev->textlength, "%s\n", mydev->text); + return sysfs_emit(buf, "%s\n", mydev->text); } static ssize_t text_store(struct device *dev, -- cgit v1.2.3 From 40758e493f4d08093fbf58390f8a4bc55b501a49 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Wed, 27 Jul 2022 16:11:16 +0200 Subject: usb: misc: onboard_usb_hub: Add reset-gpio support Despite default reset upon probe, release reset line after powering up the hub and assert reset again before powering down. Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20220727141117.909361-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub.c | 28 ++++++++++++++++++++++++++++ drivers/usb/misc/onboard_usb_hub.h | 22 +++++++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index de3627af3c84..0c81417dd9a7 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,8 @@ struct usbdev_node { struct onboard_hub { struct regulator *vdd; struct device *dev; + const struct onboard_hub_pdata *pdata; + struct gpio_desc *reset_gpio; bool always_powered_in_suspend; bool is_powered_on; bool going_away; @@ -56,6 +59,9 @@ static int onboard_hub_power_on(struct onboard_hub *hub) return err; } + fsleep(hub->pdata->reset_us); + gpiod_set_value_cansleep(hub->reset_gpio, 0); + hub->is_powered_on = true; return 0; @@ -65,6 +71,11 @@ static int onboard_hub_power_off(struct onboard_hub *hub) { int err; + if (hub->reset_gpio) { + gpiod_set_value_cansleep(hub->reset_gpio, 1); + fsleep(hub->pdata->reset_us); + } + err = regulator_disable(hub->vdd); if (err) { dev_err(hub->dev, "failed to disable regulator: %d\n", err); @@ -219,6 +230,7 @@ static void onboard_hub_attach_usb_driver(struct work_struct *work) static int onboard_hub_probe(struct platform_device *pdev) { + const struct of_device_id *of_id; struct device *dev = &pdev->dev; struct onboard_hub *hub; int err; @@ -227,10 +239,26 @@ static int onboard_hub_probe(struct platform_device *pdev) if (!hub) return -ENOMEM; + of_id = of_match_device(onboard_hub_match, &pdev->dev); + if (!of_id) + return -ENODEV; + + hub->pdata = of_id->data; + if (!hub->pdata) + return -EINVAL; + hub->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(hub->vdd)) return PTR_ERR(hub->vdd); + hub->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(hub->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n"); + + if (hub->reset_gpio) + fsleep(hub->pdata->reset_us); + hub->dev = dev; mutex_init(&hub->lock); INIT_LIST_HEAD(&hub->udev_list); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h index 3820669eb98c..562fa48fcf10 100644 --- a/drivers/usb/misc/onboard_usb_hub.h +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -6,12 +6,24 @@ #ifndef _USB_MISC_ONBOARD_USB_HUB_H #define _USB_MISC_ONBOARD_USB_HUB_H +struct onboard_hub_pdata { + unsigned long reset_us; /* reset pulse width in us */ +}; + +static const struct onboard_hub_pdata microchip_usb424_data = { + .reset_us = 1, +}; + +static const struct onboard_hub_pdata realtek_rts5411_data = { + .reset_us = 0, +}; + static const struct of_device_id onboard_hub_match[] = { - { .compatible = "usb424,2514" }, - { .compatible = "usbbda,411" }, - { .compatible = "usbbda,5411" }, - { .compatible = "usbbda,414" }, - { .compatible = "usbbda,5414" }, + { .compatible = "usb424,2514", .data = µchip_usb424_data, }, + { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, {} }; -- cgit v1.2.3 From ed92f4353ef53e544db5311fd8efa0cd7fdc4e08 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Wed, 27 Jul 2022 16:11:17 +0200 Subject: usb: misc: onboard_usb_hub: Add TI USB8041 hub support This is a 4-port 3.0 USB hub. Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20220727141117.909361-2-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub.c | 3 +++ drivers/usb/misc/onboard_usb_hub.h | 6 ++++++ 2 files changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index 0c81417dd9a7..eb8aef25a22d 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -339,6 +339,7 @@ static struct platform_driver onboard_hub_driver = { #define VENDOR_ID_MICROCHIP 0x0424 #define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_TI 0x0451 /* * Returns the onboard_hub platform device that is associated with the USB @@ -417,6 +418,8 @@ static const struct usb_device_id onboard_hub_id_table[] = { { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ {} }; MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h index 562fa48fcf10..34beab8bce3d 100644 --- a/drivers/usb/misc/onboard_usb_hub.h +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -18,8 +18,14 @@ static const struct onboard_hub_pdata realtek_rts5411_data = { .reset_us = 0, }; +static const struct onboard_hub_pdata ti_tusb8041_data = { + .reset_us = 3000, +}; + static const struct of_device_id onboard_hub_match[] = { { .compatible = "usb424,2514", .data = µchip_usb424_data, }, + { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, -- cgit v1.2.3 From 8288c99fc263bcafc5df5fa8c278b2eb8106364e Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Thu, 28 Jul 2022 08:49:37 +0200 Subject: usb: misc: onboard_usb_hub: Remove duplicated power_on delay onboard_hub_power_on() already ensures the reset pulse width delay, so there is no need to wait right after requesting GPIO as well. Fixes: 40758e493f4d ("usb: misc: onboard_usb_hub: Add reset-gpio support") Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20220728064937.917935-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index eb8aef25a22d..d1df153e7f5a 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -256,9 +256,6 @@ static int onboard_hub_probe(struct platform_device *pdev) if (IS_ERR(hub->reset_gpio)) return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n"); - if (hub->reset_gpio) - fsleep(hub->pdata->reset_us); - hub->dev = dev; mutex_init(&hub->lock); INIT_LIST_HEAD(&hub->udev_list); -- cgit v1.2.3