1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * LED Driver for Dialog DA9052 PMICs. |
4 | * |
5 | * Copyright(c) 2012 Dialog Semiconductor Ltd. |
6 | * |
7 | * Author: David Dajun Chen <dchen@diasemi.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/leds.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #include <linux/mfd/da9052/reg.h> |
17 | #include <linux/mfd/da9052/da9052.h> |
18 | #include <linux/mfd/da9052/pdata.h> |
19 | |
20 | #define DA9052_OPENDRAIN_OUTPUT 2 |
21 | #define DA9052_SET_HIGH_LVL_OUTPUT (1 << 3) |
22 | #define DA9052_MASK_UPPER_NIBBLE 0xF0 |
23 | #define DA9052_MASK_LOWER_NIBBLE 0x0F |
24 | #define DA9052_NIBBLE_SHIFT 4 |
25 | #define DA9052_MAX_BRIGHTNESS 0x5f |
26 | |
27 | struct da9052_led { |
28 | struct led_classdev cdev; |
29 | struct da9052 *da9052; |
30 | unsigned char led_index; |
31 | unsigned char id; |
32 | }; |
33 | |
34 | static unsigned char led_reg[] = { |
35 | DA9052_LED_CONT_4_REG, |
36 | DA9052_LED_CONT_5_REG, |
37 | }; |
38 | |
39 | static int da9052_set_led_brightness(struct da9052_led *led, |
40 | enum led_brightness brightness) |
41 | { |
42 | u8 val; |
43 | int error; |
44 | |
45 | val = (brightness & 0x7f) | DA9052_LED_CONT_DIM; |
46 | |
47 | error = da9052_reg_write(da9052: led->da9052, reg: led_reg[led->led_index], val); |
48 | if (error < 0) |
49 | dev_err(led->da9052->dev, "Failed to set led brightness, %d\n" , |
50 | error); |
51 | return error; |
52 | } |
53 | |
54 | static int da9052_led_set(struct led_classdev *led_cdev, |
55 | enum led_brightness value) |
56 | { |
57 | struct da9052_led *led = |
58 | container_of(led_cdev, struct da9052_led, cdev); |
59 | |
60 | return da9052_set_led_brightness(led, brightness: value); |
61 | } |
62 | |
63 | static int da9052_configure_leds(struct da9052 *da9052) |
64 | { |
65 | int error; |
66 | unsigned char register_value = DA9052_OPENDRAIN_OUTPUT |
67 | | DA9052_SET_HIGH_LVL_OUTPUT; |
68 | |
69 | error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG, |
70 | DA9052_MASK_LOWER_NIBBLE, |
71 | reg_val: register_value); |
72 | |
73 | if (error < 0) { |
74 | dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n" , |
75 | error); |
76 | return error; |
77 | } |
78 | |
79 | error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG, |
80 | DA9052_MASK_UPPER_NIBBLE, |
81 | reg_val: register_value << DA9052_NIBBLE_SHIFT); |
82 | if (error < 0) |
83 | dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n" , |
84 | error); |
85 | |
86 | return error; |
87 | } |
88 | |
89 | static int da9052_led_probe(struct platform_device *pdev) |
90 | { |
91 | struct da9052_pdata *pdata; |
92 | struct da9052 *da9052; |
93 | struct led_platform_data *pled; |
94 | struct da9052_led *led = NULL; |
95 | int error = -ENODEV; |
96 | int i; |
97 | |
98 | da9052 = dev_get_drvdata(dev: pdev->dev.parent); |
99 | pdata = dev_get_platdata(dev: da9052->dev); |
100 | if (pdata == NULL) { |
101 | dev_err(&pdev->dev, "No platform data\n" ); |
102 | goto err; |
103 | } |
104 | |
105 | pled = pdata->pled; |
106 | if (pled == NULL) { |
107 | dev_err(&pdev->dev, "No platform data for LED\n" ); |
108 | goto err; |
109 | } |
110 | |
111 | led = devm_kcalloc(dev: &pdev->dev, |
112 | n: pled->num_leds, size: sizeof(struct da9052_led), |
113 | GFP_KERNEL); |
114 | if (!led) { |
115 | error = -ENOMEM; |
116 | goto err; |
117 | } |
118 | |
119 | for (i = 0; i < pled->num_leds; i++) { |
120 | led[i].cdev.name = pled->leds[i].name; |
121 | led[i].cdev.brightness_set_blocking = da9052_led_set; |
122 | led[i].cdev.brightness = LED_OFF; |
123 | led[i].cdev.max_brightness = DA9052_MAX_BRIGHTNESS; |
124 | led[i].led_index = pled->leds[i].flags; |
125 | led[i].da9052 = dev_get_drvdata(dev: pdev->dev.parent); |
126 | |
127 | error = led_classdev_register(parent: pdev->dev.parent, led_cdev: &led[i].cdev); |
128 | if (error) { |
129 | dev_err(&pdev->dev, "Failed to register led %d\n" , |
130 | led[i].led_index); |
131 | goto err_register; |
132 | } |
133 | |
134 | error = da9052_set_led_brightness(led: &led[i], |
135 | brightness: led[i].cdev.brightness); |
136 | if (error) { |
137 | dev_err(&pdev->dev, "Unable to init led %d\n" , |
138 | led[i].led_index); |
139 | continue; |
140 | } |
141 | } |
142 | error = da9052_configure_leds(da9052: led->da9052); |
143 | if (error) { |
144 | dev_err(&pdev->dev, "Failed to configure GPIO LED%d\n" , error); |
145 | goto err_register; |
146 | } |
147 | |
148 | platform_set_drvdata(pdev, data: led); |
149 | |
150 | return 0; |
151 | |
152 | err_register: |
153 | for (i = i - 1; i >= 0; i--) |
154 | led_classdev_unregister(led_cdev: &led[i].cdev); |
155 | err: |
156 | return error; |
157 | } |
158 | |
159 | static void da9052_led_remove(struct platform_device *pdev) |
160 | { |
161 | struct da9052_led *led = platform_get_drvdata(pdev); |
162 | struct da9052_pdata *pdata; |
163 | struct da9052 *da9052; |
164 | struct led_platform_data *pled; |
165 | int i; |
166 | |
167 | da9052 = dev_get_drvdata(dev: pdev->dev.parent); |
168 | pdata = dev_get_platdata(dev: da9052->dev); |
169 | pled = pdata->pled; |
170 | |
171 | for (i = 0; i < pled->num_leds; i++) { |
172 | da9052_set_led_brightness(led: &led[i], brightness: LED_OFF); |
173 | led_classdev_unregister(led_cdev: &led[i].cdev); |
174 | } |
175 | } |
176 | |
177 | static struct platform_driver da9052_led_driver = { |
178 | .driver = { |
179 | .name = "da9052-leds" , |
180 | }, |
181 | .probe = da9052_led_probe, |
182 | .remove_new = da9052_led_remove, |
183 | }; |
184 | |
185 | module_platform_driver(da9052_led_driver); |
186 | |
187 | MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>" ); |
188 | MODULE_DESCRIPTION("LED driver for Dialog DA9052 PMIC" ); |
189 | MODULE_LICENSE("GPL" ); |
190 | |