1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * LEDs driver for Analog Devices ADP5520/ADP5501 MFD PMICs |
4 | * |
5 | * Copyright 2009 Analog Devices Inc. |
6 | * |
7 | * Loosely derived from leds-da903x: |
8 | * Copyright (C) 2008 Compulab, Ltd. |
9 | * Mike Rapoport <mike@compulab.co.il> |
10 | * |
11 | * Copyright (C) 2006-2008 Marvell International Ltd. |
12 | * Eric Miao <eric.miao@marvell.com> |
13 | */ |
14 | |
15 | #include <linux/module.h> |
16 | #include <linux/kernel.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/leds.h> |
19 | #include <linux/mfd/adp5520.h> |
20 | #include <linux/slab.h> |
21 | |
22 | struct adp5520_led { |
23 | struct led_classdev cdev; |
24 | struct device *master; |
25 | int id; |
26 | int flags; |
27 | }; |
28 | |
29 | static int adp5520_led_set(struct led_classdev *led_cdev, |
30 | enum led_brightness value) |
31 | { |
32 | struct adp5520_led *led; |
33 | |
34 | led = container_of(led_cdev, struct adp5520_led, cdev); |
35 | return adp5520_write(dev: led->master, ADP5520_LED1_CURRENT + led->id - 1, |
36 | val: value >> 2); |
37 | } |
38 | |
39 | static int adp5520_led_setup(struct adp5520_led *led) |
40 | { |
41 | struct device *dev = led->master; |
42 | int flags = led->flags; |
43 | int ret = 0; |
44 | |
45 | switch (led->id) { |
46 | case FLAG_ID_ADP5520_LED1_ADP5501_LED0: |
47 | ret |= adp5520_set_bits(dev, ADP5520_LED_TIME, |
48 | bit_mask: (flags >> ADP5520_FLAG_OFFT_SHIFT) & |
49 | ADP5520_FLAG_OFFT_MASK); |
50 | ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL, |
51 | ADP5520_LED1_EN); |
52 | break; |
53 | case FLAG_ID_ADP5520_LED2_ADP5501_LED1: |
54 | ret |= adp5520_set_bits(dev, ADP5520_LED_TIME, |
55 | bit_mask: ((flags >> ADP5520_FLAG_OFFT_SHIFT) & |
56 | ADP5520_FLAG_OFFT_MASK) << 2); |
57 | ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL, |
58 | ADP5520_R3_MODE); |
59 | ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL, |
60 | ADP5520_LED2_EN); |
61 | break; |
62 | case FLAG_ID_ADP5520_LED3_ADP5501_LED2: |
63 | ret |= adp5520_set_bits(dev, ADP5520_LED_TIME, |
64 | bit_mask: ((flags >> ADP5520_FLAG_OFFT_SHIFT) & |
65 | ADP5520_FLAG_OFFT_MASK) << 4); |
66 | ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL, |
67 | ADP5520_C3_MODE); |
68 | ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL, |
69 | ADP5520_LED3_EN); |
70 | break; |
71 | } |
72 | |
73 | return ret; |
74 | } |
75 | |
76 | static int adp5520_led_prepare(struct platform_device *pdev) |
77 | { |
78 | struct adp5520_leds_platform_data *pdata = dev_get_platdata(dev: &pdev->dev); |
79 | struct device *dev = pdev->dev.parent; |
80 | int ret = 0; |
81 | |
82 | ret |= adp5520_write(dev, ADP5520_LED1_CURRENT, val: 0); |
83 | ret |= adp5520_write(dev, ADP5520_LED2_CURRENT, val: 0); |
84 | ret |= adp5520_write(dev, ADP5520_LED3_CURRENT, val: 0); |
85 | ret |= adp5520_write(dev, ADP5520_LED_TIME, val: pdata->led_on_time << 6); |
86 | ret |= adp5520_write(dev, ADP5520_LED_FADE, FADE_VAL(pdata->fade_in, |
87 | pdata->fade_out)); |
88 | |
89 | return ret; |
90 | } |
91 | |
92 | static int adp5520_led_probe(struct platform_device *pdev) |
93 | { |
94 | struct adp5520_leds_platform_data *pdata = dev_get_platdata(dev: &pdev->dev); |
95 | struct adp5520_led *led, *led_dat; |
96 | struct led_info *cur_led; |
97 | int ret, i; |
98 | |
99 | if (pdata == NULL) { |
100 | dev_err(&pdev->dev, "missing platform data\n" ); |
101 | return -ENODEV; |
102 | } |
103 | |
104 | if (pdata->num_leds > ADP5520_01_MAXLEDS) { |
105 | dev_err(&pdev->dev, "can't handle more than %d LEDS\n" , |
106 | ADP5520_01_MAXLEDS); |
107 | return -EFAULT; |
108 | } |
109 | |
110 | led = devm_kcalloc(dev: &pdev->dev, n: pdata->num_leds, size: sizeof(*led), |
111 | GFP_KERNEL); |
112 | if (!led) |
113 | return -ENOMEM; |
114 | |
115 | ret = adp5520_led_prepare(pdev); |
116 | if (ret) { |
117 | dev_err(&pdev->dev, "failed to write\n" ); |
118 | return ret; |
119 | } |
120 | |
121 | for (i = 0; i < pdata->num_leds; ++i) { |
122 | cur_led = &pdata->leds[i]; |
123 | led_dat = &led[i]; |
124 | |
125 | led_dat->cdev.name = cur_led->name; |
126 | led_dat->cdev.default_trigger = cur_led->default_trigger; |
127 | led_dat->cdev.brightness_set_blocking = adp5520_led_set; |
128 | led_dat->cdev.brightness = LED_OFF; |
129 | |
130 | if (cur_led->flags & ADP5520_FLAG_LED_MASK) |
131 | led_dat->flags = cur_led->flags; |
132 | else |
133 | led_dat->flags = i + 1; |
134 | |
135 | led_dat->id = led_dat->flags & ADP5520_FLAG_LED_MASK; |
136 | |
137 | led_dat->master = pdev->dev.parent; |
138 | |
139 | ret = led_classdev_register(parent: led_dat->master, led_cdev: &led_dat->cdev); |
140 | if (ret) { |
141 | dev_err(&pdev->dev, "failed to register LED %d\n" , |
142 | led_dat->id); |
143 | goto err; |
144 | } |
145 | |
146 | ret = adp5520_led_setup(led: led_dat); |
147 | if (ret) { |
148 | dev_err(&pdev->dev, "failed to write\n" ); |
149 | i++; |
150 | goto err; |
151 | } |
152 | } |
153 | |
154 | platform_set_drvdata(pdev, data: led); |
155 | return 0; |
156 | |
157 | err: |
158 | if (i > 0) { |
159 | for (i = i - 1; i >= 0; i--) |
160 | led_classdev_unregister(led_cdev: &led[i].cdev); |
161 | } |
162 | |
163 | return ret; |
164 | } |
165 | |
166 | static void adp5520_led_remove(struct platform_device *pdev) |
167 | { |
168 | struct adp5520_leds_platform_data *pdata = dev_get_platdata(dev: &pdev->dev); |
169 | struct adp5520_led *led; |
170 | int i; |
171 | |
172 | led = platform_get_drvdata(pdev); |
173 | |
174 | adp5520_clr_bits(dev: led->master, ADP5520_LED_CONTROL, |
175 | ADP5520_LED1_EN | ADP5520_LED2_EN | ADP5520_LED3_EN); |
176 | |
177 | for (i = 0; i < pdata->num_leds; i++) { |
178 | led_classdev_unregister(led_cdev: &led[i].cdev); |
179 | } |
180 | } |
181 | |
182 | static struct platform_driver adp5520_led_driver = { |
183 | .driver = { |
184 | .name = "adp5520-led" , |
185 | }, |
186 | .probe = adp5520_led_probe, |
187 | .remove_new = adp5520_led_remove, |
188 | }; |
189 | |
190 | module_platform_driver(adp5520_led_driver); |
191 | |
192 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>" ); |
193 | MODULE_DESCRIPTION("LEDS ADP5520(01) Driver" ); |
194 | MODULE_LICENSE("GPL" ); |
195 | MODULE_ALIAS("platform:adp5520-led" ); |
196 | |