xref: /OK3568_Linux_fs/kernel/drivers/leds/leds-wm831x-status.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * LED driver for WM831x status LEDs
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright(C) 2009 Wolfson Microelectronics PLC.
6*4882a593Smuzhiyun  */
7*4882a593Smuzhiyun 
8*4882a593Smuzhiyun #include <linux/kernel.h>
9*4882a593Smuzhiyun #include <linux/platform_device.h>
10*4882a593Smuzhiyun #include <linux/slab.h>
11*4882a593Smuzhiyun #include <linux/leds.h>
12*4882a593Smuzhiyun #include <linux/err.h>
13*4882a593Smuzhiyun #include <linux/mfd/wm831x/core.h>
14*4882a593Smuzhiyun #include <linux/mfd/wm831x/pdata.h>
15*4882a593Smuzhiyun #include <linux/mfd/wm831x/status.h>
16*4882a593Smuzhiyun #include <linux/module.h>
17*4882a593Smuzhiyun 
18*4882a593Smuzhiyun 
19*4882a593Smuzhiyun struct wm831x_status {
20*4882a593Smuzhiyun 	struct led_classdev cdev;
21*4882a593Smuzhiyun 	struct wm831x *wm831x;
22*4882a593Smuzhiyun 	struct mutex mutex;
23*4882a593Smuzhiyun 
24*4882a593Smuzhiyun 	spinlock_t value_lock;
25*4882a593Smuzhiyun 	int reg;     /* Control register */
26*4882a593Smuzhiyun 	int reg_val; /* Control register value */
27*4882a593Smuzhiyun 
28*4882a593Smuzhiyun 	int blink;
29*4882a593Smuzhiyun 	int blink_time;
30*4882a593Smuzhiyun 	int blink_cyc;
31*4882a593Smuzhiyun 	int src;
32*4882a593Smuzhiyun 	enum led_brightness brightness;
33*4882a593Smuzhiyun };
34*4882a593Smuzhiyun 
35*4882a593Smuzhiyun #define to_wm831x_status(led_cdev) \
36*4882a593Smuzhiyun 	container_of(led_cdev, struct wm831x_status, cdev)
37*4882a593Smuzhiyun 
wm831x_status_set(struct wm831x_status * led)38*4882a593Smuzhiyun static void wm831x_status_set(struct wm831x_status *led)
39*4882a593Smuzhiyun {
40*4882a593Smuzhiyun 	unsigned long flags;
41*4882a593Smuzhiyun 
42*4882a593Smuzhiyun 	mutex_lock(&led->mutex);
43*4882a593Smuzhiyun 
44*4882a593Smuzhiyun 	led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK |
45*4882a593Smuzhiyun 			  WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK);
46*4882a593Smuzhiyun 
47*4882a593Smuzhiyun 	spin_lock_irqsave(&led->value_lock, flags);
48*4882a593Smuzhiyun 
49*4882a593Smuzhiyun 	led->reg_val |= led->src << WM831X_LED_SRC_SHIFT;
50*4882a593Smuzhiyun 	if (led->blink) {
51*4882a593Smuzhiyun 		led->reg_val |= 2 << WM831X_LED_MODE_SHIFT;
52*4882a593Smuzhiyun 		led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT;
53*4882a593Smuzhiyun 		led->reg_val |= led->blink_cyc;
54*4882a593Smuzhiyun 	} else {
55*4882a593Smuzhiyun 		if (led->brightness != LED_OFF)
56*4882a593Smuzhiyun 			led->reg_val |= 1 << WM831X_LED_MODE_SHIFT;
57*4882a593Smuzhiyun 	}
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun 	spin_unlock_irqrestore(&led->value_lock, flags);
60*4882a593Smuzhiyun 
61*4882a593Smuzhiyun 	wm831x_reg_write(led->wm831x, led->reg, led->reg_val);
62*4882a593Smuzhiyun 
63*4882a593Smuzhiyun 	mutex_unlock(&led->mutex);
64*4882a593Smuzhiyun }
65*4882a593Smuzhiyun 
wm831x_status_brightness_set(struct led_classdev * led_cdev,enum led_brightness value)66*4882a593Smuzhiyun static int wm831x_status_brightness_set(struct led_classdev *led_cdev,
67*4882a593Smuzhiyun 					 enum led_brightness value)
68*4882a593Smuzhiyun {
69*4882a593Smuzhiyun 	struct wm831x_status *led = to_wm831x_status(led_cdev);
70*4882a593Smuzhiyun 	unsigned long flags;
71*4882a593Smuzhiyun 
72*4882a593Smuzhiyun 	spin_lock_irqsave(&led->value_lock, flags);
73*4882a593Smuzhiyun 	led->brightness = value;
74*4882a593Smuzhiyun 	if (value == LED_OFF)
75*4882a593Smuzhiyun 		led->blink = 0;
76*4882a593Smuzhiyun 	spin_unlock_irqrestore(&led->value_lock, flags);
77*4882a593Smuzhiyun 	wm831x_status_set(led);
78*4882a593Smuzhiyun 
79*4882a593Smuzhiyun 	return 0;
80*4882a593Smuzhiyun }
81*4882a593Smuzhiyun 
wm831x_status_blink_set(struct led_classdev * led_cdev,unsigned long * delay_on,unsigned long * delay_off)82*4882a593Smuzhiyun static int wm831x_status_blink_set(struct led_classdev *led_cdev,
83*4882a593Smuzhiyun 				   unsigned long *delay_on,
84*4882a593Smuzhiyun 				   unsigned long *delay_off)
85*4882a593Smuzhiyun {
86*4882a593Smuzhiyun 	struct wm831x_status *led = to_wm831x_status(led_cdev);
87*4882a593Smuzhiyun 	unsigned long flags;
88*4882a593Smuzhiyun 	int ret = 0;
89*4882a593Smuzhiyun 
90*4882a593Smuzhiyun 	/* Pick some defaults if we've not been given times */
91*4882a593Smuzhiyun 	if (*delay_on == 0 && *delay_off == 0) {
92*4882a593Smuzhiyun 		*delay_on = 250;
93*4882a593Smuzhiyun 		*delay_off = 250;
94*4882a593Smuzhiyun 	}
95*4882a593Smuzhiyun 
96*4882a593Smuzhiyun 	spin_lock_irqsave(&led->value_lock, flags);
97*4882a593Smuzhiyun 
98*4882a593Smuzhiyun 	/* We only have a limited selection of settings, see if we can
99*4882a593Smuzhiyun 	 * support the configuration we're being given */
100*4882a593Smuzhiyun 	switch (*delay_on) {
101*4882a593Smuzhiyun 	case 1000:
102*4882a593Smuzhiyun 		led->blink_time = 0;
103*4882a593Smuzhiyun 		break;
104*4882a593Smuzhiyun 	case 250:
105*4882a593Smuzhiyun 		led->blink_time = 1;
106*4882a593Smuzhiyun 		break;
107*4882a593Smuzhiyun 	case 125:
108*4882a593Smuzhiyun 		led->blink_time = 2;
109*4882a593Smuzhiyun 		break;
110*4882a593Smuzhiyun 	case 62:
111*4882a593Smuzhiyun 	case 63:
112*4882a593Smuzhiyun 		/* Actually 62.5ms */
113*4882a593Smuzhiyun 		led->blink_time = 3;
114*4882a593Smuzhiyun 		break;
115*4882a593Smuzhiyun 	default:
116*4882a593Smuzhiyun 		ret = -EINVAL;
117*4882a593Smuzhiyun 		break;
118*4882a593Smuzhiyun 	}
119*4882a593Smuzhiyun 
120*4882a593Smuzhiyun 	if (ret == 0) {
121*4882a593Smuzhiyun 		switch (*delay_off / *delay_on) {
122*4882a593Smuzhiyun 		case 1:
123*4882a593Smuzhiyun 			led->blink_cyc = 0;
124*4882a593Smuzhiyun 			break;
125*4882a593Smuzhiyun 		case 3:
126*4882a593Smuzhiyun 			led->blink_cyc = 1;
127*4882a593Smuzhiyun 			break;
128*4882a593Smuzhiyun 		case 4:
129*4882a593Smuzhiyun 			led->blink_cyc = 2;
130*4882a593Smuzhiyun 			break;
131*4882a593Smuzhiyun 		case 8:
132*4882a593Smuzhiyun 			led->blink_cyc = 3;
133*4882a593Smuzhiyun 			break;
134*4882a593Smuzhiyun 		default:
135*4882a593Smuzhiyun 			ret = -EINVAL;
136*4882a593Smuzhiyun 			break;
137*4882a593Smuzhiyun 		}
138*4882a593Smuzhiyun 	}
139*4882a593Smuzhiyun 
140*4882a593Smuzhiyun 	if (ret == 0)
141*4882a593Smuzhiyun 		led->blink = 1;
142*4882a593Smuzhiyun 	else
143*4882a593Smuzhiyun 		led->blink = 0;
144*4882a593Smuzhiyun 
145*4882a593Smuzhiyun 	spin_unlock_irqrestore(&led->value_lock, flags);
146*4882a593Smuzhiyun 	wm831x_status_set(led);
147*4882a593Smuzhiyun 
148*4882a593Smuzhiyun 	return ret;
149*4882a593Smuzhiyun }
150*4882a593Smuzhiyun 
151*4882a593Smuzhiyun static const char * const led_src_texts[] = {
152*4882a593Smuzhiyun 	"otp",
153*4882a593Smuzhiyun 	"power",
154*4882a593Smuzhiyun 	"charger",
155*4882a593Smuzhiyun 	"soft",
156*4882a593Smuzhiyun };
157*4882a593Smuzhiyun 
wm831x_status_src_show(struct device * dev,struct device_attribute * attr,char * buf)158*4882a593Smuzhiyun static ssize_t wm831x_status_src_show(struct device *dev,
159*4882a593Smuzhiyun 				      struct device_attribute *attr, char *buf)
160*4882a593Smuzhiyun {
161*4882a593Smuzhiyun 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
162*4882a593Smuzhiyun 	struct wm831x_status *led = to_wm831x_status(led_cdev);
163*4882a593Smuzhiyun 	int i;
164*4882a593Smuzhiyun 	ssize_t ret = 0;
165*4882a593Smuzhiyun 
166*4882a593Smuzhiyun 	mutex_lock(&led->mutex);
167*4882a593Smuzhiyun 
168*4882a593Smuzhiyun 	for (i = 0; i < ARRAY_SIZE(led_src_texts); i++)
169*4882a593Smuzhiyun 		if (i == led->src)
170*4882a593Smuzhiyun 			ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]);
171*4882a593Smuzhiyun 		else
172*4882a593Smuzhiyun 			ret += sprintf(&buf[ret], "%s ", led_src_texts[i]);
173*4882a593Smuzhiyun 
174*4882a593Smuzhiyun 	mutex_unlock(&led->mutex);
175*4882a593Smuzhiyun 
176*4882a593Smuzhiyun 	ret += sprintf(&buf[ret], "\n");
177*4882a593Smuzhiyun 
178*4882a593Smuzhiyun 	return ret;
179*4882a593Smuzhiyun }
180*4882a593Smuzhiyun 
wm831x_status_src_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)181*4882a593Smuzhiyun static ssize_t wm831x_status_src_store(struct device *dev,
182*4882a593Smuzhiyun 				       struct device_attribute *attr,
183*4882a593Smuzhiyun 				       const char *buf, size_t size)
184*4882a593Smuzhiyun {
185*4882a593Smuzhiyun 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
186*4882a593Smuzhiyun 	struct wm831x_status *led = to_wm831x_status(led_cdev);
187*4882a593Smuzhiyun 	int i;
188*4882a593Smuzhiyun 
189*4882a593Smuzhiyun 	i = sysfs_match_string(led_src_texts, buf);
190*4882a593Smuzhiyun 	if (i >= 0) {
191*4882a593Smuzhiyun 		mutex_lock(&led->mutex);
192*4882a593Smuzhiyun 		led->src = i;
193*4882a593Smuzhiyun 		mutex_unlock(&led->mutex);
194*4882a593Smuzhiyun 		wm831x_status_set(led);
195*4882a593Smuzhiyun 	}
196*4882a593Smuzhiyun 
197*4882a593Smuzhiyun 	return size;
198*4882a593Smuzhiyun }
199*4882a593Smuzhiyun 
200*4882a593Smuzhiyun static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store);
201*4882a593Smuzhiyun 
202*4882a593Smuzhiyun static struct attribute *wm831x_status_attrs[] = {
203*4882a593Smuzhiyun 	&dev_attr_src.attr,
204*4882a593Smuzhiyun 	NULL
205*4882a593Smuzhiyun };
206*4882a593Smuzhiyun ATTRIBUTE_GROUPS(wm831x_status);
207*4882a593Smuzhiyun 
wm831x_status_probe(struct platform_device * pdev)208*4882a593Smuzhiyun static int wm831x_status_probe(struct platform_device *pdev)
209*4882a593Smuzhiyun {
210*4882a593Smuzhiyun 	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
211*4882a593Smuzhiyun 	struct wm831x_pdata *chip_pdata;
212*4882a593Smuzhiyun 	struct wm831x_status_pdata pdata;
213*4882a593Smuzhiyun 	struct wm831x_status *drvdata;
214*4882a593Smuzhiyun 	struct resource *res;
215*4882a593Smuzhiyun 	int id = pdev->id % ARRAY_SIZE(chip_pdata->status);
216*4882a593Smuzhiyun 	int ret;
217*4882a593Smuzhiyun 
218*4882a593Smuzhiyun 	res = platform_get_resource(pdev, IORESOURCE_REG, 0);
219*4882a593Smuzhiyun 	if (res == NULL) {
220*4882a593Smuzhiyun 		dev_err(&pdev->dev, "No register resource\n");
221*4882a593Smuzhiyun 		return -EINVAL;
222*4882a593Smuzhiyun 	}
223*4882a593Smuzhiyun 
224*4882a593Smuzhiyun 	drvdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_status),
225*4882a593Smuzhiyun 			       GFP_KERNEL);
226*4882a593Smuzhiyun 	if (!drvdata)
227*4882a593Smuzhiyun 		return -ENOMEM;
228*4882a593Smuzhiyun 
229*4882a593Smuzhiyun 	drvdata->wm831x = wm831x;
230*4882a593Smuzhiyun 	drvdata->reg = res->start;
231*4882a593Smuzhiyun 
232*4882a593Smuzhiyun 	if (dev_get_platdata(wm831x->dev))
233*4882a593Smuzhiyun 		chip_pdata = dev_get_platdata(wm831x->dev);
234*4882a593Smuzhiyun 	else
235*4882a593Smuzhiyun 		chip_pdata = NULL;
236*4882a593Smuzhiyun 
237*4882a593Smuzhiyun 	memset(&pdata, 0, sizeof(pdata));
238*4882a593Smuzhiyun 	if (chip_pdata && chip_pdata->status[id])
239*4882a593Smuzhiyun 		memcpy(&pdata, chip_pdata->status[id], sizeof(pdata));
240*4882a593Smuzhiyun 	else
241*4882a593Smuzhiyun 		pdata.name = dev_name(&pdev->dev);
242*4882a593Smuzhiyun 
243*4882a593Smuzhiyun 	mutex_init(&drvdata->mutex);
244*4882a593Smuzhiyun 	spin_lock_init(&drvdata->value_lock);
245*4882a593Smuzhiyun 
246*4882a593Smuzhiyun 	/* We cache the configuration register and read startup values
247*4882a593Smuzhiyun 	 * from it. */
248*4882a593Smuzhiyun 	drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg);
249*4882a593Smuzhiyun 
250*4882a593Smuzhiyun 	if (drvdata->reg_val & WM831X_LED_MODE_MASK)
251*4882a593Smuzhiyun 		drvdata->brightness = LED_FULL;
252*4882a593Smuzhiyun 	else
253*4882a593Smuzhiyun 		drvdata->brightness = LED_OFF;
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun 	/* Set a default source if configured, otherwise leave the
256*4882a593Smuzhiyun 	 * current hardware setting.
257*4882a593Smuzhiyun 	 */
258*4882a593Smuzhiyun 	if (pdata.default_src == WM831X_STATUS_PRESERVE) {
259*4882a593Smuzhiyun 		drvdata->src = drvdata->reg_val;
260*4882a593Smuzhiyun 		drvdata->src &= WM831X_LED_SRC_MASK;
261*4882a593Smuzhiyun 		drvdata->src >>= WM831X_LED_SRC_SHIFT;
262*4882a593Smuzhiyun 	} else {
263*4882a593Smuzhiyun 		drvdata->src = pdata.default_src - 1;
264*4882a593Smuzhiyun 	}
265*4882a593Smuzhiyun 
266*4882a593Smuzhiyun 	drvdata->cdev.name = pdata.name;
267*4882a593Smuzhiyun 	drvdata->cdev.default_trigger = pdata.default_trigger;
268*4882a593Smuzhiyun 	drvdata->cdev.brightness_set_blocking = wm831x_status_brightness_set;
269*4882a593Smuzhiyun 	drvdata->cdev.blink_set = wm831x_status_blink_set;
270*4882a593Smuzhiyun 	drvdata->cdev.groups = wm831x_status_groups;
271*4882a593Smuzhiyun 
272*4882a593Smuzhiyun 	ret = led_classdev_register(wm831x->dev, &drvdata->cdev);
273*4882a593Smuzhiyun 	if (ret < 0) {
274*4882a593Smuzhiyun 		dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
275*4882a593Smuzhiyun 		return ret;
276*4882a593Smuzhiyun 	}
277*4882a593Smuzhiyun 
278*4882a593Smuzhiyun 	platform_set_drvdata(pdev, drvdata);
279*4882a593Smuzhiyun 
280*4882a593Smuzhiyun 	return 0;
281*4882a593Smuzhiyun }
282*4882a593Smuzhiyun 
wm831x_status_remove(struct platform_device * pdev)283*4882a593Smuzhiyun static int wm831x_status_remove(struct platform_device *pdev)
284*4882a593Smuzhiyun {
285*4882a593Smuzhiyun 	struct wm831x_status *drvdata = platform_get_drvdata(pdev);
286*4882a593Smuzhiyun 
287*4882a593Smuzhiyun 	led_classdev_unregister(&drvdata->cdev);
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun 	return 0;
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun 
292*4882a593Smuzhiyun static struct platform_driver wm831x_status_driver = {
293*4882a593Smuzhiyun 	.driver = {
294*4882a593Smuzhiyun 		   .name = "wm831x-status",
295*4882a593Smuzhiyun 		   },
296*4882a593Smuzhiyun 	.probe = wm831x_status_probe,
297*4882a593Smuzhiyun 	.remove = wm831x_status_remove,
298*4882a593Smuzhiyun };
299*4882a593Smuzhiyun 
300*4882a593Smuzhiyun module_platform_driver(wm831x_status_driver);
301*4882a593Smuzhiyun 
302*4882a593Smuzhiyun MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
303*4882a593Smuzhiyun MODULE_DESCRIPTION("WM831x status LED driver");
304*4882a593Smuzhiyun MODULE_LICENSE("GPL");
305*4882a593Smuzhiyun MODULE_ALIAS("platform:wm831x-status");
306