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