404 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Backlight driver for ArcticSand ARC_X_C_0N_0N Devices4 *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 ARC2C060818};19 20/**21 * struct arcxcnn_platform_data22 * @name : Backlight driver name (NULL will use default)23 * @initial_brightness : initial value of backlight brightness24 * @leden : initial LED string enables, upper bit is global on/off25 * @led_config_0 : fading speed (period between intensity steps)26 * @led_config_1 : misc settings, see datasheet27 * @dim_freq : pwm dimming frequency if in pwm mode28 * @comp_config : misc config, see datasheet29 * @filter_config : RC/PWM filter config, see datasheet30 * @trim_config : full scale current trim, see datasheet31 */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 0x0167#define ARCXCNN_LEDEN_LED2 0x0268#define ARCXCNN_LEDEN_LED3 0x0469#define ARCXCNN_LEDEN_LED4 0x0870#define ARCXCNN_LEDEN_LED5 0x1071#define ARCXCNN_LEDEN_LED6 0x2072 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 0x0978#define ARCXCNN_COMP_CONFIG 0x0A79#define ARCXCNN_FILT_CONFIG 0x0B80#define ARCXCNN_IMAXTUNE 0x0C81#define ARCXCNN_ID_MSB 0x1E82#define ARCXCNN_ID_LSB 0x1F83 84#define MAX_BRIGHTNESS 409585#define INIT_BRIGHT 6086 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 = 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 145static const struct backlight_ops arcxcnn_bl_ops = {146 .options = BL_CORE_SUSPENDRESUME,147 .update_status = arcxcnn_bl_update_status,148};149 150static 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 172static 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 241static 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 352probe_register_err:353 dev_err(lp->dev,354 "failed to register backlight.\n");355 356probe_err:357 dev_err(lp->dev,358 "failure ret: %d\n", ret);359 return ret;360}361 362static 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 378static const struct of_device_id arcxcnn_dt_ids[] = {379 { .compatible = "arc,arc2c0608" },380 { }381};382MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids);383 384static const struct i2c_device_id arcxcnn_ids[] = {385 {"arc2c0608", ARC2C0608},386 { }387};388MODULE_DEVICE_TABLE(i2c, arcxcnn_ids);389 390static struct i2c_driver arcxcnn_driver = {391 .driver = {392 .name = "arcxcnn_bl",393 .of_match_table = arcxcnn_dt_ids,394 },395 .probe = arcxcnn_probe,396 .remove = arcxcnn_remove,397 .id_table = arcxcnn_ids,398};399module_i2c_driver(arcxcnn_driver);400 401MODULE_LICENSE("GPL v2");402MODULE_AUTHOR("Brian Dodge <bdodge@arcticsand.com>");403MODULE_DESCRIPTION("ARCXCNN Backlight driver");404