lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1334935826-12527-3-git-send-email-jhovold@gmail.com>
Date:	Fri, 20 Apr 2012 17:30:24 +0200
From:	Johan Hovold <jhovold@...il.com>
To:	Richard Purdie <rpurdie@...ys.net>,
	Samuel Ortiz <sameo@...ux.intel.com>,
	Arnd Bergmann <arnd@...db.de>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Florian Tobias Schandinat <FlorianSchandinat@....de>
Cc:	Andrew Morton <akpm@...ux-foundation.org>,
	linux-kernel@...r.kernel.org, linux-fbdev@...r.kernel.org,
	Johan Hovold <jhovold@...il.com>
Subject: [PATCH 2/4] misc: add LM3533 ambient light sensor driver

Add sub-driver for the ambient light sensor in National Semiconductor /
TI LM3533 lighting power chips.

Raw ADC values as well as current ALS zone can be retrieved through
sysfs. The ALS zone can also be read using a character device
(/dev/lm3533-als) which is updated on zone changes (interrupt driven or
polled).

The driver provides a configuration interface through sysfs.

Signed-off-by: Johan Hovold <jhovold@...il.com>
---
 drivers/misc/Kconfig      |   13 +
 drivers/misc/Makefile     |    1 +
 drivers/misc/lm3533-als.c |  662 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 676 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/lm3533-als.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c779509..cc8cbf0 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -314,6 +314,19 @@ config APDS9802ALS
 	  This driver can also be built as a module.  If so, the module
 	  will be called apds9802als.
 
