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: <1271327648-13497-2-git-send-email-samu.p.onkalo@nokia.com>
Date:	Thu, 15 Apr 2010 13:34:08 +0300
From:	Samu Onkalo <samu.p.onkalo@...ia.com>
To:	linux-i2c@...r.kernel.org, linux-kernel@...r.kernel.org,
	lm-sensors@...sensors.org
Cc:	Samu Onkalo <samu.p.onkalo@...ia.com>
Subject: [PATCH 1/1] misc: bh1770glc: Driver for bh1770glc combined als and ps sensor

Driver for combined ambient light and proximity sensor.
BH1770GLC and SW compatible SFH7770 sensors are supported.

Signed-off-by: Samu Onkalo <samu.p.onkalo@...ia.com>
---
 drivers/misc/Kconfig          |   12 +
 drivers/misc/Makefile         |    3 +
 drivers/misc/bh1770glc.h      |  169 ++++++++++++
 drivers/misc/bh1770glc_als.c  |  424 +++++++++++++++++++++++++++++
 drivers/misc/bh1770glc_core.c |  301 +++++++++++++++++++++
 drivers/misc/bh1770glc_ps.c   |  585 +++++++++++++++++++++++++++++++++++++++++
 include/linux/bh1770glc.h     |   39 +++
 7 files changed, 1533 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/bh1770glc.h
 create mode 100644 drivers/misc/bh1770glc_als.c
 create mode 100644 drivers/misc/bh1770glc_core.c
 create mode 100644 drivers/misc/bh1770glc_ps.c
 create mode 100644 include/linux/bh1770glc.h

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 2191c8d..4594703 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -311,6 +311,18 @@ config TI_DAC7512
 	  This driver can also be built as a module. If so, the module
 	  will be calles ti_dac7512.
 
+config BH1770GLC
+	 tristate "Rohm BH1770GLC ambient light and proximity sensor"
+	 depends on I2C
+	 default n
+	 ---help---
+	   Say Y here if you want to build a driver for BH1770GLC
+	   combined ambient light and proximity sensor chip.
+	   Driver supports also Osram SFH7770 version of the chip.
+
+	   To compile this driver as a module, choose M here: the
+	   module will be called bh1770glc. If unsure, say N here.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 27c4843..5201667 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -2,6 +2,8 @@
 # Makefile for misc devices that really don't fit anywhere else.
 #
 
+bh1770glc-objs  := bh1770glc_core.o bh1770glc_als.o bh1770glc_ps.o
+
 obj-$(CONFIG_IBM_ASM)		+= ibmasm/
 obj-$(CONFIG_HDPU_FEATURES)	+= hdpuftrs/
 obj-$(CONFIG_AD525X_DPOT)	+= ad525x_dpot.o
@@ -25,6 +27,7 @@ obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
 obj-$(CONFIG_EP93XX_PWM)	+= ep93xx_pwm.o
 obj-$(CONFIG_DS1682)		+= ds1682.o
 obj-$(CONFIG_TI_DAC7512)	+= ti_dac7512.o
+obj-$(CONFIG_BH1770GLC)		+= bh1770glc.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-$(CONFIG_IWMC3200TOP)      += iwmc3200top/
 obj-y				+= eeprom/
