| /* |
| * 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."); |
| |