root/drivers/leds/leds-cpcap.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. cpcap_led_val
  2. cpcap_led_set_power
  3. cpcap_led_set
  4. cpcap_led_probe

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

/* [<][>][^][v][top][bottom][index][help] */