162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * leds-regulator.c - LED class driver for regulator driven LEDs. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Inspired by leds-wm8350 driver. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/leds.h> 1562306a36Sopenharmony_ci#include <linux/leds-regulator.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define to_regulator_led(led_cdev) \ 2062306a36Sopenharmony_ci container_of(led_cdev, struct regulator_led, cdev) 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistruct regulator_led { 2362306a36Sopenharmony_ci struct led_classdev cdev; 2462306a36Sopenharmony_ci int enabled; 2562306a36Sopenharmony_ci struct mutex mutex; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci struct regulator *vcc; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic inline int led_regulator_get_max_brightness(struct regulator *supply) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci int ret; 3362306a36Sopenharmony_ci int voltage = regulator_list_voltage(supply, 0); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci if (voltage <= 0) 3662306a36Sopenharmony_ci return 1; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci /* even if regulator can't change voltages, 3962306a36Sopenharmony_ci * we still assume it can change status 4062306a36Sopenharmony_ci * and the LED can be turned on and off. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci ret = regulator_set_voltage(supply, voltage, voltage); 4362306a36Sopenharmony_ci if (ret < 0) 4462306a36Sopenharmony_ci return 1; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci return regulator_count_voltages(supply); 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic int led_regulator_get_voltage(struct regulator *supply, 5062306a36Sopenharmony_ci enum led_brightness brightness) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci if (brightness == 0) 5362306a36Sopenharmony_ci return -EINVAL; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci return regulator_list_voltage(supply, brightness - 1); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void regulator_led_enable(struct regulator_led *led) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci int ret; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (led->enabled) 6462306a36Sopenharmony_ci return; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci ret = regulator_enable(led->vcc); 6762306a36Sopenharmony_ci if (ret != 0) { 6862306a36Sopenharmony_ci dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret); 6962306a36Sopenharmony_ci return; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci led->enabled = 1; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic void regulator_led_disable(struct regulator_led *led) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci int ret; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (!led->enabled) 8062306a36Sopenharmony_ci return; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci ret = regulator_disable(led->vcc); 8362306a36Sopenharmony_ci if (ret != 0) { 8462306a36Sopenharmony_ci dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret); 8562306a36Sopenharmony_ci return; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci led->enabled = 0; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic int regulator_led_brightness_set(struct led_classdev *led_cdev, 9262306a36Sopenharmony_ci enum led_brightness value) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct regulator_led *led = to_regulator_led(led_cdev); 9562306a36Sopenharmony_ci int voltage; 9662306a36Sopenharmony_ci int ret = 0; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci mutex_lock(&led->mutex); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci if (value == LED_OFF) { 10162306a36Sopenharmony_ci regulator_led_disable(led); 10262306a36Sopenharmony_ci goto out; 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (led->cdev.max_brightness > 1) { 10662306a36Sopenharmony_ci voltage = led_regulator_get_voltage(led->vcc, value); 10762306a36Sopenharmony_ci dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n", 10862306a36Sopenharmony_ci value, voltage); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci ret = regulator_set_voltage(led->vcc, voltage, voltage); 11162306a36Sopenharmony_ci if (ret != 0) 11262306a36Sopenharmony_ci dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n", 11362306a36Sopenharmony_ci voltage, ret); 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci regulator_led_enable(led); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ciout: 11962306a36Sopenharmony_ci mutex_unlock(&led->mutex); 12062306a36Sopenharmony_ci return ret; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic int regulator_led_probe(struct platform_device *pdev) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct led_regulator_platform_data *pdata = 12662306a36Sopenharmony_ci dev_get_platdata(&pdev->dev); 12762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 12862306a36Sopenharmony_ci struct led_init_data init_data = {}; 12962306a36Sopenharmony_ci struct regulator_led *led; 13062306a36Sopenharmony_ci struct regulator *vcc; 13162306a36Sopenharmony_ci int ret = 0; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci vcc = devm_regulator_get_exclusive(dev, "vled"); 13462306a36Sopenharmony_ci if (IS_ERR(vcc)) { 13562306a36Sopenharmony_ci dev_err(dev, "Cannot get vcc\n"); 13662306a36Sopenharmony_ci return PTR_ERR(vcc); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 14062306a36Sopenharmony_ci if (led == NULL) 14162306a36Sopenharmony_ci return -ENOMEM; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci init_data.fwnode = dev->fwnode; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci led->cdev.max_brightness = led_regulator_get_max_brightness(vcc); 14662306a36Sopenharmony_ci /* Legacy platform data label assignment */ 14762306a36Sopenharmony_ci if (pdata) { 14862306a36Sopenharmony_ci if (pdata->brightness > led->cdev.max_brightness) { 14962306a36Sopenharmony_ci dev_err(dev, "Invalid default brightness %d\n", 15062306a36Sopenharmony_ci pdata->brightness); 15162306a36Sopenharmony_ci return -EINVAL; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci led->cdev.brightness = pdata->brightness; 15462306a36Sopenharmony_ci init_data.default_label = pdata->name; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci led->cdev.brightness_set_blocking = regulator_led_brightness_set; 15862306a36Sopenharmony_ci led->cdev.flags |= LED_CORE_SUSPENDRESUME; 15962306a36Sopenharmony_ci led->vcc = vcc; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* to handle correctly an already enabled regulator */ 16262306a36Sopenharmony_ci if (regulator_is_enabled(led->vcc)) 16362306a36Sopenharmony_ci led->enabled = 1; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci mutex_init(&led->mutex); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci platform_set_drvdata(pdev, led); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci ret = led_classdev_register_ext(dev, &led->cdev, &init_data); 17062306a36Sopenharmony_ci if (ret < 0) 17162306a36Sopenharmony_ci return ret; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return 0; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int regulator_led_remove(struct platform_device *pdev) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct regulator_led *led = platform_get_drvdata(pdev); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci led_classdev_unregister(&led->cdev); 18162306a36Sopenharmony_ci regulator_led_disable(led); 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic const struct of_device_id regulator_led_of_match[] = { 18662306a36Sopenharmony_ci { .compatible = "regulator-led", }, 18762306a36Sopenharmony_ci {} 18862306a36Sopenharmony_ci}; 18962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, regulator_led_of_match); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic struct platform_driver regulator_led_driver = { 19262306a36Sopenharmony_ci .driver = { 19362306a36Sopenharmony_ci .name = "leds-regulator", 19462306a36Sopenharmony_ci .of_match_table = regulator_led_of_match, 19562306a36Sopenharmony_ci }, 19662306a36Sopenharmony_ci .probe = regulator_led_probe, 19762306a36Sopenharmony_ci .remove = regulator_led_remove, 19862306a36Sopenharmony_ci}; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cimodule_platform_driver(regulator_led_driver); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ciMODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>"); 20362306a36Sopenharmony_ciMODULE_DESCRIPTION("Regulator driven LED driver"); 20462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 20562306a36Sopenharmony_ciMODULE_ALIAS("platform:leds-regulator"); 206