Hướng dẫn Phát triển GPIO và Pinctrl trên Linux

Tổng quan

Tài liệu này cung cấp hướng dẫn chi tiết về cách sử dụng các giao diện GPIO (General Purpose Input/Output) và Pinctrl (Pin Controller) trong nhân Linux. Người đọc sẽ nắm vững các phương pháp lập trình để cấu hình, yêu cầu và quản lý các chân GPIO.

Phạm vi áp dụng

Phiên bản Kernel Tệp trình điều khiển
Linux-4.9 trở lên pinctrl-sunxi.c

Đối tượng

Tài liệu này dành cho các nhà phát triển cần lập trình trình điều khiển thiết bị trên nền tảng Sunxi trong nhân Linux.

Giới thiệu về các Module

Khung Pinctrl trong nhân Linux được phát triển để chuẩn hóa việc quản lý các chân (pin) trên các chip SoC khác nhau, nhằm tránh việc mỗi nhà sản xuất SoC phải tự triển khai một hệ thống con quản lý chân riêng. Điều này giúp giảm đáng kể công sức khi porting hệ thống.

Chức năng chính của Module

Nhiều chip SoC tích hợp các bộ điều khiển chân, cho phép cấu hình chức năng và đặc tính của một hoặc một nhóm chân. Về mặt phần mềm, trình điều khiển Pinctrl của nhân Linux có thể thực hiện các tác vụ sau:

  • Liệt kê và đặt tên cho tất cả các chân mà bộ điều khiển chân có thể quản lý.
  • Cung cấp khả năng đa chức năng cho chân (pin multiplexing).
  • Cung cấp khả năng cấu hình các đặc tính của chân như cường độ dòng kéo/đẩy (drive strength), kéo lên/kéo xuống (pull-up/pull-down), thuộc tính dữ liệu.
  • Tương tác với hệ thống con GPIO.
  • Thực hiện chức năng ngắt dựa trên chân (pin interrupt).

Các thuật ngữ liên quan

Dưới đây là các thuật ngữ chính liên quan đến module Pinctrl:

Thuật ngữ Giải thích
SUNXI Tên gọi chung cho các nền tảng phần cứng SoC của Allwinner.
Bộ điều khiển chân (Pin Controller) Một sự trừu tượng hóa phần mềm của module phần cứng, thường đại diện cho một bộ điều khiển vật lý có khả năng xử lý đa chức năng chân và cấu hình thuộc tính.
Chân (Pin) Tùy thuộc vào kiểu đóng gói của chip, chân có thể có dạng bi, kim, v.v. Trong phần mềm, chúng thường được biểu diễn bằng một tập hợp các số nguyên không dấu từ 0 đến giá trị pin tối đa.
Nhóm chân (Pin Groups) Các thiết bị ngoại vi thường sử dụng nhiều hơn một chân. Ví dụ, một thiết bị SPI có thể sử dụng các chân {0, 8, 16, 24} của SoC, trong khi một thiết bị I2C sử dụng các chân {24, 25}. Đây được gọi là hai nhóm chân. Hệ thống con bộ điều khiển chân cần một cơ chế để liệt kê các nhóm chân và truy xuất các chân thực tế trong một nhóm cụ thể.
Cấu hình chân (Pinconfig) Các chân có thể được cấu hình theo nhiều cách khác nhau bằng phần mềm, chủ yếu liên quan đến đặc tính điện của chúng khi hoạt động như đầu vào/đầu ra. Ví dụ, một chân đầu ra có thể được đặt ở trạng thái trở kháng cao (high-impedance) hoặc "ba trạng thái" (tri-state), nghĩa là nó bị ngắt kết nối hiệu quả. Hoặc một chân đầu vào có thể được kết nối với VDD hoặc GND (kéo lên/kéo xuống) để đảm bảo có giá trị xác định khi không có tín hiệu điều khiển.
Đa chức năng chân (Pinmux) Chức năng cho phép một chân vật lý cụ thể (ball/pad/finger/v.v.) được sử dụng cho nhiều chức năng mở rộng khác nhau để hỗ trợ các gói điện có chức năng đa dạng.
Cây thiết bị (Device Tree) Như tên gọi, đây là một cấu trúc dữ liệu mô tả tài nguyên hệ thống như số lượng và loại CPU, địa chỉ bộ nhớ cơ sở, bus và cầu nối, kết nối thiết bị ngoại vi, bộ điều khiển ngắt, GPIO và đồng hồ. Trình điều khiển Pinctrl hỗ trợ lấy thông tin cấu hình chân từ các nút thiết bị được định nghĩa trong Device Tree.

Kiến trúc tổng thể

Kiến trúc module trình điều khiển Pinctrl của Sunxi được minh họa dưới đây, chia thành bốn phần chính: API Pinctrl, khung Pinctrl chung, trình điều khiển Pinctrl Sunxi và cấu hình bo mạch. (Lớp trên cùng trong hình, "device driver", đại diện cho người dùng của trình điều khiển Pinctrl).

  • Pinctrl API: Các giao diện mà Pinctrl cung cấp cho người dùng ở lớp trên.
  • Pinctrl framework: Khung trình điều khiển Pinctrl được Linux cung cấp.
  • Pinctrl sunxi driver: Trình điều khiển phải được triển khai cho nền tảng Sunxi.
  • Board configuration: Thông tin cấu hình chân của thiết bị, thường được cấu hình bằng Device Tree.

Trạng thái (State), Đa chức năng chân (Pinmux) và Cấu hình chân (Pinconfig)

Khung Pinctrl chủ yếu xử lý ba chức năng: trạng thái chân (pinstate), đa chức năng chân (pinmux) và cấu hình chân (pinconfig). Mối quan hệ giữa pinstate và pinmux/pinconfig được minh họa dưới đây.

Hệ thống có thể hoạt động ở nhiều trạng thái khác nhau, và cấu hình chân có thể thay đổi tương ứng. Ví dụ, khi hệ thống hoạt động bình thường, các chân của thiết bị cần một tập hợp cấu hình nhất định; nhưng khi hệ thống vào chế độ ngủ để tiết kiệm điện, các chân có thể cần một tập hợp cấu hình khác. Khung Pinctrl giúp quản lý hiệu quả các cấu hình chân cho thiết bị trong các trạng thái khác nhau.

Cấu trúc mã nguồn

linux
├── drivers
│   ├── pinctrl
│   │   ├── Kconfig
│   │   ├── Makefile
│   │   ├── core.c
│   │   ├── core.h
│   │   ├── devicetree.c
│   │   ├── devicetree.h
│   │   ├── pinconf.c
│   │   ├── pinconf.h
│   │   ├── pinmux.c
│   │   └── pinmux.h
│   └── sunxi
│       ├── pinctrl-sunxi-test.c
│       ├── pinctrl-sun*.c
│       └── pinctrl-sun*-r.c
└── include
    └── linux
        ├── pinctrl
        │   ├── consumer.h
        │   ├── devinfo.h
        │   ├── machine.h
        │   ├── pinconf-generic.h
        │   ├── pinconf.h
        │   ├── pinctrl-state.h
        │   ├── pinctrl.h
        │   └── pinmux.h

