1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * PWM-based multi-color LED control 4 * 5 * Copyright 2022 Sven Schwermer <sven.schwermer@disruptive-technologies.com> 6 */ 7 8 #include <linux/err.h> 9 #include <linux/kernel.h> 10 #include <linux/led-class-multicolor.h> 11 #include <linux/leds.h> 12 #include <linux/mod_devicetable.h> 13 #include <linux/module.h> 14 #include <linux/mutex.h> 15 #include <linux/platform_device.h> 16 #include <linux/property.h> 17 #include <linux/pwm.h> 18 19 struct pwm_led { 20 struct pwm_device *pwm; 21 struct pwm_state state; 22 }; 23 24 struct pwm_mc_led { 25 struct led_classdev_mc mc_cdev; 26 struct mutex lock; 27 struct pwm_led leds[]; 28 }; 29 30 static int led_pwm_mc_set(struct led_classdev *cdev, 31 enum led_brightness brightness) 32 { 33 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 34 struct pwm_mc_led *priv = container_of(mc_cdev, struct pwm_mc_led, mc_cdev); 35 unsigned long long duty; 36 int ret = 0; 37 int i; 38 39 led_mc_calc_color_components(mc_cdev, brightness); 40 41 mutex_lock(&priv->lock); 42 43 for (i = 0; i < mc_cdev->num_colors; i++) { 44 duty = priv->leds[i].state.period; 45 duty *= mc_cdev->subled_info[i].brightness; 46 do_div(duty, cdev->max_brightness); 47 48 priv->leds[i].state.duty_cycle = duty; 49 priv->leds[i].state.enabled = duty > 0; 50 ret = pwm_apply_state(priv->leds[i].pwm, 51 &priv->leds[i].state); 52 if (ret) 53 break; 54 } 55 56 mutex_unlock(&priv->lock); 57 58 return ret; 59 } 60 61 static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv, 62 struct fwnode_handle *mcnode) 63 { 64 struct mc_subled *subled = priv->mc_cdev.subled_info; 65 struct fwnode_handle *fwnode; 66 struct pwm_led *pwmled; 67 u32 color; 68 int ret; 69 70 /* iterate over the nodes inside the multi-led node */ 71 fwnode_for_each_child_node(mcnode, fwnode) { 72 pwmled = &priv->leds[priv->mc_cdev.num_colors]; 73 pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); 74 if (IS_ERR(pwmled->pwm)) { 75 ret = PTR_ERR(pwmled->pwm); 76 dev_err(dev, "unable to request PWM: %d\n", ret); 77 goto release_fwnode; 78 } 79 pwm_init_state(pwmled->pwm, &pwmled->state); 80 81 ret = fwnode_property_read_u32(fwnode, "color", &color); 82 if (ret) { 83 dev_err(dev, "cannot read color: %d\n", ret); 84 goto release_fwnode; 85 } 86 87 subled[priv->mc_cdev.num_colors].color_index = color; 88 priv->mc_cdev.num_colors++; 89 } 90 91 return 0; 92 93 release_fwnode: 94 fwnode_handle_put(fwnode); 95 return ret; 96 } 97 98 static int led_pwm_mc_probe(struct platform_device *pdev) 99 { 100 struct fwnode_handle *mcnode, *fwnode; 101 struct led_init_data init_data = {}; 102 struct led_classdev *cdev; 103 struct mc_subled *subled; 104 struct pwm_mc_led *priv; 105 int count = 0; 106 int ret = 0; 107 108 mcnode = device_get_named_child_node(&pdev->dev, "multi-led"); 109 if (!mcnode) 110 return dev_err_probe(&pdev->dev, -ENODEV, 111 "expected multi-led node\n"); 112 113 /* count the nodes inside the multi-led node */ 114 fwnode_for_each_child_node(mcnode, fwnode) 115 count++; 116 117 priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count), 118 GFP_KERNEL); 119 if (!priv) { 120 ret = -ENOMEM; 121 goto release_mcnode; 122 } 123 mutex_init(&priv->lock); 124 125 subled = devm_kcalloc(&pdev->dev, count, sizeof(*subled), GFP_KERNEL); 126 if (!subled) { 127 ret = -ENOMEM; 128 goto release_mcnode; 129 } 130 priv->mc_cdev.subled_info = subled; 131 132 /* init the multicolor's LED class device */ 133 cdev = &priv->mc_cdev.led_cdev; 134 fwnode_property_read_u32(mcnode, "max-brightness", 135 &cdev->max_brightness); 136 cdev->flags = LED_CORE_SUSPENDRESUME; 137 cdev->brightness_set_blocking = led_pwm_mc_set; 138 139 ret = iterate_subleds(&pdev->dev, priv, mcnode); 140 if (ret) 141 goto release_mcnode; 142 143 init_data.fwnode = mcnode; 144 ret = devm_led_classdev_multicolor_register_ext(&pdev->dev, 145 &priv->mc_cdev, 146 &init_data); 147 if (ret) { 148 dev_err(&pdev->dev, 149 "failed to register multicolor PWM led for %s: %d\n", 150 cdev->name, ret); 151 goto release_mcnode; 152 } 153 154 ret = led_pwm_mc_set(cdev, cdev->brightness); 155 if (ret) 156 return dev_err_probe(&pdev->dev, ret, 157 "failed to set led PWM value for %s: %d", 158 cdev->name, ret); 159 160 platform_set_drvdata(pdev, priv); 161 return 0; 162 163 release_mcnode: 164 fwnode_handle_put(mcnode); 165 return ret; 166 } 167 168 static const struct of_device_id of_pwm_leds_mc_match[] = { 169 { .compatible = "pwm-leds-multicolor", }, 170 {} 171 }; 172 MODULE_DEVICE_TABLE(of, of_pwm_leds_mc_match); 173 174 static struct platform_driver led_pwm_mc_driver = { 175 .probe = led_pwm_mc_probe, 176 .driver = { 177 .name = "leds_pwm_multicolor", 178 .of_match_table = of_pwm_leds_mc_match, 179 }, 180 }; 181 module_platform_driver(led_pwm_mc_driver); 182 183 MODULE_AUTHOR("Sven Schwermer <sven.schwermer@disruptive-technologies.com>"); 184 MODULE_DESCRIPTION("multi-color PWM LED driver"); 185 MODULE_LICENSE("GPL v2"); 186 MODULE_ALIAS("platform:leds-pwm-multicolor"); 187