1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (c) 2017 Sebastian Reichel <sre@kernel.org> 4 */ 5 6 #include <linux/leds.h> 7 #include <linux/mfd/motorola-cpcap.h> 8 #include <linux/module.h> 9 #include <linux/mutex.h> 10 #include <linux/of_device.h> 11 #include <linux/platform_device.h> 12 #include <linux/regmap.h> 13 #include <linux/regulator/consumer.h> 14 15 #define CPCAP_LED_NO_CURRENT 0x0001 16 17 struct cpcap_led_info { 18 u16 reg; 19 u16 mask; 20 u16 limit; 21 u16 init_mask; 22 u16 init_val; 23 }; 24 25 static const struct cpcap_led_info cpcap_led_red = { 26 .reg = CPCAP_REG_REDC, 27 .mask = 0x03FF, 28 .limit = 31, 29 }; 30 31 static const struct cpcap_led_info cpcap_led_green = { 32 .reg = CPCAP_REG_GREENC, 33 .mask = 0x03FF, 34 .limit = 31, 35 }; 36 37 static const struct cpcap_led_info cpcap_led_blue = { 38 .reg = CPCAP_REG_BLUEC, 39 .mask = 0x03FF, 40 .limit = 31, 41 }; 42 43 /* aux display light */ 44 static const struct cpcap_led_info cpcap_led_adl = { 45 .reg = CPCAP_REG_ADLC, 46 .mask = 0x000F, 47 .limit = 1, 48 .init_mask = 0x7FFF, 49 .init_val = 0x5FF0, 50 }; 51 52 /* camera privacy led */ 53 static const struct cpcap_led_info cpcap_led_cp = { 54 .reg = CPCAP_REG_CLEDC, 55 .mask = 0x0007, 56 .limit = 1, 57 .init_mask = 0x03FF, 58 .init_val = 0x0008, 59 }; 60 61 struct cpcap_led { 62 struct led_classdev led; 63 const struct cpcap_led_info *info; 64 struct device *dev; 65 struct regmap *regmap; 66 struct mutex update_lock; 67 struct regulator *vdd; 68 bool powered; 69 70 u32 current_limit; 71 }; 72 73 static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle) 74 { 75 current_limit &= 0x1f; /* 5 bit */ 76 duty_cycle &= 0x0f; /* 4 bit */ 77 78 return current_limit << 4 | duty_cycle; 79 } 80 81 static int cpcap_led_set_power(struct cpcap_led *led, bool status) 82 { 83 int err; 84 85 if (status == led->powered) 86 return 0; 87 88 if (status) 89 err = regulator_enable(led->vdd); 90 else 91 err = regulator_disable(led->vdd); 92 93 if (err) { 94 dev_err(led->dev, "regulator failure: %d", err); 95 return err; 96 } 97 98 led->powered = status; 99 100 return 0; 101 } 102 103 static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value) 104 { 105 struct cpcap_led *led = container_of(ledc, struct cpcap_led, led); 106 int brightness; 107 int err; 108 109 mutex_lock(&led->update_lock); 110 111 if (value > LED_OFF) { 112 err = cpcap_led_set_power(led, true); 113 if (err) 114 goto exit; 115 } 116 117 if (value == LED_OFF) { 118 /* Avoid HW issue by turning off current before duty cycle */ 119 err = regmap_update_bits(led->regmap, 120 led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT); 121 if (err) { 122 dev_err(led->dev, "regmap failed: %d", err); 123 goto exit; 124 } 125 126 brightness = cpcap_led_val(value, LED_OFF); 127 } else { 128 brightness = cpcap_led_val(value, LED_ON); 129 } 130 131 err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask, 132 brightness); 133 if (err) { 134 dev_err(led->dev, "regmap failed: %d", err); 135 goto exit; 136 } 137 138 if (value == LED_OFF) { 139 err = cpcap_led_set_power(led, false); 140 if (err) 141 goto exit; 142 } 143 144 exit: 145 mutex_unlock(&led->update_lock); 146 return err; 147 } 148 149 static const struct of_device_id cpcap_led_of_match[] = { 150 { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red }, 151 { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green }, 152 { .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue }, 153 { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl }, 154 { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp }, 155 {}, 156 }; 157 MODULE_DEVICE_TABLE(of, cpcap_led_of_match); 158 159 static int cpcap_led_probe(struct platform_device *pdev) 160 { 161 const struct of_device_id *match; 162 struct cpcap_led *led; 163 int err; 164 165 match = of_match_device(of_match_ptr(cpcap_led_of_match), &pdev->dev); 166 if (!match || !match->data) 167 return -EINVAL; 168 169 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 170 if (!led) 171 return -ENOMEM; 172 platform_set_drvdata(pdev, led); 173 led->info = match->data; 174 led->dev = &pdev->dev; 175 176 if (led->info->reg == 0x0000) { 177 dev_err(led->dev, "Unsupported LED"); 178 return -ENODEV; 179 } 180 181 led->regmap = dev_get_regmap(pdev->dev.parent, NULL); 182 if (!led->regmap) 183 return -ENODEV; 184 185 led->vdd = devm_regulator_get(&pdev->dev, "vdd"); 186 if (IS_ERR(led->vdd)) { 187 err = PTR_ERR(led->vdd); 188 dev_err(led->dev, "Couldn't get regulator: %d", err); 189 return err; 190 } 191 192 err = device_property_read_string(&pdev->dev, "label", &led->led.name); 193 if (err) { 194 dev_err(led->dev, "Couldn't read LED label: %d", err); 195 return err; 196 } 197 198 if (led->info->init_mask) { 199 err = regmap_update_bits(led->regmap, led->info->reg, 200 led->info->init_mask, led->info->init_val); 201 if (err) { 202 dev_err(led->dev, "regmap failed: %d", err); 203 return err; 204 } 205 } 206 207 mutex_init(&led->update_lock); 208 209 led->led.max_brightness = led->info->limit; 210 led->led.brightness_set_blocking = cpcap_led_set; 211 err = devm_led_classdev_register(&pdev->dev, &led->led); 212 if (err) { 213 dev_err(led->dev, "Couldn't register LED: %d", err); 214 return err; 215 } 216 217 return 0; 218 } 219 220 static struct platform_driver cpcap_led_driver = { 221 .probe = cpcap_led_probe, 222 .driver = { 223 .name = "cpcap-led", 224 .of_match_table = cpcap_led_of_match, 225 }, 226 }; 227 module_platform_driver(cpcap_led_driver); 228 229 MODULE_DESCRIPTION("CPCAP LED driver"); 230 MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); 231 MODULE_LICENSE("GPL"); 232