約10年前にLinuxカーネルを学びましたが、当時のGPIOの操作はシンプルで失礼でした。今では、pin-ctrl gpioが大量のdtを駆動します。どこから始めればよいのかわかりません。数日間投げた後、メモを取ったので、もう一度忘れませんでした。再び。
オペレーティングレジスタ
チップのマニュアルに従ってGPIO関連のレジスタアドレスを取得します(ここではs5p6818が例です)
//这是芯片手册里给出的真实物理地址
#define PHYS_BASE_GPIOA_OUTDAT (0xC001A000) //
#define PHYS_BASE_GPIOA_OUTEN (0xC001A004)
#define PHYS_BASE_GPIOA_PAD (0xC001A018)
#define PHYS_BASE_GPIOA_FUNC0 (0xC001A020)
#define PHYS_BASE_GPIOA_FUNC1 (0xC001A024)
物理アドレスを直接操作することはできず、ioremapで処理する必要があります。
パラメーター1:実際の物理アドレス
パラメーター2:アドレス空間の長さ(バイト単位)、アームレジスターは通常32であるため、4
void __iomem *gpioa_outdat_addr;
void __iomem *gpioa_outen_addr;
void __iomem *gpioa_pad_addr;
void __iomem *gpioa_func0_addr;
void __iomem *gpioa_func1_addr;
gpioa_outdat_addr = ioremap(PHYS_BASE_GPIOA_OUTDAT,4);
gpioa_outen_addr = ioremap(PHYS_BASE_GPIOA_OUTEN,4);
gpioa_pad_addr = ioremap(PHYS_BASE_GPIOA_PAD,4);
gpioa_func0_addr =ioremap(PHYS_BASE_GPIOA_FUNC0,4);
gpioa_func1_addr =ioremap(PHYS_BASE_GPIOA_FUNC1,4);
得られたvoid__iomem *値は、readlで読み取ることができ(アドレスを読み取ることができます)、writelで書き込むことができます(アドレスを書き込むことができます)。
u32 data_en = readl(gpioa_outen_addr);
data_en = 0x000FFFE2;
writel(data_en,gpioa_outen_addr);
これらのレジスタの使用方法
については、チップのマニュアルを参照し、使用後に忘れずにリリースしてください。
iounmap(gpioa_outdat_addr);
iounmap(gpioa_outen_addr);
iounmap(gpioa_pad_addr);
iounmap(gpioa_func0_addr);
iounmap(gpioa_func1_addr);
読み取りおよび書き込み操作レジスタの速度は非常に遅く、GPIOシミュレーションのタイミング中の速度要件を満たさない場合がありますが、実際にはレジスタの読み取りおよび書き込み速度は非常に高速ですが、これら2つの機能によりいくつかの安全対策が追加されています。速度を追求するには、次を使用できます
。__
raw_writel __raw_readl
これらは、アドレスのフェッチと割り当てのみを行う2つのインライン関数です。
static inline void __raw_writel(u32 value, volatile void __iomem *addr)
{
*(volatile u32 __force *)addr = value;
}
static inline u32 __raw_readl(const volatile void __iomem *addr)
{
return *(const volatile u32 __force *)addr;
}
したがって、より直接的に読み書きできます。
u32 data_en =*(const volatile u32 __force *)gpioa_outen_addr;
data_en = 0x000FFFE2;
*(volatile u32 __force *)gpioa_outen_addr = value;
これの欠点は、場合によっては、コンパイラによって最適化され、割り当てが失敗する可能性があることです。
2つはlinuxgpioドライバーを使用します
レジスタを直接操作する利点は、高速で複数のgpioピンを同時に操作できることです。欠点は、レジスタが多すぎて値の設定が非常に面倒なことです。上記の例では、GPIOを操作するために5つのレジスタを設定する必要があり、対応するビットを見つける必要があります。 、注意しないと間違ってしまい、ビット操作に1日かかる場合があります。gpioドライバーの役割が反映されています。
GPIO操作API:
//申请GPIO,参数1:gpio 编号,参数2:gpio 名字 (自定义)
int gpio_request(unsigned gpio, const char *label)
//释放GPIO ,参数1:gpio编号
void gpio_free(unsigned gpio)
//设置GPIO为输出模式,同时将输出设为value
//参数1:gpio编号,参数2:输出的值 (0或1)
static inline int gpio_direction_output(unsigned gpio, int value)
//设置GPIO为输入模式
static inline int gpio_direction_input(unsigned gpio)
//判断gpio是否有效,就是判断gpio的编号是否在范围内
//比如soc的所有gpio编号是0~128,如果输入编号在此范围则认为有效
//返回值:true 为有效,false为无效。
static inline int gpio_is_valid(unsigned int gpio)
//设置GPIO的状态,参数1:GPIO编号,参数2:设置的值(0或1)
static inline void gpio_set_value(unsigned int gpio, int value)
//获取GPIO的状态,参数1:GPIO编号
//返回值;GPIO 状态,0:GPIO低点位,1:GPIO高电位
static inline int gpio_get_value(unsigned int gpio)
GPIO番号はsocのBSPに関連しており、関連するコードを読み取ることで取得できます。
s5p6818には5つのGPIOポート(A、B、C、D、E)があり、各ポートには32ピン、合計160があるため、0から159まで順番に番号が付けられます(A0 = 0、A1 = 1 ---- --E31 = 159)
このコーディングルールを統一する必要があります。GPIOポートの名前はチップごとに異なり
ます。GPIO0と呼ばれるものもあります。GPIO1 ------ gpio_requestの2番目のパラメーターは、適用されたgpioに個別の名前を付けることです。名前は要件なしで定義されています。繰り返しを避けるために、特殊化のためにいくつかのプレフィックスまたはサフィックスを追加することをお勧めします。
//这段代码将GPIOE_30 设置为输出模式并拉低,20us后再拉高
#define FPGA_POWER_GPIONUM 158 //GPIOE_30
//request PWOER GPIO
if(gpio_request(FPGA_POWER_GPIONUM,"FPGA_POWER")){
gpio_direction_output(FPGA_POWER_GPIONUM,0);
usleep(20);
//因为已经设置为output模式,所以可以直接调用gpio_set_value
//即使gpio为input模式也可以设置,只是不能作用到引脚
gpio_set_value(FPGA_POWER_GPIONUM,1);
}
gpio_requestこれは主に、他のモジュールが占有しているかどうかを判断するためのものです。占有されていない場合は、GPIOシステムに登録して、占有していることを伝えます。この機能が失敗した場合、基本的に他のモジュールもこのGPIOを使用します。この問題は、システムを移行するときに非常に一般的です。独自のボードの配線がデモとは大きく異なるため、デモで移行するときにGPIOが占有されることがよくありますが、デモコードは通常dts gpioピンに設定されるため、DTSを確認してください。このピンはどこに設定されていますか?
3つのGPIO割り込み
また、GPIOドライバーAPIを使用して実装されているため、レジスターを直接操作するのは面倒です。操作しないと、髪の毛がなくなります。
割り込みAPIが含まれるため、割り込みのみ(これは* _ *の範囲を超えています)
//根据gpio编号得到irq编号
static inline int gpio_to_irq(unsigned int gpio)
//使能中断,参数:irq编号
void enable_irq(unsigned int irq)
//禁用中断,参数:irq编号
void disable_irq(unsigned int irq)
// 申请中断
//参数1:中断号,参数2:中断处理函数,参数3:触发方式, 参数4:中断名(自定义)
//参数5:传递给中断处理函数的参数
//返回值:不为0表示失败。
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
//释放中断
//参数1 :中断号,参数2 :中断处理函数
void free_irq(unsigned int irq, void *dev_id)
gpio_to_irqによって取得された割り込み番号は、割り込み操作全体を実行します。入力パラメーターは、GPIOを割り込みピンとして使用できる場合は上記のGPIO番号です。これは、socに関連しています。詳細については、チップのマニュアルを参照してください。
request_irqの2番目のパラメーターは、次のように定義されている割り込み処理関数です。
irqreturn_t bc_irq_handler(int irq, void *dev_id)
最初のパラメータは割り込み番号です。異なる割り込み処理機能を同じにすることができます。特定の割り込みは、割り込み番号によって区別されます。request_irqの2番目のパラメーターと最後のパラメーター。
次のコードは、gpioa-20ピンを割り込みモードに設定し、立ち下がりエッジでトリガーします。処理関数はbc_irq_handlerであり、関数名と割り込み番号が割り込み処理関数に出力されます。
irqreturn_t bc_irq_handler(int irq, void *dev_id){
printk("%s %d\n",__func__,irq);
}
#define FPGA_B_INT 20 // gpioa-20 intrupt
int b_irq = gpio_to_irq(FPGA_B_INT);
ret = request_irq(g_privdat->b_irq,bc_irq_handler,IRQF_TRIGGER_FALLING,"B int",NULL);
if(ret){
printk("request_irq error: %d\n",b_irq);
return;
}
enable_irq(b_irq);
4つのgpioとdts
このセクションも概要を超えており、DTS(デバイスツリー)が含まれますが、主にいくつかのAPIの使用法がリストされており、詳細には説明しません。十分な髪がある場合は、DTSのメモを書いてください。
dtsノートとコードの対応について簡単に説明します。
次のdtsノートで説明されているように、すべてのdtsノートには互換性のあるフィールドがあります。
//
bt_bcm {
compatible = "bluetooth-platdata";
uart_rts_gpios = <&gpio_c 6 GPIO_ACTIVE_LOW>;
uart_cts_gpios = <&gpio_c 5 GPIO_ACTIVE_LOW>;
pinctrl-names = "default", "rts_gpio";
pinctrl-0 = <&serial1_flow>;
pinctrl-1 = <&bt_flow_gpio>;
reset-gpios = <&gpio_b 26 GPIO_ACTIVE_HIGH>;
wake-gpios = <&gpio_b 27 GPIO_ACTIVE_HIGH>;
status = "okay";
};
互換性はそれ自体で定義され、ドライバーと1対1で対応します
static struct of_device_id bt_platdata_of_match[] = {
{ .compatible = "bluetooth-platdata" },
{ }
};
上記の構造はドライバーで定義され、システムに登録されます。その後、システムはdtsノートとドライバーを照合し、ドライバーでプローブ関数を呼び出します。
static int rfkill_rk_probe(struct platform_device *pdev){
......
}
プローブ関数platform_device構造の入力パラメーターには、pdev-> dev-> of_nodeおよびdtsnotecontentがあります。noteからgpio
を取得する方法は2つあります。
//参数1 :对应的probe 函数输入参数下的pdev->dev->of_node
//参数2 :note 中GPIO 列表的字段名
//参数3 :要取的gpio在列表中的下标
//参数4:gpio默认电位值
//返回值:GPIO编号
int of_get_named_gpio_flags(struct device_node *np, const char *list_name,
int index, enum of_gpio_flags *flags)
//参数1:probe函数输入参数中的设备结构体pdev->dev
//参数2:note中的字段名
//参数3:要将gpio设置为何种状态
//返回值:gpio_desc 结构体,该结构体是对GPIO编号的包装。
struct gpio_desc *__must_check devm_gpiod_get_optional(struct device *dev,
const char *con_id,
enum gpiod_flags flags)
例:
最初のタイプ:
enum of_gpio_flags flags;
gpio = of_get_named_gpio_flags(node, "BT,reset_gpio", 0, &flags);
メモ「BT、reset_gpio」を説明する必要はありません。メモのフィールドは、上記のメモの例で確認できます。
0はリストの添え字を意味します。
ここで説明するために、GPIOを設定するためのフィールドは実際には配列です。ここでは、「BT、reset_gpio」配列
に設定されているGPIOBTは1つだけです。reset_gpio= <&gpio_b 26 GPIO_ACTIVE_HIGH>;
複数のgpioを埋めることができます:
BT,reset_gpio = <&gpio_b 26 GPIO_ACTIVE_HIGH //index =0
&gpio_c 10 GPIO_ACTIVE_HIGH //index =1
&gpio_a 11 GPIO_ACTIVE_HIGH>; //index=2
この場合、対応するgpio番号はインデックスによって取得できます。
flagsは、GPIO_ACTIVE_HIGHに対応する出力パラメーターです。
2番目のタイプ。
struct gpio_desc *bt_reset;
bt_reset = devm_gpiod_get_optional(&pdev->dev, "reset",
GPIOD_OUT_LOW);
この方法は、最初の方法とはかなり異なります。2つのメソッドは同じgpio1を取得し
ますが、2番目のパラメーター置換名は同じではありません。これは、devm_gpiod_get_optionalが名前を完成させ、reset-gpiosを検索するためです
。2はパラメーターインデックスを置換せず、デフォルトはインデックスです。 0であるため、複数のgpio設定を処理できません。33
番目のパラメーターが入力され、DTSで設定された状態を取得する代わりに、gpioをこの状態に設定します。つまり、DTSのピン状態を無視します。構成。
4戻り値は、GPIO番号ではなくgpio_desc構造です。この構造は対応するGPIOにバインドされ、最初の方法で取得されたGPIO番号は、ここで取得されたgpio_descと同じGPIOピンを指します。
gpio_desc構造は、別のapiのセットで設定されます。
int gpiod_request(struct gpio_desc *desc, const char *label)
void gpiod_set_value(struct gpio_desc *desc, int value)
int gpiod_get_value(const struct gpio_desc *desc)
int gpiod_direction_input(struct gpio_desc *desc)
int gpiod_direction_output(struct gpio_desc *desc, int value)
関数名に余分な「d」があり、入力GPIO番号がgpio_desc構造に変更されるという点で、2番目のセクションのメソッドとは異なります。
GPIOには多くのAPIがありますが、一般的に使用されるAPIをいくつか紹介します。