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