diff --git a/drivers/misc/bh1770glc.h b/drivers/misc/bh1770glc.h
new file mode 100644
index 0000000..096777e
--- /dev/null
+++ b/drivers/misc/bh1770glc.h
@@ -0,0 +1,169 @@
+/*
+ * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver.
+ * Chip is combined proximity and ambient light sensor.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@...ia.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
+ *
+ */
+
+#ifndef __BH1770GLC_HEADER__
+#define __BH1770GLC_HEADER__
+
+#define BHCHIP_ALS_CONTROL	0x80 /* ALS operation mode control */
+#define BHCHIP_PS_CONTROL	0x81 /* PS operation mode control */
+#define BHCHIP_I_LED		0x82 /* active LED and LED1, LED2 current */
+#define BHCHIP_I_LED3		0x83 /* LED3 current setting */
+#define BHCHIP_ALS_PS_MEAS	0x84 /* Forced mode trigger */
+#define BHCHIP_PS_MEAS_RATE	0x85 /* PS meas. rate at stand alone mode */
+#define BHCHIP_ALS_MEAS_RATE	0x86 /* ALS meas. rate at stand alone mode */
+#define BHCHIP_PART_ID		0x8a /* Part number and revision ID */
+#define BHCHIP_MANUFACT_ID	0x8b /* Manufacturerer ID */
+#define BHCHIP_ALS_DATA_0	0x8c /* ALS DATA low byte */
+#define BHCHIP_ALS_DATA_1	0x8d /* ALS DATA high byte */
+#define BHCHIP_ALS_PS_STATUS	0x8e /* Measurement data and int status */
+#define BHCHIP_PS_DATA_LED1	0x8f /* PS data from LED1 */
+#define BHCHIP_PS_DATA_LED2	0x90 /* PS data from LED2 */
+#define BHCHIP_PS_DATA_LED3	0x91 /* PS data from LED3 */
+#define BHCHIP_INTERRUPT	0x92 /* Interrupt setting */
+#define BHCHIP_PS_TH_LED1	0x93 /* PS interrupt threshold for LED1 */
+#define BHCHIP_PS_TH_LED2	0x94 /* PS interrupt threshold for LED2 */
+#define BHCHIP_PS_TH_LED3	0x95 /* PS interrupt threshold for LED3 */
+#define BHCHIP_ALS_TH_UP_0	0x96 /* ALS upper threshold low byte */
+#define BHCHIP_ALS_TH_UP_1	0x97 /* ALS upper threshold high byte */
+#define BHCHIP_ALS_TH_LOW_0	0x98 /* ALS lower threshold low byte */
+#define BHCHIP_ALS_TH_LOW_1	0x99 /* ALS lower threshold high byte */
+
+/* MANUFACT_ID */
+#define BHCHIP_MANUFACT_ROHM	0x01
+#define BHCHIP_MANUFACT_OSRAM	0x03
+
+/* PART_ID */
+#define BHCHIP_PART		0x90
+#define BHCHIP_PART_MASK	0xf0
+#define BHCHIP_REV_MASK		0x0f
+#define BHCHIP_REV_SHIFT	0
+#define BHCHIP_REV_0		0x00
+
+/* Operating modes for both */
+#define BHCHIP_STANDBY		0x00
+#define BHCHIP_FORCED		0x02
+#define BHCHIP_STANDALONE	0x03
+
+#define BHCHIP_PS_TRIG_MEAS	(1 << 0)
+#define BHCHIP_ALS_TRIG_MEAS	(1 << 1)
+
+/* Interrupt control */
+#define BHCHIP_INT_OUTPUT_MODE	(1 << 3) /* 0 = latched */
+#define BHCHIP_INT_POLARITY	(1 << 2) /* 1 = active high */
+#define BHCHIP_INT_ALS_ENA	(1 << 1)
+#define BHCHIP_INT_PS_ENA	(1 << 0)
+
+/* Interrupt status */
+#define BHCHIP_INT_LED1_DATA	(1 << 0)
+#define BHCHIP_INT_LED1_INT	(1 << 1)
+#define BHCHIP_INT_LED2_DATA	(1 << 2)
+#define BHCHIP_INT_LED2_INT	(1 << 3)
+#define BHCHIP_INT_LED3_DATA	(1 << 4)
+#define BHCHIP_INT_LED3_INT	(1 << 5)
+#define BHCHIP_INT_LEDS_INT	((1 << 1) | (1 << 3) | (1 << 5))
+#define BHCHIP_INT_ALS_DATA	(1 << 6)
+#define BHCHIP_INT_ALS_INT	(1 << 7)
+
+#define BHCHIP_DISABLE		0
+#define BHCHIP_ENABLE		1
+
+ /* Following are milliseconds */
+#define BHCHIP_ALS_DEFAULT_RATE	200
+#define BHCHIP_PS_DEFAULT_RATE	40
+#define BHCHIP_PS_DEF_RATE_THRESH 200
+#define BHCHIP_PS_INIT_DELAY    15
+#define BHCHIP_STARTUP_DELAY	50
+
+#define BHCHIP_ALS_RANGE	65535
+#define BHCHIP_PS_RANGE		255
+#define BHCHIP_CALIB_SCALER	1000
+#define BHCHIP_ALS_NEUTRAL_CALIB_VALUE (1 * BHCHIP_CALIB_SCALER)
+#define BHCHIP_PS_NEUTRAL_CALIB_VALUE  (1 * BHCHIP_CALIB_SCALER)
+#define BHCHIP_ALS_DEF_SENS	10
+#define BHCHIP_ALS_DEF_THRES	1000
+#define BHCHIP_PS_DEF_THRES	20
+
+#define ALS_NBR_FORMAT		512
+/* coef as decimal = ALS_COEF / *(ALS_NBR_FORMAT ^ 2) */
+#define ALS_COEF		1536
+/* Scaler coefficient at zero level */
+#define ALS_ZERO_LEVEL		(ALS_NBR_FORMAT / 4)
+
+#define PS_ABOVE_THRESHOLD	1
+#define PS_BELOW_THRESHOLD	0
+
+#define BHCHIP_PS_CHANNELS 3
+
+struct bh1770glc_chip {
+	struct i2c_client		*client;
+	struct bh1770glc_platform_data	*pdata;
+	struct mutex			mutex; /* avoid parallel access */
+	struct regulator_bulk_data	regs[2];
+
+	bool				int_mode_ps;
+	bool				int_mode_als;
+
+	wait_queue_head_t		als_misc_wait; /* WQ for ALS part */
+	wait_queue_head_t		ps_misc_wait; /* WQ for PS part */
+	struct delayed_work		ps_work; /* For ps low threshold */
+
+	char				chipname[10];
+	u8				revision;
+
+	u32	als_calib;
+	int	als_rate;
+	int	als_mode;
+	int	als_users;
+	u16	als_data;
+	u16	als_threshold_hi;
+	u16	als_threshold_lo;
+	u16	als_sens;
+	loff_t	als_offset;
+
+	loff_t	ps_offset;
+	u32	ps_calib[BHCHIP_PS_CHANNELS];
+	int	ps_rate;
+	int	ps_rate_threshold;
+	int	ps_mode;
+	int	ps_users;
+	u8	ps_data[BHCHIP_PS_CHANNELS];
+	u8	ps_thresholds[BHCHIP_PS_CHANNELS];
+	u8	ps_leds[BHCHIP_PS_CHANNELS];
+	u8	ps_channels; /* nbr of leds */
+};
+
+extern struct bh1770glc_chip *bh1770glc;
+
+extern int bh1770glc_ps_mode(struct bh1770glc_chip *chip, int mode);
+extern int bh1770glc_ps_init(struct bh1770glc_chip *chip);
+extern int bh1770glc_ps_destroy(struct bh1770glc_chip *chip);
+extern void bh1770glc_ps_interrupt_handler(struct bh1770glc_chip *chip,
+					int status);
+
+extern int bh1770glc_als_mode(struct bh1770glc_chip *chip, int mode);
+extern int bh1770glc_als_init(struct bh1770glc_chip *chip);
+extern int bh1770glc_als_destroy(struct bh1770glc_chip *chip);
+extern void bh1770glc_als_interrupt_handler(struct bh1770glc_chip *chip,
+					int status);
+#endif
diff --git a/drivers/misc/bh1770glc_als.c b/drivers/misc/bh1770glc_als.c
new file mode 100644
index 0000000..81f41c6
--- /dev/null
+++ b/drivers/misc/bh1770glc_als.c
@@ -0,0 +1,424 @@
+/*
+ * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver.
+ * Chip is combined proximity and ambient light sensor.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@...ia.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/kernel.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/bh1770glc.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include "bh1770glc.h"
+
+/* Supported stand alone rates in ms from chip data sheet */
+static s16 als_rates[] = {100, 200, 500, 1000, 2000};
+
+/* chip->mutex must be locked during this function */
+static int bh1770glc_als_interrupt_control(struct bh1770glc_chip *chip,
+					   int als)
+{
+	chip->int_mode_als = als;
+
+	/* Set ALS interrupt mode, interrupt active low, latched */
+	return i2c_smbus_write_byte_data(chip->client,
+					BHCHIP_INTERRUPT,
+					(als << 1) | (chip->int_mode_ps << 0));
+}
+
+int bh1770glc_als_mode(struct bh1770glc_chip *chip, int mode)
+{
+	int r;
+
+	r = i2c_smbus_write_byte_data(chip->client, BHCHIP_ALS_CONTROL, mode);
+
+	if (r == 0)
+		chip->als_mode = mode;
+
+	return r;
+}
+
+static int bh1770glc_als_rate(struct bh1770glc_chip *chip, int rate)
+{
+	int ret = -EINVAL;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(als_rates); i++)
+		if (als_rates[i] == rate) {
+			ret = i2c_smbus_write_byte_data(chip->client,
+							BHCHIP_ALS_MEAS_RATE,
+							i);
+			if (ret == 0)
+				chip->als_rate = rate;
+			break;
+		}
+	return ret;
+}
+
+static int bh1770glc_als_set_threshold(struct bh1770glc_chip *chip,
+				int threshold_hi,
+				int threshold_lo)
+{
+	u8 data[4];
+	int ret;
+
+	chip->als_threshold_hi = threshold_hi;
+	chip->als_threshold_lo = threshold_lo;
+
+	data[0] = threshold_hi;
+	data[1] = threshold_hi >> 8;
+	data[2] = threshold_lo;
+	data[3] = threshold_lo >> 8;
+
+	ret = i2c_smbus_write_i2c_block_data(chip->client,
+					BHCHIP_ALS_TH_UP_0,
+					ARRAY_SIZE(data),
+					data);
+	return ret;
+}
+
+static int bh1770glc_als_calc_thresholds(struct bh1770glc_chip *chip, u16 data)
+{
+	int scaler;
+	int hi_thres;
+	int lo_thres;
+	int sens;
+	int ret;
+
+	/*
+	 * Recalculate new threshold limits to simulate delta measurement
+	 * mode. New limits are relative to latest measurement data.
+	 */
+	scaler = ((int)data * ALS_COEF) / ALS_NBR_FORMAT + ALS_ZERO_LEVEL;
+	sens = chip->als_sens * scaler / ALS_NBR_FORMAT;
+
+	hi_thres = min(data + sens, BHCHIP_ALS_RANGE);
+	lo_thres = max(data - sens, 0);
+
+	mutex_lock(&chip->mutex);
+	ret = bh1770glc_als_set_threshold(chip,
+					hi_thres,
+					lo_thres);
+	mutex_unlock(&chip->mutex);
+
+	return ret;
+}
+
+static int bh1770glc_als_read_result(struct bh1770glc_chip *chip)
+{
+	u16 data;
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(chip->client, BHCHIP_ALS_DATA_0);
+	if (ret < 0)
+		goto exit;
+
+	data = ret & 0xff;
+	ret = i2c_smbus_read_byte_data(chip->client, BHCHIP_ALS_DATA_1);
+	if (ret < 0)
+		goto exit;
+
+	data = data | ((ret & 0xff) << 8);
+	chip->als_data = data;
+	chip->als_offset += sizeof(struct bh1770glc_als);
+
+	ret = bh1770glc_als_calc_thresholds(chip, data);
+exit:
+	return ret;
+}
+
+void bh1770glc_als_interrupt_handler(struct bh1770glc_chip *chip, int status)
+{
+	if (chip->int_mode_als)
+		if (status & BHCHIP_INT_ALS_INT) {
+			bh1770glc_als_read_result(chip);
+			wake_up_interruptible(&bh1770glc->als_misc_wait);
+		}
+}
+
+static ssize_t bh1770glc_als_read(struct file *file, char __user *buf,
+			size_t count, loff_t *offset)
+{
+	struct bh1770glc_als als;
+	struct bh1770glc_chip *chip = bh1770glc;
+	u32 lux;
+
+	if (count < sizeof(als))
+		return -EINVAL;
+
+	if (*offset >= chip->als_offset) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		if (wait_event_interruptible(bh1770glc->als_misc_wait,
+						(*offset < chip->als_offset)))
+			return -ERESTARTSYS;
+	}
+	lux = ((u32)chip->als_data * chip->als_calib) /
+		BHCHIP_CALIB_SCALER;
+	lux = min(lux, (u32)BHCHIP_ALS_RANGE);
+
+	als.lux = lux;
+
+	*offset = chip->als_offset;
+
+	return copy_to_user(buf, &als, sizeof(als)) ? -EFAULT : sizeof(als);
+}
+
+static int bh1770glc_als_open(struct inode *inode, struct file *file)
+{
+
+	struct bh1770glc_chip *chip = bh1770glc;
+	int ret = 0;
+
+	mutex_lock(&chip->mutex);
+	if (!chip->als_users) {
+		ret = regulator_bulk_enable(ARRAY_SIZE(chip->regs),
+					chip->regs);
+		if (ret < 0)
+			goto release_lock;
+
+		if (!chip->ps_users)
+			msleep(BHCHIP_STARTUP_DELAY);
+
+		ret = bh1770glc_als_mode(chip, BHCHIP_STANDALONE);
+		if (ret < 0)
+			goto exit;
+
+		ret = bh1770glc_als_rate(chip, chip->als_rate);
+		if (ret < 0)
+			goto exit;
+
+		ret = bh1770glc_als_interrupt_control(chip, BHCHIP_ENABLE);
+		if (ret < 0)
+			goto exit;
+	}
+	/* Trig measurement and refresh the measurement result */
+	bh1770glc_als_set_threshold(chip, BHCHIP_ALS_DEF_THRES,
+				BHCHIP_ALS_DEF_THRES);
+
+	if (ret == 0)
+		chip->als_users++;
+exit:
+	if (ret < 0)
+		regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+release_lock:
+	file->f_pos = chip->als_offset;
+	/* In case of two or more user, provide newest results available */
+	if (chip->als_users > 1 &&
+	    file->f_pos >= sizeof(struct bh1770glc_als))
+		file->f_pos -= sizeof(struct bh1770glc_als);
+
+	mutex_unlock(&chip->mutex);
+	return ret;
+}
+
+static unsigned int bh1770glc_als_poll(struct file *file, poll_table *wait)
+{
+	poll_wait(file, &bh1770glc->als_misc_wait, wait);
+	if (file->f_pos < bh1770glc->als_offset)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+static int bh1770glc_als_close(struct inode *inode, struct file *file)
+{
+	struct bh1770glc_chip *chip = bh1770glc;
+	mutex_lock(&chip->mutex);
+	if (!--chip->als_users) {
+		bh1770glc_als_interrupt_control(chip, BHCHIP_DISABLE);
+		bh1770glc_als_mode(chip, BHCHIP_STANDBY);
+		regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+	}
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+
+/* SYSFS interface */
+static ssize_t bh1770glc_als_calib_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", chip->als_calib);
+}
+
+static ssize_t bh1770glc_als_calib_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t len)
+{
+	struct bh1770glc_chip *chip = dev_get_drvdata(dev);
+	unsigned long value;
+
+	if (strict_strtoul(buf, 0, &value))
+		return -EINVAL;
+
+	chip->als_calib = value;
+
+	return len;
+}
+
+static ssize_t bh1770glc_get_als_mode(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", chip->als_mode);
+}
+
+static ssize_t bh1770glc_get_als_rate(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", chip->als_rate);
+}
+
+static ssize_t bh1770glc_set_als_rate(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	unsigned long rate;
+	int ret;
+
+	if (strict_strtoul(buf, 0, &rate))
+		return -EINVAL;
+
+	mutex_lock(&chip->mutex);
+	ret = bh1770glc_als_rate(chip, rate);
+	mutex_unlock(&chip->mutex);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bh1770glc_get_als_sens(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", chip->als_sens);
+}
+
+static ssize_t bh1770glc_set_als_sens(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	int ret;
+	unsigned long sens;
+
+	if (strict_strtoul(buf, 0, &sens))
+		return -EINVAL;
+
+	chip->als_sens = sens;
+
+	/* Trick measurement by setting default thresholds */
+	mutex_lock(&chip->mutex);
+	ret = bh1770glc_als_set_threshold(chip,
+					BHCHIP_ALS_DEF_THRES,
+					BHCHIP_ALS_DEF_THRES);
+
+	mutex_unlock(&chip->mutex);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+static DEVICE_ATTR(als_calib, S_IRUGO | S_IWUSR, bh1770glc_als_calib_show,
+						 bh1770glc_als_calib_store);
+static DEVICE_ATTR(als_mode, S_IRUGO , bh1770glc_get_als_mode, NULL);
+static DEVICE_ATTR(als_rate, S_IRUGO | S_IWUSR, bh1770glc_get_als_rate,
+						bh1770glc_set_als_rate);
+static DEVICE_ATTR(als_sens, S_IRUGO | S_IWUSR, bh1770glc_get_als_sens,
+						bh1770glc_set_als_sens);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_als_calib.attr,
+	&dev_attr_als_mode.attr,
+	&dev_attr_als_rate.attr,
+	&dev_attr_als_sens.attr,
+	NULL
+};
+
+static struct attribute_group bh1770glc_attribute_group = {
+	.attrs = sysfs_attrs
+};
+
+static const struct file_operations bh1770glc_als_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= bh1770glc_als_read,
+	.poll		= bh1770glc_als_poll,
+	.open		= bh1770glc_als_open,
+	.release	= bh1770glc_als_close,
+};
+
+static struct miscdevice bh1770glc_als_miscdevice = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name  = "bh1770glc_als",
+       .fops  = &bh1770glc_als_fops
+};
+
+int bh1770glc_als_init(struct bh1770glc_chip *chip)
+{
+	int err;
+	err = bh1770glc_als_mode(chip, BHCHIP_STANDBY);
+	if (err < 0)
+		goto fail;
+
+	err = bh1770glc_als_interrupt_control(chip, BHCHIP_DISABLE);
+	if (err < 0)
+		goto fail;
+
+	chip->als_rate = BHCHIP_ALS_DEFAULT_RATE;
+
+	bh1770glc_als_miscdevice.parent = &chip->client->dev;
+	err = misc_register(&bh1770glc_als_miscdevice);
+	if (err < 0) {
+		dev_err(&chip->client->dev, "Device registration failed\n");
+		goto fail;
+	}
+
+	err = sysfs_create_group(&chip->client->dev.kobj,
+				&bh1770glc_attribute_group);
+	if (err < 0) {
+		dev_err(&chip->client->dev, "Sysfs registration failed\n");
+		goto fail2;
+	}
+	return 0;
+fail2:
+	misc_deregister(&bh1770glc_als_miscdevice);
+fail:
+	return err;
+}
+
+int bh1770glc_als_destroy(struct bh1770glc_chip *chip)
+{
+	sysfs_remove_group(&chip->client->dev.kobj,
+			&bh1770glc_attribute_group);
+	misc_deregister(&bh1770glc_als_miscdevice);
+	return 0;
+}
diff --git a/drivers/misc/bh1770glc_core.c b/drivers/misc/bh1770glc_core.c
new file mode 100644
index 0000000..36d0443
--- /dev/null
+++ b/drivers/misc/bh1770glc_core.c
@@ -0,0 +1,301 @@
+/*
+ * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver.
+ * Chip is combined proximity and ambient light sensor.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@...ia.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/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/bh1770glc.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include "bh1770glc.h"
+
+struct bh1770glc_chip *bh1770glc;
+
+static const char reg_vcc[] = "Vcc";
+static const char reg_vleds[] = "Vleds";
+
+static int bh1770glc_detect(struct bh1770glc_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	s32 ret;
+	u8 manu;
+	u8 part;
+
+	ret = i2c_smbus_read_byte_data(client, BHCHIP_MANUFACT_ID);
+	if (ret < 0)
+		goto error;
+
+	manu = (u8)ret;
+
+	ret = i2c_smbus_read_byte_data(client, BHCHIP_PART_ID);
+	if (ret < 0)
+		goto error;
+	part = (u8)ret;
+	chip->revision = (part & BHCHIP_REV_MASK) >> BHCHIP_REV_SHIFT;
+
+	if ((manu == BHCHIP_MANUFACT_ROHM) &&
+	    ((part & BHCHIP_PART_MASK)	== BHCHIP_PART)) {
+		snprintf(chip->chipname, sizeof(chip->chipname), "BH1770GLC");
+		return 0;
+	}
+
+	if ((manu == BHCHIP_MANUFACT_OSRAM) &&
+	    ((part & BHCHIP_PART_MASK)	== BHCHIP_PART)) {
+		snprintf(chip->chipname, sizeof(chip->chipname), "SFH7770");
+		return 0;
+	}
+
+	ret = -ENODEV;
+error:
+	dev_dbg(&client->dev, "BH1770GLC or SFH7770 not found\n");
+
+	return ret;
+}
+
+/* This is threaded irq handler */
+static irqreturn_t bh1770glc_irq(int irq, void *data)
+{
+	struct bh1770glc_chip *chip = data;
+	int status;
+
+	status = i2c_smbus_read_byte_data(chip->client, BHCHIP_ALS_PS_STATUS);
+
+	/* Acknowledge interrupt by reading this register */
+	i2c_smbus_read_byte_data(chip->client, BHCHIP_INTERRUPT);
+
+	bh1770glc_als_interrupt_handler(chip, status);
+	bh1770glc_ps_interrupt_handler(chip, status);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit bh1770glc_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	struct bh1770glc_chip *chip;
+	int err;
+	int i;
+
+	chip = kzalloc(sizeof *chip, GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	bh1770glc = chip;
+
+	init_waitqueue_head(&chip->ps_misc_wait);
+	init_waitqueue_head(&chip->als_misc_wait);
+
+	i2c_set_clientdata(client, chip);
+	chip->client  = client;
+
+	mutex_init(&chip->mutex);
+	chip->pdata	= client->dev.platform_data;
+	chip->als_calib = BHCHIP_ALS_NEUTRAL_CALIB_VALUE;
+	chip->als_sens	= BHCHIP_ALS_DEF_SENS;
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++)
+		chip->ps_calib[i] = BHCHIP_PS_NEUTRAL_CALIB_VALUE;
+
+	/*
+	 * Platform data contains led configuration and safety limits.
+	 * Too strong current can damage HW permanently.
+	 * Platform data filled with zeros causes minimum current.
+	 */
+	if (chip->pdata == NULL) {
+		dev_err(&client->dev, "platform data is mandatory\n");
+		err = -EINVAL;
+		goto fail1;
+	}
+
+	if (chip->pdata->setup_resources) {
+		err = chip->pdata->setup_resources();
+		if (err) {
+			err = -EINVAL;
+			goto fail1;
+		}
+	}
+
+	switch (chip->pdata->leds) {
+	case BH1770GLC_LED1:
+		chip->ps_channels = 1;
+		break;
+	case BH1770GLC_LED12:
+	case BH1770GLC_LED13:
+		chip->ps_channels = 2;
+		break;
+	case BH1770GLC_LED123:
+		chip->ps_channels = 3;
+		break;
+	default:
+		err = -EINVAL;
+		goto fail1;
+	}
+
+	chip->regs[0].supply = reg_vcc;
+	chip->regs[1].supply = reg_vleds;
+
+	err = regulator_bulk_get(&client->dev,
+				 ARRAY_SIZE(chip->regs), chip->regs);
+	if (err < 0) {
+		dev_err(&client->dev, "Cannot get regulators\n");
+		goto fail1;
+	}
+
+	err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), chip->regs);
+	if (err < 0) {
+		dev_err(&client->dev, "Cannot enable regulators\n");
+		goto fail2;
+	}
+
+	err = bh1770glc_detect(chip);
+	if (err < 0)
+		goto fail3;
+
+	err = bh1770glc_als_init(chip);
+	if (err < 0)
+		goto fail3;
+
+	err = bh1770glc_ps_init(chip);
+	if (err < 0)
+		goto fail4;
+
+	err = request_threaded_irq(client->irq, NULL,
+				bh1770glc_irq,
+				IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				"bh1770glc", chip);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n",
+			client->irq);
+		goto fail5;
+	}
+	regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+	return err;
+fail5:
+	bh1770glc_ps_destroy(chip);
+fail4:
+	bh1770glc_als_destroy(chip);
+fail3:
+	regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+fail2:
+	regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs);
+fail1:
+	kfree(chip);
+	return err;
+}
+
+static int __devexit bh1770glc_remove(struct i2c_client *client)
+{
+	struct bh1770glc_chip *chip = i2c_get_clientdata(client);
+
+	free_irq(client->irq, chip);
+
+	if (chip->pdata && chip->pdata->release_resources)
+		chip->pdata->release_resources();
+
+	bh1770glc_als_destroy(chip);
+	bh1770glc_ps_destroy(chip);
+	regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs);
+	kfree(chip);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int bh1770glc_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct bh1770glc_chip *chip = i2c_get_clientdata(client);
+
+	mutex_lock(&chip->mutex);
+	if (chip->als_users)
+		bh1770glc_als_mode(chip, BHCHIP_STANDBY);
+	if (chip->ps_users)
+		bh1770glc_ps_mode(chip, BHCHIP_STANDBY);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int bh1770glc_resume(struct i2c_client *client)
+{
+	struct bh1770glc_chip *chip = i2c_get_clientdata(client);
+
+	mutex_lock(&chip->mutex);
+	if (chip->als_users)
+		bh1770glc_als_mode(chip, BHCHIP_STANDALONE);
+	if (chip->ps_users)
+		bh1770glc_ps_mode(chip, BHCHIP_STANDALONE);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static void bh1770glc_shutdown(struct i2c_client *client)
+{
+	struct bh1770glc_chip *chip = i2c_get_clientdata(client);
+
+	bh1770glc_als_mode(chip, BHCHIP_STANDBY);
+	bh1770glc_ps_mode(chip, BHCHIP_STANDBY);
+}
+
+#else
+#define bh1770glc_suspend  NULL
+#define bh1770glc_shutdown NULL
+#define bh1770glc_resume   NULL
+#endif
+
+static const struct i2c_device_id bh1770glc_id[] = {
+	{"bh1770glc", 0 },
+	{"SFH7770", 0 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, bh1770glc_id);
+
+static struct i2c_driver bh1770glc_driver = {
+	.driver	 = {
+		.name	= "bh1770glc",
+		.owner	= THIS_MODULE,
+	},
+	.suspend  = bh1770glc_suspend,
+	.shutdown = bh1770glc_shutdown,
+	.resume	  = bh1770glc_resume,
+	.probe	  = bh1770glc_probe,
+	.remove	  = __devexit_p(bh1770glc_remove),
+	.id_table = bh1770glc_id,
+};
+
+static int __init bh1770glc_init(void)
+{
+	return i2c_add_driver(&bh1770glc_driver);
+}
+
+static void __exit bh1770glc_exit(void)
+{
+	i2c_del_driver(&bh1770glc_driver);
+}
+
+MODULE_DESCRIPTION("BH1770GLC combined ALS and proximity sensor");
+MODULE_AUTHOR("Samu Onkalo, Nokia Corporation");
+MODULE_LICENSE("GPL v2");
+
+module_init(bh1770glc_init);
+module_exit(bh1770glc_exit);
diff --git a/drivers/misc/bh1770glc_ps.c b/drivers/misc/bh1770glc_ps.c
new file mode 100644
index 0000000..5e68efe
--- /dev/null
+++ b/drivers/misc/bh1770glc_ps.c
@@ -0,0 +1,585 @@
+/*
+ * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver.
+ * Chip is combined proximity and ambient light sensor.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@...ia.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/kernel.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/bh1770glc.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include "bh1770glc.h"
+
+/* Supported stand alone rates in ms from chip data sheet */
+static s16 ps_rates[] = {10, 20, 30, 40, 70, 100, 200, 500, 1000, 2000};
+
+/* Supported IR-led currents in mA */
+static const u8 ps_curr_ma[] = {5, 10, 20, 50, 100, 150, 200};
+
+/* chip->mutex must be locked during this function */
+static int bh1770glc_ps_interrupt_control(struct bh1770glc_chip *chip, int ps)
+{
+	chip->int_mode_ps = ps;
+
+	/* Set PS interrupt mode, interrupt active low, latched */
+	return i2c_smbus_write_byte_data(chip->client,
+					BHCHIP_INTERRUPT,
+					(chip->int_mode_als << 1) | (ps << 0));
+}
+
+/* LEDs are controlled by the chip during proximity scanning */
+static int bh1770glc_led_cfg(struct bh1770glc_chip *chip, u8 ledcurr[3])
+{
+	u8 ledcfg;
+	int ret, i;
+
+	ledcfg = chip->pdata->leds;
+
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++)
+		if (ledcurr[i] > chip->pdata->led_max_curr)
+			return -EINVAL;
+
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++)
+		chip->ps_leds[i] = ledcurr[i];
+
+	/* LED cfg, current for leds 1 and 2 */
+	ret = i2c_smbus_write_byte_data(chip->client,
+					BHCHIP_I_LED,
+					(ledcfg << 6) | (ledcurr[1] << 3) |
+					 ledcurr[0]);
+	if (ret < 0)
+		goto fail;
+
+	/* Current for led 3*/
+	ret = i2c_smbus_write_byte_data(chip->client,
+					BHCHIP_I_LED3,
+					ledcurr[2]);
+fail:
+	return ret;
+}
+
+int bh1770glc_ps_mode(struct bh1770glc_chip *chip, int mode)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(chip->client, BHCHIP_PS_CONTROL, mode);
+	if (ret == 0)
+		chip->ps_mode = mode;
+	return ret;
+}
+
+static int bh1770glc_ps_rates(struct bh1770glc_chip *chip, int rate,
+			int rate_threshold)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ps_rates); i++) {
+		if (ps_rates[i] == rate) {
+			chip->ps_rate = i;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(ps_rates))
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(ps_rates); i++) {
+		if (ps_rates[i] == rate_threshold) {
+			chip->ps_rate_threshold = i;
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static int bh1770glc_ps_rate(struct bh1770glc_chip *chip, int mode)
+{
+	int ret;
+	int rate;
+
+	rate = (mode == PS_ABOVE_THRESHOLD) ?
+		chip->ps_rate_threshold : chip->ps_rate;
+
+	ret = i2c_smbus_write_byte_data(chip->client,
+					BHCHIP_PS_MEAS_RATE,
+					rate);
+	return ret;
+}
+
+static int bh1770glc_ps_read_result(struct bh1770glc_chip *chip)
+{
+	int ret;
+	int i;
+
+	mutex_lock(&chip->mutex);
+	for (i = 0; i < ARRAY_SIZE(chip->ps_data); i++) {
+		ret = i2c_smbus_read_byte_data(chip->client,
+					BHCHIP_PS_DATA_LED1 + i);
+		if (ret < 0)
+			goto out;
+		chip->ps_data[i] = ret;
+	}
+	chip->ps_offset += sizeof(struct bh1770glc_ps);
+out:
+	mutex_unlock(&chip->mutex);
+	return ret;
+}
+
+static int bh1770glc_ps_set_thresholds(struct bh1770glc_chip *chip)
+{
+	int ret, i;
+	u8 data[BHCHIP_PS_CHANNELS];
+	u32 tmp;
+
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++) {
+		tmp = ((u32)chip->ps_thresholds[i] * BHCHIP_CALIB_SCALER) /
+			chip->ps_calib[i];
+		if (tmp > BHCHIP_PS_RANGE)
+			tmp = BHCHIP_PS_RANGE;
+		data[i] = tmp;
+	}
+
+	ret = i2c_smbus_write_i2c_block_data(chip->client,
+					 BHCHIP_PS_TH_LED1,
+					 BHCHIP_PS_CHANNELS,
+					 data);
+	return ret;
+}
+
+static ssize_t bh1770glc_ps_read(struct file *file, char __user *buf,
+			size_t count, loff_t *offset)
+{
+	struct bh1770glc_ps ps;
+	struct bh1770glc_chip *chip = bh1770glc;
+	int i;
+	u16 data[BHCHIP_PS_CHANNELS];
+
+	if (count < sizeof(ps))
+		return -EINVAL;
+
+	if (*offset >= chip->ps_offset) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		if (wait_event_interruptible(bh1770glc->ps_misc_wait,
+						(*offset < chip->ps_offset)))
+			return -ERESTARTSYS;
+	}
+
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++) {
+		data[i] = ((u32)chip->ps_data[i] * chip->ps_calib[i]) /
+			BHCHIP_CALIB_SCALER;
+		if (data[i] > BHCHIP_PS_RANGE)
+			data[i] = BHCHIP_PS_RANGE;
+	}
+
+	ps.led1 = data[0];
+	ps.led2 = data[1];
+	ps.led3 = data[2];
+
+	*offset = chip->ps_offset;
+
+	return copy_to_user(buf, &ps, sizeof(ps)) ? -EFAULT : sizeof(ps);
+}
+
+static void bh1770glc_ps_work(struct work_struct *work)
+{
+	struct bh1770glc_chip *chip =
+		container_of(work, struct bh1770glc_chip, ps_work.work);
+
+	bh1770glc_ps_rate(chip, PS_BELOW_THRESHOLD);
+	bh1770glc_ps_read_result(chip);
+	wake_up_interruptible(&chip->ps_misc_wait);
+}
+
+void bh1770glc_ps_interrupt_handler(struct bh1770glc_chip *chip, int status)
+{
+	if (chip->int_mode_ps)
+		if (status & BHCHIP_INT_LEDS_INT) {
+			int rate = ps_rates[chip->ps_rate_threshold];
+
+			bh1770glc_ps_read_result(chip);
+			bh1770glc_ps_rate(chip, PS_ABOVE_THRESHOLD);
+			wake_up_interruptible(&bh1770glc->ps_misc_wait);
+
+			cancel_delayed_work_sync(&chip->ps_work);
+			/*
+			 * Let's recheck situation 50 ms after the next
+			 * expected threshold interrupt.
+			 */
+			schedule_delayed_work(&chip->ps_work,
+			  msecs_to_jiffies(rate + 50));
+		}
+}
+
+/* Proximity misc device */
+static unsigned int bh1770glc_ps_poll(struct file *file, poll_table *wait)
+{
+	poll_wait(file, &bh1770glc->ps_misc_wait, wait);
+	if (file->f_pos < bh1770glc->ps_offset)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+static int bh1770glc_ps_open(struct inode *inode, struct file *file)
+{
+	struct bh1770glc_chip *chip = bh1770glc;
+	int err;
+
+	mutex_lock(&chip->mutex);
+	err = 0;
+	if (!chip->ps_users) {
+		err = regulator_bulk_enable(ARRAY_SIZE(chip->regs),
+					chip->regs);
+		if (err < 0)
+			goto release_lock;
+
+		if (!chip->als_users)
+			msleep(BHCHIP_STARTUP_DELAY);
+
+		/* Refresh all configs in case of regulators were off */
+		err = bh1770glc_ps_set_thresholds(chip);
+		if (err < 0)
+			goto exit;
+
+		err = bh1770glc_led_cfg(chip, chip->ps_leds);
+		if (err < 0)
+			goto exit;
+
+		err = bh1770glc_ps_rate(chip, PS_BELOW_THRESHOLD);
+		if (err < 0)
+			goto exit;
+
+		err = bh1770glc_ps_interrupt_control(chip, BHCHIP_ENABLE);
+		if (err < 0)
+			goto exit;
+
+		err = bh1770glc_ps_mode(chip, BHCHIP_STANDALONE);
+	}
+	if (err == 0)
+		chip->ps_users++;
+exit:
+	if (err < 0)
+		regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+
+release_lock:
+	file->f_pos = chip->ps_offset;
+	mutex_unlock(&chip->mutex);
+
+	if (err == 0) {
+		cancel_delayed_work_sync(&chip->ps_work);
+		schedule_delayed_work(&chip->ps_work,
+				msecs_to_jiffies(BHCHIP_PS_INIT_DELAY));
+	}
+
+	return err;
+}
+
+static int bh1770glc_ps_close(struct inode *inode, struct file *file)
+{
+	struct bh1770glc_chip *chip = bh1770glc;
+	mutex_lock(&chip->mutex);
+	if (!--chip->ps_users) {
+		bh1770glc_ps_interrupt_control(chip, BHCHIP_DISABLE);
+		bh1770glc_ps_mode(chip, BHCHIP_STANDBY);
+		regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+	}
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static ssize_t bh1770glc_get_ps_mode(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", chip->ps_mode);
+}
+
+static ssize_t bh1770glc_get_ps_rate(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%d %d\n", ps_rates[chip->ps_rate],
+		ps_rates[chip->ps_rate_threshold]);
+}
+
+static ssize_t bh1770glc_set_ps_rate(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	int rate = 0, rate_threshold = 0;
+	int ret;
+
+	ret = sscanf(buf, "%d %d", &rate, &rate_threshold);
+	if (ret < 0)
+		return ret;
+
+	if (ret == 0)
+		return count;
+
+	/* Second value is optional */
+	if (ret == 1)
+		rate_threshold = ps_rates[chip->ps_rate_threshold];
+
+	mutex_lock(&chip->mutex);
+	ret = bh1770glc_ps_rates(chip, rate, rate_threshold);
+	mutex_unlock(&chip->mutex);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bh1770glc_ps_calib_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%u %u %u\n", chip->ps_calib[0],
+			chip->ps_calib[1], chip->ps_calib[2]);
+}
+
+static ssize_t bh1770glc_ps_calib_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t len)
+{
+	struct bh1770glc_chip *chip = dev_get_drvdata(dev);
+	int calib[BHCHIP_PS_CHANNELS];
+	int i, ret;
+
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++)
+		calib[i] = BHCHIP_PS_NEUTRAL_CALIB_VALUE;
+	ret = sscanf(buf, "%d %d %d", &calib[0], &calib[1], &calib[2]);
+	if (ret < 0)
+		return ret;
+	if (ret < chip->ps_channels)
+		return -EINVAL;
+
+	for (i = 0; i < chip->ps_channels; i++)
+		chip->ps_calib[i] = calib[i];
+
+	return len;
+}
+
+static ssize_t bh1770glc_get_ps_thres(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%d %d %d\n", chip->ps_thresholds[0],
+		chip->ps_thresholds[1],
+		chip->ps_thresholds[2]);
+}
+
+static ssize_t bh1770glc_set_ps_thres(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	int input[BHCHIP_PS_CHANNELS];
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(input); i++)
+		input[i] = BHCHIP_PS_RANGE;
+	ret = sscanf(buf, "%d %d %d", &input[0], &input[1], &input[2]);
+
+	if (ret < 0)
+		return ret;
+	if (ret < chip->ps_channels)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(input); i++) {
+		if ((input[i] < 0) ||
+		    (input[i] > BHCHIP_PS_RANGE))
+			return -EINVAL;
+		chip->ps_thresholds[i] = input[i];
+	}
+
+	mutex_lock(&chip->mutex);
+	ret = bh1770glc_ps_set_thresholds(chip);
+	mutex_unlock(&chip->mutex);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bh1770glc_ps_leds_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	u8 led_current[BHCHIP_PS_CHANNELS];
+	int i;
+
+	memset(led_current, 0, sizeof(led_current));
+	for (i = 0; i < chip->ps_channels; i++)
+		led_current[i] = ps_curr_ma[chip->ps_leds[i]];
+
+	return sprintf(buf, "%d %d %d\n", led_current[0],
+		led_current[1],
+		led_current[2]);
+}
+
+static ssize_t bh1770glc_ps_leds_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	int input[BHCHIP_PS_CHANNELS];
+	u8 led_curr[BHCHIP_PS_CHANNELS];
+	int ret;
+	int i, j;
+
+	ret = sscanf(buf, "%d %d %d", &input[0], &input[1], &input[2]);
+	if (ret < 0)
+		return ret;
+	if (ret < chip->ps_channels)
+		return -EINVAL;
+
+	/* Set minimum current */
+	for (i = chip->ps_channels; i < BHCHIP_PS_CHANNELS; i++)
+		led_curr[i] = BH1770GLC_LED_5mA;
+
+	for (i = 0; i < chip->ps_channels; i++) {
+		for (j = 0; j < ARRAY_SIZE(ps_curr_ma); j++)
+			if (input[i] == ps_curr_ma[j]) {
+				led_curr[i] = j;
+				break;
+			}
+		if (j == ARRAY_SIZE(ps_curr_ma))
+			return -EINVAL;
+	}
+
+	mutex_lock(&chip->mutex);
+	ret = bh1770glc_led_cfg(chip, led_curr);
+	mutex_unlock(&chip->mutex);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bh1770glc_chip_id_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct bh1770glc_chip *chip =  dev_get_drvdata(dev);
+	return sprintf(buf, "%s rev %d\n", chip->chipname, chip->revision);
+}
+
+static DEVICE_ATTR(ps_mode, S_IRUGO, bh1770glc_get_ps_mode, NULL);
+static DEVICE_ATTR(ps_rate, S_IRUGO | S_IWUSR, bh1770glc_get_ps_rate,
+						bh1770glc_set_ps_rate);
+static DEVICE_ATTR(ps_threshold, S_IRUGO | S_IWUSR, bh1770glc_get_ps_thres,
+						bh1770glc_set_ps_thres);
+static DEVICE_ATTR(ps_calib, S_IRUGO | S_IWUSR, bh1770glc_ps_calib_show,
+						 bh1770glc_ps_calib_store);
+static DEVICE_ATTR(ps_leds, S_IRUGO | S_IWUSR, bh1770glc_ps_leds_show,
+						 bh1770glc_ps_leds_store);
+static DEVICE_ATTR(chip_id, S_IRUGO, bh1770glc_chip_id_show, NULL);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_ps_calib.attr,
+	&dev_attr_ps_mode.attr,
+	&dev_attr_ps_rate.attr,
+	&dev_attr_ps_threshold.attr,
+	&dev_attr_ps_leds.attr,
+	&dev_attr_chip_id.attr,
+	NULL
+};
+
+static struct attribute_group bh1770glc_attribute_group = {
+	.attrs = sysfs_attrs
+};
+
+static const struct file_operations bh1770glc_ps_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= bh1770glc_ps_read,
+	.poll		= bh1770glc_ps_poll,
+	.open		= bh1770glc_ps_open,
+	.release	= bh1770glc_ps_close,
+};
+
+static struct miscdevice bh1770glc_ps_miscdevice = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "bh1770glc_ps",
+       .fops = &bh1770glc_ps_fops
+};
+
+int bh1770glc_ps_init(struct bh1770glc_chip *chip)
+{
+	int err;
+	int i;
+
+	for (i = 0; i < BHCHIP_PS_CHANNELS; i++) {
+		chip->ps_thresholds[i] = BHCHIP_PS_DEF_THRES;
+		chip->ps_leds[i] = chip->pdata->led_def_curr[i];
+	}
+
+	err = bh1770glc_ps_mode(chip, BHCHIP_STANDBY);
+	if (err < 0)
+		goto fail1;
+
+	err = bh1770glc_ps_interrupt_control(chip, BHCHIP_DISABLE);
+	if (err < 0)
+		goto fail1;
+
+	bh1770glc_ps_rates(chip, BHCHIP_PS_DEFAULT_RATE,
+			BHCHIP_PS_DEF_RATE_THRESH);
+
+	INIT_DELAYED_WORK(&chip->ps_work, bh1770glc_ps_work);
+
+	bh1770glc_ps_miscdevice.parent = &chip->client->dev;
+	err = misc_register(&bh1770glc_ps_miscdevice);
+	if (err < 0) {
+		dev_err(&chip->client->dev, "Device registration failed\n");
+		goto fail1;
+	}
+
+	err = sysfs_create_group(&chip->client->dev.kobj,
+				&bh1770glc_attribute_group);
+	if (err < 0) {
+		dev_err(&chip->client->dev, "Sysfs registration failed\n");
+		goto fail2;
+	}
+	return 0;
+fail2:
+	misc_deregister(&bh1770glc_ps_miscdevice);
+fail1:
+	return err;
+
+}
+
+int bh1770glc_ps_destroy(struct bh1770glc_chip *chip)
+{
+	cancel_delayed_work_sync(&chip->ps_work);
+	sysfs_remove_group(&chip->client->dev.kobj,
+			&bh1770glc_attribute_group);
+	misc_deregister(&bh1770glc_ps_miscdevice);
+	return 0;
+}
diff --git a/include/linux/bh1770glc.h b/include/linux/bh1770glc.h
new file mode 100644
index 0000000..27d2d7c
--- /dev/null
+++ b/include/linux/bh1770glc.h
@@ -0,0 +1,39 @@
+#ifndef __BH1770GLC_H__
+#define __BH1770GLC_H__
+
+struct bh1770glc_platform_data {
+/* IR-Led configuration for proximity sensing */
+#define BH1770GLC_LED1	    0x00
+#define BH1770GLC_LED12	    0x01
+#define BH1770GLC_LED13	    0x02
+#define BH1770GLC_LED123    0x03
+
+	__u8 leds;
+/* led_max_curr is a safetylimit for IR leds */
+#define BH1770GLC_LED_5mA   0
+#define BH1770GLC_LED_10mA  1
+#define BH1770GLC_LED_20mA  2
+#define BH1770GLC_LED_50mA  3
+#define BH1770GLC_LED_100mA 4
+#define BH1770GLC_LED_150mA 5
+#define BH1770GLC_LED_200mA 6
+	__u8 led_max_curr;
+	__u8 led_def_curr[3];
+
+	int (*setup_resources)(void);
+	int (*release_resources)(void);
+};
+
+/* Device name: /dev/bh1770glc_ps */
+struct bh1770glc_ps {
+	__u8 led1;
+	__u8 led2;
+	__u8 led3;
+} __attribute__((packed));
+
+/* Device name: /dev/bh1770glc_als */
+struct bh1770glc_als {
+	__u16 lux;
+} __attribute__((packed));
+
+#endif
-- 
1.6.0.4

--
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