Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b6695254 authored by Adriana Reus's avatar Adriana Reus Committed by Jonathan Cameron
Browse files

iio: light: us5182d: Add interrupt support and events



Add interrupt support for proximity.
Add two threshold events to signal rising and falling directions.

Signed-off-by: default avatarAdriana Reus <adriana.reus@intel.com>
Signed-off-by: default avatarJonathan Cameron <jic23@kernel.org>
parent 58e9042f
Loading
Loading
Loading
Loading
+270 −1
Original line number Diff line number Diff line
@@ -20,7 +20,10 @@
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/iio/sysfs.h>
#include <linux/mutex.h>
#include <linux/pm.h>
@@ -30,6 +33,8 @@
#define US5182D_CFG0_ONESHOT_EN				BIT(6)
#define US5182D_CFG0_SHUTDOWN_EN			BIT(7)
#define US5182D_CFG0_WORD_ENABLE			BIT(0)
#define US5182D_CFG0_PROX				BIT(3)
#define US5182D_CFG0_PX_IRQ				BIT(2)

#define US5182D_REG_CFG1				0x01
#define US5182D_CFG1_ALS_RES16				BIT(4)
@@ -41,6 +46,7 @@

#define US5182D_REG_CFG3				0x03
#define US5182D_CFG3_LED_CURRENT100			(BIT(4) | BIT(5))
#define US5182D_CFG3_INT_SOURCE_PX			BIT(3)

#define US5182D_REG_CFG4				0x10

@@ -55,6 +61,13 @@
#define US5182D_REG_AUTO_LDARK_GAIN		0x29
#define US5182D_REG_AUTO_HDARK_GAIN		0x2a

/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */
#define US5182D_REG_PXL_TH			0x08
#define US5182D_REG_PXH_TH			0x0a

#define US5182D_REG_PXL_TH_DEFAULT		1000
#define US5182D_REG_PXH_TH_DEFAULT		30000

#define US5182D_OPMODE_ALS			0x01
#define US5182D_OPMODE_PX			0x02
#define US5182D_OPMODE_SHIFT			4
@@ -84,6 +97,8 @@
#define US5182D_READ_WORD			2
#define US5182D_OPSTORE_SLEEP_TIME		20 /* ms */
#define US5182D_SLEEP_MS			3000 /* ms */
#define US5182D_PXH_TH_DISABLE			0xffff
#define US5182D_PXL_TH_DISABLE			0x0000

/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */
static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600,
@@ -119,6 +134,12 @@ struct us5182d_data {
	u8 upper_dark_gain;
	u16 *us5182d_dark_ths;

	u16 px_low_th;
	u16 px_high_th;

	int rising_en;
	int falling_en;

	u8 opmode;
	u8 power_mode;

@@ -148,10 +169,26 @@ static const struct {
	{US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16},
	{US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 |
			    US5182D_CFG2_PXGAIN_DEFAULT)},
	{US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100},
	{US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 |
			   US5182D_CFG3_INT_SOURCE_PX},
	{US5182D_REG_CFG4, 0x00},
};

static const struct iio_event_spec us5182d_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 us5182d_channels[] = {
	{
		.type = IIO_LIGHT,
@@ -161,6 +198,8 @@ static const struct iio_chan_spec us5182d_channels[] = {
	{
		.type = IIO_PROXIMITY,
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
		.event_spec = us5182d_events,
		.num_event_specs = ARRAY_SIZE(us5182d_events),
	}
};

@@ -487,11 +526,201 @@ static int us5182d_write_raw(struct iio_dev *indio_dev,
	return -EINVAL;
}

static int us5182d_setup_prox(struct iio_dev *indio_dev,
			      enum iio_event_direction dir, u16 val)
{
	struct us5182d_data *data = iio_priv(indio_dev);

	if (dir == IIO_EV_DIR_FALLING)
		return i2c_smbus_write_word_data(data->client,
						 US5182D_REG_PXL_TH, val);
	else if (dir == IIO_EV_DIR_RISING)
		return i2c_smbus_write_word_data(data->client,
						 US5182D_REG_PXH_TH, val);

	return 0;
}

static int us5182d_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 us5182d_data *data = iio_priv(indio_dev);

	switch (dir) {
	case IIO_EV_DIR_RISING:
		mutex_lock(&data->lock);
		*val = data->px_high_th;
		mutex_unlock(&data->lock);
		break;
	case IIO_EV_DIR_FALLING:
		mutex_lock(&data->lock);
		*val = data->px_low_th;
		mutex_unlock(&data->lock);
		break;
	default:
		return -EINVAL;
	}

	return IIO_VAL_INT;
}

static int us5182d_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 us5182d_data *data = iio_priv(indio_dev);
	int ret;

	if (val < 0 || val > USHRT_MAX || val2 != 0)
		return -EINVAL;

	switch (dir) {
	case IIO_EV_DIR_RISING:
		mutex_lock(&data->lock);
		if (data->rising_en) {
			ret = us5182d_setup_prox(indio_dev, dir, val);
			if (ret < 0)
				goto err;
		}
		data->px_high_th = val;
		mutex_unlock(&data->lock);
		break;
	case IIO_EV_DIR_FALLING:
		mutex_lock(&data->lock);
		if (data->falling_en) {
			ret = us5182d_setup_prox(indio_dev, dir, val);
			if (ret < 0)
				goto err;
		}
		data->px_low_th = val;
		mutex_unlock(&data->lock);
		break;
	default:
		return -EINVAL;
	}

	return 0;
err:
	mutex_unlock(&data->lock);
	return ret;
}

