1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sky81452-backlight.c SKY81452 backlight driver |
4 | * |
5 | * Copyright 2014 Skyworks Solutions Inc. |
6 | * Author : Gyungoh Yoo <jack.yoo@skyworksinc.com> |
7 | */ |
8 | |
9 | #include <linux/backlight.h> |
10 | #include <linux/err.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/init.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/slab.h> |
19 | |
20 | /* registers */ |
21 | #define SKY81452_REG0 0x00 |
22 | #define SKY81452_REG1 0x01 |
23 | #define SKY81452_REG2 0x02 |
24 | #define SKY81452_REG4 0x04 |
25 | #define SKY81452_REG5 0x05 |
26 | |
27 | /* bit mask */ |
28 | #define SKY81452_CS 0xFF |
29 | #define SKY81452_EN 0x3F |
30 | #define SKY81452_IGPW 0x20 |
31 | #define SKY81452_PWMMD 0x10 |
32 | #define SKY81452_PHASE 0x08 |
33 | #define SKY81452_ILIM 0x04 |
34 | #define SKY81452_VSHRT 0x03 |
35 | #define SKY81452_OCP 0x80 |
36 | #define SKY81452_OTMP 0x40 |
37 | #define SKY81452_SHRT 0x3F |
38 | #define SKY81452_OPN 0x3F |
39 | |
40 | #define SKY81452_DEFAULT_NAME "lcd-backlight" |
41 | #define SKY81452_MAX_BRIGHTNESS (SKY81452_CS + 1) |
42 | |
43 | /** |
44 | * struct sky81452_bl_platform_data - backlight platform data |
45 | * @name: backlight driver name. |
46 | * If it is not defined, default name is lcd-backlight. |
47 | * @gpiod_enable:GPIO descriptor which control EN pin |
48 | * @enable: Enable mask for current sink channel 1, 2, 3, 4, 5 and 6. |
49 | * @ignore_pwm: true if DPWMI should be ignored. |
50 | * @dpwm_mode: true is DPWM dimming mode, otherwise Analog dimming mode. |
51 | * @phase_shift:true is phase shift mode. |
52 | * @short_detection_threshold: It should be one of 4, 5, 6 and 7V. |
53 | * @boost_current_limit: It should be one of 2300, 2750mA. |
54 | */ |
55 | struct sky81452_bl_platform_data { |
56 | const char *name; |
57 | struct gpio_desc *gpiod_enable; |
58 | unsigned int enable; |
59 | bool ignore_pwm; |
60 | bool dpwm_mode; |
61 | bool phase_shift; |
62 | unsigned int short_detection_threshold; |
63 | unsigned int boost_current_limit; |
64 | }; |
65 | |
66 | #define CTZ(b) __builtin_ctz(b) |
67 | |
68 | static int sky81452_bl_update_status(struct backlight_device *bd) |
69 | { |
70 | const struct sky81452_bl_platform_data *pdata = |
71 | dev_get_platdata(dev: bd->dev.parent); |
72 | const unsigned int brightness = (unsigned int)bd->props.brightness; |
73 | struct regmap *regmap = bl_get_data(bl_dev: bd); |
74 | int ret; |
75 | |
76 | if (brightness > 0) { |
77 | ret = regmap_write(map: regmap, SKY81452_REG0, val: brightness - 1); |
78 | if (ret < 0) |
79 | return ret; |
80 | |
81 | return regmap_update_bits(map: regmap, SKY81452_REG1, SKY81452_EN, |
82 | val: pdata->enable << CTZ(SKY81452_EN)); |
83 | } |
84 | |
85 | return regmap_update_bits(map: regmap, SKY81452_REG1, SKY81452_EN, val: 0); |
86 | } |
87 | |
88 | static const struct backlight_ops sky81452_bl_ops = { |
89 | .update_status = sky81452_bl_update_status, |
90 | }; |
91 | |
92 | static ssize_t sky81452_bl_store_enable(struct device *dev, |
93 | struct device_attribute *attr, const char *buf, size_t count) |
94 | { |
95 | struct regmap *regmap = bl_get_data(to_backlight_device(dev)); |
96 | unsigned long value; |
97 | int ret; |
98 | |
99 | ret = kstrtoul(s: buf, base: 16, res: &value); |
100 | if (ret < 0) |
101 | return ret; |
102 | |
103 | ret = regmap_update_bits(map: regmap, SKY81452_REG1, SKY81452_EN, |
104 | val: value << CTZ(SKY81452_EN)); |
105 | if (ret < 0) |
106 | return ret; |
107 | |
108 | return count; |
109 | } |
110 | |
111 | static ssize_t sky81452_bl_show_open_short(struct device *dev, |
112 | struct device_attribute *attr, char *buf) |
113 | { |
114 | struct regmap *regmap = bl_get_data(to_backlight_device(dev)); |
115 | unsigned int reg, value = 0; |
116 | char tmp[3]; |
117 | int i, ret; |
118 | |
119 | reg = !strcmp(attr->attr.name, "open" ) ? SKY81452_REG5 : SKY81452_REG4; |
120 | ret = regmap_read(map: regmap, reg, val: &value); |
121 | if (ret < 0) |
122 | return ret; |
123 | |
124 | if (value & SKY81452_SHRT) { |
125 | *buf = 0; |
126 | for (i = 0; i < 6; i++) { |
127 | if (value & 0x01) { |
128 | sprintf(buf: tmp, fmt: "%d " , i + 1); |
129 | strcat(p: buf, q: tmp); |
130 | } |
131 | value >>= 1; |
132 | } |
133 | strcat(p: buf, q: "\n" ); |
134 | } else { |
135 | strcpy(p: buf, q: "none\n" ); |
136 | } |
137 | |
138 | return strlen(buf); |
139 | } |
140 | |
141 | static ssize_t sky81452_bl_show_fault(struct device *dev, |
142 | struct device_attribute *attr, char *buf) |
143 | { |
144 | struct regmap *regmap = bl_get_data(to_backlight_device(dev)); |
145 | unsigned int value = 0; |
146 | int ret; |
147 | |
148 | ret = regmap_read(map: regmap, SKY81452_REG4, val: &value); |
149 | if (ret < 0) |
150 | return ret; |
151 | |
152 | *buf = 0; |
153 | |
154 | if (value & SKY81452_OCP) |
155 | strcat(p: buf, q: "over-current " ); |
156 | |
157 | if (value & SKY81452_OTMP) |
158 | strcat(p: buf, q: "over-temperature" ); |
159 | |
160 | strcat(p: buf, q: "\n" ); |
161 | return strlen(buf); |
162 | } |
163 | |
164 | static DEVICE_ATTR(enable, S_IWGRP | S_IWUSR, NULL, sky81452_bl_store_enable); |
165 | static DEVICE_ATTR(open, S_IRUGO, sky81452_bl_show_open_short, NULL); |
166 | static DEVICE_ATTR(short, S_IRUGO, sky81452_bl_show_open_short, NULL); |
167 | static DEVICE_ATTR(fault, S_IRUGO, sky81452_bl_show_fault, NULL); |
168 | |
169 | static struct attribute *sky81452_bl_attribute[] = { |
170 | &dev_attr_enable.attr, |
171 | &dev_attr_open.attr, |
172 | &dev_attr_short.attr, |
173 | &dev_attr_fault.attr, |
174 | NULL |
175 | }; |
176 | |
177 | static const struct attribute_group sky81452_bl_attr_group = { |
178 | .attrs = sky81452_bl_attribute, |
179 | }; |
180 | |
181 | #ifdef CONFIG_OF |
182 | static struct sky81452_bl_platform_data *sky81452_bl_parse_dt( |
183 | struct device *dev) |
184 | { |
185 | struct device_node *np = of_node_get(node: dev->of_node); |
186 | struct sky81452_bl_platform_data *pdata; |
187 | int num_entry; |
188 | unsigned int sources[6]; |
189 | int ret; |
190 | |
191 | if (!np) { |
192 | dev_err(dev, "backlight node not found.\n" ); |
193 | return ERR_PTR(error: -ENODATA); |
194 | } |
195 | |
196 | pdata = devm_kzalloc(dev, size: sizeof(*pdata), GFP_KERNEL); |
197 | if (!pdata) { |
198 | of_node_put(node: np); |
199 | return ERR_PTR(error: -ENOMEM); |
200 | } |
201 | |
202 | of_property_read_string(np, propname: "name" , out_string: &pdata->name); |
203 | pdata->ignore_pwm = of_property_read_bool(np, propname: "skyworks,ignore-pwm" ); |
204 | pdata->dpwm_mode = of_property_read_bool(np, propname: "skyworks,dpwm-mode" ); |
205 | pdata->phase_shift = of_property_read_bool(np, propname: "skyworks,phase-shift" ); |
206 | pdata->gpiod_enable = devm_gpiod_get_optional(dev, NULL, flags: GPIOD_OUT_HIGH); |
207 | |
208 | ret = of_property_count_u32_elems(np, propname: "led-sources" ); |
209 | if (ret < 0) { |
210 | pdata->enable = SKY81452_EN >> CTZ(SKY81452_EN); |
211 | } else { |
212 | num_entry = ret; |
213 | if (num_entry > 6) |
214 | num_entry = 6; |
215 | |
216 | ret = of_property_read_u32_array(np, propname: "led-sources" , out_values: sources, |
217 | sz: num_entry); |
218 | if (ret < 0) { |
219 | dev_err(dev, "led-sources node is invalid.\n" ); |
220 | of_node_put(node: np); |
221 | return ERR_PTR(error: -EINVAL); |
222 | } |
223 | |
224 | pdata->enable = 0; |
225 | while (--num_entry) |
226 | pdata->enable |= (1 << sources[num_entry]); |
227 | } |
228 | |
229 | ret = of_property_read_u32(np, |
230 | propname: "skyworks,short-detection-threshold-volt" , |
231 | out_value: &pdata->short_detection_threshold); |
232 | if (ret < 0) |
233 | pdata->short_detection_threshold = 7; |
234 | |
235 | ret = of_property_read_u32(np, propname: "skyworks,current-limit-mA" , |
236 | out_value: &pdata->boost_current_limit); |
237 | if (ret < 0) |
238 | pdata->boost_current_limit = 2750; |
239 | |
240 | of_node_put(node: np); |
241 | return pdata; |
242 | } |
243 | #else |
244 | static struct sky81452_bl_platform_data *sky81452_bl_parse_dt( |
245 | struct device *dev) |
246 | { |
247 | return ERR_PTR(-EINVAL); |
248 | } |
249 | #endif |
250 | |
251 | static int sky81452_bl_init_device(struct regmap *regmap, |
252 | struct sky81452_bl_platform_data *pdata) |
253 | { |
254 | unsigned int value; |
255 | |
256 | value = pdata->ignore_pwm ? SKY81452_IGPW : 0; |
257 | value |= pdata->dpwm_mode ? SKY81452_PWMMD : 0; |
258 | value |= pdata->phase_shift ? 0 : SKY81452_PHASE; |
259 | |
260 | if (pdata->boost_current_limit == 2300) |
261 | value |= SKY81452_ILIM; |
262 | else if (pdata->boost_current_limit != 2750) |
263 | return -EINVAL; |
264 | |
265 | if (pdata->short_detection_threshold < 4 || |
266 | pdata->short_detection_threshold > 7) |
267 | return -EINVAL; |
268 | value |= (7 - pdata->short_detection_threshold) << CTZ(SKY81452_VSHRT); |
269 | |
270 | return regmap_write(map: regmap, SKY81452_REG2, val: value); |
271 | } |
272 | |
273 | static int sky81452_bl_probe(struct platform_device *pdev) |
274 | { |
275 | struct device *dev = &pdev->dev; |
276 | struct regmap *regmap = dev_get_drvdata(dev: dev->parent); |
277 | struct sky81452_bl_platform_data *pdata; |
278 | struct backlight_device *bd; |
279 | struct backlight_properties props; |
280 | const char *name; |
281 | int ret; |
282 | |
283 | pdata = sky81452_bl_parse_dt(dev); |
284 | if (IS_ERR(ptr: pdata)) |
285 | return PTR_ERR(ptr: pdata); |
286 | |
287 | ret = sky81452_bl_init_device(regmap, pdata); |
288 | if (ret < 0) { |
289 | dev_err(dev, "failed to initialize. err=%d\n" , ret); |
290 | return ret; |
291 | } |
292 | |
293 | memset(&props, 0, sizeof(props)); |
294 | props.max_brightness = SKY81452_MAX_BRIGHTNESS; |
295 | name = pdata->name ? pdata->name : SKY81452_DEFAULT_NAME; |
296 | bd = devm_backlight_device_register(dev, name, parent: dev, devdata: regmap, |
297 | ops: &sky81452_bl_ops, props: &props); |
298 | if (IS_ERR(ptr: bd)) { |
299 | dev_err(dev, "failed to register. err=%ld\n" , PTR_ERR(bd)); |
300 | return PTR_ERR(ptr: bd); |
301 | } |
302 | |
303 | platform_set_drvdata(pdev, data: bd); |
304 | |
305 | ret = sysfs_create_group(kobj: &bd->dev.kobj, grp: &sky81452_bl_attr_group); |
306 | if (ret < 0) { |
307 | dev_err(dev, "failed to create attribute. err=%d\n" , ret); |
308 | return ret; |
309 | } |
310 | |
311 | return ret; |
312 | } |
313 | |
314 | static void sky81452_bl_remove(struct platform_device *pdev) |
315 | { |
316 | const struct sky81452_bl_platform_data *pdata = |
317 | dev_get_platdata(dev: &pdev->dev); |
318 | struct backlight_device *bd = platform_get_drvdata(pdev); |
319 | |
320 | sysfs_remove_group(kobj: &bd->dev.kobj, grp: &sky81452_bl_attr_group); |
321 | |
322 | bd->props.power = FB_BLANK_UNBLANK; |
323 | bd->props.brightness = 0; |
324 | backlight_update_status(bd); |
325 | |
326 | if (pdata->gpiod_enable) |
327 | gpiod_set_value_cansleep(desc: pdata->gpiod_enable, value: 0); |
328 | } |
329 | |
330 | #ifdef CONFIG_OF |
331 | static const struct of_device_id sky81452_bl_of_match[] = { |
332 | { .compatible = "skyworks,sky81452-backlight" , }, |
333 | { } |
334 | }; |
335 | MODULE_DEVICE_TABLE(of, sky81452_bl_of_match); |
336 | #endif |
337 | |
338 | static struct platform_driver sky81452_bl_driver = { |
339 | .driver = { |
340 | .name = "sky81452-backlight" , |
341 | .of_match_table = of_match_ptr(sky81452_bl_of_match), |
342 | }, |
343 | .probe = sky81452_bl_probe, |
344 | .remove_new = sky81452_bl_remove, |
345 | }; |
346 | |
347 | module_platform_driver(sky81452_bl_driver); |
348 | |
349 | MODULE_DESCRIPTION("Skyworks SKY81452 backlight driver" ); |
350 | MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@skyworksinc.com>" ); |
351 | MODULE_LICENSE("GPL v2" ); |
352 | |