1.參考網站
關於 uboot drvier的相關概念,可以參考
https://www.itread01.com/content/1544365633.html
請先行閱讀後,再開始開發boot device driver會比較有概念.
2.基本概念
uboot引入了驅動模型(driver model),這種驅動模型為驅動的定義和訪問介面提供了統一的方法。提高了驅動之間的相容性以及訪問的標準型。 uboot驅動模型和kernel中的裝置驅動模型類似,但是又有所區別。 在後續我們將驅動模型(driver model)簡稱為DM,其實在uboot裡面也是這樣簡稱的。
具體細節建議參考./doc/driver-model/README.txt
uboot的DM主要有四個組成部分:
- udevice 簡單就是指裝置物件,可以理解為kernel中的device。
- Udevice driver- udevice的驅動,可以理解為kernel中的device_driver。和底層硬體裝置通訊,並且為裝置提供面向上層的介面。
- uclass 使用相同方式的操作集的device的組。相當於是一種抽象。uclass為那些使用相同介面的裝置提供了統一的介面。例如,GPIO uclass提供了get/set介面。再例如,一個I2C uclass下可能有10個I2C埠,4個使用一個驅動,另外6個使用另外一個驅動。
- uclass_driver - 對應uclass的驅動程式。主要提供uclass操作時,如繫結udevice時的一些操作。
關係圖如下:
其中 uclass的結構體,定義如下
Include/dm/uclass.h |
struct uclass { void *priv; struct uclass_driver *uc_drv; struct list_head dev_head; struct list_head sibling_node; }; |
其中 uclass driver的結構體,定義如下
Include/dm/uclass.h |
struct uclass_driver { const char *name; // 該uclass_driver的命令 enum uclass_id id; // 對應的uclass id /* 以下函式指標主要是呼叫時機的區別 */ int (*post_bind)(struct udevice *dev); // 在udevice被繫結到該uclass之後呼叫 int (*pre_unbind)(struct udevice *dev); // 在udevice被解綁出該uclass之前呼叫 int (*pre_probe)(struct udevice *dev); // 在該uclass的一個udevice進行probe之前呼叫 int (*post_probe)(struct udevice *dev); // 在該uclass的一個udevice進行probe之後呼叫 int (*pre_remove)(struct udevice *dev);// 在該uclass的一個udevice進行remove之前呼叫 int (*child_post_bind)(struct udevice *dev); // 在該uclass的一個udevice的一個子裝置被繫結到該udevice之後呼叫 int (*child_pre_probe)(struct udevice *dev); // 在該uclass的一個udevice的一個子裝置進行probe之前呼叫 int (*init)(struct uclass *class); // 安裝該uclass的時候呼叫 int (*destroy)(struct uclass *class); // 銷燬該uclass的時候呼叫 int priv_auto_alloc_size; // 需要為對應的uclass分配多少私有資料 int per_device_auto_alloc_size; // int per_device_platdata_auto_alloc_size; // int per_child_auto_alloc_size; // int per_child_platdata_auto_alloc_size; // const void *ops; //操作集合 uint32_t flags; // 標識為 }; |
uboot是利用通過UCLASS_DRIVER來定義uclass_driver.
以下為thermal uclass driver的範例,可以看到 thermal uclass只提供 thermal_get_temp 來讀取溫度。不同uclass會提供不同的 function給上層用,上層只要利用這些函式,基本上就可以滿足使用上的需求.
這邊先提一下 Uclass driver 並不會牽扯到 dtsi 檔案的修改.
\vendor\nxp-opensource\uboot-imx\drivers\thermal\thermal-uclass.c |
int thermal_get_temp(struct udevice *dev, int *temp) { const struct dm_thermal_ops *ops = device_get_ops(dev);
if (!ops->get_temp) return -ENOSYS;
return ops->get_temp(dev, temp); }
UCLASS_DRIVER(thermal) = { .id = UCLASS_THERMAL, /*uclass id is important*/ .name = "thermal", }; |
每一種uclass都有自己對應的ID號。定義於其uclass_driver中。其附屬的udevice的driver中的uclass id必須與其一致。 所有uclass id定義於include/dm/uclass-id.h中
include/dm/uclass-id.h |
enum uclass_id { /* These are used internally by driver model */ UCLASS_ROOT = 0, UCLASS_DEMO, UCLASS_TEST, UCLASS_TEST_FDT, UCLASS_TEST_BUS, UCLASS_TEST_PROBE, UCLASS_TEST_DUMMY, UCLASS_SPI_EMUL, /* sandbox SPI device emulator */ UCLASS_I2C_EMUL, /* sandbox I2C device emulator */ UCLASS_PCI_EMUL, /* sandbox PCI device emulator */ UCLASS_USB_EMUL, /* sandbox USB bus device emulator */ UCLASS_SIMPLE_BUS, /* bus with child devices */
/* U-Boot uclasses start here - in alphabetical order */ UCLASS_ADC, /* Analog-to-digital converter */ UCLASS_AHCI, /* SATA disk controller */ UCLASS_BLK, /* Block device */ UCLASS_CLK, /* Clock source, e.g. used by peripherals */ UCLASS_CPU, /* CPU, typically part of an SoC */ UCLASS_CROS_EC, /* Chrome OS EC */ UCLASS_DISPLAY, /* Display (e.g. DisplayPort, HDMI) */ UCLASS_DMA, /* Direct Memory Access */ UCLASS_EFI, /* EFI managed devices */ UCLASS_ETH, /* Ethernet device */ UCLASS_GPIO, /* Bank of general-purpose I/O pins */ UCLASS_FIRMWARE, /* Firmware */ UCLASS_I2C, /* I2C bus */ UCLASS_I2C_EEPROM, /* I2C EEPROM device */ UCLASS_I2C_GENERIC, /* Generic I2C device */ UCLASS_I2C_MUX, /* I2C multiplexer */ UCLASS_IDE, /* IDE device */ UCLASS_IRQ, /* Interrupt controller */ UCLASS_KEYBOARD, /* Keyboard input device */ UCLASS_LED, /* Light-emitting diode (LED) */ UCLASS_LPC, /* x86 'low pin count' interface */ UCLASS_MAILBOX, /* Mailbox controller */ UCLASS_MASS_STORAGE, /* Mass storage device */ UCLASS_MISC, /* Miscellaneous device */ UCLASS_MMC, /* SD / MMC card or chip */ UCLASS_MOD_EXP, /* RSA Mod Exp device */ UCLASS_MTD, /* Memory Technology Device (MTD) device */ UCLASS_NORTHBRIDGE, /* Intel Northbridge / SDRAM controller */ UCLASS_NVME, /* NVM Express device */ UCLASS_PANEL, /* Display panel, such as an LCD */ UCLASS_PANEL_BACKLIGHT, /* Backlight controller for panel */ UCLASS_PCH, /* x86 platform controller hub */ UCLASS_PCI, /* PCI bus */ UCLASS_PCI_GENERIC, /* Generic PCI bus device */ UCLASS_PHY, /* Physical Layer (PHY) device */ UCLASS_PINCONFIG, /* Pin configuration node device */ UCLASS_PINCTRL, /* Pinctrl (pin muxing/configuration) device */ UCLASS_PMIC, /* PMIC I/O device */ UCLASS_PWM, /* Pulse-width modulator */ UCLASS_POWER_DOMAIN, /* (SoC) Power domains */ UCLASS_PWRSEQ, /* Power sequence device */ UCLASS_RAM, /* RAM controller */ UCLASS_REGULATOR, /* Regulator device */ UCLASS_REMOTEPROC, /* Remote Processor device */ UCLASS_RESET, /* Reset controller device */ UCLASS_RTC, /* Real time clock device */ UCLASS_SCSI, /* SCSI device */ UCLASS_SERIAL, /* Serial UART */ UCLASS_SPI, /* SPI bus */ UCLASS_SPMI, /* System Power Management Interface bus */ UCLASS_SPI_FLASH, /* SPI flash */ UCLASS_SPI_GENERIC, /* Generic SPI flash target */ UCLASS_SYSCON, /* System configuration device */ UCLASS_SYSRESET, /* System reset device */ UCLASS_THERMAL, /* Thermal sensor */ UCLASS_TIMER, /* Timer device */ UCLASS_TPM, /* Trusted Platform Module TIS interface */ UCLASS_USB, /* USB bus */ UCLASS_USB_DEV_GENERIC, /* USB generic device */ UCLASS_USB_HUB, /* USB hub */ UCLASS_VIDEO, /* Video or LCD device */ UCLASS_VIDEO_BRIDGE, /* Video bridge, e.g. DisplayPort to LVDS */ UCLASS_VIDEO_CONSOLE, /* Text console driver for video device */ UCLASS_WDT, /* Watchdot Timer driver */
UCLASS_COUNT, UCLASS_INVALID = -1, };
|
其中 udevice的結構體,定義如下
include/dm/device.h |
struct udevice { const struct driver *driver; // 該udevice對應的driver const char *name; // 裝置名 void *platdata; // 該udevice的平臺數據 void *parent_platdata; // 提供給父裝置使用的平臺數據 void *uclass_platdata; // 提供給所屬uclass使用的平臺數據 int of_offset; // 該udevice的dtb節點偏移,代表了dtb裡面的這個節點node ulong driver_data; // 驅動資料 struct udevice *parent; // 父裝置 void *priv; // 私有資料的指標 struct uclass *uclass; // 所屬uclass void *uclass_priv; // 提供給所屬uclass使用的私有資料指標 void *parent_priv; // 提供給其父裝置使用的私有資料指標 struct list_head uclass_node; // 用於連線到其所屬uclass的連結串列上 struct list_head child_head; // 連結串列頭,連線其子裝置 struct list_head sibling_node; // 用於連線到其父裝置的連結串列上 uint32_t flags; // 標識 int req_seq; int seq; #ifdef CONFIG_DEVRES struct list_head devres_head; #endif |
以下為 udevice driver的結構體,定義如下
include/dm/device.h |
struct driver { char *name; // 驅動名 enum uclass_id id; // 對應的uclass id const struct udevice_id *of_match; // compatible字串的匹配表,用於和device tree裡面的裝置節點匹配 int (*bind)(struct udevice *dev); // 用於繫結目標裝置到該driver中 int (*probe)(struct udevice *dev); // 用於probe目標裝置,啟用 int (*remove)(struct udevice *dev); // 用於remove目標裝置。禁用 int (*unbind)(struct udevice *dev); // 用於解綁目標裝置到該driver中 int (*ofdata_to_platdata)(struct udevice *dev); // 在probe之前,解析對應udevice的dts節點,轉化成udevice的平臺數據 int (*child_post_bind)(struct udevice *dev); // 如果目標裝置的一個子裝置被繫結之後,呼叫 int (*child_pre_probe)(struct udevice *dev); // 在目標裝置的一個子裝置被probe之前,呼叫 int (*child_post_remove)(struct udevice *dev); // 在目標裝置的一個子裝置被remove之後,呼叫 int priv_auto_alloc_size; //需要分配多少空間作為其udevice的私有資料 int platdata_auto_alloc_size; //需要分配多少空間作為其udevice的平臺數據 int per_child_auto_alloc_size; // 對於目標裝置的每個子裝置需要分配多少空間作為父裝置的私有資料 int per_child_platdata_auto_alloc_size; // 對於目標裝置的每個子裝置需要分配多少空間作為父裝置的平臺數據 const void *ops; /* driver-specific operations */ // 操作集合的指標,提供給uclass使用,沒有規定操作集的格式,由具體uclass決定 uint32_t flags; // 一些標誌位 }; |
以下為 thermal 的 udevice driver範例,注意udevice drvier會跟dtsi有關係了,所以會有 dtsi檔案需要修改,請注意 .compatible部分.
vendor\nxp-opensource\uboot-imx\drivers\thermal\nxp_tmu.c |
static const struct dm_thermal_ops nxp_tmu_ops = { .get_temp = nxp_tmu_get_temp, }; .. .. static const struct udevice_id nxp_tmu_ids[] = { { .compatible = "fsl,imx8mq-tmu", }, { .compatible = "fsl,imx8mm-tmu", .data=FLAGS_VER2, }, /*dtsi必須有相對應的設定*/
{ } };
U_BOOT_DRIVER(nxp_tmu) = { /*請參考 udevice driver structure*/ .name = "nxp_tmu", .id = UCLASS_THERMAL, /*必須設定是哪一種 uclass*/ .ops = &nxp_tmu_ops, .of_match = nxp_tmu_ids, .bind = nxp_tmu_bind, .probe = nxp_tmu_probe, .ofdata_to_platdata = nxp_tmu_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct nxp_tmu_plat), .flags = DM_FLAG_PRE_RELOC, };
|
.compatible 相對應的 dtsi設定如下:
\vendor\nxp-opensource\uboot-imx\arch\arm\dts\fsl-imx8mm.dtsi |
tmu: tmu@30260000 { compatible = "fsl,imx8mm-tmu"; reg = <0x0 0x30260000 0x0 0x10000>; interrupt = <GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH>; little-endian; u-boot,dm-pre-reloc; #thermal-sensor-cells = <0>; };
thermal-zones { /* cpu thermal */ cpu-thermal { polling-delay-passive = <250>; polling-delay = <2000>; thermal-sensors = <&tmu>; trips { cpu_alert0: trip0 { temperature = <85000>; hysteresis = <2000>; type = "passive"; }; cpu_crit0: trip1 { temperature = <95000>; hysteresis = <2000>; type = "critical"; }; };
cooling-maps { map0 { trip = <&cpu_alert0>; cooling-device = <&A53_0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; }; }; }; }; |
其中的.ops = &nxp_tmu_ops, 就會對應到.get_temp = nxp_tmu_get_temp, 也就是uclass所提供給上層呼叫的函式,最終會呼叫到 udevice driver的 nxp_tmu_get_temp.
其中最重要的是,如果udevice driver 的probe想要被呼叫,必須要在 uboot int board_init(void)呼叫以下函式,才能 probe到udevice 的 driver.
Arch/arm/mach-imx/imx8/cpu.c |
... ret = uclass_get_device_by_name(UCLASS_THERMAL, "cpu-thermal0", &thermal_dev); .. |
大架構說完了,以下將用新增一個 pwm udevice driver 來做練習,這個pwm udevice driver只提供空殼,並不會作細部實作,其目的主要是讓大家了解 [如何新增一個新的udevice driver到 uboot中]
3.實作範例
這章節將會在 uboot中新增一個PWM udevice driver. 主要修改檔案清單如下:
|
File name |
目的 |
備註 |
1 |
uboot-imx\drivers\pwm\imx27_pwm.c |
pwm udevice driver |
|
2 |
uboot-imx\drivers\pwm\Makefile |
加上obj-$(CONFIG_PWM_IMX27) += imx27_pwm.o |
|
3 |
uboot-imx\drivers\pwm\Kconfig |
Add |
|
4 |
\uboot-imx\configs\imx8mm_evk_android_defconfig |
Add CONFIG_PWM_IMX27=y |
|
5 |
\vendor\nxp-opensource\uboot-imx\arch\arm\dts\fsl-imx8mm.dtsi |
Add pwm1: pwm@30660000 { compatible = "fsl,imx27-pwm"; reg = <0x0 0x30660000 0x0 0x10000>; interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clk IMX8MM_CLK_PWM1_ROOT>, <&clk IMX8MM_CLK_PWM1_ROOT>; clock-names = "ipg", "per"; #pwm-cells = <2>; u-boot,dm-pre-reloc; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pwm_backlight>; status = "okay"; }; pwm-backlight { pwm-lcd{
}; }; compatible = "fsl,imx8mm-iomuxc"; reg = <0x0 0x30330000 0x0 0x10000>;
pinctrl_pwm_backlight: pwm1grp { fsl,pins = < MX8MM_IOMUXC_GPIO1_IO01_PWM1_OUT 0x20 /* open drain, pull down */ >; }; }; |
|
6 |
vendor\nxp-opensource\uboot-imx\arch\arm\dts\fsl-imx8mm.dtsi |
Remove pinctrl_pwm_backlight: pwm1grp { fsl,pins = < MX8MM_IOMUXC_GPIO1_IO01_PWM1_OUT 0x20 /* open drain, pull down */ >; };
|
|
7 |
\uboot-imx\board\freescale\imx8mm_evk\imx8mm_evk.c |
Add static void pwm_init(void) { struct udevice *bus, *main_dev, *cec_dev;
int ret; uint8_t val; printf("[mark] %s [%d]\n",__func__,__LINE__); ret = uclass_get_device_by_name(UCLASS_PWM, "pwm-lcd", &bus); if (ret) { printf("%s: [%d] \n", __func__,__LINE__); return; }
return; }
{ #ifdef CONFIG_USB_TCPC setup_typec(); #endif
#ifdef CONFIG_MXC_SPI setup_spi(); #endif
#ifdef CONFIG_FEC_MXC setup_fec(); #endif
#ifdef CONFIG_FSL_FSPI board_qspi_init(); #endif printf("[mark] %s [%d]\n",__func__,__LINE__); pwm_init();
return 0; } |
|
依照上面修改後,imx27_pwm driver會 probe成功,並吐出debug message.可以利用這個空殼當範例,自己再去新增自己的udevice driver.
a)完整修改檔案
https://drive.google.com/open?id=1egEFFzToEHppy0gTqNSmOHwk-_ezqG9P
4.注意事項
Udevice driver要被probe必須要在imx8mm_evk.c中int board_init(void)函式內,呼叫
ret = uclass_get_device_by_name(UCLASS_PWM, "pwm-lcd", &bus);
才會被 probe到.
5.特別感謝
感謝 Esmond,與 Zoe的技術支援,這份文件有百分之99都是他們提供的專業技術.
留言列表