|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * CM3232 Ambient Light Sensor | 
|  | * | 
|  | * Copyright (C) 2014-2015 Capella Microsystems Inc. | 
|  | * Author: Kevin Tsai <ktsai@capellamicro.com> | 
|  | * | 
|  | * IIO driver for CM3232 (7-bit I2C slave address 0x10). | 
|  | */ | 
|  |  | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mod_devicetable.h> | 
|  | #include <linux/iio/iio.h> | 
|  | #include <linux/iio/sysfs.h> | 
|  | #include <linux/init.h> | 
|  |  | 
|  | /* Registers Address */ | 
|  | #define CM3232_REG_ADDR_CMD		0x00 | 
|  | #define CM3232_REG_ADDR_ALS		0x50 | 
|  | #define CM3232_REG_ADDR_ID		0x53 | 
|  |  | 
|  | #define CM3232_CMD_ALS_DISABLE		BIT(0) | 
|  |  | 
|  | #define CM3232_CMD_ALS_IT_SHIFT		2 | 
|  | #define CM3232_CMD_ALS_IT_MASK		(BIT(2) | BIT(3) | BIT(4)) | 
|  | #define CM3232_CMD_ALS_IT_DEFAULT	(0x01 << CM3232_CMD_ALS_IT_SHIFT) | 
|  |  | 
|  | #define CM3232_CMD_ALS_RESET		BIT(6) | 
|  |  | 
|  | #define CM3232_CMD_DEFAULT		CM3232_CMD_ALS_IT_DEFAULT | 
|  |  | 
|  | #define CM3232_HW_ID			0x32 | 
|  | #define CM3232_CALIBSCALE_DEFAULT	100000 | 
|  | #define CM3232_CALIBSCALE_RESOLUTION	100000 | 
|  | #define CM3232_MLUX_PER_LUX		1000 | 
|  |  | 
|  | #define CM3232_MLUX_PER_BIT_DEFAULT	64 | 
|  | #define CM3232_MLUX_PER_BIT_BASE_IT	100000 | 
|  |  | 
|  | static const struct { | 
|  | int val; | 
|  | int val2; | 
|  | u8 it; | 
|  | } cm3232_als_it_scales[] = { | 
|  | {0, 100000, 0},	/* 0.100000 */ | 
|  | {0, 200000, 1},	/* 0.200000 */ | 
|  | {0, 400000, 2},	/* 0.400000 */ | 
|  | {0, 800000, 3},	/* 0.800000 */ | 
|  | {1, 600000, 4},	/* 1.600000 */ | 
|  | {3, 200000, 5},	/* 3.200000 */ | 
|  | }; | 
|  |  | 
|  | struct cm3232_als_info { | 
|  | u8 regs_cmd_default; | 
|  | u8 hw_id; | 
|  | int calibscale; | 
|  | int mlux_per_bit; | 
|  | int mlux_per_bit_base_it; | 
|  | }; | 
|  |  | 
|  | static struct cm3232_als_info cm3232_als_info_default = { | 
|  | .regs_cmd_default = CM3232_CMD_DEFAULT, | 
|  | .hw_id = CM3232_HW_ID, | 
|  | .calibscale = CM3232_CALIBSCALE_DEFAULT, | 
|  | .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, | 
|  | .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, | 
|  | }; | 
|  |  | 
|  | struct cm3232_chip { | 
|  | struct i2c_client *client; | 
|  | struct cm3232_als_info *als_info; | 
|  | u8 regs_cmd; | 
|  | u16 regs_als; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * cm3232_reg_init() - Initialize CM3232 | 
|  | * @chip:	pointer of struct cm3232_chip. | 
|  | * | 
|  | * Check and initialize CM3232 ambient light sensor. | 
|  | * | 
|  | * Return: 0 for success; otherwise for error code. | 
|  | */ | 
|  | static int cm3232_reg_init(struct cm3232_chip *chip) | 
|  | { | 
|  | struct i2c_client *client = chip->client; | 
|  | s32 ret; | 
|  |  | 
|  | chip->als_info = &cm3232_als_info_default; | 
|  |  | 
|  | /* Identify device */ | 
|  | ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); | 
|  | if (ret < 0) { | 
|  | dev_err(&chip->client->dev, "Error reading addr_id\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if ((ret & 0xFF) != chip->als_info->hw_id) | 
|  | return -ENODEV; | 
|  |  | 
|  | /* Disable and reset device */ | 
|  | chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; | 
|  | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, | 
|  | chip->regs_cmd); | 
|  | if (ret < 0) { | 
|  | dev_err(&chip->client->dev, "Error writing reg_cmd\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Register default value */ | 
|  | chip->regs_cmd = chip->als_info->regs_cmd_default; | 
|  |  | 
|  | /* Configure register */ | 
|  | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, | 
|  | chip->regs_cmd); | 
|  | if (ret < 0) | 
|  | dev_err(&chip->client->dev, "Error writing reg_cmd\n"); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | *  cm3232_read_als_it() - Get sensor integration time | 
|  | *  @chip:	pointer of struct cm3232_chip | 
|  | *  @val:	pointer of int to load the integration (sec). | 
|  | *  @val2:	pointer of int to load the integration time (microsecond). | 
|  | * | 
|  | *  Report the current integration time. | 
|  | * | 
|  | *  Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. | 
|  | */ | 
|  | static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) | 
|  | { | 
|  | u16 als_it; | 
|  | int i; | 
|  |  | 
|  | als_it = chip->regs_cmd; | 
|  | als_it &= CM3232_CMD_ALS_IT_MASK; | 
|  | als_it >>= CM3232_CMD_ALS_IT_SHIFT; | 
|  | for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { | 
|  | if (als_it == cm3232_als_it_scales[i].it) { | 
|  | *val = cm3232_als_it_scales[i].val; | 
|  | *val2 = cm3232_als_it_scales[i].val2; | 
|  | return IIO_VAL_INT_PLUS_MICRO; | 
|  | } | 
|  | } | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cm3232_write_als_it() - Write sensor integration time | 
|  | * @chip:	pointer of struct cm3232_chip. | 
|  | * @val:	integration time in second. | 
|  | * @val2:	integration time in microsecond. | 
|  | * | 
|  | * Convert integration time to sensor value. | 
|  | * | 
|  | * Return: i2c_smbus_write_byte_data command return value. | 
|  | */ | 
|  | static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) | 
|  | { | 
|  | struct i2c_client *client = chip->client; | 
|  | u16 als_it, cmd; | 
|  | int i; | 
|  | s32 ret; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { | 
|  | if (val == cm3232_als_it_scales[i].val && | 
|  | val2 == cm3232_als_it_scales[i].val2) { | 
|  |  | 
|  | als_it = cm3232_als_it_scales[i].it; | 
|  | als_it <<= CM3232_CMD_ALS_IT_SHIFT; | 
|  |  | 
|  | cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; | 
|  | cmd |= als_it; | 
|  | ret = i2c_smbus_write_byte_data(client, | 
|  | CM3232_REG_ADDR_CMD, | 
|  | cmd); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | chip->regs_cmd = cmd; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cm3232_get_lux() - report current lux value | 
|  | * @chip:	pointer of struct cm3232_chip. | 
|  | * | 
|  | * Convert sensor data to lux.  It depends on integration | 
|  | * time and calibscale variable. | 
|  | * | 
|  | * Return: Zero or positive value is lux, otherwise error code. | 
|  | */ | 
|  | static int cm3232_get_lux(struct cm3232_chip *chip) | 
|  | { | 
|  | struct i2c_client *client = chip->client; | 
|  | struct cm3232_als_info *als_info = chip->als_info; | 
|  | int ret; | 
|  | int val, val2; | 
|  | int als_it; | 
|  | u64 lux; | 
|  |  | 
|  | /* Calculate mlux per bit based on als_it */ | 
|  | ret = cm3232_read_als_it(chip, &val, &val2); | 
|  | if (ret < 0) | 
|  | return -EINVAL; | 
|  | als_it = val * 1000000 + val2; | 
|  | lux = (__force u64)als_info->mlux_per_bit; | 
|  | lux *= als_info->mlux_per_bit_base_it; | 
|  | lux = div_u64(lux, als_it); | 
|  |  | 
|  | ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); | 
|  | if (ret < 0) { | 
|  | dev_err(&client->dev, "Error reading reg_addr_als\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | chip->regs_als = (u16)ret; | 
|  | lux *= chip->regs_als; | 
|  | lux *= als_info->calibscale; | 
|  | lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION); | 
|  | lux = div_u64(lux, CM3232_MLUX_PER_LUX); | 
|  |  | 
|  | if (lux > 0xFFFF) | 
|  | lux = 0xFFFF; | 
|  |  | 
|  | return (int)lux; | 
|  | } | 
|  |  | 
|  | static int cm3232_read_raw(struct iio_dev *indio_dev, | 
|  | struct iio_chan_spec const *chan, | 
|  | int *val, int *val2, long mask) | 
|  | { | 
|  | struct cm3232_chip *chip = iio_priv(indio_dev); | 
|  | struct cm3232_als_info *als_info = chip->als_info; | 
|  | int ret; | 
|  |  | 
|  | switch (mask) { | 
|  | case IIO_CHAN_INFO_PROCESSED: | 
|  | ret = cm3232_get_lux(chip); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | *val = ret; | 
|  | return IIO_VAL_INT; | 
|  | case IIO_CHAN_INFO_CALIBSCALE: | 
|  | *val = als_info->calibscale; | 
|  | return IIO_VAL_INT; | 
|  | case IIO_CHAN_INFO_INT_TIME: | 
|  | return cm3232_read_als_it(chip, val, val2); | 
|  | } | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int cm3232_write_raw(struct iio_dev *indio_dev, | 
|  | struct iio_chan_spec const *chan, | 
|  | int val, int val2, long mask) | 
|  | { | 
|  | struct cm3232_chip *chip = iio_priv(indio_dev); | 
|  | struct cm3232_als_info *als_info = chip->als_info; | 
|  |  | 
|  | switch (mask) { | 
|  | case IIO_CHAN_INFO_CALIBSCALE: | 
|  | als_info->calibscale = val; | 
|  | return 0; | 
|  | case IIO_CHAN_INFO_INT_TIME: | 
|  | return cm3232_write_als_it(chip, val, val2); | 
|  | } | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cm3232_get_it_available() - Get available ALS IT value | 
|  | * @dev:	pointer of struct device. | 
|  | * @attr:	pointer of struct device_attribute. | 
|  | * @buf:	pointer of return string buffer. | 
|  | * | 
|  | * Display the available integration time in second. | 
|  | * | 
|  | * Return: string length. | 
|  | */ | 
|  | static ssize_t cm3232_get_it_available(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | int i, len; | 
|  |  | 
|  | for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) | 
|  | len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", | 
|  | cm3232_als_it_scales[i].val, | 
|  | cm3232_als_it_scales[i].val2); | 
|  | return len + scnprintf(buf + len, PAGE_SIZE - len, "\n"); | 
|  | } | 
|  |  | 
|  | static const struct iio_chan_spec cm3232_channels[] = { | 
|  | { | 
|  | .type = IIO_LIGHT, | 
|  | .info_mask_separate = | 
|  | BIT(IIO_CHAN_INFO_PROCESSED) | | 
|  | BIT(IIO_CHAN_INFO_CALIBSCALE) | | 
|  | BIT(IIO_CHAN_INFO_INT_TIME), | 
|  | } | 
|  | }; | 
|  |  | 
|  | static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, | 
|  | S_IRUGO, cm3232_get_it_available, NULL, 0); | 
|  |  | 
|  | static struct attribute *cm3232_attributes[] = { | 
|  | &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, | 
|  | NULL, | 
|  | }; | 
|  |  | 
|  | static const struct attribute_group cm3232_attribute_group = { | 
|  | .attrs = cm3232_attributes | 
|  | }; | 
|  |  | 
|  | static const struct iio_info cm3232_info = { | 
|  | .read_raw		= &cm3232_read_raw, | 
|  | .write_raw		= &cm3232_write_raw, | 
|  | .attrs			= &cm3232_attribute_group, | 
|  | }; | 
|  |  | 
|  | static int cm3232_probe(struct i2c_client *client) | 
|  | { | 
|  | const struct i2c_device_id *id = i2c_client_get_device_id(client); | 
|  | struct cm3232_chip *chip; | 
|  | struct iio_dev *indio_dev; | 
|  | int ret; | 
|  |  | 
|  | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); | 
|  | if (!indio_dev) | 
|  | return -ENOMEM; | 
|  |  | 
|  | chip = iio_priv(indio_dev); | 
|  | i2c_set_clientdata(client, indio_dev); | 
|  | chip->client = client; | 
|  |  | 
|  | indio_dev->channels = cm3232_channels; | 
|  | indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); | 
|  | indio_dev->info = &cm3232_info; | 
|  | indio_dev->name = id->name; | 
|  | indio_dev->modes = INDIO_DIRECT_MODE; | 
|  |  | 
|  | ret = cm3232_reg_init(chip); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, | 
|  | "%s: register init failed\n", | 
|  | __func__); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return iio_device_register(indio_dev); | 
|  | } | 
|  |  | 
|  | static void cm3232_remove(struct i2c_client *client) | 
|  | { | 
|  | struct iio_dev *indio_dev = i2c_get_clientdata(client); | 
|  |  | 
|  | i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, | 
|  | CM3232_CMD_ALS_DISABLE); | 
|  |  | 
|  | iio_device_unregister(indio_dev); | 
|  | } | 
|  |  | 
|  | static const struct i2c_device_id cm3232_id[] = { | 
|  | {"cm3232", 0}, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | static int cm3232_suspend(struct device *dev) | 
|  | { | 
|  | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); | 
|  | struct cm3232_chip *chip = iio_priv(indio_dev); | 
|  | struct i2c_client *client = chip->client; | 
|  | int ret; | 
|  |  | 
|  | chip->regs_cmd |= CM3232_CMD_ALS_DISABLE; | 
|  | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, | 
|  | chip->regs_cmd); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int cm3232_resume(struct device *dev) | 
|  | { | 
|  | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); | 
|  | struct cm3232_chip *chip = iio_priv(indio_dev); | 
|  | struct i2c_client *client = chip->client; | 
|  | int ret; | 
|  |  | 
|  | chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE; | 
|  | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, | 
|  | chip->regs_cmd | CM3232_CMD_ALS_RESET); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static DEFINE_SIMPLE_DEV_PM_OPS(cm3232_pm_ops, cm3232_suspend, cm3232_resume); | 
|  |  | 
|  | MODULE_DEVICE_TABLE(i2c, cm3232_id); | 
|  |  | 
|  | static const struct of_device_id cm3232_of_match[] = { | 
|  | {.compatible = "capella,cm3232"}, | 
|  | {} | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, cm3232_of_match); | 
|  |  | 
|  | static struct i2c_driver cm3232_driver = { | 
|  | .driver = { | 
|  | .name	= "cm3232", | 
|  | .of_match_table = cm3232_of_match, | 
|  | .pm	= pm_sleep_ptr(&cm3232_pm_ops), | 
|  | }, | 
|  | .id_table	= cm3232_id, | 
|  | .probe		= cm3232_probe, | 
|  | .remove		= cm3232_remove, | 
|  | }; | 
|  |  | 
|  | module_i2c_driver(cm3232_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); | 
|  | MODULE_DESCRIPTION("CM3232 ambient light sensor driver"); | 
|  | MODULE_LICENSE("GPL"); |