Android 10 LED 驱动开发

项目目标

基于展锐SL8541E平台设计的SU806-CN智能模组增加LED电量灯和工作指示灯显示


实现步骤

1. Pimmap配置GPIO属性

Pinmap.c文件是用于对一些具有GPIO属性的I/O pin提供功能,驱动等级,上下拉等方面的配置,是需要根据外部连接电路,实际功能用途来配置状态;这个不同于芯片At Reset与After Reset后pin的状态,At Reset与After Reset是芯片内部固定的状态,不能动态修改,而Pinmap是可以被软件定义寄存器修改的。另外,Pinmap.c还配置一些电源域的选择,UART口的分配等。实际上Pinmap就是一些相关配置寄存器汇总在一起的一种表现形式而已,可读性比较好。Pinmap从U-boot阶段开始起作用。
文件路径:…\bsp\bootloader\u-boot15\board\spreadtrum\sl8541e_su806_v2\pinmap-sl8541e.c

//GPIO配置
static pinmap_t pinmap[]={
...
//LED0
{REG_PIN_RFCTL16,                       BITS_PIN_AF(3)},//function选择
										//驱动强度、上下拉配置和输入输出状态等
{REG_MISC_PIN_RFCTL16,                  BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
//LED0
{REG_PIN_RFCTL16,                       BITS_PIN_AF(3)},
{REG_MISC_PIN_RFCTL16,                  BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
//LED2
{REG_PIN_IIS1CLK,                       BITS_PIN_AF(3)},
{REG_MISC_PIN_IIS1CLK,                  BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
//LED3
{REG_PIN_IIS1DO,                        BITS_PIN_AF(3)},
{REG_MISC_PIN_IIS1DO,                   BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
//BT LED
{REG_PIN_IIS1LRCK,                      BITS_PIN_AF(3)},
{REG_MISC_PIN_IIS1LRCK,                 BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
//GREEN LED
{REG_PIN_RFCTL10,                       BITS_PIN_AF(3)},
{REG_MISC_PIN_RFCTL10,                  BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
//RED LED
{REG_PIN_RFCTL11,                       BITS_PIN_AF(3)},
{REG_MISC_PIN_RFCTL11,                  BITS_PIN_DS(1)|BIT_PIN_NULL|BIT_PIN_NUL|BIT_PIN_SLP_AP|BIT_PIN_SLP_NUL|BIT_PIN_SLP_OE},
...

2. DTS (device tree source)

.dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts就include这个.dtsi。当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。
.dts(或者其include的.dtsi)基本元素为结点和属性:

/{                        //根节点
    node1{                //node1是节点名,是/的子节点
        key=value;        //node1的属性
        ...
        node2{            //node2是node1的子节点
            key=value;    //node2的属性
            ...
        }
    }                     //node1的描述到此为止
    node3{
        key=value;
        ...
    }
}

GPIO设备树文件路径:…\bsp\kernel\kernel4.14\arch\arm\boot\dts\su806-v2-evk\su806-overlay.dts
增加设备节点:

&soc{
...
gpio_led: ght,gpio-led {			//led节点名
		compatible = "ght,gpio-led";//compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序。
		led0-gpio = <&ap_gpio 121 GPIO_ACTIVE_HIGH>;
		led1-gpio = <&ap_gpio 122 GPIO_ACTIVE_HIGH>;
		led2-gpio = <&ap_gpio 132 GPIO_ACTIVE_HIGH>;
		led3-gpio = <&ap_gpio 131 GPIO_ACTIVE_HIGH>;
		led4-gpio = <&ap_gpio 138 GPIO_ACTIVE_HIGH>;
		led5-gpio = <&ap_gpio 139 GPIO_ACTIVE_HIGH>;
		led6-gpio = <&ap_gpio 143 GPIO_ACTIVE_HIGH>;		
	};
}

3. kernel代码增加led.c驱动文件

3.1 led初始化和提供上层调用控制接口

#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/kernel.h>
#include <linux/dmi.h>
#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include <linux/of.h>
#include <asm/unaligned.h>

#define GPIO_HIGH _IO('L', 0)
#define GPIO_LOW _IO('L', 1)

#define LED_ON	1
#define LED_OFF	0
#define SIMPIE_LED_MAX	7


//==============================  Upper interface value ==============================//
//驱动模块名称定义
#define MODULE_NAME			"gpio_led"			// Name of this driver module
#define MISC_NAME			"gpio_led_device"	// Name used to register as 'misc' device

//模块函数接口定义,java上层引用的接口,这里我们通过MM_DEV_MAGIC的值来区分其它系统接口,通过_IO()来左移8移位加上自己的编号作为接口number
//此处建议加上功注释方便提供给上层做APP接口使用
#define MM_DEV_MAGIC   	'N'  

//LED
#define WORK_LED_GREEN  		_IO(MM_DEV_MAGIC, 29)//电源工作绿灯
#define WORK_LED_RED  			_IO(MM_DEV_MAGIC, 30)//电源工作红灯
#define BT_LED  				_IO(MM_DEV_MAGIC, 133)//BT工作灯

static int major;
static struct class *cls;

//gpio描述符
struct gpio_desc *led_gpio[SIMPIE_LED_MAX];

//cat命令时,将会调用该函数
static ssize_t gpio_value_show(struct device *dev, struct device_attribute *attr, char *buf)	
{
	return sprintf(buf, "%d\n", gpiod_get_value(led_gpio[0]));
}

//echo命令时,将会调用该函数
static ssize_t gpio_value_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)	
{
	pr_err("[XXJ]%c\n", buf[0]);
	if ('0' == buf[0]) {
		gpiod_direction_output(led_gpio[0], 0);
		pr_err("[XXJ]: _%s_ :gpio off\n", __func__);
	} else if ('1' == buf[0]) {
		gpiod_direction_output(led_gpio[0], 1);
		pr_err("[XXJ]: _%s_ :gpio on\n", __func__);
	} else
		pr_err("I only support 0 or 1 to ctrl gpio on or off\n");
	pr_err("[XXJ]gpio_value_store\n");
	return len;
}

//定义一个名字为gpio_led的设备属性文件
static DEVICE_ATTR(gpio_led, 0664, gpio_value_show, gpio_value_store); 	

//提供给上层控制的接口
long gpio_led_ioctl( struct file *file, unsigned int cmd, unsigned long arg )
{
	switch(cmd)
	{
		case WORK_LED_GREEN:
			gpiod_direction_output(led_gpio[4], arg);
			break;
		case WORK_LED_RED:
			gpiod_direction_output(led_gpio[5], arg);
			break;
		case BT_LED:
			gpiod_direction_output(led_gpio[6], arg);
			break;
		default:
			pr_err("[XXJ] %s default: break\n",__func__);
			break;
	}
    return 0;
}

struct file_operations gpio_led_ops={
         .owner  = THIS_MODULE,
		 .unlocked_ioctl = gpio_led_ioctl,
};

//led灯初始化
static int simpie_led_init(struct platform_device *pdev)
{
	int ret = 0;
	int i;
	
	//申请gpio设备
	led_gpio[0] = devm_gpiod_get(&pdev->dev, "led0", GPIOD_OUT_LOW);
	led_gpio[1] = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW);
	led_gpio[2] = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW);
	led_gpio[3] = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW);
	led_gpio[4] = devm_gpiod_get(&pdev->dev, "work_led_green", GPIOD_OUT_LOW);
	led_gpio[5] = devm_gpiod_get(&pdev->dev, "work_led_red", GPIOD_OUT_LOW);
	led_gpio[6] = devm_gpiod_get(&pdev->dev, "bt_led", GPIOD_OUT_LOW);
	
	for(i = 0;i < SIMPIE_LED_MAX;i++){
		if(IS_ERR(led_gpio[i])){
			ret = PTR_ERR(led_gpio[i]);
			return ret;
		}
		//输出初始电平
		ret = gpiod_direction_output(led_gpio[i], LED_OFF);
	}
	
	device_create_file(&pdev->dev, &dev_attr_gpio_led);
	return ret;
}

//驱动入口
static int gpio_led_probe(struct platform_device *pdev)
{
	int ret = 0;
	pr_err("[XXJ]gpio_led_probe start...\n");

	//LED灯gpio初始化及输出配置
	ret = simpie_led_init(pdev);
	
	pr_err("[XXJ]gpio_led_probe end...\n");
	
	return 0;	
}

//绑定设备树
static struct of_device_id gpio_led_match_table[] = {
	{ .compatible = "ght,gpio-led"},
	{ }
};

static int gpio_led_remove(struct platform_device *pdev)
{
	pr_err("[XXJ]gpio_led_remove...\n");
	return 0;
}

static struct platform_driver gpio_led_driver = {
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = gpio_led_match_table,
	},
	.probe = gpio_led_probe,
	.remove = gpio_led_remove,
};

//gpio初始化入口
static int gpio_led_init(void)
{
	struct device *mydev;

	pr_err("[XXJ]gpio_led_init start...\n");
	platform_driver_register(&gpio_led_driver);
	
	major=register_chrdev(0,"gpiotest", &gpio_led_ops);
	
	//创建gpio_led_class设备
	cls=class_create(THIS_MODULE, "gpio_led_class");	
	
	//在gpio_led_class设备目录下创建一个gpio_led_device属性文件
	mydev = device_create(cls, 0, MKDEV(major,0),NULL,MISC_NAME);	
	if(sysfs_create_file(&(mydev->kobj), &dev_attr_gpio_led.attr)){  
            return -1;
	}
	
	//启动定时器
	charger_water_light_start(1);
	return 0;
}

static void gpio_led_exit(void)
{
	pr_err("[XXJ]gpio_led_exit...\n");
	platform_driver_unregister(&gpio_led_driver);

	device_destroy(cls, MKDEV(major,0));
    class_destroy(cls);
    unregister_chrdev(major, "gpiotest");
	
	//关闭定时器
	charger_water_light_start(0);
}

module_init(gpio_led_init);
module_exit(gpio_led_exit);

MODULE_AUTHOR("LXR");
MODULE_DESCRIPTION("Device_create Driver");
MODULE_LICENSE("GPL");

3.2 创建内核高精度定时器hrtimer并设置内核停止休眠

#include <linux/hrtimer.h>
#include <linux/jiffies.h>
#include <linux/suspend.h>
#include <linux/power_supply.h>

struct wakeup_source suspend_lock;
extern  void wakeup_source_init(struct wakeup_source *lock, const char *name);
extern  void __pm_stay_awake(struct wakeup_source *ws);

static struct hrtimer timer;
ktime_t kt;
#define _MS_TO_NS(x) (x * 1000 * 1000)

unsigned int last_status = 0;

//保持唤醒状态
static void keep_wekeup(void)
{
	wakeup_source_init(&suspend_lock,"my wakelock");
	__pm_stay_awake(&suspend_lock);
}

//####################  定时器循环处理 ####################//
static ktime_t auto_water_light_proc(void)
{
	int i;
	//翻转gpio电平
	if(last_status){
		for(i = 0;i < 4;i++)		
			gpiod_direction_output(led_gpio[i], LED_ON);
	}
	else{
		for(i = 0;i < 4;i++)		
			gpiod_direction_output(led_gpio[i], LED_OFF);
	}
	last_status = !last_status;
	//返回设置下一次延时的时间	
    return ktime_set(0,_MS_TO_NS(1000));
}

//####################  定时器回调函数,定时器时间达到后进入这个函数 #####################//
static enum hrtimer_restart  hrtimer_hander(struct hrtimer *t)
{
 	printk("[dc_pin] %s Callback ...\r\n",__func__ );
	
    /* 设置下次过期时间 */
   	// kt = ktime_set(3,0); 
   	
   	//此处为自定义的循环函数,返回下一次循环的时间。
   	kt = auto_water_light_proc();
   	
	//设置循环时间
    hrtimer_forward_now(t, kt);
 
    /* 该参数将重新启动定时器 */    
    return HRTIMER_RESTART;  
}

//####################  定时器启动开关  ####################//
static void charger_water_light_start(int ON_OFF)
{
   	printk("[dc_pin] %s ON_OFF = %d \n",__func__,ON_OFF);

	if(ON_OFF)
	{
		printk("[dc_pin] hrtimer_start... \r\n");
		
		//首次延时100ms		
	    kt = ktime_set(0,_MS_TO_NS(100));
	 
	    /* hrtimer初始化 */
	    hrtimer_init(&timer,CLOCK_MONOTONIC,HRTIMER_MODE_ABS_PINNED  );

		/* 设置回调函数 */
		timer.function = hrtimer_hander;
	
	    /* hrtimer启动 */
	    hrtimer_start(&timer,kt,HRTIMER_MODE_REL_PINNED );
		
		/* 内核禁止休眠 */
		keep_wekeup();
	}
	else
	{
	    //可以增加其它关闭的操作
		/* hrtimer注销 */
		hrtimer_cancel(&timer);
		printk("bye hrtimer\r\n");
	}
}

4. 编译

驱动路径下makefile结尾增加 obj-y += led.o把文件编入内核代码

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值