【问题标题】:Why do my calls to gpiod_set_value return an "invalid GPIO" error?为什么我对 gpiod_set_value 的调用返回“无效 GPIO”错误?
【发布时间】:2020-08-11 08:40:35
【问题描述】:

我正在尝试为 OV2680 摄像头传感器编写驱动程序。我想打开一些 GPIO 引脚作为其->probe() 函数中的步骤之一。这些 GpioIo() 管脚在 DSDT 表中声明,如下所示(对于 OV2680 所依赖的设备;请参阅full DSDT table

        Device (PMI1)
        {
            Name (_ADR, Zero)  // _ADR: Address
            Name (_HID, "INT3472")  // _HID: Hardware ID
            Name (_CID, "INT3472")  // _CID: Compatible ID
            Name (_DDN, "INCL-CRDD")  // _DDN: DOS Device Name
            Name (_UID, One)  // _UID: Unique ID
            Method (_CRS, 0, NotSerialized)  // _CRS: Current Resource Settings
            {
                Name (SBUF, ResourceTemplate ()
                {
                    GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly,
                        "\\_SB.PCI0.GPI0", 0x00, ResourceConsumer, ,
                        )
                        {   // Pin list
                            0x0079
                        }
                    GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly,
                        "\\_SB.PCI0.GPI0", 0x00, ResourceConsumer, ,
                        )
                        {   // Pin list
                            0x007A
                        }
                    GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly,
                        "\\_SB.PCI0.GPI0", 0x00, ResourceConsumer, ,
                        )
                        {   // Pin list
                            0x008F
                        }
                })
                Return (SBUF) /* \_SB_.PCI0.PMI1._CRS.SBUF */
            }
        }

        Device (CAM1)
        {
            Name (_ADR, Zero)  // _ADR: Address
            Name (_HID, "OVTI2680")  // _HID: Hardware ID
            Name (_CID, "OVTI2680")  // _CID: Compatible ID
            Name (_DDN, "OV2680-CRDD")  // _DDN: DOS Device Name
            Name (_UID, One)  // _UID: Unique ID
            Name (_DEP, Package (0x02)  // _DEP: Dependencies
            {
                PMI1, 
                I2C2
            })
            Name (_PLD, Package (0x01)  // _PLD: Physical Location of Device
            {
                ToPLD (
                    PLD_Revision           = 0x2,
                    PLD_IgnoreColor        = 0x1,
                    PLD_Red                = 0x0,
                    PLD_Green              = 0x0,
                    PLD_Blue               = 0x0,
                    PLD_Width              = 0x0,
                    PLD_Height             = 0x0,
                    PLD_UserVisible        = 0x1,
                    PLD_Dock               = 0x0,
                    PLD_Lid                = 0x0,
                    PLD_Panel              = "FRONT",
                    PLD_VerticalPosition   = "CENTER",
                    PLD_HorizontalPosition = "RIGHT",
                    PLD_Shape              = "VERTICALRECTANGLE",
                    PLD_GroupOrientation   = 0x0,
                    PLD_GroupToken         = 0x0,
                    PLD_GroupPosition      = 0x0,
                    PLD_Bay                = 0x0,
                    PLD_Ejectable          = 0x1,
                    PLD_EjectRequired      = 0x1,
                    PLD_CabinetNumber      = 0x0,
                    PLD_CardCageNumber     = 0x0,
                    PLD_Reference          = 0x0,
                    PLD_Rotation           = 0x0,
                    PLD_Order              = 0x0,
                    PLD_VerticalOffset     = 0xFFFF,
                    PLD_HorizontalOffset   = 0xFFFF)

            })
            Method (_CRS, 0, NotSerialized)  // _CRS: Current Resource Settings
            {
                Name (SBUF, ResourceTemplate ()
                {
                    I2cSerialBusV2 (0x0010, ControllerInitiated, 0x00061A80,
                        AddressingMode7Bit, "\\_SB.PCI0.I2C2",
                        0x00, ResourceConsumer, , Exclusive,
                        )
                })
                Return (SBUF) /* \_SB_.PCI0.CAM1._CRS.SBUF */
            }
        }

