net: rfkill: rfkill-gpio: Add workaround for faulty interconnect design 70/315970/3
authorMichal Wilczynski <m.wilczynski@samsung.com>
Fri, 6 Dec 2024 16:51:00 +0000 (17:51 +0100)
committerMichal Wilczynski <m.wilczynski@samsung.com>
Tue, 10 Dec 2024 19:02:50 +0000 (20:02 +0100)
Vendor provided custom rfkill drivers seems to restrict operation of
rfkill to only enable the Wi-Fi/Bluetooth at the start. The flipping
on/off is restricted. Additionally they include a lot of delays when
accessing GPIO controller pcal6408ahk_c. This seems to indicate some
instability in the hardware.

So I spent some more time trying to analyze this, and here's what I
found:
1) The delays are not really needed. The GPIO controller and i2c bus
   seems to work correctly as designed. So I don't think any
   modifications in the GPIO/i2c drivers are appropriate.
2) The thing that is really needed is the state change on the GPIO line
   connected to the wireless chip i.e the value on the GPIO is already 1
   on the boot, but it needs to go from 0->1 for Wi-Fi chip to get up
   properly.

In the new implementation work around this by introducing new option
'edge-triggered'. This allows for the the state change on GPIO 0->1,
which seems to be required by the wireless chip to start working
correctly.

There is still msleep() required between flipping GPIO values, it seems
like there is no way around this.

Nevertheless in the new implementation it should be possible to turn the
power on/off to Wi-Fi and Bluetooth programatically, through this
crashed the driver in my testing.

Change-Id: I1181edf61d036e91aad8d7bb9bd79684296384dd
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
net/rfkill/rfkill-gpio.c

index 4e32d659524e0d82c85cd4c82132cf18c24c3adf..d1980962ee3f07d09686ca49d009a73e3517134a 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/slab.h>
 #include <linux/acpi.h>
 #include <linux/gpio/consumer.h>
+#include <linux/delay.h>
 
 struct rfkill_gpio_data {
        const char              *name;
@@ -25,8 +26,17 @@ struct rfkill_gpio_data {
        struct clk              *clk;
 
        bool                    clk_enabled;
+       bool                    blocked;
+       bool                    edge_triggered;
 };
 
+static void rfkill_gpiod_toggle(struct gpio_desc *desc, bool blocked)
+{
+       gpiod_set_value_cansleep(desc, blocked);
+       msleep(10);
+       gpiod_set_value_cansleep(desc, !blocked);
+}
+
 static int rfkill_gpio_set_power(void *data, bool blocked)
 {
        struct rfkill_gpio_data *rfkill = data;
@@ -34,8 +44,17 @@ static int rfkill_gpio_set_power(void *data, bool blocked)
        if (!blocked && !IS_ERR(rfkill->clk) && !rfkill->clk_enabled)
                clk_enable(rfkill->clk);
 
-       gpiod_set_value_cansleep(rfkill->shutdown_gpio, !blocked);
-       gpiod_set_value_cansleep(rfkill->reset_gpio, !blocked);
+       if (rfkill->edge_triggered &&
+           rfkill->blocked != blocked) {
+               rfkill_gpiod_toggle(rfkill->shutdown_gpio, blocked);
+               rfkill_gpiod_toggle(rfkill->reset_gpio, blocked);
+
+               rfkill->blocked = blocked;
+       } else {
+               gpiod_set_value_cansleep(rfkill->shutdown_gpio, !blocked);
+               gpiod_set_value_cansleep(rfkill->reset_gpio, !blocked);
+       }
+
 
        if (blocked && !IS_ERR(rfkill->clk) && rfkill->clk_enabled)
                clk_disable(rfkill->clk);
@@ -106,6 +125,9 @@ static int rfkill_gpio_probe(struct platform_device *pdev)
                        return ret;
        }
 
+       rfkill->edge_triggered =
+               device_property_read_bool(&pdev->dev, "edge-triggered");
+
        rfkill->clk = devm_clk_get(&pdev->dev, NULL);
 
        gpio = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_ASIS);
@@ -140,6 +162,8 @@ static int rfkill_gpio_probe(struct platform_device *pdev)
        if (!rfkill->rfkill_dev)
                return -ENOMEM;
 
+       rfkill->blocked = true;
+
        ret = rfkill_register(rfkill->rfkill_dev);
        if (ret < 0)
                goto err_destroy;