1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Backlight driver for ArcticSand ARC_X_C_0N_0N Devices 4 * 5 * Copyright 2016 ArcticSand, Inc. 6 * Author : Brian Dodge <bdodge@arcticsand.com> 7 */ 8 9 #include <linux/backlight.h> 10 #include <linux/err.h> 11 #include <linux/i2c.h> 12 #include <linux/module.h> 13 #include <linux/of.h> 14 #include <linux/slab.h> 15 16 enum arcxcnn_chip_id { 17 ARC2C0608 18 }; 19 20 /** 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 31 */ 32 struct arcxcnn_platform_data { 33 const char *name; 34 u16 initial_brightness; 35 u8 leden; 36 u8 led_config_0; 37 u8 led_config_1; 38 u8 dim_freq; 39 u8 comp_config; 40 u8 filter_config; 41 u8 trim_config; 42 }; 43 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 */ 54 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 72 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) */ 76 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 83 84 #define MAX_BRIGHTNESS 4095 85 #define INIT_BRIGHT 60 86 87 struct arcxcnn { 88 struct i2c_client *client; 89 struct backlight_device *bl; 90 struct device *dev; 91 struct arcxcnn_platform_data *pdata; 92 }; 93 94 static int arcxcnn_update_field(struct arcxcnn *lp, u8 reg, u8 mask, u8 data) 95 { 96 int ret; 97 u8 tmp; 98 99 ret = i2c_smbus_read_byte_data(lp->client, reg); 100 if (ret < 0) { 101 dev_err(lp->dev, "failed to read 0x%.2x\n", reg); 102 return ret; 103 } 104 105 tmp = (u8)ret; 106 tmp &= ~mask; 107 tmp |= data & mask; 108 109 return i2c_smbus_write_byte_data(lp->client, reg, tmp); 110 } 111 112 static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness) 113 { 114 int ret; 115 u8 val; 116 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); 121 if (ret < 0) 122 return ret; 123 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); 128 } 129 130 static int arcxcnn_bl_update_status(struct backlight_device *bl) 131 { 132 struct arcxcnn *lp = bl_get_data(bl); 133 u32 brightness = backlight_get_brightness(bl); 134 int ret; 135 136 ret = arcxcnn_set_brightness(lp, brightness); 137 if (ret) 138 return ret; 139 140 /* set power-on/off/save modes */ 141 return arcxcnn_update_field(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, 142 (bl->props.power == 0) ? 0 : ARCXCNN_CMD_STDBY); 143 } 144 145 static const struct backlight_ops arcxcnn_bl_ops = { 146 .options = BL_CORE_SUSPENDRESUME, 147 .update_status = arcxcnn_bl_update_status, 148 }; 149 150 static int arcxcnn_backlight_register(struct arcxcnn *lp) 151 { 152 struct backlight_properties *props; 153 const char *name = lp->pdata->name ? : "arctic_bl"; 154 155 props = devm_kzalloc(lp->dev, sizeof(*props), GFP_KERNEL); 156 if (!props) 157 return -ENOMEM; 158 159 props->type = BACKLIGHT_PLATFORM; 160 props->max_brightness = MAX_BRIGHTNESS; 161 162 if (lp->pdata->initial_brightness > props->max_brightness) 163 lp->pdata->initial_brightness = props->max_brightness; 164 165 props->brightness = lp->pdata->initial_brightness; 166 167 lp->bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp, 168 &arcxcnn_bl_ops, props); 169 return PTR_ERR_OR_ZERO(lp->bl); 170 } 171 172 static void arcxcnn_parse_dt(struct arcxcnn *lp) 173 { 174 struct device *dev = lp->dev; 175 struct device_node *node = dev->of_node; 176 u32 prog_val, num_entry, entry, sources[ARCXCNN_LEDEN_BITS]; 177 int ret; 178 179 /* device tree entry isn't required, defaults are OK */ 180 if (!node) 181 return; 182 183 ret = of_property_read_string(node, "label", &lp->pdata->name); 184 if (ret < 0) 185 lp->pdata->name = NULL; 186 187 ret = of_property_read_u32(node, "default-brightness", &prog_val); 188 if (ret == 0) 189 lp->pdata->initial_brightness = prog_val; 190 191 ret = of_property_read_u32(node, "arc,led-config-0", &prog_val); 192 if (ret == 0) 193 lp->pdata->led_config_0 = (u8)prog_val; 194 195 ret = of_property_read_u32(node, "arc,led-config-1", &prog_val); 196 if (ret == 0) 197 lp->pdata->led_config_1 = (u8)prog_val; 198 199 ret = of_property_read_u32(node, "arc,dim-freq", &prog_val); 200 if (ret == 0) 201 lp->pdata->dim_freq = (u8)prog_val; 202 203 ret = of_property_read_u32(node, "arc,comp-config", &prog_val); 204 if (ret == 0) 205 lp->pdata->comp_config = (u8)prog_val; 206 207 ret = of_property_read_u32(node, "arc,filter-config", &prog_val); 208 if (ret == 0) 209 lp->pdata->filter_config = (u8)prog_val; 210 211 ret = of_property_read_u32(node, "arc,trim-config", &prog_val); 212 if (ret == 0) 213 lp->pdata->trim_config = (u8)prog_val; 214 215 ret = of_property_count_u32_elems(node, "led-sources"); 216 if (ret < 0) { 217 lp->pdata->leden = ARCXCNN_LEDEN_MASK; /* all on is default */ 218 } else { 219 num_entry = ret; 220 if (num_entry > ARCXCNN_LEDEN_BITS) 221 num_entry = ARCXCNN_LEDEN_BITS; 222 223 ret = of_property_read_u32_array(node, "led-sources", sources, 224 num_entry); 225 if (ret < 0) { 226 dev_err(dev, "led-sources node is invalid.\n"); 227 return; 228 } 229 230 lp->pdata->leden = 0; 231 232 /* for each enable in source, set bit in led enable */ 233 for (entry = 0; entry < num_entry; entry++) { 234 u8 onbit = 1 << sources[entry]; 235 236 lp->pdata->leden |= onbit; 237 } 238 } 239 } 240 241 static int arcxcnn_probe(struct i2c_client *cl) 242 { 243 struct arcxcnn *lp; 244 int ret; 245 246 if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 247 return -EIO; 248 249 lp = devm_kzalloc(&cl->dev, sizeof(*lp), GFP_KERNEL); 250 if (!lp) 251 return -ENOMEM; 252 253 lp->client = cl; 254 lp->dev = &cl->dev; 255 lp->pdata = dev_get_platdata(&cl->dev); 256 257 /* reset the device */ 258 ret = i2c_smbus_write_byte_data(lp->client, 259 ARCXCNN_CMD, ARCXCNN_CMD_RESET); 260 if (ret) 261 goto probe_err; 262 263 if (!lp->pdata) { 264 lp->pdata = devm_kzalloc(lp->dev, 265 sizeof(*lp->pdata), GFP_KERNEL); 266 if (!lp->pdata) 267 return -ENOMEM; 268 269 /* Setup defaults based on power-on defaults */ 270 lp->pdata->name = NULL; 271 lp->pdata->initial_brightness = INIT_BRIGHT; 272 lp->pdata->leden = ARCXCNN_LEDEN_MASK; 273 274 lp->pdata->led_config_0 = i2c_smbus_read_byte_data( 275 lp->client, ARCXCNN_FADECTRL); 276 277 lp->pdata->led_config_1 = i2c_smbus_read_byte_data( 278 lp->client, ARCXCNN_ILED_CONFIG); 279 /* insure dim mode is not default pwm */ 280 lp->pdata->led_config_1 |= ARCXCNN_ILED_DIM_INT; 281 282 lp->pdata->dim_freq = i2c_smbus_read_byte_data( 283 lp->client, ARCXCNN_DIMFREQ); 284 285 lp->pdata->comp_config = i2c_smbus_read_byte_data( 286 lp->client, ARCXCNN_COMP_CONFIG); 287 288 lp->pdata->filter_config = i2c_smbus_read_byte_data( 289 lp->client, ARCXCNN_FILT_CONFIG); 290 291 lp->pdata->trim_config = i2c_smbus_read_byte_data( 292 lp->client, ARCXCNN_IMAXTUNE); 293 294 if (IS_ENABLED(CONFIG_OF)) 295 arcxcnn_parse_dt(lp); 296 } 297 298 i2c_set_clientdata(cl, lp); 299 300 /* constrain settings to what is possible */ 301 if (lp->pdata->initial_brightness > MAX_BRIGHTNESS) 302 lp->pdata->initial_brightness = MAX_BRIGHTNESS; 303 304 /* set initial brightness */ 305 ret = arcxcnn_set_brightness(lp, lp->pdata->initial_brightness); 306 if (ret) 307 goto probe_err; 308 309 /* set other register values directly */ 310 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_FADECTRL, 311 lp->pdata->led_config_0); 312 if (ret) 313 goto probe_err; 314 315 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_ILED_CONFIG, 316 lp->pdata->led_config_1); 317 if (ret) 318 goto probe_err; 319 320 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_DIMFREQ, 321 lp->pdata->dim_freq); 322 if (ret) 323 goto probe_err; 324 325 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_COMP_CONFIG, 326 lp->pdata->comp_config); 327 if (ret) 328 goto probe_err; 329 330 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_FILT_CONFIG, 331 lp->pdata->filter_config); 332 if (ret) 333 goto probe_err; 334 335 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_IMAXTUNE, 336 lp->pdata->trim_config); 337 if (ret) 338 goto probe_err; 339 340 /* set initial LED Enables */ 341 arcxcnn_update_field(lp, ARCXCNN_LEDEN, 342 ARCXCNN_LEDEN_MASK, lp->pdata->leden); 343 344 ret = arcxcnn_backlight_register(lp); 345 if (ret) 346 goto probe_register_err; 347 348 backlight_update_status(lp->bl); 349 350 return 0; 351 352 probe_register_err: 353 dev_err(lp->dev, 354 "failed to register backlight.\n"); 355 356 probe_err: 357 dev_err(lp->dev, 358 "failure ret: %d\n", ret); 359 return ret; 360 } 361 362 static void arcxcnn_remove(struct i2c_client *cl) 363 { 364 struct arcxcnn *lp = i2c_get_clientdata(cl); 365 366 /* disable all strings (ignore errors) */ 367 i2c_smbus_write_byte_data(lp->client, 368 ARCXCNN_LEDEN, 0x00); 369 /* reset the device (ignore errors) */ 370 i2c_smbus_write_byte_data(lp->client, 371 ARCXCNN_CMD, ARCXCNN_CMD_RESET); 372 373 lp->bl->props.brightness = 0; 374 375 backlight_update_status(lp->bl); 376 } 377 378 static const struct of_device_id arcxcnn_dt_ids[] = { 379 { .compatible = "arc,arc2c0608" }, 380 { } 381 }; 382 MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids); 383 384 static const struct i2c_device_id arcxcnn_ids[] = { 385 {"arc2c0608", ARC2C0608}, 386 { } 387 }; 388 MODULE_DEVICE_TABLE(i2c, arcxcnn_ids); 389 390 static struct i2c_driver arcxcnn_driver = { 391 .driver = { 392 .name = "arcxcnn_bl", 393 .of_match_table = of_match_ptr(arcxcnn_dt_ids), 394 }, 395 .probe_new = arcxcnn_probe, 396 .remove = arcxcnn_remove, 397 .id_table = arcxcnn_ids, 398 }; 399 module_i2c_driver(arcxcnn_driver); 400 401 MODULE_LICENSE("GPL v2"); 402 MODULE_AUTHOR("Brian Dodge <bdodge@arcticsand.com>"); 403 MODULE_DESCRIPTION("ARCXCNN Backlight driver"); 404