linux/drivers/leds/leds-cpcap.c
<<
>>
Prefs
   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
  17struct cpcap_led_info {
  18        u16 reg;
  19        u16 mask;
  20        u16 limit;
  21        u16 init_mask;
  22        u16 init_val;
  23};
  24
  25static const struct cpcap_led_info cpcap_led_red = {
  26        .reg    = CPCAP_REG_REDC,
  27        .mask   = 0x03FF,
  28        .limit  = 31,
  29};
  30
  31static const struct cpcap_led_info cpcap_led_green = {
  32        .reg    = CPCAP_REG_GREENC,
  33        .mask   = 0x03FF,
  34        .limit  = 31,
  35};
  36
  37static 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 */
  44static 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 */
  53static 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
  61struct 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
  73static 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
  81static 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
 103static 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
 144exit:
 145        mutex_unlock(&led->update_lock);
 146        return err;
 147}
 148
 149static 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};
 157MODULE_DEVICE_TABLE(of, cpcap_led_of_match);
 158
 159static 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
 220static 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};
 227module_platform_driver(cpcap_led_driver);
 228
 229MODULE_DESCRIPTION("CPCAP LED driver");
 230MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
 231MODULE_LICENSE("GPL");
 232