注意没有 _DSD 段,这意味着我必须在驱动程序代码according to the documentation 中明确声明它们。那没问题;我有这个 ACPI 设备的struct acpi_device(通过抓取驱动程序匹配的 OV2680 设备的依赖项),所以我可以这样做并按照文档说明使用acpi_dev_add_driver_gpios() 添加它们。我的问题出现在Getting GPIO Descriptor 阶段;文档说要使用gpiod_get_index(),该函数需要struct device,而不是struct acpi_device。我试图通过传递struct acpi_device::dev 成员来实现这一点,但是尽管这样做时我没有收到任何错误消息,但当我设置 GPIO 引脚时实际上似乎没有发生任何事情,所以我不认为它是工作。

鉴于这是特定于硬件的,我不确定 MRE 是否有用,但这里有一个应该编译并成功插入的驱动程序:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>

static const struct acpi_gpio_params gpio1 = {0, 0, false};
static const struct acpi_gpio_params gpio2 = {1, 0, false};
static const struct acpi_gpio_params gpio3 = {2, 0, false};

static const struct acpi_gpio_mapping int3472_acpi_gpios[] = {
    {"gpio1", &gpio1, 1},
    {"gpio2", &gpio2, 1},
    {"gpio3", &gpio3, 1},
    {}
};

static int ov2680_probe(struct i2c_client *client)
{    
    /*
     * The driver will match the OV2680 device, but the GPIO
     * pins lie in its dependent INT3472, so we need to walk
     * up the dependencies to find that device.
    */
   struct acpi_device *int3472_device;

   /* get ACPI handle of OV2680 device */
   struct acpi_handle *dev_handle = ACPI_HANDLE(&client->dev);

   /* Get dependent devices */
   struct acpi_handle_list dep_devices;
   acpi_evaluate_reference(dev_handle, "_DEP", NULL, &dep_devices);

   int i;
   for (i=0; i < dep_devices.count; i++) {
       struct acpi_device_info *devinfo;
       acpi_get_object_info(dep_devices.handles[i], &devinfo);

       if (devinfo->valid & ACPI_VALID_HID && !strcmp(devinfo->hardware_id.string, "INT3472")) {
           acpi_bus_get_device(dep_devices.handles[i], &int3472_device);
       }
   }

   int ret;

   ret = acpi_dev_add_driver_gpios(int3472_device, int3472_acpi_gpios);

   struct gpio_desc *gpiod1, *gpiod2, *gpiod3;

   gpiod1 = gpiod_get_index(&int3472_device->dev, NULL, 0, GPIOD_ASIS);
   gpiod2 = gpiod_get_index(&int3472_device->dev, NULL, 1, GPIOD_ASIS);
   gpiod3 = gpiod_get_index(&int3472_device->dev, NULL, 2, GPIOD_ASIS);
   
   gpiod_set_value_cansleep(gpiod1, 1);
   gpiod_set_value_cansleep(gpiod2, 1);
   gpiod_set_value_cansleep(gpiod3, 1);

   return 0;
}

static int ov2680_remove(struct i2c_client *client)
{
    /*
     * Code goes here to get acpi_device, turn off all
     * the GPIO pins, remove them from the ACPI device
     * and whatnot
     */

    return 0;
}

static const struct acpi_device_id ov2680_acpi_match[] = {
    {"OVTI2680", 0},
    { }
};
MODULE_DEVICE_TABLE(acpi, ov2680_acpi_match);

static struct i2c_driver ov2680_driver = {
    .driver = {
        .name = "ov2680",
        .acpi_match_table = ov2680_acpi_match,
    },
    .probe_new = ov2680_probe,
    .remove = ov2680_remove,
};
module_i2c_driver(ov2680_driver);

