Linux Audio

Check our new training course

Loading...
v6.13.7
  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");
v5.14.15
  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
 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");