summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSebastian Reichel <sre@kernel.org>2017-03-24 04:47:32 -0400
committerJacek Anaszewski <jacek.anaszewski@gmail.com>2017-03-29 15:02:27 -0400
commitcd3b0b05328e4791c1c5a4edd91599f409bd645f (patch)
tree6246b66eae9e60d94e9c894cbcdc0036df9c9725 /drivers
parente631bf9caac5d735bccdbe02f6084a02e148e9cb (diff)
leds: cpcap: new driver
Motorola CPCAP is a PMIC (power management integrated circuit) found in multiple smartphones. This driver adds support for the chip's LED controllers. This introduces support for all controllers used by the Droid 4. According to Motorola's driver (no datasheets available) there a couple of more LED controllers. I did not add support for them, since I cannot verify that they work with my modifications. Signed-off-by: Sebastian Reichel <sre@kernel.org> Acked-by: Pavel Machek <pavel@ucw.cz> Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/leds/Kconfig9
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-cpcap.c239
3 files changed, 249 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 8075d2ebccff..6c2999872090 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -76,6 +76,15 @@ config LEDS_BCM6358
76 This option enables support for LEDs connected to the BCM6358 76 This option enables support for LEDs connected to the BCM6358
77 LED HW controller accessed via MMIO registers. 77 LED HW controller accessed via MMIO registers.
78 78
79config LEDS_CPCAP
80 tristate "LED Support for Motorola CPCAP"
81 depends on LEDS_CLASS
82 depends on MFD_CPCAP
83 depends on OF
84 help
85 This option enables support for LEDs offered by Motorola's
86 CPCAP PMIC.
87
79config LEDS_LM3530 88config LEDS_LM3530
80 tristate "LCD Backlight driver for LM3530" 89 tristate "LCD Backlight driver for LM3530"
81 depends on LEDS_CLASS 90 depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index b021e03e73b6..45f133962ed8 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
11obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o 11obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
12obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o 12obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
13obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o 13obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
14obj-$(CONFIG_LEDS_CPCAP) += leds-cpcap.o
14obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o 15obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
15obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o 16obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
16obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o 17obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
diff --git a/drivers/leds/leds-cpcap.c b/drivers/leds/leds-cpcap.c
new file mode 100644
index 000000000000..f0f28c442807
--- /dev/null
+++ b/drivers/leds/leds-cpcap.c
@@ -0,0 +1,239 @@
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");