+config ALS_LM3533
+	tristate "LM3533 Ambient Light Sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor on
+	  National Semiconductor / TI LM3533 Lighting Power chips.
+
+	  The sensor can be used to control the LEDs and backlights of the chip
+	  through defining five light zones and three sets of corresponding
+	  brightness target levels. The driver presents a character device
+	  (/dev/lm3533-als) which can be used to retrieve the current light
+	  zone or to poll for zone changes.
+
 config ISL29003
 	tristate "Intersil ISL29003 ambient light sensor"
 	depends on I2C && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3e1d8010..122a168 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_CS5535_MFGPT)	+= cs5535-mfgpt.o
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_APDS9802ALS)	+= apds9802als.o
+obj-$(CONFIG_ALS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_ISL29020)		+= isl29020.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
diff --git a/drivers/misc/lm3533-als.c b/drivers/misc/lm3533-als.c
new file mode 100644
index 0000000..0348c6d
--- /dev/null
+++ b/drivers/misc/lm3533-als.c
@@ -0,0 +1,662 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@...il.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/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+/* Default poll intervall for zone changes in polled mode in ms. */
+#define LM3533_ALS_POLL_INTERVAL		1000
+
+#define LM3533_ALS_RESISTOR_MAX			0x7f
+#define LM3533_ALS_BOUNDARY_MAX			0xff
+#define LM3533_ALS_TARGET_MAX			0xff
+#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_AVERAGE		0x37
+#define LM3533_REG_ALS_READ_ADC_RAW		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#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_ZONE_CHANGED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+	struct miscdevice cdev;
+
+	int irq;
+
+	atomic_t open_ref;
+
+	unsigned long poll_interval;
+	struct delayed_work dwork;
+
+	wait_queue_head_t read_wait;
+
+	struct mutex mutex;
+	unsigned long flags;
+	u8 zone;
+};
+
+#define to_lm3533_als(_cdev) \
+	container_of(_cdev, struct lm3533_als, cdev)
+
+
+static int lm3533_als_get_zone(struct lm3533_als *als, u8 *zone)
+{
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(als->cdev.this_device, "failed to read zone\n");
+		return ret;
+	}
+
+	*zone = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, *zone, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static void lm3533_als_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct lm3533_als *als = container_of(dwork, struct lm3533_als, dwork);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	if (als->zone != zone) {
+		als->zone = zone;
+		set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	}
+	mutex_unlock(&als->mutex);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		wake_up_interruptible(&als->read_wait);
+out:
+	if (als->irq < 0)
+		schedule_delayed_work(dwork, als->poll_interval);
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+	struct lm3533_als *als = dev_id;
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	als->zone = zone;
+	set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	mutex_unlock(&als->mutex);
+
+	wake_up_interruptible(&als->read_wait);
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct lm3533_als *als, int enable)
+{
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	return lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+}
+
+static int lm3533_als_int_enable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	ret = lm3533_als_set_int_mode(als, 1);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not enable int mode\n");
+		goto err;
+	}
+
+	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					als->cdev.name, als);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not get irq %d\n",
+								als->irq);
+		goto err;
+	}
+
+	return ret;
+err:
+	als->irq = -EINVAL;
+
+	return ret;
+}
+
+static int lm3533_als_int_disable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	free_irq(als->irq, als);
+
+	ret = lm3533_als_set_int_mode(als, 0);
+	if (ret) {
+		dev_warn(als->cdev.this_device,
+					"could not disable int mode\n");
+	}
+
+	return ret;
+}
+
+static int lm3533_als_open(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = to_lm3533_als(file->private_data);
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	file->private_data = als;
+
+	if (atomic_inc_return(&als->open_ref) != 1)
+		goto out;
+
+	/* Enable interrupt mode if requested, but fall back to polled mode on
+	 * errors.
+	 */
+	if (als->irq >= 0)
+		lm3533_als_int_enable(als);
+
+	/* Make sure first read returns current zone. */
+	mutex_lock(&als->mutex);
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	als->zone = (u8)-1;
+	mutex_unlock(&als->mutex);
+
+	schedule_delayed_work(&als->dwork, 0);
+out:
+	return nonseekable_open(inode, file);
+}
+
+static int lm3533_als_release(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = file->private_data;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (atomic_dec_return(&als->open_ref))
+		return 0;
+
+	if (als->irq >= 0)
+		lm3533_als_int_disable(als);
+
+	cancel_delayed_work_sync(&als->dwork);
+
+	return 0;
+}
+
+static unsigned int lm3533_als_poll(struct file *file,
+						struct poll_table_struct *pt)
+{
+	struct lm3533_als *als = file->private_data;
+	unsigned mask = 0;
+
+	poll_wait(file, &als->read_wait, pt);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static ssize_t lm3533_als_read(struct file *file, char __user *buf,
+						size_t count, loff_t *f_pos)
+{
+	struct lm3533_als *als = file->private_data;
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (!count)
+		return 0;
+
+	mutex_lock(&als->mutex);
+	while (!test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags)) {
+		mutex_unlock(&als->mutex);
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(als->read_wait,
+					test_bit(LM3533_ALS_FLAG_ZONE_CHANGED,
+								&als->flags));
+		if (ret)
+			return -ERESTARTSYS;
+
+		mutex_lock(&als->mutex);
+	}
+
+	count = min(count, sizeof(als->zone));
+	if (copy_to_user(buf, &als->zone, count)) {
+		ret = -EFAULT;
+		goto out;
+	}
+	*f_pos += count;
+	ret = count;
+
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+out:
+	mutex_unlock(&als->mutex);
+
+	return ret;
+}
+
+static const struct file_operations lm3533_als_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.open		= lm3533_als_open,
+	.release	= lm3533_als_release,
+	.poll		= lm3533_als_poll,
+	.read		= lm3533_als_read,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+#define to_lm3533_dev_attr(_dev_attr) \
+	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RO(_name, _reg) \
+	LM3533_REG_ATTR(_name, S_IRUGO, show_lm3533_als_reg, NULL, _reg, 0)
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_BOUNDARY_LOW_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_low, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_BOUNDARY_HIGH_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_high, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/* ALS Zone boundaries
+ *
+ * boundary[0-3]_low	0-255
+ * boundary[0-3]_high	0-255
+ */
+static ALS_BOUNDARY_LOW_ATTR_RW(0);
+static ALS_BOUNDARY_LOW_ATTR_RW(1);
+static ALS_BOUNDARY_LOW_ATTR_RW(2);
+static ALS_BOUNDARY_LOW_ATTR_RW(3);
+
+static ALS_BOUNDARY_HIGH_ATTR_RW(0);
+static ALS_BOUNDARY_HIGH_ATTR_RW(1);
+static ALS_BOUNDARY_HIGH_ATTR_RW(2);
+static ALS_BOUNDARY_HIGH_ATTR_RW(3);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/* ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+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);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+/* ALS ADC
+ *
+ * adc_average	0-255
+ * adc_raw	0-255
+ */
+static LM3533_REG_ATTR_RO(adc_average, LM3533_REG_ALS_READ_ADC_AVERAGE);
+static LM3533_REG_ATTR_RO(adc_raw, LM3533_REG_ALS_READ_ADC_RAW);
+
+/* ALS Gain resistor setting
+ *
+ * gain		0-31
+ */
+static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
+						LM3533_ALS_RESISTOR_MAX);
+/* ALS Current Zone
+ *
+ * zone		0-4
+ */
+static LM3533_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_adc_average.dev_attr.attr,
+	&lm3533_dev_attr_adc_raw.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_low.dev_attr.attr,
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&lm3533_dev_attr_gain.dev_attr.attr,
+	&dev_attr_zone.attr,
+	NULL,
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
+								int 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(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(lm3533->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	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;
+	}
+
+	als = kzalloc(sizeof(*als), GFP_KERNEL);
+	if (!als)
+		return -ENOMEM;
+
+	als->lm3533 = lm3533;
+	if (pdata->int_mode)
+		als->irq = lm3533->irq;
+	else
+		als->irq = -EINVAL;
+
+	if (!pdata->poll_interval)
+		pdata->poll_interval = LM3533_ALS_POLL_INTERVAL;
+	als->poll_interval = msecs_to_jiffies(pdata->poll_interval);
+
+	als->cdev.name = "lm3533-als";
+	als->cdev.parent = pdev->dev.parent;
+	als->cdev.minor = MISC_DYNAMIC_MINOR;
+	als->cdev.fops = &lm3533_als_fops;
+
+	mutex_init(&als->mutex);
+	INIT_DELAYED_WORK(&als->dwork, lm3533_als_work);
+	init_waitqueue_head(&als->read_wait);
+
+	platform_set_drvdata(pdev, als);
+
+	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
+	if (ret)
+		goto err;
+
+	ret = lm3533_als_enable(lm3533);
+	if (ret)
+		goto err;
+
+	ret = misc_register(&als->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	ret = sysfs_create_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	misc_deregister(&als->cdev);
+err_disable:
+	lm3533_als_disable(lm3533);
+err:
+	kfree(als);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct lm3533_als *als = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	sysfs_remove_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	misc_deregister(&als->cdev);
+	lm3533_als_disable(als->lm3533);
+	kfree(als);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver = {
+		.name = "lm3533-als",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@...il.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