Cấu hình Module

Cấu hình Kernel Menuconfig

Để cấu hình trình điều khiển Pinctrl của Allwinner, bạn cần làm theo các bước sau trong menu cấu hình nhân Linux:

  1. Vào thư mục gốc của Longan và chạy ./build.sh menuconfig.
  2. Từ menu chính, chọn Device Drivers.
  3. Tiếp tục chọn Pin controllers.
  4. Sau đó, chọn Allwinner SoC PINCTRL DRIVER.
  5. Trình điều khiển Pinctrl của Sunxi thường được biên dịch trực tiếp vào nhân (ví dụ: trên nền tảng sun50iw9p1, các nền tảng khác tương tự).

Cấu trúc và đường dẫn mã nguồn Device Tree

Đối với Linux 4.9:

  • Cấu hình Device Tree chung cho tất cả các giải pháp SoC (CPU ARM64): kernel/{KERNEL}/arch/arm64/boot/dts/sunxi/sun*-pinctrl.dtsi.
  • Cấu hình Device Tree chung cho tất cả các giải pháp SoC (CPU ARM32): kernel/{KERNEL}/arch/arm32/boot/dts/sun*-pinctrl.dtsi.
  • Đường dẫn Device Tree cấp bo mạch (board.dts): /device/config/chips/{IC}/configs/{BOARD}/board.dts.

Cấu trúc bao gồm của Device Tree:

board.dts
└── sun*.dtsi
    ├── sun*-pinctrl.dtsi
    └── sun*-clk.dtsi

Đối với Linux 5.4:

  • Trong nhân 5.4, không còn duy trì các tệp pinctrl.dtsi riêng biệt cho ARM64/ARM32. Thông tin chân được đặt trực tiếp trong: kernel/{KERNEL}/arch/arm64/boot/dts/sun*.dtsi hoặc kernel/{KERNEL}/arch/arm32/boot/dts/sun*.dtsi.
  • Đường dẫn Device Tree cấp bo mạch (board.dts): /device/config/chips/{IC}/configs/{BOARD}/board.dts.

Cấu trúc bao gồm của Device Tree:

board.dts
└── sun*.dtsi

Cấu hình chung cho bộ điều khiển GPIO trong Device Tree

Trong tệp kernel/{KERNEL}/arch/arm64/boot/dts/sunxi/sun*-pinctrl.dtsi (đối với Linux 5.4, trực tiếp trong sun*.dtsi), các thông tin cấu hình chung cho bộ điều khiển Pinctrl của SoC được định nghĩa. Thông thường, không nên sửa đổi các tệp này, chúng được duy trì bởi người phát triển trình điều khiển Pinctrl. Hiện tại, trên nền tảng Sunxi, chúng ta đăng ký hai thiết bị Pinctrl dựa trên các miền nguồn: thiết bị r_pio (tất cả các chân sau PL0) và thiết bị pio (tất cả các chân trước PL0). Thông tin cấu hình chung cho hai thiết bị này như sau:

r_pio: pinctrl@07022000 {
    compatible = "allwinner,sun50iw9p1-r-pinctrl"; // Thuộc tính tương thích, dùng để gắn kết driver và thiết bị
    reg = <0x0 0x07022000 0x0 0x400>; // Địa chỉ cơ sở thanh ghi 0x07022000 và phạm vi 0x400
    clocks = <&clk_cpurpio>; // Đồng hồ sử dụng bởi r_pio
    device_type = "r_pio"; // Loại thiết bị
    gpio-controller; // Chỉ ra đây là một bộ điều khiển GPIO
    interrupt-controller; // Chỉ ra đây là một bộ điều khiển ngắt, có thể xóa nếu không hỗ trợ ngắt
    #interrupt-cells = <3>; // Số lượng tham số cần cấu hình cho thuộc tính ngắt pin, có thể xóa nếu không hỗ trợ ngắt
    #size-cells = <0>; // Không sử dụng, cấu hình là 0
    #gpio-cells = <6>; // Số lượng tham số cần cấu hình cho thuộc tính GPIO, đối với Linux-5.4 là 3

    /*
     * Các cấu hình sau đây là cấu hình chân được sử dụng bởi module. Module thao tác với chân
     * bằng cách tham chiếu đến các nút tương ứng.
     * Vì các chân thường thay đổi giữa các bo mạch, khuyến nghị sửa đổi thông qua board dts
     * (tham khảo phần tiếp theo).
     */
    s_rsb0_pins_a: s_rsb0@0 {
        allwinner,pins = "PL0", "PL1";
        allwinner,function = "s_rsb0";
        allwinner,muxsel = <2>;
        allwinner,drive = <2>;
        allwinner,pull = <1>;
    };

    /*
     * Đối với Linux-5.4, cấu hình chân của module được tham chiếu như sau. Module thao tác
     * với chân bằng cách tham chiếu đến các nút tương ứng.
     * Vì các chân thường thay đổi giữa các bo mạch, khuyến nghị đặt tham chiếu chân của module
     * vào board dts (ví dụ: pinctrl-0 = <&scr1_ph_pins>;) và sử dụng tên có tính nhận diện cao hơn
     * như scr1_ph_pins.
     */
    scr1_ph_pins: scr1-ph-pins {
        pins = "PH0", "PH1";
        function = "sim1";
        drive-strength = <10>;
        bias-pull-up;
    };
};

pio: pinctrl@0300b000 {
    compatible = "allwinner,sun50iw9p1-pinctrl"; // Thuộc tính tương thích
    reg = <0x0 0x0300b000 0x0 0x400>; // Địa chỉ cơ sở thanh ghi và phạm vi
    interrupts = , // Cấu hình ngắt và số ngắt GIC cho mỗi bank hỗ trợ ngắt
                 ,
                 ,
                 ,
                 ,
                 ,
                 ;
    device_type = "pio"; // Loại thiết bị
    clocks = <&clk_pio>, <&clk_losc>, <&clk_hosc>; // Đồng hồ sử dụng bởi thiết bị
    gpio-controller; // Là bộ điều khiển GPIO
    interrupt-controller; // Là bộ điều khiển ngắt
    #interrupt-cells = <3>; // Số lượng tham số cho thuộc tính ngắt pin
    #size-cells = <0>; // Không sử dụng
    #gpio-cells = <6>; // Số lượng tham số cho thuộc tính GPIO, đối với Linux-5.4 là 3
    /* Lấy thời gian chống rung (debounce) theo micro giây làm đối số */
};

Cấu hình cấp bo mạch trong board.dts

Tệp board.dts chứa thông tin thiết bị cụ thể cho mỗi nền tảng bo mạch (ví dụ: bo mạch demo, bo mạch demo2.0). Ví dụ, đối với bo mạch demo, đường dẫn board.dts là:

/device/config/chips/{CHIP}/configs/demo/board.dts

Các cấu hình trong board.dts sẽ áp dụng các quy tắc ghi đè sau nếu chúng tồn tại trong các tệp *.dtsi (ví dụ: sun50iw9p1.dtsi):

  • Đối với các thuộc tính và nút trùng lặp, thông tin cấu hình trong board.dts sẽ ghi đè lên thông tin trong *.dtsi.
  • Các thuộc tính và nút mới thêm vào sẽ được bổ sung vào tệp .dtb cuối cùng.

