设备树
breathe_led: breathe-led {
compatible = "breathe-led";
led-gpios = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>; // 使用GPIO0_A0
pwm-channel = <0>; // 可选,如果使用PWM方式
max-brightness = <255>;
breathe-interval = <10>; // 更小的间隔让呼吸更平滑
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
};
代码文件
新建:/home/alientek/sdk/rk3568_linux_sdk/kernel/drivers/leds/leds-breathe-rk3568.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/of.h>
#define DRIVER_NAME "breathe-led"
#define BREATHE_INTERVAL_MS 10
// 呼吸曲线查找表(模拟正弦波,0-255范围)
static const u8 breathe_curve[] = {
0, 0, 0, 0, 0, 1, 1, 2,
2, 3, 4, 5, 6, 7, 9, 10,
12, 14, 16, 18, 20, 23, 25, 28,
31, 34, 37, 41, 44, 48, 52, 56,
60, 65, 69, 74, 79, 84, 89, 94,
100, 105, 111, 117, 123, 129, 135, 141,
148, 154, 161, 167, 174, 181, 188, 195,
202, 209, 216, 223, 230, 237, 244, 251,
255, 251, 244, 237, 230, 223, 216, 209,
202, 195, 188, 181, 174, 167, 161, 154,
148, 141, 135, 129, 123, 117, 111, 105,
100, 94, 89, 84, 79, 74, 69, 65,
60, 56, 52, 48, 44, 41, 37, 34,
31, 28, 25, 23, 20, 18, 16, 14,
12, 10, 9, 7, 6, 5, 4, 3,
2, 2, 1, 1, 0, 0, 0, 0
};
#define BREATHE_CURVE_SIZE ARRAY_SIZE(breathe_curve)
struct breathe_led {
struct device *dev;
struct gpio_desc *gpiod;
struct timer_list timer;
struct work_struct work;
int brightness;
int direction;
int max_brightness;
int breathe_interval;
int pwm_period; //pwm周期
int pwm_duty; //当前占空比
int pwm_counter; //pwm计数器
int curve_index; //呼吸曲线索引
bool is_active;
};
static void breathe_led_pwm_control(struct breathe_led *led){
if(!led->gpiod)
return;
led->pwm_counter++;
if(led->pwm_counter >= led->pwm_period){
led->pwm_counter = 0;
}
if(led->pwm_counter < led->pwm_duty){
gpiod_set_value(led->gpiod,1);
}else{
gpiod_set_value(led->gpiod,0);
}
}
static void update_breathe_brightness(struct breathe_led *led){
//更新曲线索引
led->curve_index++;
if(led->curve_index >= BREATHE_CURVE_SIZE){
led->curve_index = 0;
}
led->brightness = breathe_curve[led->curve_index];
led->pwm_duty = (led->brightness * led->pwm_period) / 255;
}
static void breathe_led_work(struct work_struct *work){
struct breathe_led *led = container_of(work,struct breathe_led,work);
//使用查找表版本的呼吸曲线
update_breathe_brightness(led);
//应用pwm控制
breathe_led_pwm_control(led);
if(led->is_active){
mod_timer(&led->timer,jiffies + msecs_to_jiffies(led->breathe_interval));
}
}
static void breathe_led_timer(struct timer_list *t){
struct breathe_led *led = from_timer(led,t,timer);
schedule_work(&led->work);
}
static void breathe_led_start(struct breathe_led *led){
if(led->is_active)
return;
led->is_active = true;
led->brightness = 0;
led->pwm_counter = 0;
led->pwm_duty = 0;
led->curve_index = 0;
led->direction = 1;
//启动定时器
mod_timer(&led->timer,jiffies + msecs_to_jiffies(led->breathe_interval));
dev_info(led->dev,"Breathe LED started, period: %d, interval: %dms\\n",led->pwm_period,led->breathe_interval);
}
static void breathe_led_stop(struct breathe_led *led){
if(!led->is_active)
return;
led->is_active = false;
del_timer_sync(&led->timer);
cancel_work_sync(&led->work);
//关闭LED
gpiod_set_value(led->gpiod,0);
dev_info(led->dev,"Breathe LED stopped\\n");
}
static ssize_t brightness_show(struct device *dev,struct device_attribute *attr,char *buf){
struct breathe_led *led = dev_get_drvdata(dev);
return sprintf(buf,"%d\\n",led->brightness);
}
static ssize_t brightness_store(struct device *dev,struct device_attribute *attr,const char *buf,size_t count){
struct breathe_led *led = dev_get_drvdata(dev);
int value;
if(kstrtoint(buf,0,&value)){
return -EINVAL;
}
if(value >= 0 && value <= led->max_brightness){
led->brightness = value;
led->pwm_duty = (value * led->pwm_period) / led->max_brightness;
breathe_led_pwm_control(led);
}
return count;
}
static DEVICE_ATTR(brightness,0644,brightness_show,brightness_store);
static ssize_t breathe_show(struct device *dev,struct device_attribute *attr,char *buf){
struct breathe_led *led = dev_get_drvdata(dev);
return sprintf(buf,"%d\\n",led->is_active ? 1 : 0);
}
static ssize_t breathe_store(struct device *dev,struct device_attribute *attr,const char *buf,size_t count){
struct breathe_led *led = dev_get_drvdata(dev);
int value;
if(kstrtoint(buf,0,&value))
return -EINVAL;
if(value){
breathe_led_start(led);
}else{
breathe_led_stop(led);
}
return count;
}
static DEVICE_ATTR(breathe,0644,breathe_show,breathe_store);
static struct attribute *breathe_led_attrs[] = {
&dev_attr_breathe.attr,
&dev_attr_brightness.attr,//亮度控制
NULL,
};
static struct attribute_group breathe_led_attr_group = {
.attrs = breathe_led_attrs
};
static int breathe_led_probe(struct platform_device *pdev){
struct device *dev = &pdev->dev;
struct breathe_led *led;
int ret;
led = devm_kzalloc(dev,sizeof(*led),GFP_KERNEL);
if(!led)
return -ENOMEM;
led->dev = dev;
//获取GPIO
led->gpiod = devm_gpiod_get(dev,"led",GPIOD_OUT_LOW);
if(IS_ERR(led->gpiod)){
dev_err(dev,"Failed to get GPIO\\n");
return PTR_ERR(led->gpiod);
}
//从设备树获取参数
device_property_read_u32(dev,"max-brightness",&led->max_brightness);
if(!led->max_brightness)
led->max_brightness = 255;
device_property_read_u32(dev,"breathe-interval",&led->breathe_interval);
if(!led->breathe_interval)
led->breathe_interval = BREATHE_INTERVAL_MS;
//设置pwm参数
led->pwm_period = 50;
led->pwm_counter = 0;
led->pwm_duty = 0;
led->curve_index = 0;
//初始化定时器与工作队列
timer_setup(&led->timer,breathe_led_timer,0);
INIT_WORK(&led->work,breathe_led_work);
platform_set_drvdata(pdev,led);
ret = sysfs_create_group(&dev->kobj,&breathe_led_attr_group);
if(ret){
dev_err(dev,"Failed to create sysfs group\\n");
return ret;
}
//启动呼吸灯效果
breathe_led_start(led);
dev_err(dev,"Breathe LED driver probed successfully\\n");
dev_info(dev,"PWM period: %d,Max brightness: %d,Interval: %dms\\n",led->pwm_period,led->max_brightness,led->breathe_interval);
return 0;
}
static int breathe_led_remove(struct platform_device *pdev){
struct breathe_led *led = platform_get_drvdata(pdev);
breathe_led_stop(led);
sysfs_remove_group(&pdev->dev.kobj,&breathe_led_attr_group);
dev_info(&pdev->dev,"Breathe LED driver removed\\n");
return 0;
}
static const struct of_device_id breathe_led_of_match[] = {
{ .compatible = "breathe-led" },
{ }
};
MODULE_DEVICE_TABLE(of,breathe_led_of_match);
static struct platform_driver breathe_led_driver = {
.probe = breathe_led_probe,
.remove = breathe_led_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = breathe_led_of_match
}
};
module_platform_driver(breathe_led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Liangrong.Ren");
MODULE_DESCRIPTION("RK3568 Breathe LED Driver");
MODULE_VERSION("1.0");
Kconfig配置
修改/home/alientek/sdk/rk3568_linux_sdk/kernel/drivers/leds/Kconfig,加入下面内容
config LEDS_BREATHE_RK3568
tristate "RK3568 Breathe LED support"
depends on (ARCH_ROCKCHIP && LEDS_CLASS) || COMPILE_TEST
help
This option enables support for breathe LED on RK3568 SoCs.
It creates a breathing effect using GPIO and timer.
Makefile配置
修改/home/alientek/sdk/rk3568_linux_sdk/kernel/drivers/leds/Makefile,加入下面内容
obj-$(CONFIG_LEDS_BREATHE_RK3568) += leds-breathe-rk3568.o
编译内核
执行编译内核命令,进入内核kernel的目录
1. 进入sdk的内核目录
cd /home/alientek/sdk/rk3568_linux_sdk/kernel
2. 执行下面命令
make ARCH=arm64 menuconfig
3. 进入页面按/进入搜索CONFIG_LEDS_BREATHE_RK3568:
Symbol: LEDS_BREATHE_RK3568 [=y]
│ Type : tristate
│ Prompt: RK3568 Breathe LED support
│ Location:
│ -> Device Drivers
│ (1) -> LED Support (NEW_LEDS [=y])
│ Defined at drivers/leds/Kconfig:765
│ Depends on: NEW_LEDS [=y] && (ARCH_ROCKCHIP [=y] && LEDS_CLASS [=y] || COMPILE_TEST [=n])
4. 然后按上面数字1进入页面,按空格选中:
<*> RK3568 Breathe LED support
5. 然后保存退出
6. make ARCH=arm64 savedefconfig
7. cp .config arch/arm64/configs/rockchip_defconfig
8. 退回到sdk根目录编译内核,重新烧写boot.img
启动日志
[ 1.968598] breathe-led breathe-led: Breathe LED started, period: 50, interval: 10ms
[ 1.968615] breathe-led breathe-led: Breathe LED driver probed successfully
[ 1.968625] breathe-led breathe-led: PWM period: 50,Max brightness: 255,Interval: 10ms
看到这个日志就成功了,开发板上的LED灯两快闪一短闪,目前模拟实现就只能这样了。