开发板:tiny4412SDK + S702 + 4GB Flash
要移植的内核版本:Linux-4.4.0 (支持device tree)
u-boot版本:友善之臂自带的 U-Boot 2010.12
busybox版本:busybox 1.25
目标:
学习设备树中GPIO控制器的使用,实现配置引脚为输出功能,写简单的字符设备驱动程序,实现点亮LED。
原理图:
tiny4412 核心板上有6颗LED,这里我们只控制其中4颗,它们分别接在GPM4_0、GPM4_1、GPM4_2、GPM4_3 引脚。想要点亮LED,首先得配置引脚为输出功能,输出低电平时LED点亮,高电平时,LED熄灭。重点在于设备树中GPIO控制器资源的使用。
设备树参考:
参考:Samsung GPIO and Pin Mux/Config controller
Example 1: A pin-controller node with pin groups.
pinctrl_0: pinctrl@11400000 {
compatible = "samsung,exynos4210-pinctrl";
reg = <0x11400000 0x1000>;
interrupts = <0 47 0>;
/* ... */
uart0_data: uart0-data {
samsung,pins = "gpa0-0", "gpa0-1";
samsung,pin-function = <2>;
samsung,pin-pud = <0>;
samsung,pin-drv = <0>;
};
Example 3: A uart client node that supports 'default' and 'flow-control' states.
uart@13800000 {
compatible = "samsung,exynos4210-uart";
reg = <0x13800000 0x100>;
interrupts = <0 52 0>;
pinctrl-names = "default", "flow-control;
pinctrl-0 = <&uart0_data>;
pinctrl-1 = <&uart0_data &uart0_fctl>;
};
"samsung,pins" property of the child node. The following pin configuration properties are supported.
- samsung,pin-val: Initial value of pin output buffer.
- samsung,pin-pud: Pull up/down configuration.
- samsung,pin-drv: Drive strength configuration.
- samsung,pin-pud-pdn: Pull up/down configuration in power down mode.
- samsung,pin-drv-pdn: Drive strength configuration in power down mode.
设备树:
&pinctrl@11000000 {
led_demo: led{
samsung,pins = "gpm4-0", "gpm4-1" ,"gpm4-2", "gpm4-3";
samsung,pin-function = <0x1>; //1为输出
samsung,pin-pud = <0x0>; //没有上拉
samsung,pin-drv = <0x0>; //驱动强度?
};
};
led_pin {
compatible = "tiny4412,led_demo";
pinctrl-names = "led_demo";
pinctrl-0 = <&led_demo>;
tiny4412,int_gpio1 = <&gpm4 0 GPIO_ACTIVE_HIGH>;
tiny4412,int_gpio2 = <&gpm4 1 GPIO_ACTIVE_HIGH>;
tiny4412,int_gpio3 = <&gpm4 2 GPIO_ACTIVE_HIGH>;
tiny4412,int_gpio4 = <&gpm4 3 GPIO_ACTIVE_HIGH>;
};
我们在 pinctrl 中增加了 led 节点,它代表了一种引脚功能,比如这里设置的 gpm4_0、gpm4_1、gpm4_2、gpm4_3,引脚功能为 0x01 输出,无上拉等等。在其它地方,我们可以引用它,来表示引脚支持的功能。
下面我们增加了 led_pin 节点,它有一个属性 pinctrl-names = “led_demo”,这是我们给引脚功能状态起的名字,如果支持多种功能,可以是字符串列表的形式。字符串的个数要和下面 pinctrl-n 的个数对应,pinctrl-0 引用了我们前面定义的那个将引脚设置为输出功能的属性。
在代码中,我们可以用过 pinctrl-names 来获得特定的引脚功能,并设置它。如果 pinctrl-names 为 “default”,那么这种功能状态将设置为默认的引脚状态,代码中无需处理。
代码片段:
static int led_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
dev_t devid;
struct pinctrl *pctrl;
struct pinctrl_state *pstate;
pctrl = devm_pinctrl_get(dev);
if(pctrl == NULL)
{
printk("devm_pinctrl_get error\n");
}
pstate = pinctrl_lookup_state(pctrl, "led_demo");
if(pstate == NULL)
{
printk("pinctrl_lookup_state error\n");
}
pinctrl_select_state(pctrl, pstate);//设置为输出模式
printk("enter %s\n",__func__);
led1 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio1", 0);;
led2 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio2", 0);;
led3 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio3", 0);;
led4 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio4", 0);;
if(led1 <= 0)
{
printk("%s error\n",__func__);
return -EINVAL;
}
else
{
printk("led1 %d\n",led1);
printk("led2 %d\n",led2);
printk("led3 %d\n",led3);
printk("led4 %d\n",led4);
devm_gpio_request_one(dev, led1, GPIOF_OUT_INIT_HIGH, "LED1");
devm_gpio_request_one(dev, led2, GPIOF_OUT_INIT_HIGH, "LED2");
devm_gpio_request_one(dev, led3, GPIOF_OUT_INIT_HIGH, "LED3");
devm_gpio_request_one(dev, led4, GPIOF_OUT_INIT_HIGH, "LED4");
}
完整代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LED_CNT 4
static int major;
static struct cdev led_cdev; //内核中用cdev描述一个字符设备
static struct class *cls;
static int led1,led2,led3,led4;
static ssize_t led_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
char buf;
int minor = iminor(file->f_inode);
printk("minor is %d\n",minor);
printk("%s\n",__func__);
if(count != 1){
printk("count != 1\n");
return 1;
}
if (copy_from_user(&buf, user_buf, count))
return -EFAULT;
printk("rcv %d\n",buf);
if(buf == 0x01)
{
switch(minor){
case 0:
gpio_set_value(led1, 0);
break;
case 1:
gpio_set_value(led2, 0);
break;
case 2:
gpio_set_value(led3, 0);
break;
case 3:
gpio_set_value(led4, 0);
break;
default:
printk("%s rcv minor error\n",__func__);
}
}
else if(buf == 0x0)
{
switch(minor){
case 0:
gpio_set_value(led1, 1);
break;
case 1:
gpio_set_value(led2, 1);
break;
case 2:
gpio_set_value(led3, 1);
break;
case 3:
gpio_set_value(led4, 1);
break;
default:
printk("%s rcv minor error\n",__func__);
}
}
}
static int led_open(struct inode *inode, struct file *file)
{
printk("led_open\n");
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
dev_t devid;
struct pinctrl *pctrl;
struct pinctrl_state *pstate;
pctrl = devm_pinctrl_get(dev);
if(pctrl == NULL)
{
printk("devm_pinctrl_get error\n");
}
pstate = pinctrl_lookup_state(pctrl, "led_demo");
if(pstate == NULL)
{
printk("pinctrl_lookup_state error\n");
}
pinctrl_select_state(pctrl, pstate);//设置为输出模式
printk("enter %s\n",__func__);
led1 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio1", 0);;
led2 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio2", 0);;
led3 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio3", 0);;
led4 = of_get_named_gpio(dev->of_node, "tiny4412,int_gpio4", 0);;
if(led1 <= 0)
{
printk("%s error\n",__func__);
return -EINVAL;
}
else
{
printk("led1 %d\n",led1);
printk("led2 %d\n",led2);
printk("led3 %d\n",led3);
printk("led4 %d\n",led4);
devm_gpio_request_one(dev, led1, GPIOF_OUT_INIT_HIGH, "LED1");
devm_gpio_request_one(dev, led2, GPIOF_OUT_INIT_HIGH, "LED2");
devm_gpio_request_one(dev, led3, GPIOF_OUT_INIT_HIGH, "LED3");
devm_gpio_request_one(dev, led4, GPIOF_OUT_INIT_HIGH, "LED4");
}
if(alloc_chrdev_region(&devid, 0, LED_CNT, "led") < 0)/* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
{
printk("%s ERROR\n",__func__);
goto error;
}
major = MAJOR(devid);
cdev_init(&led_cdev, &led_fops); //绑定文件操作函数
cdev_add(&led_cdev, devid, LED_CNT); //注册到内核
cls = class_create(THIS_MODULE, "led"); //创建led类,向类中添加设备,mdev会帮我们创建设备节点
device_create(cls, NULL, MKDEV(major, 0), NULL, "led0");
device_create(cls, NULL, MKDEV(major, 1), NULL, "led1");
device_create(cls, NULL, MKDEV(major, 2), NULL, "led2");
device_create(cls, NULL, MKDEV(major, 3), NULL, "led3");
error:
unregister_chrdev_region(MKDEV(major, 0), LED_CNT);
return 0;
}
static int led_remove(struct platform_device *pdev) {
printk("enter %s\n",__func__);
device_destroy(cls, MKDEV(major, 0));
device_destroy(cls, MKDEV(major, 1));
device_destroy(cls, MKDEV(major, 2));
device_destroy(cls, MKDEV(major, 3));
class_destroy(cls);
cdev_del(&led_cdev);
unregister_chrdev_region(MKDEV(major, 0), LED_CNT);
printk("%s enter.\n", __func__);
return 0;
}
static const struct of_device_id led_dt_ids[] = {
{ .compatible = "tiny4412,led_demo", },
{},
};
MODULE_DEVICE_TABLE(of, led_dt_ids);
static struct platform_driver led_driver = {
.driver = {
.name = "led_demo",
.of_match_table = of_match_ptr(led_dt_ids),
},
.probe = led_probe,
.remove = led_remove,
};
static int led_init(void){
int ret;
printk("enter %s\n",__func__);
ret = platform_driver_register(&led_driver);
if (ret)
printk(KERN_ERR "led demo: probe failed: %d\n", ret);
return ret;
}
static void led_exit(void)
{
printk("enter %s\n",__func__);
platform_driver_unregister(&led_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");