Ví dụ cấu hình Pinctrl đơn giản trong board.dts trên Linux 4.9:

pio: pinctrl@0300b000 {
    input-debounce = <0 0 0 0 0 0 0>; /* Cấu hình tần số lấy mẫu ngắt, mỗi giá trị tương ứng
                                        với một bank hỗ trợ ngắt, đơn vị micro giây */

    spi0_pins_a: spi0@0 {
        allwinner,pins = "PC0", "PC2", "PC4";
        allwinner,pname = "spi0_sclk", "spi0_mosi", "spi0_miso";
        allwinner,function = "spi0";
    };
};

Đối với Linux 5.4, không nên sử dụng phương pháp ghi đè trên. Thay vào đó, hãy sửa đổi nút tham chiếu pinctrl-0 của trình điều khiển.

Ví dụ cấu hình board.dts trên Linux 5.4:

&pio{
    input-debounce = <0 0 0 0 1 0 0 0 0>; // Cấu hình tần số lấy mẫu ngắt, mỗi giá trị tương ứng
                                        // với một bank hỗ trợ ngắt, đơn vị micro giây
    vcc-pe-supply = <&reg_pio1_8>; // Cấu hình điện áp chịu đựng của cổng IO,
                                   // ví dụ: cổng PE được đặt là 1.8V
};

Mô tả giao diện Module

Giao diện Pinctrl

pinctrl_get

  • Nguyên mẫu hàm: struct pinctrl *pinctrl_get(struct device *dev);
  • Mục đích: Lấy một con trỏ xử lý Pinctrl cho thiết bị. Tất cả các thao tác trên chân phải dựa trên con trỏ này.
  • Tham số:
    • dev: Con trỏ tới cấu trúc thiết bị yêu cầu con trỏ xử lý Pinctrl.
  • Trả về:
    • Thành công: Trả về con trỏ xử lý Pinctrl.
    • Thất bại: Trả về NULL.

pinctrl_put

  • Nguyên mẫu hàm: void pinctrl_put(struct pinctrl *p);
  • Mục đích: Giải phóng con trỏ xử lý Pinctrl. Phải được sử dụng cùng với pinctrl_get.
  • Tham số:
    • p: Con trỏ tới con trỏ xử lý Pinctrl cần giải phóng.
  • Trả về: Không có giá trị trả về.

Cảnh báo: Phải được sử dụng cùng với pinctrl_get.

devm_pinctrl_get

  • Nguyên mẫu hàm: struct pinctrl *devm_pinctrl_get(struct device *dev);
  • Mục đích: Tương tự như pinctrl_get, nhưng devm_pinctrl_get sẽ ghi lại con trỏ xử lý Pinctrl được yêu cầu và liên kết nó với thông tin của thiết bị. Khi trình điều khiển thiết bị yêu cầu tài nguyên pin, nên ưu tiên sử dụng giao diện devm_pinctrl_get.
  • Tham số:
    • dev: Con trỏ tới cấu trúc thiết bị yêu cầu con trỏ xử lý Pinctrl.
  • Trả về:
    • Thành công: Trả về con trỏ xử lý Pinctrl.
    • Thất bại: Trả về NULL.

devm_pinctrl_put

  • Nguyên mẫu hàm: void devm_pinctrl_put(struct pinctrl *p);
  • Mục đích: Giải phóng con trỏ xử lý Pinctrl. Phải được sử dụng cùng với devm_pinctrl_get.
  • Tham số:
    • p: Con trỏ tới con trỏ xử lý Pinctrl cần giải phóng.
  • Trả về: Không có giá trị trả về.

Cảnh báo: Phải được sử dụng cùng với devm_pinctrl_get, nhưng không nhất thiết phải gọi tường minh giao diện này.

pinctrl_lookup_state

  • Nguyên mẫu hàm: struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name);
  • Mục đích: Tìm kiếm một con trỏ trạng thái (state handle) dựa trên con trỏ xử lý Pinctrl và tên trạng thái.
  • Tham số:
    • p: Con trỏ tới con trỏ xử lý Pinctrl cần thao tác.
    • name: Tên của trạng thái, ví dụ: "default", "sleep".
  • Trả về:
    • Thành công: Trả về con trỏ trạng thái struct pinctrl_state *.
    • Thất bại: Trả về NULL.

pinctrl_select_state

  • Nguyên mẫu hàm: int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);
  • Mục đích: Đặt trạng thái của các chân được quản lý bởi con trỏ xử lý Pinctrl p thành trạng thái được chỉ định bởi con trỏ trạng thái s.
  • Tham số:
    • p: Con trỏ tới con trỏ xử lý Pinctrl cần thao tác.
    • s: Con trỏ tới con trỏ trạng thái.
  • Trả về:
    • Thành công: Trả về 0.
    • Thất bại: Trả về mã lỗi.

devm_pinctrl_get_select

  • Nguyên mẫu hàm: struct pinctrl *devm_pinctrl_get_select(struct device *dev, const char *name);
  • Mục đích: Lấy con trỏ xử lý Pinctrl cho thiết bị và thiết lập con trỏ đó ở trạng thái được chỉ định.
  • Tham số:
    • dev: Con trỏ tới cấu trúc thiết bị quản lý con trỏ xử lý Pinctrl.
    • name: Tên của trạng thái cần đặt, ví dụ: "default", "sleep".
  • Trả về:
    • Thành công: Trả về con trỏ xử lý Pinctrl.
    • Thất bại: Trả về NULL.

devm_pinctrl_get_select_default

  • Nguyên mẫu hàm: struct pinctrl *devm_pinctrl_get_select_default(struct device *dev);
  • Mục đích: Lấy con trỏ xử lý Pinctrl cho thiết bị và thiết lập con trỏ đó ở trạng thái mặc định.
  • Tham số:
    • dev: Con trỏ tới cấu trúc thiết bị quản lý con trỏ xử lý Pinctrl.
  • Trả về:
    • Thành công: Trả về con trỏ xử lý Pinctrl.
    • Thất bại: Trả về NULL.

pin_config_get

  • Mục đích: Lấy thuộc tính của một chân cụ thể.
  • Tham số:
    • dev_name: Tên của thiết bị Pinctrl.
    • name: Tên của chân.
    • config: Biến để lưu thông tin cấu hình của chân.
  • Trả về:
    • Thành công: Trả về số hiệu của chân.
    • Thất bại: Trả về mã lỗi.

Cảnh báo: Giao diện này đã bị loại bỏ trong Linux 5.4.

pin_config_set

  • Mục đích: Đặt thuộc tính của một chân cụ thể.
  • Tham số:
    • dev_name: Tên của thiết bị Pinctrl.
    • name: Tên của chân.
    • config: Thông tin cấu hình của chân.
  • Trả về:
    • Thành công: Trả về 0.
    • Thất bại: Trả về mã lỗi.

Cảnh báo: Giao diện này đã bị loại bỏ trong Linux 5.4.

Giao diện GPIO

