Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

195
drivers/iio/light/Kconfig Normal file
View 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

View 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

View 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
View 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");

View 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
View 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
View 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");

File diff suppressed because it is too large Load diff

View 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");

View 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");

View 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");

View 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
View 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 = &ltr501_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 = &ltr501_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 = &ltr501_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
View 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
View 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
View 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
View 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");

View 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");