GNU Linux-libre 4.14.266-gnu1
[releases.git] / drivers / leds / leds-cpcap.c
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
25 struct cpcap_led_info {
26         u16 reg;
27         u16 mask;
28         u16 limit;
29         u16 init_mask;
30         u16 init_val;
31 };
32
33 static const struct cpcap_led_info cpcap_led_red = {
34         .reg    = CPCAP_REG_REDC,
35         .mask   = 0x03FF,
36         .limit  = 31,
37 };
38
39 static const struct cpcap_led_info cpcap_led_green = {
40         .reg    = CPCAP_REG_GREENC,
41         .mask   = 0x03FF,
42         .limit  = 31,
43 };
44
45 static 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 */
52 static 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 */
61 static 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
69 struct 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
81 static 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
89 static 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
111 static 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
152 exit:
153         mutex_unlock(&led->update_lock);
154         return err;
155 }
156
157 static 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 };
165 MODULE_DEVICE_TABLE(of, cpcap_led_of_match);
166
167 static 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
228 static 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 };
235 module_platform_driver(cpcap_led_driver);
236
237 MODULE_DESCRIPTION("CPCAP LED driver");
238 MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
239 MODULE_LICENSE("GPL");