summaryrefslogtreecommitdiff
path: root/drivers/iio/light/as73211.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/light/as73211.c')
-rw-r--r--drivers/iio/light/as73211.c223
1 files changed, 162 insertions, 61 deletions
diff --git a/drivers/iio/light/as73211.c b/drivers/iio/light/as73211.c
index 3ba2378df3dd..32719f584c47 100644
--- a/drivers/iio/light/as73211.c
+++ b/drivers/iio/light/as73211.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Support for AMS AS73211 JENCOLOR(R) Digital XYZ Sensor
+ * Support for AMS AS73211 JENCOLOR(R) Digital XYZ Sensor and AMS AS7331
+ * UVA, UVB and UVC (DUV) Ultraviolet Sensor
*
* Author: Christian Eggers <ceggers@arri.de>
*
@@ -9,10 +10,13 @@
* Color light sensor with 16-bit channels for x, y, z and temperature);
* 7-bit I2C slave address 0x74 .. 0x77.
*
- * Datasheet: https://ams.com/documents/20143/36005/AS73211_DS000556_3-01.pdf
+ * Datasheets:
+ * AS73211: https://ams.com/documents/20143/36005/AS73211_DS000556_3-01.pdf
+ * AS7331: https://ams.com/documents/20143/9106314/AS7331_DS001047_4-00.pdf
*/
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/i2c.h>
@@ -84,6 +88,20 @@ static const int as73211_hardwaregain_avail[] = {
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
};
+struct as73211_data;
+
+/**
+ * struct as73211_spec_dev_data - device-specific data
+ * @intensity_scale: Function to retrieve intensity scale values.
+ * @channels: Device channels.
+ * @num_channels: Number of channels of the device.
+ */
+struct as73211_spec_dev_data {
+ int (*intensity_scale)(struct as73211_data *data, int chan, int *val, int *val2);
+ struct iio_chan_spec const *channels;
+ int num_channels;
+};
+
/**
* struct as73211_data - Instance data for one AS73211
* @client: I2C client.
@@ -94,6 +112,7 @@ static const int as73211_hardwaregain_avail[] = {
* @mutex: Keeps cached registers in sync with the device.
* @completion: Completion to wait for interrupt.
* @int_time_avail: Available integration times (depend on sampling frequency).
+ * @spec_dev: device-specific configuration.
*/
struct as73211_data {
struct i2c_client *client;
@@ -104,6 +123,7 @@ struct as73211_data {
struct mutex mutex;
struct completion completion;
int int_time_avail[AS73211_SAMPLE_TIME_NUM * 2];
+ const struct as73211_spec_dev_data *spec_dev;
};
#define AS73211_COLOR_CHANNEL(_color, _si, _addr) { \
@@ -138,6 +158,10 @@ struct as73211_data {
#define AS73211_SCALE_Y 298384270 /* nW/m^2 */
#define AS73211_SCALE_Z 160241927 /* nW/m^2 */
+#define AS7331_SCALE_UVA 340000 /* nW/cm^2 */
+#define AS7331_SCALE_UVB 378000 /* nW/cm^2 */
+#define AS7331_SCALE_UVC 166000 /* nW/cm^2 */
+
/* Channel order MUST match devices result register order */
#define AS73211_SCAN_INDEX_TEMP 0
#define AS73211_SCAN_INDEX_X 1
@@ -154,6 +178,12 @@ struct as73211_data {
BIT(AS73211_SCAN_INDEX_TEMP) | \
AS73211_SCAN_MASK_COLOR)
+static const unsigned long as73211_scan_masks[] = {
+ AS73211_SCAN_MASK_COLOR,
+ AS73211_SCAN_MASK_ALL,
+ 0
+};
+
static const struct iio_chan_spec as73211_channels[] = {
{
.type = IIO_TEMP,
@@ -176,6 +206,28 @@ static const struct iio_chan_spec as73211_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS),
};
+static const struct iio_chan_spec as7331_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = AS73211_OUT_TEMP,
+ .scan_index = AS73211_SCAN_INDEX_TEMP,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ }
+ },
+ AS73211_COLOR_CHANNEL(LIGHT_UVA, AS73211_SCAN_INDEX_X, AS73211_OUT_MRES1),
+ AS73211_COLOR_CHANNEL(LIGHT_UVB, AS73211_SCAN_INDEX_Y, AS73211_OUT_MRES2),
+ AS73211_COLOR_CHANNEL(LIGHT_DUV, AS73211_SCAN_INDEX_Z, AS73211_OUT_MRES3),
+ IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS),
+};
+
static unsigned int as73211_integration_time_1024cyc(struct as73211_data *data)
{
/*
@@ -316,6 +368,48 @@ static int as73211_req_data(struct as73211_data *data)
return 0;
}
+static int as73211_intensity_scale(struct as73211_data *data, int chan,
+ int *val, int *val2)
+{
+ switch (chan) {
+ case IIO_MOD_X:
+ *val = AS73211_SCALE_X;
+ break;
+ case IIO_MOD_Y:
+ *val = AS73211_SCALE_Y;
+ break;
+ case IIO_MOD_Z:
+ *val = AS73211_SCALE_Z;
+ break;
+ default:
+ return -EINVAL;
+ }
+ *val2 = as73211_integration_time_1024cyc(data) * as73211_gain(data);
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int as7331_intensity_scale(struct as73211_data *data, int chan,
+ int *val, int *val2)
+{
+ switch (chan) {
+ case IIO_MOD_LIGHT_UVA:
+ *val = AS7331_SCALE_UVA;
+ break;
+ case IIO_MOD_LIGHT_UVB:
+ *val = AS7331_SCALE_UVB;
+ break;
+ case IIO_MOD_LIGHT_DUV:
+ *val = AS7331_SCALE_UVC;
+ break;
+ default:
+ return -EINVAL;
+ }
+ *val2 = as73211_integration_time_1024cyc(data) * as73211_gain(data);
+
+ return IIO_VAL_FRACTIONAL;
+}
+
static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
@@ -325,18 +419,17 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
case IIO_CHAN_INFO_RAW: {
int ret;
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret < 0)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = as73211_req_data(data);
if (ret < 0) {
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
}
ret = i2c_smbus_read_word_data(data->client, chan->address);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
@@ -355,30 +448,13 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
*val2 = AS73211_SCALE_TEMP_MICRO;
return IIO_VAL_INT_PLUS_MICRO;
- case IIO_INTENSITY: {
- unsigned int scale;
-
- switch (chan->channel2) {
- case IIO_MOD_X:
- scale = AS73211_SCALE_X;
- break;
- case IIO_MOD_Y:
- scale = AS73211_SCALE_Y;
- break;
- case IIO_MOD_Z:
- scale = AS73211_SCALE_Z;
- break;
- default:
- return -EINVAL;
- }
- scale /= as73211_gain(data);
- scale /= as73211_integration_time_1024cyc(data);
- *val = scale;
- return IIO_VAL_INT;
+ case IIO_INTENSITY:
+ return data->spec_dev->intensity_scale(data, chan->channel2,
+ val, val2);
default:
return -EINVAL;
- }}
+ }
case IIO_CHAN_INFO_SAMP_FREQ:
/* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) */
@@ -441,6 +517,16 @@ static int _as73211_write_raw(struct iio_dev *indio_dev,
struct as73211_data *data = iio_priv(indio_dev);
int ret;
+ /* Need to switch to config mode ... */
+ if ((data->osr & AS73211_OSR_DOS_MASK) != AS73211_OSR_DOS_CONFIG) {
+ data->osr &= ~AS73211_OSR_DOS_MASK;
+ data->osr |= AS73211_OSR_DOS_CONFIG;
+
+ ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr);
+ if (ret < 0)
+ return ret;
+ }
+
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ: {
int reg_bits, freq_kHz = val / HZ_PER_KHZ; /* 1024, 2048, ... */
@@ -525,28 +611,14 @@ static int as73211_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec con
struct as73211_data *data = iio_priv(indio_dev);
int ret;
- mutex_lock(&data->mutex);
-
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret < 0)
- goto error_unlock;
+ guard(mutex)(&data->mutex);
- /* Need to switch to config mode ... */
- if ((data->osr & AS73211_OSR_DOS_MASK) != AS73211_OSR_DOS_CONFIG) {
- data->osr &= ~AS73211_OSR_DOS_MASK;
- data->osr |= AS73211_OSR_DOS_CONFIG;
-
- ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr);
- if (ret < 0)
- goto error_release;
- }
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = _as73211_write_raw(indio_dev, chan, val, val2, mask);
+ iio_device_release_direct(indio_dev);
-error_release:
- iio_device_release_direct_mode(indio_dev);
-error_unlock:
- mutex_unlock(&data->mutex);
return ret;
}
@@ -566,8 +638,8 @@ static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p)
struct as73211_data *data = iio_priv(indio_dev);
struct {
__le16 chan[4];
- s64 ts __aligned(8);
- } scan;
+ aligned_s64 ts;
+ } scan = { };
int data_result, ret;
mutex_lock(&data->mutex);
@@ -602,9 +674,12 @@ static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p)
/* AS73211 starts reading at address 2 */
ret = i2c_master_recv(data->client,
- (char *)&scan.chan[1], 3 * sizeof(scan.chan[1]));
+ (char *)&scan.chan[0], 3 * sizeof(scan.chan[0]));
if (ret < 0)
goto done;
+
+ /* Avoid pushing uninitialized data */
+ scan.chan[3] = 0;
}
if (data_result) {
@@ -612,9 +687,15 @@ static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p)
* Saturate all channels (in case of overflows). Temperature channel
* is not affected by overflows.
*/
- scan.chan[1] = cpu_to_le16(U16_MAX);
- scan.chan[2] = cpu_to_le16(U16_MAX);
- scan.chan[3] = cpu_to_le16(U16_MAX);
+ if (*indio_dev->active_scan_mask == AS73211_SCAN_MASK_ALL) {
+ scan.chan[1] = cpu_to_le16(U16_MAX);
+ scan.chan[2] = cpu_to_le16(U16_MAX);
+ scan.chan[3] = cpu_to_le16(U16_MAX);
+ } else {
+ scan.chan[0] = cpu_to_le16(U16_MAX);
+ scan.chan[1] = cpu_to_le16(U16_MAX);
+ scan.chan[2] = cpu_to_le16(U16_MAX);
+ }
}
iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev));
@@ -676,14 +757,19 @@ static int as73211_probe(struct i2c_client *client)
i2c_set_clientdata(client, indio_dev);
data->client = client;
+ data->spec_dev = i2c_get_match_data(client);
+ if (!data->spec_dev)
+ return -EINVAL;
+
mutex_init(&data->mutex);
init_completion(&data->completion);
indio_dev->info = &as73211_info;
indio_dev->name = AS73211_DRV_NAME;
- indio_dev->channels = as73211_channels;
- indio_dev->num_channels = ARRAY_SIZE(as73211_channels);
+ indio_dev->channels = data->spec_dev->channels;
+ indio_dev->num_channels = data->spec_dev->num_channels;
indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->available_scan_masks = as73211_scan_masks;
ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_OSR);
if (ret < 0)
@@ -755,30 +841,45 @@ static int as73211_probe(struct i2c_client *client)
return devm_iio_device_register(dev, indio_dev);
}
-static int __maybe_unused as73211_suspend(struct device *dev)
+static int as73211_suspend(struct device *dev)
{
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
return as73211_power(indio_dev, false);
}
-static int __maybe_unused as73211_resume(struct device *dev)
+static int as73211_resume(struct device *dev)
{
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
return as73211_power(indio_dev, true);
}
-static SIMPLE_DEV_PM_OPS(as73211_pm_ops, as73211_suspend, as73211_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(as73211_pm_ops, as73211_suspend,
+ as73211_resume);
+
+static const struct as73211_spec_dev_data as73211_spec = {
+ .intensity_scale = as73211_intensity_scale,
+ .channels = as73211_channels,
+ .num_channels = ARRAY_SIZE(as73211_channels),
+};
+
+static const struct as73211_spec_dev_data as7331_spec = {
+ .intensity_scale = as7331_intensity_scale,
+ .channels = as7331_channels,
+ .num_channels = ARRAY_SIZE(as7331_channels),
+};
static const struct of_device_id as73211_of_match[] = {
- { .compatible = "ams,as73211" },
+ { .compatible = "ams,as73211", &as73211_spec },
+ { .compatible = "ams,as7331", &as7331_spec },
{ }
};
MODULE_DEVICE_TABLE(of, as73211_of_match);
static const struct i2c_device_id as73211_id[] = {
- { "as73211", 0 },
+ { "as73211", (kernel_ulong_t)&as73211_spec },
+ { "as7331", (kernel_ulong_t)&as7331_spec },
{ }
};
MODULE_DEVICE_TABLE(i2c, as73211_id);
@@ -787,9 +888,9 @@ static struct i2c_driver as73211_driver = {
.driver = {
.name = AS73211_DRV_NAME,
.of_match_table = as73211_of_match,
- .pm = &as73211_pm_ops,
+ .pm = pm_sleep_ptr(&as73211_pm_ops),
},
- .probe_new = as73211_probe,
+ .probe = as73211_probe,
.id_table = as73211_id,
};
module_i2c_driver(as73211_driver);