Linux的GPIO的控制有多种的方式,pinctrl或者gpiolib等方式,以上的方式有很多文档,可以自行搜索学习,此篇重点说明寄存器的相关控制方式,以EC20为例进行说明(该说明需要结合datasheet,针对datasheet中的相关说明进行提取在下面进行说明)
一、寄存器地址
GPIO配置寄存器地址的计算方式:gpio_n:0x01000000+0x1000*n
例如,GPIO25配置寄存器的计算方式为,0x01000000+0x1000*25 = 0x1019000
GPIO输入输出寄存器地址的计算方式:0x01000000+0x1000*n+0x04
例如,GPIO25输入输出寄存器的计算方式为,0x1019000+0x04 = 0x1019004
二、devmem工具的使用
Linux下可使用devmem工具来读取具体的内存地址,以读取GPIO25的内存地址为例进行说明
三、寄存器地址解析
十六进制数 | 二进制数 | 寄存器名 |
0x00000283 | 001 010 0000 11 | 配置寄存器 |
0x00000003 | 0011 | 输入输出寄存器 |
GPIO配置寄存器 | ||
Bit位 | 配置功能 | 具体含义 |
Bit1-0 | 上下拉 | 0表示浮空 1表示下拉 2表示保持, 3表示上拉 |
Bit5-2 | 功能位 | 0表示GPIO功能 非0表示复用功能 |
Bit8-6 | 驱动能力 | <0>:2mA <1>:4mA <2>:6mA <3>:8mA <4>:10mA <5>:12mA <6>:14mA <7>:16mA |
Bit9 | 方向位 | 1表示输出,0表述输入 |
输入输出寄存器 | |
Bit位 | 具体含义 |
Bit1 | 输出方向对应的值,1表示高电平,0表示低电平 |
Bit0 | 输入方面对应的值,1表示高电平,0表示低电平 |
根据不同寄存器的二进制数值可见GPIO为输出模式6mA高电平
通过查看/sys/kernel/debug/pinctrl/1000000.pinctrl/pinmux-pins下GPIO_25的复用信息,为GPIO功能
查看sys/kernel/debug/gpio的相关信息,为输出模式输出能力6mA 上拉
四、驱动中的操作
根据上述的寄存器地址结合Linux的特性,使用设备树编写一个简单的驱动程序,使用设备树初始化配置GPIO
驱动代码如下:
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/bug.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <asm/io.h>
static volatile uint32_t *gpio_cfg_vreg;
static volatile uint32_t *gpio_dir_lv_reg;
/*
* DTS Config ways
*/
#define REGISTER_ADDR (0)
#define GPIO_NUM (1)
/*
* Mdm9x07 gpio register address translation
*/
#define DEV_MEM_VALUE(n) (0x01000000 + 0x1000 * (n))
/*
* Mdm9x07 platform gpio pull registers
*/
#define GPIO_NO_PULL 0x0
#define GPIO_PULL_DOWN 0x1
#define GPIO_KEEPER 0x2
#define GPIO_PULL_UP 0x3
/*
* Mdm9x07 platform gpio strength registers
*/
#define GPIO_DRV_2_MA 0x0
#define GPIO_DRV_4_MA 0x40
#define GPIO_DRV_6_MA 0x80
#define GPIO_DRV_8_MA 0xc0
#define GPIO_DRV_10_MA 0x100
#define GPIO_DRV_12_MA 0x140
#define GPIO_DRV_14_MA 0x180
#define GPIO_DRV_16_MA 0x1c0
enum mdm_gpio_drv_strength {
DRV_2_MA = 0, /* drive strength to 2mA */
DRV_4_MA, /* drive strength to 4mA */
DRV_6_MA, /* drive strength to 6mA */
DRV_8_MA, /* drive strength to 8mA */
DRV_10_MA, /* drive strength to 10mA */
DRV_12_MA, /* drive strength to 12mA */
DRV_14_MA, /* drive strength to 14mA */
DRV_16_MA, /* drive strength to 16mA */
};
enum mdm_gpio_pull {
NO_PULL = 0, /* The pad configured NO_PULL */
PULL_DOWN, /* The pad configured PULL_DOWN */
KEEPER, /* The pad configured KEEPER */
PULL_UP, /* The pad configured PULL_UP */
};
enum mdm_gpio_direction {
GPIO_IN = 0, /* Allows you to read the Input value of the GPIO */
GPIO_OUT, /* Controls the value of the GPIO Output */
};
enum mdm_gpio_dir_lv {
LOW_LEVEL = 0, /* High level in terms of input and output */
HIGH_LEVEL, /* Low level in terms of input and output */
};
/**
* struct mdm_gpio_hwdata: gpio information
* @addr: pipe description.
* @drv_strength: Controls the GPIO pad drive strength.
* @gpio_pull: The pad can be configured to employ an internal weak pull up, pulldown, or keeper function.
* @gpio_direction: Controls the Out and In on the GPIO
*/
struct mdm_gpio_hwdata {
uint32_t gpio_num;
uint32_t addr;
enum mdm_gpio_drv_strength drv_strength;
enum mdm_gpio_pull gpio_pull;
enum mdm_gpio_direction gpio_direction;
enum mdm_gpio_dir_lv gpio_level;
};
static struct mdm_gpio_hwdata *mdm_gpio_dt_to_hwdata(struct platform_device *pdev)
{
int ret;
struct mdm_gpio_hwdata *gpio_hwdata;
struct device_node *node = pdev->dev.of_node;
gpio_hwdata = devm_kzalloc(&pdev->dev, sizeof(*gpio_hwdata), GFP_KERNEL);
if (!gpio_hwdata) {
return NULL;
}
#if GPIO_NUM
/* read gpio number */
ret = of_property_read_u32(node, "qcom,gpio-num",
&gpio_hwdata->gpio_num);
if (ret) {
dev_err(&pdev->dev, "Unable to get gpio number.\n");
return NULL;
}
gpio_hwdata->addr = DEV_MEM_VALUE(gpio_hwdata->gpio_num);
dev_info(&pdev->dev, "gpio num = %d, gpio reg addr = %x\n", gpio_hwdata->gpio_num, gpio_hwdata->addr);
#endif
#if REGISTER_ADDR
/* read addr */
ret = of_property_read_u32(node, "qcom,gpio-addr",
&gpio_hwdata->addr);
if (ret) {
dev_err(&pdev->dev, "Unable to retrieve register base.\n");
return NULL;
}
#endif
/* read drv_strength */
ret = of_property_read_u32(node, "qcom,drv_strength", &gpio_hwdata->drv_strength);
if (ret)
gpio_hwdata->drv_strength = DRV_2_MA;
dev_info(&pdev->dev, "drv_strength = %d\n", gpio_hwdata->drv_strength);
/* read gpio_pull */
ret = of_property_read_u32(node, "qcom,gpio_pull", &gpio_hwdata->gpio_pull);
if (ret)
gpio_hwdata->gpio_pull = PULL_DOWN;
dev_info(&pdev->dev, "gpio_pull = %d\n", gpio_hwdata->gpio_pull);
/* read gpio direction */
ret = of_property_read_u32(node, "qcom,gpio_dir", &gpio_hwdata->gpio_direction);
if (ret)
gpio_hwdata->gpio_direction = GPIO_IN;
dev_info(&pdev->dev, "gpio_dir = %d\n", gpio_hwdata->gpio_direction);
/* read gpio level */
ret = of_property_read_u32(node, "qcom,gpio_level", &gpio_hwdata->gpio_level);
if (ret)
gpio_hwdata->gpio_level = LOW_LEVEL;
dev_info(&pdev->dev, "gpio_level = %d\n", gpio_hwdata->gpio_level);
return gpio_hwdata;
}
static void mdm_gpio_set_cfg(struct mdm_gpio_hwdata *gpio_hwdata)
{
uint32_t reg = 0;
switch (gpio_hwdata->drv_strength) {
case DRV_2_MA:
reg = reg | GPIO_DRV_2_MA;
break;
case DRV_4_MA:
reg = reg | GPIO_DRV_4_MA;
break;
case DRV_6_MA:
reg = reg | GPIO_DRV_6_MA;
break;
case DRV_8_MA:
reg = reg | GPIO_DRV_8_MA;
break;
case DRV_10_MA:
reg = reg | GPIO_DRV_10_MA;
break;
case DRV_12_MA:
reg = reg | GPIO_DRV_12_MA;
break;
case DRV_14_MA:
reg = reg | GPIO_DRV_14_MA;
break;
case DRV_16_MA:
reg = reg | GPIO_DRV_16_MA;
break;
default:
break;
}
switch (gpio_hwdata->gpio_pull) {
case NO_PULL:
reg = reg | GPIO_NO_PULL;
break;
case PULL_DOWN:
reg = reg | GPIO_PULL_DOWN;
break;
case KEEPER:
reg = reg | GPIO_KEEPER;
break;
case PULL_UP:
reg = reg | GPIO_PULL_UP;
break;
default:
break;
}
writel(reg, gpio_cfg_vreg);
}
static void mdm_gpio_set_direction(struct mdm_gpio_hwdata *gpio_hwdata)
{
uint32_t reg = readl(gpio_cfg_vreg);
if (gpio_hwdata->gpio_direction)
reg |= (1 << 9); // bit9 1:out
else
reg &= ~(1 << 9); // bit9 0:in
writel(reg, gpio_cfg_vreg);
}
static void mdm_gpio_set_dir_level(struct mdm_gpio_hwdata *gpio_hwdata)
{
uint32_t reg = readl(gpio_dir_lv_reg);
if (gpio_hwdata->gpio_direction == 0 && gpio_hwdata->gpio_level == 0)
reg &= ~(1U << 0); // bit0 = 0 (in low level)
else if (gpio_hwdata->gpio_direction == 0 && gpio_hwdata->gpio_level == 1)
reg |= (1U << 0); // bit0 = 1 (in high level)
else if (gpio_hwdata->gpio_direction == 1 && gpio_hwdata->gpio_level == 0)
reg &= (1U << 1); // bit1 = 0 (out low level)
else if (gpio_hwdata->gpio_direction == 1 && gpio_hwdata->gpio_level == 1)
reg |= (1U << 1); // bit1 = 1 (out high level)
writel(reg, gpio_dir_lv_reg);
}
static int mdm_gpio_probe(struct platform_device *pdev)
{
int ret = 0;
struct mdm_gpio_hwdata *gpio_hwdata;
/* gpio dt to hwdata */
gpio_hwdata = mdm_gpio_dt_to_hwdata(pdev);
if (!gpio_hwdata){
dev_err(&pdev->dev, "Failed dt to hwdata\n");
ret = -EINVAL;
goto err_exit;
}
/* gpio_cfg_vreg ioremap */
gpio_cfg_vreg = (uint32_t *)devm_ioremap(&pdev->dev, gpio_hwdata->addr, 4);
if(!gpio_cfg_vreg) {
dev_err(&pdev->dev, "Failed to map gpio_cfg_vreg registers\n");
ret = -ENOMEM;
goto err_exit;
}
/* gpio_dir_lv_reg ioremap */
gpio_dir_lv_reg = (uint32_t *)devm_ioremap(&pdev->dev, gpio_hwdata->addr + 4, 4);
if(!gpio_dir_lv_reg) {
dev_err(&pdev->dev, "Failed to map gpio_dir_lv_reg registers\n");
ret = -ENOMEM;
goto err_exit;
}
dev_info(&pdev->dev, "gpio_dir_lv_reg = %x \n", gpio_hwdata->addr + 4);
mdm_gpio_set_cfg(gpio_hwdata);
mdm_gpio_set_direction(gpio_hwdata);
mdm_gpio_set_dir_level(gpio_hwdata);
dev_info(&pdev->dev, "Registered msm_gpio.\n");
return ret;
err_exit:
return ret;
}
static const struct of_device_id mdm_gpio_dt_ids[] = {
{ .compatible = "qcom,mdm9x07-gpio",
},
{}
};
static struct platform_driver mdm_gpio_driver = {
.probe = mdm_gpio_probe,
.driver = {
.name = "msm_gpio",
.owner = THIS_MODULE,
.of_match_table = mdm_gpio_dt_ids,
},
};
static int __init gpio_mdm_init(void)
{
return platform_driver_register(&mdm_gpio_driver);
}
subsys_initcall(gpio_mdm_init);
static void __exit gpio_mdm_cleanup(void)
{
platform_driver_unregister(&mdm_gpio_driver);
}
module_exit(gpio_mdm_cleanup);
MODULE_DESCRIPTION("MSM GPIO DRIVER");
MODULE_LICENSE("GPL v2");
设备树配置如下:
gpio_25: gpio@0x01019000 {
compatible = "qcom,mdm9x07-gpio";
#if 1
/*
gpio number
*/
qcom,gpio-num = <25>;
#endif
#if REGISTER_ADDR
/*
gpio register address
*/
qcom,gpio-addr = <0x01019000>;
#endif
/*
<0>:2mA <1>:4mA <2>:6mA <3>:8mA <4>:10mA <5>:12mA <6>:14mA <7>:16mA
*/
qcom,drv_strength = <2>;
/*
<0>:no pull <1>:pull down <2>:keeper <3>:pull up
*/
qcom,gpio_pull = <3>;
/*
<0>: in <1>:out
*/
qcom,gpio_dir = <1>;
/*
<0>:low_level <1>:hight_level
*/
qcom,gpio_level = <1>;
status = "okay";
};
查看dmesg的打印信息,可见驱动正常匹配
经测试,改驱动代码在SDX65同样适用