[linux 驱动]platform总线设备驱动详解与实战

目录

[1 描述](#1 描述)

[2 结构体](#2 结构体)

[2.1 bus_type](#2.1 bus_type)

[2.2 platform_bus_type](#2.2 platform_bus_type)

[2.2.1 platform_match](#2.2.1 platform_match)

[2.2.2 platform_uevent](#2.2.2 platform_uevent)

[2.2.3 platform_dma_configure](#2.2.3 platform_dma_configure)

[2.2.4 platform_dev_pm_ops](#2.2.4 platform_dev_pm_ops)

[2.3 platform_driver](#2.3 platform_driver)

[2.4 platform_device](#2.4 platform_device)

[3 platform注册](#3 platform注册)

[3.1 platform_driver_register](#3.1 platform_driver_register)

[3.1.1 __platform_driver_register分析](#3.1.1 __platform_driver_register分析)

[3.1.1.1 platform_drv_probe](#3.1.1.1 platform_drv_probe)

[3.1.1.2 platform_drv_remove](#3.1.1.2 platform_drv_remove)

[3.1.1.3 platform_drv_shutdown](#3.1.1.3 platform_drv_shutdown)

[3.1.1.4 driver_register](#3.1.1.4 driver_register)

[3.2 platform_driver_unregister](#3.2 platform_driver_unregister)

[3.3 platform_device_register](#3.3 platform_device_register)

[3.4 platform_device_unregister](#3.4 platform_device_unregister)

[4 jw_io_core.c 驱动分析](#4 jw_io_core.c 驱动分析)

4.1设备树

[4.1.1 设备树文件](#4.1.1 设备树文件)

[4.2 驱动分析](#4.2 驱动分析)

[4.2.1 驱动框架](#4.2.1 驱动框架)

[4.2.2 platform驱动文件系统](#4.2.2 platform驱动文件系统)

[4.2.3 probe参数传递](#4.2.3 probe参数传递)

[4.2.4 驱动设备匹配分析](#4.2.4 驱动设备匹配分析)


1 描述

下图是Linux总线、驱动和设备模式,向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。

总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、SPI、USB 等总线。在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。

platform总线在drivers/base/platform.c文件中描述。

**/sys/bus/**目录下包含了有关系统总线的各类信息。

rk3399_Android11:/sys/bus # ls
amba  clockevents  container  event_source  gpio  i2c  mdio_bus  mipi-dsi  mmc_rpmb  pci          platform       scsi  spi  usb-serial
cec   clocksource  cpu        genpd         hid   iio  media     mmc       nvmem     pci_express  scmi_protocol  sdio  usb  workqueue
rk3399_Android11:/sys/bus #

**/sys/bus/platform/**目录下保存着当前板子 platform 总线下的设备和驱动,其中devices 子目录为 platform 设备,drivers 子目录为 plartofm 驱动。

rk3399_Android11:/sys/bus/platform # ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent
rk3399_Android11:/sys/bus/platform # ls devices/
110000.ramoops            fe330000.sdhci                              ff420030.pwm                   ff788000.gpio3     ffa5c000.qos               ffa8f400.nocp-vio0-msch1  firmware:optee           serial8250
Fixed\ MDIO\ bus.0        fe380000.usb                                ff630000.dfi                   ff790000.gpio4     ffa60080.qos               ffa8f800.nocp-vio1-msch1  gpio-keys                snd-soc-dummy
adc_keys                  fe3a0000.usb                                ff650000.vepu                  ff7c0000.phy       ffa60100.qos               ffa90000.qos              hdmi-audio-codec.1.auto  timer
alarmtimer                fe3c0000.usb                                ff650400.vdpu                  ff800000.phy       ffa60180.qos               ffa98000.qos              hdmi-audio-codec.5.auto  usb@fe800000
amba                      fe3e0000.usb                                ff650800.iommu                 ff848000.watchdog  ffa70000.qos               ffaa0000.qos              ion.0.auto               usb@fe900000
armv7sec.0                fe800000.dwc3                               ff660000.rkvdec                ff850000.rktimer   ffa70080.qos               ffaa0080.qos              jw_io_init               vcc-phy-regulator
backlight                 fe900000.dwc3                               ff660480.iommu                 ff870000.spdif     ffa74000.qos               ffaa8000.qos              mpp-srv                  vcc-pwr-1v8-regulator
charge-animation          fec00000.dp                                 ff670000.iep                   ff880000.i2s       ffa76000.qos               ffaa8080.qos              pinctrl                  vcc-sys
cpufreq-dt                ff100000.saradc                             ff670800.iommu                 ff8a0000.i2s       ffa86000.nocp-cci-msch0    ffab0000.qos              pmu_a53                  vcc1v8-s0
cpuinfo                   ff110000.i2c                                ff680000.rga                   ff8b8000.rng       ffa86400.nocp-gpu-msch0    ffab0080.qos              pmu_a72                  vcc3v3-sys
ddr_timing                ff180000.serial                             ff690000.efuse                 ff8d0000.sram      ffa86800.nocp-hp-msch0     ffab8000.qos              psci                     vcc5v0-host-regulator
display-subsystem         ff260000.tsadc                              ff720000.gpio0                 ff8f0000.vop       ffa86c00.nocp-lp-msch0     ffac0000.qos              reg-dummy                vdd-log
dmc                       ff310000.power-management                   ff730000.gpio1                 ff8f3f00.iommu     ffa87000.nocp-video-msch0  ffac0080.qos              regulatory.0             wireless-bluetooth
dw-hdmi-cec.3.auto        ff310000.power-management:power-controller  ff770000.syscon                ff900000.vop       ffa87400.nocp-vio0-msch0   ffac8000.qos              rk-headset               wireless-wlan
dw-hdmi-hdcp.4.auto       ff320000.syscon                             ff770000.syscon:io-domains     ff903f00.iommu     ffa87800.nocp-vio1-msch0   ffac8080.qos              rk808-clkout             xhci-hcd.6.auto
dw-hdmi-i2s-audio.2.auto  ff320000.syscon:io-domains                  ff770000.syscon:phy@f780       ff914000.iommu     ffa8e000.nocp-cci-msch1    ffad0000.qos              rk808-regulator
es8323-sound              ff320000.syscon:reboot-mode                 ff770000.syscon:pvtm           ff924000.iommu     ffa8e400.nocp-gpu-msch1    ffad8080.qos              rk808-rtc
fe300000.ethernet         ff3c0000.i2c                                ff770000.syscon:usb2-phy@e450  ff940000.hdmi      ffa8e800.nocp-hp-msch1     ffae0000.qos              rockchip-suspend
fe310000.dwmmc            ff3d0000.i2c                                ff770000.syscon:usb2-phy@e460  ff9a0000.gpu       ffa8ec00.nocp-lp-msch1     fiq-debugger              rockchip-system-monitor
fe320000.dwmmc            ff420020.pwm                                ff780000.gpio2                 ffa58000.qos       ffa8f000.nocp-video-msch1  fiq_debugger.0            sdio-pwrseq
rk3399_Android11:/sys/bus/platform # ls devices/jw_io_init/
driver  driver_override  led_display  mic_switch  modalias  of_node  power  subsystem  uevent
rk3399_Android11:/sys/bus/platform # ls drivers
4g-modem-platdata  dw-mipi-dsi       inno-mipi-dphy        mpp_vdpu2            remotectl-pwm     rk618-hdmi          rk628-rgb         rkisp_hw                rockchip-i2s           rockchip-rng             serial8250
RKNPU              dw_mmc            inno-video-combo-phy  mpp_vepu1            rfkill_bt         rk618-lvds          rk630-tve         rknand                  rockchip-i2s-tdm       rockchip-saradc          snd-soc-dummy
adc_keys           dw_wdt            innohdmi-rockchip     mpp_vepu2            rga2              rk618-rgb           rk805-pinctrl     rockchip,bus            rockchip-inno-dwc3     rockchip-snps-pcie3-phy  soc-audio
ahci               dwc2              jw_io_control         naneng-combphy       rga3_core0        rk618-scaler        rk805-pwrkey      rockchip,dmcdbg         rockchip-iodomain      rockchip-spdif           spdif-dir
alarmtimer         dwc3              leds-gpio             of_fixed_clk         rga3_core1        rk618-vif           rk808-clkout      rockchip-cdndp-sound    rockchip-lvds          rockchip-spi             spdif-dit
arm-scmi           dwc3-of-simple    mali                  of_fixed_factor_clk  rgb13h-flash      rk628-combrxphy     rk808-regulator   rockchip-clcok-pvtm     rockchip-mipi-csi2     rockchip-system-monitor  spirit\ module
armv7sec           dwhdmi-rockchip   mali-utgard           ohci-platform        rk-crypto         rk628-combtxphy     rk817-battery     rockchip-cpuinfo        rockchip-mipi-dphy-rx  rockchip-thermal         spirit-io
armv8-pmu          dwmmc_rockchip    mbox-scpi             panel-simple         rk-fiq-debugger   rk628-cru           rk817-charger     rockchip-csi2-dphy-hw   rockchip-nocp          rockchip-tve             sram
asoc-simple-card   ehci-platform     mh248                 poweroff-gpio        rk-hdmi-dp-sound  rk628-csi           rk817-codec       rockchip-dfi            rockchip-pcie          rockchip-typec-phy       syscon
bt-sco             fiq_debugger      midgard               pwm-backlight        rk-hdmi-sound     rk628-dsi           rk818-battery     rockchip-dmc            rockchip-pcie-phy      rockchip-u3phy           syscon-reboot-mode
cdn-dp             gpio-clk          mpp-iep2              pwm-clock            rk-multicodecs    rk628-efuse         rk818-charger     rockchip-dp             rockchip-pdm           rockchip-usb-phy         wlan-platdata
cpufreq-dt         gpio-keys         mpp_jpgdec            pwm-regulator        rk3328-codec      rk628-gvi           rk_codec_digital  rockchip-drm            rockchip-pinctrl       rockchip-usb2phy         xhci-hcd
dummy_codec        gpio-regulator    mpp_rkvdec            pwrseq_emmc          rk3368-mailbox    rk628-hdmi          rk_gmac-dwmac     rockchip-edp-phy-grf    rockchip-pm            rockchip-vop
dw-apb-uart        hdmi-audio-codec  mpp_rkvdec2           pwrseq_simple        rk3368-thermal    rk628-hdmirx        rk_iommu          rockchip-edpphy-naneng  rockchip-pm-domain     rockchip-vop2
dw-hdmi-cec        i2c-gpio          mpp_rkvenc            ramoops              rk3x-i2c          rk628-lvds          rkcifhw           rockchip-efuse          rockchip-pvtm          rockchip_headset
dw-hdmi-hdcp       iep               mpp_service           reg-dummy            rk618-cru         rk628-pinctrl       rkisp             rockchip-emmc-phy       rockchip-pwm           sdhci-arasan
dw-hdmi-i2s-audio  inno-hdmi-phy     mpp_vdpu1             reg-fixed-voltage    rk618-dsi         rk628-post-process  rkisp1            rockchip-gpio           rockchip-rgb           sdhci-dwcmshc
rk3399_Android11:/sys/bus/platform # ls drivers/jw_io_control/
bind  jw_io_init  uevent  unbind
rk3399_Android11:/sys/bus/platform # ls drivers/jw_io_control/jw_io_init/
driver  driver_override  led_display  mic_switch  modalias  of_node  power  subsystem  uevent
rk3399_Android11:/sys/bus/platform #

2 结构体

2.1 bus_type

struct bus_type 在 Linux 内核中是一个非常重要的结构体,它用于表示和管理不同类型的总线。在 Linux 的设备模型中,总线(bus)是一个关键的抽象概念,它用于将设备和驱动程序连接起来。每种类型的总线(如 platporm、PCI、USB、I2C 等)都由一个 bus_type 结构体来表示,这个结构体包含了该类型总线所需的所有信息和操作函数。

cpp 复制代码
 122 struct bus_type {
 123         const char              *name;
 124         const char              *dev_name;
 125         struct device           *dev_root;
 126         const struct attribute_group **bus_groups;
 127         const struct attribute_group **dev_groups;
 128         const struct attribute_group **drv_groups;
 129 
 130         int (*match)(struct device *dev, struct device_driver *drv);
 131         int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 132         int (*probe)(struct device *dev);
 133         void (*sync_state)(struct device *dev);
 134         int (*remove)(struct device *dev);
 135         void (*shutdown)(struct device *dev);
 136 
 137         int (*online)(struct device *dev);
 138         int (*offline)(struct device *dev);
 139 
 140         int (*suspend)(struct device *dev, pm_message_t state);
 141         int (*resume)(struct device *dev);
 142 
 143         int (*num_vf)(struct device *dev);
 144 
 145         int (*dma_configure)(struct device *dev);
 146 
 147         const struct dev_pm_ops *pm;
 148 
 149         const struct iommu_ops *iommu_ops;
 150 
 151         struct subsys_private *p;
 152         struct lock_class_key lock_key;
 153 
 154         bool need_parent_lock;
 155 
 156         ANDROID_KABI_RESERVE(1);
 157         ANDROID_KABI_RESERVE(2);
 158         ANDROID_KABI_RESERVE(3);
 159         ANDROID_KABI_RESERVE(4);
 160 };

bus_type结构体的成员解释。

name 是总线的名称,如 "pci"、"usb" 等,用于在系统中唯一标识总线类型。

dev_name 通常用于构造设备在系统中的名称,但在这个结构体定义中,它可能不是所有情况下都使用或必需。其确切用途可能取决于内核版本和具体实现。

dev_root:指向该总线类型下所有设备的根设备。在一些总线类型中,设备可能会以树状结构组织,其中 dev_root 表示这棵树的根节点。

bus_groups, dev_groups, drv_groups:这些字段分别指向属性组的数组,这些属性组可以通过 sysfs 接口被用户空间访问。bus_groups 包含总线级别的属性,dev_groups 包含设备级别的属性,而 drv_groups 包含驱动程序级别的属性。

match、uevent、probe、sync_state、remove、shutdown、online、offline、suspend、resume等函数指针分别指向处理设备添加、移除、状态同步、电源管理等操作的函数。这些函数定义了当设备或驱动程序与总线交互时应执行的行为。

num_vf 可能用于某些支持虚拟功能(如 SR-IOV)的总线,用于返回设备的虚拟功能数量。

dma_configure 用于配置设备的 DMA(直接内存访问)特性。

pm:指向 dev_pm_ops 结构体的指针,该结构体包含了一组用于电源管理的函数指针,如设备挂起、恢复等操作。

iommu_ops:指向 iommu_ops 结构体的指针,该结构体定义了一组用于管理输入/输出内存管理单元(IOMMU)的函数。IOMMU 用于在物理内存和设备地址空间之间建立映射。

p:指向 subsys_private 结构体的指针,这通常是一个私有数据结构,用于存储与总线子系统相关的私有信息。

lock_key:用于锁类别的键,这有助于调试和性能分析,确保锁的正确使用和避免死锁。

need_parent_lock:一个布尔值,指示在访问总线上的设备时是否需要锁定其父设备。这有助于处理总线层次结构中的并发访问。

ANDROID_KABI_RESERVE:这些是 Android 特有的保留字段,用于确保在 Android 和 Linux 内核之间的接口保持稳定。这些字段当前未使用,但保留以供将来扩展。

2.2 platform_bus_type

platform_bus_type 结构体是 struct bus_type 的一个实例,专门用于表示和管理平台总线(platform bus)。在 Linux 内核中,平台总线是一种特殊的总线,用于连接那些不直接挂接在标准总线(如 PCI、USB)上的设备。这些设备通常是通过设备树在内核启动时被识别和初始化的。

cpp 复制代码
1179 struct bus_type platform_bus_type = {
1180         .name           = "platform",
1181         .dev_groups     = platform_dev_groups,
1182         .match          = platform_match,
1183         .uevent         = platform_uevent,
1184         .dma_configure  = platform_dma_configure,
1185         .pm             = &platform_dev_pm_ops,
1186 };

platform_bus_type结构体的成员解释。

.name = "platform": 指定了总线类型的名称为 "platform"。这是该总线在内核中的唯一标识符。

.dev_groups = platform_dev_groups;: 指向一个包含属性组的数组,这些属性组与平台总线上的设备相关。用户空间程序可以通过 sysfs 接口访问这些属性组,以获取或设置设备的配置信息。

.match = platform_match;: 指向一个函数,该函数用于在设备添加到平台总线时,检查设备与已注册的驱动程序是否匹配。如果匹配,内核将调用该驱动程序的 probe 函数来初始化设备。

.uevent = platform_uevent;: 指向一个函数,该函数用于生成并发送 uevent 消息。当设备被添加到平台总线或从中移除时,会触发这些消息。用户空间程序可以监听这些消息,并据此执行相应的操作。

.dma_configure = platform_dma_configure;: 指向一个函数,该函数用于配置平台设备上的 DMA(直接内存访问)特性。不过,请注意,并非所有平台设备都需要 DMA 支持,因此这个字段可能在一些情况下不会被使用。

.pm = &platform_dev_pm_ops;: 指向一个 dev_pm_ops 结构体,该结构体包含了一组用于电源管理的函数指针。这些函数定义了平台设备在电源管理(如挂起、恢复)时的行为。

2.2.1 platform_match

函数 platform_match 是 Linux 内核中用于匹配平台设备(platform_device)和平台驱动程序(platform_driver)的函数。它是 struct bus_type 结构体中 match 字段的一个实现,专门用于平台总线(platform_bus_type)。该函数的目的是确定给定的设备(dev)和驱动程序(drv)是否匹配,以便内核可以将设备绑定到正确的驱动程序上。

cpp 复制代码
988 static int platform_match(struct device *dev, struct device_driver *drv)
 989 {
 990         struct platform_device *pdev = to_platform_device(dev);
 991         struct platform_driver *pdrv = to_platform_driver(drv);
 992 
 993         /* When driver_override is set, only bind to the matching driver */
 994         if (pdev->driver_override)
 995                 return !strcmp(pdev->driver_override, drv->name);
 996 
 997         /* Attempt an OF style match first */
 998         if (of_driver_match_device(dev, drv))
 999                 return 1;
1000 
1001         /* Then try ACPI style match */
1002         if (acpi_driver_match_device(dev, drv))
1003                 return 1;
1004 
1005         /* Then try to match against the id table */
1006         if (pdrv->id_table)
1007                 return platform_match_id(pdrv->id_table, pdev) != NULL;
1008 
1009         /* fall-back to driver name match */
1010         return (strcmp(pdev->name, drv->name) == 0);
1011 }

使用 of_driver_match_device(dev, drv) 尝试进行基于设备树的匹配。如果设备树中存在与驱动程序相匹配的条目,则返回 1 表示成功匹配

2.2.2 platform_uevent

函数 platform_uevent 是 Linux 内核中用于为平台设备(platform_device)生成并发送 uevent 消息的函数。uevent 是一种内核向用户空间发送事件的机制,允许用户空间程序响应设备的添加、移除或其他变化。在这个特定的函数中,它主要用于为平台设备构建并添加一个 MODALIAS 环境变量,该变量可以帮助用户空间的 udev 或其他守护进程找到并加载适当的驱动程序

cpp 复制代码
942 static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)
 943 {
 944         struct platform_device  *pdev = to_platform_device(dev);
 945         int rc;
 946 
 947         /* Some devices have extra OF data and an OF-style MODALIAS */
 948         rc = of_device_uevent_modalias(dev, env);
 949         if (rc != -ENODEV)
 950                 return rc;
 951 
 952         rc = acpi_device_uevent_modalias(dev, env);
 953         if (rc != -ENODEV)
 954                 return rc;
 955 
 956         add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,
 957                         pdev->name);
 958         return 0;
 959 }

2.2.3 platform_dma_configure

函数 platform_dma_configure 是用于为平台设备(platform_device)配置直接内存访问(DMA)设置的。它接受一个指向 struct device 结构的指针作为参数,这个结构代表了需要配置DMA的设备。函数的主要目的是根据设备的设备树(Device Tree)或ACPI信息(如果可用)来配置DMA属性

cpp 复制代码
1157 int platform_dma_configure(struct device *dev)
1158 {
1159         enum dev_dma_attr attr;
1160         int ret = 0;
1161 
1162         if (dev->of_node) {
1163                 ret = of_dma_configure(dev, dev->of_node, true);
1164         } else if (has_acpi_companion(dev)) {
1165                 attr = acpi_get_dma_attr(to_acpi_device_node(dev->fwnode));
1166                 if (attr != DEV_DMA_NOT_SUPPORTED)
1167                         ret = acpi_dma_configure(dev, attr);
1168         }
1169 
1170         return ret;
1171 }

2.2.4 platform_dev_pm_ops

struct dev_pm_ops 是 Linux 内核中用于定义设备电源管理操作的结构体,它包含了一系列指向电源管理函数的指针,这些函数用于控制设备的电源状态,如挂起、恢复、休眠等

platform_dev_pm_ops 结构体,为平台设备提供了一套基本的电源管理操作,并通过使用USE_PLATFORM_PM_SLEEP_OPS 宏来简化其他电源管理操作的指定

cpp 复制代码
1173 static const struct dev_pm_ops platform_dev_pm_ops = {
1174         .runtime_suspend = pm_generic_runtime_suspend,
1175         .runtime_resume = pm_generic_runtime_resume,
1176         USE_PLATFORM_PM_SLEEP_OPS
1177 };

2.3 platform_driver

struct platform_driver 是 Linux 内核中用于定义平台设备驱动程序的结构体。这个结构体包含了指向各种函数的指针,这些函数是驱动程序与内核交互的关键部分,以及一些用于描述驱动程序本身的信息。

cpp 复制代码
183 struct platform_driver {
184         int (*probe)(struct platform_device *);
185         int (*remove)(struct platform_device *);
186         void (*shutdown)(struct platform_device *);
187         int (*suspend)(struct platform_device *, pm_message_t state);
188         int (*resume)(struct platform_device *);
189         struct device_driver driver;
190         const struct platform_device_id *id_table;
191         bool prevent_deferred_probe;
192 };     

platform_driver结构体的成员解释。

**int (*probe)(struct platform_device *):**这是一个指向函数的指针,该函数是驱动程序的初始化函数。当内核发现与驱动程序匹配的平台设备时,会调用此函数。驱动程序的 probe 函数负责设置设备所需的任何资源(如内存、中断等),并注册设备到内核中,以便其他部分的内核或用户空间程序可以使用它。

**int (*remove)(struct platform_device *):**这是一个指向函数的指针,该函数在设备被移除时调用。它负责释放 probe 函数中分配的所有资源,并清理驱动程序与设备之间的任何关联。

**void (*shutdown)(struct platform_device *):**这是一个指向函数的指针,该函数在系统关闭时调用。它允许驱动程序执行必要的清理工作,以确保在系统关闭过程中设备处于安全状态。

**int (*suspend)(struct platform_device *, pm_message_t state):**这是一个指向函数的指针,该函数在系统进入低功耗状态时调用,如系统挂起或休眠。它允许驱动程序保存设备的状态,并关闭设备以节省电源。state 参数指示了系统即将进入的电源状态。

**int (*resume)(struct platform_device *):**这是一个指向函数的指针,该函数在系统从低功耗状态恢复时调用。它允许驱动程序恢复设备的状态,并重新启用设备。

**struct device_driver driver:**这是一个 device_driver 结构体,它包含了描述驱动程序本身的信息,如名称、总线类型等。这个结构体是通用的,用于内核中的设备驱动模型。

**const struct platform_device_id *id_table:**这是一个指向 platform_device_id 结构体数组的指针,该数组用于匹配设备和驱动程序。每个 platform_device_id 结构体包含一个或多个用于匹配设备名称的字符串,以及一个可选的驱动程序数据指针。如果 id_table 为 NULL,则驱动程序将匹配所有平台设备。

**bool prevent_deferred_probe:**这是一个布尔值,用于控制是否延迟探测设备。如果设置为 true,则驱动程序将不会参与内核的延迟探测机制,这可能会影响设备的初始化和启动顺序。

struct platform_device_id 结构体是用于匹配平台设备和平台驱动程序的一个关键组成部分。

cpp 复制代码
546 struct platform_device_id {
547         char name[PLATFORM_NAME_SIZE];
548         kernel_ulong_t driver_data;
549 };  

struct device_driver 是 Linux 内核中用于描述设备驱动程序的一个结构体。它包含了驱动程序的名称、所属的总线类型、模块所有者、以及一些回调函数等关键信息。

cpp 复制代码
298 struct device_driver {
 299         const char              *name;
 300         struct bus_type         *bus;
 301        
 302         struct module           *owner; 
 303         const char              *mod_name;      /* used for built-in modules */
 304         
 305         bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */
 306         enum probe_type probe_type;
 307 
 308         const struct of_device_id       *of_match_table;
 309         const struct acpi_device_id     *acpi_match_table;
 310 
 311         int (*probe) (struct device *dev);
 312         void (*sync_state)(struct device *dev);
 313         int (*remove) (struct device *dev);
 314         void (*shutdown) (struct device *dev);
 315         int (*suspend) (struct device *dev, pm_message_t state);
 316         int (*resume) (struct device *dev);
 317         const struct attribute_group **groups;
 318         
 319         const struct dev_pm_ops *pm;
 320         void (*coredump) (struct device *dev);
 321         
 322         struct driver_private *p;
 323 
 324         ANDROID_KABI_RESERVE(1);
 325         ANDROID_KABI_RESERVE(2);
 326         ANDROID_KABI_RESERVE(3);
 327         ANDROID_KABI_RESERVE(4);
 328 };

device_driver结构体的成员解释。

**const char *name:**驱动程序的名称,用于在内核中唯一标识该驱动程序。

**struct bus_type *bus:**指向该驱动程序所属的总线类型的指针。总线类型定义了设备如何与系统进行通信,以及设备之间如何相互通信。

**struct module *owner:**指向拥有该驱动程序的模块的指针。如果驱动程序是静态编译进内核的(非模块化),则此指针可能为 NULL。

**const char *mod_name:**用于内置模块的名称,主要用于调试和日志记录。对于非模块化驱动程序,此字段可能不被使用。

**bool suppress_bind_attrs:**一个标志,用于禁用通过 sysfs 接口进行的绑定(bind)和解绑(unbind)操作。这有助于保护系统稳定性,防止用户空间程序意外更改设备绑定。

**enum probe_type probe_type:**指定驱动程序的探测类型。这可能影响驱动程序如何以及何时探测其设备。

**const struct of_device_id *of_match_table; 和 const struct acpi_device_id *acpi_match_table;:**分别用于匹配 Open Firmware (OF) 和 ACPI 描述的设备。这些表允许驱动程序通过设备的硬件描述(如设备树或 ACPI 表)来识别设备。

回调函数:包括 probe、sync_state、remove、shutdown、suspend、resume 等,这些函数是驱动程序与内核交互的关键部分。它们分别在设备发现、状态同步、设备移除、系统关闭、系统休眠和唤醒等情况下被调用。

**const struct attribute_group **groups;:**指向属性组的指针数组,这些属性组用于通过 sysfs 导出驱动程序的配置选项或状态信息。

**const struct dev_pm_ops *pm:**指向电源管理操作表的指针,该表定义了驱动程序在电源管理事件(如系统休眠和唤醒)中的行为。

**void (*coredump) (struct device *dev):**一个可选的回调函数,用于在系统崩溃时转储设备的核心信息。

**struct driver_private *p:**指向驱动程序私有数据的指针,这通常用于存储驱动程序内部使用的数据结构或状态信息。

**ANDROID_KABI_RESERVE 宏:**这些宏在 Android 内核中用于保留结构体的未来扩展空间。它们不直接影响驱动程序的正常功能,但为将来的内核更新或新功能的添加提供了灵活性。通过在结构体末尾添加这些保留字段,Android 可以确保在不影响现有驱动程序兼容性的情况下,向 struct device_driver 结构体添加新成员。这些保留字段通常不会被普通驱动程序代码直接访问。

of_match_table 就是采用设备树的时候驱动使用的匹配表,是数组,每个匹配项都为 of_device_id 结构体类型

cpp 复制代码
241 struct of_device_id {
242         char    name[32];
243         char    type[32];
244         char    compatible[128];
245         const void *data;
246 };   

compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功

2.4 platform_device

struct platform_device 是 Linux 内核中用于表示平台设备的一个结构体。平台设备是一种不依赖于传统 PCI/USB/I2C 等总线的设备,它们通常通过静态配置或设备树(Device Tree)来声明。

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中

cpp 复制代码
23 struct platform_device {
 24         const char      *name;
 25         int             id;
 26         bool            id_auto;
 27         struct device   dev;
 28         u32             num_resources;
 29         struct resource *resource;
 30 
 31         const struct platform_device_id *id_entry;
 32         char *driver_override; /* Driver name to force a match */
 33 
 34         /* MFD cell pointer */
 35         struct mfd_cell *mfd_cell;
 36 
 37         /* arch specific additions */
 38         struct pdev_archdata    archdata;
 39 };      

platform_device结构体的成员解释。

**const char *name:**设备的名称。这个名称是唯一的,用于在内核中标识设备。它也被用于在设备树中查找相应的设备描述。

**int id:**设备的实例ID。在某些情况下,可能需要在同一个设备类型下区分不同的设备实例,此时可以通过这个ID来区分。

**bool id_auto:**一个标志,用于指示是否自动分配设备ID。如果设置为true,内核可能会根据设备的某些属性(如物理地址)自动生成一个唯一的ID。

**struct device dev:**内嵌的struct device结构体,表示一个通用的设备。这个结构体包含了设备的许多基本属性,如设备类型、父设备、设备驱动等。通过dev成员,platform_device能够继承和使用device结构体中定义的所有功能。

**u32 num_resources:**表示设备所使用的资源数量。资源可以是I/O端口、中断号、DMA通道等。

**struct resource *resource:**指向资源数组的指针。每个资源都通过struct resource结构体来描述,包括资源类型、起始地址、长度等信息。

**const struct platform_device_id *id_entry:**指向platform_device_id结构体的指针,该结构体通常用于匹配设备与其驱动程序。当设备被枚举时,内核会检查id_entry中定义的设备名称是否与驱动程序中定义的设备ID相匹配。

**char *driver_override:**一个可选的字符串,用于强制指定驱动程序名称。在某些情况下,如果内核无法自动匹配到合适的驱动程序,可以通过设置这个成员来强制指定一个驱动程序。

**struct mfd_cell *mfd_cell:**用于多功能设备(Multi-Function Device, MFD)的特定成员。MFD是指在一个物理设备中包含多个逻辑子设备的设备。mfd_cell用于描述这些子设备的信息。

**struct pdev_archdata archdata:**架构特定的附加数据。这个成员允许不同的硬件架构为平台设备添加额外的信息或数据。它的具体内容和用途取决于具体的硬件架构。

struct device结构体如下所示。

cpp 复制代码
1031 struct device {
1032         struct device           *parent;
1033 
1034         struct device_private   *p;
1035 
1036         struct kobject kobj;
1037         const char              *init_name; /* initial name of the device */
1038         const struct device_type *type;
1039 
1040         struct mutex            mutex;  /* mutex to synchronize calls to
1041                                          * its driver.
1042                                          */
1043 
1044         struct bus_type *bus;           /* type of bus device is on */
1045         struct device_driver *driver;   /* which driver has allocated this
1046                                            device */
1047         void            *platform_data; /* Platform specific data, device
1048                                            core doesn't touch it */
1049         void            *driver_data;   /* Driver data, set and get with
1050                                            dev_set/get_drvdata */
1051         struct dev_links_info   links;
1052         struct dev_pm_info      power;
1053         struct dev_pm_domain    *pm_domain;
1054 
1055 #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
1056         struct irq_domain       *msi_domain;
1057 #endif
1058 #ifdef CONFIG_PINCTRL
1059         struct dev_pin_info     *pins;
1060 #endif
1061 #ifdef CONFIG_GENERIC_MSI_IRQ
1062         struct list_head        msi_list;
1063 #endif
1064 
1065 #ifdef CONFIG_NUMA
1066         int             numa_node;      /* NUMA node this device is close to */
1067 #endif
1068         const struct dma_map_ops *dma_ops;
1069         u64             *dma_mask;      /* dma mask (if dma'able device) */
1070         u64             coherent_dma_mask;/* Like dma_mask, but for
1071                                              alloc_coherent mappings as
1072                                              not all hardware supports
1073                                              64 bit addresses for consistent
1074                                              allocations such descriptors. */
1075         u64             bus_dma_mask;   /* upstream dma_mask constraint */
1076         unsigned long   dma_pfn_offset;
1077 
1078         struct device_dma_parameters *dma_parms;
1079 
1080         struct list_head        dma_pools;      /* dma pools (if dma'ble) */
1081 
1082         struct dma_coherent_mem *dma_mem; /* internal for coherent mem
1083                                              override */
1084 #ifdef CONFIG_DMA_CMA
1085         struct cma *cma_area;           /* contiguous memory area for dma
1086                                            allocations */
1087 #endif
1088         struct removed_region *removed_mem;
1089         /* arch specific additions */
1090         struct dev_archdata     archdata;
1091 
1092         struct device_node      *of_node; /* associated device tree node */
1093         struct fwnode_handle    *fwnode; /* firmware device node */
1094 
1095         dev_t                   devt;   /* dev_t, creates the sysfs "dev" */
1096         u32                     id;     /* device instance */
1097 
1098         spinlock_t              devres_lock;
1099         struct list_head        devres_head;
1100 
1101         struct klist_node       knode_class;
1102         struct class            *class;
1103         const struct attribute_group **groups;  /* optional groups */
1104 
1105         void    (*release)(struct device *dev);
1106         struct iommu_group      *iommu_group;
1107         struct iommu_fwspec     *iommu_fwspec;
1108 
1109         bool                    offline_disabled:1;
1110         bool                    offline:1;
1111         bool                    of_node_reused:1;
1112         bool                    state_synced:1;
1113 
1114         ANDROID_KABI_RESERVE(1);
1115         ANDROID_KABI_RESERVE(2);
1116         ANDROID_KABI_RESERVE(3);
1117         ANDROID_KABI_RESERVE(4);
1118         ANDROID_KABI_RESERVE(5);
1119         ANDROID_KABI_RESERVE(6);
1120         ANDROID_KABI_RESERVE(7);
1121         ANDROID_KABI_RESERVE(8);
1122 };

struct device_node 是 Linux 内核中用于表示设备树(Device Tree)中一个节点(Node)的数据结构。设备树是一种数据结构,用于描述硬件设备的信息,如它们的地址、中断、时钟等,这些信息对于操作系统在启动过程中配置和初始化硬件设备至关重要。

cpp 复制代码
 51 struct device_node {
  52         const char *name;       
  53         const char *type;
  54         phandle phandle;
  55         const char *full_name;
  56         struct fwnode_handle fwnode;
  57 
  58         struct  property *properties;
  59         struct  property *deadprops;    /* removed properties */
  60         struct  device_node *parent;
  61         struct  device_node *child;
  62         struct  device_node *sibling;
  63 #if defined(CONFIG_OF_KOBJ)
  64         struct  kobject kobj;
  65 #endif
  66         unsigned long _flags;
  67         void    *data;
  68 #if defined(CONFIG_SPARC)
  69         const char *path_component_name;
  70         unsigned int unique_id;
  71         struct of_irq_controller *irq_trans;
  72 #endif
  73 };

device_node结构体的成员解释。

**const char *name;:**节点的名称。在设备树中,每个节点都有一个唯一的名称,用于标识该节点。

**const char *type;:**节点的类型。类型通常用于描述节点的功能或类别,例如 "cpu", "serial", "memory" 等。

**phandle phandle;:**节点的句柄(Phandle)。在设备树中,每个节点都可以有一个唯一的句柄,用于在设备树的其他部分引用该节点。

**const char *full_name;:**节点的完整名称。这通常是节点路径的字符串表示,从根节点开始到该节点的完整路径。

**struct fwnode_handle fwnode;:**这是一个通用句柄,用于将设备树节点与其他类型的固件节点(如 ACPI 节点)统一处理。它允许内核以一种通用的方式访问和操作不同类型的固件节点。

struct property *properties;:指向节点属性列表的指针。属性是节点的一部分,用于存储有关节点的额外信息,如寄存器的地址、中断号等。

**struct property *deadprops;:**指向已删除属性列表的指针。在设备树处理过程中,某些属性可能会被删除或标记为无效,这些属性会被移动到 deadprops 列表中。

**struct device_node *parent;、struct device_node *child;、struct device_node *sibling;:**分别指向父节点、第一个子节点和兄弟节点的指针。这些指针构成了设备树中节点之间的链接,使得可以遍历整个设备树。

**struct kobject kobj;(条件编译):**如果定义了 CONFIG_OF_KOBJ 配置选项,则包含此成员。它用于将设备树节点与内核的对象模型(kobject)集成,从而允许使用内核的通用对象管理功能。

**unsigned long _flags;:**用于存储节点标志的位字段。这些标志可能用于控制节点的某些行为或属性。

**void *data;:**一个指向任意数据的指针,通常用于存储与节点相关的私有数据。

**const char *path_component_name;、unsigned int unique_id;、struct of_irq_controller *irq_trans;(条件编译,针对 SPARC 架构):**这些成员是 SPARC 架构特有的,可能用于处理与 SPARC 相关的特定硬件特性,如中断控制器和唯一标识符。

struct resource 是 Linux 内核中用于表示和管理系统资源(如内存、I/O 端口、中断等)的一个结构体。

cpp 复制代码
20 struct resource {
 21         resource_size_t start;
 22         resource_size_t end;
 23         const char *name;
 24         unsigned long flags;
 25         unsigned long desc;
 26         struct resource *parent, *sibling, *child;
 27 
 28         ANDROID_KABI_RESERVE(1);
 29         ANDROID_KABI_RESERVE(2);
 30         ANDROID_KABI_RESERVE(3);
 31         ANDROID_KABI_RESERVE(4);
 32 };

flags 表示资源类型。

3 platform注册

3.1 platform_driver_register

|------|------------------------------|------------------------------------------------------------|
| 函数原型 | #define platform_driver_register(drv) __platform_driver_register(drv, THIS_MODULE) ||
| 参数 | struct platform_driver *drv | 指向 platform_driver 结构的指针。platform_driver 结构定义了平台驱动的相关信息和操作 |
| 返回值 | | |
| 功能 | 用于注册平台设备驱动 ||

cpp 复制代码
648 int __platform_driver_register(struct platform_driver *drv,
 649                                 struct module *owner)
 650 {
 651         drv->driver.owner = owner;     
 652         drv->driver.bus = &platform_bus_type;
 653         drv->driver.probe = platform_drv_probe;
 654         drv->driver.remove = platform_drv_remove;
 655         drv->driver.shutdown = platform_drv_shutdown;
 656 
 657         return driver_register(&drv->driver);
 658 }

3.1.1 __platform_driver_register分析

3.1.1.1 platform_drv_probe

该函数处理设备的探测过程,包括设置时钟默认值、附加到电源管理域、调用struct platform_driver的探测函数,并根据需要处理探测失败的情况。

cpp 复制代码
587 static int platform_drv_probe(struct device *_dev)
 588 {
 589         struct platform_driver *drv = to_platform_driver(_dev->driver);
 590         struct platform_device *dev = to_platform_device(_dev);
 591         int ret;
 592 
 593         ret = of_clk_set_defaults(_dev->of_node, false);
 594         if (ret < 0)
 595                 return ret;
 596 
 597         ret = dev_pm_domain_attach(_dev, true);
 598         if (ret)
 599                 goto out;
 600 
 601         if (drv->probe) {
 602                 ret = drv->probe(dev);
 603                 if (ret)
 604                         dev_pm_domain_detach(_dev, true);
 605         }
 606 
 607 out:
 608         if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
 609                 dev_warn(_dev, "probe deferral not supported\n");
 610                 ret = -ENXIO;
 611         }
 612 
 613         return ret;
 614 }

将设备附加到电源管理域。

cpp 复制代码
104 int dev_pm_domain_attach(struct device *dev, bool power_on)
105 {                
106         int ret; 
107 
108         if (dev->pm_domain)
109                 return 0;
110 
111         ret = acpi_dev_pm_attach(dev, power_on);
112         if (!ret)
113                 ret = genpd_dev_pm_attach(dev);
114 
115         return ret < 0 ? ret : 0;
116 }
3.1.1.2 platform_drv_remove

执行struct platform_driver中的remove函数。

cpp 复制代码
 621 static int platform_drv_remove(struct device *_dev)
 622 {
 623         struct platform_driver *drv = to_platform_driver(_dev->driver);
 624         struct platform_device *dev = to_platform_device(_dev);
 625         int ret = 0;
 626 
 627         if (drv->remove)
 628                 ret = drv->remove(dev);
 629         dev_pm_domain_detach(_dev, true);
 630 
 631         return ret;
 632 }
3.1.1.3 platform_drv_shutdown

执行struct platform_driver中的shutdown函数。

cpp 复制代码
634 static void platform_drv_shutdown(struct device *_dev)
 635 {
 636         struct platform_driver *drv = to_platform_driver(_dev->driver);
 637         struct platform_device *dev = to_platform_device(_dev);
 638 
 639         if (drv->shutdown)
 640                 drv->shutdown(dev);
 641 }
3.1.1.4 driver_register
cpp 复制代码
146 int driver_register(struct device_driver *drv)
147 {
148         int ret;
149         struct device_driver *other;
150 
151         if (!drv->bus->p) {
152                 pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
153                            drv->name, drv->bus->name);
154                 return -EINVAL;
155         }
156 
157         if ((drv->bus->probe && drv->probe) ||
158             (drv->bus->remove && drv->remove) ||
159             (drv->bus->shutdown && drv->shutdown))
160                 printk(KERN_WARNING "Driver '%s' needs updating - please use "
161                         "bus_type methods\n", drv->name);
162 
163         other = driver_find(drv->name, drv->bus);
164         if (other) {
165                 printk(KERN_ERR "Error: Driver '%s' is already registered, "
166                         "aborting...\n", drv->name);
167                 return -EBUSY;
168         }
169 
170         ret = bus_add_driver(drv);
171         if (ret)
172                 return ret;
173         ret = driver_add_groups(drv, drv->groups);
174         if (ret) {
175                 bus_remove_driver(drv);
176                 return ret;
177         }
178         kobject_uevent(&drv->p->kobj, KOBJ_ADD);
179 
180         return ret;
181 }

3.2 platform_driver_unregister

|------|------------------------------|------------------------------------------------------------|
| 函数原型 | void platform_driver_unregister(struct platform_driver *drv) ||
| 参数 | struct platform_driver *drv | 指向 platform_driver 结构的指针。platform_driver 结构定义了平台驱动的相关信息和操作 |
| 返回值 | | |
| 功能 | 用于注销平台设备驱动 ||

cpp 复制代码
 665 void platform_driver_unregister(struct platform_driver *drv)
 666 {
 667         driver_unregister(&drv->driver);
 668 }
cpp 复制代码
190 void driver_unregister(struct device_driver *drv)
191 {                                
192         if (!drv || !drv->p) {
193                 WARN(1, "Unexpected driver unregister!\n");
194                 return;
195         }
196         driver_remove_groups(drv, drv->groups);
197         bus_remove_driver(drv); 
198 }

3.3 platform_device_register

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。

|------|-------------------------------|-------------------------------------------|
| 函数原型 | int platform_device_register(struct platform_device *pdev) ||
| 参数 | struct platform_device *pdev | 指向 platform_device 结构体的指针,这个结构体包含了设备的相关信息 |
| 返回值 | | 成功:0 失败:错误码 |
| 功能 | 注册平台设备 ||

cpp 复制代码
493 int platform_device_register(struct platform_device *pdev)
 494 {
 495         device_initialize(&pdev->dev);
 496         arch_setup_pdev_archdata(pdev);
 497         return platform_device_add(pdev);
 498 }

3.4 platform_device_unregister

|------|-------------------------------|-------------------------------------------|
| 函数原型 | int platform_device_unregister(struct platform_device *pdev) ||
| 参数 | struct platform_device *pdev | 指向 platform_device 结构体的指针,这个结构体包含了设备的相关信息 |
| 返回值 | | 成功:0 失败:错误码 |
| 功能 | 注销注册平台设备 ||

cpp 复制代码
509 void platform_device_unregister(struct platform_device *pdev)
 510 {
 511         platform_device_del(pdev);
 512         platform_device_put(pdev);
 513 }

4 jw_io_core.c 驱动分析

4.1设备树

4.1.1 设备树文件

Bootloader会将设备树传递给内核,然后内核识别设备树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

cpp 复制代码
 9 / {
 10         compatible = "rockchip,rk3399-tve1030g-avb", "rockchip,rk3399";
 11         jw_io_init {
 12             compatible = "jw_io_control";
 13             hdmi_5v_gpio = <&gpio4 RK_PD5 GPIO_ACTIVE_HIGH>;
 14             //vbus_5v_gpio = <&gpio1 RK_PD0 GPIO_ACTIVE_HIGH>;
 15             led_ctl = <&gpio4 RK_PD4 GPIO_ACTIVE_HIGH>;
 16             hub_rst_gpio = <&gpio4 RK_PD6 GPIO_ACTIVE_HIGH>;
 17             power_hold_gpio = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>;
 18             mic_switch_gpio = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>;
 19             audio_switch_gpio = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>;
 20             pwm_gpio = <&gpio4 RK_PC6 GPIO_ACTIVE_HIGH>;
 21             SPDIF_gpio = <&gpio3 RK_PC0 GPIO_ACTIVE_HIGH>;
 22             pwms = <&pwm1 0 1000000 0>;
 23             pinctrl-names = "default";
 24             pinctrl-0 = <&jw_io_gpio>;
 25 
 26            ddr_check_1_gpio =  <&gpio2 RK_PA0 GPIO_ACTIVE_HIGH>;
 27            ddr_check_2_gpio =  <&gpio2 RK_PA7 GPIO_ACTIVE_HIGH>;
 28            ddr_check_3_gpio =  <&gpio2 RK_PB3 GPIO_ACTIVE_HIGH>;
 29            ddr_check_4_gpio =  <&gpio2 RK_PB4 GPIO_ACTIVE_HIGH>;
 30     };
 31 };

 761 &pinctrl {
 884                 jw_io_init{
 885                 jw_io_gpio: jw_io_gpio_col{
 886                                 rockchip,pins =
 887                                         <2 0 RK_FUNC_GPIO &pcfg_pull_up>,
 888                                         <2 7 RK_FUNC_GPIO &pcfg_pull_up>,
 889                                         <2 11 RK_FUNC_GPIO &pcfg_pull_up>,
 890                                         <2 12 RK_FUNC_GPIO &pcfg_pull_up>;
 891                 };
 892         };
 900 };

生成的文件节点在**/proc/device-tree/**jw_io_init下。

rk3399_Android11:/proc/device-tree # ls
#address-cells             dummy_cpll           i2c@ff140000                   mipi-dphy-tx1rx1@ff968000  pcie@f8000000                  qos@ffa76000     rkisp1@ff920000          spi@ff1f0000           vcc3v3-sys
#size-cells                dummy_vpll           i2c@ff150000                   mpp-srv                    phy@ff7c0000                   qos@ffa90000     rktimer@ff850000         spi@ff200000           vcc5v0-host-regulator
__symbols__                dwmmc@fe310000       i2c@ff160000                   name                       phy@ff800000                   qos@ffa98000     rkvdec@ff660000          spi@ff350000           vdd-log
adc_keys                   dwmmc@fe320000       i2c@ff3c0000                   nocp-cci-msch0@ffa86000    pinctrl                        qos@ffaa0000     rng@ff8b8000             sram@ff8d0000          vdpu@ff650400
aliases                    edp@ff970000         i2c@ff3d0000                   nocp-cci-msch1@ffa8e000    pmu-clock-controller@ff750000  qos@ffaa0080     rockchip-suspend         syscon@ff320000        vepu@ff650000
amba                       efuse@ff690000       i2c@ff3e0000                   nocp-gpu-msch0@ffa86400    pmu_a53                        qos@ffaa8000     rockchip-system-monitor  syscon@ff770000        vop@ff8f0000
backlight                  energy-costs         i2s@ff880000                   nocp-gpu-msch1@ffa8e400    pmu_a72                        qos@ffaa8080     saradc@ff100000          test-power             vop@ff900000
charge-animation           es8323-sound         i2s@ff890000                   nocp-hp-msch0@ffa86800     power-management@ff310000      qos@ffab0000     sdhci@fe330000           thermal-zones          voppwm@ff8f01a0
chosen                     ethernet@fe300000    i2s@ff8a0000                   nocp-hp-msch1@ffa8e800     psci                           qos@ffab0080     sdio-pwrseq              timer                  voppwm@ff9001a0
clock-controller@ff760000  external-gmac-clock  iep@ff670000                   nocp-lp-msch0@ffa86c00     pwm@ff420000                   qos@ffab8000     serial-number            tsadc@ff260000         watchdog@ff848000
compatible                 fiq-debugger         interrupt-controller@fee00000  nocp-lp-msch1@ffa8ec00     pwm@ff420010                   qos@ffac0000     serial@ff180000          usb@fe340000           wireless-bluetooth
cpuinfo                    firmware             interrupt-parent               nocp-video-msch0@ffa87000  pwm@ff420020                   qos@ffac0080     serial@ff190000          usb@fe380000           wireless-wlan
cpus                       gpio-keys            iommu@ff650800                 nocp-video-msch1@ffa8f000  pwm@ff420030                   qos@ffac8000     serial@ff1a0000          usb@fe3a0000           xin24m
ddr_timing                 gpu@ff9a0000         iommu@ff660480                 nocp-vio0-msch0@ffa87400   qos@ffa58000                   qos@ffac8080     serial@ff1b0000          usb@fe3c0000           xin32k
dfi@ff630000               hdmi-dp-sound        iommu@ff670800                 nocp-vio0-msch1@ffa8f400   qos@ffa5c000                   qos@ffad0000     serial@ff370000          usb@fe3e0000
display-subsystem          hdmi-hdcp2@ff988000  iommu@ff8f3f00                 nocp-vio1-msch0@ffa87800   qos@ffa60080                   qos@ffad8080     spdif-out                usb@fe800000
dmc                        hdmi-sound           iommu@ff903f00                 nocp-vio1-msch1@ffa8f800   qos@ffa60100                   qos@ffae0000     spdif-sound              usb@fe900000
dp-sound                   hdmi@ff940000        iommu@ff914000                 opp-table0                 qos@ffa60180                   reserved-memory  spdif@ff870000           vcc-phy-regulator
dp@fec00000                i2c@ff110000         iommu@ff924000                 opp-table1                 qos@ffa70000                   rga@ff680000     spi@ff1c0000             vcc-pwr-1v8-regulator
dsi@ff960000               i2c@ff120000         jw_io_init                     opp-table2                 qos@ffa70080                   rk-headset       spi@ff1d0000             vcc-sys
dsi@ff968000               i2c@ff130000         memory                         opp-table3                 qos@ffa74000                   rkisp1@ff910000  spi@ff1e0000             vcc1v8-s0
rk3399_Android11:/proc/device-tree # ls jw_io_init/
audio_switch_gpio  compatible  ddr_check_1_gpio  ddr_check_2_gpio  ddr_check_3_gpio  ddr_check_4_gpio  hdmi_5v_gpio  hub_rst_gpio  led_ctl  mic_switch_gpio  name  pinctrl-0  pinctrl-names  power_hold_gpio  pwm_gpio  pwms
rk3399_Android11:/proc/device-tree #

**/sys/bus/platform/devices/**文件下生成了节点jw_io_init。

rk3399_Android11:/sys/bus/platform # ls devices/jw_io_init/
driver  driver_override  led_display  mic_switch  modalias  of_node  power  subsystem  uevent

4.2 驱动分析

4.2.1 驱动框架

cpp 复制代码
 14 #include <linux/module.h>
 15 #include <linux/moduleparam.h>
 16 #include <linux/init.h>
 17 #include <linux/delay.h>
 18 #include <linux/pm.h>
 19 #include <linux/i2c.h>
 20 #include <linux/spi/spi.h>
 21 #include <linux/platform_device.h>
 22 #include <linux/errno.h>
 23 #include <linux/err.h>
 24 #include <linux/debugfs.h>
 25 #include <linux/of_gpio.h>
 26 #include <linux/gpio.h>
 27 #include <linux/iio/consumer.h>
 28 #include "jwio.h"
     
199 static int jw_io_control_probe(struct platform_device *pdev)
200 {
201         struct device_node *node = pdev->dev.of_node;
202         struct jw_io_pdata *pdata;
203         int ret;
204         enum of_gpio_flags flags;
205         printk(" #######jw_io_control_probe####### \n");
206         enable = 0 ;
207 
208         pdata = kzalloc(sizeof(struct jw_io_pdata), GFP_KERNEL);
209         if (pdata == NULL) {
210                 printk("%s failed to allocate driver data\n",__FUNCTION__);
211                 return -ENOMEM;
212         }
213         memset(pdata,0,sizeof(struct jw_io_pdata));
214 
216                 ret = of_get_named_gpio_flags(node, "power_hold_gpio", 0, &flags);
217                 if (ret < 0) {
218                                 printk("%s() Can not read property power_hold_gpio\n", __FUNCTION__);
219                                 goto err;
220                 } else {
221                                 pdata->power_hold_gpio = ret;
222                                 ret = devm_gpio_request(&pdev->dev, pdata->power_hold_gpio, "power_hold_gpio");
223                                 if(ret < 0){
224                                                 printk("%s() devm_gpio_request power_hold_gpio request ERROR\n", __FUNCTION__);
225                                                 goto err;
226                                 }
227                                 ret = gpio_direction_output(pdata->power_hold_gpio,1);
228                                 if(ret < 0){
229                                 printk("%s() gpio_direction_input power_hold_gpio set ERROR\n", __FUNCTION__);
230                                 goto err;
231                 }
232                                 }
397 }
398 
399 static int jw_io_control_remove(struct platform_device *pdev)
400 {
401         if(JWpdata_info)
402                 kfree(JWpdata_info);
403         return 0;
404 }
405 
406 static int jw_io_control_suspend(struct platform_device *pdev, pm_message_t state)
407 {
408         printk("LED_early_suspend LED_early_suspend LED_early_suspend !!!!\n");
409 
410 
411         enable = 0;
412         LED_SET(0);
413         return 0;
414 }
415 
416 static int jw_io_control_resume(struct platform_device *pdev)
417 {
418         printk("LED_early_resume LED_early_resume LED_early_resume !!!!\n");
419         gpio_direction_output(JWpdata_info->hub_rst_gpio,0);
420         msleep(800);
421         gpio_direction_output(JWpdata_info->hub_rst_gpio,1);
422 
423         enable = 1;
424     LED_SET(11);
425         return 0;
426 }
428 static const struct of_device_id jw_io_control_of_match[] = {
429         { .compatible = "jw_io_control", },
430         {},
431 };
432 MODULE_DEVICE_TABLE(of, jw_io_control_of_match);
433 
434 static struct platform_driver jw_io_control_driver = {
435         .probe  = jw_io_control_probe,
436         .remove = jw_io_control_remove,
437         .resume =       jw_io_control_resume,
438         .suspend =      jw_io_control_suspend,
439         .driver = {
440                 .name   = "jw_io_control",
441                 .owner  = THIS_MODULE,
442                 .of_match_table = of_match_ptr(jw_io_control_of_match),
443         },
444 };
445 
446 static int __init jw_io_control_init(void)
447 {
448         platform_driver_register(&jw_io_control_driver);
449         return 0;
450 }
451 
452 static void __exit jw_io_control_exit(void)
453 {
454         platform_driver_unregister(&jw_io_control_driver);
455 }
456 
457 subsys_initcall(jw_io_control_init);
458 
460 MODULE_DESCRIPTION("jw io Core Driver");
461 MODULE_LICENSE("GPL");

4.2.2 platform驱动文件系统

当一个平台设备驱动被注册时,内核会创建相应的目录和文件,并将这些信息暴露到 /sys/bus/platform/drivers/ 目录中

rk3399_Android11:/sys/bus/platform/drivers/jw_io_control # ls
bind  jw_io_init  uevent  unbind
rk3399_Android11:/sys/bus/platform/drivers/jw_io_control/jw_io_init # ls
driver  driver_override  led_display  mic_switch  modalias  of_node  power  subsystem  uevent

**/sys/bus/platform/drivers/**文件下生成文件节点jw_io_control,struct platform_driver实例化jw_io_control_driver驱动时将驱动的名称设置为jw_io_control

**bind:**用于将平台设备绑定到 jw_io_control驱动程序。可以通过写入设备的 ID 来将设备绑定到驱动程序。

**unbind:**用于将设备从 jw_io_control驱动程序中解绑。

**jw_io_control:**显示驱动程序的名称

cpp 复制代码
434 static struct platform_driver jw_io_control_driver = {
435         .probe  = jw_io_control_probe,
436         .remove = jw_io_control_remove,
437         .resume =       jw_io_control_resume,
438         .suspend =      jw_io_control_suspend,
439         .driver = {
440                 .name   = "jw_io_control",
441                 .owner  = THIS_MODULE,
442                 .of_match_table = of_match_ptr(jw_io_control_of_match),
443         },
444 };

jw_io_control文件创建定位:platform_driver_register->__platform_driver_register->driver_register->bus_add_driver->driver_create_file

4.2.3 probe参数传递

static int jw_io_control_probe(struct platform_device *pdev)函数中的pdev参数是如何传递进来的?

设备注册:当一个平台设备被注册到系统中时,它会被内核添加到平台总线上。设备的注册过程通常涉及 platform_device_register() 函数。或者内核识别设备树,生成平台设备。

驱动程序绑定:驱动程序会通过 platform_driver 结构体中的 probe 函数指针与设备进行绑定。这个结构体定义了驱动程序的行为和处理函数。platform_driver 结构体通常包含 probe、remove、suspend 和 resume 等回调函数。这些函数会在设备注册、解绑、挂起和恢复时被调用。

Probe 函数调用:当设备和驱动程序绑定时,内核会调用 probe 函数,pdev 是由内核在设备和驱动程序绑定时自动传递给 probe 函数的,包含了当前设备的相关信息和资源。此时,pdev 参数就是 platform_device 结构体的指针,它代表了当前正在被初始化的设备。platform_device 结构体包含了设备的相关信息,如设备的 ID、资源(内存、IO 地址等)、设备名称和设备状态等。

4.2.4 驱动设备匹配分析

platform 总线是 bus_type 的一个具体实例,platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。

cpp 复制代码
988 static int platform_match(struct device *dev, struct device_driver *drv)
 989 {
 990         struct platform_device *pdev = to_platform_device(dev);
 991         struct platform_driver *pdrv = to_platform_driver(drv);
 992 
 993         /* When driver_override is set, only bind to the matching driver */
 994         if (pdev->driver_override)
 995                 return !strcmp(pdev->driver_override, drv->name);
 996 
 997         /* Attempt an OF style match first */
 998         if (of_driver_match_device(dev, drv))
 999                 return 1;
1000 
1001         /* Then try ACPI style match */
1002         if (acpi_driver_match_device(dev, drv))
1003                 return 1;
1004 
1005         /* Then try to match against the id table */
1006         if (pdrv->id_table)
1007                 return platform_match_id(pdrv->id_table, pdev) != NULL;
1008 
1009         /* fall-back to driver name match */
1010         return (strcmp(pdev->name, drv->name) == 0);
1011 }

of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。device_driver 结构体(表示 设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表, 设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是 否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数 就会执行

追踪:of_driver_match_device->of_match_device->of_match_node->__of_match_node->__of_device_is_compatible

cpp 复制代码
1063 const struct of_device_id *__of_match_node(const struct of_device_id *matches,
1064                                            const struct device_node *node)
1065 {
1066         const struct of_device_id *best_match = NULL;
1067         int score, best_score = 0;
1068 
1069         if (!matches)
1070                 return NULL;
1071 
1072         for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
1073                 score = __of_device_is_compatible(node, matches->compatible,
1074                                                   matches->type, matches->name);
1075                 if (score > best_score) {
1076                         best_match = matches;
1077                         best_score = score;
1078                 }
1079         }
1080 
1081         return best_match;
1082 }

struct device_node 是一个在 Linux 内核中用于表示设备树节点的结构体

cpp 复制代码
 500 static int __of_device_is_compatible(const struct device_node *device,
 501                                      const char *compat, const char *type, const char *name)
 502 {
 503         struct property *prop;
 504         const char *cp;
 505         int index = 0, score = 0;
 506 
 507         /* Compatible match has highest priority */
 508         if (compat && compat[0]) {
 509                 prop = __of_find_property(device, "compatible", NULL);
 510                 for (cp = of_prop_next_string(prop, NULL); cp;
 511                      cp = of_prop_next_string(prop, cp), index++) {
 512                         if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
 513                                 score = INT_MAX/2 - (index << 2);
 514                                 break;
 515                         }
 516                 }
 517                 if (!score)
 518                         return 0;
 519         }
 520 
 521         /* Matching type is better than matching name */
 522         if (type && type[0]) {
 523                 if (!device->type || of_node_cmp(type, device->type))
 524                         return 0;
 525                 score += 2;
 526         }
 527 
 528         /* Matching name is a bit better than not */
 529         if (name && name[0]) {
 530                 if (!device->name || of_node_cmp(name, device->name))
 531                         return 0;
 532                 score++;
 533         }
 534 
 535         return score;
 536 }
相关推荐
云飞云共享云桌面33 分钟前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq1 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮2 小时前
Linux 使用中的问题
linux·运维
dsywws3 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零4 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
小林熬夜学编程5 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
程思扬6 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节
sun0077006 小时前
拷贝 cp -rdp 和 cp -a
linux·运维·服务器
wowocpp6 小时前
ubuntu 22.04 server 安装 anaconda3
linux·运维·ubuntu
乡村农夫6 小时前
cuda 环境搭建
linux