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.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