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        struct cpcap_led *led;
 162        int err;
 163
 164        led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
 165        if (!led)
 166                return -ENOMEM;
 167        platform_set_drvdata(pdev, led);
 168        led->info = device_get_match_data(&pdev->dev);
 169        led->dev = &pdev->dev;
 170
 171        if (led->info->reg == 0x0000) {
 172                dev_err(led->dev, "Unsupported LED");
 173                return -ENODEV;
 174        }
 175
 176        led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
 177        if (!led->regmap)
 178                return -ENODEV;
 179
 180        led->vdd = devm_regulator_get(&pdev->dev, "vdd");
 181        if (IS_ERR(led->vdd)) {
 182                err = PTR_ERR(led->vdd);
 183                dev_err(led->dev, "Couldn't get regulator: %d", err);
 184                return err;
 185        }
 186
 187        err = device_property_read_string(&pdev->dev, "label", &led->led.name);
 188        if (err) {
 189                dev_err(led->dev, "Couldn't read LED label: %d", err);
 190                return err;
 191        }
 192
 193        if (led->info->init_mask) {
 194                err = regmap_update_bits(led->regmap, led->info->reg,
 195                        led->info->init_mask, led->info->init_val);
 196                if (err) {
 197                        dev_err(led->dev, "regmap failed: %d", err);
 198                        return err;
 199                }
 200        }
 201
 202        mutex_init(&led->update_lock);
 203
 204        led->led.max_brightness = led->info->limit;
 205        led->led.brightness_set_blocking = cpcap_led_set;
 206        err = devm_led_classdev_register(&pdev->dev, &led->led);
 207        if (err) {
 208                dev_err(led->dev, "Couldn't register LED: %d", err);
 209                return err;
 210        }
 211
 212        return 0;
 213}
 214
 215static struct platform_driver cpcap_led_driver = {
 216        .probe = cpcap_led_probe,
 217        .driver = {
 218                .name = "cpcap-led",
 219                .of_match_table = cpcap_led_of_match,
 220        },
 221};
 222module_platform_driver(cpcap_led_driver);
 223
 224MODULE_DESCRIPTION("CPCAP LED driver");
 225MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
 226MODULE_LICENSE("GPL");
 227