|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * Copyright (c) 2017 Sebastian Reichel <sre@kernel.org> | 
|  | */ | 
|  |  | 
|  | #include <linux/leds.h> | 
|  | #include <linux/mfd/motorola-cpcap.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/regulator/consumer.h> | 
|  |  | 
|  | #define CPCAP_LED_NO_CURRENT 0x0001 | 
|  |  | 
|  | struct cpcap_led_info { | 
|  | u16 reg; | 
|  | u16 mask; | 
|  | u16 limit; | 
|  | u16 init_mask; | 
|  | u16 init_val; | 
|  | }; | 
|  |  | 
|  | static const struct cpcap_led_info cpcap_led_red = { | 
|  | .reg	= CPCAP_REG_REDC, | 
|  | .mask	= 0x03FF, | 
|  | .limit	= 31, | 
|  | }; | 
|  |  | 
|  | static const struct cpcap_led_info cpcap_led_green = { | 
|  | .reg	= CPCAP_REG_GREENC, | 
|  | .mask	= 0x03FF, | 
|  | .limit	= 31, | 
|  | }; | 
|  |  | 
|  | static const struct cpcap_led_info cpcap_led_blue = { | 
|  | .reg	= CPCAP_REG_BLUEC, | 
|  | .mask	= 0x03FF, | 
|  | .limit	= 31, | 
|  | }; | 
|  |  | 
|  | /* aux display light */ | 
|  | static const struct cpcap_led_info cpcap_led_adl = { | 
|  | .reg		= CPCAP_REG_ADLC, | 
|  | .mask		= 0x000F, | 
|  | .limit		= 1, | 
|  | .init_mask	= 0x7FFF, | 
|  | .init_val	= 0x5FF0, | 
|  | }; | 
|  |  | 
|  | /* camera privacy led */ | 
|  | static const struct cpcap_led_info cpcap_led_cp = { | 
|  | .reg		= CPCAP_REG_CLEDC, | 
|  | .mask		= 0x0007, | 
|  | .limit		= 1, | 
|  | .init_mask	= 0x03FF, | 
|  | .init_val	= 0x0008, | 
|  | }; | 
|  |  | 
|  | struct cpcap_led { | 
|  | struct led_classdev led; | 
|  | const struct cpcap_led_info *info; | 
|  | struct device *dev; | 
|  | struct regmap *regmap; | 
|  | struct mutex update_lock; | 
|  | struct regulator *vdd; | 
|  | bool powered; | 
|  |  | 
|  | u32 current_limit; | 
|  | }; | 
|  |  | 
|  | static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle) | 
|  | { | 
|  | current_limit &= 0x1f; /* 5 bit */ | 
|  | duty_cycle &= 0x0f; /* 4 bit */ | 
|  |  | 
|  | return current_limit << 4 | duty_cycle; | 
|  | } | 
|  |  | 
|  | static int cpcap_led_set_power(struct cpcap_led *led, bool status) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | if (status == led->powered) | 
|  | return 0; | 
|  |  | 
|  | if (status) | 
|  | err = regulator_enable(led->vdd); | 
|  | else | 
|  | err = regulator_disable(led->vdd); | 
|  |  | 
|  | if (err) { | 
|  | dev_err(led->dev, "regulator failure: %d", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | led->powered = status; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value) | 
|  | { | 
|  | struct cpcap_led *led = container_of(ledc, struct cpcap_led, led); | 
|  | int brightness; | 
|  | int err; | 
|  |  | 
|  | mutex_lock(&led->update_lock); | 
|  |  | 
|  | if (value > LED_OFF) { | 
|  | err = cpcap_led_set_power(led, true); | 
|  | if (err) | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | if (value == LED_OFF) { | 
|  | /* Avoid HW issue by turning off current before duty cycle */ | 
|  | err = regmap_update_bits(led->regmap, | 
|  | led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT); | 
|  | if (err) { | 
|  | dev_err(led->dev, "regmap failed: %d", err); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | brightness = cpcap_led_val(value, LED_OFF); | 
|  | } else { | 
|  | brightness = cpcap_led_val(value, LED_ON); | 
|  | } | 
|  |  | 
|  | err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask, | 
|  | brightness); | 
|  | if (err) { | 
|  | dev_err(led->dev, "regmap failed: %d", err); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | if (value == LED_OFF) { | 
|  | err = cpcap_led_set_power(led, false); | 
|  | if (err) | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | exit: | 
|  | mutex_unlock(&led->update_lock); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id cpcap_led_of_match[] = { | 
|  | { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red }, | 
|  | { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green }, | 
|  | { .compatible = "motorola,cpcap-led-blue",  .data = &cpcap_led_blue }, | 
|  | { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl }, | 
|  | { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, cpcap_led_of_match); | 
|  |  | 
|  | static int cpcap_led_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct cpcap_led *led; | 
|  | int err; | 
|  |  | 
|  | led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); | 
|  | if (!led) | 
|  | return -ENOMEM; | 
|  | platform_set_drvdata(pdev, led); | 
|  | led->info = device_get_match_data(&pdev->dev); | 
|  | led->dev = &pdev->dev; | 
|  |  | 
|  | if (led->info->reg == 0x0000) { | 
|  | dev_err(led->dev, "Unsupported LED"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | led->regmap = dev_get_regmap(pdev->dev.parent, NULL); | 
|  | if (!led->regmap) | 
|  | return -ENODEV; | 
|  |  | 
|  | led->vdd = devm_regulator_get(&pdev->dev, "vdd"); | 
|  | if (IS_ERR(led->vdd)) { | 
|  | err = PTR_ERR(led->vdd); | 
|  | dev_err(led->dev, "Couldn't get regulator: %d", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = device_property_read_string(&pdev->dev, "label", &led->led.name); | 
|  | if (err) { | 
|  | dev_err(led->dev, "Couldn't read LED label: %d", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | if (led->info->init_mask) { | 
|  | err = regmap_update_bits(led->regmap, led->info->reg, | 
|  | led->info->init_mask, led->info->init_val); | 
|  | if (err) { | 
|  | dev_err(led->dev, "regmap failed: %d", err); | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | mutex_init(&led->update_lock); | 
|  |  | 
|  | led->led.max_brightness = led->info->limit; | 
|  | led->led.brightness_set_blocking = cpcap_led_set; | 
|  | err = devm_led_classdev_register(&pdev->dev, &led->led); | 
|  | if (err) { | 
|  | dev_err(led->dev, "Couldn't register LED: %d", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver cpcap_led_driver = { | 
|  | .probe = cpcap_led_probe, | 
|  | .driver = { | 
|  | .name = "cpcap-led", | 
|  | .of_match_table = cpcap_led_of_match, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(cpcap_led_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("CPCAP LED driver"); | 
|  | MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); | 
|  | MODULE_LICENSE("GPL"); |