mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
195
drivers/iio/light/Kconfig
Normal file
195
drivers/iio/light/Kconfig
Normal file
|
@ -0,0 +1,195 @@
|
|||
#
|
||||
# Light sensors
|
||||
#
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
menu "Light sensors"
|
||||
|
||||
config ADJD_S311
|
||||
tristate "ADJD-S311-CR999 digital color sensor"
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the Avago ADJD-S311-CR999
|
||||
digital color light sensor.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called adjd_s311.
|
||||
|
||||
config AL3320A
|
||||
tristate "AL3320A ambient light sensor"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you want to build a driver for the Dyna Image AL3320A
|
||||
ambient light sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called al3320a.
|
||||
|
||||
config APDS9300
|
||||
tristate "APDS9300 ambient light sensor"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you want to build a driver for the Avago APDS9300
|
||||
ambient light sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called apds9300.
|
||||
|
||||
config CM32181
|
||||
depends on I2C
|
||||
tristate "CM32181 driver"
|
||||
help
|
||||
Say Y here if you use cm32181.
|
||||
This option enables ambient light sensor using
|
||||
Capella cm32181 device driver.
|
||||
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called cm32181.
|
||||
|
||||
config CM36651
|
||||
depends on I2C
|
||||
tristate "CM36651 driver"
|
||||
help
|
||||
Say Y here if you use cm36651.
|
||||
This option enables proximity & RGB sensor using
|
||||
Capella cm36651 device driver.
|
||||
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called cm36651.
|
||||
|
||||
config GP2AP020A00F
|
||||
tristate "Sharp GP2AP020A00F Proximity/ALS sensor"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select IRQ_WORK
|
||||
help
|
||||
Say Y here if you have a Sharp GP2AP020A00F proximity/ALS combo-chip
|
||||
hooked to an I2C bus.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called gp2ap020a00f.
|
||||
|
||||
config ISL29125
|
||||
tristate "Intersil ISL29125 digital color light sensor"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say Y here if you want to build a driver for the Intersil ISL29125
|
||||
RGB light sensor for I2C.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called isl29125.
|
||||
|
||||
config HID_SENSOR_ALS
|
||||
depends on HID_SENSOR_HUB
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select HID_SENSOR_IIO_COMMON
|
||||
select HID_SENSOR_IIO_TRIGGER
|
||||
tristate "HID ALS"
|
||||
help
|
||||
Say yes here to build support for the HID SENSOR
|
||||
Ambient light sensor.
|
||||
|
||||
config HID_SENSOR_PROX
|
||||
depends on HID_SENSOR_HUB
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select HID_SENSOR_IIO_COMMON
|
||||
select HID_SENSOR_IIO_TRIGGER
|
||||
tristate "HID PROX"
|
||||
help
|
||||
Say yes here to build support for the HID SENSOR
|
||||
Proximity sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-sensor-prox.
|
||||
|
||||
config SENSORS_LM3533
|
||||
tristate "LM3533 ambient light sensor"
|
||||
depends on MFD_LM3533
|
||||
help
|
||||
If you say yes here you get support for the ambient light sensor
|
||||
interface on National Semiconductor / TI LM3533 Lighting Power
|
||||
chips.
|
||||
|
||||
The sensor interface can be used to control the LEDs and backlights
|
||||
of the chip through defining five light zones and three sets of
|
||||
corresponding output-current values.
|
||||
|
||||
The driver provides raw and mean adc readings along with the current
|
||||
light zone through sysfs. A threshold event can be generated on zone
|
||||
changes. The ALS-control output values can be set per zone for the
|
||||
three current output channels.
|
||||
|
||||
config LTR501
|
||||
tristate "LTR-501ALS-01 light sensor"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
If you say yes here you get support for the Lite-On LTR-501ALS-01
|
||||
ambient light and proximity sensor.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ltr501.
|
||||
|
||||
config TCS3414
|
||||
tristate "TAOS TCS3414 digital color sensor"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
If you say yes here you get support for the TAOS TCS3414
|
||||
family of digital color sensors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tcs3414.
|
||||
|
||||
config TCS3472
|
||||
tristate "TAOS TCS3472 color light-to-digital converter"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
If you say yes here you get support for the TAOS TCS3472
|
||||
family of color light-to-digital converters with IR filter.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tcs3472.
|
||||
|
||||
config SENSORS_TSL2563
|
||||
tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the Taos TSL2560,
|
||||
TSL2561, TSL2562 and TSL2563 ambient light sensors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tsl2563.
|
||||
|
||||
config TSL4531
|
||||
tristate "TAOS TSL4531 ambient light sensors"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you want to build a driver for the TAOS TSL4531 family
|
||||
of ambient light sensors with direct lux output.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called tsl4531.
|
||||
|
||||
config VCNL4000
|
||||
tristate "VCNL4000 combined ALS and proximity sensor"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you want to build a driver for the Vishay VCNL4000
|
||||
combined ambient light and proximity sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called vcnl4000.
|
||||
|
||||
endmenu
|
21
drivers/iio/light/Makefile
Normal file
21
drivers/iio/light/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# Makefile for IIO Light sensors
|
||||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_ADJD_S311) += adjd_s311.o
|
||||
obj-$(CONFIG_AL3320A) += al3320a.o
|
||||
obj-$(CONFIG_APDS9300) += apds9300.o
|
||||
obj-$(CONFIG_CM32181) += cm32181.o
|
||||
obj-$(CONFIG_CM36651) += cm36651.o
|
||||
obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o
|
||||
obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o
|
||||
obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o
|
||||
obj-$(CONFIG_ISL29125) += isl29125.o
|
||||
obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
|
||||
obj-$(CONFIG_LTR501) += ltr501.o
|
||||
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
|
||||
obj-$(CONFIG_TCS3414) += tcs3414.o
|
||||
obj-$(CONFIG_TCS3472) += tcs3472.o
|
||||
obj-$(CONFIG_TSL4531) += tsl4531.o
|
||||
obj-$(CONFIG_VCNL4000) += vcnl4000.o
|
321
drivers/iio/light/adjd_s311.c
Normal file
321
drivers/iio/light/adjd_s311.c
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor
|
||||
*
|
||||
* Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* driver for ADJD-S311-CR999 digital color sensor (10-bit channels for
|
||||
* red, green, blue, clear); 7-bit I2C slave address 0x74
|
||||
*
|
||||
* limitations: no calibration, no offset mode, no sleep mode
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define ADJD_S311_DRV_NAME "adjd_s311"
|
||||
|
||||
#define ADJD_S311_CTRL 0x00
|
||||
#define ADJD_S311_CONFIG 0x01
|
||||
#define ADJD_S311_CAP_RED 0x06
|
||||
#define ADJD_S311_CAP_GREEN 0x07
|
||||
#define ADJD_S311_CAP_BLUE 0x08
|
||||
#define ADJD_S311_CAP_CLEAR 0x09
|
||||
#define ADJD_S311_INT_RED 0x0a
|
||||
#define ADJD_S311_INT_GREEN 0x0c
|
||||
#define ADJD_S311_INT_BLUE 0x0e
|
||||
#define ADJD_S311_INT_CLEAR 0x10
|
||||
#define ADJD_S311_DATA_RED 0x40
|
||||
#define ADJD_S311_DATA_GREEN 0x42
|
||||
#define ADJD_S311_DATA_BLUE 0x44
|
||||
#define ADJD_S311_DATA_CLEAR 0x46
|
||||
#define ADJD_S311_OFFSET_RED 0x48
|
||||
#define ADJD_S311_OFFSET_GREEN 0x49
|
||||
#define ADJD_S311_OFFSET_BLUE 0x4a
|
||||
#define ADJD_S311_OFFSET_CLEAR 0x4b
|
||||
|
||||
#define ADJD_S311_CTRL_GOFS 0x02
|
||||
#define ADJD_S311_CTRL_GSSR 0x01
|
||||
#define ADJD_S311_CAP_MASK 0x0f
|
||||
#define ADJD_S311_INT_MASK 0x0fff
|
||||
#define ADJD_S311_DATA_MASK 0x03ff
|
||||
|
||||
struct adjd_s311_data {
|
||||
struct i2c_client *client;
|
||||
u16 *buffer;
|
||||
};
|
||||
|
||||
enum adjd_s311_channel_idx {
|
||||
IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR
|
||||
};
|
||||
|
||||
#define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2)
|
||||
#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2)
|
||||
#define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan))
|
||||
|
||||
static int adjd_s311_req_data(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
int tries = 10;
|
||||
|
||||
int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL,
|
||||
ADJD_S311_CTRL_GSSR);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
while (tries--) {
|
||||
ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!(ret & ADJD_S311_CTRL_GSSR))
|
||||
break;
|
||||
msleep(20);
|
||||
}
|
||||
|
||||
if (tries < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"adjd_s311_req_data() failed, data not ready\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val)
|
||||
{
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
|
||||
int ret = adjd_s311_req_data(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(data->client, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret & ADJD_S311_DATA_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
s64 time_ns = iio_get_time_ns();
|
||||
int i, j = 0;
|
||||
|
||||
int ret = adjd_s311_req_data(indio_dev);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
for_each_set_bit(i, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
ADJD_S311_DATA_REG(i));
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
data->buffer[j++] = ret & ADJD_S311_DATA_MASK;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, time_ns);
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
#define ADJD_S311_CHANNEL(_color, _scan_idx) { \
|
||||
.type = IIO_INTENSITY, \
|
||||
.modified = 1, \
|
||||
.address = (IDX_##_color), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \
|
||||
BIT(IIO_CHAN_INFO_INT_TIME), \
|
||||
.channel2 = (IIO_MOD_LIGHT_##_color), \
|
||||
.scan_index = (_scan_idx), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 10, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec adjd_s311_channels[] = {
|
||||
ADJD_S311_CHANNEL(RED, 0),
|
||||
ADJD_S311_CHANNEL(GREEN, 1),
|
||||
ADJD_S311_CHANNEL(BLUE, 2),
|
||||
ADJD_S311_CHANNEL(CLEAR, 3),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static int adjd_s311_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = adjd_s311_read_data(indio_dev,
|
||||
ADJD_S311_DATA_REG(chan->address), val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_HARDWAREGAIN:
|
||||
ret = i2c_smbus_read_byte_data(data->client,
|
||||
ADJD_S311_CAP_REG(chan->address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret & ADJD_S311_CAP_MASK;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
ADJD_S311_INT_REG(chan->address));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = 0;
|
||||
/*
|
||||
* not documented, based on measurement:
|
||||
* 4095 LSBs correspond to roughly 4 ms
|
||||
*/
|
||||
*val2 = ret & ADJD_S311_INT_MASK;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int adjd_s311_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_HARDWAREGAIN:
|
||||
if (val < 0 || val > ADJD_S311_CAP_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_byte_data(data->client,
|
||||
ADJD_S311_CAP_REG(chan->address), val);
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_word_data(data->client,
|
||||
ADJD_S311_INT_REG(chan->address), val2);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int adjd_s311_update_scan_mode(struct iio_dev *indio_dev,
|
||||
const unsigned long *scan_mask)
|
||||
{
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
|
||||
kfree(data->buffer);
|
||||
data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
|
||||
if (data->buffer == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_info adjd_s311_info = {
|
||||
.read_raw = adjd_s311_read_raw,
|
||||
.write_raw = adjd_s311_write_raw,
|
||||
.update_scan_mode = adjd_s311_update_scan_mode,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int adjd_s311_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct adjd_s311_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int err;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &adjd_s311_info;
|
||||
indio_dev->name = ADJD_S311_DRV_NAME;
|
||||
indio_dev->channels = adjd_s311_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
err = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
adjd_s311_trigger_handler, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = iio_device_register(indio_dev);
|
||||
if (err)
|
||||
goto exit_unreg_buffer;
|
||||
|
||||
dev_info(&client->dev, "ADJD-S311 color sensor registered\n");
|
||||
|
||||
return 0;
|
||||
|
||||
exit_unreg_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int adjd_s311_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct adjd_s311_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
kfree(data->buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adjd_s311_id[] = {
|
||||
{ "adjd_s311", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adjd_s311_id);
|
||||
|
||||
static struct i2c_driver adjd_s311_driver = {
|
||||
.driver = {
|
||||
.name = ADJD_S311_DRV_NAME,
|
||||
},
|
||||
.probe = adjd_s311_probe,
|
||||
.remove = adjd_s311_remove,
|
||||
.id_table = adjd_s311_id,
|
||||
};
|
||||
module_i2c_driver(adjd_s311_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("ADJD-S311 color sensor");
|
||||
MODULE_LICENSE("GPL");
|
232
drivers/iio/light/al3320a.c
Normal file
232
drivers/iio/light/al3320a.c
Normal file
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* AL3320A - Dyna Image Ambient Light Sensor
|
||||
*
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* IIO driver for AL3320A (7-bit I2C slave address 0x1C).
|
||||
*
|
||||
* TODO: interrupt support, thresholds
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
#define AL3320A_DRV_NAME "al3320a"
|
||||
|
||||
#define AL3320A_REG_CONFIG 0x00
|
||||
#define AL3320A_REG_STATUS 0x01
|
||||
#define AL3320A_REG_INT 0x02
|
||||
#define AL3320A_REG_WAIT 0x06
|
||||
#define AL3320A_REG_CONFIG_RANGE 0x07
|
||||
#define AL3320A_REG_PERSIST 0x08
|
||||
#define AL3320A_REG_MEAN_TIME 0x09
|
||||
#define AL3320A_REG_ADUMMY 0x0A
|
||||
#define AL3320A_REG_DATA_LOW 0x22
|
||||
|
||||
#define AL3320A_REG_LOW_THRESH_LOW 0x30
|
||||
#define AL3320A_REG_LOW_THRESH_HIGH 0x31
|
||||
#define AL3320A_REG_HIGH_THRESH_LOW 0x32
|
||||
#define AL3320A_REG_HIGH_THRESH_HIGH 0x33
|
||||
|
||||
#define AL3320A_CONFIG_DISABLE 0x00
|
||||
#define AL3320A_CONFIG_ENABLE 0x01
|
||||
|
||||
#define AL3320A_GAIN_SHIFT 1
|
||||
#define AL3320A_GAIN_MASK (BIT(2) | BIT(1))
|
||||
|
||||
/* chip params default values */
|
||||
#define AL3320A_DEFAULT_MEAN_TIME 4
|
||||
#define AL3320A_DEFAULT_WAIT_TIME 0 /* no waiting */
|
||||
|
||||
#define AL3320A_SCALE_AVAILABLE "0.512 0.128 0.032 0.01"
|
||||
|
||||
enum al3320a_range {
|
||||
AL3320A_RANGE_1, /* 33.28 Klx */
|
||||
AL3320A_RANGE_2, /* 8.32 Klx */
|
||||
AL3320A_RANGE_3, /* 2.08 Klx */
|
||||
AL3320A_RANGE_4 /* 0.65 Klx */
|
||||
};
|
||||
|
||||
static const int al3320a_scales[][2] = {
|
||||
{0, 512000}, {0, 128000}, {0, 32000}, {0, 10000}
|
||||
};
|
||||
|
||||
struct al3320a_data {
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec al3320a_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
}
|
||||
};
|
||||
|
||||
static IIO_CONST_ATTR(in_illuminance_scale_available, AL3320A_SCALE_AVAILABLE);
|
||||
|
||||
static struct attribute *al3320a_attributes[] = {
|
||||
&iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group al3320a_attribute_group = {
|
||||
.attrs = al3320a_attributes,
|
||||
};
|
||||
|
||||
static int al3320a_init(struct al3320a_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* power on */
|
||||
ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG,
|
||||
AL3320A_CONFIG_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG_RANGE,
|
||||
AL3320A_RANGE_3 << AL3320A_GAIN_SHIFT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_MEAN_TIME,
|
||||
AL3320A_DEFAULT_MEAN_TIME);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_WAIT,
|
||||
AL3320A_DEFAULT_WAIT_TIME);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int al3320a_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
struct al3320a_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
/*
|
||||
* ALS ADC value is stored in two adjacent registers:
|
||||
* - low byte of output is stored at AL3320A_REG_DATA_LOW
|
||||
* - high byte of output is stored at AL3320A_REG_DATA_LOW + 1
|
||||
*/
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
AL3320A_REG_DATA_LOW);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = i2c_smbus_read_byte_data(data->client,
|
||||
AL3320A_REG_CONFIG_RANGE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & AL3320A_GAIN_MASK) >> AL3320A_GAIN_SHIFT;
|
||||
*val = al3320a_scales[ret][0];
|
||||
*val2 = al3320a_scales[ret][1];
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int al3320a_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int val,
|
||||
int val2, long mask)
|
||||
{
|
||||
struct al3320a_data *data = iio_priv(indio_dev);
|
||||
int i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
for (i = 0; i < ARRAY_SIZE(al3320a_scales); i++) {
|
||||
if (val == al3320a_scales[i][0] &&
|
||||
val2 == al3320a_scales[i][1])
|
||||
return i2c_smbus_write_byte_data(data->client,
|
||||
AL3320A_REG_CONFIG_RANGE,
|
||||
i << AL3320A_GAIN_SHIFT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_info al3320a_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = al3320a_read_raw,
|
||||
.write_raw = al3320a_write_raw,
|
||||
.attrs = &al3320a_attribute_group,
|
||||
};
|
||||
|
||||
static int al3320a_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct al3320a_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &al3320a_info;
|
||||
indio_dev->name = AL3320A_DRV_NAME;
|
||||
indio_dev->channels = al3320a_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(al3320a_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = al3320a_init(data);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "al3320a chip init failed\n");
|
||||
return ret;
|
||||
}
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static int al3320a_remove(struct i2c_client *client)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(client, AL3320A_REG_CONFIG,
|
||||
AL3320A_CONFIG_DISABLE);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id al3320a_id[] = {
|
||||
{"al3320a", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, al3320a_id);
|
||||
|
||||
static struct i2c_driver al3320a_driver = {
|
||||
.driver = {
|
||||
.name = AL3320A_DRV_NAME,
|
||||
},
|
||||
.probe = al3320a_probe,
|
||||
.remove = al3320a_remove,
|
||||
.id_table = al3320a_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(al3320a_driver);
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
|
||||
MODULE_DESCRIPTION("AL3320A Ambient Light Sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
531
drivers/iio/light/apds9300.c
Normal file
531
drivers/iio/light/apds9300.c
Normal file
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* apds9300.c - IIO driver for Avago APDS9300 ambient light sensor
|
||||
*
|
||||
* Copyright 2013 Oleksandr Kravchenko <o.v.kravchenko@globallogic.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/events.h>
|
||||
|
||||
#define APDS9300_DRV_NAME "apds9300"
|
||||
#define APDS9300_IRQ_NAME "apds9300_event"
|
||||
|
||||
/* Command register bits */
|
||||
#define APDS9300_CMD BIT(7) /* Select command register. Must write as 1 */
|
||||
#define APDS9300_WORD BIT(5) /* I2C write/read: if 1 word, if 0 byte */
|
||||
#define APDS9300_CLEAR BIT(6) /* Interrupt clear. Clears pending interrupt */
|
||||
|
||||
/* Register set */
|
||||
#define APDS9300_CONTROL 0x00 /* Control of basic functions */
|
||||
#define APDS9300_THRESHLOWLOW 0x02 /* Low byte of low interrupt threshold */
|
||||
#define APDS9300_THRESHHIGHLOW 0x04 /* Low byte of high interrupt threshold */
|
||||
#define APDS9300_INTERRUPT 0x06 /* Interrupt control */
|
||||
#define APDS9300_DATA0LOW 0x0c /* Low byte of ADC channel 0 */
|
||||
#define APDS9300_DATA1LOW 0x0e /* Low byte of ADC channel 1 */
|
||||
|
||||
/* Power on/off value for APDS9300_CONTROL register */
|
||||
#define APDS9300_POWER_ON 0x03
|
||||
#define APDS9300_POWER_OFF 0x00
|
||||
|
||||
/* Interrupts */
|
||||
#define APDS9300_INTR_ENABLE 0x10
|
||||
/* Interrupt Persist Function: Any value outside of threshold range */
|
||||
#define APDS9300_THRESH_INTR 0x01
|
||||
|
||||
#define APDS9300_THRESH_MAX 0xffff /* Max threshold value */
|
||||
|
||||
struct apds9300_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex mutex;
|
||||
int power_state;
|
||||
int thresh_low;
|
||||
int thresh_hi;
|
||||
int intr_en;
|
||||
};
|
||||
|
||||
/* Lux calculation */
|
||||
|
||||
/* Calculated values 1000 * (CH1/CH0)^1.4 for CH1/CH0 from 0 to 0.52 */
|
||||
static const u16 apds9300_lux_ratio[] = {
|
||||
0, 2, 4, 7, 11, 15, 19, 24, 29, 34, 40, 45, 51, 57, 64, 70, 77, 84, 91,
|
||||
98, 105, 112, 120, 128, 136, 144, 152, 160, 168, 177, 185, 194, 203,
|
||||
212, 221, 230, 239, 249, 258, 268, 277, 287, 297, 307, 317, 327, 337,
|
||||
347, 358, 368, 379, 390, 400,
|
||||
};
|
||||
|
||||
static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1)
|
||||
{
|
||||
unsigned long lux, tmp;
|
||||
|
||||
/* avoid division by zero */
|
||||
if (ch0 == 0)
|
||||
return 0;
|
||||
|
||||
tmp = DIV_ROUND_UP(ch1 * 100, ch0);
|
||||
if (tmp <= 52) {
|
||||
lux = 3150 * ch0 - (unsigned long)DIV_ROUND_UP_ULL(ch0
|
||||
* apds9300_lux_ratio[tmp] * 5930ull, 1000);
|
||||
} else if (tmp <= 65) {
|
||||
lux = 2290 * ch0 - 2910 * ch1;
|
||||
} else if (tmp <= 80) {
|
||||
lux = 1570 * ch0 - 1800 * ch1;
|
||||
} else if (tmp <= 130) {
|
||||
lux = 338 * ch0 - 260 * ch1;
|
||||
} else {
|
||||
lux = 0;
|
||||
}
|
||||
|
||||
return lux / 100000;
|
||||
}
|
||||
|
||||
static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number)
|
||||
{
|
||||
int ret;
|
||||
u8 flags = APDS9300_CMD | APDS9300_WORD;
|
||||
|
||||
if (!data->power_state)
|
||||
return -EBUSY;
|
||||
|
||||
/* Select ADC0 or ADC1 data register */
|
||||
flags |= adc_number ? APDS9300_DATA1LOW : APDS9300_DATA0LOW;
|
||||
|
||||
ret = i2c_smbus_read_word_data(data->client, flags);
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev,
|
||||
"failed to read ADC%d value\n", adc_number);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int apds9300_set_thresh_low(struct apds9300_data *data, int value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!data->power_state)
|
||||
return -EBUSY;
|
||||
|
||||
if (value > APDS9300_THRESH_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHLOWLOW
|
||||
| APDS9300_CMD | APDS9300_WORD, value);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "failed to set thresh_low\n");
|
||||
return ret;
|
||||
}
|
||||
data->thresh_low = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9300_set_thresh_hi(struct apds9300_data *data, int value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!data->power_state)
|
||||
return -EBUSY;
|
||||
|
||||
if (value > APDS9300_THRESH_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHHIGHLOW
|
||||
| APDS9300_CMD | APDS9300_WORD, value);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "failed to set thresh_hi\n");
|
||||
return ret;
|
||||
}
|
||||
data->thresh_hi = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9300_set_intr_state(struct apds9300_data *data, int state)
|
||||
{
|
||||
int ret;
|
||||
u8 cmd;
|
||||
|
||||
if (!data->power_state)
|
||||
return -EBUSY;
|
||||
|
||||
cmd = state ? APDS9300_INTR_ENABLE | APDS9300_THRESH_INTR : 0x00;
|
||||
ret = i2c_smbus_write_byte_data(data->client,
|
||||
APDS9300_INTERRUPT | APDS9300_CMD, cmd);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev,
|
||||
"failed to set interrupt state %d\n", state);
|
||||
return ret;
|
||||
}
|
||||
data->intr_en = state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9300_set_power_state(struct apds9300_data *data, int state)
|
||||
{
|
||||
int ret;
|
||||
u8 cmd;
|
||||
|
||||
cmd = state ? APDS9300_POWER_ON : APDS9300_POWER_OFF;
|
||||
ret = i2c_smbus_write_byte_data(data->client,
|
||||
APDS9300_CONTROL | APDS9300_CMD, cmd);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev,
|
||||
"failed to set power state %d\n", state);
|
||||
return ret;
|
||||
}
|
||||
data->power_state = state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apds9300_clear_intr(struct apds9300_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte(data->client, APDS9300_CLEAR | APDS9300_CMD);
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev, "failed to clear interrupt\n");
|
||||
}
|
||||
|
||||
static int apds9300_chip_init(struct apds9300_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Need to set power off to ensure that the chip is off */
|
||||
ret = apds9300_set_power_state(data, 0);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
/*
|
||||
* Probe the chip. To do so we try to power up the device and then to
|
||||
* read back the 0x03 code
|
||||
*/
|
||||
ret = apds9300_set_power_state(data, 1);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
ret = i2c_smbus_read_byte_data(data->client,
|
||||
APDS9300_CONTROL | APDS9300_CMD);
|
||||
if (ret != APDS9300_POWER_ON) {
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
* Disable interrupt to ensure thai it is doesn't enable
|
||||
* i.e. after device soft reset
|
||||
*/
|
||||
ret = apds9300_set_intr_state(data, 0);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
dev_err(&data->client->dev, "failed to init the chip\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int apds9300_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val, int *val2,
|
||||
long mask)
|
||||
{
|
||||
int ch0, ch1, ret = -EINVAL;
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
ch0 = apds9300_get_adc_val(data, 0);
|
||||
if (ch0 < 0) {
|
||||
ret = ch0;
|
||||
break;
|
||||
}
|
||||
ch1 = apds9300_get_adc_val(data, 1);
|
||||
if (ch1 < 0) {
|
||||
ret = ch1;
|
||||
break;
|
||||
}
|
||||
*val = apds9300_calculate_lux(ch0, ch1);
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_INTENSITY:
|
||||
ret = apds9300_get_adc_val(data, chan->channel);
|
||||
if (ret < 0)
|
||||
break;
|
||||
*val = ret;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int apds9300_read_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, enum iio_event_info info,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
*val = data->thresh_hi;
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
*val = data->thresh_low;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int apds9300_write_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, enum iio_event_info info, int val,
|
||||
int val2)
|
||||
{
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
if (dir == IIO_EV_DIR_RISING)
|
||||
ret = apds9300_set_thresh_hi(data, val);
|
||||
else
|
||||
ret = apds9300_set_thresh_low(data, val);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int apds9300_read_interrupt_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir)
|
||||
{
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
|
||||
return data->intr_en;
|
||||
}
|
||||
|
||||
static int apds9300_write_interrupt_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, int state)
|
||||
{
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = apds9300_set_intr_state(data, state);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info apds9300_info_no_irq = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = apds9300_read_raw,
|
||||
};
|
||||
|
||||
static const struct iio_info apds9300_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = apds9300_read_raw,
|
||||
.read_event_value = apds9300_read_thresh,
|
||||
.write_event_value = apds9300_write_thresh,
|
||||
.read_event_config = apds9300_read_interrupt_config,
|
||||
.write_event_config = apds9300_write_interrupt_config,
|
||||
};
|
||||
|
||||
static const struct iio_event_spec apds9300_event_spec[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
}, {
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_FALLING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
},
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec apds9300_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.channel = 0,
|
||||
.indexed = true,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
}, {
|
||||
.type = IIO_INTENSITY,
|
||||
.channel = 0,
|
||||
.channel2 = IIO_MOD_LIGHT_BOTH,
|
||||
.indexed = true,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.event_spec = apds9300_event_spec,
|
||||
.num_event_specs = ARRAY_SIZE(apds9300_event_spec),
|
||||
}, {
|
||||
.type = IIO_INTENSITY,
|
||||
.channel = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_IR,
|
||||
.indexed = true,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
},
|
||||
};
|
||||
|
||||
static irqreturn_t apds9300_interrupt_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *dev_info = private;
|
||||
struct apds9300_data *data = iio_priv(dev_info);
|
||||
|
||||
iio_push_event(dev_info,
|
||||
IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
|
||||
IIO_EV_TYPE_THRESH,
|
||||
IIO_EV_DIR_EITHER),
|
||||
iio_get_time_ns());
|
||||
|
||||
apds9300_clear_intr(data);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int apds9300_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct apds9300_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
|
||||
ret = apds9300_chip_init(data);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
mutex_init(&data->mutex);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = apds9300_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(apds9300_channels);
|
||||
indio_dev->name = APDS9300_DRV_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
if (client->irq)
|
||||
indio_dev->info = &apds9300_info;
|
||||
else
|
||||
indio_dev->info = &apds9300_info_no_irq;
|
||||
|
||||
if (client->irq) {
|
||||
ret = devm_request_threaded_irq(&client->dev, client->irq,
|
||||
NULL, apds9300_interrupt_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
APDS9300_IRQ_NAME, indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "irq request error %d\n", -ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
/* Ensure that power off in case of error */
|
||||
apds9300_set_power_state(data, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int apds9300_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
/* Ensure that power off and interrupts are disabled */
|
||||
apds9300_set_intr_state(data, 0);
|
||||
apds9300_set_power_state(data, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int apds9300_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = apds9300_set_power_state(data, 0);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int apds9300_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct apds9300_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = apds9300_set_power_state(data, 1);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend, apds9300_resume);
|
||||
#define APDS9300_PM_OPS (&apds9300_pm_ops)
|
||||
#else
|
||||
#define APDS9300_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static struct i2c_device_id apds9300_id[] = {
|
||||
{ APDS9300_DRV_NAME, 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, apds9300_id);
|
||||
|
||||
static struct i2c_driver apds9300_driver = {
|
||||
.driver = {
|
||||
.name = APDS9300_DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = APDS9300_PM_OPS,
|
||||
},
|
||||
.probe = apds9300_probe,
|
||||
.remove = apds9300_remove,
|
||||
.id_table = apds9300_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(apds9300_driver);
|
||||
|
||||
MODULE_AUTHOR("Kravchenko Oleksandr <o.v.kravchenko@globallogic.com>");
|
||||
MODULE_AUTHOR("GlobalLogic inc.");
|
||||
MODULE_DESCRIPTION("APDS9300 ambient light photo sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
371
drivers/iio/light/cm32181.c
Normal file
371
drivers/iio/light/cm32181.c
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Capella Microsystems Inc.
|
||||
* Author: Kevin Tsai <ktsai@capellamicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2, as published
|
||||
* by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
/* Registers Address */
|
||||
#define CM32181_REG_ADDR_CMD 0x00
|
||||
#define CM32181_REG_ADDR_ALS 0x04
|
||||
#define CM32181_REG_ADDR_STATUS 0x06
|
||||
#define CM32181_REG_ADDR_ID 0x07
|
||||
|
||||
/* Number of Configurable Registers */
|
||||
#define CM32181_CONF_REG_NUM 0x01
|
||||
|
||||
/* CMD register */
|
||||
#define CM32181_CMD_ALS_ENABLE 0x00
|
||||
#define CM32181_CMD_ALS_DISABLE 0x01
|
||||
#define CM32181_CMD_ALS_INT_EN 0x02
|
||||
|
||||
#define CM32181_CMD_ALS_IT_SHIFT 6
|
||||
#define CM32181_CMD_ALS_IT_MASK (0x0F << CM32181_CMD_ALS_IT_SHIFT)
|
||||
#define CM32181_CMD_ALS_IT_DEFAULT (0x00 << CM32181_CMD_ALS_IT_SHIFT)
|
||||
|
||||
#define CM32181_CMD_ALS_SM_SHIFT 11
|
||||
#define CM32181_CMD_ALS_SM_MASK (0x03 << CM32181_CMD_ALS_SM_SHIFT)
|
||||
#define CM32181_CMD_ALS_SM_DEFAULT (0x01 << CM32181_CMD_ALS_SM_SHIFT)
|
||||
|
||||
#define CM32181_MLUX_PER_BIT 5 /* ALS_SM=01 IT=800ms */
|
||||
#define CM32181_MLUX_PER_BIT_BASE_IT 800000 /* Based on IT=800ms */
|
||||
#define CM32181_CALIBSCALE_DEFAULT 1000
|
||||
#define CM32181_CALIBSCALE_RESOLUTION 1000
|
||||
#define MLUX_PER_LUX 1000
|
||||
|
||||
static const u8 cm32181_reg[CM32181_CONF_REG_NUM] = {
|
||||
CM32181_REG_ADDR_CMD,
|
||||
};
|
||||
|
||||
static const int als_it_bits[] = {12, 8, 0, 1, 2, 3};
|
||||
static const int als_it_value[] = {25000, 50000, 100000, 200000, 400000,
|
||||
800000};
|
||||
|
||||
struct cm32181_chip {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
u16 conf_regs[CM32181_CONF_REG_NUM];
|
||||
int calibscale;
|
||||
};
|
||||
|
||||
/**
|
||||
* cm32181_reg_init() - Initialize CM32181 registers
|
||||
* @cm32181: pointer of struct cm32181.
|
||||
*
|
||||
* Initialize CM32181 ambient light sensor register to default values.
|
||||
*
|
||||
* Return: 0 for success; otherwise for error code.
|
||||
*/
|
||||
static int cm32181_reg_init(struct cm32181_chip *cm32181)
|
||||
{
|
||||
struct i2c_client *client = cm32181->client;
|
||||
int i;
|
||||
s32 ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* check device ID */
|
||||
if ((ret & 0xFF) != 0x81)
|
||||
return -ENODEV;
|
||||
|
||||
/* Default Values */
|
||||
cm32181->conf_regs[CM32181_REG_ADDR_CMD] = CM32181_CMD_ALS_ENABLE |
|
||||
CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT;
|
||||
cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT;
|
||||
|
||||
/* Initialize registers*/
|
||||
for (i = 0; i < CM32181_CONF_REG_NUM; i++) {
|
||||
ret = i2c_smbus_write_word_data(client, cm32181_reg[i],
|
||||
cm32181->conf_regs[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm32181_read_als_it() - Get sensor integration time (ms)
|
||||
* @cm32181: pointer of struct cm32181
|
||||
* @val2: pointer of int to load the als_it value.
|
||||
*
|
||||
* Report the current integartion time by millisecond.
|
||||
*
|
||||
* Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL.
|
||||
*/
|
||||
static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2)
|
||||
{
|
||||
u16 als_it;
|
||||
int i;
|
||||
|
||||
als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD];
|
||||
als_it &= CM32181_CMD_ALS_IT_MASK;
|
||||
als_it >>= CM32181_CMD_ALS_IT_SHIFT;
|
||||
for (i = 0; i < ARRAY_SIZE(als_it_bits); i++) {
|
||||
if (als_it == als_it_bits[i]) {
|
||||
*val2 = als_it_value[i];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm32181_write_als_it() - Write sensor integration time
|
||||
* @cm32181: pointer of struct cm32181.
|
||||
* @val: integration time by millisecond.
|
||||
*
|
||||
* Convert integration time (ms) to sensor value.
|
||||
*
|
||||
* Return: i2c_smbus_write_word_data command return value.
|
||||
*/
|
||||
static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val)
|
||||
{
|
||||
struct i2c_client *client = cm32181->client;
|
||||
u16 als_it;
|
||||
int ret, i, n;
|
||||
|
||||
n = ARRAY_SIZE(als_it_value);
|
||||
for (i = 0; i < n; i++)
|
||||
if (val <= als_it_value[i])
|
||||
break;
|
||||
if (i >= n)
|
||||
i = n - 1;
|
||||
|
||||
als_it = als_it_bits[i];
|
||||
als_it <<= CM32181_CMD_ALS_IT_SHIFT;
|
||||
|
||||
mutex_lock(&cm32181->lock);
|
||||
cm32181->conf_regs[CM32181_REG_ADDR_CMD] &=
|
||||
~CM32181_CMD_ALS_IT_MASK;
|
||||
cm32181->conf_regs[CM32181_REG_ADDR_CMD] |=
|
||||
als_it;
|
||||
ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD,
|
||||
cm32181->conf_regs[CM32181_REG_ADDR_CMD]);
|
||||
mutex_unlock(&cm32181->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm32181_get_lux() - report current lux value
|
||||
* @cm32181: pointer of struct cm32181.
|
||||
*
|
||||
* Convert sensor raw data to lux. It depends on integration
|
||||
* time and claibscale variable.
|
||||
*
|
||||
* Return: Positive value is lux, otherwise is error code.
|
||||
*/
|
||||
static int cm32181_get_lux(struct cm32181_chip *cm32181)
|
||||
{
|
||||
struct i2c_client *client = cm32181->client;
|
||||
int ret;
|
||||
int als_it;
|
||||
unsigned long lux;
|
||||
|
||||
ret = cm32181_read_als_it(cm32181, &als_it);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
|
||||
lux = CM32181_MLUX_PER_BIT;
|
||||
lux *= CM32181_MLUX_PER_BIT_BASE_IT;
|
||||
lux /= als_it;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
lux *= ret;
|
||||
lux *= cm32181->calibscale;
|
||||
lux /= CM32181_CALIBSCALE_RESOLUTION;
|
||||
lux /= MLUX_PER_LUX;
|
||||
|
||||
if (lux > 0xFFFF)
|
||||
lux = 0xFFFF;
|
||||
|
||||
return lux;
|
||||
}
|
||||
|
||||
static int cm32181_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct cm32181_chip *cm32181 = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
ret = cm32181_get_lux(cm32181);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
*val = cm32181->calibscale;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
*val = 0;
|
||||
ret = cm32181_read_als_it(cm32181, val2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int cm32181_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct cm32181_chip *cm32181 = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
cm32181->calibscale = val;
|
||||
return val;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
ret = cm32181_write_als_it(cm32181, val2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm32181_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 values by millisecond.
|
||||
*
|
||||
* Return: string length.
|
||||
*/
|
||||
static ssize_t cm32181_get_it_available(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int i, n, len;
|
||||
|
||||
n = ARRAY_SIZE(als_it_value);
|
||||
for (i = 0, len = 0; i < n; i++)
|
||||
len += sprintf(buf + len, "0.%06u ", als_it_value[i]);
|
||||
return len + sprintf(buf + len, "\n");
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec cm32181_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, cm32181_get_it_available, NULL, 0);
|
||||
|
||||
static struct attribute *cm32181_attributes[] = {
|
||||
&iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group cm32181_attribute_group = {
|
||||
.attrs = cm32181_attributes
|
||||
};
|
||||
|
||||
static const struct iio_info cm32181_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &cm32181_read_raw,
|
||||
.write_raw = &cm32181_write_raw,
|
||||
.attrs = &cm32181_attribute_group,
|
||||
};
|
||||
|
||||
static int cm32181_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct cm32181_chip *cm32181;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm32181));
|
||||
if (!indio_dev) {
|
||||
dev_err(&client->dev, "devm_iio_device_alloc failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
cm32181 = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
cm32181->client = client;
|
||||
|
||||
mutex_init(&cm32181->lock);
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = cm32181_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(cm32181_channels);
|
||||
indio_dev->info = &cm32181_info;
|
||||
indio_dev->name = id->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = cm32181_reg_init(cm32181);
|
||||
if (ret) {
|
||||
dev_err(&client->dev,
|
||||
"%s: register init failed\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_iio_device_register(&client->dev, indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&client->dev,
|
||||
"%s: regist device failed\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id cm32181_id[] = {
|
||||
{ "cm32181", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, cm32181_id);
|
||||
|
||||
static const struct of_device_id cm32181_of_match[] = {
|
||||
{ .compatible = "capella,cm32181" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_driver cm32181_driver = {
|
||||
.driver = {
|
||||
.name = "cm32181",
|
||||
.of_match_table = of_match_ptr(cm32181_of_match),
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.id_table = cm32181_id,
|
||||
.probe = cm32181_probe,
|
||||
};
|
||||
|
||||
module_i2c_driver(cm32181_driver);
|
||||
|
||||
MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>");
|
||||
MODULE_DESCRIPTION("CM32181 ambient light sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
750
drivers/iio/light/cm36651.c
Normal file
750
drivers/iio/light/cm36651.c
Normal file
|
@ -0,0 +1,750 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
||||
* Author: Beomho Seo <beomho.seo@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2, as published
|
||||
* by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/events.h>
|
||||
|
||||
/* Slave address 0x19 for PS of 7 bit addressing protocol for I2C */
|
||||
#define CM36651_I2C_ADDR_PS 0x19
|
||||
/* Alert Response Address */
|
||||
#define CM36651_ARA 0x0C
|
||||
|
||||
/* Ambient light sensor */
|
||||
#define CM36651_CS_CONF1 0x00
|
||||
#define CM36651_CS_CONF2 0x01
|
||||
#define CM36651_ALS_WH_M 0x02
|
||||
#define CM36651_ALS_WH_L 0x03
|
||||
#define CM36651_ALS_WL_M 0x04
|
||||
#define CM36651_ALS_WL_L 0x05
|
||||
#define CM36651_CS_CONF3 0x06
|
||||
#define CM36651_CS_CONF_REG_NUM 0x02
|
||||
|
||||
/* Proximity sensor */
|
||||
#define CM36651_PS_CONF1 0x00
|
||||
#define CM36651_PS_THD 0x01
|
||||
#define CM36651_PS_CANC 0x02
|
||||
#define CM36651_PS_CONF2 0x03
|
||||
#define CM36651_PS_REG_NUM 0x04
|
||||
|
||||
/* CS_CONF1 command code */
|
||||
#define CM36651_ALS_ENABLE 0x00
|
||||
#define CM36651_ALS_DISABLE 0x01
|
||||
#define CM36651_ALS_INT_EN 0x02
|
||||
#define CM36651_ALS_THRES 0x04
|
||||
|
||||
/* CS_CONF2 command code */
|
||||
#define CM36651_CS_CONF2_DEFAULT_BIT 0x08
|
||||
|
||||
/* CS_CONF3 channel integration time */
|
||||
#define CM36651_CS_IT1 0x00 /* Integration time 80 msec */
|
||||
#define CM36651_CS_IT2 0x40 /* Integration time 160 msec */
|
||||
#define CM36651_CS_IT3 0x80 /* Integration time 320 msec */
|
||||
#define CM36651_CS_IT4 0xC0 /* Integration time 640 msec */
|
||||
|
||||
/* PS_CONF1 command code */
|
||||
#define CM36651_PS_ENABLE 0x00
|
||||
#define CM36651_PS_DISABLE 0x01
|
||||
#define CM36651_PS_INT_EN 0x02
|
||||
#define CM36651_PS_PERS2 0x04
|
||||
#define CM36651_PS_PERS3 0x08
|
||||
#define CM36651_PS_PERS4 0x0C
|
||||
|
||||
/* PS_CONF1 command code: integration time */
|
||||
#define CM36651_PS_IT1 0x00 /* Integration time 0.32 msec */
|
||||
#define CM36651_PS_IT2 0x10 /* Integration time 0.42 msec */
|
||||
#define CM36651_PS_IT3 0x20 /* Integration time 0.52 msec */
|
||||
#define CM36651_PS_IT4 0x30 /* Integration time 0.64 msec */
|
||||
|
||||
/* PS_CONF1 command code: duty ratio */
|
||||
#define CM36651_PS_DR1 0x00 /* Duty ratio 1/80 */
|
||||
#define CM36651_PS_DR2 0x40 /* Duty ratio 1/160 */
|
||||
#define CM36651_PS_DR3 0x80 /* Duty ratio 1/320 */
|
||||
#define CM36651_PS_DR4 0xC0 /* Duty ratio 1/640 */
|
||||
|
||||
/* PS_THD command code */
|
||||
#define CM36651_PS_INITIAL_THD 0x05
|
||||
|
||||
/* PS_CANC command code */
|
||||
#define CM36651_PS_CANC_DEFAULT 0x00
|
||||
|
||||
/* PS_CONF2 command code */
|
||||
#define CM36651_PS_HYS1 0x00
|
||||
#define CM36651_PS_HYS2 0x01
|
||||
#define CM36651_PS_SMART_PERS_EN 0x02
|
||||
#define CM36651_PS_DIR_INT 0x04
|
||||
#define CM36651_PS_MS 0x10
|
||||
|
||||
#define CM36651_CS_COLOR_NUM 4
|
||||
|
||||
#define CM36651_CLOSE_PROXIMITY 0x32
|
||||
#define CM36651_FAR_PROXIMITY 0x33
|
||||
|
||||
#define CM36651_CS_INT_TIME_AVAIL "0.08 0.16 0.32 0.64"
|
||||
#define CM36651_PS_INT_TIME_AVAIL "0.000320 0.000420 0.000520 0.000640"
|
||||
|
||||
enum cm36651_operation_mode {
|
||||
CM36651_LIGHT_EN,
|
||||
CM36651_PROXIMITY_EN,
|
||||
CM36651_PROXIMITY_EV_EN,
|
||||
};
|
||||
|
||||
enum cm36651_light_channel_idx {
|
||||
CM36651_LIGHT_CHANNEL_IDX_RED,
|
||||
CM36651_LIGHT_CHANNEL_IDX_GREEN,
|
||||
CM36651_LIGHT_CHANNEL_IDX_BLUE,
|
||||
CM36651_LIGHT_CHANNEL_IDX_CLEAR,
|
||||
};
|
||||
|
||||
enum cm36651_command {
|
||||
CM36651_CMD_READ_RAW_LIGHT,
|
||||
CM36651_CMD_READ_RAW_PROXIMITY,
|
||||
CM36651_CMD_PROX_EV_EN,
|
||||
CM36651_CMD_PROX_EV_DIS,
|
||||
};
|
||||
|
||||
static const u8 cm36651_cs_reg[CM36651_CS_CONF_REG_NUM] = {
|
||||
CM36651_CS_CONF1,
|
||||
CM36651_CS_CONF2,
|
||||
};
|
||||
|
||||
static const u8 cm36651_ps_reg[CM36651_PS_REG_NUM] = {
|
||||
CM36651_PS_CONF1,
|
||||
CM36651_PS_THD,
|
||||
CM36651_PS_CANC,
|
||||
CM36651_PS_CONF2,
|
||||
};
|
||||
|
||||
struct cm36651_data {
|
||||
const struct cm36651_platform_data *pdata;
|
||||
struct i2c_client *client;
|
||||
struct i2c_client *ps_client;
|
||||
struct i2c_client *ara_client;
|
||||
struct mutex lock;
|
||||
struct regulator *vled_reg;
|
||||
unsigned long flags;
|
||||
int cs_int_time[CM36651_CS_COLOR_NUM];
|
||||
int ps_int_time;
|
||||
u8 cs_ctrl_regs[CM36651_CS_CONF_REG_NUM];
|
||||
u8 ps_ctrl_regs[CM36651_PS_REG_NUM];
|
||||
u16 color[CM36651_CS_COLOR_NUM];
|
||||
};
|
||||
|
||||
static int cm36651_setup_reg(struct cm36651_data *cm36651)
|
||||
{
|
||||
struct i2c_client *client = cm36651->client;
|
||||
struct i2c_client *ps_client = cm36651->ps_client;
|
||||
int i, ret;
|
||||
|
||||
/* CS initialization */
|
||||
cm36651->cs_ctrl_regs[CM36651_CS_CONF1] = CM36651_ALS_ENABLE |
|
||||
CM36651_ALS_THRES;
|
||||
cm36651->cs_ctrl_regs[CM36651_CS_CONF2] = CM36651_CS_CONF2_DEFAULT_BIT;
|
||||
|
||||
for (i = 0; i < CM36651_CS_CONF_REG_NUM; i++) {
|
||||
ret = i2c_smbus_write_byte_data(client, cm36651_cs_reg[i],
|
||||
cm36651->cs_ctrl_regs[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* PS initialization */
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_CONF1] = CM36651_PS_ENABLE |
|
||||
CM36651_PS_IT2;
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_THD] = CM36651_PS_INITIAL_THD;
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_CANC] = CM36651_PS_CANC_DEFAULT;
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_CONF2] = CM36651_PS_HYS2 |
|
||||
CM36651_PS_DIR_INT | CM36651_PS_SMART_PERS_EN;
|
||||
|
||||
for (i = 0; i < CM36651_PS_REG_NUM; i++) {
|
||||
ret = i2c_smbus_write_byte_data(ps_client, cm36651_ps_reg[i],
|
||||
cm36651->ps_ctrl_regs[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set shutdown mode */
|
||||
ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1,
|
||||
CM36651_ALS_DISABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(cm36651->ps_client,
|
||||
CM36651_PS_CONF1, CM36651_PS_DISABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cm36651_read_output(struct cm36651_data *cm36651,
|
||||
struct iio_chan_spec const *chan, int *val)
|
||||
{
|
||||
struct i2c_client *client = cm36651->client;
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
*val = i2c_smbus_read_word_data(client, chan->address);
|
||||
if (*val < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1,
|
||||
CM36651_ALS_DISABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
*val = i2c_smbus_read_byte(cm36651->ps_client);
|
||||
if (*val < 0)
|
||||
return ret;
|
||||
|
||||
if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) {
|
||||
ret = i2c_smbus_write_byte_data(cm36651->ps_client,
|
||||
CM36651_PS_CONF1, CM36651_PS_DISABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t cm36651_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct iio_dev *indio_dev = data;
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
struct i2c_client *client = cm36651->client;
|
||||
int ev_dir, ret;
|
||||
u64 ev_code;
|
||||
|
||||
/*
|
||||
* The PS INT pin is an active low signal that PS INT move logic low
|
||||
* when the object is detect. Once the MCU host received the PS INT
|
||||
* "LOW" signal, the Host needs to read the data at Alert Response
|
||||
* Address(ARA) to clear the PS INT signal. After clearing the PS
|
||||
* INT pin, the PS INT signal toggles from low to high.
|
||||
*/
|
||||
ret = i2c_smbus_read_byte(cm36651->ara_client);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
"%s: Data read failed: %d\n", __func__, ret);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
switch (ret) {
|
||||
case CM36651_CLOSE_PROXIMITY:
|
||||
ev_dir = IIO_EV_DIR_RISING;
|
||||
break;
|
||||
case CM36651_FAR_PROXIMITY:
|
||||
ev_dir = IIO_EV_DIR_FALLING;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"%s: Data read wrong: %d\n", __func__, ret);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY,
|
||||
CM36651_CMD_READ_RAW_PROXIMITY,
|
||||
IIO_EV_TYPE_THRESH, ev_dir);
|
||||
|
||||
iio_push_event(indio_dev, ev_code, iio_get_time_ns());
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int cm36651_set_operation_mode(struct cm36651_data *cm36651, int cmd)
|
||||
{
|
||||
struct i2c_client *client = cm36651->client;
|
||||
struct i2c_client *ps_client = cm36651->ps_client;
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (cmd) {
|
||||
case CM36651_CMD_READ_RAW_LIGHT:
|
||||
ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1,
|
||||
cm36651->cs_ctrl_regs[CM36651_CS_CONF1]);
|
||||
break;
|
||||
case CM36651_CMD_READ_RAW_PROXIMITY:
|
||||
if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags))
|
||||
return CM36651_PROXIMITY_EV_EN;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(ps_client, CM36651_PS_CONF1,
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_CONF1]);
|
||||
break;
|
||||
case CM36651_CMD_PROX_EV_EN:
|
||||
if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) {
|
||||
dev_err(&client->dev,
|
||||
"Already proximity event enable state\n");
|
||||
return ret;
|
||||
}
|
||||
set_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags);
|
||||
|
||||
ret = i2c_smbus_write_byte_data(ps_client,
|
||||
cm36651_ps_reg[CM36651_PS_CONF1],
|
||||
CM36651_PS_INT_EN | CM36651_PS_PERS2 | CM36651_PS_IT2);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Proximity enable event failed\n");
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case CM36651_CMD_PROX_EV_DIS:
|
||||
if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) {
|
||||
dev_err(&client->dev,
|
||||
"Already proximity event disable state\n");
|
||||
return ret;
|
||||
}
|
||||
clear_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags);
|
||||
ret = i2c_smbus_write_byte_data(ps_client,
|
||||
CM36651_PS_CONF1, CM36651_PS_DISABLE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "Write register failed\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_read_channel(struct cm36651_data *cm36651,
|
||||
struct iio_chan_spec const *chan, int *val)
|
||||
{
|
||||
struct i2c_client *client = cm36651->client;
|
||||
int cmd, ret;
|
||||
|
||||
if (chan->type == IIO_LIGHT)
|
||||
cmd = CM36651_CMD_READ_RAW_LIGHT;
|
||||
else if (chan->type == IIO_PROXIMITY)
|
||||
cmd = CM36651_CMD_READ_RAW_PROXIMITY;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
ret = cm36651_set_operation_mode(cm36651, cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "CM36651 set operation mode failed\n");
|
||||
return ret;
|
||||
}
|
||||
/* Delay for work after enable operation */
|
||||
msleep(50);
|
||||
ret = cm36651_read_output(cm36651, chan, val);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "CM36651 read output failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_read_int_time(struct cm36651_data *cm36651,
|
||||
struct iio_chan_spec const *chan, int *val2)
|
||||
{
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT1)
|
||||
*val2 = 80000;
|
||||
else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT2)
|
||||
*val2 = 160000;
|
||||
else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT3)
|
||||
*val2 = 320000;
|
||||
else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT4)
|
||||
*val2 = 640000;
|
||||
else
|
||||
return -EINVAL;
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
if (cm36651->ps_int_time == CM36651_PS_IT1)
|
||||
*val2 = 320;
|
||||
else if (cm36651->ps_int_time == CM36651_PS_IT2)
|
||||
*val2 = 420;
|
||||
else if (cm36651->ps_int_time == CM36651_PS_IT3)
|
||||
*val2 = 520;
|
||||
else if (cm36651->ps_int_time == CM36651_PS_IT4)
|
||||
*val2 = 640;
|
||||
else
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
static int cm36651_write_int_time(struct cm36651_data *cm36651,
|
||||
struct iio_chan_spec const *chan, int val)
|
||||
{
|
||||
struct i2c_client *client = cm36651->client;
|
||||
struct i2c_client *ps_client = cm36651->ps_client;
|
||||
int int_time, ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
if (val == 80000)
|
||||
int_time = CM36651_CS_IT1;
|
||||
else if (val == 160000)
|
||||
int_time = CM36651_CS_IT2;
|
||||
else if (val == 320000)
|
||||
int_time = CM36651_CS_IT3;
|
||||
else if (val == 640000)
|
||||
int_time = CM36651_CS_IT4;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF3,
|
||||
int_time >> 2 * (chan->address));
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "CS integration time write failed\n");
|
||||
return ret;
|
||||
}
|
||||
cm36651->cs_int_time[chan->address] = int_time;
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
if (val == 320)
|
||||
int_time = CM36651_PS_IT1;
|
||||
else if (val == 420)
|
||||
int_time = CM36651_PS_IT2;
|
||||
else if (val == 520)
|
||||
int_time = CM36651_PS_IT3;
|
||||
else if (val == 640)
|
||||
int_time = CM36651_PS_IT4;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(ps_client,
|
||||
CM36651_PS_CONF1, int_time);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "PS integration time write failed\n");
|
||||
return ret;
|
||||
}
|
||||
cm36651->ps_int_time = int_time;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cm36651->lock);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = cm36651_read_channel(cm36651, chan, val);
|
||||
break;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
*val = 0;
|
||||
ret = cm36651_read_int_time(cm36651, chan, val2);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
mutex_unlock(&cm36651->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
struct i2c_client *client = cm36651->client;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (mask == IIO_CHAN_INFO_INT_TIME) {
|
||||
ret = cm36651_write_int_time(cm36651, chan, val2);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "Integration time write failed\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_read_prox_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
enum iio_event_info info,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
|
||||
*val = cm36651->ps_ctrl_regs[CM36651_PS_THD];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cm36651_write_prox_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
enum iio_event_info info,
|
||||
int val, int val2)
|
||||
{
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
struct i2c_client *client = cm36651->client;
|
||||
int ret;
|
||||
|
||||
if (val < 3 || val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_THD] = val;
|
||||
ret = i2c_smbus_write_byte_data(cm36651->ps_client, CM36651_PS_THD,
|
||||
cm36651->ps_ctrl_regs[CM36651_PS_THD]);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "PS threshold write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cm36651_write_prox_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
int state)
|
||||
{
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
int cmd, ret = -EINVAL;
|
||||
|
||||
mutex_lock(&cm36651->lock);
|
||||
|
||||
cmd = state ? CM36651_CMD_PROX_EV_EN : CM36651_CMD_PROX_EV_DIS;
|
||||
ret = cm36651_set_operation_mode(cm36651, cmd);
|
||||
|
||||
mutex_unlock(&cm36651->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_read_prox_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir)
|
||||
{
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
int event_en;
|
||||
|
||||
mutex_lock(&cm36651->lock);
|
||||
|
||||
event_en = test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags);
|
||||
|
||||
mutex_unlock(&cm36651->lock);
|
||||
|
||||
return event_en;
|
||||
}
|
||||
|
||||
#define CM36651_LIGHT_CHANNEL(_color, _idx) { \
|
||||
.type = IIO_LIGHT, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_INT_TIME), \
|
||||
.address = _idx, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_LIGHT_##_color, \
|
||||
} \
|
||||
|
||||
static const struct iio_event_spec cm36651_event_spec[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_EITHER,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
}
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec cm36651_channels[] = {
|
||||
{
|
||||
.type = IIO_PROXIMITY,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME),
|
||||
.event_spec = cm36651_event_spec,
|
||||
.num_event_specs = ARRAY_SIZE(cm36651_event_spec),
|
||||
},
|
||||
CM36651_LIGHT_CHANNEL(RED, CM36651_LIGHT_CHANNEL_IDX_RED),
|
||||
CM36651_LIGHT_CHANNEL(GREEN, CM36651_LIGHT_CHANNEL_IDX_GREEN),
|
||||
CM36651_LIGHT_CHANNEL(BLUE, CM36651_LIGHT_CHANNEL_IDX_BLUE),
|
||||
CM36651_LIGHT_CHANNEL(CLEAR, CM36651_LIGHT_CHANNEL_IDX_CLEAR),
|
||||
};
|
||||
|
||||
static IIO_CONST_ATTR(in_illuminance_integration_time_available,
|
||||
CM36651_CS_INT_TIME_AVAIL);
|
||||
static IIO_CONST_ATTR(in_proximity_integration_time_available,
|
||||
CM36651_PS_INT_TIME_AVAIL);
|
||||
|
||||
static struct attribute *cm36651_attributes[] = {
|
||||
&iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr,
|
||||
&iio_const_attr_in_proximity_integration_time_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group cm36651_attribute_group = {
|
||||
.attrs = cm36651_attributes
|
||||
};
|
||||
|
||||
static const struct iio_info cm36651_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &cm36651_read_raw,
|
||||
.write_raw = &cm36651_write_raw,
|
||||
.read_event_value = &cm36651_read_prox_thresh,
|
||||
.write_event_value = &cm36651_write_prox_thresh,
|
||||
.read_event_config = &cm36651_read_prox_event_config,
|
||||
.write_event_config = &cm36651_write_prox_event_config,
|
||||
.attrs = &cm36651_attribute_group,
|
||||
};
|
||||
|
||||
static int cm36651_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct cm36651_data *cm36651;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm36651));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
cm36651 = iio_priv(indio_dev);
|
||||
|
||||
cm36651->vled_reg = devm_regulator_get(&client->dev, "vled");
|
||||
if (IS_ERR(cm36651->vled_reg)) {
|
||||
dev_err(&client->dev, "get regulator vled failed\n");
|
||||
return PTR_ERR(cm36651->vled_reg);
|
||||
}
|
||||
|
||||
ret = regulator_enable(cm36651->vled_reg);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "enable regulator vled failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
cm36651->client = client;
|
||||
cm36651->ps_client = i2c_new_dummy(client->adapter,
|
||||
CM36651_I2C_ADDR_PS);
|
||||
if (!cm36651->ps_client) {
|
||||
dev_err(&client->dev, "%s: new i2c device failed\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto error_disable_reg;
|
||||
}
|
||||
|
||||
cm36651->ara_client = i2c_new_dummy(client->adapter, CM36651_ARA);
|
||||
if (!cm36651->ara_client) {
|
||||
dev_err(&client->dev, "%s: new i2c device failed\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto error_i2c_unregister_ps;
|
||||
}
|
||||
|
||||
mutex_init(&cm36651->lock);
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = cm36651_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(cm36651_channels);
|
||||
indio_dev->info = &cm36651_info;
|
||||
indio_dev->name = id->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = cm36651_setup_reg(cm36651);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "%s: register setup failed\n", __func__);
|
||||
goto error_i2c_unregister_ara;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(client->irq, NULL, cm36651_irq_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"cm36651", indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "%s: request irq failed\n", __func__);
|
||||
goto error_i2c_unregister_ara;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "%s: regist device failed\n", __func__);
|
||||
goto error_free_irq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_free_irq:
|
||||
free_irq(client->irq, indio_dev);
|
||||
error_i2c_unregister_ara:
|
||||
i2c_unregister_device(cm36651->ara_client);
|
||||
error_i2c_unregister_ps:
|
||||
i2c_unregister_device(cm36651->ps_client);
|
||||
error_disable_reg:
|
||||
regulator_disable(cm36651->vled_reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cm36651_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct cm36651_data *cm36651 = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
regulator_disable(cm36651->vled_reg);
|
||||
free_irq(client->irq, indio_dev);
|
||||
i2c_unregister_device(cm36651->ps_client);
|
||||
i2c_unregister_device(cm36651->ara_client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id cm36651_id[] = {
|
||||
{ "cm36651", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, cm36651_id);
|
||||
|
||||
static const struct of_device_id cm36651_of_match[] = {
|
||||
{ .compatible = "capella,cm36651" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_driver cm36651_driver = {
|
||||
.driver = {
|
||||
.name = "cm36651",
|
||||
.of_match_table = cm36651_of_match,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = cm36651_probe,
|
||||
.remove = cm36651_remove,
|
||||
.id_table = cm36651_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(cm36651_driver);
|
||||
|
||||
MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>");
|
||||
MODULE_DESCRIPTION("CM36651 proximity/ambient light sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
1654
drivers/iio/light/gp2ap020a00f.c
Normal file
1654
drivers/iio/light/gp2ap020a00f.c
Normal file
File diff suppressed because it is too large
Load diff
392
drivers/iio/light/hid-sensor-als.c
Normal file
392
drivers/iio/light/hid-sensor-als.c
Normal file
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* HID Sensors Driver
|
||||
* Copyright (c) 2012, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hid-sensor-hub.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include "../common/hid-sensors/hid-sensor-trigger.h"
|
||||
|
||||
#define CHANNEL_SCAN_INDEX_ILLUM 0
|
||||
|
||||
struct als_state {
|
||||
struct hid_sensor_hub_callbacks callbacks;
|
||||
struct hid_sensor_common common_attributes;
|
||||
struct hid_sensor_hub_attribute_info als_illum;
|
||||
u32 illum;
|
||||
int scale_pre_decml;
|
||||
int scale_post_decml;
|
||||
int scale_precision;
|
||||
int value_offset;
|
||||
};
|
||||
|
||||
/* Channel definitions */
|
||||
static const struct iio_chan_spec als_channels[] = {
|
||||
{
|
||||
.type = IIO_INTENSITY,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_BOTH,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) |
|
||||
BIT(IIO_CHAN_INFO_HYSTERESIS),
|
||||
.scan_index = CHANNEL_SCAN_INDEX_ILLUM,
|
||||
}
|
||||
};
|
||||
|
||||
/* Adjust channel real bits based on report descriptor */
|
||||
static void als_adjust_channel_bit_mask(struct iio_chan_spec *channels,
|
||||
int channel, int size)
|
||||
{
|
||||
channels[channel].scan_type.sign = 's';
|
||||
/* Real storage bits will change based on the report desc. */
|
||||
channels[channel].scan_type.realbits = size * 8;
|
||||
/* Maximum size of a sample to capture is u32 */
|
||||
channels[channel].scan_type.storagebits = sizeof(u32) * 8;
|
||||
}
|
||||
|
||||
/* Channel read_raw handler */
|
||||
static int als_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2,
|
||||
long mask)
|
||||
{
|
||||
struct als_state *als_state = iio_priv(indio_dev);
|
||||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case 0:
|
||||
switch (chan->scan_index) {
|
||||
case CHANNEL_SCAN_INDEX_ILLUM:
|
||||
report_id = als_state->als_illum.report_id;
|
||||
address =
|
||||
HID_USAGE_SENSOR_LIGHT_ILLUM;
|
||||
break;
|
||||
default:
|
||||
report_id = -1;
|
||||
break;
|
||||
}
|
||||
if (report_id >= 0) {
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&als_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&als_state->common_attributes,
|
||||
true);
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
*val = sensor_hub_input_attr_get_raw_value(
|
||||
als_state->common_attributes.hsdev,
|
||||
HID_USAGE_SENSOR_ALS, address,
|
||||
report_id);
|
||||
hid_sensor_power_state(&als_state->common_attributes,
|
||||
false);
|
||||
} else {
|
||||
*val = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = als_state->scale_pre_decml;
|
||||
*val2 = als_state->scale_post_decml;
|
||||
ret_type = als_state->scale_precision;
|
||||
break;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = als_state->value_offset;
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret_type = hid_sensor_read_samp_freq_value(
|
||||
&als_state->common_attributes, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_HYSTERESIS:
|
||||
ret_type = hid_sensor_read_raw_hyst_value(
|
||||
&als_state->common_attributes, val, val2);
|
||||
break;
|
||||
default:
|
||||
ret_type = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret_type;
|
||||
}
|
||||
|
||||
/* Channel write_raw handler */
|
||||
static int als_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val,
|
||||
int val2,
|
||||
long mask)
|
||||
{
|
||||
struct als_state *als_state = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = hid_sensor_write_samp_freq_value(
|
||||
&als_state->common_attributes, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_HYSTERESIS:
|
||||
ret = hid_sensor_write_raw_hyst_value(
|
||||
&als_state->common_attributes, val, val2);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info als_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &als_read_raw,
|
||||
.write_raw = &als_write_raw,
|
||||
};
|
||||
|
||||
/* Function to push data to buffer */
|
||||
static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data,
|
||||
int len)
|
||||
{
|
||||
dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
|
||||
iio_push_to_buffers(indio_dev, data);
|
||||
}
|
||||
|
||||
/* Callback handler to send event after all samples are received and captured */
|
||||
static int als_proc_event(struct hid_sensor_hub_device *hsdev,
|
||||
unsigned usage_id,
|
||||
void *priv)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(priv);
|
||||
struct als_state *als_state = iio_priv(indio_dev);
|
||||
|
||||
dev_dbg(&indio_dev->dev, "als_proc_event\n");
|
||||
if (atomic_read(&als_state->common_attributes.data_ready))
|
||||
hid_sensor_push_data(indio_dev,
|
||||
&als_state->illum,
|
||||
sizeof(als_state->illum));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Capture samples in local storage */
|
||||
static int als_capture_sample(struct hid_sensor_hub_device *hsdev,
|
||||
unsigned usage_id,
|
||||
size_t raw_len, char *raw_data,
|
||||
void *priv)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(priv);
|
||||
struct als_state *als_state = iio_priv(indio_dev);
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (usage_id) {
|
||||
case HID_USAGE_SENSOR_LIGHT_ILLUM:
|
||||
als_state->illum = *(u32 *)raw_data;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse report which is specific to an usage id*/
|
||||
static int als_parse_report(struct platform_device *pdev,
|
||||
struct hid_sensor_hub_device *hsdev,
|
||||
struct iio_chan_spec *channels,
|
||||
unsigned usage_id,
|
||||
struct als_state *st)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
|
||||
usage_id,
|
||||
HID_USAGE_SENSOR_LIGHT_ILLUM,
|
||||
&st->als_illum);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_ILLUM,
|
||||
st->als_illum.size);
|
||||
|
||||
dev_dbg(&pdev->dev, "als %x:%x\n", st->als_illum.index,
|
||||
st->als_illum.report_id);
|
||||
|
||||
st->scale_precision = hid_sensor_format_scale(
|
||||
HID_USAGE_SENSOR_ALS,
|
||||
&st->als_illum,
|
||||
&st->scale_pre_decml, &st->scale_post_decml);
|
||||
|
||||
/* Set Sensitivity field ids, when there is no individual modifier */
|
||||
if (st->common_attributes.sensitivity.index < 0) {
|
||||
sensor_hub_input_get_attribute_info(hsdev,
|
||||
HID_FEATURE_REPORT, usage_id,
|
||||
HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
|
||||
HID_USAGE_SENSOR_DATA_LIGHT,
|
||||
&st->common_attributes.sensitivity);
|
||||
dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
|
||||
st->common_attributes.sensitivity.index,
|
||||
st->common_attributes.sensitivity.report_id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Function to initialize the processing for usage id */
|
||||
static int hid_als_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
static const char *name = "als";
|
||||
struct iio_dev *indio_dev;
|
||||
struct als_state *als_state;
|
||||
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
|
||||
struct iio_chan_spec *channels;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
als_state = iio_priv(indio_dev);
|
||||
als_state->common_attributes.hsdev = hsdev;
|
||||
als_state->common_attributes.pdev = pdev;
|
||||
|
||||
ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_ALS,
|
||||
&als_state->common_attributes);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to setup common attributes\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
channels = kmemdup(als_channels, sizeof(als_channels), GFP_KERNEL);
|
||||
if (!channels) {
|
||||
dev_err(&pdev->dev, "failed to duplicate channels\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = als_parse_report(pdev, hsdev, channels,
|
||||
HID_USAGE_SENSOR_ALS, als_state);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to setup attributes\n");
|
||||
goto error_free_dev_mem;
|
||||
}
|
||||
|
||||
indio_dev->channels = channels;
|
||||
indio_dev->num_channels =
|
||||
ARRAY_SIZE(als_channels);
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->info = &als_info;
|
||||
indio_dev->name = name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
|
||||
NULL, NULL);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to initialize trigger buffer\n");
|
||||
goto error_free_dev_mem;
|
||||
}
|
||||
atomic_set(&als_state->common_attributes.data_ready, 0);
|
||||
ret = hid_sensor_setup_trigger(indio_dev, name,
|
||||
&als_state->common_attributes);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "trigger setup failed\n");
|
||||
goto error_unreg_buffer_funcs;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "device register failed\n");
|
||||
goto error_remove_trigger;
|
||||
}
|
||||
|
||||
als_state->callbacks.send_event = als_proc_event;
|
||||
als_state->callbacks.capture_sample = als_capture_sample;
|
||||
als_state->callbacks.pdev = pdev;
|
||||
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ALS,
|
||||
&als_state->callbacks);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "callback reg failed\n");
|
||||
goto error_iio_unreg;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
error_iio_unreg:
|
||||
iio_device_unregister(indio_dev);
|
||||
error_remove_trigger:
|
||||
hid_sensor_remove_trigger(&als_state->common_attributes);
|
||||
error_unreg_buffer_funcs:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
error_free_dev_mem:
|
||||
kfree(indio_dev->channels);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Function to deinitialize the processing for usage id */
|
||||
static int hid_als_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct als_state *als_state = iio_priv(indio_dev);
|
||||
|
||||
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS);
|
||||
iio_device_unregister(indio_dev);
|
||||
hid_sensor_remove_trigger(&als_state->common_attributes);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
kfree(indio_dev->channels);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_device_id hid_als_ids[] = {
|
||||
{
|
||||
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
|
||||
.name = "HID-SENSOR-200041",
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, hid_als_ids);
|
||||
|
||||
static struct platform_driver hid_als_platform_driver = {
|
||||
.id_table = hid_als_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = hid_als_probe,
|
||||
.remove = hid_als_remove,
|
||||
};
|
||||
module_platform_driver(hid_als_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("HID Sensor ALS");
|
||||
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
|
||||
MODULE_LICENSE("GPL");
|
384
drivers/iio/light/hid-sensor-prox.c
Normal file
384
drivers/iio/light/hid-sensor-prox.c
Normal file
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* HID Sensors Driver
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program.
|
||||
*
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hid-sensor-hub.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include "../common/hid-sensors/hid-sensor-trigger.h"
|
||||
|
||||
#define CHANNEL_SCAN_INDEX_PRESENCE 0
|
||||
|
||||
struct prox_state {
|
||||
struct hid_sensor_hub_callbacks callbacks;
|
||||
struct hid_sensor_common common_attributes;
|
||||
struct hid_sensor_hub_attribute_info prox_attr;
|
||||
u32 human_presence;
|
||||
};
|
||||
|
||||
/* Channel definitions */
|
||||
static const struct iio_chan_spec prox_channels[] = {
|
||||
{
|
||||
.type = IIO_PROXIMITY,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_NO_MOD,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) |
|
||||
BIT(IIO_CHAN_INFO_HYSTERESIS),
|
||||
.scan_index = CHANNEL_SCAN_INDEX_PRESENCE,
|
||||
}
|
||||
};
|
||||
|
||||
/* Adjust channel real bits based on report descriptor */
|
||||
static void prox_adjust_channel_bit_mask(struct iio_chan_spec *channels,
|
||||
int channel, int size)
|
||||
{
|
||||
channels[channel].scan_type.sign = 's';
|
||||
/* Real storage bits will change based on the report desc. */
|
||||
channels[channel].scan_type.realbits = size * 8;
|
||||
/* Maximum size of a sample to capture is u32 */
|
||||
channels[channel].scan_type.storagebits = sizeof(u32) * 8;
|
||||
}
|
||||
|
||||
/* Channel read_raw handler */
|
||||
static int prox_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2,
|
||||
long mask)
|
||||
{
|
||||
struct prox_state *prox_state = iio_priv(indio_dev);
|
||||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
switch (chan->scan_index) {
|
||||
case CHANNEL_SCAN_INDEX_PRESENCE:
|
||||
report_id = prox_state->prox_attr.report_id;
|
||||
address =
|
||||
HID_USAGE_SENSOR_HUMAN_PRESENCE;
|
||||
break;
|
||||
default:
|
||||
report_id = -1;
|
||||
break;
|
||||
}
|
||||
if (report_id >= 0) {
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&prox_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&prox_state->common_attributes,
|
||||
true);
|
||||
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
*val = sensor_hub_input_attr_get_raw_value(
|
||||
prox_state->common_attributes.hsdev,
|
||||
HID_USAGE_SENSOR_PROX, address,
|
||||
report_id);
|
||||
hid_sensor_power_state(&prox_state->common_attributes,
|
||||
false);
|
||||
} else {
|
||||
*val = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = prox_state->prox_attr.units;
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = hid_sensor_convert_exponent(
|
||||
prox_state->prox_attr.unit_expo);
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret_type = hid_sensor_read_samp_freq_value(
|
||||
&prox_state->common_attributes, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_HYSTERESIS:
|
||||
ret_type = hid_sensor_read_raw_hyst_value(
|
||||
&prox_state->common_attributes, val, val2);
|
||||
break;
|
||||
default:
|
||||
ret_type = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret_type;
|
||||
}
|
||||
|
||||
/* Channel write_raw handler */
|
||||
static int prox_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val,
|
||||
int val2,
|
||||
long mask)
|
||||
{
|
||||
struct prox_state *prox_state = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = hid_sensor_write_samp_freq_value(
|
||||
&prox_state->common_attributes, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_HYSTERESIS:
|
||||
ret = hid_sensor_write_raw_hyst_value(
|
||||
&prox_state->common_attributes, val, val2);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info prox_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &prox_read_raw,
|
||||
.write_raw = &prox_write_raw,
|
||||
};
|
||||
|
||||
/* Function to push data to buffer */
|
||||
static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data,
|
||||
int len)
|
||||
{
|
||||
dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
|
||||
iio_push_to_buffers(indio_dev, data);
|
||||
}
|
||||
|
||||
/* Callback handler to send event after all samples are received and captured */
|
||||
static int prox_proc_event(struct hid_sensor_hub_device *hsdev,
|
||||
unsigned usage_id,
|
||||
void *priv)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(priv);
|
||||
struct prox_state *prox_state = iio_priv(indio_dev);
|
||||
|
||||
dev_dbg(&indio_dev->dev, "prox_proc_event\n");
|
||||
if (atomic_read(&prox_state->common_attributes.data_ready))
|
||||
hid_sensor_push_data(indio_dev,
|
||||
&prox_state->human_presence,
|
||||
sizeof(prox_state->human_presence));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Capture samples in local storage */
|
||||
static int prox_capture_sample(struct hid_sensor_hub_device *hsdev,
|
||||
unsigned usage_id,
|
||||
size_t raw_len, char *raw_data,
|
||||
void *priv)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(priv);
|
||||
struct prox_state *prox_state = iio_priv(indio_dev);
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (usage_id) {
|
||||
case HID_USAGE_SENSOR_HUMAN_PRESENCE:
|
||||
prox_state->human_presence = *(u32 *)raw_data;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse report which is specific to an usage id*/
|
||||
static int prox_parse_report(struct platform_device *pdev,
|
||||
struct hid_sensor_hub_device *hsdev,
|
||||
struct iio_chan_spec *channels,
|
||||
unsigned usage_id,
|
||||
struct prox_state *st)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
|
||||
usage_id,
|
||||
HID_USAGE_SENSOR_HUMAN_PRESENCE,
|
||||
&st->prox_attr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
prox_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_PRESENCE,
|
||||
st->prox_attr.size);
|
||||
|
||||
dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr.index,
|
||||
st->prox_attr.report_id);
|
||||
|
||||
/* Set Sensitivity field ids, when there is no individual modifier */
|
||||
if (st->common_attributes.sensitivity.index < 0) {
|
||||
sensor_hub_input_get_attribute_info(hsdev,
|
||||
HID_FEATURE_REPORT, usage_id,
|
||||
HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
|
||||
HID_USAGE_SENSOR_DATA_PRESENCE,
|
||||
&st->common_attributes.sensitivity);
|
||||
dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
|
||||
st->common_attributes.sensitivity.index,
|
||||
st->common_attributes.sensitivity.report_id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Function to initialize the processing for usage id */
|
||||
static int hid_prox_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
static const char *name = "prox";
|
||||
struct iio_dev *indio_dev;
|
||||
struct prox_state *prox_state;
|
||||
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
|
||||
struct iio_chan_spec *channels;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev,
|
||||
sizeof(struct prox_state));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
prox_state = iio_priv(indio_dev);
|
||||
prox_state->common_attributes.hsdev = hsdev;
|
||||
prox_state->common_attributes.pdev = pdev;
|
||||
|
||||
ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_PROX,
|
||||
&prox_state->common_attributes);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to setup common attributes\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
channels = kmemdup(prox_channels, sizeof(prox_channels), GFP_KERNEL);
|
||||
if (!channels) {
|
||||
dev_err(&pdev->dev, "failed to duplicate channels\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = prox_parse_report(pdev, hsdev, channels,
|
||||
HID_USAGE_SENSOR_PROX, prox_state);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to setup attributes\n");
|
||||
goto error_free_dev_mem;
|
||||
}
|
||||
|
||||
indio_dev->channels = channels;
|
||||
indio_dev->num_channels =
|
||||
ARRAY_SIZE(prox_channels);
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->info = &prox_info;
|
||||
indio_dev->name = name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
|
||||
NULL, NULL);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to initialize trigger buffer\n");
|
||||
goto error_free_dev_mem;
|
||||
}
|
||||
atomic_set(&prox_state->common_attributes.data_ready, 0);
|
||||
ret = hid_sensor_setup_trigger(indio_dev, name,
|
||||
&prox_state->common_attributes);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "trigger setup failed\n");
|
||||
goto error_unreg_buffer_funcs;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "device register failed\n");
|
||||
goto error_remove_trigger;
|
||||
}
|
||||
|
||||
prox_state->callbacks.send_event = prox_proc_event;
|
||||
prox_state->callbacks.capture_sample = prox_capture_sample;
|
||||
prox_state->callbacks.pdev = pdev;
|
||||
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PROX,
|
||||
&prox_state->callbacks);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "callback reg failed\n");
|
||||
goto error_iio_unreg;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
error_iio_unreg:
|
||||
iio_device_unregister(indio_dev);
|
||||
error_remove_trigger:
|
||||
hid_sensor_remove_trigger(&prox_state->common_attributes);
|
||||
error_unreg_buffer_funcs:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
error_free_dev_mem:
|
||||
kfree(indio_dev->channels);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Function to deinitialize the processing for usage id */
|
||||
static int hid_prox_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct prox_state *prox_state = iio_priv(indio_dev);
|
||||
|
||||
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PROX);
|
||||
iio_device_unregister(indio_dev);
|
||||
hid_sensor_remove_trigger(&prox_state->common_attributes);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
kfree(indio_dev->channels);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_device_id hid_prox_ids[] = {
|
||||
{
|
||||
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
|
||||
.name = "HID-SENSOR-200011",
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, hid_prox_ids);
|
||||
|
||||
static struct platform_driver hid_prox_platform_driver = {
|
||||
.id_table = hid_prox_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = hid_prox_probe,
|
||||
.remove = hid_prox_remove,
|
||||
};
|
||||
module_platform_driver(hid_prox_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("HID Sensor Proximity");
|
||||
MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>");
|
||||
MODULE_LICENSE("GPL");
|
347
drivers/iio/light/isl29125.c
Normal file
347
drivers/iio/light/isl29125.c
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* isl29125.c - Support for Intersil ISL29125 RGB light sensor
|
||||
*
|
||||
* Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* RGB light sensor with 16-bit channels for red, green, blue);
|
||||
* 7-bit I2C slave address 0x44
|
||||
*
|
||||
* TODO: interrupt support, IR compensation, thresholds, 12bit
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define ISL29125_DRV_NAME "isl29125"
|
||||
|
||||
#define ISL29125_DEVICE_ID 0x00
|
||||
#define ISL29125_CONF1 0x01
|
||||
#define ISL29125_CONF2 0x02
|
||||
#define ISL29125_CONF3 0x03
|
||||
#define ISL29125_STATUS 0x08
|
||||
#define ISL29125_GREEN_DATA 0x09
|
||||
#define ISL29125_RED_DATA 0x0b
|
||||
#define ISL29125_BLUE_DATA 0x0d
|
||||
|
||||
#define ISL29125_ID 0x7d
|
||||
|
||||
#define ISL29125_MODE_MASK GENMASK(2, 0)
|
||||
#define ISL29125_MODE_PD 0x0
|
||||
#define ISL29125_MODE_G 0x1
|
||||
#define ISL29125_MODE_R 0x2
|
||||
#define ISL29125_MODE_B 0x3
|
||||
#define ISL29125_MODE_RGB 0x5
|
||||
|
||||
#define ISL29125_MODE_RANGE BIT(3)
|
||||
|
||||
#define ISL29125_STATUS_CONV BIT(1)
|
||||
|
||||
struct isl29125_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
u8 conf1;
|
||||
u16 buffer[8]; /* 3x 16-bit, padding, 8 bytes timestamp */
|
||||
};
|
||||
|
||||
#define ISL29125_CHANNEL(_color, _si) { \
|
||||
.type = IIO_INTENSITY, \
|
||||
.modified = 1, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.channel2 = IIO_MOD_LIGHT_##_color, \
|
||||
.scan_index = _si, \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec isl29125_channels[] = {
|
||||
ISL29125_CHANNEL(GREEN, 0),
|
||||
ISL29125_CHANNEL(RED, 1),
|
||||
ISL29125_CHANNEL(BLUE, 2),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
static const struct {
|
||||
u8 mode, data;
|
||||
} isl29125_regs[] = {
|
||||
{ISL29125_MODE_G, ISL29125_GREEN_DATA},
|
||||
{ISL29125_MODE_R, ISL29125_RED_DATA},
|
||||
{ISL29125_MODE_B, ISL29125_BLUE_DATA},
|
||||
};
|
||||
|
||||
static int isl29125_read_data(struct isl29125_data *data, int si)
|
||||
{
|
||||
int tries = 5;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
data->conf1 | isl29125_regs[si].mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
msleep(101);
|
||||
|
||||
while (tries--) {
|
||||
ret = i2c_smbus_read_byte_data(data->client, ISL29125_STATUS);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
if (ret & ISL29125_STATUS_CONV)
|
||||
break;
|
||||
msleep(20);
|
||||
}
|
||||
|
||||
if (tries < 0) {
|
||||
dev_err(&data->client->dev, "data not ready\n");
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_word_data(data->client, isl29125_regs[si].data);
|
||||
|
||||
fail:
|
||||
i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, data->conf1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isl29125_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct isl29125_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
mutex_lock(&data->lock);
|
||||
ret = isl29125_read_data(data, chan->scan_index);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
if (data->conf1 & ISL29125_MODE_RANGE)
|
||||
*val2 = 152590; /* 10k lux full range */
|
||||
else
|
||||
*val2 = 5722; /* 375 lux full range */
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int isl29125_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct isl29125_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
if (val2 == 152590)
|
||||
data->conf1 |= ISL29125_MODE_RANGE;
|
||||
else if (val2 == 5722)
|
||||
data->conf1 &= ~ISL29125_MODE_RANGE;
|
||||
else
|
||||
return -EINVAL;
|
||||
return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
data->conf1);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t isl29125_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct isl29125_data *data = iio_priv(indio_dev);
|
||||
int i, j = 0;
|
||||
|
||||
for_each_set_bit(i, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
int ret = i2c_smbus_read_word_data(data->client,
|
||||
isl29125_regs[i].data);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
data->buffer[j++] = ret;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
|
||||
iio_get_time_ns());
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct iio_info isl29125_info = {
|
||||
.read_raw = isl29125_read_raw,
|
||||
.write_raw = isl29125_write_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int isl29125_buffer_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct isl29125_data *data = iio_priv(indio_dev);
|
||||
|
||||
data->conf1 |= ISL29125_MODE_RGB;
|
||||
return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
data->conf1);
|
||||
}
|
||||
|
||||
static int isl29125_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct isl29125_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = iio_triggered_buffer_predisable(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->conf1 &= ~ISL29125_MODE_MASK;
|
||||
data->conf1 |= ISL29125_MODE_PD;
|
||||
return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
data->conf1);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = {
|
||||
.preenable = isl29125_buffer_preenable,
|
||||
.postenable = &iio_triggered_buffer_postenable,
|
||||
.predisable = isl29125_buffer_predisable,
|
||||
};
|
||||
|
||||
static int isl29125_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct isl29125_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &isl29125_info;
|
||||
indio_dev->name = ISL29125_DRV_NAME;
|
||||
indio_dev->channels = isl29125_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(isl29125_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, ISL29125_DEVICE_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != ISL29125_ID)
|
||||
return -ENODEV;
|
||||
|
||||
data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE;
|
||||
ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
data->conf1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, ISL29125_STATUS, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
isl29125_trigger_handler, &isl29125_buffer_setup_ops);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto buffer_cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isl29125_powerdown(struct isl29125_data *data)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
(data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD);
|
||||
}
|
||||
|
||||
static int isl29125_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
isl29125_powerdown(iio_priv(indio_dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int isl29125_suspend(struct device *dev)
|
||||
{
|
||||
struct isl29125_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return isl29125_powerdown(data);
|
||||
}
|
||||
|
||||
static int isl29125_resume(struct device *dev)
|
||||
{
|
||||
struct isl29125_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
|
||||
data->conf1);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, isl29125_resume);
|
||||
|
||||
static const struct i2c_device_id isl29125_id[] = {
|
||||
{ "isl29125", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, isl29125_id);
|
||||
|
||||
static struct i2c_driver isl29125_driver = {
|
||||
.driver = {
|
||||
.name = ISL29125_DRV_NAME,
|
||||
.pm = &isl29125_pm_ops,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = isl29125_probe,
|
||||
.remove = isl29125_remove,
|
||||
.id_table = isl29125_id,
|
||||
};
|
||||
module_i2c_driver(isl29125_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("ISL29125 RGB light sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
927
drivers/iio/light/lm3533-als.c
Normal file
927
drivers/iio/light/lm3533-als.c
Normal file
|
@ -0,0 +1,927 @@
|
|||
/*
|
||||
* lm3533-als.c -- LM3533 Ambient Light Sensor driver
|
||||
*
|
||||
* Copyright (C) 2011-2012 Texas Instruments
|
||||
*
|
||||
* Author: Johan Hovold <jhovold@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <linux/mfd/lm3533.h>
|
||||
|
||||
|
||||
#define LM3533_ALS_RESISTOR_MIN 1
|
||||
#define LM3533_ALS_RESISTOR_MAX 127
|
||||
#define LM3533_ALS_CHANNEL_CURRENT_MAX 2
|
||||
#define LM3533_ALS_THRESH_MAX 3
|
||||
#define LM3533_ALS_ZONE_MAX 4
|
||||
|
||||
#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
|
||||
#define LM3533_REG_ALS_CONF 0x31
|
||||
#define LM3533_REG_ALS_ZONE_INFO 0x34
|
||||
#define LM3533_REG_ALS_READ_ADC_RAW 0x37
|
||||
#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
|
||||
#define LM3533_REG_ALS_BOUNDARY_BASE 0x50
|
||||
#define LM3533_REG_ALS_TARGET_BASE 0x60
|
||||
|
||||
#define LM3533_ALS_ENABLE_MASK 0x01
|
||||
#define LM3533_ALS_INPUT_MODE_MASK 0x02
|
||||
#define LM3533_ALS_INT_ENABLE_MASK 0x01
|
||||
|
||||
#define LM3533_ALS_ZONE_SHIFT 2
|
||||
#define LM3533_ALS_ZONE_MASK 0x1c
|
||||
|
||||
#define LM3533_ALS_FLAG_INT_ENABLED 1
|
||||
|
||||
|
||||
struct lm3533_als {
|
||||
struct lm3533 *lm3533;
|
||||
struct platform_device *pdev;
|
||||
|
||||
unsigned long flags;
|
||||
int irq;
|
||||
|
||||
atomic_t zone;
|
||||
struct mutex thresh_mutex;
|
||||
};
|
||||
|
||||
|
||||
static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
|
||||
int *adc)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 reg;
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
if (average)
|
||||
reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
|
||||
else
|
||||
reg = LM3533_REG_ALS_READ_ADC_RAW;
|
||||
|
||||
ret = lm3533_read(als->lm3533, reg, &val);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "failed to read adc\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*adc = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "failed to read zone\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
|
||||
*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
|
||||
*zone = atomic_read(&als->zone);
|
||||
} else {
|
||||
ret = _lm3533_als_get_zone(indio_dev, zone);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* channel output channel 0..2
|
||||
* zone zone 0..4
|
||||
*/
|
||||
static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
|
||||
{
|
||||
return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
|
||||
}
|
||||
|
||||
static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
|
||||
unsigned zone, u8 *val)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 reg;
|
||||
int ret;
|
||||
|
||||
if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (zone > LM3533_ALS_ZONE_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
reg = lm3533_als_get_target_reg(channel, zone);
|
||||
ret = lm3533_read(als->lm3533, reg, val);
|
||||
if (ret)
|
||||
dev_err(&indio_dev->dev, "failed to get target current\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
|
||||
unsigned zone, u8 val)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 reg;
|
||||
int ret;
|
||||
|
||||
if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (zone > LM3533_ALS_ZONE_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
reg = lm3533_als_get_target_reg(channel, zone);
|
||||
ret = lm3533_write(als->lm3533, reg, val);
|
||||
if (ret)
|
||||
dev_err(&indio_dev->dev, "failed to set target current\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
|
||||
int *val)
|
||||
{
|
||||
u8 zone;
|
||||
u8 target;
|
||||
int ret;
|
||||
|
||||
ret = lm3533_als_get_zone(indio_dev, &zone);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lm3533_als_get_target(indio_dev, channel, zone, &target);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = target;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case 0:
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
ret = lm3533_als_get_adc(indio_dev, false, val);
|
||||
break;
|
||||
case IIO_CURRENT:
|
||||
ret = lm3533_als_get_current(indio_dev, chan->channel,
|
||||
val);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_AVERAGE_RAW:
|
||||
ret = lm3533_als_get_adc(indio_dev, true, val);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
#define CHANNEL_CURRENT(_channel) \
|
||||
{ \
|
||||
.type = IIO_CURRENT, \
|
||||
.channel = _channel, \
|
||||
.indexed = true, \
|
||||
.output = true, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec lm3533_als_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.channel = 0,
|
||||
.indexed = true,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) |
|
||||
BIT(IIO_CHAN_INFO_RAW),
|
||||
},
|
||||
CHANNEL_CURRENT(0),
|
||||
CHANNEL_CURRENT(1),
|
||||
CHANNEL_CURRENT(2),
|
||||
};
|
||||
|
||||
static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
|
||||
{
|
||||
|
||||
struct iio_dev *indio_dev = dev_id;
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 zone;
|
||||
int ret;
|
||||
|
||||
/* Clear interrupt by reading the ALS zone register. */
|
||||
ret = _lm3533_als_get_zone(indio_dev, &zone);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
atomic_set(&als->zone, zone);
|
||||
|
||||
iio_push_event(indio_dev,
|
||||
IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
|
||||
0,
|
||||
IIO_EV_TYPE_THRESH,
|
||||
IIO_EV_DIR_EITHER),
|
||||
iio_get_time_ns());
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 mask = LM3533_ALS_INT_ENABLE_MASK;
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
if (enable)
|
||||
val = mask;
|
||||
else
|
||||
val = 0;
|
||||
|
||||
ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "failed to set int mode %d\n",
|
||||
enable);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 mask = LM3533_ALS_INT_ENABLE_MASK;
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "failed to get int mode\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*enable = !!(val & mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
|
||||
{
|
||||
u8 offset = !raising;
|
||||
|
||||
return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
|
||||
}
|
||||
|
||||
static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
|
||||
bool raising, u8 *val)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 reg;
|
||||
int ret;
|
||||
|
||||
if (nr > LM3533_ALS_THRESH_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
reg = lm3533_als_get_threshold_reg(nr, raising);
|
||||
ret = lm3533_read(als->lm3533, reg, val);
|
||||
if (ret)
|
||||
dev_err(&indio_dev->dev, "failed to get threshold\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
|
||||
bool raising, u8 val)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 val2;
|
||||
u8 reg, reg2;
|
||||
int ret;
|
||||
|
||||
if (nr > LM3533_ALS_THRESH_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
reg = lm3533_als_get_threshold_reg(nr, raising);
|
||||
reg2 = lm3533_als_get_threshold_reg(nr, !raising);
|
||||
|
||||
mutex_lock(&als->thresh_mutex);
|
||||
ret = lm3533_read(als->lm3533, reg2, &val2);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "failed to get threshold\n");
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* This device does not allow negative hysteresis (in fact, it uses
|
||||
* whichever value is smaller as the lower bound) so we need to make
|
||||
* sure that thresh_falling <= thresh_raising.
|
||||
*/
|
||||
if ((raising && (val < val2)) || (!raising && (val > val2))) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = lm3533_write(als->lm3533, reg, val);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "failed to set threshold\n");
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&als->thresh_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
|
||||
u8 *val)
|
||||
{
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
u8 falling;
|
||||
u8 raising;
|
||||
int ret;
|
||||
|
||||
if (nr > LM3533_ALS_THRESH_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&als->thresh_mutex);
|
||||
ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
|
||||
if (ret)
|
||||
goto out;
|
||||
ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
*val = raising - falling;
|
||||
out:
|
||||
mutex_unlock(&als->thresh_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t show_thresh_either_en(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
int enable;
|
||||
int ret;
|
||||
|
||||
if (als->irq) {
|
||||
ret = lm3533_als_get_int_mode(indio_dev, &enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
enable = 0;
|
||||
}
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
|
||||
}
|
||||
|
||||
static ssize_t store_thresh_either_en(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
unsigned long enable;
|
||||
bool int_enabled;
|
||||
u8 zone;
|
||||
int ret;
|
||||
|
||||
if (!als->irq)
|
||||
return -EBUSY;
|
||||
|
||||
if (kstrtoul(buf, 0, &enable))
|
||||
return -EINVAL;
|
||||
|
||||
int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
|
||||
|
||||
if (enable && !int_enabled) {
|
||||
ret = lm3533_als_get_zone(indio_dev, &zone);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
atomic_set(&als->zone, zone);
|
||||
|
||||
set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
|
||||
}
|
||||
|
||||
ret = lm3533_als_set_int_mode(indio_dev, enable);
|
||||
if (ret) {
|
||||
if (!int_enabled)
|
||||
clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!enable)
|
||||
clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t show_zone(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
u8 zone;
|
||||
int ret;
|
||||
|
||||
ret = lm3533_als_get_zone(indio_dev, &zone);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
|
||||
}
|
||||
|
||||
enum lm3533_als_attribute_type {
|
||||
LM3533_ATTR_TYPE_HYSTERESIS,
|
||||
LM3533_ATTR_TYPE_TARGET,
|
||||
LM3533_ATTR_TYPE_THRESH_FALLING,
|
||||
LM3533_ATTR_TYPE_THRESH_RAISING,
|
||||
};
|
||||
|
||||
struct lm3533_als_attribute {
|
||||
struct device_attribute dev_attr;
|
||||
enum lm3533_als_attribute_type type;
|
||||
u8 val1;
|
||||
u8 val2;
|
||||
};
|
||||
|
||||
static inline struct lm3533_als_attribute *
|
||||
to_lm3533_als_attr(struct device_attribute *attr)
|
||||
{
|
||||
return container_of(attr, struct lm3533_als_attribute, dev_attr);
|
||||
}
|
||||
|
||||
static ssize_t show_als_attr(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
switch (als_attr->type) {
|
||||
case LM3533_ATTR_TYPE_HYSTERESIS:
|
||||
ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
|
||||
&val);
|
||||
break;
|
||||
case LM3533_ATTR_TYPE_TARGET:
|
||||
ret = lm3533_als_get_target(indio_dev, als_attr->val1,
|
||||
als_attr->val2, &val);
|
||||
break;
|
||||
case LM3533_ATTR_TYPE_THRESH_FALLING:
|
||||
ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
|
||||
false, &val);
|
||||
break;
|
||||
case LM3533_ATTR_TYPE_THRESH_RAISING:
|
||||
ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
|
||||
true, &val);
|
||||
break;
|
||||
default:
|
||||
ret = -ENXIO;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", val);
|
||||
}
|
||||
|
||||
static ssize_t store_als_attr(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
if (kstrtou8(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
switch (als_attr->type) {
|
||||
case LM3533_ATTR_TYPE_TARGET:
|
||||
ret = lm3533_als_set_target(indio_dev, als_attr->val1,
|
||||
als_attr->val2, val);
|
||||
break;
|
||||
case LM3533_ATTR_TYPE_THRESH_FALLING:
|
||||
ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
|
||||
false, val);
|
||||
break;
|
||||
case LM3533_ATTR_TYPE_THRESH_RAISING:
|
||||
ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
|
||||
true, val);
|
||||
break;
|
||||
default:
|
||||
ret = -ENXIO;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
|
||||
{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
|
||||
.type = _type, \
|
||||
.val1 = _val1, \
|
||||
.val2 = _val2 }
|
||||
|
||||
#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
|
||||
struct lm3533_als_attribute lm3533_als_attr_##_name = \
|
||||
ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
|
||||
|
||||
#define ALS_TARGET_ATTR_RW(_channel, _zone) \
|
||||
LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \
|
||||
S_IRUGO | S_IWUSR, \
|
||||
show_als_attr, store_als_attr, \
|
||||
LM3533_ATTR_TYPE_TARGET, _channel, _zone)
|
||||
/*
|
||||
* ALS output current values (ALS mapper targets)
|
||||
*
|
||||
* out_current[0-2]_current[0-4]_raw 0-255
|
||||
*/
|
||||
static ALS_TARGET_ATTR_RW(0, 0);
|
||||
static ALS_TARGET_ATTR_RW(0, 1);
|
||||
static ALS_TARGET_ATTR_RW(0, 2);
|
||||
static ALS_TARGET_ATTR_RW(0, 3);
|
||||
static ALS_TARGET_ATTR_RW(0, 4);
|
||||
|
||||
static ALS_TARGET_ATTR_RW(1, 0);
|
||||
static ALS_TARGET_ATTR_RW(1, 1);
|
||||
static ALS_TARGET_ATTR_RW(1, 2);
|
||||
static ALS_TARGET_ATTR_RW(1, 3);
|
||||
static ALS_TARGET_ATTR_RW(1, 4);
|
||||
|
||||
static ALS_TARGET_ATTR_RW(2, 0);
|
||||
static ALS_TARGET_ATTR_RW(2, 1);
|
||||
static ALS_TARGET_ATTR_RW(2, 2);
|
||||
static ALS_TARGET_ATTR_RW(2, 3);
|
||||
static ALS_TARGET_ATTR_RW(2, 4);
|
||||
|
||||
#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
|
||||
LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \
|
||||
S_IRUGO | S_IWUSR, \
|
||||
show_als_attr, store_als_attr, \
|
||||
LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
|
||||
|
||||
#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
|
||||
LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \
|
||||
S_IRUGO | S_IWUSR, \
|
||||
show_als_attr, store_als_attr, \
|
||||
LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
|
||||
/*
|
||||
* ALS Zone thresholds (boundaries)
|
||||
*
|
||||
* in_illuminance0_thresh[0-3]_falling_value 0-255
|
||||
* in_illuminance0_thresh[0-3]_raising_value 0-255
|
||||
*/
|
||||
static ALS_THRESH_FALLING_ATTR_RW(0);
|
||||
static ALS_THRESH_FALLING_ATTR_RW(1);
|
||||
static ALS_THRESH_FALLING_ATTR_RW(2);
|
||||
static ALS_THRESH_FALLING_ATTR_RW(3);
|
||||
|
||||
static ALS_THRESH_RAISING_ATTR_RW(0);
|
||||
static ALS_THRESH_RAISING_ATTR_RW(1);
|
||||
static ALS_THRESH_RAISING_ATTR_RW(2);
|
||||
static ALS_THRESH_RAISING_ATTR_RW(3);
|
||||
|
||||
#define ALS_HYSTERESIS_ATTR_RO(_nr) \
|
||||
LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \
|
||||
S_IRUGO, show_als_attr, NULL, \
|
||||
LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
|
||||
/*
|
||||
* ALS Zone threshold hysteresis
|
||||
*
|
||||
* threshY_hysteresis = threshY_raising - threshY_falling
|
||||
*
|
||||
* in_illuminance0_thresh[0-3]_hysteresis 0-255
|
||||
* in_illuminance0_thresh[0-3]_hysteresis 0-255
|
||||
*/
|
||||
static ALS_HYSTERESIS_ATTR_RO(0);
|
||||
static ALS_HYSTERESIS_ATTR_RO(1);
|
||||
static ALS_HYSTERESIS_ATTR_RO(2);
|
||||
static ALS_HYSTERESIS_ATTR_RO(3);
|
||||
|
||||
#define ILLUMINANCE_ATTR_RO(_name) \
|
||||
DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
|
||||
#define ILLUMINANCE_ATTR_RW(_name) \
|
||||
DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
|
||||
show_##_name, store_##_name)
|
||||
/*
|
||||
* ALS Zone threshold-event enable
|
||||
*
|
||||
* in_illuminance0_thresh_either_en 0,1
|
||||
*/
|
||||
static ILLUMINANCE_ATTR_RW(thresh_either_en);
|
||||
|
||||
/*
|
||||
* ALS Current Zone
|
||||
*
|
||||
* in_illuminance0_zone 0-4
|
||||
*/
|
||||
static ILLUMINANCE_ATTR_RO(zone);
|
||||
|
||||
static struct attribute *lm3533_als_event_attributes[] = {
|
||||
&dev_attr_in_illuminance0_thresh_either_en.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
|
||||
&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group lm3533_als_event_attribute_group = {
|
||||
.attrs = lm3533_als_event_attributes
|
||||
};
|
||||
|
||||
static struct attribute *lm3533_als_attributes[] = {
|
||||
&dev_attr_in_illuminance0_zone.attr,
|
||||
&lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
|
||||
&lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group lm3533_als_attribute_group = {
|
||||
.attrs = lm3533_als_attributes
|
||||
};
|
||||
|
||||
static int lm3533_als_set_input_mode(struct lm3533_als *als, bool pwm_mode)
|
||||
{
|
||||
u8 mask = LM3533_ALS_INPUT_MODE_MASK;
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
if (pwm_mode)
|
||||
val = mask; /* pwm input */
|
||||
else
|
||||
val = 0; /* analog input */
|
||||
|
||||
ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
|
||||
if (ret) {
|
||||
dev_err(&als->pdev->dev, "failed to set input mode %d\n",
|
||||
pwm_mode);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
|
||||
if (ret) {
|
||||
dev_err(&als->pdev->dev, "failed to set resistor\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_setup(struct lm3533_als *als,
|
||||
struct lm3533_als_platform_data *pdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* ALS input is always high impedance in PWM-mode. */
|
||||
if (!pdata->pwm_mode) {
|
||||
ret = lm3533_als_set_resistor(als, pdata->r_select);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
|
||||
{
|
||||
u8 mask = LM3533_ALS_INT_ENABLE_MASK;
|
||||
int ret;
|
||||
|
||||
/* Make sure interrupts are disabled. */
|
||||
ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
|
||||
if (ret) {
|
||||
dev_err(&als->pdev->dev, "failed to disable interrupts\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
dev_name(&als->pdev->dev), dev);
|
||||
if (ret) {
|
||||
dev_err(&als->pdev->dev, "failed to request irq %d\n",
|
||||
als->irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3533_als_enable(struct lm3533_als *als)
|
||||
{
|
||||
u8 mask = LM3533_ALS_ENABLE_MASK;
|
||||
int ret;
|
||||
|
||||
ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
|
||||
if (ret)
|
||||
dev_err(&als->pdev->dev, "failed to enable ALS\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm3533_als_disable(struct lm3533_als *als)
|
||||
{
|
||||
u8 mask = LM3533_ALS_ENABLE_MASK;
|
||||
int ret;
|
||||
|
||||
ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
|
||||
if (ret)
|
||||
dev_err(&als->pdev->dev, "failed to disable ALS\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info lm3533_als_info = {
|
||||
.attrs = &lm3533_als_attribute_group,
|
||||
.event_attrs = &lm3533_als_event_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &lm3533_als_read_raw,
|
||||
};
|
||||
|
||||
static int lm3533_als_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct lm3533 *lm3533;
|
||||
struct lm3533_als_platform_data *pdata;
|
||||
struct lm3533_als *als;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
lm3533 = dev_get_drvdata(pdev->dev.parent);
|
||||
if (!lm3533)
|
||||
return -EINVAL;
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "no platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
indio_dev->info = &lm3533_als_info;
|
||||
indio_dev->channels = lm3533_als_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
|
||||
indio_dev->name = dev_name(&pdev->dev);
|
||||
indio_dev->dev.parent = pdev->dev.parent;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
als = iio_priv(indio_dev);
|
||||
als->lm3533 = lm3533;
|
||||
als->pdev = pdev;
|
||||
als->irq = lm3533->irq;
|
||||
atomic_set(&als->zone, 0);
|
||||
mutex_init(&als->thresh_mutex);
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
if (als->irq) {
|
||||
ret = lm3533_als_setup_irq(als, indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = lm3533_als_setup(als, pdata);
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
|
||||
ret = lm3533_als_enable(als);
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register ALS\n");
|
||||
goto err_disable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable:
|
||||
lm3533_als_disable(als);
|
||||
err_free_irq:
|
||||
if (als->irq)
|
||||
free_irq(als->irq, indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm3533_als_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct lm3533_als *als = iio_priv(indio_dev);
|
||||
|
||||
lm3533_als_set_int_mode(indio_dev, false);
|
||||
iio_device_unregister(indio_dev);
|
||||
lm3533_als_disable(als);
|
||||
if (als->irq)
|
||||
free_irq(als->irq, indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver lm3533_als_driver = {
|
||||
.driver = {
|
||||
.name = "lm3533-als",
|
||||
},
|
||||
.probe = lm3533_als_probe,
|
||||
.remove = lm3533_als_remove,
|
||||
};
|
||||
module_platform_driver(lm3533_als_driver);
|
||||
|
||||
MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
|
||||
MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:lm3533-als");
|
445
drivers/iio/light/ltr501.c
Normal file
445
drivers/iio/light/ltr501.c
Normal file
|
@ -0,0 +1,445 @@
|
|||
/*
|
||||
* ltr501.c - Support for Lite-On LTR501 ambient light and proximity sensor
|
||||
*
|
||||
* Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* 7-bit I2C slave address 0x23
|
||||
*
|
||||
* TODO: interrupt, threshold, measurement rate, IR LED characteristics
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define LTR501_DRV_NAME "ltr501"
|
||||
|
||||
#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */
|
||||
#define LTR501_PS_CONTR 0x81 /* PS operation mode */
|
||||
#define LTR501_PART_ID 0x86
|
||||
#define LTR501_MANUFAC_ID 0x87
|
||||
#define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */
|
||||
#define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */
|
||||
#define LTR501_ALS_PS_STATUS 0x8c
|
||||
#define LTR501_PS_DATA 0x8d /* 16-bit, little endian */
|
||||
|
||||
#define LTR501_ALS_CONTR_SW_RESET BIT(2)
|
||||
#define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2))
|
||||
#define LTR501_CONTR_PS_GAIN_SHIFT 2
|
||||
#define LTR501_CONTR_ALS_GAIN_MASK BIT(3)
|
||||
#define LTR501_CONTR_ACTIVE BIT(1)
|
||||
|
||||
#define LTR501_STATUS_ALS_RDY BIT(2)
|
||||
#define LTR501_STATUS_PS_RDY BIT(0)
|
||||
|
||||
#define LTR501_PS_DATA_MASK 0x7ff
|
||||
|
||||
struct ltr501_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock_als, lock_ps;
|
||||
u8 als_contr, ps_contr;
|
||||
};
|
||||
|
||||
static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask)
|
||||
{
|
||||
int tries = 100;
|
||||
int ret;
|
||||
|
||||
while (tries--) {
|
||||
ret = i2c_smbus_read_byte_data(data->client,
|
||||
LTR501_ALS_PS_STATUS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if ((ret & drdy_mask) == drdy_mask)
|
||||
return 0;
|
||||
msleep(25);
|
||||
}
|
||||
|
||||
dev_err(&data->client->dev, "ltr501_drdy() failed, data not ready\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int ltr501_read_als(struct ltr501_data *data, __le16 buf[2])
|
||||
{
|
||||
int ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* always read both ALS channels in given order */
|
||||
return i2c_smbus_read_i2c_block_data(data->client,
|
||||
LTR501_ALS_DATA1, 2 * sizeof(__le16), (u8 *) buf);
|
||||
}
|
||||
|
||||
static int ltr501_read_ps(struct ltr501_data *data)
|
||||
{
|
||||
int ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return i2c_smbus_read_word_data(data->client, LTR501_PS_DATA);
|
||||
}
|
||||
|
||||
#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared) { \
|
||||
.type = IIO_INTENSITY, \
|
||||
.modified = 1, \
|
||||
.address = (_addr), \
|
||||
.channel2 = (_mod), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = (_shared), \
|
||||
.scan_index = (_idx), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
} \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ltr501_channels[] = {
|
||||
LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0),
|
||||
LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR,
|
||||
BIT(IIO_CHAN_INFO_SCALE)),
|
||||
{
|
||||
.type = IIO_PROXIMITY,
|
||||
.address = LTR501_PS_DATA,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
.scan_index = 2,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 11,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_CPU,
|
||||
},
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
static const int ltr501_ps_gain[4][2] = {
|
||||
{1, 0}, {0, 250000}, {0, 125000}, {0, 62500}
|
||||
};
|
||||
|
||||
static int ltr501_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct ltr501_data *data = iio_priv(indio_dev);
|
||||
__le16 buf[2];
|
||||
int ret, i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
mutex_lock(&data->lock_als);
|
||||
ret = ltr501_read_als(data, buf);
|
||||
mutex_unlock(&data->lock_als);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ?
|
||||
buf[0] : buf[1]);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_PROXIMITY:
|
||||
mutex_lock(&data->lock_ps);
|
||||
ret = ltr501_read_ps(data);
|
||||
mutex_unlock(&data->lock_ps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret & LTR501_PS_DATA_MASK;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
if (data->als_contr & LTR501_CONTR_ALS_GAIN_MASK) {
|
||||
*val = 0;
|
||||
*val2 = 5000;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
} else {
|
||||
*val = 1;
|
||||
*val2 = 0;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
case IIO_PROXIMITY:
|
||||
i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >>
|
||||
LTR501_CONTR_PS_GAIN_SHIFT;
|
||||
*val = ltr501_ps_gain[i][0];
|
||||
*val2 = ltr501_ps_gain[i][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ltr501_get_ps_gain_index(int val, int val2)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ltr501_ps_gain); i++)
|
||||
if (val == ltr501_ps_gain[i][0] && val2 == ltr501_ps_gain[i][1])
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ltr501_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct ltr501_data *data = iio_priv(indio_dev);
|
||||
int i;
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
if (val == 0 && val2 == 5000)
|
||||
data->als_contr |= LTR501_CONTR_ALS_GAIN_MASK;
|
||||
else if (val == 1 && val2 == 0)
|
||||
data->als_contr &= ~LTR501_CONTR_ALS_GAIN_MASK;
|
||||
else
|
||||
return -EINVAL;
|
||||
return i2c_smbus_write_byte_data(data->client,
|
||||
LTR501_ALS_CONTR, data->als_contr);
|
||||
case IIO_PROXIMITY:
|
||||
i = ltr501_get_ps_gain_index(val, val2);
|
||||
if (i < 0)
|
||||
return -EINVAL;
|
||||
data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK;
|
||||
data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT;
|
||||
return i2c_smbus_write_byte_data(data->client,
|
||||
LTR501_PS_CONTR, data->ps_contr);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(in_proximity_scale_available, "1 0.25 0.125 0.0625");
|
||||
static IIO_CONST_ATTR(in_intensity_scale_available, "1 0.005");
|
||||
|
||||
static struct attribute *ltr501_attributes[] = {
|
||||
&iio_const_attr_in_proximity_scale_available.dev_attr.attr,
|
||||
&iio_const_attr_in_intensity_scale_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group ltr501_attribute_group = {
|
||||
.attrs = ltr501_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info ltr501_info = {
|
||||
.read_raw = ltr501_read_raw,
|
||||
.write_raw = ltr501_write_raw,
|
||||
.attrs = <r501_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int ltr501_write_contr(struct i2c_client *client, u8 als_val, u8 ps_val)
|
||||
{
|
||||
int ret = i2c_smbus_write_byte_data(client, LTR501_ALS_CONTR, als_val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, LTR501_PS_CONTR, ps_val);
|
||||
}
|
||||
|
||||
static irqreturn_t ltr501_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct ltr501_data *data = iio_priv(indio_dev);
|
||||
u16 buf[8];
|
||||
__le16 als_buf[2];
|
||||
u8 mask = 0;
|
||||
int j = 0;
|
||||
int ret;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
/* figure out which data needs to be ready */
|
||||
if (test_bit(0, indio_dev->active_scan_mask) ||
|
||||
test_bit(1, indio_dev->active_scan_mask))
|
||||
mask |= LTR501_STATUS_ALS_RDY;
|
||||
if (test_bit(2, indio_dev->active_scan_mask))
|
||||
mask |= LTR501_STATUS_PS_RDY;
|
||||
|
||||
ret = ltr501_drdy(data, mask);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
if (mask & LTR501_STATUS_ALS_RDY) {
|
||||
ret = i2c_smbus_read_i2c_block_data(data->client,
|
||||
LTR501_ALS_DATA1, sizeof(als_buf), (u8 *) als_buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (test_bit(0, indio_dev->active_scan_mask))
|
||||
buf[j++] = le16_to_cpu(als_buf[1]);
|
||||
if (test_bit(1, indio_dev->active_scan_mask))
|
||||
buf[j++] = le16_to_cpu(als_buf[0]);
|
||||
}
|
||||
|
||||
if (mask & LTR501_STATUS_PS_RDY) {
|
||||
ret = i2c_smbus_read_word_data(data->client, LTR501_PS_DATA);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
buf[j++] = ret & LTR501_PS_DATA_MASK;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, buf,
|
||||
iio_get_time_ns());
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ltr501_init(struct ltr501_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, LTR501_ALS_CONTR);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->als_contr = ret | LTR501_CONTR_ACTIVE;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, LTR501_PS_CONTR);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->ps_contr = ret | LTR501_CONTR_ACTIVE;
|
||||
|
||||
return ltr501_write_contr(data->client, data->als_contr,
|
||||
data->ps_contr);
|
||||
}
|
||||
|
||||
static int ltr501_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct ltr501_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
mutex_init(&data->lock_als);
|
||||
mutex_init(&data->lock_ps);
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, LTR501_PART_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if ((ret >> 4) != 0x8)
|
||||
return -ENODEV;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = <r501_info;
|
||||
indio_dev->channels = ltr501_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ltr501_channels);
|
||||
indio_dev->name = LTR501_DRV_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = ltr501_init(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
ltr501_trigger_handler, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto error_unreg_buffer;
|
||||
|
||||
return 0;
|
||||
|
||||
error_unreg_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltr501_powerdown(struct ltr501_data *data)
|
||||
{
|
||||
return ltr501_write_contr(data->client,
|
||||
data->als_contr & ~LTR501_CONTR_ACTIVE,
|
||||
data->ps_contr & ~LTR501_CONTR_ACTIVE);
|
||||
}
|
||||
|
||||
static int ltr501_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
ltr501_powerdown(iio_priv(indio_dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int ltr501_suspend(struct device *dev)
|
||||
{
|
||||
struct ltr501_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return ltr501_powerdown(data);
|
||||
}
|
||||
|
||||
static int ltr501_resume(struct device *dev)
|
||||
{
|
||||
struct ltr501_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
|
||||
return ltr501_write_contr(data->client, data->als_contr,
|
||||
data->ps_contr);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume);
|
||||
|
||||
static const struct i2c_device_id ltr501_id[] = {
|
||||
{ "ltr501", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ltr501_id);
|
||||
|
||||
static struct i2c_driver ltr501_driver = {
|
||||
.driver = {
|
||||
.name = LTR501_DRV_NAME,
|
||||
.pm = <r501_pm_ops,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ltr501_probe,
|
||||
.remove = ltr501_remove,
|
||||
.id_table = ltr501_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(ltr501_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("Lite-On LTR501 ambient light and proximity sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
405
drivers/iio/light/tcs3414.c
Normal file
405
drivers/iio/light/tcs3414.c
Normal file
|
@ -0,0 +1,405 @@
|
|||
/*
|
||||
* tcs3414.c - Support for TAOS TCS3414 digital color sensor
|
||||
*
|
||||
* Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* Digital color sensor with 16-bit channels for red, green, blue, clear);
|
||||
* 7-bit I2C slave address 0x39 (TCS3414) or 0x29, 0x49, 0x59 (TCS3413,
|
||||
* TCS3415, TCS3416, resp.)
|
||||
*
|
||||
* TODO: sync, interrupt support, thresholds, prescaler
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define TCS3414_DRV_NAME "tcs3414"
|
||||
|
||||
#define TCS3414_COMMAND BIT(7)
|
||||
#define TCS3414_COMMAND_WORD (TCS3414_COMMAND | BIT(5))
|
||||
|
||||
#define TCS3414_CONTROL (TCS3414_COMMAND | 0x00)
|
||||
#define TCS3414_TIMING (TCS3414_COMMAND | 0x01)
|
||||
#define TCS3414_ID (TCS3414_COMMAND | 0x04)
|
||||
#define TCS3414_GAIN (TCS3414_COMMAND | 0x07)
|
||||
#define TCS3414_DATA_GREEN (TCS3414_COMMAND_WORD | 0x10)
|
||||
#define TCS3414_DATA_RED (TCS3414_COMMAND_WORD | 0x12)
|
||||
#define TCS3414_DATA_BLUE (TCS3414_COMMAND_WORD | 0x14)
|
||||
#define TCS3414_DATA_CLEAR (TCS3414_COMMAND_WORD | 0x16)
|
||||
|
||||
#define TCS3414_CONTROL_ADC_VALID BIT(4)
|
||||
#define TCS3414_CONTROL_ADC_EN BIT(1)
|
||||
#define TCS3414_CONTROL_POWER BIT(0)
|
||||
|
||||
#define TCS3414_INTEG_MASK GENMASK(1, 0)
|
||||
#define TCS3414_INTEG_12MS 0x0
|
||||
#define TCS3414_INTEG_100MS 0x1
|
||||
#define TCS3414_INTEG_400MS 0x2
|
||||
|
||||
#define TCS3414_GAIN_MASK GENMASK(5, 4)
|
||||
#define TCS3414_GAIN_SHIFT 4
|
||||
|
||||
struct tcs3414_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
u8 control;
|
||||
u8 gain;
|
||||
u8 timing;
|
||||
u16 buffer[8]; /* 4x 16-bit + 8 bytes timestamp */
|
||||
};
|
||||
|
||||
#define TCS3414_CHANNEL(_color, _si, _addr) { \
|
||||
.type = IIO_INTENSITY, \
|
||||
.modified = 1, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_INT_TIME), \
|
||||
.channel2 = IIO_MOD_LIGHT_##_color, \
|
||||
.address = _addr, \
|
||||
.scan_index = _si, \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
/* scale factors: 1/gain */
|
||||
static const int tcs3414_scales[][2] = {
|
||||
{1, 0}, {0, 250000}, {0, 62500}, {0, 15625}
|
||||
};
|
||||
|
||||
/* integration time in ms */
|
||||
static const int tcs3414_times[] = { 12, 100, 400 };
|
||||
|
||||
static const struct iio_chan_spec tcs3414_channels[] = {
|
||||
TCS3414_CHANNEL(GREEN, 0, TCS3414_DATA_GREEN),
|
||||
TCS3414_CHANNEL(RED, 1, TCS3414_DATA_RED),
|
||||
TCS3414_CHANNEL(BLUE, 2, TCS3414_DATA_BLUE),
|
||||
TCS3414_CHANNEL(CLEAR, 3, TCS3414_DATA_CLEAR),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static int tcs3414_req_data(struct tcs3414_data *data)
|
||||
{
|
||||
int tries = 25;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control | TCS3414_CONTROL_ADC_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
while (tries--) {
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3414_CONTROL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret & TCS3414_CONTROL_ADC_VALID)
|
||||
break;
|
||||
msleep(20);
|
||||
}
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (tries < 0) {
|
||||
dev_err(&data->client->dev, "data not ready\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcs3414_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct tcs3414_data *data = iio_priv(indio_dev);
|
||||
int i, ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
mutex_lock(&data->lock);
|
||||
ret = tcs3414_req_data(data);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
ret = i2c_smbus_read_word_data(data->client, chan->address);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT;
|
||||
*val = tcs3414_scales[i][0];
|
||||
*val2 = tcs3414_scales[i][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
*val = 0;
|
||||
*val2 = tcs3414_times[data->timing & TCS3414_INTEG_MASK] * 1000;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int tcs3414_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct tcs3414_data *data = iio_priv(indio_dev);
|
||||
int i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
for (i = 0; i < ARRAY_SIZE(tcs3414_scales); i++) {
|
||||
if (val == tcs3414_scales[i][0] &&
|
||||
val2 == tcs3414_scales[i][1]) {
|
||||
data->gain &= ~TCS3414_GAIN_MASK;
|
||||
data->gain |= i << TCS3414_GAIN_SHIFT;
|
||||
return i2c_smbus_write_byte_data(
|
||||
data->client, TCS3414_GAIN,
|
||||
data->gain);
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) {
|
||||
if (val == tcs3414_times[i] * 1000) {
|
||||
data->timing &= ~TCS3414_INTEG_MASK;
|
||||
data->timing |= i;
|
||||
return i2c_smbus_write_byte_data(
|
||||
data->client, TCS3414_TIMING,
|
||||
data->timing);
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t tcs3414_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct tcs3414_data *data = iio_priv(indio_dev);
|
||||
int i, j = 0;
|
||||
|
||||
for_each_set_bit(i, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
int ret = i2c_smbus_read_word_data(data->client,
|
||||
TCS3414_DATA_GREEN + 2*i);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
data->buffer[j++] = ret;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
|
||||
iio_get_time_ns());
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(scale_available, "1 0.25 0.0625 0.015625");
|
||||
static IIO_CONST_ATTR_INT_TIME_AVAIL("0.012 0.1 0.4");
|
||||
|
||||
static struct attribute *tcs3414_attributes[] = {
|
||||
&iio_const_attr_scale_available.dev_attr.attr,
|
||||
&iio_const_attr_integration_time_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tcs3414_attribute_group = {
|
||||
.attrs = tcs3414_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info tcs3414_info = {
|
||||
.read_raw = tcs3414_read_raw,
|
||||
.write_raw = tcs3414_write_raw,
|
||||
.attrs = &tcs3414_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int tcs3414_buffer_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tcs3414_data *data = iio_priv(indio_dev);
|
||||
|
||||
data->control |= TCS3414_CONTROL_ADC_EN;
|
||||
return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control);
|
||||
}
|
||||
|
||||
static int tcs3414_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tcs3414_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = iio_triggered_buffer_predisable(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->control &= ~TCS3414_CONTROL_ADC_EN;
|
||||
return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops tcs3414_buffer_setup_ops = {
|
||||
.preenable = tcs3414_buffer_preenable,
|
||||
.postenable = &iio_triggered_buffer_postenable,
|
||||
.predisable = tcs3414_buffer_predisable,
|
||||
};
|
||||
|
||||
static int tcs3414_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct tcs3414_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &tcs3414_info;
|
||||
indio_dev->name = TCS3414_DRV_NAME;
|
||||
indio_dev->channels = tcs3414_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(tcs3414_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3414_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (ret & 0xf0) {
|
||||
case 0x00:
|
||||
dev_info(&client->dev, "TCS3404 found\n");
|
||||
break;
|
||||
case 0x10:
|
||||
dev_info(&client->dev, "TCS3413/14/15/16 found\n");
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data->control = TCS3414_CONTROL_POWER;
|
||||
ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->timing = TCS3414_INTEG_12MS; /* free running */
|
||||
ret = i2c_smbus_write_byte_data(data->client, TCS3414_TIMING,
|
||||
data->timing);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3414_GAIN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->gain = ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
tcs3414_trigger_handler, &tcs3414_buffer_setup_ops);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto buffer_cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tcs3414_powerdown(struct tcs3414_data *data)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control & ~(TCS3414_CONTROL_POWER |
|
||||
TCS3414_CONTROL_ADC_EN));
|
||||
}
|
||||
|
||||
static int tcs3414_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
tcs3414_powerdown(iio_priv(indio_dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tcs3414_suspend(struct device *dev)
|
||||
{
|
||||
struct tcs3414_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return tcs3414_powerdown(data);
|
||||
}
|
||||
|
||||
static int tcs3414_resume(struct device *dev)
|
||||
{
|
||||
struct tcs3414_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
|
||||
data->control);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend, tcs3414_resume);
|
||||
|
||||
static const struct i2c_device_id tcs3414_id[] = {
|
||||
{ "tcs3414", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tcs3414_id);
|
||||
|
||||
static struct i2c_driver tcs3414_driver = {
|
||||
.driver = {
|
||||
.name = TCS3414_DRV_NAME,
|
||||
.pm = &tcs3414_pm_ops,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = tcs3414_probe,
|
||||
.remove = tcs3414_remove,
|
||||
.id_table = tcs3414_id,
|
||||
};
|
||||
module_i2c_driver(tcs3414_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("TCS3414 digital color sensors driver");
|
||||
MODULE_LICENSE("GPL");
|
379
drivers/iio/light/tcs3472.c
Normal file
379
drivers/iio/light/tcs3472.c
Normal file
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* tcs3472.c - Support for TAOS TCS3472 color light-to-digital converter
|
||||
*
|
||||
* Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* Color light sensor with 16-bit channels for red, green, blue, clear);
|
||||
* 7-bit I2C slave address 0x39 (TCS34721, TCS34723) or 0x29 (TCS34725,
|
||||
* TCS34727)
|
||||
*
|
||||
* TODO: interrupt support, thresholds, wait time
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define TCS3472_DRV_NAME "tcs3472"
|
||||
|
||||
#define TCS3472_COMMAND BIT(7)
|
||||
#define TCS3472_AUTO_INCR BIT(5)
|
||||
|
||||
#define TCS3472_ENABLE (TCS3472_COMMAND | 0x00)
|
||||
#define TCS3472_ATIME (TCS3472_COMMAND | 0x01)
|
||||
#define TCS3472_WTIME (TCS3472_COMMAND | 0x03)
|
||||
#define TCS3472_AILT (TCS3472_COMMAND | 0x04)
|
||||
#define TCS3472_AIHT (TCS3472_COMMAND | 0x06)
|
||||
#define TCS3472_PERS (TCS3472_COMMAND | 0x0c)
|
||||
#define TCS3472_CONFIG (TCS3472_COMMAND | 0x0d)
|
||||
#define TCS3472_CONTROL (TCS3472_COMMAND | 0x0f)
|
||||
#define TCS3472_ID (TCS3472_COMMAND | 0x12)
|
||||
#define TCS3472_STATUS (TCS3472_COMMAND | 0x13)
|
||||
#define TCS3472_CDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x14)
|
||||
#define TCS3472_RDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x16)
|
||||
#define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18)
|
||||
#define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a)
|
||||
|
||||
#define TCS3472_STATUS_AVALID BIT(0)
|
||||
#define TCS3472_ENABLE_AEN BIT(1)
|
||||
#define TCS3472_ENABLE_PON BIT(0)
|
||||
#define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1))
|
||||
|
||||
struct tcs3472_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
u8 enable;
|
||||
u8 control;
|
||||
u8 atime;
|
||||
u16 buffer[8]; /* 4 16-bit channels + 64-bit timestamp */
|
||||
};
|
||||
|
||||
#define TCS3472_CHANNEL(_color, _si, _addr) { \
|
||||
.type = IIO_INTENSITY, \
|
||||
.modified = 1, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE) | \
|
||||
BIT(IIO_CHAN_INFO_INT_TIME), \
|
||||
.channel2 = IIO_MOD_LIGHT_##_color, \
|
||||
.address = _addr, \
|
||||
.scan_index = _si, \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const int tcs3472_agains[] = { 1, 4, 16, 60 };
|
||||
|
||||
static const struct iio_chan_spec tcs3472_channels[] = {
|
||||
TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA),
|
||||
TCS3472_CHANNEL(RED, 1, TCS3472_RDATA),
|
||||
TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA),
|
||||
TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static int tcs3472_req_data(struct tcs3472_data *data)
|
||||
{
|
||||
int tries = 50;
|
||||
int ret;
|
||||
|
||||
while (tries--) {
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret & TCS3472_STATUS_AVALID)
|
||||
break;
|
||||
msleep(20);
|
||||
}
|
||||
|
||||
if (tries < 0) {
|
||||
dev_err(&data->client->dev, "data not ready\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcs3472_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct tcs3472_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = tcs3472_req_data(data);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
ret = i2c_smbus_read_word_data(data->client, chan->address);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
*val = tcs3472_agains[data->control &
|
||||
TCS3472_CONTROL_AGAIN_MASK];
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
*val = 0;
|
||||
*val2 = (256 - data->atime) * 2400;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int tcs3472_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct tcs3472_data *data = iio_priv(indio_dev);
|
||||
int i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
if (val2 != 0)
|
||||
return -EINVAL;
|
||||
for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) {
|
||||
if (val == tcs3472_agains[i]) {
|
||||
data->control &= ~TCS3472_CONTROL_AGAIN_MASK;
|
||||
data->control |= i;
|
||||
return i2c_smbus_write_byte_data(
|
||||
data->client, TCS3472_CONTROL,
|
||||
data->control);
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
for (i = 0; i < 256; i++) {
|
||||
if (val2 == (256 - i) * 2400) {
|
||||
data->atime = i;
|
||||
return i2c_smbus_write_word_data(
|
||||
data->client, TCS3472_ATIME,
|
||||
data->atime);
|
||||
}
|
||||
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static irqreturn_t tcs3472_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct tcs3472_data *data = iio_priv(indio_dev);
|
||||
int i, j = 0;
|
||||
|
||||
int ret = tcs3472_req_data(data);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
for_each_set_bit(i, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
TCS3472_CDATA + 2*i);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
data->buffer[j++] = ret;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
|
||||
iio_get_time_ns());
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static ssize_t tcs3472_show_int_time_available(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
size_t len = 0;
|
||||
int i;
|
||||
|
||||
for (i = 1; i <= 256; i++)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ",
|
||||
2400 * i);
|
||||
|
||||
/* replace trailing space by newline */
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(calibscale_available, "1 4 16 60");
|
||||
static IIO_DEV_ATTR_INT_TIME_AVAIL(tcs3472_show_int_time_available);
|
||||
|
||||
static struct attribute *tcs3472_attributes[] = {
|
||||
&iio_const_attr_calibscale_available.dev_attr.attr,
|
||||
&iio_dev_attr_integration_time_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tcs3472_attribute_group = {
|
||||
.attrs = tcs3472_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info tcs3472_info = {
|
||||
.read_raw = tcs3472_read_raw,
|
||||
.write_raw = tcs3472_write_raw,
|
||||
.attrs = &tcs3472_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int tcs3472_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct tcs3472_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &tcs3472_info;
|
||||
indio_dev->name = TCS3472_DRV_NAME;
|
||||
indio_dev->channels = tcs3472_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret == 0x44)
|
||||
dev_info(&client->dev, "TCS34721/34725 found\n");
|
||||
else if (ret == 0x4d)
|
||||
dev_info(&client->dev, "TCS34723/34727 found\n");
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->control = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->atime = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, TCS3472_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* enable device */
|
||||
data->enable = ret | TCS3472_ENABLE_PON | TCS3472_ENABLE_AEN;
|
||||
ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
|
||||
data->enable);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
tcs3472_trigger_handler, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto buffer_cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tcs3472_powerdown(struct tcs3472_data *data)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
|
||||
data->enable & ~(TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON));
|
||||
}
|
||||
|
||||
static int tcs3472_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
tcs3472_powerdown(iio_priv(indio_dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tcs3472_suspend(struct device *dev)
|
||||
{
|
||||
struct tcs3472_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return tcs3472_powerdown(data);
|
||||
}
|
||||
|
||||
static int tcs3472_resume(struct device *dev)
|
||||
{
|
||||
struct tcs3472_data *data = iio_priv(i2c_get_clientdata(
|
||||
to_i2c_client(dev)));
|
||||
return i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
|
||||
data->enable | (TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON));
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, tcs3472_resume);
|
||||
|
||||
static const struct i2c_device_id tcs3472_id[] = {
|
||||
{ "tcs3472", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tcs3472_id);
|
||||
|
||||
static struct i2c_driver tcs3472_driver = {
|
||||
.driver = {
|
||||
.name = TCS3472_DRV_NAME,
|
||||
.pm = &tcs3472_pm_ops,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = tcs3472_probe,
|
||||
.remove = tcs3472_remove,
|
||||
.id_table = tcs3472_id,
|
||||
};
|
||||
module_i2c_driver(tcs3472_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("TCS3472 color light sensors driver");
|
||||
MODULE_LICENSE("GPL");
|
901
drivers/iio/light/tsl2563.c
Normal file
901
drivers/iio/light/tsl2563.c
Normal file
|
@ -0,0 +1,901 @@
|
|||
/*
|
||||
* drivers/iio/light/tsl2563.c
|
||||
*
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
|
||||
* Contact: Amit Kucheria <amit.kucheria@verdurent.com>
|
||||
*
|
||||
* Converted to IIO driver
|
||||
* Amit Kucheria <amit.kucheria@verdurent.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/platform_data/tsl2563.h>
|
||||
|
||||
/* Use this many bits for fraction part. */
|
||||
#define ADC_FRAC_BITS 14
|
||||
|
||||
/* Given number of 1/10000's in ADC_FRAC_BITS precision. */
|
||||
#define FRAC10K(f) (((f) * (1L << (ADC_FRAC_BITS))) / (10000))
|
||||
|
||||
/* Bits used for fraction in calibration coefficients.*/
|
||||
#define CALIB_FRAC_BITS 10
|
||||
/* 0.5 in CALIB_FRAC_BITS precision */
|
||||
#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1))
|
||||
/* Make a fraction from a number n that was multiplied with b. */
|
||||
#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b))
|
||||
/* Decimal 10^(digits in sysfs presentation) */
|
||||
#define CALIB_BASE_SYSFS 1000
|
||||
|
||||
#define TSL2563_CMD 0x80
|
||||
#define TSL2563_CLEARINT 0x40
|
||||
|
||||
#define TSL2563_REG_CTRL 0x00
|
||||
#define TSL2563_REG_TIMING 0x01
|
||||
#define TSL2563_REG_LOWLOW 0x02 /* data0 low threshold, 2 bytes */
|
||||
#define TSL2563_REG_LOWHIGH 0x03
|
||||
#define TSL2563_REG_HIGHLOW 0x04 /* data0 high threshold, 2 bytes */
|
||||
#define TSL2563_REG_HIGHHIGH 0x05
|
||||
#define TSL2563_REG_INT 0x06
|
||||
#define TSL2563_REG_ID 0x0a
|
||||
#define TSL2563_REG_DATA0LOW 0x0c /* broadband sensor value, 2 bytes */
|
||||
#define TSL2563_REG_DATA0HIGH 0x0d
|
||||
#define TSL2563_REG_DATA1LOW 0x0e /* infrared sensor value, 2 bytes */
|
||||
#define TSL2563_REG_DATA1HIGH 0x0f
|
||||
|
||||
#define TSL2563_CMD_POWER_ON 0x03
|
||||
#define TSL2563_CMD_POWER_OFF 0x00
|
||||
#define TSL2563_CTRL_POWER_MASK 0x03
|
||||
|
||||
#define TSL2563_TIMING_13MS 0x00
|
||||
#define TSL2563_TIMING_100MS 0x01
|
||||
#define TSL2563_TIMING_400MS 0x02
|
||||
#define TSL2563_TIMING_MASK 0x03
|
||||
#define TSL2563_TIMING_GAIN16 0x10
|
||||
#define TSL2563_TIMING_GAIN1 0x00
|
||||
|
||||
#define TSL2563_INT_DISBLED 0x00
|
||||
#define TSL2563_INT_LEVEL 0x10
|
||||
#define TSL2563_INT_PERSIST(n) ((n) & 0x0F)
|
||||
|
||||
struct tsl2563_gainlevel_coeff {
|
||||
u8 gaintime;
|
||||
u16 min;
|
||||
u16 max;
|
||||
};
|
||||
|
||||
static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = {
|
||||
{
|
||||
.gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16,
|
||||
.min = 0,
|
||||
.max = 65534,
|
||||
}, {
|
||||
.gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1,
|
||||
.min = 2048,
|
||||
.max = 65534,
|
||||
}, {
|
||||
.gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1,
|
||||
.min = 4095,
|
||||
.max = 37177,
|
||||
}, {
|
||||
.gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1,
|
||||
.min = 3000,
|
||||
.max = 65535,
|
||||
},
|
||||
};
|
||||
|
||||
struct tsl2563_chip {
|
||||
struct mutex lock;
|
||||
struct i2c_client *client;
|
||||
struct delayed_work poweroff_work;
|
||||
|
||||
/* Remember state for suspend and resume functions */
|
||||
bool suspended;
|
||||
|
||||
struct tsl2563_gainlevel_coeff const *gainlevel;
|
||||
|
||||
u16 low_thres;
|
||||
u16 high_thres;
|
||||
u8 intr;
|
||||
bool int_enabled;
|
||||
|
||||
/* Calibration coefficients */
|
||||
u32 calib0;
|
||||
u32 calib1;
|
||||
int cover_comp_gain;
|
||||
|
||||
/* Cache current values, to be returned while suspended */
|
||||
u32 data0;
|
||||
u32 data1;
|
||||
};
|
||||
|
||||
static int tsl2563_set_power(struct tsl2563_chip *chip, int on)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
u8 cmd;
|
||||
|
||||
cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF;
|
||||
return i2c_smbus_write_byte_data(client,
|
||||
TSL2563_CMD | TSL2563_REG_CTRL, cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return value is 0 for off, 1 for on, or a negative error
|
||||
* code if reading failed.
|
||||
*/
|
||||
static int tsl2563_get_power(struct tsl2563_chip *chip)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON;
|
||||
}
|
||||
|
||||
static int tsl2563_configure(struct tsl2563_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_TIMING,
|
||||
chip->gainlevel->gaintime);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_HIGHLOW,
|
||||
chip->high_thres & 0xFF);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_HIGHHIGH,
|
||||
(chip->high_thres >> 8) & 0xFF);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_LOWLOW,
|
||||
chip->low_thres & 0xFF);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_LOWHIGH,
|
||||
(chip->low_thres >> 8) & 0xFF);
|
||||
/*
|
||||
* Interrupt register is automatically written anyway if it is relevant
|
||||
* so is not here.
|
||||
*/
|
||||
error_ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tsl2563_poweroff_work(struct work_struct *work)
|
||||
{
|
||||
struct tsl2563_chip *chip =
|
||||
container_of(work, struct tsl2563_chip, poweroff_work.work);
|
||||
tsl2563_set_power(chip, 0);
|
||||
}
|
||||
|
||||
static int tsl2563_detect(struct tsl2563_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = tsl2563_set_power(chip, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = tsl2563_get_power(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret ? 0 : -ENODEV;
|
||||
}
|
||||
|
||||
static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*id = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* "Normalized" ADC value is one obtained with 400ms of integration time and
|
||||
* 16x gain. This function returns the number of bits of shift needed to
|
||||
* convert between normalized values and HW values obtained using given
|
||||
* timing and gain settings.
|
||||
*/
|
||||
static int adc_shiftbits(u8 timing)
|
||||
{
|
||||
int shift = 0;
|
||||
|
||||
switch (timing & TSL2563_TIMING_MASK) {
|
||||
case TSL2563_TIMING_13MS:
|
||||
shift += 5;
|
||||
break;
|
||||
case TSL2563_TIMING_100MS:
|
||||
shift += 2;
|
||||
break;
|
||||
case TSL2563_TIMING_400MS:
|
||||
/* no-op */
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(timing & TSL2563_TIMING_GAIN16))
|
||||
shift += 4;
|
||||
|
||||
return shift;
|
||||
}
|
||||
|
||||
/* Convert a HW ADC value to normalized scale. */
|
||||
static u32 normalize_adc(u16 adc, u8 timing)
|
||||
{
|
||||
return adc << adc_shiftbits(timing);
|
||||
}
|
||||
|
||||
static void tsl2563_wait_adc(struct tsl2563_chip *chip)
|
||||
{
|
||||
unsigned int delay;
|
||||
|
||||
switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) {
|
||||
case TSL2563_TIMING_13MS:
|
||||
delay = 14;
|
||||
break;
|
||||
case TSL2563_TIMING_100MS:
|
||||
delay = 101;
|
||||
break;
|
||||
default:
|
||||
delay = 402;
|
||||
}
|
||||
/*
|
||||
* TODO: Make sure that we wait at least required delay but why we
|
||||
* have to extend it one tick more?
|
||||
*/
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2);
|
||||
}
|
||||
|
||||
static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
|
||||
if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) {
|
||||
|
||||
(adc > chip->gainlevel->max) ?
|
||||
chip->gainlevel++ : chip->gainlevel--;
|
||||
|
||||
i2c_smbus_write_byte_data(client,
|
||||
TSL2563_CMD | TSL2563_REG_TIMING,
|
||||
chip->gainlevel->gaintime);
|
||||
|
||||
tsl2563_wait_adc(chip);
|
||||
tsl2563_wait_adc(chip);
|
||||
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsl2563_get_adc(struct tsl2563_chip *chip)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
u16 adc0, adc1;
|
||||
int retry = 1;
|
||||
int ret = 0;
|
||||
|
||||
if (chip->suspended)
|
||||
goto out;
|
||||
|
||||
if (!chip->int_enabled) {
|
||||
cancel_delayed_work(&chip->poweroff_work);
|
||||
|
||||
if (!tsl2563_get_power(chip)) {
|
||||
ret = tsl2563_set_power(chip, 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
ret = tsl2563_configure(chip);
|
||||
if (ret)
|
||||
goto out;
|
||||
tsl2563_wait_adc(chip);
|
||||
}
|
||||
}
|
||||
|
||||
while (retry) {
|
||||
ret = i2c_smbus_read_word_data(client,
|
||||
TSL2563_CMD | TSL2563_REG_DATA0LOW);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
adc0 = ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client,
|
||||
TSL2563_CMD | TSL2563_REG_DATA1LOW);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
adc1 = ret;
|
||||
|
||||
retry = tsl2563_adjust_gainlevel(chip, adc0);
|
||||
}
|
||||
|
||||
chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime);
|
||||
chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime);
|
||||
|
||||
if (!chip->int_enabled)
|
||||
schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int calib_to_sysfs(u32 calib)
|
||||
{
|
||||
return (int) (((calib * CALIB_BASE_SYSFS) +
|
||||
CALIB_FRAC_HALF) >> CALIB_FRAC_BITS);
|
||||
}
|
||||
|
||||
static inline u32 calib_from_sysfs(int value)
|
||||
{
|
||||
return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Conversions between lux and ADC values.
|
||||
*
|
||||
* The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are
|
||||
* appropriate constants. Different constants are needed for different
|
||||
* kinds of light, determined by the ratio adc1/adc0 (basically the ratio
|
||||
* of the intensities in infrared and visible wavelengths). lux_table below
|
||||
* lists the upper threshold of the adc1/adc0 ratio and the corresponding
|
||||
* constants.
|
||||
*/
|
||||
|
||||
struct tsl2563_lux_coeff {
|
||||
unsigned long ch_ratio;
|
||||
unsigned long ch0_coeff;
|
||||
unsigned long ch1_coeff;
|
||||
};
|
||||
|
||||
static const struct tsl2563_lux_coeff lux_table[] = {
|
||||
{
|
||||
.ch_ratio = FRAC10K(1300),
|
||||
.ch0_coeff = FRAC10K(315),
|
||||
.ch1_coeff = FRAC10K(262),
|
||||
}, {
|
||||
.ch_ratio = FRAC10K(2600),
|
||||
.ch0_coeff = FRAC10K(337),
|
||||
.ch1_coeff = FRAC10K(430),
|
||||
}, {
|
||||
.ch_ratio = FRAC10K(3900),
|
||||
.ch0_coeff = FRAC10K(363),
|
||||
.ch1_coeff = FRAC10K(529),
|
||||
}, {
|
||||
.ch_ratio = FRAC10K(5200),
|
||||
.ch0_coeff = FRAC10K(392),
|
||||
.ch1_coeff = FRAC10K(605),
|
||||
}, {
|
||||
.ch_ratio = FRAC10K(6500),
|
||||
.ch0_coeff = FRAC10K(229),
|
||||
.ch1_coeff = FRAC10K(291),
|
||||
}, {
|
||||
.ch_ratio = FRAC10K(8000),
|
||||
.ch0_coeff = FRAC10K(157),
|
||||
.ch1_coeff = FRAC10K(180),
|
||||
}, {
|
||||
.ch_ratio = FRAC10K(13000),
|
||||
.ch0_coeff = FRAC10K(34),
|
||||
.ch1_coeff = FRAC10K(26),
|
||||
}, {
|
||||
.ch_ratio = ULONG_MAX,
|
||||
.ch0_coeff = 0,
|
||||
.ch1_coeff = 0,
|
||||
},
|
||||
};
|
||||
|
||||
/* Convert normalized, scaled ADC values to lux. */
|
||||
static unsigned int adc_to_lux(u32 adc0, u32 adc1)
|
||||
{
|
||||
const struct tsl2563_lux_coeff *lp = lux_table;
|
||||
unsigned long ratio, lux, ch0 = adc0, ch1 = adc1;
|
||||
|
||||
ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX;
|
||||
|
||||
while (lp->ch_ratio < ratio)
|
||||
lp++;
|
||||
|
||||
lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff;
|
||||
|
||||
return (unsigned int) (lux >> ADC_FRAC_BITS);
|
||||
}
|
||||
|
||||
/* Apply calibration coefficient to ADC count. */
|
||||
static u32 calib_adc(u32 adc, u32 calib)
|
||||
{
|
||||
unsigned long scaled = adc;
|
||||
|
||||
scaled *= calib;
|
||||
scaled >>= CALIB_FRAC_BITS;
|
||||
|
||||
return (u32) scaled;
|
||||
}
|
||||
|
||||
static int tsl2563_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val,
|
||||
int val2,
|
||||
long mask)
|
||||
{
|
||||
struct tsl2563_chip *chip = iio_priv(indio_dev);
|
||||
|
||||
if (mask != IIO_CHAN_INFO_CALIBSCALE)
|
||||
return -EINVAL;
|
||||
if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
|
||||
chip->calib0 = calib_from_sysfs(val);
|
||||
else if (chan->channel2 == IIO_MOD_LIGHT_IR)
|
||||
chip->calib1 = calib_from_sysfs(val);
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsl2563_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val,
|
||||
int *val2,
|
||||
long mask)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
u32 calib0, calib1;
|
||||
struct tsl2563_chip *chip = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
ret = tsl2563_get_adc(chip);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
calib0 = calib_adc(chip->data0, chip->calib0) *
|
||||
chip->cover_comp_gain;
|
||||
calib1 = calib_adc(chip->data1, chip->calib1) *
|
||||
chip->cover_comp_gain;
|
||||
*val = adc_to_lux(calib0, calib1);
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_INTENSITY:
|
||||
ret = tsl2563_get_adc(chip);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
|
||||
*val = chip->data0;
|
||||
else
|
||||
*val = chip->data1;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
|
||||
*val = calib_to_sysfs(chip->calib0);
|
||||
else
|
||||
*val = calib_to_sysfs(chip->calib1);
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto error_ret;
|
||||
}
|
||||
|
||||
error_ret:
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_event_spec tsl2563_events[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
}, {
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_FALLING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
},
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec tsl2563_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.indexed = 1,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
.channel = 0,
|
||||
}, {
|
||||
.type = IIO_INTENSITY,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_BOTH,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_CALIBSCALE),
|
||||
.event_spec = tsl2563_events,
|
||||
.num_event_specs = ARRAY_SIZE(tsl2563_events),
|
||||
}, {
|
||||
.type = IIO_INTENSITY,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_IR,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_CALIBSCALE),
|
||||
}
|
||||
};
|
||||
|
||||
static int tsl2563_read_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, enum iio_event_info info, int *val,
|
||||
int *val2)
|
||||
{
|
||||
struct tsl2563_chip *chip = iio_priv(indio_dev);
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
*val = chip->high_thres;
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
*val = chip->low_thres;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int tsl2563_write_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, enum iio_event_info info, int val,
|
||||
int val2)
|
||||
{
|
||||
struct tsl2563_chip *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
u8 address;
|
||||
|
||||
if (dir == IIO_EV_DIR_RISING)
|
||||
address = TSL2563_REG_HIGHLOW;
|
||||
else
|
||||
address = TSL2563_REG_LOWLOW;
|
||||
mutex_lock(&chip->lock);
|
||||
ret = i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | address,
|
||||
val & 0xFF);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | (address + 1),
|
||||
(val >> 8) & 0xFF);
|
||||
if (dir == IIO_EV_DIR_RISING)
|
||||
chip->high_thres = val;
|
||||
else
|
||||
chip->low_thres = val;
|
||||
|
||||
error_ret:
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t tsl2563_event_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *dev_info = private;
|
||||
struct tsl2563_chip *chip = iio_priv(dev_info);
|
||||
|
||||
iio_push_event(dev_info,
|
||||
IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
|
||||
0,
|
||||
IIO_EV_TYPE_THRESH,
|
||||
IIO_EV_DIR_EITHER),
|
||||
iio_get_time_ns());
|
||||
|
||||
/* clear the interrupt and push the event */
|
||||
i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, int state)
|
||||
{
|
||||
struct tsl2563_chip *chip = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
if (state && !(chip->intr & 0x30)) {
|
||||
chip->intr &= ~0x30;
|
||||
chip->intr |= 0x10;
|
||||
/* ensure the chip is actually on */
|
||||
cancel_delayed_work(&chip->poweroff_work);
|
||||
if (!tsl2563_get_power(chip)) {
|
||||
ret = tsl2563_set_power(chip, 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
ret = tsl2563_configure(chip);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_INT,
|
||||
chip->intr);
|
||||
chip->int_enabled = true;
|
||||
}
|
||||
|
||||
if (!state && (chip->intr & 0x30)) {
|
||||
chip->intr &= ~0x30;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_INT,
|
||||
chip->intr);
|
||||
chip->int_enabled = false;
|
||||
/* now the interrupt is not enabled, we can go to sleep */
|
||||
schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir)
|
||||
{
|
||||
struct tsl2563_chip *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
ret = i2c_smbus_read_byte_data(chip->client,
|
||||
TSL2563_CMD | TSL2563_REG_INT);
|
||||
mutex_unlock(&chip->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return !!(ret & 0x30);
|
||||
}
|
||||
|
||||
static const struct iio_info tsl2563_info_no_irq = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &tsl2563_read_raw,
|
||||
.write_raw = &tsl2563_write_raw,
|
||||
};
|
||||
|
||||
static const struct iio_info tsl2563_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &tsl2563_read_raw,
|
||||
.write_raw = &tsl2563_write_raw,
|
||||
.read_event_value = &tsl2563_read_thresh,
|
||||
.write_event_value = &tsl2563_write_thresh,
|
||||
.read_event_config = &tsl2563_read_interrupt_config,
|
||||
.write_event_config = &tsl2563_write_interrupt_config,
|
||||
};
|
||||
|
||||
static int tsl2563_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *device_id)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct tsl2563_chip *chip;
|
||||
struct tsl2563_platform_data *pdata = client->dev.platform_data;
|
||||
struct device_node *np = client->dev.of_node;
|
||||
int err = 0;
|
||||
u8 id = 0;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
chip = iio_priv(indio_dev);
|
||||
|
||||
i2c_set_clientdata(client, chip);
|
||||
chip->client = client;
|
||||
|
||||
err = tsl2563_detect(chip);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "detect error %d\n", -err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = tsl2563_read_id(chip, &id);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "read id error %d\n", -err);
|
||||
return err;
|
||||
}
|
||||
|
||||
mutex_init(&chip->lock);
|
||||
|
||||
/* Default values used until userspace says otherwise */
|
||||
chip->low_thres = 0x0;
|
||||
chip->high_thres = 0xffff;
|
||||
chip->gainlevel = tsl2563_gainlevel_table;
|
||||
chip->intr = TSL2563_INT_PERSIST(4);
|
||||
chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS);
|
||||
chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS);
|
||||
|
||||
if (pdata)
|
||||
chip->cover_comp_gain = pdata->cover_comp_gain;
|
||||
else if (np)
|
||||
of_property_read_u32(np, "amstaos,cover-comp-gain",
|
||||
&chip->cover_comp_gain);
|
||||
else
|
||||
chip->cover_comp_gain = 1;
|
||||
|
||||
dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f);
|
||||
indio_dev->name = client->name;
|
||||
indio_dev->channels = tsl2563_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels);
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
if (client->irq)
|
||||
indio_dev->info = &tsl2563_info;
|
||||
else
|
||||
indio_dev->info = &tsl2563_info_no_irq;
|
||||
|
||||
if (client->irq) {
|
||||
err = devm_request_threaded_irq(&client->dev, client->irq,
|
||||
NULL,
|
||||
&tsl2563_event_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
"tsl2563_event",
|
||||
indio_dev);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "irq request error %d\n", -err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = tsl2563_configure(chip);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "configure error %d\n", -err);
|
||||
return err;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work);
|
||||
|
||||
/* The interrupt cannot yet be enabled so this is fine without lock */
|
||||
schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
|
||||
|
||||
err = iio_device_register(indio_dev);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "iio registration error %d\n", -err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
cancel_delayed_work(&chip->poweroff_work);
|
||||
flush_scheduled_work();
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tsl2563_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tsl2563_chip *chip = i2c_get_clientdata(client);
|
||||
struct iio_dev *indio_dev = iio_priv_to_dev(chip);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
if (!chip->int_enabled)
|
||||
cancel_delayed_work(&chip->poweroff_work);
|
||||
/* Ensure that interrupts are disabled - then flush any bottom halves */
|
||||
chip->intr &= ~0x30;
|
||||
i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | TSL2563_REG_INT,
|
||||
chip->intr);
|
||||
flush_scheduled_work();
|
||||
tsl2563_set_power(chip, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tsl2563_suspend(struct device *dev)
|
||||
{
|
||||
struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
ret = tsl2563_set_power(chip, 0);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
chip->suspended = true;
|
||||
|
||||
out:
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsl2563_resume(struct device *dev)
|
||||
{
|
||||
struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
ret = tsl2563_set_power(chip, 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = tsl2563_configure(chip);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
chip->suspended = false;
|
||||
|
||||
out:
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, tsl2563_resume);
|
||||
#define TSL2563_PM_OPS (&tsl2563_pm_ops)
|
||||
#else
|
||||
#define TSL2563_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id tsl2563_id[] = {
|
||||
{ "tsl2560", 0 },
|
||||
{ "tsl2561", 1 },
|
||||
{ "tsl2562", 2 },
|
||||
{ "tsl2563", 3 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tsl2563_id);
|
||||
|
||||
static struct i2c_driver tsl2563_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "tsl2563",
|
||||
.pm = TSL2563_PM_OPS,
|
||||
},
|
||||
.probe = tsl2563_probe,
|
||||
.remove = tsl2563_remove,
|
||||
.id_table = tsl2563_id,
|
||||
};
|
||||
module_i2c_driver(tsl2563_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Nokia Corporation");
|
||||
MODULE_DESCRIPTION("tsl2563 light sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
261
drivers/iio/light/tsl4531.c
Normal file
261
drivers/iio/light/tsl4531.c
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* tsl4531.c - Support for TAOS TSL4531 ambient light sensor
|
||||
*
|
||||
* Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* IIO driver for the TSL4531x family
|
||||
* TSL45311/TSL45313: 7-bit I2C slave address 0x39
|
||||
* TSL45315/TSL45317: 7-bit I2C slave address 0x29
|
||||
*
|
||||
* TODO: single cycle measurement
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
#define TSL4531_DRV_NAME "tsl4531"
|
||||
|
||||
#define TCS3472_COMMAND BIT(7)
|
||||
|
||||
#define TSL4531_CONTROL (TCS3472_COMMAND | 0x00)
|
||||
#define TSL4531_CONFIG (TCS3472_COMMAND | 0x01)
|
||||
#define TSL4531_DATA (TCS3472_COMMAND | 0x04)
|
||||
#define TSL4531_ID (TCS3472_COMMAND | 0x0a)
|
||||
|
||||
/* operating modes in control register */
|
||||
#define TSL4531_MODE_POWERDOWN 0x00
|
||||
#define TSL4531_MODE_SINGLE_ADC 0x02
|
||||
#define TSL4531_MODE_NORMAL 0x03
|
||||
|
||||
/* integration time control in config register */
|
||||
#define TSL4531_TCNTRL_400MS 0x00
|
||||
#define TSL4531_TCNTRL_200MS 0x01
|
||||
#define TSL4531_TCNTRL_100MS 0x02
|
||||
|
||||
/* part number in id register */
|
||||
#define TSL45311_ID 0x8
|
||||
#define TSL45313_ID 0x9
|
||||
#define TSL45315_ID 0xa
|
||||
#define TSL45317_ID 0xb
|
||||
#define TSL4531_ID_SHIFT 4
|
||||
|
||||
struct tsl4531_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
int int_time;
|
||||
};
|
||||
|
||||
static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.2 0.4");
|
||||
|
||||
static struct attribute *tsl4531_attributes[] = {
|
||||
&iio_const_attr_integration_time_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tsl4531_attribute_group = {
|
||||
.attrs = tsl4531_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec tsl4531_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME)
|
||||
}
|
||||
};
|
||||
|
||||
static int tsl4531_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct tsl4531_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
TSL4531_DATA);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
/* 0.. 1x, 1 .. 2x, 2 .. 4x */
|
||||
*val = 1 << data->int_time;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (data->int_time == 0)
|
||||
*val2 = 400000;
|
||||
else if (data->int_time == 1)
|
||||
*val2 = 200000;
|
||||
else if (data->int_time == 2)
|
||||
*val2 = 100000;
|
||||
else
|
||||
return -EINVAL;
|
||||
*val = 0;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int tsl4531_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct tsl4531_data *data = iio_priv(indio_dev);
|
||||
int int_time, ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
if (val2 == 400000)
|
||||
int_time = 0;
|
||||
else if (val2 == 200000)
|
||||
int_time = 1;
|
||||
else if (val2 == 100000)
|
||||
int_time = 2;
|
||||
else
|
||||
return -EINVAL;
|
||||
mutex_lock(&data->lock);
|
||||
ret = i2c_smbus_write_byte_data(data->client,
|
||||
TSL4531_CONFIG, int_time);
|
||||
if (ret >= 0)
|
||||
data->int_time = int_time;
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info tsl4531_info = {
|
||||
.read_raw = tsl4531_read_raw,
|
||||
.write_raw = tsl4531_write_raw,
|
||||
.attrs = &tsl4531_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int tsl4531_check_id(struct i2c_client *client)
|
||||
{
|
||||
int ret = i2c_smbus_read_byte_data(client, TSL4531_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (ret >> TSL4531_ID_SHIFT) {
|
||||
case TSL45311_ID:
|
||||
case TSL45313_ID:
|
||||
case TSL45315_ID:
|
||||
case TSL45317_ID:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int tsl4531_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct tsl4531_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
if (!tsl4531_check_id(client)) {
|
||||
dev_err(&client->dev, "no TSL4531 sensor\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL,
|
||||
TSL4531_MODE_NORMAL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONFIG,
|
||||
TSL4531_TCNTRL_400MS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &tsl4531_info;
|
||||
indio_dev->channels = tsl4531_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(tsl4531_channels);
|
||||
indio_dev->name = TSL4531_DRV_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
}
|
||||
|
||||
static int tsl4531_powerdown(struct i2c_client *client)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(client, TSL4531_CONTROL,
|
||||
TSL4531_MODE_POWERDOWN);
|
||||
}
|
||||
|
||||
static int tsl4531_remove(struct i2c_client *client)
|
||||
{
|
||||
iio_device_unregister(i2c_get_clientdata(client));
|
||||
tsl4531_powerdown(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tsl4531_suspend(struct device *dev)
|
||||
{
|
||||
return tsl4531_powerdown(to_i2c_client(dev));
|
||||
}
|
||||
|
||||
static int tsl4531_resume(struct device *dev)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(to_i2c_client(dev), TSL4531_CONTROL,
|
||||
TSL4531_MODE_NORMAL);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, tsl4531_resume);
|
||||
#define TSL4531_PM_OPS (&tsl4531_pm_ops)
|
||||
#else
|
||||
#define TSL4531_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id tsl4531_id[] = {
|
||||
{ "tsl4531", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tsl4531_id);
|
||||
|
||||
static struct i2c_driver tsl4531_driver = {
|
||||
.driver = {
|
||||
.name = TSL4531_DRV_NAME,
|
||||
.pm = TSL4531_PM_OPS,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = tsl4531_probe,
|
||||
.remove = tsl4531_remove,
|
||||
.id_table = tsl4531_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(tsl4531_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("TAOS TSL4531 ambient light sensors driver");
|
||||
MODULE_LICENSE("GPL");
|
198
drivers/iio/light/vcnl4000.c
Normal file
198
drivers/iio/light/vcnl4000.c
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* vcnl4000.c - Support for Vishay VCNL4000 combined ambient light and
|
||||
* proximity sensor
|
||||
*
|
||||
* Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* IIO driver for VCNL4000 (7-bit I2C slave address 0x13)
|
||||
*
|
||||
* TODO:
|
||||
* allow to adjust IR current
|
||||
* proximity threshold and event handling
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
#define VCNL4000_DRV_NAME "vcnl4000"
|
||||
|
||||
#define VCNL4000_COMMAND 0x80 /* Command register */
|
||||
#define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */
|
||||
#define VCNL4000_LED_CURRENT 0x83 /* IR LED current for proximity mode */
|
||||
#define VCNL4000_AL_PARAM 0x84 /* Ambient light parameter register */
|
||||
#define VCNL4000_AL_RESULT_HI 0x85 /* Ambient light result register, MSB */
|
||||
#define VCNL4000_AL_RESULT_LO 0x86 /* Ambient light result register, LSB */
|
||||
#define VCNL4000_PS_RESULT_HI 0x87 /* Proximity result register, MSB */
|
||||
#define VCNL4000_PS_RESULT_LO 0x88 /* Proximity result register, LSB */
|
||||
#define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */
|
||||
#define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */
|
||||
|
||||
/* Bit masks for COMMAND register */
|
||||
#define VCNL4000_AL_RDY 0x40 /* ALS data ready? */
|
||||
#define VCNL4000_PS_RDY 0x20 /* proximity data ready? */
|
||||
#define VCNL4000_AL_OD 0x10 /* start on-demand ALS measurement */
|
||||
#define VCNL4000_PS_OD 0x08 /* start on-demand proximity measurement */
|
||||
|
||||
struct vcnl4000_data {
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
static const struct i2c_device_id vcnl4000_id[] = {
|
||||
{ "vcnl4000", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
|
||||
|
||||
static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
|
||||
u8 rdy_mask, u8 data_reg, int *val)
|
||||
{
|
||||
int tries = 20;
|
||||
__be16 buf;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
|
||||
req_mask);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* wait for data to become ready */
|
||||
while (tries--) {
|
||||
ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret & rdy_mask)
|
||||
break;
|
||||
msleep(20); /* measurement takes up to 100 ms */
|
||||
}
|
||||
|
||||
if (tries < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"vcnl4000_measure() failed, data not ready\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(data->client,
|
||||
data_reg, sizeof(buf), (u8 *) &buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = be16_to_cpu(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec vcnl4000_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
}, {
|
||||
.type = IIO_PROXIMITY,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
}
|
||||
};
|
||||
|
||||
static int vcnl4000_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
struct vcnl4000_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
ret = vcnl4000_measure(data,
|
||||
VCNL4000_AL_OD, VCNL4000_AL_RDY,
|
||||
VCNL4000_AL_RESULT_HI, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
ret = vcnl4000_measure(data,
|
||||
VCNL4000_PS_OD, VCNL4000_PS_RDY,
|
||||
VCNL4000_PS_RESULT_HI, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
*val = 0;
|
||||
*val2 = 250000;
|
||||
ret = IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info vcnl4000_info = {
|
||||
.read_raw = vcnl4000_read_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int vcnl4000_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct vcnl4000_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_info(&client->dev, "VCNL4000 Ambient light/proximity sensor, Prod %02x, Rev: %02x\n",
|
||||
ret >> 4, ret & 0xf);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &vcnl4000_info;
|
||||
indio_dev->channels = vcnl4000_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels);
|
||||
indio_dev->name = VCNL4000_DRV_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static struct i2c_driver vcnl4000_driver = {
|
||||
.driver = {
|
||||
.name = VCNL4000_DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = vcnl4000_probe,
|
||||
.id_table = vcnl4000_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(vcnl4000_driver);
|
||||
|
||||
MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
|
||||
MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue