linux/drivers/iio/light/cm3232.c
<<
>>
Prefs
   1/*
   2 * CM3232 Ambient Light Sensor
   3 *
   4 * Copyright (C) 2014-2015 Capella Microsystems Inc.
   5 * Author: Kevin Tsai <ktsai@capellamicro.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License version 2, as published
   9 * by the Free Software Foundation.
  10 *
  11 * IIO driver for CM3232 (7-bit I2C slave address 0x10).
  12 */
  13
  14#include <linux/i2c.h>
  15#include <linux/module.h>
  16#include <linux/iio/iio.h>
  17#include <linux/iio/sysfs.h>
  18#include <linux/init.h>
  19
  20/* Registers Address */
  21#define CM3232_REG_ADDR_CMD             0x00
  22#define CM3232_REG_ADDR_ALS             0x50
  23#define CM3232_REG_ADDR_ID              0x53
  24
  25#define CM3232_CMD_ALS_DISABLE          BIT(0)
  26
  27#define CM3232_CMD_ALS_IT_SHIFT         2
  28#define CM3232_CMD_ALS_IT_MASK          (BIT(2) | BIT(3) | BIT(4))
  29#define CM3232_CMD_ALS_IT_DEFAULT       (0x01 << CM3232_CMD_ALS_IT_SHIFT)
  30
  31#define CM3232_CMD_ALS_RESET            BIT(6)
  32
  33#define CM3232_CMD_DEFAULT              CM3232_CMD_ALS_IT_DEFAULT
  34
  35#define CM3232_HW_ID                    0x32
  36#define CM3232_CALIBSCALE_DEFAULT       100000
  37#define CM3232_CALIBSCALE_RESOLUTION    100000
  38#define CM3232_MLUX_PER_LUX             1000
  39
  40#define CM3232_MLUX_PER_BIT_DEFAULT     64
  41#define CM3232_MLUX_PER_BIT_BASE_IT     100000
  42
  43static const struct {
  44        int val;
  45        int val2;
  46        u8 it;
  47} cm3232_als_it_scales[] = {
  48        {0, 100000, 0}, /* 0.100000 */
  49        {0, 200000, 1}, /* 0.200000 */
  50        {0, 400000, 2}, /* 0.400000 */
  51        {0, 800000, 3}, /* 0.800000 */
  52        {1, 600000, 4}, /* 1.600000 */
  53        {3, 200000, 5}, /* 3.200000 */
  54};
  55
  56struct cm3232_als_info {
  57        u8 regs_cmd_default;
  58        u8 hw_id;
  59        int calibscale;
  60        int mlux_per_bit;
  61        int mlux_per_bit_base_it;
  62};
  63
  64static struct cm3232_als_info cm3232_als_info_default = {
  65        .regs_cmd_default = CM3232_CMD_DEFAULT,
  66        .hw_id = CM3232_HW_ID,
  67        .calibscale = CM3232_CALIBSCALE_DEFAULT,
  68        .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT,
  69        .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT,
  70};
  71
  72struct cm3232_chip {
  73        struct i2c_client *client;
  74        struct cm3232_als_info *als_info;
  75        u8 regs_cmd;
  76        u16 regs_als;
  77};
  78
  79/**
  80 * cm3232_reg_init() - Initialize CM3232
  81 * @chip:       pointer of struct cm3232_chip.
  82 *
  83 * Check and initialize CM3232 ambient light sensor.
  84 *
  85 * Return: 0 for success; otherwise for error code.
  86 */
  87static int cm3232_reg_init(struct cm3232_chip *chip)
  88{
  89        struct i2c_client *client = chip->client;
  90        s32 ret;
  91
  92        chip->als_info = &cm3232_als_info_default;
  93
  94        /* Identify device */
  95        ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID);
  96        if (ret < 0) {
  97                dev_err(&chip->client->dev, "Error reading addr_id\n");
  98                return ret;
  99        }
 100
 101        if ((ret & 0xFF) != chip->als_info->hw_id)
 102                return -ENODEV;
 103
 104        /* Disable and reset device */
 105        chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET;
 106        ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
 107                                        chip->regs_cmd);
 108        if (ret < 0) {
 109                dev_err(&chip->client->dev, "Error writing reg_cmd\n");
 110                return ret;
 111        }
 112
 113        /* Register default value */
 114        chip->regs_cmd = chip->als_info->regs_cmd_default;
 115
 116        /* Configure register */
 117        ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
 118                                        chip->regs_cmd);
 119        if (ret < 0)
 120                dev_err(&chip->client->dev, "Error writing reg_cmd\n");
 121
 122        return 0;
 123}
 124
 125/**
 126 *  cm3232_read_als_it() - Get sensor integration time
 127 *  @chip:      pointer of struct cm3232_chip
 128 *  @val:       pointer of int to load the integration (sec).
 129 *  @val2:      pointer of int to load the integration time (microsecond).
 130 *
 131 *  Report the current integration time.
 132 *
 133 *  Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL.
 134 */
 135static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2)
 136{
 137        u16 als_it;
 138        int i;
 139
 140        als_it = chip->regs_cmd;
 141        als_it &= CM3232_CMD_ALS_IT_MASK;
 142        als_it >>= CM3232_CMD_ALS_IT_SHIFT;
 143        for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) {
 144                if (als_it == cm3232_als_it_scales[i].it) {
 145                        *val = cm3232_als_it_scales[i].val;
 146                        *val2 = cm3232_als_it_scales[i].val2;
 147                        return IIO_VAL_INT_PLUS_MICRO;
 148                }
 149        }
 150
 151        return -EINVAL;
 152}
 153
 154/**
 155 * cm3232_write_als_it() - Write sensor integration time
 156 * @chip:       pointer of struct cm3232_chip.
 157 * @val:        integration time in second.
 158 * @val2:       integration time in microsecond.
 159 *
 160 * Convert integration time to sensor value.
 161 *
 162 * Return: i2c_smbus_write_byte_data command return value.
 163 */
 164static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2)
 165{
 166        struct i2c_client *client = chip->client;
 167        u16 als_it, cmd;
 168        int i;
 169        s32 ret;
 170
 171        for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) {
 172                if (val == cm3232_als_it_scales[i].val &&
 173                        val2 == cm3232_als_it_scales[i].val2) {
 174
 175                        als_it = cm3232_als_it_scales[i].it;
 176                        als_it <<= CM3232_CMD_ALS_IT_SHIFT;
 177
 178                        cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK;
 179                        cmd |= als_it;
 180                        ret = i2c_smbus_write_byte_data(client,
 181                                                        CM3232_REG_ADDR_CMD,
 182                                                        cmd);
 183                        if (ret < 0)
 184                                return ret;
 185                        chip->regs_cmd = cmd;
 186                        return 0;
 187                }
 188        }
 189        return -EINVAL;
 190}
 191
 192/**
 193 * cm3232_get_lux() - report current lux value
 194 * @chip:       pointer of struct cm3232_chip.
 195 *
 196 * Convert sensor data to lux.  It depends on integration
 197 * time and calibscale variable.
 198 *
 199 * Return: Zero or positive value is lux, otherwise error code.
 200 */
 201static int cm3232_get_lux(struct cm3232_chip *chip)
 202{
 203        struct i2c_client *client = chip->client;
 204        struct cm3232_als_info *als_info = chip->als_info;
 205        int ret;
 206        int val, val2;
 207        int als_it;
 208        u64 lux;
 209
 210        /* Calculate mlux per bit based on als_it */
 211        ret = cm3232_read_als_it(chip, &val, &val2);
 212        if (ret < 0)
 213                return -EINVAL;
 214        als_it = val * 1000000 + val2;
 215        lux = (__force u64)als_info->mlux_per_bit;
 216        lux *= als_info->mlux_per_bit_base_it;
 217        lux = div_u64(lux, als_it);
 218
 219        ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS);
 220        if (ret < 0) {
 221                dev_err(&client->dev, "Error reading reg_addr_als\n");
 222                return ret;
 223        }
 224
 225        chip->regs_als = (u16)ret;
 226        lux *= chip->regs_als;
 227        lux *= als_info->calibscale;
 228        lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION);
 229        lux = div_u64(lux, CM3232_MLUX_PER_LUX);
 230
 231        if (lux > 0xFFFF)
 232                lux = 0xFFFF;
 233
 234        return (int)lux;
 235}
 236
 237static int cm3232_read_raw(struct iio_dev *indio_dev,
 238                        struct iio_chan_spec const *chan,
 239                        int *val, int *val2, long mask)
 240{
 241        struct cm3232_chip *chip = iio_priv(indio_dev);
 242        struct cm3232_als_info *als_info = chip->als_info;
 243        int ret;
 244
 245        switch (mask) {
 246        case IIO_CHAN_INFO_PROCESSED:
 247                ret = cm3232_get_lux(chip);
 248                if (ret < 0)
 249                        return ret;
 250                *val = ret;
 251                return IIO_VAL_INT;
 252        case IIO_CHAN_INFO_CALIBSCALE:
 253                *val = als_info->calibscale;
 254                return IIO_VAL_INT;
 255        case IIO_CHAN_INFO_INT_TIME:
 256                return cm3232_read_als_it(chip, val, val2);
 257        }
 258
 259        return -EINVAL;
 260}
 261
 262static int cm3232_write_raw(struct iio_dev *indio_dev,
 263                        struct iio_chan_spec const *chan,
 264                        int val, int val2, long mask)
 265{
 266        struct cm3232_chip *chip = iio_priv(indio_dev);
 267        struct cm3232_als_info *als_info = chip->als_info;
 268
 269        switch (mask) {
 270        case IIO_CHAN_INFO_CALIBSCALE:
 271                als_info->calibscale = val;
 272                return 0;
 273        case IIO_CHAN_INFO_INT_TIME:
 274                return cm3232_write_als_it(chip, val, val2);
 275        }
 276
 277        return -EINVAL;
 278}
 279
 280/**
 281 * cm3232_get_it_available() - Get available ALS IT value
 282 * @dev:        pointer of struct device.
 283 * @attr:       pointer of struct device_attribute.
 284 * @buf:        pointer of return string buffer.
 285 *
 286 * Display the available integration time in second.
 287 *
 288 * Return: string length.
 289 */
 290static ssize_t cm3232_get_it_available(struct device *dev,
 291                        struct device_attribute *attr, char *buf)
 292{
 293        int i, len;
 294
 295        for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++)
 296                len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ",
 297                        cm3232_als_it_scales[i].val,
 298                        cm3232_als_it_scales[i].val2);
 299        return len + scnprintf(buf + len, PAGE_SIZE - len, "\n");
 300}
 301
 302static const struct iio_chan_spec cm3232_channels[] = {
 303        {
 304                .type = IIO_LIGHT,
 305                .info_mask_separate =
 306                        BIT(IIO_CHAN_INFO_PROCESSED) |
 307                        BIT(IIO_CHAN_INFO_CALIBSCALE) |
 308                        BIT(IIO_CHAN_INFO_INT_TIME),
 309        }
 310};
 311
 312static IIO_DEVICE_ATTR(in_illuminance_integration_time_available,
 313                        S_IRUGO, cm3232_get_it_available, NULL, 0);
 314
 315static struct attribute *cm3232_attributes[] = {
 316        &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr,
 317        NULL,
 318};
 319
 320static const struct attribute_group cm3232_attribute_group = {
 321        .attrs = cm3232_attributes
 322};
 323
 324static const struct iio_info cm3232_info = {
 325        .driver_module          = THIS_MODULE,
 326        .read_raw               = &cm3232_read_raw,
 327        .write_raw              = &cm3232_write_raw,
 328        .attrs                  = &cm3232_attribute_group,
 329};
 330
 331static int cm3232_probe(struct i2c_client *client,
 332                        const struct i2c_device_id *id)
 333{
 334        struct cm3232_chip *chip;
 335        struct iio_dev *indio_dev;
 336        int ret;
 337
 338        indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
 339        if (!indio_dev)
 340                return -ENOMEM;
 341
 342        chip = iio_priv(indio_dev);
 343        i2c_set_clientdata(client, indio_dev);
 344        chip->client = client;
 345
 346        indio_dev->dev.parent = &client->dev;
 347        indio_dev->channels = cm3232_channels;
 348        indio_dev->num_channels = ARRAY_SIZE(cm3232_channels);
 349        indio_dev->info = &cm3232_info;
 350        indio_dev->name = id->name;
 351        indio_dev->modes = INDIO_DIRECT_MODE;
 352
 353        ret = cm3232_reg_init(chip);
 354        if (ret) {
 355                dev_err(&client->dev,
 356                        "%s: register init failed\n",
 357                        __func__);
 358                return ret;
 359        }
 360
 361        return iio_device_register(indio_dev);
 362}
 363
 364static int cm3232_remove(struct i2c_client *client)
 365{
 366        struct iio_dev *indio_dev = i2c_get_clientdata(client);
 367
 368        i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
 369                CM3232_CMD_ALS_DISABLE);
 370
 371        iio_device_unregister(indio_dev);
 372
 373        return 0;
 374}
 375
 376static const struct i2c_device_id cm3232_id[] = {
 377        {"cm3232", 0},
 378        {}
 379};
 380
 381#ifdef CONFIG_PM_SLEEP
 382static int cm3232_suspend(struct device *dev)
 383{
 384        struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
 385        struct cm3232_chip *chip = iio_priv(indio_dev);
 386        struct i2c_client *client = chip->client;
 387        int ret;
 388
 389        chip->regs_cmd |= CM3232_CMD_ALS_DISABLE;
 390        ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
 391                                        chip->regs_cmd);
 392
 393        return ret;
 394}
 395
 396static int cm3232_resume(struct device *dev)
 397{
 398        struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
 399        struct cm3232_chip *chip = iio_priv(indio_dev);
 400        struct i2c_client *client = chip->client;
 401        int ret;
 402
 403        chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE;
 404        ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
 405                                        chip->regs_cmd | CM3232_CMD_ALS_RESET);
 406
 407        return ret;
 408}
 409
 410static const struct dev_pm_ops cm3232_pm_ops = {
 411        SET_SYSTEM_SLEEP_PM_OPS(cm3232_suspend, cm3232_resume)};
 412#endif
 413
 414MODULE_DEVICE_TABLE(i2c, cm3232_id);
 415
 416static const struct of_device_id cm3232_of_match[] = {
 417        {.compatible = "capella,cm3232"},
 418        {}
 419};
 420MODULE_DEVICE_TABLE(of, cm3232_of_match);
 421
 422static struct i2c_driver cm3232_driver = {
 423        .driver = {
 424                .name   = "cm3232",
 425                .of_match_table = of_match_ptr(cm3232_of_match),
 426#ifdef CONFIG_PM_SLEEP
 427                .pm     = &cm3232_pm_ops,
 428#endif
 429        },
 430        .id_table       = cm3232_id,
 431        .probe          = cm3232_probe,
 432        .remove         = cm3232_remove,
 433};
 434
 435module_i2c_driver(cm3232_driver);
 436
 437MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>");
 438MODULE_DESCRIPTION("CM3232 ambient light sensor driver");
 439MODULE_LICENSE("GPL");
 440