]>
Commit | Line | Data |
---|---|---|
e739c5bb OD |
1 | /* |
2 | * Backlight driver for ArcticSand ARC_X_C_0N_0N Devices | |
3 | * | |
4 | * Copyright 2016 ArcticSand, Inc. | |
5 | * Author : Brian Dodge <bdodge@arcticsand.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 | |
9 | * as published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along | |
17 | * with this program; if not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <linux/backlight.h> | |
21 | #include <linux/err.h> | |
22 | #include <linux/i2c.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/of.h> | |
25 | #include <linux/slab.h> | |
26 | ||
27 | enum arcxcnn_chip_id { | |
28 | ARC2C0608 | |
29 | }; | |
30 | ||
31 | /** | |
32 | * struct arcxcnn_platform_data | |
33 | * @name : Backlight driver name (NULL will use default) | |
34 | * @initial_brightness : initial value of backlight brightness | |
35 | * @leden : initial LED string enables, upper bit is global on/off | |
36 | * @led_config_0 : fading speed (period between intensity steps) | |
37 | * @led_config_1 : misc settings, see datasheet | |
38 | * @dim_freq : pwm dimming frequency if in pwm mode | |
39 | * @comp_config : misc config, see datasheet | |
40 | * @filter_config : RC/PWM filter config, see datasheet | |
41 | * @trim_config : full scale current trim, see datasheet | |
42 | */ | |
43 | struct arcxcnn_platform_data { | |
44 | const char *name; | |
45 | u16 initial_brightness; | |
46 | u8 leden; | |
47 | u8 led_config_0; | |
48 | u8 led_config_1; | |
49 | u8 dim_freq; | |
50 | u8 comp_config; | |
51 | u8 filter_config; | |
52 | u8 trim_config; | |
53 | }; | |
54 | ||
55 | #define ARCXCNN_CMD 0x00 /* Command Register */ | |
56 | #define ARCXCNN_CMD_STDBY 0x80 /* I2C Standby */ | |
57 | #define ARCXCNN_CMD_RESET 0x40 /* Reset */ | |
58 | #define ARCXCNN_CMD_BOOST 0x10 /* Boost */ | |
59 | #define ARCXCNN_CMD_OVP_MASK 0x0C /* --- Over Voltage Threshold */ | |
60 | #define ARCXCNN_CMD_OVP_XXV 0x0C /* <rsvrd> Over Voltage Threshold */ | |
61 | #define ARCXCNN_CMD_OVP_20V 0x08 /* 20v Over Voltage Threshold */ | |
62 | #define ARCXCNN_CMD_OVP_24V 0x04 /* 24v Over Voltage Threshold */ | |
63 | #define ARCXCNN_CMD_OVP_31V 0x00 /* 31.4v Over Voltage Threshold */ | |
64 | #define ARCXCNN_CMD_EXT_COMP 0x01 /* part (0) or full (1) ext. comp */ | |
65 | ||
66 | #define ARCXCNN_CONFIG 0x01 /* Configuration */ | |
67 | #define ARCXCNN_STATUS1 0x02 /* Status 1 */ | |
68 | #define ARCXCNN_STATUS2 0x03 /* Status 2 */ | |
69 | #define ARCXCNN_FADECTRL 0x04 /* Fading Control */ | |
70 | #define ARCXCNN_ILED_CONFIG 0x05 /* ILED Configuration */ | |
71 | #define ARCXCNN_ILED_DIM_PWM 0x00 /* config dim mode pwm */ | |
72 | #define ARCXCNN_ILED_DIM_INT 0x04 /* config dim mode internal */ | |
73 | #define ARCXCNN_LEDEN 0x06 /* LED Enable Register */ | |
74 | #define ARCXCNN_LEDEN_ISETEXT 0x80 /* Full-scale current set extern */ | |
75 | #define ARCXCNN_LEDEN_MASK 0x3F /* LED string enables mask */ | |
76 | #define ARCXCNN_LEDEN_BITS 0x06 /* Bits of LED string enables */ | |
77 | #define ARCXCNN_LEDEN_LED1 0x01 | |
78 | #define ARCXCNN_LEDEN_LED2 0x02 | |
79 | #define ARCXCNN_LEDEN_LED3 0x04 | |
80 | #define ARCXCNN_LEDEN_LED4 0x08 | |
81 | #define ARCXCNN_LEDEN_LED5 0x10 | |
82 | #define ARCXCNN_LEDEN_LED6 0x20 | |
83 | ||
84 | #define ARCXCNN_WLED_ISET_LSB 0x07 /* LED ISET LSB (in upper nibble) */ | |
85 | #define ARCXCNN_WLED_ISET_LSB_SHIFT 0x04 /* ISET LSB Left Shift */ | |
86 | #define ARCXCNN_WLED_ISET_MSB 0x08 /* LED ISET MSB (8 bits) */ | |
87 | ||
88 | #define ARCXCNN_DIMFREQ 0x09 | |
89 | #define ARCXCNN_COMP_CONFIG 0x0A | |
90 | #define ARCXCNN_FILT_CONFIG 0x0B | |
91 | #define ARCXCNN_IMAXTUNE 0x0C | |
92 | #define ARCXCNN_ID_MSB 0x1E | |
93 | #define ARCXCNN_ID_LSB 0x1F | |
94 | ||
95 | #define MAX_BRIGHTNESS 4095 | |
96 | #define INIT_BRIGHT 60 | |
97 | ||
98 | struct arcxcnn { | |
99 | struct i2c_client *client; | |
100 | struct backlight_device *bl; | |
101 | struct device *dev; | |
102 | struct arcxcnn_platform_data *pdata; | |
103 | }; | |
104 | ||
105 | static int arcxcnn_update_field(struct arcxcnn *lp, u8 reg, u8 mask, u8 data) | |
106 | { | |
107 | int ret; | |
108 | u8 tmp; | |
109 | ||
110 | ret = i2c_smbus_read_byte_data(lp->client, reg); | |
111 | if (ret < 0) { | |
112 | dev_err(lp->dev, "failed to read 0x%.2x\n", reg); | |
113 | return ret; | |
114 | } | |
115 | ||
116 | tmp = (u8)ret; | |
117 | tmp &= ~mask; | |
118 | tmp |= data & mask; | |
119 | ||
120 | return i2c_smbus_write_byte_data(lp->client, reg, tmp); | |
121 | } | |
122 | ||
123 | static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness) | |
124 | { | |
125 | int ret; | |
126 | u8 val; | |
127 | ||
128 | /* lower nibble of brightness goes in upper nibble of LSB register */ | |
129 | val = (brightness & 0xF) << ARCXCNN_WLED_ISET_LSB_SHIFT; | |
130 | ret = i2c_smbus_write_byte_data(lp->client, | |
131 | ARCXCNN_WLED_ISET_LSB, val); | |
132 | if (ret < 0) | |
133 | return ret; | |
134 | ||
135 | /* remaining 8 bits of brightness go in MSB register */ | |
136 | val = (brightness >> 4); | |
137 | return i2c_smbus_write_byte_data(lp->client, | |
138 | ARCXCNN_WLED_ISET_MSB, val); | |
139 | } | |
140 | ||
141 | static int arcxcnn_bl_update_status(struct backlight_device *bl) | |
142 | { | |
143 | struct arcxcnn *lp = bl_get_data(bl); | |
144 | u32 brightness = bl->props.brightness; | |
145 | int ret; | |
146 | ||
147 | if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) | |
148 | brightness = 0; | |
149 | ||
150 | ret = arcxcnn_set_brightness(lp, brightness); | |
151 | if (ret) | |
152 | return ret; | |
153 | ||
154 | /* set power-on/off/save modes */ | |
155 | return arcxcnn_update_field(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, | |
156 | (bl->props.power == 0) ? 0 : ARCXCNN_CMD_STDBY); | |
157 | } | |
158 | ||
159 | static const struct backlight_ops arcxcnn_bl_ops = { | |
160 | .options = BL_CORE_SUSPENDRESUME, | |
161 | .update_status = arcxcnn_bl_update_status, | |
162 | }; | |
163 | ||
164 | static int arcxcnn_backlight_register(struct arcxcnn *lp) | |
165 | { | |
166 | struct backlight_properties *props; | |
167 | const char *name = lp->pdata->name ? : "arctic_bl"; | |
168 | ||
169 | props = devm_kzalloc(lp->dev, sizeof(*props), GFP_KERNEL); | |
170 | if (!props) | |
171 | return -ENOMEM; | |
172 | ||
173 | props->type = BACKLIGHT_PLATFORM; | |
174 | props->max_brightness = MAX_BRIGHTNESS; | |
175 | ||
176 | if (lp->pdata->initial_brightness > props->max_brightness) | |
177 | lp->pdata->initial_brightness = props->max_brightness; | |
178 | ||
179 | props->brightness = lp->pdata->initial_brightness; | |
180 | ||
181 | lp->bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp, | |
182 | &arcxcnn_bl_ops, props); | |
183 | return PTR_ERR_OR_ZERO(lp->bl); | |
184 | } | |
185 | ||
186 | static void arcxcnn_parse_dt(struct arcxcnn *lp) | |
187 | { | |
188 | struct device *dev = lp->dev; | |
189 | struct device_node *node = dev->of_node; | |
190 | u32 prog_val, num_entry, entry, sources[ARCXCNN_LEDEN_BITS]; | |
191 | int ret; | |
192 | ||
193 | /* device tree entry isn't required, defaults are OK */ | |
194 | if (!node) | |
195 | return; | |
196 | ||
197 | ret = of_property_read_string(node, "label", &lp->pdata->name); | |
198 | if (ret < 0) | |
199 | lp->pdata->name = NULL; | |
200 | ||
201 | ret = of_property_read_u32(node, "default-brightness", &prog_val); | |
202 | if (ret == 0) | |
203 | lp->pdata->initial_brightness = prog_val; | |
204 | ||
205 | ret = of_property_read_u32(node, "arc,led-config-0", &prog_val); | |
206 | if (ret == 0) | |
207 | lp->pdata->led_config_0 = (u8)prog_val; | |
208 | ||
209 | ret = of_property_read_u32(node, "arc,led-config-1", &prog_val); | |
210 | if (ret == 0) | |
211 | lp->pdata->led_config_1 = (u8)prog_val; | |
212 | ||
213 | ret = of_property_read_u32(node, "arc,dim-freq", &prog_val); | |
214 | if (ret == 0) | |
215 | lp->pdata->dim_freq = (u8)prog_val; | |
216 | ||
217 | ret = of_property_read_u32(node, "arc,comp-config", &prog_val); | |
218 | if (ret == 0) | |
219 | lp->pdata->comp_config = (u8)prog_val; | |
220 | ||
221 | ret = of_property_read_u32(node, "arc,filter-config", &prog_val); | |
222 | if (ret == 0) | |
223 | lp->pdata->filter_config = (u8)prog_val; | |
224 | ||
225 | ret = of_property_read_u32(node, "arc,trim-config", &prog_val); | |
226 | if (ret == 0) | |
227 | lp->pdata->trim_config = (u8)prog_val; | |
228 | ||
229 | ret = of_property_count_u32_elems(node, "led-sources"); | |
230 | if (ret < 0) { | |
231 | lp->pdata->leden = ARCXCNN_LEDEN_MASK; /* all on is default */ | |
232 | } else { | |
233 | num_entry = ret; | |
234 | if (num_entry > ARCXCNN_LEDEN_BITS) | |
235 | num_entry = ARCXCNN_LEDEN_BITS; | |
236 | ||
237 | ret = of_property_read_u32_array(node, "led-sources", sources, | |
238 | num_entry); | |
239 | if (ret < 0) { | |
240 | dev_err(dev, "led-sources node is invalid.\n"); | |
241 | return; | |
242 | } | |
243 | ||
244 | lp->pdata->leden = 0; | |
245 | ||
246 | /* for each enable in source, set bit in led enable */ | |
247 | for (entry = 0; entry < num_entry; entry++) { | |
248 | u8 onbit = 1 << sources[entry]; | |
249 | ||
250 | lp->pdata->leden |= onbit; | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id) | |
256 | { | |
257 | struct arcxcnn *lp; | |
258 | int ret; | |
259 | ||
260 | if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | |
261 | return -EIO; | |
262 | ||
263 | lp = devm_kzalloc(&cl->dev, sizeof(*lp), GFP_KERNEL); | |
264 | if (!lp) | |
265 | return -ENOMEM; | |
266 | ||
267 | lp->client = cl; | |
268 | lp->dev = &cl->dev; | |
269 | lp->pdata = dev_get_platdata(&cl->dev); | |
270 | ||
271 | /* reset the device */ | |
272 | ret = i2c_smbus_write_byte_data(lp->client, | |
273 | ARCXCNN_CMD, ARCXCNN_CMD_RESET); | |
274 | if (ret) | |
275 | goto probe_err; | |
276 | ||
277 | if (!lp->pdata) { | |
278 | lp->pdata = devm_kzalloc(lp->dev, | |
279 | sizeof(*lp->pdata), GFP_KERNEL); | |
280 | if (!lp->pdata) | |
281 | return -ENOMEM; | |
282 | ||
283 | /* Setup defaults based on power-on defaults */ | |
284 | lp->pdata->name = NULL; | |
285 | lp->pdata->initial_brightness = INIT_BRIGHT; | |
286 | lp->pdata->leden = ARCXCNN_LEDEN_MASK; | |
287 | ||
288 | lp->pdata->led_config_0 = i2c_smbus_read_byte_data( | |
289 | lp->client, ARCXCNN_FADECTRL); | |
290 | ||
291 | lp->pdata->led_config_1 = i2c_smbus_read_byte_data( | |
292 | lp->client, ARCXCNN_ILED_CONFIG); | |
293 | /* insure dim mode is not default pwm */ | |
294 | lp->pdata->led_config_1 |= ARCXCNN_ILED_DIM_INT; | |
295 | ||
296 | lp->pdata->dim_freq = i2c_smbus_read_byte_data( | |
297 | lp->client, ARCXCNN_DIMFREQ); | |
298 | ||
299 | lp->pdata->comp_config = i2c_smbus_read_byte_data( | |
300 | lp->client, ARCXCNN_COMP_CONFIG); | |
301 | ||
302 | lp->pdata->filter_config = i2c_smbus_read_byte_data( | |
303 | lp->client, ARCXCNN_FILT_CONFIG); | |
304 | ||
305 | lp->pdata->trim_config = i2c_smbus_read_byte_data( | |
306 | lp->client, ARCXCNN_IMAXTUNE); | |
307 | ||
308 | if (IS_ENABLED(CONFIG_OF)) | |
309 | arcxcnn_parse_dt(lp); | |
310 | } | |
311 | ||
312 | i2c_set_clientdata(cl, lp); | |
313 | ||
314 | /* constrain settings to what is possible */ | |
315 | if (lp->pdata->initial_brightness > MAX_BRIGHTNESS) | |
316 | lp->pdata->initial_brightness = MAX_BRIGHTNESS; | |
317 | ||
318 | /* set initial brightness */ | |
319 | ret = arcxcnn_set_brightness(lp, lp->pdata->initial_brightness); | |
320 | if (ret) | |
321 | goto probe_err; | |
322 | ||
323 | /* set other register values directly */ | |
324 | ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_FADECTRL, | |
325 | lp->pdata->led_config_0); | |
326 | if (ret) | |
327 | goto probe_err; | |
328 | ||
329 | ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_ILED_CONFIG, | |
330 | lp->pdata->led_config_1); | |
331 | if (ret) | |
332 | goto probe_err; | |
333 | ||
334 | ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_DIMFREQ, | |
335 | lp->pdata->dim_freq); | |
336 | if (ret) | |
337 | goto probe_err; | |
338 | ||
339 | ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_COMP_CONFIG, | |
340 | lp->pdata->comp_config); | |
341 | if (ret) | |
342 | goto probe_err; | |
343 | ||
344 | ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_FILT_CONFIG, | |
345 | lp->pdata->filter_config); | |
346 | if (ret) | |
347 | goto probe_err; | |
348 | ||
349 | ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_IMAXTUNE, | |
350 | lp->pdata->trim_config); | |
351 | if (ret) | |
352 | goto probe_err; | |
353 | ||
354 | /* set initial LED Enables */ | |
355 | arcxcnn_update_field(lp, ARCXCNN_LEDEN, | |
356 | ARCXCNN_LEDEN_MASK, lp->pdata->leden); | |
357 | ||
358 | ret = arcxcnn_backlight_register(lp); | |
359 | if (ret) | |
360 | goto probe_register_err; | |
361 | ||
362 | backlight_update_status(lp->bl); | |
363 | ||
364 | return 0; | |
365 | ||
366 | probe_register_err: | |
367 | dev_err(lp->dev, | |
368 | "failed to register backlight.\n"); | |
369 | ||
370 | probe_err: | |
371 | dev_err(lp->dev, | |
372 | "failure ret: %d\n", ret); | |
373 | return ret; | |
374 | } | |
375 | ||
376 | static int arcxcnn_remove(struct i2c_client *cl) | |
377 | { | |
378 | struct arcxcnn *lp = i2c_get_clientdata(cl); | |
379 | ||
380 | /* disable all strings (ignore errors) */ | |
381 | i2c_smbus_write_byte_data(lp->client, | |
382 | ARCXCNN_LEDEN, 0x00); | |
383 | /* reset the device (ignore errors) */ | |
384 | i2c_smbus_write_byte_data(lp->client, | |
385 | ARCXCNN_CMD, ARCXCNN_CMD_RESET); | |
386 | ||
387 | lp->bl->props.brightness = 0; | |
388 | ||
389 | backlight_update_status(lp->bl); | |
390 | ||
391 | return 0; | |
392 | } | |
393 | ||
394 | static const struct of_device_id arcxcnn_dt_ids[] = { | |
395 | { .compatible = "arc,arc2c0608" }, | |
396 | { } | |
397 | }; | |
398 | MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids); | |
399 | ||
400 | static const struct i2c_device_id arcxcnn_ids[] = { | |
401 | {"arc2c0608", ARC2C0608}, | |
402 | { } | |
403 | }; | |
404 | MODULE_DEVICE_TABLE(i2c, arcxcnn_ids); | |
405 | ||
406 | static struct i2c_driver arcxcnn_driver = { | |
407 | .driver = { | |
408 | .name = "arcxcnn_bl", | |
409 | .of_match_table = of_match_ptr(arcxcnn_dt_ids), | |
410 | }, | |
411 | .probe = arcxcnn_probe, | |
412 | .remove = arcxcnn_remove, | |
413 | .id_table = arcxcnn_ids, | |
414 | }; | |
415 | module_i2c_driver(arcxcnn_driver); | |
416 | ||
417 | MODULE_LICENSE("GPL v2"); | |
418 | MODULE_AUTHOR("Brian Dodge <bdodge@arcticsand.com>"); | |
419 | MODULE_DESCRIPTION("ARCXCNN Backlight driver"); |