arcxcnn_bl.c (11062B)
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 16enum 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 */ 32struct 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 87struct arcxcnn { 88 struct i2c_client *client; 89 struct backlight_device *bl; 90 struct device *dev; 91 struct arcxcnn_platform_data *pdata; 92}; 93 94static 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 112static 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 130static int arcxcnn_bl_update_status(struct backlight_device *bl) 131{ 132 struct arcxcnn *lp = bl_get_data(bl); 133 u32 brightness = bl->props.brightness; 134 int ret; 135 136 if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) 137 brightness = 0; 138 139 ret = arcxcnn_set_brightness(lp, brightness); 140 if (ret) 141 return ret; 142 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); 146} 147 148static const struct backlight_ops arcxcnn_bl_ops = { 149 .options = BL_CORE_SUSPENDRESUME, 150 .update_status = arcxcnn_bl_update_status, 151}; 152 153static int arcxcnn_backlight_register(struct arcxcnn *lp) 154{ 155 struct backlight_properties *props; 156 const char *name = lp->pdata->name ? : "arctic_bl"; 157 158 props = devm_kzalloc(lp->dev, sizeof(*props), GFP_KERNEL); 159 if (!props) 160 return -ENOMEM; 161 162 props->type = BACKLIGHT_PLATFORM; 163 props->max_brightness = MAX_BRIGHTNESS; 164 165 if (lp->pdata->initial_brightness > props->max_brightness) 166 lp->pdata->initial_brightness = props->max_brightness; 167 168 props->brightness = lp->pdata->initial_brightness; 169 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); 173} 174 175static void arcxcnn_parse_dt(struct arcxcnn *lp) 176{ 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]; 180 int ret; 181 182 /* device tree entry isn't required, defaults are OK */ 183 if (!node) 184 return; 185 186 ret = of_property_read_string(node, "label", &lp->pdata->name); 187 if (ret < 0) 188 lp->pdata->name = NULL; 189 190 ret = of_property_read_u32(node, "default-brightness", &prog_val); 191 if (ret == 0) 192 lp->pdata->initial_brightness = prog_val; 193 194 ret = of_property_read_u32(node, "arc,led-config-0", &prog_val); 195 if (ret == 0) 196 lp->pdata->led_config_0 = (u8)prog_val; 197 198 ret = of_property_read_u32(node, "arc,led-config-1", &prog_val); 199 if (ret == 0) 200 lp->pdata->led_config_1 = (u8)prog_val; 201 202 ret = of_property_read_u32(node, "arc,dim-freq", &prog_val); 203 if (ret == 0) 204 lp->pdata->dim_freq = (u8)prog_val; 205 206 ret = of_property_read_u32(node, "arc,comp-config", &prog_val); 207 if (ret == 0) 208 lp->pdata->comp_config = (u8)prog_val; 209 210 ret = of_property_read_u32(node, "arc,filter-config", &prog_val); 211 if (ret == 0) 212 lp->pdata->filter_config = (u8)prog_val; 213 214 ret = of_property_read_u32(node, "arc,trim-config", &prog_val); 215 if (ret == 0) 216 lp->pdata->trim_config = (u8)prog_val; 217 218 ret = of_property_count_u32_elems(node, "led-sources"); 219 if (ret < 0) { 220 lp->pdata->leden = ARCXCNN_LEDEN_MASK; /* all on is default */ 221 } else { 222 num_entry = ret; 223 if (num_entry > ARCXCNN_LEDEN_BITS) 224 num_entry = ARCXCNN_LEDEN_BITS; 225 226 ret = of_property_read_u32_array(node, "led-sources", sources, 227 num_entry); 228 if (ret < 0) { 229 dev_err(dev, "led-sources node is invalid.\n"); 230 return; 231 } 232 233 lp->pdata->leden = 0; 234 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]; 238 239 lp->pdata->leden |= onbit; 240 } 241 } 242} 243 244static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id) 245{ 246 struct arcxcnn *lp; 247 int ret; 248 249 if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 250 return -EIO; 251 252 lp = devm_kzalloc(&cl->dev, sizeof(*lp), GFP_KERNEL); 253 if (!lp) 254 return -ENOMEM; 255 256 lp->client = cl; 257 lp->dev = &cl->dev; 258 lp->pdata = dev_get_platdata(&cl->dev); 259 260 /* reset the device */ 261 ret = i2c_smbus_write_byte_data(lp->client, 262 ARCXCNN_CMD, ARCXCNN_CMD_RESET); 263 if (ret) 264 goto probe_err; 265 266 if (!lp->pdata) { 267 lp->pdata = devm_kzalloc(lp->dev, 268 sizeof(*lp->pdata), GFP_KERNEL); 269 if (!lp->pdata) 270 return -ENOMEM; 271 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; 276 277 lp->pdata->led_config_0 = i2c_smbus_read_byte_data( 278 lp->client, ARCXCNN_FADECTRL); 279 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; 284 285 lp->pdata->dim_freq = i2c_smbus_read_byte_data( 286 lp->client, ARCXCNN_DIMFREQ); 287 288 lp->pdata->comp_config = i2c_smbus_read_byte_data( 289 lp->client, ARCXCNN_COMP_CONFIG); 290 291 lp->pdata->filter_config = i2c_smbus_read_byte_data( 292 lp->client, ARCXCNN_FILT_CONFIG); 293 294 lp->pdata->trim_config = i2c_smbus_read_byte_data( 295 lp->client, ARCXCNN_IMAXTUNE); 296 297 if (IS_ENABLED(CONFIG_OF)) 298 arcxcnn_parse_dt(lp); 299 } 300 301 i2c_set_clientdata(cl, lp); 302 303 /* constrain settings to what is possible */ 304 if (lp->pdata->initial_brightness > MAX_BRIGHTNESS) 305 lp->pdata->initial_brightness = MAX_BRIGHTNESS; 306 307 /* set initial brightness */ 308 ret = arcxcnn_set_brightness(lp, lp->pdata->initial_brightness); 309 if (ret) 310 goto probe_err; 311 312 /* set other register values directly */ 313 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_FADECTRL, 314 lp->pdata->led_config_0); 315 if (ret) 316 goto probe_err; 317 318 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_ILED_CONFIG, 319 lp->pdata->led_config_1); 320 if (ret) 321 goto probe_err; 322 323 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_DIMFREQ, 324 lp->pdata->dim_freq); 325 if (ret) 326 goto probe_err; 327 328 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_COMP_CONFIG, 329 lp->pdata->comp_config); 330 if (ret) 331 goto probe_err; 332 333 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_FILT_CONFIG, 334 lp->pdata->filter_config); 335 if (ret) 336 goto probe_err; 337 338 ret = i2c_smbus_write_byte_data(lp->client, ARCXCNN_IMAXTUNE, 339 lp->pdata->trim_config); 340 if (ret) 341 goto probe_err; 342 343 /* set initial LED Enables */ 344 arcxcnn_update_field(lp, ARCXCNN_LEDEN, 345 ARCXCNN_LEDEN_MASK, lp->pdata->leden); 346 347 ret = arcxcnn_backlight_register(lp); 348 if (ret) 349 goto probe_register_err; 350 351 backlight_update_status(lp->bl); 352 353 return 0; 354 355probe_register_err: 356 dev_err(lp->dev, 357 "failed to register backlight.\n"); 358 359probe_err: 360 dev_err(lp->dev, 361 "failure ret: %d\n", ret); 362 return ret; 363} 364 365static int arcxcnn_remove(struct i2c_client *cl) 366{ 367 struct arcxcnn *lp = i2c_get_clientdata(cl); 368 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); 375 376 lp->bl->props.brightness = 0; 377 378 backlight_update_status(lp->bl); 379 380 return 0; 381} 382 383static const struct of_device_id arcxcnn_dt_ids[] = { 384 { .compatible = "arc,arc2c0608" }, 385 { } 386}; 387MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids); 388 389static const struct i2c_device_id arcxcnn_ids[] = { 390 {"arc2c0608", ARC2C0608}, 391 { } 392}; 393MODULE_DEVICE_TABLE(i2c, arcxcnn_ids); 394 395static struct i2c_driver arcxcnn_driver = { 396 .driver = { 397 .name = "arcxcnn_bl", 398 .of_match_table = of_match_ptr(arcxcnn_dt_ids), 399 }, 400 .probe = arcxcnn_probe, 401 .remove = arcxcnn_remove, 402 .id_table = arcxcnn_ids, 403}; 404module_i2c_driver(arcxcnn_driver); 405 406MODULE_LICENSE("GPL v2"); 407MODULE_AUTHOR("Brian Dodge <bdodge@arcticsand.com>"); 408MODULE_DESCRIPTION("ARCXCNN Backlight driver");