mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-07-12 18:19:42 -07:00
wifi: free the USB OTG port when WiFi is off (internal-WiFi mux boards)
Some clones (XiFan R36Max2 family) wire the internal USB WiFi module to the same OTG data lines as the physical USB port through a hardware mux gpio: WiFi and the port are mutually exclusive, and users report that on the stock OS disabling WiFi brings the port back. Port ROCKNIX's 0026 USB2 PHY patch, which reads switch-gpios from the otg-port node and exposes usb_switch_gpio / usb_switch_ext in sysfs (boot default: internal WiFi). ROCKNIX itself never toggles the mux from userspace, so the physical port stays dead there; wire it to our WiFi toggle instead: wifictl enable routes the mux to the internal module, wifictl disable flips it to the external port and powers its vbus. The sysfs writes are no-ops on boards without the mux, and the patch is inert unless the generated overlay (USBMUX hardware variant on the site) adds switch-gpios to the PHY. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+238
@@ -0,0 +1,238 @@
|
||||
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
|
||||
index 1bbff11..xxxxxxx 100644
|
||||
--- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c 2025-12-07 05:25:03.000000000 +0800
|
||||
+++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c 2026-03-24 08:32:09.772362168 +0800
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
@@ -189,6 +190,8 @@
|
||||
* @port_id: flag for otg port or host port.
|
||||
* @suspended: phy suspended flag.
|
||||
* @vbus_attached: otg device vbus status.
|
||||
+ * @switch_gpio: GPIO descriptor for USB signal routing switch.
|
||||
+ * @ext_supply: regulator for external USB device power supply.
|
||||
* @host_disconnect: usb host disconnect status.
|
||||
* @bvalid_irq: IRQ number assigned for vbus valid rise detection.
|
||||
* @id_irq: IRQ number assigned for ID pin detection.
|
||||
@@ -209,6 +212,8 @@
|
||||
unsigned int port_id;
|
||||
bool suspended;
|
||||
bool vbus_attached;
|
||||
+ struct gpio_desc *switch_gpio;
|
||||
+ struct regulator *ext_supply;
|
||||
bool host_disconnect;
|
||||
int bvalid_irq;
|
||||
int id_irq;
|
||||
@@ -640,6 +645,24 @@
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
+/*
|
||||
+ * Check if we should be in USB host mode.
|
||||
+ * Returns true if:
|
||||
+ * - EXTCON_USB_HOST is set, OR
|
||||
+ * - switch_gpio exists and is low (internal device mode)
|
||||
+ */
|
||||
+static bool rockchip_usb2phy_is_host_mode(struct rockchip_usb2phy *rphy,
|
||||
+ struct rockchip_usb2phy_port *rport)
|
||||
+{
|
||||
+ if (rphy->edev && extcon_get_state(rphy->edev, EXTCON_USB_HOST) > 0)
|
||||
+ return true;
|
||||
+
|
||||
+ if (rport->switch_gpio && gpiod_get_value_cansleep(rport->switch_gpio) == 0)
|
||||
+ return true;
|
||||
+
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
|
||||
{
|
||||
struct rockchip_usb2phy_port *rport =
|
||||
@@ -666,7 +689,7 @@
|
||||
rockchip_usb2phy_power_off(rport->phy);
|
||||
fallthrough;
|
||||
case OTG_STATE_B_IDLE:
|
||||
- if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) > 0) {
|
||||
+ if (rockchip_usb2phy_is_host_mode(rphy, rport)) {
|
||||
dev_dbg(&rport->phy->dev, "usb otg host connect\n");
|
||||
rport->state = OTG_STATE_A_HOST;
|
||||
rockchip_usb2phy_power_on(rport->phy);
|
||||
@@ -740,7 +763,7 @@
|
||||
sch_work = true;
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
- if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) == 0) {
|
||||
+ if (!rockchip_usb2phy_is_host_mode(rphy, rport)) {
|
||||
dev_dbg(&rport->phy->dev, "usb otg host disconnect\n");
|
||||
rport->state = OTG_STATE_B_IDLE;
|
||||
rockchip_usb2phy_power_off(rport->phy);
|
||||
@@ -1042,6 +1065,115 @@
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Helper to find OTG port
|
||||
+ */
|
||||
+static struct rockchip_usb2phy_port *
|
||||
+rockchip_usb2phy_find_switch_port(struct rockchip_usb2phy *rphy)
|
||||
+{
|
||||
+ unsigned int i;
|
||||
+
|
||||
+ for (i = 0; i < rphy->phy_cfg->num_ports; i++) {
|
||||
+ if (rphy->ports[i].port_id == USB2PHY_PORT_OTG)
|
||||
+ return &rphy->ports[i];
|
||||
+ }
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static ssize_t usb_switch_gpio_show(struct device *dev,
|
||||
+ struct device_attribute *attr, char *buf)
|
||||
+{
|
||||
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(dev);
|
||||
+ struct rockchip_usb2phy_port *rport;
|
||||
+
|
||||
+ rport = rockchip_usb2phy_find_switch_port(rphy);
|
||||
+ if (!rport || !rport->switch_gpio)
|
||||
+ return sysfs_emit(buf, "unsupported\n");
|
||||
+
|
||||
+ return sysfs_emit(buf, "%d\n",
|
||||
+ gpiod_get_value_cansleep(rport->switch_gpio));
|
||||
+}
|
||||
+
|
||||
+static ssize_t usb_switch_gpio_store(struct device *dev,
|
||||
+ struct device_attribute *attr,
|
||||
+ const char *buf, size_t count)
|
||||
+{
|
||||
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(dev);
|
||||
+ struct rockchip_usb2phy_port *rport;
|
||||
+ unsigned int val;
|
||||
+ int ret;
|
||||
+
|
||||
+ rport = rockchip_usb2phy_find_switch_port(rphy);
|
||||
+ if (!rport || !rport->switch_gpio)
|
||||
+ return -ENOTSUPP;
|
||||
+
|
||||
+ ret = kstrtouint(buf, 10, &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ gpiod_set_value_cansleep(rport->switch_gpio, val ? 1 : 0);
|
||||
+ dev_info(dev, "switch_gpio set to %d\n", val ? 1 : 0);
|
||||
+
|
||||
+ return count;
|
||||
+}
|
||||
+static DEVICE_ATTR_RW(usb_switch_gpio);
|
||||
+
|
||||
+static ssize_t usb_switch_ext_show(struct device *dev,
|
||||
+ struct device_attribute *attr, char *buf)
|
||||
+{
|
||||
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(dev);
|
||||
+ struct rockchip_usb2phy_port *rport;
|
||||
+
|
||||
+ rport = rockchip_usb2phy_find_switch_port(rphy);
|
||||
+ if (!rport || !rport->ext_supply)
|
||||
+ return sysfs_emit(buf, "unsupported\n");
|
||||
+
|
||||
+ return sysfs_emit(buf, "%d\n", regulator_is_enabled(rport->ext_supply));
|
||||
+}
|
||||
+
|
||||
+static ssize_t usb_switch_ext_store(struct device *dev,
|
||||
+ struct device_attribute *attr,
|
||||
+ const char *buf, size_t count)
|
||||
+{
|
||||
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(dev);
|
||||
+ struct rockchip_usb2phy_port *rport;
|
||||
+ unsigned int val;
|
||||
+ int ret;
|
||||
+
|
||||
+ rport = rockchip_usb2phy_find_switch_port(rphy);
|
||||
+ if (!rport || !rport->ext_supply)
|
||||
+ return -ENOTSUPP;
|
||||
+
|
||||
+ ret = kstrtouint(buf, 10, &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (val)
|
||||
+ ret = regulator_enable(rport->ext_supply);
|
||||
+ else
|
||||
+ ret = regulator_disable(rport->ext_supply);
|
||||
+
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "failed to %s ext-supply: %d\n",
|
||||
+ val ? "enable" : "disable", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ dev_info(dev, "ext-supply %s\n", val ? "enabled" : "disabled");
|
||||
+
|
||||
+ return count;
|
||||
+}
|
||||
+static DEVICE_ATTR_RW(usb_switch_ext);
|
||||
+
|
||||
+static struct attribute *rockchip_usb2phy_switch_attrs[] = {
|
||||
+ &dev_attr_usb_switch_gpio.attr,
|
||||
+ &dev_attr_usb_switch_ext.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group rockchip_usb2phy_switch_group = {
|
||||
+ .attrs = rockchip_usb2phy_switch_attrs,
|
||||
+};
|
||||
+
|
||||
static irqreturn_t rockchip_usb2phy_id_irq(int irq, void *data)
|
||||
{
|
||||
struct rockchip_usb2phy_port *rport = data;
|
||||
@@ -1281,6 +1413,34 @@
|
||||
|
||||
mutex_init(&rport->mutex);
|
||||
|
||||
+ /*
|
||||
+ * Get optional switch GPIO for USB signal routing.
|
||||
+ * Some devices use a GPIO to multiplex USB signals between
|
||||
+ * an external USB port and an internal device (like WiFi).
|
||||
+ * Default to internal device (GPIO low) on init.
|
||||
+ */
|
||||
+ rport->switch_gpio = devm_fwnode_gpiod_get(rphy->dev,
|
||||
+ of_fwnode_handle(child_np),
|
||||
+ "switch", GPIOD_OUT_LOW,
|
||||
+ "usb-switch");
|
||||
+ if (IS_ERR(rport->switch_gpio)) {
|
||||
+ if (PTR_ERR(rport->switch_gpio) != -ENOENT)
|
||||
+ dev_warn(rphy->dev, "failed to get switch GPIO: %ld\n",
|
||||
+ PTR_ERR(rport->switch_gpio));
|
||||
+ rport->switch_gpio = NULL;
|
||||
+ } else if (rport->switch_gpio) {
|
||||
+ gpiod_set_value_cansleep(rport->switch_gpio, 0);
|
||||
+ dev_info(rphy->dev, "USB switch GPIO initialized, default to internal device\n");
|
||||
+ }
|
||||
+
|
||||
+ rport->ext_supply = devm_regulator_get_optional(&rport->phy->dev, "ext");
|
||||
+ if (IS_ERR(rport->ext_supply)) {
|
||||
+ rport->ext_supply = NULL;
|
||||
+ } else if (rport->ext_supply) {
|
||||
+ regulator_enable(rport->ext_supply);
|
||||
+ dev_info(rphy->dev, "ext-supply regulator initialized, default enabled\n");
|
||||
+ }
|
||||
+
|
||||
rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1);
|
||||
if (rport->mode == USB_DR_MODE_HOST ||
|
||||
rport->mode == USB_DR_MODE_UNKNOWN) {
|
||||
@@ -1480,6 +1640,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Create sysfs for USB switch control */
|
||||
+ if (rockchip_usb2phy_find_switch_port(rphy)) {
|
||||
+ ret = sysfs_create_group(&dev->kobj, &rockchip_usb2phy_switch_group);
|
||||
+ if (ret)
|
||||
+ dev_warn(dev, "failed to create usb_switch sysfs: %d\n", ret);
|
||||
+ }
|
||||
+
|
||||
return PTR_ERR_OR_ZERO(provider);
|
||||
|
||||
put_child:
|
||||
@@ -225,12 +225,35 @@ has_ap_mode() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Alguns clones têm o módulo WiFi USB interno dividindo as linhas de dados
|
||||
# da OTG com a porta USB física através de um mux de hardware (patch 0026
|
||||
# expõe o gpio como sysfs usb_switch_gpio: 0 = WiFi interno, 1 = porta
|
||||
# externa; usb_switch_ext liga o vbus da porta). Seguir o toggle de WiFi,
|
||||
# igual ao SO stock: desabilitar o WiFi libera a porta física pra dongles
|
||||
# e acessórios. No-op em placas sem o mux.
|
||||
set_usb_wifi_mux() {
|
||||
local val="$1" f
|
||||
for f in /sys/devices/platform/*/*usb2phy*/usb_switch_gpio \
|
||||
/sys/devices/platform/*usb2phy*/usb_switch_gpio; do
|
||||
[ -e "${f}" ] && echo "${val}" > "${f}" 2>/dev/null
|
||||
done
|
||||
for f in /sys/devices/platform/*/*usb2phy*/usb_switch_ext \
|
||||
/sys/devices/platform/*usb2phy*/usb_switch_ext; do
|
||||
if [ -e "${f}" ]; then
|
||||
# porta externa ativa (mux=1) precisa do vbus dela ligado
|
||||
if [ "${val}" = "1" ]; then echo 1 > "${f}" 2>/dev/null; else echo 0 > "${f}" 2>/dev/null; fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
case "${1}" in
|
||||
enable)
|
||||
rfkill unblock wifi
|
||||
set_usb_wifi_mux 0
|
||||
;;
|
||||
disable)
|
||||
rfkill block wifi
|
||||
set_usb_wifi_mux 1
|
||||
;;
|
||||
connect)
|
||||
if [ "${WIFI_TYPE}" = "1" ] && \
|
||||
|
||||
Reference in New Issue
Block a user