diff options
Diffstat (limited to 'drivers/iio/light/lm3533-als.c')
| -rw-r--r-- | drivers/iio/light/lm3533-als.c | 928 | 
1 files changed, 928 insertions, 0 deletions
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c new file mode 100644 index 00000000000..c1aadc6b865 --- /dev/null +++ b/drivers/iio/light/lm3533-als.c @@ -0,0 +1,928 @@ +/* + * lm3533-als.c -- LM3533 Ambient Light Sensor driver + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under  the terms of the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/atomic.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_ALS_RESISTOR_MIN			1 +#define LM3533_ALS_RESISTOR_MAX			127 +#define LM3533_ALS_CHANNEL_CURRENT_MAX		2 +#define LM3533_ALS_THRESH_MAX			3 +#define LM3533_ALS_ZONE_MAX			4 + +#define LM3533_REG_ALS_RESISTOR_SELECT		0x30 +#define LM3533_REG_ALS_CONF			0x31 +#define LM3533_REG_ALS_ZONE_INFO		0x34 +#define LM3533_REG_ALS_READ_ADC_RAW		0x37 +#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38 +#define LM3533_REG_ALS_BOUNDARY_BASE		0x50 +#define LM3533_REG_ALS_TARGET_BASE		0x60 + +#define LM3533_ALS_ENABLE_MASK			0x01 +#define LM3533_ALS_INPUT_MODE_MASK		0x02 +#define LM3533_ALS_INT_ENABLE_MASK		0x01 + +#define LM3533_ALS_ZONE_SHIFT			2 +#define LM3533_ALS_ZONE_MASK			0x1c + +#define LM3533_ALS_FLAG_INT_ENABLED		1 + + +struct lm3533_als { +	struct lm3533 *lm3533; +	struct platform_device *pdev; + +	unsigned long flags; +	int irq; + +	atomic_t zone; +	struct mutex thresh_mutex; +}; + + +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average, +								int *adc) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 reg; +	u8 val; +	int ret; + +	if (average) +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE; +	else +		reg = LM3533_REG_ALS_READ_ADC_RAW; + +	ret = lm3533_read(als->lm3533, reg, &val); +	if (ret) { +		dev_err(&indio_dev->dev, "failed to read adc\n"); +		return ret; +	} + +	*adc = val; + +	return 0; +} + +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 val; +	int ret; + +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); +	if (ret) { +		dev_err(&indio_dev->dev, "failed to read zone\n"); +		return ret; +	} + +	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT; +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX); + +	return 0; +} + +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	int ret; + +	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) { +		*zone = atomic_read(&als->zone); +	} else { +		ret = _lm3533_als_get_zone(indio_dev, zone); +		if (ret) +			return ret; +	} + +	return 0; +} + +/* + * channel	output channel 0..2 + * zone		zone 0..4 + */ +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone) +{ +	return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone; +} + +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel, +							unsigned zone, u8 *val) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 reg; +	int ret; + +	if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) +		return -EINVAL; + +	if (zone > LM3533_ALS_ZONE_MAX) +		return -EINVAL; + +	reg = lm3533_als_get_target_reg(channel, zone); +	ret = lm3533_read(als->lm3533, reg, val); +	if (ret) +		dev_err(&indio_dev->dev, "failed to get target current\n"); + +	return ret; +} + +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel, +							unsigned zone, u8 val) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 reg; +	int ret; + +	if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) +		return -EINVAL; + +	if (zone > LM3533_ALS_ZONE_MAX) +		return -EINVAL; + +	reg = lm3533_als_get_target_reg(channel, zone); +	ret = lm3533_write(als->lm3533, reg, val); +	if (ret) +		dev_err(&indio_dev->dev, "failed to set target current\n"); + +	return ret; +} + +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel, +								int *val) +{ +	u8 zone; +	u8 target; +	int ret; + +	ret = lm3533_als_get_zone(indio_dev, &zone); +	if (ret) +		return ret; + +	ret = lm3533_als_get_target(indio_dev, channel, zone, &target); +	if (ret) +		return ret; + +	*val = target; + +	return 0; +} + +static int lm3533_als_read_raw(struct iio_dev *indio_dev, +				struct iio_chan_spec const *chan, +				int *val, int *val2, long mask) +{ +	int ret; + +	switch (mask) { +	case 0: +		switch (chan->type) { +		case IIO_LIGHT: +			ret = lm3533_als_get_adc(indio_dev, false, val); +			break; +		case IIO_CURRENT: +			ret = lm3533_als_get_current(indio_dev, chan->channel, +									val); +			break; +		default: +			return -EINVAL; +		} +		break; +	case IIO_CHAN_INFO_AVERAGE_RAW: +		ret = lm3533_als_get_adc(indio_dev, true, val); +		break; +	default: +		return -EINVAL; +	} + +	if (ret) +		return ret; + +	return IIO_VAL_INT; +} + +#define CHANNEL_CURRENT(_channel)					\ +	{								\ +		.type		= IIO_CURRENT,				\ +		.channel	= _channel,				\ +		.indexed	= true,					\ +		.output		= true,					\ +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\ +	} + +static const struct iio_chan_spec lm3533_als_channels[] = { +	{ +		.type		= IIO_LIGHT, +		.channel	= 0, +		.indexed	= true, +		.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | +				   BIT(IIO_CHAN_INFO_RAW), +	}, +	CHANNEL_CURRENT(0), +	CHANNEL_CURRENT(1), +	CHANNEL_CURRENT(2), +}; + +static irqreturn_t lm3533_als_isr(int irq, void *dev_id) +{ + +	struct iio_dev *indio_dev = dev_id; +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 zone; +	int ret; + +	/* Clear interrupt by reading the ALS zone register. */ +	ret = _lm3533_als_get_zone(indio_dev, &zone); +	if (ret) +		goto out; + +	atomic_set(&als->zone, zone); + +	iio_push_event(indio_dev, +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT, +					    0, +					    IIO_EV_TYPE_THRESH, +					    IIO_EV_DIR_EITHER), +		       iio_get_time_ns()); +out: +	return IRQ_HANDLED; +} + +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 mask = LM3533_ALS_INT_ENABLE_MASK; +	u8 val; +	int ret; + +	if (enable) +		val = mask; +	else +		val = 0; + +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask); +	if (ret) { +		dev_err(&indio_dev->dev, "failed to set int mode %d\n", +								enable); +		return ret; +	} + +	return 0; +} + +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 mask = LM3533_ALS_INT_ENABLE_MASK; +	u8 val; +	int ret; + +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); +	if (ret) { +		dev_err(&indio_dev->dev, "failed to get int mode\n"); +		return ret; +	} + +	*enable = !!(val & mask); + +	return 0; +} + +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising) +{ +	u8 offset = !raising; + +	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset; +} + +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr, +							bool raising, u8 *val) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 reg; +	int ret; + +	if (nr > LM3533_ALS_THRESH_MAX) +		return -EINVAL; + +	reg = lm3533_als_get_threshold_reg(nr, raising); +	ret = lm3533_read(als->lm3533, reg, val); +	if (ret) +		dev_err(&indio_dev->dev, "failed to get threshold\n"); + +	return ret; +} + +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr, +							bool raising, u8 val) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 val2; +	u8 reg, reg2; +	int ret; + +	if (nr > LM3533_ALS_THRESH_MAX) +		return -EINVAL; + +	reg = lm3533_als_get_threshold_reg(nr, raising); +	reg2 = lm3533_als_get_threshold_reg(nr, !raising); + +	mutex_lock(&als->thresh_mutex); +	ret = lm3533_read(als->lm3533, reg2, &val2); +	if (ret) { +		dev_err(&indio_dev->dev, "failed to get threshold\n"); +		goto out; +	} +	/* +	 * This device does not allow negative hysteresis (in fact, it uses +	 * whichever value is smaller as the lower bound) so we need to make +	 * sure that thresh_falling <= thresh_raising. +	 */ +	if ((raising && (val < val2)) || (!raising && (val > val2))) { +		ret = -EINVAL; +		goto out; +	} + +	ret = lm3533_write(als->lm3533, reg, val); +	if (ret) { +		dev_err(&indio_dev->dev, "failed to set threshold\n"); +		goto out; +	} +out: +	mutex_unlock(&als->thresh_mutex); + +	return ret; +} + +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr, +								u8 *val) +{ +	struct lm3533_als *als = iio_priv(indio_dev); +	u8 falling; +	u8 raising; +	int ret; + +	if (nr > LM3533_ALS_THRESH_MAX) +		return -EINVAL; + +	mutex_lock(&als->thresh_mutex); +	ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling); +	if (ret) +		goto out; +	ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising); +	if (ret) +		goto out; + +	*val = raising - falling; +out: +	mutex_unlock(&als->thresh_mutex); + +	return ret; +} + +static ssize_t show_thresh_either_en(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct lm3533_als *als = iio_priv(indio_dev); +	int enable; +	int ret; + +	if (als->irq) { +		ret = lm3533_als_get_int_mode(indio_dev, &enable); +		if (ret) +			return ret; +	} else { +		enable = 0; +	} + +	return scnprintf(buf, PAGE_SIZE, "%u\n", enable); +} + +static ssize_t store_thresh_either_en(struct device *dev, +					struct device_attribute *attr, +					const char *buf, size_t len) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct lm3533_als *als = iio_priv(indio_dev); +	unsigned long enable; +	bool int_enabled; +	u8 zone; +	int ret; + +	if (!als->irq) +		return -EBUSY; + +	if (kstrtoul(buf, 0, &enable)) +		return -EINVAL; + +	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + +	if (enable && !int_enabled) { +		ret = lm3533_als_get_zone(indio_dev, &zone); +		if (ret) +			return ret; + +		atomic_set(&als->zone, zone); + +		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); +	} + +	ret = lm3533_als_set_int_mode(indio_dev, enable); +	if (ret) { +		if (!int_enabled) +			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + +		return ret; +	} + +	if (!enable) +		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + +	return len; +} + +static ssize_t show_zone(struct device *dev, +				struct device_attribute *attr, char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	u8 zone; +	int ret; + +	ret = lm3533_als_get_zone(indio_dev, &zone); +	if (ret) +		return ret; + +	return scnprintf(buf, PAGE_SIZE, "%u\n", zone); +} + +enum lm3533_als_attribute_type { +	LM3533_ATTR_TYPE_HYSTERESIS, +	LM3533_ATTR_TYPE_TARGET, +	LM3533_ATTR_TYPE_THRESH_FALLING, +	LM3533_ATTR_TYPE_THRESH_RAISING, +}; + +struct lm3533_als_attribute { +	struct device_attribute dev_attr; +	enum lm3533_als_attribute_type type; +	u8 val1; +	u8 val2; +}; + +static inline struct lm3533_als_attribute * +to_lm3533_als_attr(struct device_attribute *attr) +{ +	return container_of(attr, struct lm3533_als_attribute, dev_attr); +} + +static ssize_t show_als_attr(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); +	u8 val; +	int ret; + +	switch (als_attr->type) { +	case LM3533_ATTR_TYPE_HYSTERESIS: +		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1, +									&val); +		break; +	case LM3533_ATTR_TYPE_TARGET: +		ret = lm3533_als_get_target(indio_dev, als_attr->val1, +							als_attr->val2, &val); +		break; +	case LM3533_ATTR_TYPE_THRESH_FALLING: +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, +								false, &val); +		break; +	case LM3533_ATTR_TYPE_THRESH_RAISING: +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, +								true, &val); +		break; +	default: +		ret = -ENXIO; +	} + +	if (ret) +		return ret; + +	return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_als_attr(struct device *dev, +					struct device_attribute *attr, +					const char *buf, size_t len) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); +	u8 val; +	int ret; + +	if (kstrtou8(buf, 0, &val)) +		return -EINVAL; + +	switch (als_attr->type) { +	case LM3533_ATTR_TYPE_TARGET: +		ret = lm3533_als_set_target(indio_dev, als_attr->val1, +							als_attr->val2, val); +		break; +	case LM3533_ATTR_TYPE_THRESH_FALLING: +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, +								false, val); +		break; +	case LM3533_ATTR_TYPE_THRESH_RAISING: +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, +								true, val); +		break; +	default: +		ret = -ENXIO; +	} + +	if (ret) +		return ret; + +	return len; +} + +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\ +	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\ +	  .type		= _type,					\ +	  .val1		= _val1,					\ +	  .val2		= _val2 } + +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ +	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \ +		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) + +#define ALS_TARGET_ATTR_RW(_channel, _zone)				\ +	LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw,	\ +				S_IRUGO | S_IWUSR,			\ +				show_als_attr, store_als_attr,		\ +				LM3533_ATTR_TYPE_TARGET, _channel, _zone) +/* + * ALS output current values (ALS mapper targets) + * + * out_current[0-2]_current[0-4]_raw		0-255 + */ +static ALS_TARGET_ATTR_RW(0, 0); +static ALS_TARGET_ATTR_RW(0, 1); +static ALS_TARGET_ATTR_RW(0, 2); +static ALS_TARGET_ATTR_RW(0, 3); +static ALS_TARGET_ATTR_RW(0, 4); + +static ALS_TARGET_ATTR_RW(1, 0); +static ALS_TARGET_ATTR_RW(1, 1); +static ALS_TARGET_ATTR_RW(1, 2); +static ALS_TARGET_ATTR_RW(1, 3); +static ALS_TARGET_ATTR_RW(1, 4); + +static ALS_TARGET_ATTR_RW(2, 0); +static ALS_TARGET_ATTR_RW(2, 1); +static ALS_TARGET_ATTR_RW(2, 2); +static ALS_TARGET_ATTR_RW(2, 3); +static ALS_TARGET_ATTR_RW(2, 4); + +#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\ +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\ +			S_IRUGO | S_IWUSR,				\ +			show_als_attr, store_als_attr,		\ +			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0) + +#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\ +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\ +			S_IRUGO | S_IWUSR,				\ +			show_als_attr, store_als_attr,			\ +			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0) +/* + * ALS Zone thresholds (boundaries) + * + * in_illuminance0_thresh[0-3]_falling_value	0-255 + * in_illuminance0_thresh[0-3]_raising_value	0-255 + */ +static ALS_THRESH_FALLING_ATTR_RW(0); +static ALS_THRESH_FALLING_ATTR_RW(1); +static ALS_THRESH_FALLING_ATTR_RW(2); +static ALS_THRESH_FALLING_ATTR_RW(3); + +static ALS_THRESH_RAISING_ATTR_RW(0); +static ALS_THRESH_RAISING_ATTR_RW(1); +static ALS_THRESH_RAISING_ATTR_RW(2); +static ALS_THRESH_RAISING_ATTR_RW(3); + +#define ALS_HYSTERESIS_ATTR_RO(_nr)					\ +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\ +			S_IRUGO, show_als_attr, NULL,			\ +			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0) +/* + * ALS Zone threshold hysteresis + * + * threshY_hysteresis = threshY_raising - threshY_falling + * + * in_illuminance0_thresh[0-3]_hysteresis	0-255 + * in_illuminance0_thresh[0-3]_hysteresis	0-255 + */ +static ALS_HYSTERESIS_ATTR_RO(0); +static ALS_HYSTERESIS_ATTR_RO(1); +static ALS_HYSTERESIS_ATTR_RO(2); +static ALS_HYSTERESIS_ATTR_RO(3); + +#define ILLUMINANCE_ATTR_RO(_name) \ +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL) +#define ILLUMINANCE_ATTR_RW(_name) \ +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \ +						show_##_name, store_##_name) +/* + * ALS Zone threshold-event enable + * + * in_illuminance0_thresh_either_en		0,1 + */ +static ILLUMINANCE_ATTR_RW(thresh_either_en); + +/* + * ALS Current Zone + * + * in_illuminance0_zone		0-4 + */ +static ILLUMINANCE_ATTR_RO(zone); + +static struct attribute *lm3533_als_event_attributes[] = { +	&dev_attr_in_illuminance0_thresh_either_en.attr, +	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr, +	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr, +	NULL +}; + +static struct attribute_group lm3533_als_event_attribute_group = { +	.attrs = lm3533_als_event_attributes +}; + +static struct attribute *lm3533_als_attributes[] = { +	&dev_attr_in_illuminance0_zone.attr, +	&lm3533_als_attr_out_current0_current0_raw.dev_attr.attr, +	&lm3533_als_attr_out_current0_current1_raw.dev_attr.attr, +	&lm3533_als_attr_out_current0_current2_raw.dev_attr.attr, +	&lm3533_als_attr_out_current0_current3_raw.dev_attr.attr, +	&lm3533_als_attr_out_current0_current4_raw.dev_attr.attr, +	&lm3533_als_attr_out_current1_current0_raw.dev_attr.attr, +	&lm3533_als_attr_out_current1_current1_raw.dev_attr.attr, +	&lm3533_als_attr_out_current1_current2_raw.dev_attr.attr, +	&lm3533_als_attr_out_current1_current3_raw.dev_attr.attr, +	&lm3533_als_attr_out_current1_current4_raw.dev_attr.attr, +	&lm3533_als_attr_out_current2_current0_raw.dev_attr.attr, +	&lm3533_als_attr_out_current2_current1_raw.dev_attr.attr, +	&lm3533_als_attr_out_current2_current2_raw.dev_attr.attr, +	&lm3533_als_attr_out_current2_current3_raw.dev_attr.attr, +	&lm3533_als_attr_out_current2_current4_raw.dev_attr.attr, +	NULL +}; + +static struct attribute_group lm3533_als_attribute_group = { +	.attrs = lm3533_als_attributes +}; + +static int lm3533_als_set_input_mode(struct lm3533_als *als, bool pwm_mode) +{ +	u8 mask = LM3533_ALS_INPUT_MODE_MASK; +	u8 val; +	int ret; + +	if (pwm_mode) +		val = mask;	/* pwm input */ +	else +		val = 0;	/* analog input */ + +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask); +	if (ret) { +		dev_err(&als->pdev->dev, "failed to set input mode %d\n", +								pwm_mode); +		return ret; +	} + +	return 0; +} + +static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val) +{ +	int ret; + +	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) +		return -EINVAL; + +	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val); +	if (ret) { +		dev_err(&als->pdev->dev, "failed to set resistor\n"); +		return ret; +	} + +	return 0; +} + +static int lm3533_als_setup(struct lm3533_als *als, +			    struct lm3533_als_platform_data *pdata) +{ +	int ret; + +	ret = lm3533_als_set_input_mode(als, pdata->pwm_mode); +	if (ret) +		return ret; + +	/* ALS input is always high impedance in PWM-mode. */ +	if (!pdata->pwm_mode) { +		ret = lm3533_als_set_resistor(als, pdata->r_select); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int lm3533_als_setup_irq(struct lm3533_als *als, void *dev) +{ +	u8 mask = LM3533_ALS_INT_ENABLE_MASK; +	int ret; + +	/* Make sure interrupts are disabled. */ +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask); +	if (ret) { +		dev_err(&als->pdev->dev, "failed to disable interrupts\n"); +		return ret; +	} + +	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr, +					IRQF_TRIGGER_LOW | IRQF_ONESHOT, +					dev_name(&als->pdev->dev), dev); +	if (ret) { +		dev_err(&als->pdev->dev, "failed to request irq %d\n", +								als->irq); +		return ret; +	} + +	return 0; +} + +static int lm3533_als_enable(struct lm3533_als *als) +{ +	u8 mask = LM3533_ALS_ENABLE_MASK; +	int ret; + +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask); +	if (ret) +		dev_err(&als->pdev->dev, "failed to enable ALS\n"); + +	return ret; +} + +static int lm3533_als_disable(struct lm3533_als *als) +{ +	u8 mask = LM3533_ALS_ENABLE_MASK; +	int ret; + +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask); +	if (ret) +		dev_err(&als->pdev->dev, "failed to disable ALS\n"); + +	return ret; +} + +static const struct iio_info lm3533_als_info = { +	.attrs		= &lm3533_als_attribute_group, +	.event_attrs	= &lm3533_als_event_attribute_group, +	.driver_module	= THIS_MODULE, +	.read_raw	= &lm3533_als_read_raw, +}; + +static int lm3533_als_probe(struct platform_device *pdev) +{ +	struct lm3533 *lm3533; +	struct lm3533_als_platform_data *pdata; +	struct lm3533_als *als; +	struct iio_dev *indio_dev; +	int ret; + +	lm3533 = dev_get_drvdata(pdev->dev.parent); +	if (!lm3533) +		return -EINVAL; + +	pdata = pdev->dev.platform_data; +	if (!pdata) { +		dev_err(&pdev->dev, "no platform data\n"); +		return -EINVAL; +	} + +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als)); +	if (!indio_dev) +		return -ENOMEM; + +	indio_dev->info = &lm3533_als_info; +	indio_dev->channels = lm3533_als_channels; +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels); +	indio_dev->name = dev_name(&pdev->dev); +	indio_dev->dev.parent = pdev->dev.parent; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	als = iio_priv(indio_dev); +	als->lm3533 = lm3533; +	als->pdev = pdev; +	als->irq = lm3533->irq; +	atomic_set(&als->zone, 0); +	mutex_init(&als->thresh_mutex); + +	platform_set_drvdata(pdev, indio_dev); + +	if (als->irq) { +		ret = lm3533_als_setup_irq(als, indio_dev); +		if (ret) +			return ret; +	} + +	ret = lm3533_als_setup(als, pdata); +	if (ret) +		goto err_free_irq; + +	ret = lm3533_als_enable(als); +	if (ret) +		goto err_free_irq; + +	ret = iio_device_register(indio_dev); +	if (ret) { +		dev_err(&pdev->dev, "failed to register ALS\n"); +		goto err_disable; +	} + +	return 0; + +err_disable: +	lm3533_als_disable(als); +err_free_irq: +	if (als->irq) +		free_irq(als->irq, indio_dev); + +	return ret; +} + +static int lm3533_als_remove(struct platform_device *pdev) +{ +	struct iio_dev *indio_dev = platform_get_drvdata(pdev); +	struct lm3533_als *als = iio_priv(indio_dev); + +	lm3533_als_set_int_mode(indio_dev, false); +	iio_device_unregister(indio_dev); +	lm3533_als_disable(als); +	if (als->irq) +		free_irq(als->irq, indio_dev); + +	return 0; +} + +static struct platform_driver lm3533_als_driver = { +	.driver	= { +		.name	= "lm3533-als", +		.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");  | 
