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