目录
项目目标
基于展锐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把文件编入内核代码