在很多SOC内部都有pin的控制器,通过配置pin控制器,可以将引脚配置为特定的功能特性,在软件方面,linux内核提供pinctrl子系统,目的为了统一soc厂商的pin脚管理。
以NXP i.MX7D为例,每个IO引脚有多达8种的复用功能,具体用哪一种功能,通过IOMUXC来配置引脚的具体特性。
在Linux内核DT文件夹中arch/arm/boot/dts/imx7d-pinfunc.h的文件中,定义了所有引脚的复用配置。以下面的为例
#define MX7D_PAD_I2C1_SDA_GPIO4_IO9 0x014c 0x03BC 0x0000 0x5 0x0
0x014c是IOMUXC_SW_MUX_CTL_PAD_I2C1_SDA多路复用寄存器的偏移量
0x03BC是IOMUXC_SW_PAD_CTL_PAD_I2C1_SDA控制寄存器的偏移量
0x5 是IOMUXC_SW_MUX_CTL_PAD_I2C1_SDA多路复用寄存器ALT5的模式,也就当做普通的GPIO来用
引脚的具体配置,从linux3.x内核后都是在设备树中进行具体的复用和配置。
以ledclassRGB节点为例,此节点对应的IO配置为pinctrl_gpio_leds、pinctrl_gpio_led,其中pinctrl_gpio_leds主要对MX7D_PAD_SAI2_TX_BCLK__GPIO6_IO20、MX7D_PAD_SAI2_RX_DATA__GPIO6_IO21引脚进行复用,最后的0x11是配置前面控制寄存器的值
0x11
mux_reg:复用配置寄存器偏移地址
conf_reg:引脚配置寄存器偏移地址
input_reg:输入配置寄存器偏移地址
mux_mode:复用配置寄存器值
input_val:输入配置寄存器值
ledclassRGB {
compatible = "arrow,RGBclassleds";
reg = <0x30200000 0x60000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds &pinctrl_gpio_led>;
red {
label = "red";
};
green {
label = "green";
};
blue {
label = "blue";
linux,default-trigger = "heartbeat";
};
};
pinctrl_gpio_leds: pinctrl_gpio_leds_grp {
fsl,pins = <
MX7D_PAD_SAI2_TX_BCLK__GPIO6_IO20 0x11
MX7D_PAD_SAI2_RX_DATA__GPIO6_IO21 0x11
>;
};
对外围设备的控制是通过写入及读取其寄存器来实现的,通过内存地址空间(MMIO)或地址空间(PIO)的连续地址来访问这些寄存器的
1:MMIO
主存和IO设备使用相同的总线地址
使用常规指令访问IO设备
linux支持的不同体系结构中使用广泛的IO方法
2:PIO
主存和IO设备使用不同的地址空间
使用特殊的CPU指令来访问IO设备
x86上的示例:IN和OUT指令
i.MX7D使用的是MMIO,但是驱动程序中无法直接访问物理地址,需要MMU进行映射。
可以通过下面的函数
1:使用ioremap、iounmap
2: 使用devm_ioremap、devm_iounmap
3:ioread8\ioread16 iowrite8\iowrite16
#include
#include
#include
#include
#include
#include
#define GPDAT1_offset 0x00
#define GPDIR1_offset 0x04
#define GPDAT6_offset 0x50000
#define GPDIR6_offset 0x50004
#define GPIO1_DIR_MASK 1 << 2
#define GPIO1_DATA_MASK 1 << 2
#define GPIO6_DIR_MASK (1 << 20 | 1 << 21)
#define GPIO6_DATA_MASK (1 << 20 | 1 << 21)
#define LED_RED_MASK 1 << 2
#define LED_GREEN_MASK 1 << 20
#define LED_BLUE_MASK 1 << 21
struct led_dev
{
u32 led_mask; /* different mask if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};
static void led_control(struct led_classdev *led_cdev, enum led_brightness b)
{
u32 read, write;
struct led_dev *led = container_of(led_cdev, struct led_dev, cdev);
if (b != LED_OFF) { /* LED ON */
if (led->led_mask == LED_RED_MASK) {
read = ioread32(led->base + GPDAT1_offset);
write = read | led->led_mask;
iowrite32(write, led->base + GPDAT1_offset);
}
if ((led->led_mask == LED_GREEN_MASK) || (led->led_mask == LED_BLUE_MASK)) {
read = ioread32(led->base + GPDAT6_offset);
write = read | led->led_mask;
iowrite32(write, led->base + GPDAT6_offset);
}
}
else {
if (led->led_mask == LED_RED_MASK) {
read = ioread32(led->base + GPDAT1_offset);
write = read & ~(led->led_mask);
iowrite32(write, led->base + GPDAT1_offset);
}
if ((led->led_mask == LED_GREEN_MASK) || (led->led_mask == LED_BLUE_MASK)) {
read = ioread32(led->base + GPDAT6_offset);
write = read & ~(led->led_mask);
iowrite32(write, led->base + GPDAT6_offset);
}
}
}
static int __init ledclass_probe(struct platform_device *pdev)
{
u32 GPDIR1_read, GPDIR1_write;
u32 GPDIR6_read, GPDIR6_write;
u32 GPDAT1_read, GPDAT1_write;
u32 GPDAT6_read, GPDAT6_write;
void __iomem *g_ioremap_addr;
struct device_node *child;
struct resource *r;
struct device *dev = &pdev->dev;
int count, ret;
dev_info(dev, "platform_probe enter\n");
/* get our first memory resource from device tree */
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
dev_err(dev, "IORESOURCE_MEM, 0 does not exist\n");
return -EINVAL;
}
dev_info(dev, "r->start = 0x%08lx\n", (long unsigned int)r->start);
dev_info(dev, "r->end = 0x%08lx\n", (long unsigned int)r->end);
/* ioremap our memory region */
g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));
if (!g_ioremap_addr) {
dev_err(dev, "ioremap failed \n");
return -ENOMEM;
}
count = of_get_child_count(dev->of_node);
if (!count)
return -EINVAL;
dev_info(dev, "there are %d nodes\n", count);
/* Set GPIO1_IO_2 direction bit to output */
GPDIR1_read = ioread32(g_ioremap_addr + GPDIR1_offset);
GPDIR1_write = GPDIR1_read | (GPIO1_DIR_MASK);
iowrite32(GPDIR1_write, g_ioremap_addr + GPDIR1_offset);
GPDIR6_read = ioread32(g_ioremap_addr + GPDIR6_offset);
GPDIR6_write = GPDIR6_read | (GPIO6_DIR_MASK);
iowrite32(GPDIR6_write, g_ioremap_addr + GPDIR6_offset);
/* set all leds to 0 output */
GPDAT1_read = ioread32(g_ioremap_addr + GPDAT1_offset);
GPDAT1_write = GPDAT1_read & ~(GPIO1_DATA_MASK);
iowrite32(GPDAT1_write, g_ioremap_addr + GPDAT1_offset);
GPDAT6_read = ioread32(g_ioremap_addr + GPDAT6_offset);
GPDAT6_write = GPDAT6_read & ~(GPIO6_DATA_MASK);
iowrite32(GPDAT6_write, g_ioremap_addr + GPDAT6_offset);
for_each_child_of_node(dev->of_node, child){
struct led_dev *led_device;
struct led_classdev *cdev;
led_device = devm_kzalloc(dev, sizeof(*led_device), GFP_KERNEL);
if (!led_device)
return -ENOMEM;
cdev = &led_device->cdev;
led_device->base = g_ioremap_addr;
of_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"red") == 0) {
led_device->led_mask = LED_RED_MASK;
led_device->cdev.default_trigger = "heartbeat";
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->led_mask = LED_GREEN_MASK;
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->led_mask = LED_BLUE_MASK;
}
else {
dev_info(dev, "Bad device tree value\n");
return -EINVAL;
}
/* Disable timer trigger until led is on */
led_device->cdev.brightness = LED_OFF;
led_device->cdev.brightness_set = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret) {
dev_err(dev, "failed to register the led %s\n", cdev->name);
of_node_put(child);
return ret;
}
}
dev_info(dev, "leds_probe exit\n");
return 0;
}
static int __exit ledclass_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "leds_remove enter\n");
dev_info(&pdev->dev, "leds_remove exit\n");
return 0;
}
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,RGBclassleds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static struct platform_driver led_platform_driver = {
.probe = ledclass_probe,
.remove = ledclass_remove,
.driver = {
.name = "RGBclassleds",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
static int ledRGBclass_init(void)
{
int ret_val;
pr_info("demo_init enter\n");
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
pr_info("demo_init exit\n");
return 0;
}
static void ledRGBclass_exit(void)
{
pr_info("led driver enter\n");
platform_driver_unregister(&led_platform_driver);
pr_info("led driver exit\n");
}
module_init(ledRGBclass_init);
module_exit(ledRGBclass_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal ");
MODULE_DESCRIPTION("This is a driver that turns on/off RGB leds \
using the LED subsystem");