linux/drivers/iio/light/veml3235.c
Javier Carrasco 82e1cedeb8 iio: light: veml3235: fix scale to conform to ABI
The current scale is not ABI-compliant as it is just the sensor gain
instead of the value that acts as a multiplier to be applied to the raw
value (there is no offset).

Use the iio-gts helpers to obtain the proper scale values according to
the gain and integration time to match the resolution tables from the
datasheet. When at it, use 'scale' instead of 'gain' consistently for
the get/set functions to avoid misunderstandings.

Fixes: c5a23f80c1 ("iio: light: add support for veml3235")
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
Link: https://patch.msgid.link/20241230-veml3235_scale-v3-2-48a5795e2f64@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
2025-01-04 12:36:39 +00:00

547 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* VEML3235 Ambient Light Sensor
*
* Copyright (c) 2024, Javier Carrasco <javier.carrasco.cruz@gmail.com>
*
* Datasheet: https://www.vishay.com/docs/80131/veml3235.pdf
* Appnote-80222: https://www.vishay.com/docs/80222/designingveml3235.pdf
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/iio-gts-helper.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#define VEML3235_REG_CONF 0x00
#define VEML3235_REG_WH_DATA 0x04
#define VEML3235_REG_ALS_DATA 0x05
#define VEML3235_REG_ID 0x09
#define VEML3235_CONF_SD BIT(0)
#define VEML3235_CONF_SD0 BIT(15)
struct veml3235_rf {
struct regmap_field *it;
struct regmap_field *gain;
struct regmap_field *id;
};
struct veml3235_data {
struct i2c_client *client;
struct device *dev;
struct regmap *regmap;
struct veml3235_rf rf;
struct iio_gts gts;
};
static const struct iio_itime_sel_mul veml3235_it_sel[] = {
GAIN_SCALE_ITIME_US(50000, 0, 1),
GAIN_SCALE_ITIME_US(100000, 1, 2),
GAIN_SCALE_ITIME_US(200000, 2, 4),
GAIN_SCALE_ITIME_US(400000, 3, 8),
GAIN_SCALE_ITIME_US(800000, 4, 16),
};
/*
* The MSB (DG) doubles the value of the rest of the field, which leads to
* two possible combinations to obtain gain = 2 and gain = 4. The gain
* handling can be simplified by restricting DG = 1 to the only gain that
* really requires it, gain = 8. Note that "X10" is a reserved value.
*/
#define VEML3235_SEL_GAIN_X1 0
#define VEML3235_SEL_GAIN_X2 1
#define VEML3235_SEL_GAIN_X4 3
#define VEML3235_SEL_GAIN_X8 7
static const struct iio_gain_sel_pair veml3235_gain_sel[] = {
GAIN_SCALE_GAIN(1, VEML3235_SEL_GAIN_X1),
GAIN_SCALE_GAIN(2, VEML3235_SEL_GAIN_X2),
GAIN_SCALE_GAIN(4, VEML3235_SEL_GAIN_X4),
GAIN_SCALE_GAIN(8, VEML3235_SEL_GAIN_X8),
};
static int veml3235_power_on(struct veml3235_data *data)
{
int ret;
ret = regmap_clear_bits(data->regmap, VEML3235_REG_CONF,
VEML3235_CONF_SD | VEML3235_CONF_SD0);
if (ret)
return ret;
/* Wait 4 ms to let processor & oscillator start correctly */
fsleep(4000);
return 0;
}
static int veml3235_shut_down(struct veml3235_data *data)
{
return regmap_set_bits(data->regmap, VEML3235_REG_CONF,
VEML3235_CONF_SD | VEML3235_CONF_SD0);
}
static void veml3235_shut_down_action(void *data)
{
veml3235_shut_down(data);
}
enum veml3235_chan {
CH_ALS,
CH_WHITE,
};
static const struct iio_chan_spec veml3235_channels[] = {
{
.type = IIO_LIGHT,
.channel = CH_ALS,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_INTENSITY,
.channel = CH_WHITE,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_BOTH,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
BIT(IIO_CHAN_INFO_SCALE),
},
};
static const struct regmap_range veml3235_readable_ranges[] = {
regmap_reg_range(VEML3235_REG_CONF, VEML3235_REG_ID),
};
static const struct regmap_access_table veml3235_readable_table = {
.yes_ranges = veml3235_readable_ranges,
.n_yes_ranges = ARRAY_SIZE(veml3235_readable_ranges),
};
static const struct regmap_range veml3235_writable_ranges[] = {
regmap_reg_range(VEML3235_REG_CONF, VEML3235_REG_CONF),
};
static const struct regmap_access_table veml3235_writable_table = {
.yes_ranges = veml3235_writable_ranges,
.n_yes_ranges = ARRAY_SIZE(veml3235_writable_ranges),
};
static const struct regmap_range veml3235_volatile_ranges[] = {
regmap_reg_range(VEML3235_REG_WH_DATA, VEML3235_REG_ALS_DATA),
};
static const struct regmap_access_table veml3235_volatile_table = {
.yes_ranges = veml3235_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(veml3235_volatile_ranges),
};
static const struct regmap_config veml3235_regmap_config = {
.name = "veml3235_regmap",
.reg_bits = 8,
.val_bits = 16,
.max_register = VEML3235_REG_ID,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.rd_table = &veml3235_readable_table,
.wr_table = &veml3235_writable_table,
.volatile_table = &veml3235_volatile_table,
.cache_type = REGCACHE_RBTREE,
};
static int veml3235_get_it(struct veml3235_data *data, int *val, int *val2)
{
int ret, it_idx;
ret = regmap_field_read(data->rf.it, &it_idx);
if (ret)
return ret;
ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx);
if (ret < 0)
return ret;
*val2 = ret;
*val = 0;
return IIO_VAL_INT_PLUS_MICRO;
}
static int veml3235_set_it(struct iio_dev *indio_dev, int val, int val2)
{
struct veml3235_data *data = iio_priv(indio_dev);
int ret, gain_idx, it_idx, new_gain, prev_gain, prev_it;
bool in_range;
if (val || !iio_gts_valid_time(&data->gts, val2))
return -EINVAL;
ret = regmap_field_read(data->rf.it, &it_idx);
if (ret)
return ret;
ret = regmap_field_read(data->rf.gain, &gain_idx);
if (ret)
return ret;
prev_it = iio_gts_find_int_time_by_sel(&data->gts, it_idx);
if (prev_it < 0)
return prev_it;
if (prev_it == val2)
return 0;
prev_gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx);
if (prev_gain < 0)
return prev_gain;
ret = iio_gts_find_new_gain_by_gain_time_min(&data->gts, prev_gain, prev_it,
val2, &new_gain, &in_range);
if (ret)
return ret;
if (!in_range)
dev_dbg(data->dev, "Optimal gain out of range\n");
ret = iio_gts_find_sel_by_int_time(&data->gts, val2);
if (ret < 0)
return ret;
ret = regmap_field_write(data->rf.it, ret);
if (ret)
return ret;
ret = iio_gts_find_sel_by_gain(&data->gts, new_gain);
if (ret < 0)
return ret;
return regmap_field_write(data->rf.gain, ret);
}
static int veml3235_set_scale(struct iio_dev *indio_dev, int val, int val2)
{
struct veml3235_data *data = iio_priv(indio_dev);
int ret, it_idx, gain_sel, time_sel;
ret = regmap_field_read(data->rf.it, &it_idx);
if (ret)
return ret;
ret = iio_gts_find_gain_time_sel_for_scale(&data->gts, val, val2,
&gain_sel, &time_sel);
if (ret)
return ret;
ret = regmap_field_write(data->rf.it, time_sel);
if (ret)
return ret;
return regmap_field_write(data->rf.gain, gain_sel);
}
static int veml3235_get_scale(struct veml3235_data *data, int *val, int *val2)
{
int gain, it, reg, ret;
ret = regmap_field_read(data->rf.gain, &reg);
if (ret) {
dev_err(data->dev, "failed to read gain %d\n", ret);
return ret;
}
gain = iio_gts_find_gain_by_sel(&data->gts, reg);
if (gain < 0)
return gain;
ret = regmap_field_read(data->rf.it, &reg);
if (ret) {
dev_err(data->dev, "failed to read integration time %d\n", ret);
return ret;
}
it = iio_gts_find_int_time_by_sel(&data->gts, reg);
if (it < 0)
return it;
ret = iio_gts_get_scale(&data->gts, gain, it, val, val2);
if (ret)
return ret;
return IIO_VAL_INT_PLUS_NANO;
}
static int veml3235_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct veml3235_data *data = iio_priv(indio_dev);
struct regmap *regmap = data->regmap;
int ret, reg;
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_LIGHT:
ret = regmap_read(regmap, VEML3235_REG_ALS_DATA, &reg);
if (ret < 0)
return ret;
*val = reg;
return IIO_VAL_INT;
case IIO_INTENSITY:
ret = regmap_read(regmap, VEML3235_REG_WH_DATA, &reg);
if (ret < 0)
return ret;
*val = reg;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_INT_TIME:
return veml3235_get_it(data, val, val2);
case IIO_CHAN_INFO_SCALE:
return veml3235_get_scale(data, val, val2);
default:
return -EINVAL;
}
}
static int veml3235_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
struct veml3235_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_INT_TIME:
return iio_gts_avail_times(&data->gts, vals, type, length);
case IIO_CHAN_INFO_SCALE:
return iio_gts_all_avail_scales(&data->gts, vals, type, length);
default:
return -EINVAL;
}
}
static int veml3235_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
long mask)
{
switch (mask) {
case IIO_CHAN_INFO_SCALE:
return IIO_VAL_INT_PLUS_NANO;
case IIO_CHAN_INFO_INT_TIME:
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static int veml3235_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
switch (mask) {
case IIO_CHAN_INFO_INT_TIME:
return veml3235_set_it(indio_dev, val, val2);
case IIO_CHAN_INFO_SCALE:
return veml3235_set_scale(indio_dev, val, val2);
}
return -EINVAL;
}
static void veml3235_read_id(struct veml3235_data *data)
{
int ret, reg;
ret = regmap_field_read(data->rf.id, &reg);
if (ret) {
dev_info(data->dev, "failed to read ID\n");
return;
}
if (reg != 0x35)
dev_info(data->dev, "Unknown ID %d\n", reg);
}
static const struct reg_field veml3235_rf_it =
REG_FIELD(VEML3235_REG_CONF, 4, 6);
static const struct reg_field veml3235_rf_gain =
REG_FIELD(VEML3235_REG_CONF, 11, 13);
static const struct reg_field veml3235_rf_id =
REG_FIELD(VEML3235_REG_ID, 0, 7);
static int veml3235_regfield_init(struct veml3235_data *data)
{
struct regmap *regmap = data->regmap;
struct device *dev = data->dev;
struct regmap_field *rm_field;
struct veml3235_rf *rf = &data->rf;
rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_it);
if (IS_ERR(rm_field))
return PTR_ERR(rm_field);
rf->it = rm_field;
rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_gain);
if (IS_ERR(rm_field))
return PTR_ERR(rm_field);
rf->gain = rm_field;
rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_id);
if (IS_ERR(rm_field))
return PTR_ERR(rm_field);
rf->id = rm_field;
return 0;
}
static int veml3235_hw_init(struct iio_dev *indio_dev)
{
struct veml3235_data *data = iio_priv(indio_dev);
struct device *dev = data->dev;
int ret;
ret = devm_iio_init_iio_gts(data->dev, 0, 272640000,
veml3235_gain_sel, ARRAY_SIZE(veml3235_gain_sel),
veml3235_it_sel, ARRAY_SIZE(veml3235_it_sel),
&data->gts);
if (ret)
return dev_err_probe(data->dev, ret, "failed to init iio gts\n");
/* Set gain to 1 and integration time to 100 ms */
ret = regmap_field_write(data->rf.gain, 0x00);
if (ret)
return dev_err_probe(data->dev, ret, "failed to set gain\n");
ret = regmap_field_write(data->rf.it, 0x01);
if (ret)
return dev_err_probe(data->dev, ret,
"failed to set integration time\n");
ret = veml3235_power_on(data);
if (ret)
return dev_err_probe(dev, ret, "failed to power on\n");
return devm_add_action_or_reset(dev, veml3235_shut_down_action, data);
}
static const struct iio_info veml3235_info = {
.read_raw = veml3235_read_raw,
.read_avail = veml3235_read_avail,
.write_raw = veml3235_write_raw,
.write_raw_get_fmt = veml3235_write_raw_get_fmt,
};
static int veml3235_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct veml3235_data *data;
struct iio_dev *indio_dev;
struct regmap *regmap;
int ret;
regmap = devm_regmap_init_i2c(client, &veml3235_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap),
"failed to setup regmap\n");
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
data->dev = dev;
data->regmap = regmap;
ret = veml3235_regfield_init(data);
if (ret)
return dev_err_probe(dev, ret, "failed to init regfield\n");
ret = devm_regulator_get_enable(dev, "vdd");
if (ret)
return dev_err_probe(dev, ret, "failed to enable regulator\n");
indio_dev->name = "veml3235";
indio_dev->channels = veml3235_channels;
indio_dev->num_channels = ARRAY_SIZE(veml3235_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &veml3235_info;
veml3235_read_id(data);
ret = veml3235_hw_init(indio_dev);
if (ret < 0)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
static int veml3235_runtime_suspend(struct device *dev)
{
struct veml3235_data *data = iio_priv(dev_get_drvdata(dev));
int ret;
ret = veml3235_shut_down(data);
if (ret < 0)
dev_err(data->dev, "failed to suspend: %d\n", ret);
return ret;
}
static int veml3235_runtime_resume(struct device *dev)
{
struct veml3235_data *data = iio_priv(dev_get_drvdata(dev));
int ret;
ret = veml3235_power_on(data);
if (ret < 0)
dev_err(data->dev, "failed to resume: %d\n", ret);
return ret;
}
static DEFINE_RUNTIME_DEV_PM_OPS(veml3235_pm_ops, veml3235_runtime_suspend,
veml3235_runtime_resume, NULL);
static const struct of_device_id veml3235_of_match[] = {
{ .compatible = "vishay,veml3235" },
{ }
};
MODULE_DEVICE_TABLE(of, veml3235_of_match);
static const struct i2c_device_id veml3235_id[] = {
{ "veml3235" },
{ }
};
MODULE_DEVICE_TABLE(i2c, veml3235_id);
static struct i2c_driver veml3235_driver = {
.driver = {
.name = "veml3235",
.of_match_table = veml3235_of_match,
.pm = pm_ptr(&veml3235_pm_ops),
},
.probe = veml3235_probe,
.id_table = veml3235_id,
};
module_i2c_driver(veml3235_driver);
MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");
MODULE_DESCRIPTION("VEML3235 Ambient Light Sensor");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("IIO_GTS_HELPER");