|
@@ -1,5 +1,5 @@
|
|
/*
|
|
/*
|
|
- * vcnl4000.c - Support for Vishay VCNL4000/4010/4020 combined ambient
|
|
|
|
|
|
+ * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4200 combined ambient
|
|
* light and proximity sensor
|
|
* light and proximity sensor
|
|
*
|
|
*
|
|
* Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
|
|
* Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
|
|
@@ -8,13 +8,15 @@
|
|
* the GNU General Public License. See the file COPYING in the main
|
|
* the GNU General Public License. See the file COPYING in the main
|
|
* directory of this archive for more details.
|
|
* directory of this archive for more details.
|
|
*
|
|
*
|
|
- * IIO driver for VCNL4000 (7-bit I2C slave address 0x13)
|
|
|
|
|
|
+ * IIO driver for:
|
|
|
|
+ * VCNL4000/10/20 (7-bit I2C slave address 0x13)
|
|
|
|
+ * VCNL4200 (7-bit I2C slave address 0x51)
|
|
*
|
|
*
|
|
* TODO:
|
|
* TODO:
|
|
* allow to adjust IR current
|
|
* allow to adjust IR current
|
|
* proximity threshold and event handling
|
|
* proximity threshold and event handling
|
|
* periodic ALS/proximity measurement (VCNL4010/20)
|
|
* periodic ALS/proximity measurement (VCNL4010/20)
|
|
- * interrupts (VCNL4010/20)
|
|
|
|
|
|
+ * interrupts (VCNL4010/20, VCNL4200)
|
|
*/
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
@@ -28,6 +30,7 @@
|
|
#define VCNL4000_DRV_NAME "vcnl4000"
|
|
#define VCNL4000_DRV_NAME "vcnl4000"
|
|
#define VCNL4000_PROD_ID 0x01
|
|
#define VCNL4000_PROD_ID 0x01
|
|
#define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */
|
|
#define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */
|
|
|
|
+#define VCNL4200_PROD_ID 0x58
|
|
|
|
|
|
#define VCNL4000_COMMAND 0x80 /* Command register */
|
|
#define VCNL4000_COMMAND 0x80 /* Command register */
|
|
#define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */
|
|
#define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */
|
|
@@ -40,6 +43,12 @@
|
|
#define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */
|
|
#define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */
|
|
#define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */
|
|
#define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */
|
|
|
|
|
|
|
|
+#define VCNL4200_AL_CONF 0x00 /* Ambient light configuration */
|
|
|
|
+#define VCNL4200_PS_CONF1 0x03 /* Proximity configuration */
|
|
|
|
+#define VCNL4200_PS_DATA 0x08 /* Proximity data */
|
|
|
|
+#define VCNL4200_AL_DATA 0x09 /* Ambient light data */
|
|
|
|
+#define VCNL4200_DEV_ID 0x0e /* Device ID, slave address and version */
|
|
|
|
+
|
|
/* Bit masks for COMMAND register */
|
|
/* Bit masks for COMMAND register */
|
|
#define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */
|
|
#define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */
|
|
#define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */
|
|
#define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */
|
|
@@ -49,6 +58,14 @@
|
|
enum vcnl4000_device_ids {
|
|
enum vcnl4000_device_ids {
|
|
VCNL4000,
|
|
VCNL4000,
|
|
VCNL4010,
|
|
VCNL4010,
|
|
|
|
+ VCNL4200,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct vcnl4200_channel {
|
|
|
|
+ u8 reg;
|
|
|
|
+ ktime_t last_measurement;
|
|
|
|
+ ktime_t sampling_rate;
|
|
|
|
+ struct mutex lock;
|
|
};
|
|
};
|
|
|
|
|
|
struct vcnl4000_data {
|
|
struct vcnl4000_data {
|
|
@@ -57,7 +74,9 @@ struct vcnl4000_data {
|
|
int rev;
|
|
int rev;
|
|
int al_scale;
|
|
int al_scale;
|
|
const struct vcnl4000_chip_spec *chip_spec;
|
|
const struct vcnl4000_chip_spec *chip_spec;
|
|
- struct mutex lock;
|
|
|
|
|
|
+ struct mutex vcnl4000_lock;
|
|
|
|
+ struct vcnl4200_channel vcnl4200_al;
|
|
|
|
+ struct vcnl4200_channel vcnl4200_ps;
|
|
};
|
|
};
|
|
|
|
|
|
struct vcnl4000_chip_spec {
|
|
struct vcnl4000_chip_spec {
|
|
@@ -71,6 +90,7 @@ static const struct i2c_device_id vcnl4000_id[] = {
|
|
{ "vcnl4000", VCNL4000 },
|
|
{ "vcnl4000", VCNL4000 },
|
|
{ "vcnl4010", VCNL4010 },
|
|
{ "vcnl4010", VCNL4010 },
|
|
{ "vcnl4020", VCNL4010 },
|
|
{ "vcnl4020", VCNL4010 },
|
|
|
|
+ { "vcnl4200", VCNL4200 },
|
|
{ }
|
|
{ }
|
|
};
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
|
|
MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
|
|
@@ -101,6 +121,42 @@ static int vcnl4000_init(struct vcnl4000_data *data)
|
|
|
|
|
|
data->rev = ret & 0xf;
|
|
data->rev = ret & 0xf;
|
|
data->al_scale = 250000;
|
|
data->al_scale = 250000;
|
|
|
|
+ mutex_init(&data->vcnl4000_lock);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int vcnl4200_init(struct vcnl4000_data *data)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ if ((ret & 0xff) != VCNL4200_PROD_ID)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ data->rev = (ret >> 8) & 0xf;
|
|
|
|
+
|
|
|
|
+ /* Set defaults and enable both channels */
|
|
|
|
+ ret = i2c_smbus_write_byte_data(data->client, VCNL4200_AL_CONF, 0x00);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+ ret = i2c_smbus_write_byte_data(data->client, VCNL4200_PS_CONF1, 0x00);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ data->al_scale = 24000;
|
|
|
|
+ data->vcnl4200_al.reg = VCNL4200_AL_DATA;
|
|
|
|
+ data->vcnl4200_ps.reg = VCNL4200_PS_DATA;
|
|
|
|
+ /* Integration time is 50ms, but the experiments show 54ms in total. */
|
|
|
|
+ data->vcnl4200_al.sampling_rate = ktime_set(0, 54000 * 1000);
|
|
|
|
+ data->vcnl4200_ps.sampling_rate = ktime_set(0, 4200 * 1000);
|
|
|
|
+ data->vcnl4200_al.last_measurement = ktime_set(0, 0);
|
|
|
|
+ data->vcnl4200_ps.last_measurement = ktime_set(0, 0);
|
|
|
|
+ mutex_init(&data->vcnl4200_al.lock);
|
|
|
|
+ mutex_init(&data->vcnl4200_ps.lock);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
};
|
|
};
|
|
@@ -112,7 +168,7 @@ static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
|
|
__be16 buf;
|
|
__be16 buf;
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
- mutex_lock(&data->lock);
|
|
|
|
|
|
+ mutex_lock(&data->vcnl4000_lock);
|
|
|
|
|
|
ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
|
|
ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
|
|
req_mask);
|
|
req_mask);
|
|
@@ -141,16 +197,43 @@ static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
goto fail;
|
|
goto fail;
|
|
|
|
|
|
- mutex_unlock(&data->lock);
|
|
|
|
|
|
+ mutex_unlock(&data->vcnl4000_lock);
|
|
*val = be16_to_cpu(buf);
|
|
*val = be16_to_cpu(buf);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
fail:
|
|
fail:
|
|
- mutex_unlock(&data->lock);
|
|
|
|
|
|
+ mutex_unlock(&data->vcnl4000_lock);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int vcnl4200_measure(struct vcnl4000_data *data,
|
|
|
|
+ struct vcnl4200_channel *chan, int *val)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+ s64 delta;
|
|
|
|
+ ktime_t next_measurement;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&chan->lock);
|
|
|
|
+
|
|
|
|
+ next_measurement = ktime_add(chan->last_measurement,
|
|
|
|
+ chan->sampling_rate);
|
|
|
|
+ delta = ktime_us_delta(next_measurement, ktime_get());
|
|
|
|
+ if (delta > 0)
|
|
|
|
+ usleep_range(delta, delta + 500);
|
|
|
|
+ chan->last_measurement = ktime_get();
|
|
|
|
+
|
|
|
|
+ mutex_unlock(&chan->lock);
|
|
|
|
+
|
|
|
|
+ ret = i2c_smbus_read_word_data(data->client, chan->reg);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ *val = ret;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val)
|
|
static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val)
|
|
{
|
|
{
|
|
return vcnl4000_measure(data,
|
|
return vcnl4000_measure(data,
|
|
@@ -158,6 +241,11 @@ static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val)
|
|
VCNL4000_AL_RESULT_HI, val);
|
|
VCNL4000_AL_RESULT_HI, val);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val)
|
|
|
|
+{
|
|
|
|
+ return vcnl4200_measure(data, &data->vcnl4200_al, val);
|
|
|
|
+}
|
|
|
|
+
|
|
static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val)
|
|
static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val)
|
|
{
|
|
{
|
|
return vcnl4000_measure(data,
|
|
return vcnl4000_measure(data,
|
|
@@ -165,6 +253,11 @@ static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val)
|
|
VCNL4000_PS_RESULT_HI, val);
|
|
VCNL4000_PS_RESULT_HI, val);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val)
|
|
|
|
+{
|
|
|
|
+ return vcnl4200_measure(data, &data->vcnl4200_ps, val);
|
|
|
|
+}
|
|
|
|
+
|
|
static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
|
|
static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
|
|
[VCNL4000] = {
|
|
[VCNL4000] = {
|
|
.prod = "VCNL4000",
|
|
.prod = "VCNL4000",
|
|
@@ -178,6 +271,12 @@ static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
|
|
.measure_light = vcnl4000_measure_light,
|
|
.measure_light = vcnl4000_measure_light,
|
|
.measure_proximity = vcnl4000_measure_proximity,
|
|
.measure_proximity = vcnl4000_measure_proximity,
|
|
},
|
|
},
|
|
|
|
+ [VCNL4200] = {
|
|
|
|
+ .prod = "VCNL4200",
|
|
|
|
+ .init = vcnl4200_init,
|
|
|
|
+ .measure_light = vcnl4200_measure_light,
|
|
|
|
+ .measure_proximity = vcnl4200_measure_proximity,
|
|
|
|
+ },
|
|
};
|
|
};
|
|
|
|
|
|
static const struct iio_chan_spec vcnl4000_channels[] = {
|
|
static const struct iio_chan_spec vcnl4000_channels[] = {
|
|
@@ -246,7 +345,6 @@ static int vcnl4000_probe(struct i2c_client *client,
|
|
data->client = client;
|
|
data->client = client;
|
|
data->id = id->driver_data;
|
|
data->id = id->driver_data;
|
|
data->chip_spec = &vcnl4000_chip_spec_cfg[data->id];
|
|
data->chip_spec = &vcnl4000_chip_spec_cfg[data->id];
|
|
- mutex_init(&data->lock);
|
|
|
|
|
|
|
|
ret = data->chip_spec->init(data);
|
|
ret = data->chip_spec->init(data);
|
|
if (ret < 0)
|
|
if (ret < 0)
|