18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Azoteq IQS621/622 Ambient Light Sensors
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/device.h>
98c2ecf20Sopenharmony_ci#include <linux/iio/events.h>
108c2ecf20Sopenharmony_ci#include <linux/iio/iio.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/mfd/iqs62x.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/mutex.h>
158c2ecf20Sopenharmony_ci#include <linux/notifier.h>
168c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
178c2ecf20Sopenharmony_ci#include <linux/regmap.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define IQS621_ALS_FLAGS_LIGHT			BIT(7)
208c2ecf20Sopenharmony_ci#define IQS621_ALS_FLAGS_RANGE			GENMASK(3, 0)
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define IQS621_ALS_UI_OUT			0x17
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define IQS621_ALS_THRESH_DARK			0x80
258c2ecf20Sopenharmony_ci#define IQS621_ALS_THRESH_LIGHT			0x81
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define IQS622_IR_RANGE				0x15
288c2ecf20Sopenharmony_ci#define IQS622_IR_FLAGS				0x16
298c2ecf20Sopenharmony_ci#define IQS622_IR_FLAGS_TOUCH			BIT(1)
308c2ecf20Sopenharmony_ci#define IQS622_IR_FLAGS_PROX			BIT(0)
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define IQS622_IR_UI_OUT			0x17
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define IQS622_IR_THRESH_PROX			0x91
358c2ecf20Sopenharmony_ci#define IQS622_IR_THRESH_TOUCH			0x92
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistruct iqs621_als_private {
388c2ecf20Sopenharmony_ci	struct iqs62x_core *iqs62x;
398c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev;
408c2ecf20Sopenharmony_ci	struct notifier_block notifier;
418c2ecf20Sopenharmony_ci	struct mutex lock;
428c2ecf20Sopenharmony_ci	bool light_en;
438c2ecf20Sopenharmony_ci	bool range_en;
448c2ecf20Sopenharmony_ci	bool prox_en;
458c2ecf20Sopenharmony_ci	u8 als_flags;
468c2ecf20Sopenharmony_ci	u8 ir_flags_mask;
478c2ecf20Sopenharmony_ci	u8 ir_flags;
488c2ecf20Sopenharmony_ci	u8 thresh_light;
498c2ecf20Sopenharmony_ci	u8 thresh_dark;
508c2ecf20Sopenharmony_ci	u8 thresh_prox;
518c2ecf20Sopenharmony_ci};
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int iqs621_als_init(struct iqs621_als_private *iqs621_als)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
568c2ecf20Sopenharmony_ci	unsigned int event_mask = 0;
578c2ecf20Sopenharmony_ci	int ret;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	switch (iqs621_als->ir_flags_mask) {
608c2ecf20Sopenharmony_ci	case IQS622_IR_FLAGS_TOUCH:
618c2ecf20Sopenharmony_ci		ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_TOUCH,
628c2ecf20Sopenharmony_ci				   iqs621_als->thresh_prox);
638c2ecf20Sopenharmony_ci		break;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	case IQS622_IR_FLAGS_PROX:
668c2ecf20Sopenharmony_ci		ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_PROX,
678c2ecf20Sopenharmony_ci				   iqs621_als->thresh_prox);
688c2ecf20Sopenharmony_ci		break;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	default:
718c2ecf20Sopenharmony_ci		ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT,
728c2ecf20Sopenharmony_ci				   iqs621_als->thresh_light);
738c2ecf20Sopenharmony_ci		if (ret)
748c2ecf20Sopenharmony_ci			return ret;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_DARK,
778c2ecf20Sopenharmony_ci				   iqs621_als->thresh_dark);
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	if (ret)
818c2ecf20Sopenharmony_ci		return ret;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	if (iqs621_als->light_en || iqs621_als->range_en)
848c2ecf20Sopenharmony_ci		event_mask |= iqs62x->dev_desc->als_mask;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (iqs621_als->prox_en)
878c2ecf20Sopenharmony_ci		event_mask |= iqs62x->dev_desc->ir_mask;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	return regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
908c2ecf20Sopenharmony_ci				  event_mask, 0);
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic int iqs621_als_notifier(struct notifier_block *notifier,
948c2ecf20Sopenharmony_ci			       unsigned long event_flags, void *context)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct iqs62x_event_data *event_data = context;
978c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als;
988c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev;
998c2ecf20Sopenharmony_ci	bool light_new, light_old;
1008c2ecf20Sopenharmony_ci	bool prox_new, prox_old;
1018c2ecf20Sopenharmony_ci	u8 range_new, range_old;
1028c2ecf20Sopenharmony_ci	s64 timestamp;
1038c2ecf20Sopenharmony_ci	int ret;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	iqs621_als = container_of(notifier, struct iqs621_als_private,
1068c2ecf20Sopenharmony_ci				  notifier);
1078c2ecf20Sopenharmony_ci	indio_dev = iqs621_als->indio_dev;
1088c2ecf20Sopenharmony_ci	timestamp = iio_get_time_ns(indio_dev);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	mutex_lock(&iqs621_als->lock);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
1138c2ecf20Sopenharmony_ci		ret = iqs621_als_init(iqs621_als);
1148c2ecf20Sopenharmony_ci		if (ret) {
1158c2ecf20Sopenharmony_ci			dev_err(indio_dev->dev.parent,
1168c2ecf20Sopenharmony_ci				"Failed to re-initialize device: %d\n", ret);
1178c2ecf20Sopenharmony_ci			ret = NOTIFY_BAD;
1188c2ecf20Sopenharmony_ci		} else {
1198c2ecf20Sopenharmony_ci			ret = NOTIFY_OK;
1208c2ecf20Sopenharmony_ci		}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci		goto err_mutex;
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	if (!iqs621_als->light_en && !iqs621_als->range_en &&
1268c2ecf20Sopenharmony_ci	    !iqs621_als->prox_en) {
1278c2ecf20Sopenharmony_ci		ret = NOTIFY_DONE;
1288c2ecf20Sopenharmony_ci		goto err_mutex;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* IQS621 only */
1328c2ecf20Sopenharmony_ci	light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT;
1338c2ecf20Sopenharmony_ci	light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (iqs621_als->light_en && light_new && !light_old)
1368c2ecf20Sopenharmony_ci		iio_push_event(indio_dev,
1378c2ecf20Sopenharmony_ci			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
1388c2ecf20Sopenharmony_ci						    IIO_EV_TYPE_THRESH,
1398c2ecf20Sopenharmony_ci						    IIO_EV_DIR_RISING),
1408c2ecf20Sopenharmony_ci			       timestamp);
1418c2ecf20Sopenharmony_ci	else if (iqs621_als->light_en && !light_new && light_old)
1428c2ecf20Sopenharmony_ci		iio_push_event(indio_dev,
1438c2ecf20Sopenharmony_ci			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
1448c2ecf20Sopenharmony_ci						    IIO_EV_TYPE_THRESH,
1458c2ecf20Sopenharmony_ci						    IIO_EV_DIR_FALLING),
1468c2ecf20Sopenharmony_ci			       timestamp);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/* IQS621 and IQS622 */
1498c2ecf20Sopenharmony_ci	range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE;
1508c2ecf20Sopenharmony_ci	range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	if (iqs621_als->range_en && (range_new > range_old))
1538c2ecf20Sopenharmony_ci		iio_push_event(indio_dev,
1548c2ecf20Sopenharmony_ci			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
1558c2ecf20Sopenharmony_ci						    IIO_EV_TYPE_CHANGE,
1568c2ecf20Sopenharmony_ci						    IIO_EV_DIR_RISING),
1578c2ecf20Sopenharmony_ci			       timestamp);
1588c2ecf20Sopenharmony_ci	else if (iqs621_als->range_en && (range_new < range_old))
1598c2ecf20Sopenharmony_ci		iio_push_event(indio_dev,
1608c2ecf20Sopenharmony_ci			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
1618c2ecf20Sopenharmony_ci						    IIO_EV_TYPE_CHANGE,
1628c2ecf20Sopenharmony_ci						    IIO_EV_DIR_FALLING),
1638c2ecf20Sopenharmony_ci			       timestamp);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	/* IQS622 only */
1668c2ecf20Sopenharmony_ci	prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask;
1678c2ecf20Sopenharmony_ci	prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (iqs621_als->prox_en && prox_new && !prox_old)
1708c2ecf20Sopenharmony_ci		iio_push_event(indio_dev,
1718c2ecf20Sopenharmony_ci			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
1728c2ecf20Sopenharmony_ci						    IIO_EV_TYPE_THRESH,
1738c2ecf20Sopenharmony_ci						    IIO_EV_DIR_RISING),
1748c2ecf20Sopenharmony_ci			       timestamp);
1758c2ecf20Sopenharmony_ci	else if (iqs621_als->prox_en && !prox_new && prox_old)
1768c2ecf20Sopenharmony_ci		iio_push_event(indio_dev,
1778c2ecf20Sopenharmony_ci			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
1788c2ecf20Sopenharmony_ci						    IIO_EV_TYPE_THRESH,
1798c2ecf20Sopenharmony_ci						    IIO_EV_DIR_FALLING),
1808c2ecf20Sopenharmony_ci			       timestamp);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	iqs621_als->als_flags = event_data->als_flags;
1838c2ecf20Sopenharmony_ci	iqs621_als->ir_flags = event_data->ir_flags;
1848c2ecf20Sopenharmony_ci	ret = NOTIFY_OK;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cierr_mutex:
1878c2ecf20Sopenharmony_ci	mutex_unlock(&iqs621_als->lock);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	return ret;
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic void iqs621_als_notifier_unregister(void *context)
1938c2ecf20Sopenharmony_ci{
1948c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als = context;
1958c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev = iqs621_als->indio_dev;
1968c2ecf20Sopenharmony_ci	int ret;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh,
1998c2ecf20Sopenharmony_ci						 &iqs621_als->notifier);
2008c2ecf20Sopenharmony_ci	if (ret)
2018c2ecf20Sopenharmony_ci		dev_err(indio_dev->dev.parent,
2028c2ecf20Sopenharmony_ci			"Failed to unregister notifier: %d\n", ret);
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int iqs621_als_read_raw(struct iio_dev *indio_dev,
2068c2ecf20Sopenharmony_ci			       struct iio_chan_spec const *chan,
2078c2ecf20Sopenharmony_ci			       int *val, int *val2, long mask)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
2108c2ecf20Sopenharmony_ci	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
2118c2ecf20Sopenharmony_ci	int ret;
2128c2ecf20Sopenharmony_ci	__le16 val_buf;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	switch (chan->type) {
2158c2ecf20Sopenharmony_ci	case IIO_INTENSITY:
2168c2ecf20Sopenharmony_ci		ret = regmap_read(iqs62x->regmap, chan->address, val);
2178c2ecf20Sopenharmony_ci		if (ret)
2188c2ecf20Sopenharmony_ci			return ret;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci		*val &= IQS621_ALS_FLAGS_RANGE;
2218c2ecf20Sopenharmony_ci		return IIO_VAL_INT;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	case IIO_PROXIMITY:
2248c2ecf20Sopenharmony_ci	case IIO_LIGHT:
2258c2ecf20Sopenharmony_ci		ret = regmap_raw_read(iqs62x->regmap, chan->address, &val_buf,
2268c2ecf20Sopenharmony_ci				      sizeof(val_buf));
2278c2ecf20Sopenharmony_ci		if (ret)
2288c2ecf20Sopenharmony_ci			return ret;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci		*val = le16_to_cpu(val_buf);
2318c2ecf20Sopenharmony_ci		return IIO_VAL_INT;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	default:
2348c2ecf20Sopenharmony_ci		return -EINVAL;
2358c2ecf20Sopenharmony_ci	}
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic int iqs621_als_read_event_config(struct iio_dev *indio_dev,
2398c2ecf20Sopenharmony_ci					const struct iio_chan_spec *chan,
2408c2ecf20Sopenharmony_ci					enum iio_event_type type,
2418c2ecf20Sopenharmony_ci					enum iio_event_direction dir)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
2448c2ecf20Sopenharmony_ci	int ret;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	mutex_lock(&iqs621_als->lock);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	switch (chan->type) {
2498c2ecf20Sopenharmony_ci	case IIO_LIGHT:
2508c2ecf20Sopenharmony_ci		ret = iqs621_als->light_en;
2518c2ecf20Sopenharmony_ci		break;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	case IIO_INTENSITY:
2548c2ecf20Sopenharmony_ci		ret = iqs621_als->range_en;
2558c2ecf20Sopenharmony_ci		break;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	case IIO_PROXIMITY:
2588c2ecf20Sopenharmony_ci		ret = iqs621_als->prox_en;
2598c2ecf20Sopenharmony_ci		break;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	default:
2628c2ecf20Sopenharmony_ci		ret = -EINVAL;
2638c2ecf20Sopenharmony_ci	}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	mutex_unlock(&iqs621_als->lock);
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	return ret;
2688c2ecf20Sopenharmony_ci}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic int iqs621_als_write_event_config(struct iio_dev *indio_dev,
2718c2ecf20Sopenharmony_ci					 const struct iio_chan_spec *chan,
2728c2ecf20Sopenharmony_ci					 enum iio_event_type type,
2738c2ecf20Sopenharmony_ci					 enum iio_event_direction dir,
2748c2ecf20Sopenharmony_ci					 int state)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
2778c2ecf20Sopenharmony_ci	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
2788c2ecf20Sopenharmony_ci	unsigned int val;
2798c2ecf20Sopenharmony_ci	int ret;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	mutex_lock(&iqs621_als->lock);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->als_flags, &val);
2848c2ecf20Sopenharmony_ci	if (ret)
2858c2ecf20Sopenharmony_ci		goto err_mutex;
2868c2ecf20Sopenharmony_ci	iqs621_als->als_flags = val;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	switch (chan->type) {
2898c2ecf20Sopenharmony_ci	case IIO_LIGHT:
2908c2ecf20Sopenharmony_ci		ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
2918c2ecf20Sopenharmony_ci					 iqs62x->dev_desc->als_mask,
2928c2ecf20Sopenharmony_ci					 iqs621_als->range_en || state ? 0 :
2938c2ecf20Sopenharmony_ci									 0xFF);
2948c2ecf20Sopenharmony_ci		if (!ret)
2958c2ecf20Sopenharmony_ci			iqs621_als->light_en = state;
2968c2ecf20Sopenharmony_ci		break;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	case IIO_INTENSITY:
2998c2ecf20Sopenharmony_ci		ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
3008c2ecf20Sopenharmony_ci					 iqs62x->dev_desc->als_mask,
3018c2ecf20Sopenharmony_ci					 iqs621_als->light_en || state ? 0 :
3028c2ecf20Sopenharmony_ci									 0xFF);
3038c2ecf20Sopenharmony_ci		if (!ret)
3048c2ecf20Sopenharmony_ci			iqs621_als->range_en = state;
3058c2ecf20Sopenharmony_ci		break;
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci	case IIO_PROXIMITY:
3088c2ecf20Sopenharmony_ci		ret = regmap_read(iqs62x->regmap, IQS622_IR_FLAGS, &val);
3098c2ecf20Sopenharmony_ci		if (ret)
3108c2ecf20Sopenharmony_ci			goto err_mutex;
3118c2ecf20Sopenharmony_ci		iqs621_als->ir_flags = val;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci		ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
3148c2ecf20Sopenharmony_ci					 iqs62x->dev_desc->ir_mask,
3158c2ecf20Sopenharmony_ci					 state ? 0 : 0xFF);
3168c2ecf20Sopenharmony_ci		if (!ret)
3178c2ecf20Sopenharmony_ci			iqs621_als->prox_en = state;
3188c2ecf20Sopenharmony_ci		break;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	default:
3218c2ecf20Sopenharmony_ci		ret = -EINVAL;
3228c2ecf20Sopenharmony_ci	}
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_cierr_mutex:
3258c2ecf20Sopenharmony_ci	mutex_unlock(&iqs621_als->lock);
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	return ret;
3288c2ecf20Sopenharmony_ci}
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_cistatic int iqs621_als_read_event_value(struct iio_dev *indio_dev,
3318c2ecf20Sopenharmony_ci				       const struct iio_chan_spec *chan,
3328c2ecf20Sopenharmony_ci				       enum iio_event_type type,
3338c2ecf20Sopenharmony_ci				       enum iio_event_direction dir,
3348c2ecf20Sopenharmony_ci				       enum iio_event_info info,
3358c2ecf20Sopenharmony_ci				       int *val, int *val2)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
3388c2ecf20Sopenharmony_ci	int ret = IIO_VAL_INT;
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	mutex_lock(&iqs621_als->lock);
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci	switch (dir) {
3438c2ecf20Sopenharmony_ci	case IIO_EV_DIR_RISING:
3448c2ecf20Sopenharmony_ci		*val = iqs621_als->thresh_light * 16;
3458c2ecf20Sopenharmony_ci		break;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	case IIO_EV_DIR_FALLING:
3488c2ecf20Sopenharmony_ci		*val = iqs621_als->thresh_dark * 4;
3498c2ecf20Sopenharmony_ci		break;
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	case IIO_EV_DIR_EITHER:
3528c2ecf20Sopenharmony_ci		if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH)
3538c2ecf20Sopenharmony_ci			*val = iqs621_als->thresh_prox * 4;
3548c2ecf20Sopenharmony_ci		else
3558c2ecf20Sopenharmony_ci			*val = iqs621_als->thresh_prox;
3568c2ecf20Sopenharmony_ci		break;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	default:
3598c2ecf20Sopenharmony_ci		ret = -EINVAL;
3608c2ecf20Sopenharmony_ci	}
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	mutex_unlock(&iqs621_als->lock);
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	return ret;
3658c2ecf20Sopenharmony_ci}
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_cistatic int iqs621_als_write_event_value(struct iio_dev *indio_dev,
3688c2ecf20Sopenharmony_ci					const struct iio_chan_spec *chan,
3698c2ecf20Sopenharmony_ci					enum iio_event_type type,
3708c2ecf20Sopenharmony_ci					enum iio_event_direction dir,
3718c2ecf20Sopenharmony_ci					enum iio_event_info info,
3728c2ecf20Sopenharmony_ci					int val, int val2)
3738c2ecf20Sopenharmony_ci{
3748c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
3758c2ecf20Sopenharmony_ci	struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
3768c2ecf20Sopenharmony_ci	unsigned int thresh_reg, thresh_val;
3778c2ecf20Sopenharmony_ci	u8 ir_flags_mask, *thresh_cache;
3788c2ecf20Sopenharmony_ci	int ret = -EINVAL;
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	mutex_lock(&iqs621_als->lock);
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	switch (dir) {
3838c2ecf20Sopenharmony_ci	case IIO_EV_DIR_RISING:
3848c2ecf20Sopenharmony_ci		thresh_reg = IQS621_ALS_THRESH_LIGHT;
3858c2ecf20Sopenharmony_ci		thresh_val = val / 16;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci		thresh_cache = &iqs621_als->thresh_light;
3888c2ecf20Sopenharmony_ci		ir_flags_mask = 0;
3898c2ecf20Sopenharmony_ci		break;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	case IIO_EV_DIR_FALLING:
3928c2ecf20Sopenharmony_ci		thresh_reg = IQS621_ALS_THRESH_DARK;
3938c2ecf20Sopenharmony_ci		thresh_val = val / 4;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci		thresh_cache = &iqs621_als->thresh_dark;
3968c2ecf20Sopenharmony_ci		ir_flags_mask = 0;
3978c2ecf20Sopenharmony_ci		break;
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_ci	case IIO_EV_DIR_EITHER:
4008c2ecf20Sopenharmony_ci		/*
4018c2ecf20Sopenharmony_ci		 * The IQS622 supports two detection thresholds, both measured
4028c2ecf20Sopenharmony_ci		 * in the same arbitrary units reported by read_raw: proximity
4038c2ecf20Sopenharmony_ci		 * (0 through 255 in steps of 1), and touch (0 through 1020 in
4048c2ecf20Sopenharmony_ci		 * steps of 4).
4058c2ecf20Sopenharmony_ci		 *
4068c2ecf20Sopenharmony_ci		 * Based on the single detection threshold chosen by the user,
4078c2ecf20Sopenharmony_ci		 * select the hardware threshold that gives the best trade-off
4088c2ecf20Sopenharmony_ci		 * between range and resolution.
4098c2ecf20Sopenharmony_ci		 *
4108c2ecf20Sopenharmony_ci		 * By default, the close-range (but coarse) touch threshold is
4118c2ecf20Sopenharmony_ci		 * chosen during probe.
4128c2ecf20Sopenharmony_ci		 */
4138c2ecf20Sopenharmony_ci		switch (val) {
4148c2ecf20Sopenharmony_ci		case 0 ... 255:
4158c2ecf20Sopenharmony_ci			thresh_reg = IQS622_IR_THRESH_PROX;
4168c2ecf20Sopenharmony_ci			thresh_val = val;
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci			ir_flags_mask = IQS622_IR_FLAGS_PROX;
4198c2ecf20Sopenharmony_ci			break;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci		case 256 ... 1020:
4228c2ecf20Sopenharmony_ci			thresh_reg = IQS622_IR_THRESH_TOUCH;
4238c2ecf20Sopenharmony_ci			thresh_val = val / 4;
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_ci			ir_flags_mask = IQS622_IR_FLAGS_TOUCH;
4268c2ecf20Sopenharmony_ci			break;
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci		default:
4298c2ecf20Sopenharmony_ci			goto err_mutex;
4308c2ecf20Sopenharmony_ci		}
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci		thresh_cache = &iqs621_als->thresh_prox;
4338c2ecf20Sopenharmony_ci		break;
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	default:
4368c2ecf20Sopenharmony_ci		goto err_mutex;
4378c2ecf20Sopenharmony_ci	}
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	if (thresh_val > 0xFF)
4408c2ecf20Sopenharmony_ci		goto err_mutex;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	ret = regmap_write(iqs62x->regmap, thresh_reg, thresh_val);
4438c2ecf20Sopenharmony_ci	if (ret)
4448c2ecf20Sopenharmony_ci		goto err_mutex;
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	*thresh_cache = thresh_val;
4478c2ecf20Sopenharmony_ci	iqs621_als->ir_flags_mask = ir_flags_mask;
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_cierr_mutex:
4508c2ecf20Sopenharmony_ci	mutex_unlock(&iqs621_als->lock);
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	return ret;
4538c2ecf20Sopenharmony_ci}
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_cistatic const struct iio_info iqs621_als_info = {
4568c2ecf20Sopenharmony_ci	.read_raw = &iqs621_als_read_raw,
4578c2ecf20Sopenharmony_ci	.read_event_config = iqs621_als_read_event_config,
4588c2ecf20Sopenharmony_ci	.write_event_config = iqs621_als_write_event_config,
4598c2ecf20Sopenharmony_ci	.read_event_value = iqs621_als_read_event_value,
4608c2ecf20Sopenharmony_ci	.write_event_value = iqs621_als_write_event_value,
4618c2ecf20Sopenharmony_ci};
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_cistatic const struct iio_event_spec iqs621_als_range_events[] = {
4648c2ecf20Sopenharmony_ci	{
4658c2ecf20Sopenharmony_ci		.type = IIO_EV_TYPE_CHANGE,
4668c2ecf20Sopenharmony_ci		.dir = IIO_EV_DIR_EITHER,
4678c2ecf20Sopenharmony_ci		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
4688c2ecf20Sopenharmony_ci	},
4698c2ecf20Sopenharmony_ci};
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_cistatic const struct iio_event_spec iqs621_als_light_events[] = {
4728c2ecf20Sopenharmony_ci	{
4738c2ecf20Sopenharmony_ci		.type = IIO_EV_TYPE_THRESH,
4748c2ecf20Sopenharmony_ci		.dir = IIO_EV_DIR_EITHER,
4758c2ecf20Sopenharmony_ci		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
4768c2ecf20Sopenharmony_ci	},
4778c2ecf20Sopenharmony_ci	{
4788c2ecf20Sopenharmony_ci		.type = IIO_EV_TYPE_THRESH,
4798c2ecf20Sopenharmony_ci		.dir = IIO_EV_DIR_RISING,
4808c2ecf20Sopenharmony_ci		.mask_separate = BIT(IIO_EV_INFO_VALUE),
4818c2ecf20Sopenharmony_ci	},
4828c2ecf20Sopenharmony_ci	{
4838c2ecf20Sopenharmony_ci		.type = IIO_EV_TYPE_THRESH,
4848c2ecf20Sopenharmony_ci		.dir = IIO_EV_DIR_FALLING,
4858c2ecf20Sopenharmony_ci		.mask_separate = BIT(IIO_EV_INFO_VALUE),
4868c2ecf20Sopenharmony_ci	},
4878c2ecf20Sopenharmony_ci};
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_cistatic const struct iio_chan_spec iqs621_als_channels[] = {
4908c2ecf20Sopenharmony_ci	{
4918c2ecf20Sopenharmony_ci		.type = IIO_INTENSITY,
4928c2ecf20Sopenharmony_ci		.address = IQS621_ALS_FLAGS,
4938c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
4948c2ecf20Sopenharmony_ci		.event_spec = iqs621_als_range_events,
4958c2ecf20Sopenharmony_ci		.num_event_specs = ARRAY_SIZE(iqs621_als_range_events),
4968c2ecf20Sopenharmony_ci	},
4978c2ecf20Sopenharmony_ci	{
4988c2ecf20Sopenharmony_ci		.type = IIO_LIGHT,
4998c2ecf20Sopenharmony_ci		.address = IQS621_ALS_UI_OUT,
5008c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
5018c2ecf20Sopenharmony_ci		.event_spec = iqs621_als_light_events,
5028c2ecf20Sopenharmony_ci		.num_event_specs = ARRAY_SIZE(iqs621_als_light_events),
5038c2ecf20Sopenharmony_ci	},
5048c2ecf20Sopenharmony_ci};
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic const struct iio_event_spec iqs622_als_prox_events[] = {
5078c2ecf20Sopenharmony_ci	{
5088c2ecf20Sopenharmony_ci		.type = IIO_EV_TYPE_THRESH,
5098c2ecf20Sopenharmony_ci		.dir = IIO_EV_DIR_EITHER,
5108c2ecf20Sopenharmony_ci		.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
5118c2ecf20Sopenharmony_ci				 BIT(IIO_EV_INFO_VALUE),
5128c2ecf20Sopenharmony_ci	},
5138c2ecf20Sopenharmony_ci};
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_cistatic const struct iio_chan_spec iqs622_als_channels[] = {
5168c2ecf20Sopenharmony_ci	{
5178c2ecf20Sopenharmony_ci		.type = IIO_INTENSITY,
5188c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_LIGHT_BOTH,
5198c2ecf20Sopenharmony_ci		.address = IQS622_ALS_FLAGS,
5208c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
5218c2ecf20Sopenharmony_ci		.event_spec = iqs621_als_range_events,
5228c2ecf20Sopenharmony_ci		.num_event_specs = ARRAY_SIZE(iqs621_als_range_events),
5238c2ecf20Sopenharmony_ci		.modified = true,
5248c2ecf20Sopenharmony_ci	},
5258c2ecf20Sopenharmony_ci	{
5268c2ecf20Sopenharmony_ci		.type = IIO_INTENSITY,
5278c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_LIGHT_IR,
5288c2ecf20Sopenharmony_ci		.address = IQS622_IR_RANGE,
5298c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
5308c2ecf20Sopenharmony_ci		.modified = true,
5318c2ecf20Sopenharmony_ci	},
5328c2ecf20Sopenharmony_ci	{
5338c2ecf20Sopenharmony_ci		.type = IIO_PROXIMITY,
5348c2ecf20Sopenharmony_ci		.address = IQS622_IR_UI_OUT,
5358c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
5368c2ecf20Sopenharmony_ci		.event_spec = iqs622_als_prox_events,
5378c2ecf20Sopenharmony_ci		.num_event_specs = ARRAY_SIZE(iqs622_als_prox_events),
5388c2ecf20Sopenharmony_ci	},
5398c2ecf20Sopenharmony_ci};
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_cistatic int iqs621_als_probe(struct platform_device *pdev)
5428c2ecf20Sopenharmony_ci{
5438c2ecf20Sopenharmony_ci	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
5448c2ecf20Sopenharmony_ci	struct iqs621_als_private *iqs621_als;
5458c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev;
5468c2ecf20Sopenharmony_ci	unsigned int val;
5478c2ecf20Sopenharmony_ci	int ret;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als));
5508c2ecf20Sopenharmony_ci	if (!indio_dev)
5518c2ecf20Sopenharmony_ci		return -ENOMEM;
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	iqs621_als = iio_priv(indio_dev);
5548c2ecf20Sopenharmony_ci	iqs621_als->iqs62x = iqs62x;
5558c2ecf20Sopenharmony_ci	iqs621_als->indio_dev = indio_dev;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci	if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) {
5588c2ecf20Sopenharmony_ci		ret = regmap_read(iqs62x->regmap, IQS622_IR_THRESH_TOUCH,
5598c2ecf20Sopenharmony_ci				  &val);
5608c2ecf20Sopenharmony_ci		if (ret)
5618c2ecf20Sopenharmony_ci			return ret;
5628c2ecf20Sopenharmony_ci		iqs621_als->thresh_prox = val;
5638c2ecf20Sopenharmony_ci		iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH;
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci		indio_dev->channels = iqs622_als_channels;
5668c2ecf20Sopenharmony_ci		indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels);
5678c2ecf20Sopenharmony_ci	} else {
5688c2ecf20Sopenharmony_ci		ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT,
5698c2ecf20Sopenharmony_ci				  &val);
5708c2ecf20Sopenharmony_ci		if (ret)
5718c2ecf20Sopenharmony_ci			return ret;
5728c2ecf20Sopenharmony_ci		iqs621_als->thresh_light = val;
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci		ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_DARK,
5758c2ecf20Sopenharmony_ci				  &val);
5768c2ecf20Sopenharmony_ci		if (ret)
5778c2ecf20Sopenharmony_ci			return ret;
5788c2ecf20Sopenharmony_ci		iqs621_als->thresh_dark = val;
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci		indio_dev->channels = iqs621_als_channels;
5818c2ecf20Sopenharmony_ci		indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels);
5828c2ecf20Sopenharmony_ci	}
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
5858c2ecf20Sopenharmony_ci	indio_dev->name = iqs62x->dev_desc->dev_name;
5868c2ecf20Sopenharmony_ci	indio_dev->info = &iqs621_als_info;
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci	mutex_init(&iqs621_als->lock);
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci	iqs621_als->notifier.notifier_call = iqs621_als_notifier;
5918c2ecf20Sopenharmony_ci	ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh,
5928c2ecf20Sopenharmony_ci					       &iqs621_als->notifier);
5938c2ecf20Sopenharmony_ci	if (ret) {
5948c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
5958c2ecf20Sopenharmony_ci		return ret;
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	ret = devm_add_action_or_reset(&pdev->dev,
5998c2ecf20Sopenharmony_ci				       iqs621_als_notifier_unregister,
6008c2ecf20Sopenharmony_ci				       iqs621_als);
6018c2ecf20Sopenharmony_ci	if (ret)
6028c2ecf20Sopenharmony_ci		return ret;
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_ci	return devm_iio_device_register(&pdev->dev, indio_dev);
6058c2ecf20Sopenharmony_ci}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_cistatic struct platform_driver iqs621_als_platform_driver = {
6088c2ecf20Sopenharmony_ci	.driver = {
6098c2ecf20Sopenharmony_ci		.name = "iqs621-als",
6108c2ecf20Sopenharmony_ci	},
6118c2ecf20Sopenharmony_ci	.probe = iqs621_als_probe,
6128c2ecf20Sopenharmony_ci};
6138c2ecf20Sopenharmony_cimodule_platform_driver(iqs621_als_platform_driver);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
6168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors");
6178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
6188c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:iqs621-als");
619