Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

66
drivers/iio/light/Kconfig Normal file
View File

@@ -0,0 +1,66 @@
#
# Light sensors
#
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 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 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 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.
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.
endmenu

View File

@@ -0,0 +1,9 @@
#
# Makefile for IIO Light sensors
#
obj-$(CONFIG_ADJD_S311) += adjd_s311.o
obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
obj-$(CONFIG_VCNL4000) += vcnl4000.o
obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o

View File

@@ -0,0 +1,364 @@
/*
* 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/init.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_LO 0x0a
#define ADJD_S311_INT_RED_HI 0x0b
#define ADJD_S311_INT_GREEN_LO 0x0c
#define ADJD_S311_INT_GREEN_HI 0x0d
#define ADJD_S311_INT_BLUE_LO 0x0e
#define ADJD_S311_INT_BLUE_HI 0x0f
#define ADJD_S311_INT_CLEAR_LO 0x10
#define ADJD_S311_INT_CLEAR_HI 0x11
#define ADJD_S311_DATA_RED_LO 0x40
#define ADJD_S311_DATA_RED_HI 0x41
#define ADJD_S311_DATA_GREEN_LO 0x42
#define ADJD_S311_DATA_GREEN_HI 0x43
#define ADJD_S311_DATA_BLUE_LO 0x44
#define ADJD_S311_DATA_BLUE_HI 0x45
#define ADJD_S311_DATA_CLEAR_LO 0x46
#define ADJD_S311_DATA_CLEAR_HI 0x47
#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_LO + (chan) * 2)
#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED_LO + (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 ssize_t adjd_s311_read_int_time(struct iio_dev *indio_dev,
uintptr_t private, const struct iio_chan_spec *chan, char *buf)
{
struct adjd_s311_data *data = iio_priv(indio_dev);
s32 ret;
ret = i2c_smbus_read_word_data(data->client,
ADJD_S311_INT_REG(chan->address));
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", ret & ADJD_S311_INT_MASK);
}
static ssize_t adjd_s311_write_int_time(struct iio_dev *indio_dev,
uintptr_t private, const struct iio_chan_spec *chan, const char *buf,
size_t len)
{
struct adjd_s311_data *data = iio_priv(indio_dev);
unsigned long int_time;
int ret;
ret = kstrtoul(buf, 10, &int_time);
if (ret)
return ret;
if (int_time > ADJD_S311_INT_MASK)
return -EINVAL;
ret = i2c_smbus_write_word_data(data->client,
ADJD_S311_INT_REG(chan->address), int_time);
if (ret < 0)
return ret;
return len;
}
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 len = 0;
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;
len += 2;
}
if (indio_dev->scan_timestamp)
*(s64 *)((u8 *)data->buffer + ALIGN(len, sizeof(s64)))
= time_ns;
iio_push_to_buffers(indio_dev, (u8 *)data->buffer);
done:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static const struct iio_chan_spec_ext_info adjd_s311_ext_info[] = {
{
.name = "integration_time",
.read = adjd_s311_read_int_time,
.write = adjd_s311_write_int_time,
},
{ }
};
#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), \
.channel2 = (IIO_MOD_LIGHT_##_color), \
.scan_index = (_scan_idx), \
.scan_type = IIO_ST('u', 10, 16, 0), \
.ext_info = adjd_s311_ext_info, \
}
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, 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;
}
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);
int ret;
switch (mask) {
case IIO_CHAN_INFO_HARDWAREGAIN:
if (val < 0 || val > ADJD_S311_CAP_MASK)
return -EINVAL;
ret = i2c_smbus_write_byte_data(data->client,
ADJD_S311_CAP_REG(chan->address), val);
return ret;
}
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 = iio_device_alloc(sizeof(*data));
if (indio_dev == NULL) {
err = -ENOMEM;
goto exit;
}
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)
goto exit_free_device;
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);
exit_free_device:
iio_device_free(indio_dev);
exit:
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);
iio_device_free(indio_dev);
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");

View File

@@ -0,0 +1,370 @@
/*
* 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/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"
/*Format: HID-SENSOR-usage_id_in_hex*/
/*Usage ID from spec for Accelerometer-3D: 0x200041*/
#define DRIVER_NAME "HID-SENSOR-200041"
#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;
};
/* Channel definitions */
static const struct iio_chan_spec als_channels[] = {
{
.type = IIO_INTENSITY,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_BOTH,
.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;
int ret_type;
*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)
*val = sensor_hub_input_attr_get_raw_value(
als_state->common_attributes.hsdev,
HID_USAGE_SENSOR_ALS, address,
report_id);
else {
*val = 0;
return -EINVAL;
}
ret_type = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_SCALE:
*val = als_state->als_illum.units;
ret_type = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_OFFSET:
*val = hid_sensor_convert_exponent(
als_state->als_illum.unit_expo);
ret_type = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_SAMP_FREQ:
ret = hid_sensor_read_samp_freq_value(
&als_state->common_attributes, val, val2);
ret_type = IIO_VAL_INT_PLUS_MICRO;
break;
case IIO_CHAN_INFO_HYSTERESIS:
ret = hid_sensor_read_raw_hyst_value(
&als_state->common_attributes, val, val2);
ret_type = IIO_VAL_INT_PLUS_MICRO;
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 int als_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
long mask)
{
return IIO_VAL_INT_PLUS_MICRO;
}
static const struct iio_info als_info = {
.driver_module = THIS_MODULE,
.read_raw = &als_read_raw,
.write_raw = &als_write_raw,
.write_raw_get_fmt = &als_write_raw_get_fmt,
};
/* Function to push data to buffer */
static void hid_sensor_push_data(struct iio_dev *indio_dev, u8 *data, int len)
{
dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
iio_push_to_buffers(indio_dev, (u8 *)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 [%d]\n",
als_state->common_attributes.data_ready);
if (als_state->common_attributes.data_ready)
hid_sensor_push_data(indio_dev,
(u8 *)&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);
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 = iio_device_alloc(sizeof(struct als_state));
if (indio_dev == NULL) {
ret = -ENOMEM;
goto error_ret;
}
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");
goto error_free_dev;
}
channels = kmemdup(als_channels, sizeof(als_channels), GFP_KERNEL);
if (!channels) {
ret = -ENOMEM;
dev_err(&pdev->dev, "failed to duplicate channels\n");
goto error_free_dev;
}
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;
}
als_state->common_attributes.data_ready = false;
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(indio_dev);
error_unreg_buffer_funcs:
iio_triggered_buffer_cleanup(indio_dev);
error_free_dev_mem:
kfree(indio_dev->channels);
error_free_dev:
iio_device_free(indio_dev);
error_ret:
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);
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS);
iio_device_unregister(indio_dev);
hid_sensor_remove_trigger(indio_dev);
iio_triggered_buffer_cleanup(indio_dev);
kfree(indio_dev->channels);
iio_device_free(indio_dev);
return 0;
}
static struct platform_driver hid_als_platform_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.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,931 @@
/*
* 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 = iio_device_alloc(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)
goto err_free_dev;
}
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);
err_free_dev:
iio_device_free(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);
iio_device_free(indio_dev);
return 0;
}
static struct platform_driver lm3533_als_driver = {
.driver = {
.name = "lm3533-als",
.owner = THIS_MODULE,
},
.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");

887
drivers/iio/light/tsl2563.c Normal file
View File

@@ -0,0 +1,887 @@
/*
* 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 (chan->channel == IIO_MOD_LIGHT_BOTH)
chip->calib0 = calib_from_sysfs(val);
else
chip->calib1 = calib_from_sysfs(val);
return 0;
}
static int tsl2563_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
int *val2,
long m)
{
int ret = -EINVAL;
u32 calib0, calib1;
struct tsl2563_chip *chip = iio_priv(indio_dev);
mutex_lock(&chip->lock);
switch (m) {
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->channel == 0)
*val = chip->data0;
else
*val = chip->data1;
ret = IIO_VAL_INT;
break;
default:
break;
}
break;
case IIO_CHAN_INFO_CALIBSCALE:
if (chan->channel == 0)
*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_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_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING) |
IIO_EV_BIT(IIO_EV_TYPE_THRESH,
IIO_EV_DIR_FALLING)),
}, {
.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,
u64 event_code,
int *val)
{
struct tsl2563_chip *chip = iio_priv(indio_dev);
switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) {
case IIO_EV_DIR_RISING:
*val = chip->high_thres;
break;
case IIO_EV_DIR_FALLING:
*val = chip->low_thres;
break;
default:
return -EINVAL;
}
return 0;
}
static int tsl2563_write_thresh(struct iio_dev *indio_dev,
u64 event_code,
int val)
{
struct tsl2563_chip *chip = iio_priv(indio_dev);
int ret;
u8 address;
if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == 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 (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == 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,
u64 event_code,
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,
u64 event_code)
{
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;
int err = 0;
u8 id = 0;
indio_dev = iio_device_alloc(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);
goto fail1;
}
err = tsl2563_read_id(chip, &id);
if (err) {
dev_err(&client->dev, "read id error %d\n", -err);
goto fail1;
}
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
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 = request_threaded_irq(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);
goto fail1;
}
}
err = tsl2563_configure(chip);
if (err) {
dev_err(&client->dev, "configure error %d\n", -err);
goto fail2;
}
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 fail3;
}
return 0;
fail3:
cancel_delayed_work(&chip->poweroff_work);
flush_scheduled_work();
fail2:
if (client->irq)
free_irq(client->irq, indio_dev);
fail1:
iio_device_free(indio_dev);
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);
if (client->irq)
free_irq(client->irq, indio_dev);
iio_device_free(indio_dev);
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");

View File

@@ -0,0 +1,217 @@
/*
* 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;
u16 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 = iio_device_alloc(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)
goto error_free_dev;
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;
ret = iio_device_register(indio_dev);
if (ret < 0)
goto error_free_dev;
return 0;
error_free_dev:
iio_device_free(indio_dev);
return ret;
}
static int vcnl4000_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
iio_device_unregister(indio_dev);
iio_device_free(indio_dev);
return 0;
}
static struct i2c_driver vcnl4000_driver = {
.driver = {
.name = VCNL4000_DRV_NAME,
.owner = THIS_MODULE,
},
.probe = vcnl4000_probe,
.remove = vcnl4000_remove,
.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");