linux/drivers/video/backlight/arcxcnn_bl.c
<<
>>
Prefs
   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");
 409