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