static int us5182d_read_event_config(struct iio_dev *indio_dev,
	const struct iio_chan_spec *chan, enum iio_event_type type,
	enum iio_event_direction dir)
{
	struct us5182d_data *data = iio_priv(indio_dev);
	int ret;

	switch (dir) {
	case IIO_EV_DIR_RISING:
		mutex_lock(&data->lock);
		ret = data->rising_en;
		mutex_unlock(&data->lock);
		break;
	case IIO_EV_DIR_FALLING:
		mutex_lock(&data->lock);
		ret = data->falling_en;
		mutex_unlock(&data->lock);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static int us5182d_write_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 us5182d_data *data = iio_priv(indio_dev);
	int ret;
	u16 new_th;

	mutex_lock(&data->lock);

	switch (dir) {
	case IIO_EV_DIR_RISING:
		if (data->rising_en == state) {
			mutex_unlock(&data->lock);
			return 0;
		}
		new_th = US5182D_PXH_TH_DISABLE;
		if (state) {
			data->power_mode = US5182D_CONTINUOUS;
			ret = us5182d_set_power_state(data, true);
			if (ret < 0)
				goto err;
			ret = us5182d_px_enable(data);
			if (ret < 0)
				goto err_poweroff;
			new_th = data->px_high_th;
		}
		ret = us5182d_setup_prox(indio_dev, dir, new_th);
		if (ret < 0)
			goto err_poweroff;
		data->rising_en = state;
		break;
	case IIO_EV_DIR_FALLING:
		if (data->falling_en == state) {
			mutex_unlock(&data->lock);
			return 0;
		}
		new_th =  US5182D_PXL_TH_DISABLE;
		if (state) {
			data->power_mode = US5182D_CONTINUOUS;
			ret = us5182d_set_power_state(data, true);
			if (ret < 0)
				goto err;
			ret = us5182d_px_enable(data);
			if (ret < 0)
				goto err_poweroff;
			new_th = data->px_low_th;
		}
		ret = us5182d_setup_prox(indio_dev, dir, new_th);
		if (ret < 0)
			goto err_poweroff;
		data->falling_en = state;
		break;
	default:
		ret = -EINVAL;
		goto err;
	}

	if (!state) {
		ret = us5182d_set_power_state(data, false);
		if (ret < 0)
			goto err;
	}

	if (!data->falling_en && !data->rising_en && !data->default_continuous)
		data->power_mode = US5182D_ONESHOT;

	mutex_unlock(&data->lock);
	return 0;

err_poweroff:
	if (state)
		us5182d_set_power_state(data, false);
err:
	mutex_unlock(&data->lock);
	return ret;
}

static const struct iio_info us5182d_info = {
	.driver_module	= THIS_MODULE,
	.read_raw = us5182d_read_raw,
	.write_raw = us5182d_write_raw,
	.attrs = &us5182d_attr_group,
	.read_event_value = &us5182d_read_thresh,
	.write_event_value = &us5182d_write_thresh,
	.read_event_config = &us5182d_read_event_config,
	.write_event_config = &us5182d_write_event_config,
};

static int us5182d_reset(struct iio_dev *indio_dev)
@@ -513,6 +742,9 @@ static int us5182d_init(struct iio_dev *indio_dev)

	data->opmode = 0;
	data->power_mode = US5182D_CONTINUOUS;
	data->px_low_th = US5182D_REG_PXL_TH_DEFAULT;
	data->px_high_th = US5182D_REG_PXH_TH_DEFAULT;

	for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) {
		ret = i2c_smbus_write_byte_data(data->client,
						us5182d_regvals[i].reg,
@@ -583,6 +815,33 @@ static int us5182d_dark_gain_config(struct iio_dev *indio_dev)
					 US5182D_REG_DARK_AUTO_EN_DEFAULT);
}

static irqreturn_t us5182d_irq_thread_handler(int irq, void *private)
{
	struct iio_dev *indio_dev = private;
	struct us5182d_data *data = iio_priv(indio_dev);
	enum iio_event_direction dir;
	int ret;
	u64 ev;

	ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
	if (ret < 0) {
		dev_err(&data->client->dev, "i2c transfer error in irq\n");
		return IRQ_HANDLED;
	}

	dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
	ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir);

	iio_push_event(indio_dev, ev, iio_get_time_ns());

	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0,
					ret & ~US5182D_CFG0_PX_IRQ);
	if (ret < 0)
		dev_err(&data->client->dev, "i2c transfer error in irq\n");

	return IRQ_HANDLED;
}

static int us5182d_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
@@ -614,6 +873,16 @@ static int us5182d_probe(struct i2c_client *client,
		return (ret < 0) ? ret : -ENODEV;
	}

	if (client->irq > 0) {
		ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
						us5182d_irq_thread_handler,
						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
						"us5182d-irq", indio_dev);
		if (ret < 0)
			return ret;
	} else
		dev_warn(&client->dev, "no valid irq found\n");

	us5182d_get_platform_data(indio_dev);
	ret = us5182d_init(indio_dev);
	if (ret < 0)