前回のブログにデバイスツリーを追加しました
【IMX6ULLドライバ開発と学習】 12. Linuxドライバデバイスツリー
このブログでは、Pinctrl サブシステムと GPIO サブシステムの使用法を紹介します。
Pinctrl サブシステム リファレンス ドキュメント:
カーネル ドキュメント リンク: https://www.kernel.org/doc/Documentation/
カーネル ソース ドキュメント: Documentation/devicetree/bindings/pinctrl/pinctrl -バインディング.txt
GPIO サブシステム リファレンス ドキュメント:
カーネル ドキュメント リンク: https://www.kernel.org/doc/Documentation/
カーネル ソース コード ドキュメント: Documentation/devicetree/bindings/gpio/ さまざまなチップ メーカーのドキュメント
コードは [13.led_button_drv_tree_gpio_pinctrl] から取得したものです:
https://gitee.com/chenshao777/imx6-ull_-drivers
Pinctrl サブシステムと GPIO サブシステムを使用する理由は何ですか?
ドライバ開発ではレジスタを使って周辺機器を操作することもできますが、面倒なので
PinctrlサブシステムとGPIOサブシステムを導入します。
Pinctrl サブシステム:ピンの多重化、ピンの電気的特性の構成などに使用できます。
GPIO サブシステム:ピンを制御し、入力と出力を設定します。
デバイス ツリーでハードウェア リソースを定義する以前の方法
(1)デバイス ツリー ノードのピン属性をカスタマイズする (2)
ドライバー コードのof_property_read_u32関数を通じて属性値を読み取り、特定のピンを取得する
(3) GPIO に従ってグループおよびピン ペア レジスタ アドレス マッピング (ioremap) の欠点
:ドライバー コードでレジスタ操作を実行し、アドレス マッピング操作 (ioremap) を実行する必要があります。
Pinctrl サブシステムと GPIO サブシステムの導入
画像を一目で要約する
1. Pinctrl サブシステム
Pinctrl サブシステムはピンの多重化を担当し、属性定義は
ピンを多重化し、ピンを構成するために使用される IOMUX (プルアップ抵抗やプルダウン抵抗など) に対応すると考えることができます。
//client端:
@节点名字 {
pinctrl-names = "default, sleep"; // 定义有几个状态
pinctrl-0 = <&pinctrl_自定义名字A>; // 第一个状态对应的引脚属性
pinctrl-1 = <&pinctrl_自定义名字B>; // 第二个状态对应的引脚属性
xxx-gpio = <&gpiox n flag>; //
status = "okay";
};
//pincontroller服务端
pinctrl_自定义名字A: 自定义名字 {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性, // 引脚 A
>;
};
pinctrl_自定义名字B: 自定义名字 {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性; // 引脚 B
>;
};
PS:
ピンは複数の状態を持つことができます。たとえば、シリアル ポート モードとして構成したり、スリープ中の時間を節約するために GPIO モードとして構成したりできます。
2. gpio 属性を追加し、ピンと有効な状態を指定します
@节点名字 {
pinctrl-names = "default, sleep"; // 定义有几个状态
pinctrl-0 = <&pinctrl_自定义名字A>; // 第一个状态对应的引脚属性
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; //指定GPIO1_3引脚,模式低电平有效
status = "okay";
};
使用例:
デバイスノード
ピンコントロール
聞く:
デバイスノードの led-gpio の後に <&gpio1 3 flag> &gpio1 is a group が続くのはなぜですか
。なぜその後に 2 ビットを追加する必要があるのですか? これは、gpio1 ノードがimx6ull.dtsiファイルで定義されており、pinctrl が定義されているためです ( #gpio-cells = <2>;この属性は、GPIO グループの後に 2 つのパラメーターが続くことを示します)
聞く:
pinctrl側で 「MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0」を取得するにはどうすればよいですか?
IMX6ULL の pinctrl コードを取得したい場合は、公式ツールPins_Tool_for_i.MX_Processors_v6_x64を使用して取得し、リンクをダウンロードして自分で使用できます。
この 0x10B0 は、実際にはリファレンス マニュアルのIOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03レジスタに設定されている値です
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03は、imx6ul-pinfunc.h ファイルで定義されたマクロです。
3. GPIO サブシステム API の使用
新しい GPIO サブシステムのセットが推奨されます
(1) GPIOの取得( gpiod_get )
struct gpio_desc *my_dev_gpio;
........
//从设备树中获取GPIO引脚,并设置成默认输出模式,无效(LOW表示无效逻辑)
my_dev_gpio = gpiod_get(dev, "led", GPIOD_OUT_LOW);
2 番目のパラメータは、デバイス ツリーの xxx-gpio の xxx に対応します。
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
(2) [オプション] カスタム デバイス名プロパティ ( of_property_read_string )を取得します。
char a[20];
const char *str = a;
of_property_read_string(np, "my_name", &str);
(3) デバイスノードの作成(device_create)
//创建设备节点 /dev/xxx
device_create(my_dev_class, NULL, MKDEV(major, 0), NULL, str);
(4)写GPIO(gpiod_set_value)
gpiod_set_value(my_dev_gpio, status);
(5) GPIOを解放し、デバイスノードを破壊します。
//释放GPIO
gpiod_put(my_dev_gpio);
//销毁设备
device_destroy(my_dev_class, MKDEV(major, 0));
完全なコード:
LEDとビープ音の両方を追加したので、デバイスノードを開くときとノードを破棄するときにどちらであるかを区別する必要があるため、以下の2つを配列にし、デバイス内のカスタムmy_name属性でデバイスを区別する必要があります。木
char dev_names[10][20]={
}; //保存设备树中的名字
struct gpio_desc *my_dev_gpio[10];
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/string.h>
int major; //设备号
static struct class *my_dev_class;
int dev_cnt;
char dev_names[10][20]={
}; //保存设备树中的名字
struct gpio_desc *my_dev_gpio[10];
/*=============================file_operations ==============================*/
static ssize_t my_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
printk("drv_read function run....\n");
return 1;
}
static ssize_t my_drv_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
struct inode *inode = file_inode(filp);
int minor = iminor(inode);
char status;
int err;
err = copy_from_user(&status, buf, 1);
gpiod_set_value(my_dev_gpio[minor], status);
printk("drv_write function run....\n");
return 0;
}
static int my_drv_open (struct inode *node, struct file *filp)
{
struct inode *inode = file_inode(filp);
int minor = iminor(inode);
printk("drv_open function run....\n");
gpiod_set_value(my_dev_gpio[minor], 0);
return 0;
}
static int my_drv_release (struct inode *node, struct file *filp)
{
printk("drv_release function run....\n");
return 0;
}
/* operations结构体:为应用层提供驱动接口 */
static struct file_operations my_dev_ops = {
.owner = THIS_MODULE,
.read = my_drv_read,
.write = my_drv_write,
.open = my_drv_open,
.release = my_drv_release,
};
/*=============================platform_driver==============================*/
/* 如果匹配到了内核根据设备树生成的platform_device,
该函数会被调用,如果有多个匹配的设备节点,该函数
会被多次调用
*/
static int my_probe(struct platform_device *pdev)
{
/* 从内核根据设备树生成的 platform_device
结构体中获取到设备节点
*/
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int gpio_pin;
char a[20];
const char *str = a;
of_property_read_string(np, "my_name", &str);
//保存设备的名字
strcpy(dev_names[dev_cnt], str);
//从设备树中获取GPIO引脚,并设置成默认输出模式,无效(LOW表示无效逻辑)
my_dev_gpio[dev_cnt] = gpiod_get(dev, str, GPIOD_OUT_LOW);
//从struct desc结构体转成GPIO子系统标号
gpio_pin = desc_to_gpio(my_dev_gpio[dev_cnt]);
//创建设备节点 /dev/xxx
device_create(my_dev_class, NULL, MKDEV(major, dev_cnt), NULL, str);
dev_cnt++;
printk("my_probe run, my_name = %s\n", str);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
/* 从内核根据设备树生成的 platform_device
结构体中获取到设备节点
*/
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int gpio_pin, i;
char a[20];
const char *str = a;
int flag = 0;
of_property_read_string(np, "my_name", &str);
for(i = 0; i < dev_cnt; i++){
if(strcmp(dev_names[i],str) == 0){
strcpy(dev_names[i], "");
gpio_pin = desc_to_gpio(my_dev_gpio[i]);
//释放GPIO
gpiod_put(my_dev_gpio[i]);
//销毁设备
device_destroy(my_dev_class, MKDEV(major, i));
printk("my_remove run, device_destroy %s, my_gpio = %d\n", str, gpio_pin);
}
if(dev_names[i])
flag = 1;
}
if(flag == 0){
dev_cnt = 0;
printk("all removed\n");
}
return 0;
}
static struct of_device_id my_dev_match[] = {
{
.compatible = "hc-led-beep"},
{
.compatible = "hc-led-beep"},
// {.compatible = "hc-key"},
{
},
};
static struct platform_driver dev_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_platform_driver",
.of_match_table = my_dev_match,
},
};
/*=============================驱动出入口函数==============================*/
/* 驱动入口函数:insmod xx.ko 时会被调用 */
static int dev_init(void)
{
major = register_chrdev(0, "hc_dev_drv", &my_dev_ops);
if(major < 0){
printk("register_chrdev famy\n");
return major;
}
my_dev_class = class_create(THIS_MODULE, "my_dev_class");
if(IS_ERR(my_dev_class)){
printk("class_create failed\n");
return 1;
}
platform_driver_register(&dev_driver);
return 0;
}
/* 驱动出口函数: rmmod xx.ko 时会被调用 */
static void dev_exit(void)
{
platform_driver_unregister(&dev_driver);
class_destroy(my_dev_class);
unregister_chrdev(major, "hc_dev_drv");
printk("my_dev driver exit\n");
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");