MODULE_AUTHOR("Dan Scally");
MODULE_DESCRIPTION("A driver for OmniVision 2680 sensors");
MODULE_LICENSE("GPL");

dmesg 报告在添加引脚或任何内容时没有问题,但对 gpiod_set_value_cansleep() 的调用会在此处引发错误:

[4840.774633] gpiod_set_value_cansleep: GPIO 无效(错误指针)

这是因为对 gpiod_get_index() 的调用失败,因此 GPIO 描述符无效。

问题:

  1. 我使用&amp;int3472-&gt;device 作为gpiod_get_index() 的参数是正确的方法吗?
  2. 如果是这样,什么可能导致对gpiod_get_index() 的调用失败?

编辑:

grep -H 15 /sys/bus/acpi/devices/*/status的输出

/sys/bus/acpi/devices/ACPI000C:00/status:15
/sys/bus/acpi/devices/BOSC0200:00/status:15
/sys/bus/acpi/devices/device:16/status:15
/sys/bus/acpi/devices/device:17/status:15
/sys/bus/acpi/devices/device:32/status:15
/sys/bus/acpi/devices/INT33D3:00/status:15
/sys/bus/acpi/devices/INT33D6:00/status:15
/sys/bus/acpi/devices/INT3400:00/status:15
/sys/bus/acpi/devices/INT340E:00/status:15
/sys/bus/acpi/devices/INT344B:00/status:15
/sys/bus/acpi/devices/INT3472:08/status:15
/sys/bus/acpi/devices/INT3472:09/status:15
/sys/bus/acpi/devices/INT3F0D:00/status:15
/sys/bus/acpi/devices/MSFT0001:00/status:15
/sys/bus/acpi/devices/MSFT0101:00/status:15
/sys/bus/acpi/devices/OVTI2680:00/status:15
/sys/bus/acpi/devices/OVTI5648:00/status:15
/sys/bus/acpi/devices/PNP0103:00/status:15
/sys/bus/acpi/devices/PNP0401:01/status:15
/sys/bus/acpi/devices/PNP0A05:04/status:15
/sys/bus/acpi/devices/PNP0C09:00/status:15
/sys/bus/acpi/devices/PNP0C0C:00/status:15
/sys/bus/acpi/devices/PNP0C0D:00/status:15
/sys/bus/acpi/devices/VPC2004:00/status:15
/sys/bus/acpi/devices/WCOM508C:00/status:15

【问题讨论】:

  • 在这种情况下,对gpiod_set_value_cansleep() 的调用失败,因为对gpiod_get_index() 的调用返回了ERR_PTR() 值。所以问题在于对gpiod_get_index() 的调用,而不是对gpiod_set_value_cansleep() 的调用。
  • @IanAbbot yeeeeeeaaaaaas 看起来像 gpio_descs 返回无效。原来我没有加载 GPIO 驱动程序,这可能就是原因,所以只需用这些重新构建内核,看看有什么不同(如果有的话)。

标签: linux linux-kernel linux-device-driver gpio


【解决方案1】:

(根据我之前给出的cmets收集答案)

为了澄清起见,我不得不说,从您的 DSDT 我们可以得到以下信息。 PMIC 共有 3 组,即DSCxCLPxPMIx。我相信它们是基于模型的,例如 DesktopLaptop2-in-1。在每种情况下,同一组中的所有 PMIC 都有不同的_UID。从grep -H 15 ... 提供的输出中,我们仅列举了十分之二的实例 INT3472:08INT3472:09(正好是 DSDT 中最后定义的两个)。他们是PMIx,您可以通过grep -H . /sys/bus/acpi/devices/INT3472:*/path查看。