gpio_request

  • Nguyên mẫu hàm: int gpio_request(unsigned gpio, const char *label);
  • Mục đích: Yêu cầu một chân GPIO, giành quyền truy cập vào chân đó.
  • Tham số:
    • gpio: Số hiệu GPIO.
    • label: Tên nhãn cho GPIO, có thể là NULL.
  • Trả về:
    • Thành công: Trả về 0.
    • Thất bại: Trả về mã lỗi.

gpio_free

  • Nguyên mẫu hàm: void gpio_free(unsigned gpio);
  • Mục đích: Giải phóng một chân GPIO.
  • Tham số:
    • gpio: Số hiệu GPIO.
  • Trả về: Không có giá trị trả về.

gpio_direction_input

  • Nguyên mẫu hàm: int gpio_direction_input(unsigned gpio);
  • Mục đích: Đặt chân GPIO làm đầu vào.
  • Tham số:
    • gpio: Số hiệu GPIO.
  • Trả về:
    • Thành công: Trả về 0.
    • Thất bại: Trả về mã lỗi.

__gpio_get_value

  • Nguyên mẫu hàm: int __gpio_get_value(unsigned gpio);
  • Mục đích: Đọc giá trị mức logic của chân GPIO (sau khi đã được cấu hình là đầu vào/đầu ra).
  • Tham số:
    • gpio: Số hiệu GPIO.
  • Trả về: Trả về mức logic của GPIO: 1 cho cao, 0 cho thấp.

__gpio_set_value

  • Nguyên mẫu hàm: void __gpio_set_value(unsigned gpio, int value);
  • Mục đích: Đặt giá trị mức logic của chân GPIO (sau khi đã được cấu hình là đầu vào/đầu ra).
  • Tham số:
    • gpio: Số hiệu GPIO.
    • value: Giá trị mức logic mong muốn: khác 0 cho cao, 0 cho thấp.
  • Trả về: Không có giá trị trả về.

of_get_named_gpio

  • Nguyên mẫu hàm: int of_get_named_gpio(struct device_node *np, const char *propname, int index);
  • Mục đích: Phân tích thuộc tính GPIO từ Device Tree (DTS) theo tên và trả về số hiệu GPIO.
  • Tham số:
    • np: Con trỏ tới nút thiết bị sử dụng GPIO.
    • propname: Tên của thuộc tính trong DTS.
    • index: Chỉ số của thuộc tính trong DTS (nếu là một danh sách).
  • Trả về:
    • Thành công: Trả về số hiệu GPIO.
    • Thất bại: Trả về mã lỗi.

of_get_named_gpio_flags

  • Nguyên mẫu hàm: int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, enum of_gpio_flags *flags);
  • Mục đích: Phân tích thuộc tính GPIO từ DTS theo tên và trả về số hiệu GPIO cùng với cờ cấu hình.
  • Tham số:
    • np: Con trỏ tới nút thiết bị sử dụng GPIO.
    • list_name: Tên của thuộc tính danh sách trong DTS.
    • index: Chỉ số của thuộc tính trong danh sách.
    • flags: Trên nền tảng Sunxi (Linux 4.9 trở về trước), biến này phải được định nghĩa là kiểu struct gpio_config * vì Pinctrl của Sunxi hỗ trợ thông tin kéo lên/kéo xuống, cường độ dòng kéo/đẩy, v.v., trong khi enum of_gpio_flags * chuẩn của kernel chỉ chứa thông tin đầu vào/đầu ra. Nền tảng Sunxi sau này cần chuẩn hóa giao diện này.
  • Trả về:
    • Thành công: Trả về số hiệu GPIO.
    • Thất bại: Trả về mã lỗi.

Cảnh báo: Tham số flags của giao diện này, trên các nền tảng Sunxi Linux 4.9 trở về trước, phải được định nghĩa là biến kiểu struct gpio_config. Linux 5.4 đã chuẩn hóa giao diện này và sử dụng trực tiếp định nghĩa enum of_gpio_flags.

Ví dụ sử dụng

Ví dụ cấu hình DTS cho việc sử dụng chân

Đối với các trình điều khiển sử dụng chân, chúng thường cấu hình một số chức năng phổ biến của chân như sau:

  • Trình điều khiển chỉ cấu hình GPIO chung, tức là dùng cho đầu vào, đầu ra hoặc ngắt.
  • Trình điều khiển cấu hình đa chức năng chân (pin mux) cho các thiết bị như UART, LCD, v.v., cho các chức năng đặc biệt.
  • Trình điều khiển cấu hình cả chức năng chung và đặc tính của chân.

Dưới đây là mô tả cho các kịch bản sử dụng phổ biến:

Cấu hình chức năng GPIO chung/Ngắt

Cách 1: Cấu hình GPIO, Ngắt. Ví dụ cấu hình Device Tree:

soc {
    ...
    gpiokey {
        device_type = "gpiokey";
        compatible = "gpio-keys";

        ok_key {
            device_type = "ok_key";
            label = "ok_key";
            gpios = <&r_pio PL 0x4 0x0 0x1 0x0 0x1>; // Đối với Linux-5.4, nên là gpios = <&r_pio 0 4 GPIO_ACTIVE_HIGH>;
            linux,input-type = "1>";
            linux,code = <0x1c>;
            wakeup-source = <0x1>;
        };
    };
    ...
};

Giải thích: Phương pháp cấu hình GPIO đầu vào/đầu ra/ngắt sử dụng DTS. Giải thích các tham số cấu hình:

Đối với Linux-4.9:

gpios = <&r_pio PL 0x4 0x0 0x1 0x0 0x1>;
          |    |   |   |   |   |   `--- Mức đầu ra, chỉ có hiệu lực với đầu ra
          |    |   |   |   |   `------- Cường độ dòng kéo/đẩy (drive strength), giá trị 0x0 dùng mặc định
          |    |   |   |   `----------- Kéo lên/xuống (pull-up/pull-down), giá trị 0x1 dùng mặc định
          |    |   |   `--------------- Loại đa chức năng (multiplexing type)
          |    |   `------------------- Chân nào trong bank hiện tại
          |    `----------------------- Bank nào
          `--------------------------- Trỏ đến PIO nào, thuộc CPU thì dùng &r_pio

Khi cấu hình GPIO theo cách trên, trình điều khiển cần gọi giao diện sau để phân tích các tham số cấu hình từ DTS:

int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index,
                            enum of_gpio_flags *flags)

Sau khi lấy được thông tin cấu hình GPIO (lưu trong tham số flags, xem mục 4.2.8), hãy gọi các giao diện chuẩn tương ứng để thực hiện chức năng mong muốn.

Đối với Linux-5.4:

gpios = <&r_pio 0 4 GPIO_ACTIVE_HIGH>;
          |   |   |
          |   |   `------------------- Trạng thái khi GPIO hoạt động (active). Nếu cần kéo lên/xuống,
          |   |                       có thể OR thêm cờ GPIO_PULL_UP hoặc GPIO_PULL_DOWN.
          |   `----------------------- Bank nào (ví dụ: 0 cho bank PL)
          `--------------------------- Trỏ đến PIO nào, thuộc CPU thì dùng &r_pio

