blob: 24b9e63d54760a720c5a3e2c897daca6e5011aaa [file] [log] [blame]
/*
* 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) "sysled: " 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 "led_sys.h"
#define AML_DEV_NAME "sysled"
#define AML_LED_NAME "led-sys"
static void aml_sysled_output_setup(struct aml_sysled_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_sysled_work(struct work_struct *work)
{
struct aml_sysled_dev *ldev;
ldev = container_of(work, struct aml_sysled_dev, work);
mutex_lock(&ldev->lock);
aml_sysled_output_setup(ldev, ldev->new_brightness);
mutex_unlock(&ldev->lock);
}
static void aml_sysled_brightness_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct aml_sysled_dev *ldev;
struct platform_device *pdev;
pdev = to_platform_device(cdev->dev->parent);
ldev = platform_get_drvdata(pdev);
ldev->new_brightness = value;
schedule_work(&ldev->work);
}
static int aml_sysled_dt_parse(struct platform_device *pdev)
{
struct device_node *node;
struct aml_sysled_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;
pr_info("led_gpio = %u\n", ldev->d.pin);
pr_info("active_low = %u\n", ldev->d.active_low);
gpio_request(ldev->d.pin, AML_DEV_NAME);
gpio_direction_output(ldev->d.pin, 1);
return 0;
}
static const struct of_device_id aml_sysled_dt_match[] = {
{
.compatible = "amlogic, sysled",
},
{},
};
static int aml_sysled_probe(struct platform_device *pdev)
{
struct aml_sysled_dev *ldev;
int ret;
ldev = kzalloc(sizeof(struct aml_sysled_dev), GFP_KERNEL);
/* set driver data */
platform_set_drvdata(pdev, ldev);
/* parse dt param */
ret = aml_sysled_dt_parse(pdev);
if (ret)
return ret;
/* register led class device */
ldev->cdev.name = AML_LED_NAME;
ldev->cdev.brightness_set = aml_sysled_brightness_set;
mutex_init(&ldev->lock);
INIT_WORK(&ldev->work, aml_sysled_work);
ret = led_classdev_register(&pdev->dev, &ldev->cdev);
if (ret < 0) {
kfree(ldev);
return ret;
}
/* set led default on */
aml_sysled_output_setup(ldev, 1);
pr_info("module probed ok\n");
return 0;
}
static int __exit aml_sysled_remove(struct platform_device *pdev)
{
struct aml_sysled_dev *ldev = platform_get_drvdata(pdev);
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_sysled_shutdown(struct platform_device *pdev)
{
struct aml_sysled_dev *ldev = platform_get_drvdata(pdev);
/* set led off*/
aml_sysled_output_setup(ldev, 0);
pr_info("module shutdown ok\n");
}
#ifdef CONFIG_PM
static int aml_sysled_suspend(struct platform_device *pdev, pm_message_t state)
{
struct aml_sysled_dev *ldev = platform_get_drvdata(pdev);
/* set led off */
aml_sysled_output_setup(ldev, 0);
pr_info("module suspend ok\n");
return 0;
}
static int aml_sysled_resume(struct platform_device *pdev)
{
struct aml_sysled_dev *ldev = platform_get_drvdata(pdev);
/* set led on */
aml_sysled_output_setup(ldev, 1);
pr_info("module resume ok\n");
return 0;
}
#endif
static struct platform_driver aml_sysled_driver = {
.driver = {
.name = AML_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = aml_sysled_dt_match,
},
.probe = aml_sysled_probe,
.remove = __exit_p(aml_sysled_remove),
.shutdown = aml_sysled_shutdown,
#ifdef CONFIG_PM
.suspend = aml_sysled_suspend,
.resume = aml_sysled_resume,
#endif
};
static int __init aml_sysled_init(void)
{
pr_info("module init\n");
if (platform_driver_register(&aml_sysled_driver)) {
pr_err("failed to register driver\n");
return -ENODEV;
}
return 0;
}
static void __exit aml_sysled_exit(void)
{
pr_info("module exit\n");
platform_driver_unregister(&aml_sysled_driver);
}
module_init(aml_sysled_init);
module_exit(aml_sysled_exit);
MODULE_DESCRIPTION("Amlogic sys led driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amlogic, Inc.");