1 // SPDX-License-Identifier: GPL-2.0-only
3 * Backlight driver for ArcticSand ARC_X_C_0N_0N Devices
5 * Copyright 2016 ArcticSand, Inc.
6 * Author : Brian Dodge <bdodge@arcticsand.com>
9 #include <linux/backlight.h>
10 #include <linux/err.h>
11 #include <linux/i2c.h>
12 #include <linux/module.h>
14 #include <linux/slab.h>
16 enum arcxcnn_chip_id
{
21 * struct arcxcnn_platform_data
22 * @name : Backlight driver name (NULL will use default)
23 * @initial_brightness : initial value of backlight brightness
24 * @leden : initial LED string enables, upper bit is global on/off
25 * @led_config_0 : fading speed (period between intensity steps)
26 * @led_config_1 : misc settings, see datasheet
27 * @dim_freq : pwm dimming frequency if in pwm mode
28 * @comp_config : misc config, see datasheet
29 * @filter_config : RC/PWM filter config, see datasheet
30 * @trim_config : full scale current trim, see datasheet
32 struct arcxcnn_platform_data
{
34 u16 initial_brightness
;
44 #define ARCXCNN_CMD 0x00 /* Command Register */
45 #define ARCXCNN_CMD_STDBY 0x80 /* I2C Standby */
46 #define ARCXCNN_CMD_RESET 0x40 /* Reset */
47 #define ARCXCNN_CMD_BOOST 0x10 /* Boost */
48 #define ARCXCNN_CMD_OVP_MASK 0x0C /* --- Over Voltage Threshold */
49 #define ARCXCNN_CMD_OVP_XXV 0x0C /* <rsvrd> Over Voltage Threshold */
50 #define ARCXCNN_CMD_OVP_20V 0x08 /* 20v Over Voltage Threshold */
51 #define ARCXCNN_CMD_OVP_24V 0x04 /* 24v Over Voltage Threshold */
52 #define ARCXCNN_CMD_OVP_31V 0x00 /* 31.4v Over Voltage Threshold */
53 #define ARCXCNN_CMD_EXT_COMP 0x01 /* part (0) or full (1) ext. comp */
55 #define ARCXCNN_CONFIG 0x01 /* Configuration */
56 #define ARCXCNN_STATUS1 0x02 /* Status 1 */
57 #define ARCXCNN_STATUS2 0x03 /* Status 2 */
58 #define ARCXCNN_FADECTRL 0x04 /* Fading Control */
59 #define ARCXCNN_ILED_CONFIG 0x05 /* ILED Configuration */
60 #define ARCXCNN_ILED_DIM_PWM 0x00 /* config dim mode pwm */
61 #define ARCXCNN_ILED_DIM_INT 0x04 /* config dim mode internal */
62 #define ARCXCNN_LEDEN 0x06 /* LED Enable Register */
63 #define ARCXCNN_LEDEN_ISETEXT 0x80 /* Full-scale current set extern */
64 #define ARCXCNN_LEDEN_MASK 0x3F /* LED string enables mask */
65 #define ARCXCNN_LEDEN_BITS 0x06 /* Bits of LED string enables */
66 #define ARCXCNN_LEDEN_LED1 0x01
67 #define ARCXCNN_LEDEN_LED2 0x02
68 #define ARCXCNN_LEDEN_LED3 0x04
69 #define ARCXCNN_LEDEN_LED4 0x08
70 #define ARCXCNN_LEDEN_LED5 0x10
71 #define ARCXCNN_LEDEN_LED6 0x20
73 #define ARCXCNN_WLED_ISET_LSB 0x07 /* LED ISET LSB (in upper nibble) */
74 #define ARCXCNN_WLED_ISET_LSB_SHIFT 0x04 /* ISET LSB Left Shift */
75 #define ARCXCNN_WLED_ISET_MSB 0x08 /* LED ISET MSB (8 bits) */
77 #define ARCXCNN_DIMFREQ 0x09
78 #define ARCXCNN_COMP_CONFIG 0x0A
79 #define ARCXCNN_FILT_CONFIG 0x0B
80 #define ARCXCNN_IMAXTUNE 0x0C
81 #define ARCXCNN_ID_MSB 0x1E
82 #define ARCXCNN_ID_LSB 0x1F
84 #define MAX_BRIGHTNESS 4095
85 #define INIT_BRIGHT 60
88 struct i2c_client
*client
;
89 struct backlight_device
*bl
;
91 struct arcxcnn_platform_data
*pdata
;
94 static int arcxcnn_update_field(struct arcxcnn
*lp
, u8 reg
, u8 mask
, u8 data
)
99 ret
= i2c_smbus_read_byte_data(lp
->client
, reg
);
101 dev_err(lp
->dev
, "failed to read 0x%.2x\n", reg
);
109 return i2c_smbus_write_byte_data(lp
->client
, reg
, tmp
);
112 static int arcxcnn_set_brightness(struct arcxcnn
*lp
, u32 brightness
)
117 /* lower nibble of brightness goes in upper nibble of LSB register */
118 val
= (brightness
& 0xF) << ARCXCNN_WLED_ISET_LSB_SHIFT
;
119 ret
= i2c_smbus_write_byte_data(lp
->client
,
120 ARCXCNN_WLED_ISET_LSB
, val
);
124 /* remaining 8 bits of brightness go in MSB register */
125 val
= (brightness
>> 4);
126 return i2c_smbus_write_byte_data(lp
->client
,
127 ARCXCNN_WLED_ISET_MSB
, val
);
130 static int arcxcnn_bl_update_status(struct backlight_device
*bl
)
132 struct arcxcnn
*lp
= bl_get_data(bl
);
133 u32 brightness
= bl
->props
.brightness
;
136 if (bl
->props
.state
& (BL_CORE_SUSPENDED
| BL_CORE_FBBLANK
))
139 ret
= arcxcnn_set_brightness(lp
, brightness
);
143 /* set power-on/off/save modes */
144 return arcxcnn_update_field(lp
, ARCXCNN_CMD
, ARCXCNN_CMD_STDBY
,
145 (bl
->props
.power
== 0) ? 0 : ARCXCNN_CMD_STDBY
);
148 static const struct backlight_ops arcxcnn_bl_ops
= {
149 .options
= BL_CORE_SUSPENDRESUME
,
150 .update_status
= arcxcnn_bl_update_status
,
153 static int arcxcnn_backlight_register(struct arcxcnn
*lp
)
155 struct backlight_properties
*props
;
156 const char *name
= lp
->pdata
->name
? : "arctic_bl";
158 props
= devm_kzalloc(lp
->dev
, sizeof(*props
), GFP_KERNEL
);
162 props
->type
= BACKLIGHT_PLATFORM
;
163 props
->max_brightness
= MAX_BRIGHTNESS
;
165 if (lp
->pdata
->initial_brightness
> props
->max_brightness
)
166 lp
->pdata
->initial_brightness
= props
->max_brightness
;
168 props
->brightness
= lp
->pdata
->initial_brightness
;
170 lp
->bl
= devm_backlight_device_register(lp
->dev
, name
, lp
->dev
, lp
,
171 &arcxcnn_bl_ops
, props
);
172 return PTR_ERR_OR_ZERO(lp
->bl
);
175 static void arcxcnn_parse_dt(struct arcxcnn
*lp
)
177 struct device
*dev
= lp
->dev
;
178 struct device_node
*node
= dev
->of_node
;
179 u32 prog_val
, num_entry
, entry
, sources
[ARCXCNN_LEDEN_BITS
];
182 /* device tree entry isn't required, defaults are OK */
186 ret
= of_property_read_string(node
, "label", &lp
->pdata
->name
);
188 lp
->pdata
->name
= NULL
;
190 ret
= of_property_read_u32(node
, "default-brightness", &prog_val
);
192 lp
->pdata
->initial_brightness
= prog_val
;
194 ret
= of_property_read_u32(node
, "arc,led-config-0", &prog_val
);
196 lp
->pdata
->led_config_0
= (u8
)prog_val
;
198 ret
= of_property_read_u32(node
, "arc,led-config-1", &prog_val
);
200 lp
->pdata
->led_config_1
= (u8
)prog_val
;
202 ret
= of_property_read_u32(node
, "arc,dim-freq", &prog_val
);
204 lp
->pdata
->dim_freq
= (u8
)prog_val
;
206 ret
= of_property_read_u32(node
, "arc,comp-config", &prog_val
);
208 lp
->pdata
->comp_config
= (u8
)prog_val
;
210 ret
= of_property_read_u32(node
, "arc,filter-config", &prog_val
);
212 lp
->pdata
->filter_config
= (u8
)prog_val
;
214 ret
= of_property_read_u32(node
, "arc,trim-config", &prog_val
);
216 lp
->pdata
->trim_config
= (u8
)prog_val
;
218 ret
= of_property_count_u32_elems(node
, "led-sources");
220 lp
->pdata
->leden
= ARCXCNN_LEDEN_MASK
; /* all on is default */
223 if (num_entry
> ARCXCNN_LEDEN_BITS
)
224 num_entry
= ARCXCNN_LEDEN_BITS
;
226 ret
= of_property_read_u32_array(node
, "led-sources", sources
,
229 dev_err(dev
, "led-sources node is invalid.\n");
233 lp
->pdata
->leden
= 0;
235 /* for each enable in source, set bit in led enable */
236 for (entry
= 0; entry
< num_entry
; entry
++) {
237 u8 onbit
= 1 << sources
[entry
];
239 lp
->pdata
->leden
|= onbit
;
244 static int arcxcnn_probe(struct i2c_client
*cl
, const struct i2c_device_id
*id
)
249 if (!i2c_check_functionality(cl
->adapter
, I2C_FUNC_SMBUS_BYTE_DATA
))
252 lp
= devm_kzalloc(&cl
->dev
, sizeof(*lp
), GFP_KERNEL
);
258 lp
->pdata
= dev_get_platdata(&cl
->dev
);
260 /* reset the device */
261 ret
= i2c_smbus_write_byte_data(lp
->client
,
262 ARCXCNN_CMD
, ARCXCNN_CMD_RESET
);
267 lp
->pdata
= devm_kzalloc(lp
->dev
,
268 sizeof(*lp
->pdata
), GFP_KERNEL
);
272 /* Setup defaults based on power-on defaults */
273 lp
->pdata
->name
= NULL
;
274 lp
->pdata
->initial_brightness
= INIT_BRIGHT
;
275 lp
->pdata
->leden
= ARCXCNN_LEDEN_MASK
;
277 lp
->pdata
->led_config_0
= i2c_smbus_read_byte_data(
278 lp
->client
, ARCXCNN_FADECTRL
);
280 lp
->pdata
->led_config_1
= i2c_smbus_read_byte_data(
281 lp
->client
, ARCXCNN_ILED_CONFIG
);
282 /* insure dim mode is not default pwm */
283 lp
->pdata
->led_config_1
|= ARCXCNN_ILED_DIM_INT
;
285 lp
->pdata
->dim_freq
= i2c_smbus_read_byte_data(
286 lp
->client
, ARCXCNN_DIMFREQ
);
288 lp
->pdata
->comp_config
= i2c_smbus_read_byte_data(
289 lp
->client
, ARCXCNN_COMP_CONFIG
);
291 lp
->pdata
->filter_config
= i2c_smbus_read_byte_data(
292 lp
->client
, ARCXCNN_FILT_CONFIG
);
294 lp
->pdata
->trim_config
= i2c_smbus_read_byte_data(
295 lp
->client
, ARCXCNN_IMAXTUNE
);
297 if (IS_ENABLED(CONFIG_OF
))
298 arcxcnn_parse_dt(lp
);
301 i2c_set_clientdata(cl
, lp
);
303 /* constrain settings to what is possible */
304 if (lp
->pdata
->initial_brightness
> MAX_BRIGHTNESS
)
305 lp
->pdata
->initial_brightness
= MAX_BRIGHTNESS
;
307 /* set initial brightness */
308 ret
= arcxcnn_set_brightness(lp
, lp
->pdata
->initial_brightness
);
312 /* set other register values directly */
313 ret
= i2c_smbus_write_byte_data(lp
->client
, ARCXCNN_FADECTRL
,
314 lp
->pdata
->led_config_0
);
318 ret
= i2c_smbus_write_byte_data(lp
->client
, ARCXCNN_ILED_CONFIG
,
319 lp
->pdata
->led_config_1
);
323 ret
= i2c_smbus_write_byte_data(lp
->client
, ARCXCNN_DIMFREQ
,
324 lp
->pdata
->dim_freq
);
328 ret
= i2c_smbus_write_byte_data(lp
->client
, ARCXCNN_COMP_CONFIG
,
329 lp
->pdata
->comp_config
);
333 ret
= i2c_smbus_write_byte_data(lp
->client
, ARCXCNN_FILT_CONFIG
,
334 lp
->pdata
->filter_config
);
338 ret
= i2c_smbus_write_byte_data(lp
->client
, ARCXCNN_IMAXTUNE
,
339 lp
->pdata
->trim_config
);
343 /* set initial LED Enables */
344 arcxcnn_update_field(lp
, ARCXCNN_LEDEN
,
345 ARCXCNN_LEDEN_MASK
, lp
->pdata
->leden
);
347 ret
= arcxcnn_backlight_register(lp
);
349 goto probe_register_err
;
351 backlight_update_status(lp
->bl
);
357 "failed to register backlight.\n");
361 "failure ret: %d\n", ret
);
365 static int arcxcnn_remove(struct i2c_client
*cl
)
367 struct arcxcnn
*lp
= i2c_get_clientdata(cl
);
369 /* disable all strings (ignore errors) */
370 i2c_smbus_write_byte_data(lp
->client
,
371 ARCXCNN_LEDEN
, 0x00);
372 /* reset the device (ignore errors) */
373 i2c_smbus_write_byte_data(lp
->client
,
374 ARCXCNN_CMD
, ARCXCNN_CMD_RESET
);
376 lp
->bl
->props
.brightness
= 0;
378 backlight_update_status(lp
->bl
);
383 static const struct of_device_id arcxcnn_dt_ids
[] = {
384 { .compatible
= "arc,arc2c0608" },
387 MODULE_DEVICE_TABLE(of
, arcxcnn_dt_ids
);
389 static const struct i2c_device_id arcxcnn_ids
[] = {
390 {"arc2c0608", ARC2C0608
},
393 MODULE_DEVICE_TABLE(i2c
, arcxcnn_ids
);
395 static struct i2c_driver arcxcnn_driver
= {
397 .name
= "arcxcnn_bl",
398 .of_match_table
= of_match_ptr(arcxcnn_dt_ids
),
400 .probe
= arcxcnn_probe
,
401 .remove
= arcxcnn_remove
,
402 .id_table
= arcxcnn_ids
,
404 module_i2c_driver(arcxcnn_driver
);
406 MODULE_LICENSE("GPL v2");
407 MODULE_AUTHOR("Brian Dodge <bdodge@arcticsand.com>");
408 MODULE_DESCRIPTION("ARCXCNN Backlight driver");