您感兴趣的是PMI1,它使用来自Intel GPIO driver 的三个 GPIO 线,即引脚 121、122 和 143(您可以将它们解码为 Community #2、Group #5 或 GPP_F,相对于组别针1、2、23,这可以帮助你理解_INI通过DSDT中的其他方法接触这些线的方法),并且提供 3 + 7 = 10个根据@ 987654322@.

现在看代码。 _DEP ACPI 方法仅用于链接电源资源,Linux 内核还有其他方法可以劫持其他设备的资源,因为您尝试做的是使用与您正在创建的设备无关的资源驱动程序。

方法是通过ACPI HID查找设备:

struct acpi_device *adev;
struct device *phys_dev;
struct gpio_desc *desc;

...

adev = acpi_dev_get_first_match_dev("INT3472", "1", -1);
if (!adev) {
  pr_err("Oops, we didn't find an ACPI device!\n");
  return -ENODEV;
}

phys_dev = get_device(acpi_get_first_physical_node(adev));
acpi_dev_put(adev);

if (!phys_dev) {
  pr_err("Oops, we didn't find a physical device!\n");
  return -ENODEV;
}

desc = gpiod_get_index(phys_dev, NULL, 0, GPIOD_ASIS);
if (IS_ERR(desc)) {
  pr_err("Something went wrong when retrieving GPIO\n");
  put_device(phys_dev);
  return PTR_ERR(desc);
}

...

gpiod_put(desc);
put_device(phys_dev);

简化此操作的黑客方法(因为您知道设备实例的总线类型和确切名称,但 Linux 不保证它在引导时保持相同)是:

struct device *phys_dev;
struct gpio_desc *desc;

...

phys_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-INT3472:09");
if (!phys_dev) {
  pr_err("Oops, we didn't find a physical device!\n");
  return -ENODEV;
}

desc = gpiod_get_index(phys_dev, NULL, 0, GPIOD_ASIS);
if (IS_ERR(desc)) {
  pr_err("Something went wrong when retrieving GPIO\n");
  put_device(phys_dev);
  return PTR_ERR(desc);
}

...

gpiod_put(desc);
put_device(phys_dev);

旁注:

  1. OV2680 相机传感器已经有 existing driver,扩展它而不是为 ACPI 案例创建特定的分支是有意义的。
  2. 正确的方法是在没有任何黑客攻击的情况下使用资源,即在 PMIC MFD driver 中。

【讨论】:

  • 嗨,@0andriy,这篇文章很有用。gpiod_get_index 应该调用 phy_dev 并获取资源,但是我如何调用 adev->dev?像 gpiod_get_index(&adev->dev, ....)?
  • @PoplarGiant 这取决于您获得struct acpi_device 的设备是什么。如果 ACPI 设备是配套设备,acpi_get_first_physical_node() 应该会有所帮助。因此,如果没有看到 ASL,就很难给出正确的答案。
  • 我使用acpi_bus_register_driver(&amp;pmic_driver)在modue_init中注册了一个驱动,并在pmic_driver_add(struct acpi_device* adev)中获取了acpi_device adev。而且我设法使用与 acpi_get_first_physical_node 的实现类似的代码获取 gpio 资源,但我无法在驱动程序/媒体/i2c/xxx 中调用未导出的 acpi_get_first_physical_node。我目前复制 acpi_get_first_physical_node 的代码,这看起来很hacky。我想避免 hacky 代码。
  • 我发现adev有空fwnode,phsy_dev有非空fwnode。 fwnode 似乎是获取 gpio 资源所必需的。我猜我错过了从 pmic_driver_add() 获得的 adev 和实际的 phsy_dev 节点之间的一些必要绑定。你能分享一下这方面的想法吗?
  • @PoplarGiant 您使用的是纯 ACPI 驱动程序模型,不应该使用。将您的 PMIC 驱动程序切换为 平台驱动程序。 WRT导出函数,看来我在想别的东西。我需要考虑一下。
猜你喜欢
  • 2019-11-17
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-18
  • 1970-01-01
  • 2015-12-26
  • 2017-02-05
相关资源
最近更新 更多