LED驱动
添加节点
/*
* drivers/amlogic/led/led_sys.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#define pr_fmt(fmt) "rc_led: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#define AML_DEV_NAME "rc_led"
#define AML_LED_NAME "led-rc"
enum {
LED_GPIO_STATE_OFF = 0,
LED_GPIO_STATE_ON,
};
struct aml_led_gpio {
const char *name;
unsigned int pin;
unsigned int active_low;
unsigned int state;
};
struct aml_rcled_dev {
struct aml_led_gpio d;
struct led_classdev cdev;
enum led_brightness new_brightness;
unsigned int status;
struct work_struct work;
struct mutex lock;
};
static struct class *rcled_class;
struct aml_rcled_dev *rcled_dev;
static void aml_rcled_output_setup(struct aml_rcled_dev *ldev,
enum led_brightness value)
{
unsigned int level = !!value;
if (ldev->d.active_low)
level = !level;
gpio_direction_output(ldev->d.pin, level);
}
static void aml_rcled_work(struct work_struct *work)
{
char led_blink = 0,d_blink = 0;
struct aml_rcled_dev *ldev;
ldev = container_of(work, struct aml_rcled_dev, work);
led_blink = !gpio_get_value(ldev->d.pin);
//0 LED灯灭,1 LED灯常亮。2,LED灯慢闪。3,LED灯快闪。4,LED灯间闪
switch(rcled_dev->status){
case 0:
mutex_lock(&ldev->lock);
aml_rcled_output_setup(ldev, 0);
mutex_unlock(&ldev->lock);
break;
case 1:
mutex_lock(&ldev->lock);
aml_rcled_output_setup(ldev, 1);
mutex_unlock(&ldev->lock);
break;
// default:
// mutex_lock(&ldev->lock);
// aml_rcled_output_setup(ldev, ldev->new_brightness);
// mutex_unlock(&ldev->lock);
// break;
}
while(rcled_dev->status > 1){ //快闪,慢闪,间闪需要无限循环。
switch(rcled_dev->status){
case 2:
mutex_lock(&ldev->lock);
aml_rcled_output_setup(ldev, led_blink);
led_blink = !led_blink;
mutex_unlock(&ldev->lock);
msleep(500);
break;
case 3:
mutex_lock(&ldev->lock);
aml_rcled_output_setup(ldev, led_blink);
led_blink = !led_blink;
mutex_unlock(&ldev->lock);
msleep(100);
break;
case 4:
mutex_lock(&ldev->lock);
aml_rcled_output_setup(ldev, led_blink);
led_blink = !led_blink;
mutex_unlock(&ldev->lock);
msleep(200);
d_blink++;
if(d_blink % 10 == 0)
msleep(2000);
break;
}
}
}
static void aml_rcled_brightness_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct aml_rcled_dev *ldev;
struct platform_device *pdev;
pdev = to_platform_device(cdev->dev->parent);
ldev = platform_get_drvdata(pdev);
ldev->new_brightness = value;
ldev->status = 1;
schedule_work(&ldev->work);
}
static int aml_rcled_dt_parse(struct platform_device *pdev)
{
struct device_node *node;
struct aml_rcled_dev *ldev;
int led_gpio;
enum of_gpio_flags flags;
ldev = platform_get_drvdata(pdev);
node = pdev->dev.of_node;
if (!node) {
pr_err("failed to find node for %s\n", AML_DEV_NAME);
return -ENODEV;
}
led_gpio = of_get_named_gpio_flags(node, "led_gpio", 0, &flags);
if (!gpio_is_valid(led_gpio)) {
pr_err("gpio %d is not valid\n", led_gpio);
return -EINVAL;
}
ldev->d.pin = led_gpio;
ldev->d.active_low = flags & OF_GPIO_ACTIVE_LOW;
gpio_request(ldev->d.pin, AML_DEV_NAME);
// gpio_direction_output(ldev->d.pin, 0);
return 0;
}
static const struct of_device_id aml_rcled_dt_match[] = {
{
.compatible = "ruichi, rcled",
},
{},
};
static ssize_t led_status_show(struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", rcled_dev->status);
}
static ssize_t led_status_store(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
unsigned int val;
int ret;
ret = sscanf(buf, "%d", &val);
if (ret == 1) { //success
rcled_dev->status = val;
rcled_dev->new_brightness = 1;
schedule_work(&rcled_dev->work);
} else {
pr_info("invalid data\n");
return -EINVAL;
}
return count;
}
void led_status_set(int status){
rcled_dev->status = status;
rcled_dev->new_brightness = 1;
schedule_work(&rcled_dev->work);
}
EXPORT_SYMBOL(led_status_set);
static struct class_attribute rcled_class_attrs[] =
{
__ATTR(status, 0644,
led_status_show, led_status_store),
};
static int creat_rcled_class(void)
{
int i;
rcled_class = class_create(THIS_MODULE, "rcled");
if (IS_ERR(rcled_class)) {
pr_info("create debug class failed\n");
return -1;
}
for (i = 0; i < ARRAY_SIZE(rcled_class_attrs); i++) {
if (class_create_file(rcled_class,
&rcled_class_attrs[i])) {
pr_info("create debug attribute %s failed\n",
rcled_class_attrs[i].attr.name);
}
}
return 0;
}
static int remove_rcled_class(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(rcled_class_attrs); i++)
class_remove_file(rcled_class, &rcled_class_attrs[i]);
class_destroy(rcled_class);
rcled_class = NULL;
return 0;
}
static int aml_rcled_probe(struct platform_device *pdev)
{
struct aml_rcled_dev *ldev;
int ret;
ldev = kzalloc(sizeof(struct aml_rcled_dev), GFP_KERNEL);
/* set driver data */
platform_set_drvdata(pdev, ldev);
/* parse dt param */
ret = aml_rcled_dt_parse(pdev);
if (ret)
return ret;
/* register led class device */
ldev->cdev.name = AML_LED_NAME;
ldev->cdev.brightness_set = aml_rcled_brightness_set;
rcled_dev = ldev;
mutex_init(&ldev->lock);
INIT_WORK(&ldev->work, aml_rcled_work);
ret = led_classdev_register(&pdev->dev, &ldev->cdev);
if (ret < 0) {
kfree(ldev);
return ret;
}
/* set led default on */
//aml_rcled_output_setup(ldev, 0);
creat_rcled_class();
led_status_set(2);
pr_info("rcled: module probed ok\n");
return 0;
}
static int __exit aml_rcled_remove(struct platform_device *pdev)
{
struct aml_rcled_dev *ldev = platform_get_drvdata(pdev);
remove_rcled_class();
led_classdev_unregister(&ldev->cdev);
cancel_work_sync(&ldev->work);
gpio_free(ldev->d.pin);
platform_set_drvdata(pdev, NULL);
kfree(ldev);
pr_info("module removed ok\n");
return 0;
}
static void aml_rcled_shutdown(struct platform_device *pdev)
{
struct aml_rcled_dev *ldev = platform_get_drvdata(pdev);
/* set led off*/
aml_rcled_output_setup(ldev, 0);
pr_info("module shutdown ok\n");
}
#ifdef CONFIG_PM
static int aml_rcled_suspend(struct platform_device *pdev, pm_message_t state)
{
struct aml_rcled_dev *ldev = platform_get_drvdata(pdev);
/* set led off */
aml_rcled_output_setup(ldev, 0);
pr_info("module suspend ok\n");
return 0;
}
static int aml_rcled_resume(struct platform_device *pdev)
{
struct aml_rcled_dev *ldev = platform_get_drvdata(pdev);
/* set led on */
aml_rcled_output_setup(ldev, 1);
pr_info("module resume ok\n");
return 0;
}
#endif
static struct platform_driver aml_rcled_driver = {
.driver = {
.name = AML_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = aml_rcled_dt_match,
},
.probe = aml_rcled_probe,
.remove = __exit_p(aml_rcled_remove),
.shutdown = aml_rcled_shutdown,
#ifdef CONFIG_PM
.suspend = aml_rcled_suspend,
.resume = aml_rcled_resume,
#endif
};
static int __init aml_rcled_init(void)
{
if (platform_driver_register(&aml_rcled_driver)) {
pr_err("failed to register driver\n");
return -ENODEV;
}
return 0;
}
static void __exit aml_rcled_exit(void)
{
pr_info("module exit\n");
platform_driver_unregister(&aml_rcled_driver);
}
subsys_initcall(aml_rcled_init);
module_exit(aml_rcled_exit);
MODULE_DESCRIPTION("ruichi led driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("rubin zhanghb@ruichi-group.com");
网友评论