Cách 2: Cấu hình chân thiết bị (Pinmux)

Ví dụ cấu hình Device Tree:

soc {
    pio: pinctrl@0300b000 {
        ...
        uart0_ph_pins_a: uart0-ph-pins-a {
            allwinner,pins = "PH7", "PH8";
            allwinner,function = "uart0";
            allwinner,muxsel = <3>;
            allwinner,drive = <0x1>;
            allwinner,pull = <0x1>;
        };
        /* Đối với Linux-5.4, vui lòng sử dụng cách cấu hình dưới đây */
        mmc2_ds_pin: mmc2-ds-pin {
            pins = "PC1";
            function = "mmc2";
            drive-strength = <30>;
            bias-pull-up;
        };
        ...
    };
    ...
    uart0: uart@05000000 {
        compatible = "allwinner,sun8i-uart";
        device_type = "uart0";
        reg = <0x0 0x05000000 0x0 0x400>;
        interrupts = ;
        clocks = <&clk_uart0>;
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&uart0_pins_a>;
        pinctrl-1 = <&uart0_pins_b>;
        uart0_regulator = "vcc-io";
        uart0_port = <0>;
        uart0_type = <2>;
    };
    ...
};

Trong đó:

  • pinctrl-0 tương ứng với "default" trong pinctrl-names, tức là cấu hình chân cho chế độ hoạt động bình thường của module.
  • pinctrl-1 tương ứng với "sleep" trong pinctrl-names, tức là cấu hình chân cho chế độ ngủ của module.

Ví dụ sử dụng giao diện

Cấu hình chân thiết bị

Thông thường, trình điều khiển thiết bị chỉ cần sử dụng giao diện devm_pinctrl_get_select_default để yêu cầu tất cả tài nguyên chân của thiết bị.

static int my_driver_init(struct platform_device *plat_dev)
{
    struct pinctrl *device_pinctrl;
    
    // Yêu cầu pinctrl cho thiết bị, đặt ở trạng thái mặc định
    device_pinctrl = devm_pinctrl_get_select_default(&plat_dev->dev);
    if (IS_ERR_OR_NULL(device_pinctrl)) {
        dev_err(&plat_dev->dev, "Không thể lấy pinctrl mặc định cho thiết bị!\n");
        return -EINVAL;
    }

    dev_info(&plat_dev->dev, "Đã lấy pinctrl và đặt trạng thái mặc định thành công.\n");
    return 0;
}

Lấy số hiệu GPIO

static int my_gpio_number_get(struct platform_device *plat_dev)
{
    struct device *hw_dev = &plat_dev->dev;
    struct device_node *dev_node = hw_dev->of_node;
    unsigned int gpio_number;

    // Lấy cấu hình GPIO từ nút thiết bị.
    gpio_number = of_get_named_gpio(dev_node, "my-virtual-gpio", 0);
    if (!gpio_is_valid(gpio_number)) {
        if (gpio_number != -EPROBE_DEFER)
            dev_err(hw_dev, "Lỗi khi lấy GPIO 'my-virtual-gpio'\n");
        return gpio_number;
    }
    dev_info(hw_dev, "Đã lấy GPIO số %d thành công.\n", gpio_number);
    return 0;
}

Cấu hình thuộc tính GPIO

Sử dụng các giao diện pin_config_set/pin_config_get/pin_config_group_set/pin_config_group_get để điều khiển riêng các thuộc tính của một chân hoặc nhóm chân cụ thể.

// Định nghĩa giả cho cấu hình GPIO Sunxi, cần thay thế bằng định nghĩa thực tế
struct sunxi_gpio_config {
    const char *name;
    unsigned int mulsel; // Pinmux function
    unsigned int pull;   // Pull-up/down
    unsigned int drive;  // Drive strength
    unsigned int data;   // Initial data level
};

// Định nghĩa giả cho các hàm đóng gói/giải gói cấu hình
#define SUNXI_PINCFG_TYPE_FUNC 0x1
#define SUNXI_PINCFG_TYPE_PUD  0x2
#define SUNXI_PINCFG_TYPE_DRV  0x3
#define SUNXI_PINCFG_TYPE_DAT  0x4
#define SUNXI_PINCFG_PACK(type, val)   ((unsigned long)(((type) << 16) | (val)))
#define SUNXI_PINCFG_UNPACK_VALUE(cfg) ((unsigned int)((cfg) & 0xFFFF))
#define GPIO_PULL_DEFAULT 0x0
#define GPIO_DRVLVL_DEFAULT 0x0
#define GPIO_DATA_DEFAULT 0x0

// Giả định SUNXI_PINCTRL là một tên thiết bị pinctrl hợp lệ
#define SUNXI_PINCTRL "pio"

// Giả định hàm dt_get_gpio_list và pin_config_get đã được định nghĩa
extern int dt_get_gpio_list(struct device_node *node, struct sunxi_gpio_config **list, unsigned int *count);
extern int pin_config_get(const char *dev_name, const char *pin_name, unsigned long *config);

static int check_pin_resources(void)
{
    struct device *current_dev;
    struct device_node *device_node;
    struct pinctrl *gpio_pinctrl;
    struct sunxi_gpio_config *pin_config_list = NULL;
    unsigned int pin_count = 0;
    unsigned int idx;
    unsigned long current_config;
    int op_result;

    // Tìm thiết bị để kiểm tra
    current_dev = bus_find_device_by_name(&platform_bus_type, NULL, "my-test-device"); // Thay "my-test-device" bằng tên thiết bị thực tế
    if (!current_dev) {
        pr_warn("Không tìm thấy thiết bị ['my-test-device']...\n");
        return -EINVAL;
    }

    device_node = of_find_node_by_type(NULL, dev_name(current_dev));
    if (!device_node) {
        pr_warn("Không tìm thấy nút DTS cho thiết bị ['%s']...\n", dev_name(current_dev));
        put_device(current_dev); // Giải phóng tham chiếu thiết bị
        return -EINVAL;
    }
    current_dev->of_node = device_node;

    pr_warn("===== Bắt đầu kiểm tra tài nguyên chân cho thiết bị ['%s'] =====\n", dev_name(current_dev));

    pr_warn("Bước 1: Yêu cầu tất cả tài nguyên chân.\n");
    gpio_pinctrl = devm_pinctrl_get_select_default(current_dev);
    if (IS_ERR_OR_NULL(gpio_pinctrl)) {
        pr_warn("Yêu cầu con trỏ pinctrl cho thiết bị ['%s'] thất bại...\n", dev_name(current_dev));
        put_device(current_dev);
        return -EINVAL;
    }

    pr_warn("Bước 2: Lấy số lượng chân của thiết bị ['%s'].\n", dev_name(current_dev));
    op_result = dt_get_gpio_list(device_node, &pin_config_list, &pin_count);
    if (op_result < 0 || pin_count == 0) {
        pr_warn("Thiết bị không có tài nguyên chân hoặc không tìm thấy khóa chính!\n");
        devm_pinctrl_put(gpio_pinctrl); // Giải phóng pinctrl
        put_device(current_dev);
        return -EINVAL;
    }

    pr_warn("Bước 3: Lấy và kiểm tra cấu hình chân của thiết bị ['%s'].\n", dev_name(current_dev));
    for (idx = 0; idx < pin_count; idx++) {
        struct sunxi_gpio_config *current_gpio_cfg = &pin_config_list[idx];

        // Kiểm tra cấu hình chức năng
        current_config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_FUNC, 0xFFFF);
        pin_config_get(SUNXI_PINCTRL, current_gpio_cfg->name, ¤t_config);
        if (current_gpio_cfg->mulsel != SUNXI_PINCFG_UNPACK_VALUE(current_config)) {
            pr_err("Lỗi: Giá trị đa chức năng không khớp với DTS cho chân %s.\n", current_gpio_cfg->name);
            op_result = -EINVAL;
            goto cleanup;
        }

        // Kiểm tra cấu hình kéo lên/xuống
        if (current_gpio_cfg->pull != GPIO_PULL_DEFAULT) {
            current_config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, 0xFFFF);
            pin_config_get(SUNXI_PINCTRL, current_gpio_cfg->name, ¤t_config);
            if (current_gpio_cfg->pull != SUNXI_PINCFG_UNPACK_VALUE(current_config)) {
                pr_err("Lỗi: Giá trị kéo lên/xuống không khớp với DTS cho chân %s.\n", current_gpio_cfg->name);
                op_result = -EINVAL;
                goto cleanup;
            }
        }

        // Kiểm tra cấu hình cường độ dòng kéo/đẩy
        if (current_gpio_cfg->drive != GPIO_DRVLVL_DEFAULT) {
            current_config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV, 0XFFFF);
            pin_config_get(SUNXI_PINCTRL, current_gpio_cfg->name, ¤t_config);
            if (current_gpio_cfg->drive != SUNXI_PINCFG_UNPACK_VALUE(current_config)) {
                pr_err("Lỗi: Giá trị cường độ dòng kéo/đẩy không khớp với DTS cho chân %s.\n", current_gpio_cfg->name);
                op_result = -EINVAL;
                goto cleanup;
            }
        }

        // Kiểm tra cấu hình dữ liệu
        if (current_gpio_cfg->data != GPIO_DATA_DEFAULT) {
            current_config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT, 0XFFFF);
            pin_config_get(SUNXI_PINCTRL, current_gpio_cfg->name, ¤t_config);
            if (current_gpio_cfg->data != SUNXI_PINCFG_UNPACK_VALUE(current_config)) {
                pr_err("Lỗi: Giá trị dữ liệu chân không khớp với DTS cho chân %s.\n", current_gpio_cfg->name);
                op_result = -EINVAL;
                goto cleanup;
            }
        }
    }

    pr_warn("Kiểm tra tài nguyên pinctrl thành công!\n");
    op_result = 0;

cleanup:
    // dt_get_gpio_list cần có hàm giải phóng nếu cấp phát động. Giả sử được giải phóng tự động hoặc không cần.
    devm_pinctrl_put(gpio_pinctrl);
    put_device(current_dev); // Giải phóng tham chiếu thiết bị
    pr_warn("===== Kết thúc kiểm tra tài nguyên chân =====\n\n");
    return op_result;
}

// Lưu ý: Tồn tại hai thiết bị pinctrl là SUNXI_PINCTRL và SUNXI_R_PINCTRL.
// Các chân thuộc miền CPU cần sử dụng SUNXI_R_PINCTRL.

Cảnh báo: Trong Linux 5.4, sử dụng pinctrl_gpio_set_config để cấu hình thuộc tính GPIO. Tham số config tương ứng được tạo bằng pinconf_to_config_pack:

  • SUNXI_PINCFG_TYPE_FUNC không còn hiệu lực và chức năng FUNC hiện chưa được hỗ trợ (khuyến nghị sử dụng giao diện pinctrl_select_state thay thế).
  • SUNXI_PINCFG_TYPE_PUD đã được cập nhật thành định nghĩa chuẩn của kernel (PIN_CONFIG_BIAS_PULL_UP/PIN_CONFIG_BIAS_PULL_DOWN).
  • SUNXI_PINCFG_TYPE_DRV đã được cập nhật thành định nghĩa chuẩn của kernel (PIN_CONFIG_DRIVE_STRENGTH), với mối quan hệ giá trị tương ứng là (4.9->5.4: 0->10, 1->20...).
  • SUNXI_PINCFG_TYPE_DAT không còn hiệu lực và chức năng DAT hiện chưa được hỗ trợ (khuyến nghị sử dụng gpio_direction_output hoặc __gpio_set_value để đặt mức logic).

Trình điều khiển thiết bị sử dụng chức năng ngắt GPIO

Cách 1: Lấy số ngắt ảo thông qua gpio_to_irq, sau đó gọi hàm yêu cầu ngắt. Hiện tại, Sunxi-Pinctrl sử dụng irq-domain để triển khai chức năng IRQ ảo cho GPIO. Khi sử dụng chức năng ngắt GPIO, trình điều khiển thiết bị chỉ cần lấy số IRQ ảo thông qua gpio_to_irq, sau đó có thể thao tác như các giao diện IRQ chuẩn khác.

static irqreturn_t gpio_irq_event_handler(int irq, void *dev_id)
{
    // Xử lý sự kiện ngắt GPIO
    pr_info("Ngắt GPIO đã kích hoạt trên IRQ %d!\n", irq);
    return IRQ_HANDLED;
}

static int sunxi_gpio_interrupt_setup(struct platform_device *plat_dev)
{
    struct device *hw_dev = &plat_dev->dev;
    int virtual_irq_num;
    int call_result;

    // Ánh xạ GPIO thành IRQ ảo
    virtual_irq_num = gpio_to_irq(GPIOA(0)); // Giả sử GPIOA(0) là một macro hợp lệ
    if (IS_ERR_VALUE(virtual_irq_num)) {
        dev_warn(hw_dev, "Không thể ánh xạ GPIO [%d] tới IRQ ảo, lỗi = %d\n",
                 GPIOA(0), virtual_irq_num);
        return -EINVAL;
    }
    dev_debug(hw_dev, "GPIO [%d] được ánh xạ tới IRQ ảo [%d] thành công.\n", GPIOA(0), virtual_irq_num);

    // Yêu cầu IRQ ảo, đặt loại IRQ thành kích hoạt mức cao
    call_result = devm_request_irq(hw_dev, virtual_irq_num, gpio_irq_event_handler,
                                   IRQF_TRIGGER_HIGH, "GPIOA0_EINT", NULL);
    if (IS_ERR_VALUE(call_result)) {
        dev_err(hw_dev, "Yêu cầu IRQ ảo %d thất bại, lỗi = %d\n", virtual_irq_num, call_result);
        return -EINVAL;
    }
    dev_info(hw_dev, "Đã yêu cầu IRQ ảo %d cho GPIOA0 thành công.\n", virtual_irq_num);
    return 0;
}

Cách 2: Cấu hình ngắt GPIO thông qua DTS, lấy số ngắt ảo qua các hàm phân tích DTS, sau đó gọi hàm yêu cầu ngắt. Ví dụ:

Cấu hình DTS:

soc {
    ...
    Vdevice: vdevice@0 {
        compatible = "allwinner,sun8i-vdevice";
        device_type = "Vdevice";
        interrupt-parent = <&pio>; // Bộ điều khiển ngắt phụ thuộc (nút có thuộc tính interrupt-controller)
        interrupts = <PD 3 IRQ_TYPE_LEVEL_HIGH>; // Ngắt GPIO, bao gồm bank, offset và kiểu kích hoạt
                        |  |   `------------------ Điều kiện/loại kích hoạt ngắt
                        |  `------------------------- Offset chân trong bank
                        `--------------------------- Bank nào (PD)
        pinctrl-names = "default";
        pinctrl-0 = <&vdevice_pins_a>;
        test-gpios = <&pio PC 3 1 2 2 1>; // Ví dụ cấu hình GPIO
        status = "okay";
    };
    ...
};

Trong trình điều khiển, sử dụng giao diện chuẩn platform_get_irq() để lấy số IRQ ảo, như sau:

static struct completion irq_completion; // Giả định khai báo toàn cục hoặc thuộc cấu trúc thiết bị

static int my_pctrl_driver_probe(struct platform_device *plat_dev)
{
    struct device_node *dev_node = plat_dev->dev.of_node;
    int gpio_irq;
    int init_result;

    if (!dev_node) {
        pr_err("Thiết bị ảo không thể lấy of_node\n");
        return -ENODEV;
    }
    // ... (các khởi tạo khác)

    gpio_irq = platform_get_irq(plat_dev, 0);
    if (gpio_irq < 0) {
        pr_err("Không thể lấy IRQ!\n");
        return -EBUSY;
    }
    // Lưu gpio_irq vào cấu trúc dữ liệu của thiết bị
    // my_device_data->irq = gpio_irq;
    pr_info("Đã lấy IRQ %d từ DTS.\n", gpio_irq);

    init_completion(&irq_completion); // Khởi tạo completion object
    
    return 0; // Thay đổi về 0 nếu thành công
}

// Xử lý ngắt mẫu
static irqreturn_t pin_eint_handler_demo(int irq, void *dev_id)
{
    pr_info("Ngắt chân đã kích hoạt: IRQ %d\n", irq);
    complete(&irq_completion); // Hoàn thành completion object
    return IRQ_HANDLED;
}

// Hàm yêu cầu và kiểm tra ngắt
static int request_and_test_irq(int virq_num)
{
    int request_status;
    int trigger_type = IRQF_TRIGGER_HIGH; // Hoặc IRQF_TRIGGER_LOW, IRQF_TRIGGER_RISING, v.v.

    reinit_completion(&irq_completion); // Đặt lại completion object để chờ lần tiếp theo

    pr_warn("Bước 1: Yêu cầu IRQ (%s mức) cho IRQ: %d.\n",
            (trigger_type == IRQF_TRIGGER_HIGH) ? "cao" : "thấp", virq_num);
    request_status = request_irq(virq_num, pin_eint_handler_demo,
                                 trigger_type, "PIN_EINT_TEST", NULL);
    if (IS_ERR_VALUE(request_status)) {
        pr_warn("Yêu cầu IRQ thất bại!\n");
        return -EINVAL;
    }

    pr_warn("Bước 2: Chờ ngắt.\n");
    request_status = wait_for_completion_timeout(&irq_completion, HZ * 5); // Chờ tối đa 5 giây

    if (request_status == 0) {
        pr_warn("Hết thời gian chờ ngắt!\n");
        free_irq(virq_num, NULL);
        return -ETIMEDOUT;
    }

    free_irq(virq_num, NULL);

    pr_warn("Kiểm tra ngắt chân thành công!\n");
    return 0;
}

Trình điều khiển thiết bị thiết lập chức năng chống rung (debounce) ngắt

Cách 1: Cấu hình debounce cho mỗi bank ngắt thông qua DTS. Ví dụ với thiết bị PIO:

&pio {
    /* Lấy thời gian chống rung theo micro giây làm đối số */
    input-debounce = <0 0 0 0 0 0 0>;
                      | | | | | | `---------- Bank PA
                      | | | | | `------------ Bank PC
                      | | | | `-------------- Bank PD
                      | | | `---------------- Bank PF
                      | | `------------------ Bank PG
                      | `-------------------- Bank PH
                      `---------------------- Bank PI
};

Lưu ý: Giá trị thuộc tính input-debounce phải bao gồm tất cả các bank mà thiết bị PIO hỗ trợ ngắt. Nếu thiếu, các giá trị thuộc tính sẽ được đặt theo thứ tự bank vào thanh ghi debounce, và các bank bị thiếu sẽ có giá trị debounce mặc định (nếu chưa được sửa đổi khi khởi động). Trên nền tảng Sunxi Linux 4.9, tần số lấy mẫu ngắt tối đa là 24M, tối thiểu là 32k, và giá trị thuộc tính debounce chỉ có thể là 0 hoặc 1. Đối với Linux 5.4, phạm vi giá trị debounce là 0~1.000.000 (đơn vị micro giây).

Cách 2: Module trình điều khiển gọi các giao diện liên quan đến GPIO để đặt debounce ngắt.

static inline int gpio_set_debounce(unsigned gpio, unsigned debounce);
int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);

Trong trình điều khiển, bạn có thể gọi hai giao diện trên để đặt thanh ghi debounce ngắt tương ứng với GPIO. Lưu ý rằng debounce được tính bằng mili giây (giao diện này đã bị loại bỏ trong Linux 5.4).

Câu hỏi thường gặp và gỡ lỗi

Các phương pháp gỡ lỗi thông thường

Sử dụng sunxi_dump để đọc/ghi thanh ghi

Cần bật module SUNXI_DUMP:

make kernel_menuconfig
	---> Device Drivers
		---> dump reg driver for sunxi platform (Chọn)

Cách sử dụng:

cd /sys/class/sunxi_dump
1. Đọc một thanh ghi:
echo 0x0300b048 > dump ; cat dump
2. Ghi giá trị vào thanh ghi:
echo 0x0300b058 0xfff > write ; cat write
3. Đọc một vùng thanh ghi liên tiếp:
echo 0x0300b000,0x0300bfff > dump ; cat dump

4. Ghi một nhóm giá trị thanh ghi:
echo 0x0300b058 0xfff,0x0300b0a0 0xfff > write ; cat write

Bằng cách này, bạn có thể xem và sửa đổi các thanh ghi GPIO tương ứng để tìm ra nguyên nhân vấn đề.

Sử dụng nút gỡ lỗi của sunxi_pinctrl

Cần bật DEBUG_FS:

make kernel_menuconfig
	---> Kernel hacking
		---> Compile-time checks and compiler options
			---> Debug Filesystem (Chọn)

Gắn kết hệ thống tệp và vào thư mục tương ứng:

mount -t debugfs none /sys/kernel/debug
cd /sys/kernel/debug/sunxi_pinctrl

1. Xem cấu hình của chân:

echo PC2 > sunxi_pin
cat sunxi_pin_configure

2. Sửa đổi thuộc tính của chân:

Mỗi chân có bốn thuộc tính: đa chức năng (function), dữ liệu (data), cường độ dòng kéo/đẩy (dlevel), kéo lên/kéo xuống (pull). Lệnh để sửa đổi thuộc tính chân như sau:

echo PC2 1 > pull ; cat pull
cat sunxi_pin_configure  // Xem tình trạng sửa đổi

Lưu ý: Trên nền tảng Sunxi, hiện có nhiều thiết bị Pinctrl, bao gồm pio, r_pioaxpxxx-gpio. Khi thao tác với các chân sau PL, vui lòng chuyển đổi thiết bị chân bằng lệnh sau, nếu không thao tác sẽ thất bại:

echo pio > /sys/kernel/debug/sunxi_pinctrl/dev_name // Chuyển sang thiết bị pio
cat /sys/kernel/debug/sunxi_pinctrl/dev_name
echo r_pio > /sys/kernel/debug/sunxi_pinctrl/dev_name // Chuyển sang thiết bị r_pio
cat /sys/kernel/debug/sunxi_pinctrl/dev_name

Sử dụng nút gỡ lỗi của pinctrl core

mount -t debugfs none /sys/kernel/debug
cd /sys/kernel/debug/pinctrl

1. Xem các thiết bị quản lý chân:

cat pinctrl-devices

2. Xem trạng thái của chân và các thiết bị sử dụng tương ứng:

console:/sys/kernel/debug/pinctrl # ls
pinctrl-devices pinctrl-handles pinctrl-maps pio r_pio
console:/sys/kernel/debug/pinctrl # cat pinctrl-handles
Requested pin control handlers their pinmux maps:
device: twi3 current state: sleep
	state: default
        type: MUX_GROUP controller pio group: PA10 (10) function: twi3 (15)
        type: CONFIGS_GROUP controller pio group PA10 (10)config 00001409
config 00000005
        type: MUX_GROUP controller pio group: PA11 (11) function: twi3 (15)
        type: CONFIGS_GROUP controller pio group PA11 (11)config 00001409
config 00000005
	state: sleep
        type: MUX_GROUP controller pio group: PA10 (10) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PA10 (10)config 00001409
config 00000001
        type: MUX_GROUP controller pio group: PA11 (11) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PA11 (11)config 00001409
config 00000001
device: twi5 current state: default
    state: default
        type: MUX_GROUP controller r_pio group: PL0 (0) function: s_twi0 (3)
        type: CONFIGS_GROUP controller r_pio group PL0 (0)config 00001409
config 00000005
        type: MUX_GROUP controller r_pio group: PL1 (1) function: s_twi0 (3)
        type: CONFIGS_GROUP controller r_pio group PL1 (1)config 00001409
config 00000005
	state: sleep
        type: MUX_GROUP controller r_pio group: PL0 (0) function: io_disabled (4)
        type: CONFIGS_GROUP controller r_pio group PL0 (0)config 00001409
config 00000001
        type: MUX_GROUP controller r_pio group: PL1 (1) function: io_disabled (4)
        type: CONFIGS_GROUP controller r_pio group PL1 (1)config 00001409
config 00000001
device: soc@03000000:pwm5@0300a000 current state: active
	state: active
        type: MUX_GROUP controller pio group: PA12 (12) function: pwm5 (16)
        type: CONFIGS_GROUP controller pio group PA12 (12)config 00000001
config 00000000
config 00000000
	state: sleep
        type: MUX_GROUP controller pio group: PA12 (12) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PA12 (12)config 00000001
config 00000000
config 00000000
device: uart0 current state: default
	state: default
	state: sleep
device: uart1 current state: default
	state: default
        type: MUX_GROUP controller pio group: PG6 (95) function: uart1 (37)
        type: CONFIGS_GROUP controller pio group PG6 (95)config 00001409
config 00000005
        type: MUX_GROUP controller pio group: PG7 (96) function: uart1 (37)
        type: CONFIGS_GROUP controller pio group PG7 (96)config 00001409
config 00000005
        type: MUX_GROUP controller pio group: PG8 (97) function: uart1 (37)
        type: CONFIGS_GROUP controller pio group PG8 (97)config 00001409
config 00000005
        type: MUX_GROUP controller pio group: PG9 (98) function: uart1 (37)
        type: CONFIGS_GROUP controller pio group PG9 (98)config 00001409
config 00000005
	state: sleep
        type: MUX_GROUP controller pio group: PG6 (95) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PG6 (95)config 00001409
config 00000001
        type: MUX_GROUP controller pio group: PG7 (96) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PG7 (96)config 00001409
config 00000001
        type: MUX_GROUP controller pio group: PG8 (97) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PG8 (97)config 00001409
config 00000001
        type: MUX_GROUP controller pio group: PG9 (98) function: io_disabled (5)
        type: CONFIGS_GROUP controller pio group PG9 (98)config 00001409
....

Từ một phần nhật ký trên, có thể thấy thiết bị nào quản lý các chân nào và trạng thái hiện tại của chân có chính xác hay không. Ví dụ với thiết bị twi3, nó quản lý các chân PA10/PA11, có hai nhóm trạng thái là sleepdefault. Trạng thái default là trạng thái sử dụng bình thường, trong khi trạng thái sleep cho biết chân ở trạng thái "io disabled", tức là chân không thể sử dụng bình thường. Hiện tại, các chân được sử dụng bởi thiết bị twi3 đang ở trạng thái sleep.

Các bước khắc phục sự cố ngắt GPIO

Ngắt GPIO liên tục phản hồi

  1. Kiểm tra xem tín hiệu ngắt có liên tục kích hoạt hay không.
  2. Sử dụng nút sunxi_dump để xác nhận xem bit pending của ngắt đã được xóa chưa (tham khảo mục 6.1.1).
  3. Có thực hiện chuyển đổi đa chức năng chân (pin mux) cho GPIO phát hiện ngắt trong chương trình xử lý ngắt GPIO hay không. Việc này không được phép vì có thể dẫn đến ngắt bất thường.

Không phát hiện được ngắt GPIO

  1. Kiểm tra xem tín hiệu ngắt có bình thường hay không. Nếu không, kiểm tra phần cứng. Nếu bình thường, chuyển sang bước 2.
  2. Sử dụng nút sunxi_dump để xem bit pending của ngắt GPIO đã được đặt hay chưa. Nếu đã đặt, chuyển sang bước 5. Nếu không, chuyển sang bước 3.
  3. Sử dụng nút sunxi_dump để xem phương thức kích hoạt ngắt của GPIO có được cấu hình chính xác hay không. Nếu đúng, chuyển sang bước 4. Nếu không, chuyển sang bước 5.
  4. Kiểm tra đồng hồ lấy mẫu ngắt. Mặc định nên là 32kHz. Bạn có thể chuyển đồng hồ lấy mẫu ngắt GPIO sang 24MHz thông qua nút sunxi_dump để thử nghiệm.
  5. Sử dụng sunxi_dump để xác nhận ngắt đã được bật hay chưa.

Thẻ: linux GPIO pinctrl Device Tree Kernel Development

Đăng vào ngày 18 tháng 7 lúc 15:15