[PATCH] hwmon: add GP2A002 proximity/ambient sensor

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
This patch adds support the GP2A002 sensor

Signed-off-by: Minkyu Kang <mk7.kang at samsung.com>
Signed-off-by: Suchang Woo <suchang.woo at samsung.com>
---
 drivers/hwmon/Kconfig    |    7 +
 drivers/hwmon/Makefile   |    1 +
 drivers/hwmon/gp2ap002.c |  471 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/gp2ap002.h |   21 ++
 4 files changed, 500 insertions(+), 0 deletions(-)
 create mode 100644 drivers/hwmon/gp2ap002.c
 create mode 100644 include/linux/gp2ap002.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..cc4e200 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1017,6 +1017,13 @@ config SENSORS_APPLESMC
 	  Say Y here if you have an applicable laptop and want to experience
 	  the awesome power of applesmc.
 
+config SENSORS_GP2AP002
+	tristate "GP2AP002 series Proximity/Ambient Sensor"
+	depends on HWMON && I2C
+	help
+	  This driver provides support for Proximity/Ambient Sensor.
+
+
 config HWMON_DEBUG_CHIP
 	bool "Hardware Monitoring Chip debugging messages"
 	default n
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..5dd1671 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SENSORS_FSCPOS)	+= fscpos.o
 obj-$(CONFIG_SENSORS_G760A)	+= g760a.o
 obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
 obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
+obj-$(CONFIG_SENSORS_GP2AP002)	+= gp2ap002.o
 obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
 obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
 obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
diff --git a/drivers/hwmon/gp2ap002.c b/drivers/hwmon/gp2ap002.c
new file mode 100644
index 0000000..6861584
--- /dev/null
+++ b/drivers/hwmon/gp2ap002.c
@@ -0,0 +1,471 @@
+/*
+ *  gp2ap002.c - Proximity/Ambient light sensor
+ *
+ *  Copyright (C) 2009 Samsung Electronics
+ *  Minkyu Kang <mk7.kang at samsung.com>
+ *  Suchang Woo <suchang.woo at samsung.com>
+ *  Kyungmin Park <kyungmin.park at samsung.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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/input.h>
+#include <linux/gp2ap002.h>
+
+#define GP2AP002_PROX		0x00	/* Read Only */
+#define GP2AP002_GAIN		0x01
+#define GP2AP002_HYS		0x02
+#define GP2AP002_CYCLE		0x03
+#define GP2AP002_OPMOD		0x04
+#define GP2AP002_CON		0x06
+
+#define PROX_VO_NO_DETECT	(0 << 0)
+#define PROX_VO_DETECT		(1 << 0)
+
+#define GAIN_LED0_SMALL		(0 << 3)
+#define GAIN_LED0_LARGE		(1 << 3)
+
+#define HYS_HYSD		(1 << 7)
+#define HYS_HYSC1		(1 << 6)
+#define HYS_HYSC0		(1 << 5)
+#define HYS_HYSF3		(1 << 3)
+#define HYS_HYSF2		(1 << 2)
+#define HYS_HYSF1		(1 << 1)
+#define HYS_HYSF0		(1 << 0)
+
+#define OPMOD_SSD_SHUTDOWN	(0 << 0)
+#define OPMOD_SSD_OPERATING	(1 << 0)
+#define OPMOD_VCON_NORMAL	(0 << 1)
+#define OPMOD_VCON_IRQ		(1 << 1)
+
+#define CON_OCON1		(1 << 4)
+#define CON_OCON0		(1 << 3)
+
+#define GP2AP002_MAX_LUX	10
+
+static int lux_table[GP2AP002_MAX_LUX] = {
+	1, 165, 288, 497, 869, 1532, 2692, 4692, 8280, 100000,
+};
+
+struct gp2ap002_chip {
+	struct i2c_client	*client;
+	struct device		*dev;
+	struct input_dev	*idev;
+	struct work_struct	work;
+	struct mutex		lock;
+
+	void (*power_enable)(int onoff);
+	int (*get_adc)(void);
+	int vo_gpio;
+
+	/* Proximity */
+	int enable;
+	int mode;
+	int vo;
+	/* Ambient Light */
+	int adc;
+	int level;
+};
+
+static int gp2ap002_write_reg(struct i2c_client *client, int reg, u8 value)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, reg, value);
+
+	if (ret < 0)
+		dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int gp2ap002_read_reg(struct i2c_client *client, int reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, reg);
+
+	if (ret < 0)
+		dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+	return ret;
+}
+
+static void gp2ap002_get_vo(struct gp2ap002_chip *chip)
+{
+	if (chip->mode == OPMOD_VCON_IRQ) {
+		chip->vo = gp2ap002_read_reg(chip->client,
+				GP2AP002_PROX) & PROX_VO_DETECT;
+	} else {
+		chip->vo = !gpio_get_value(chip->vo_gpio);
+	}
+}
+
+static void gp2ap002_get_adc(struct gp2ap002_chip *chip)
+{
+	if (chip->get_adc)
+		chip->adc = chip->get_adc();
+}
+
+static void gp2ap002_get_level(struct gp2ap002_chip *chip)
+{
+	int i;
+
+	gp2ap002_get_adc(chip);
+
+	for (i = 0; i < GP2AP002_MAX_LUX; i++) {
+		if (lux_table[i] > chip->adc) {
+			chip->level = i;
+			break;
+		}
+	}
+}
+
+static void gp2ap002_set_mode(struct gp2ap002_chip *chip, int enable)
+{
+	if (enable == chip->enable)
+		return;
+
+	chip->enable = enable;
+	chip->vo = 0;
+
+	gp2ap002_write_reg(chip->client, GP2AP002_OPMOD, OPMOD_SSD_SHUTDOWN);
+
+	if (enable) {
+		gp2ap002_write_reg(chip->client, GP2AP002_OPMOD,
+				OPMOD_SSD_OPERATING | chip->mode);
+		gp2ap002_get_vo(chip);
+	}
+}
+
+static void gp2ap002_set_enable(struct gp2ap002_chip *chip, const char *buf)
+{
+	if (!strncmp(buf, "1", 1))
+		gp2ap002_set_mode(chip, 1);
+	else if (!strncmp(buf, "0", 1))
+		gp2ap002_set_mode(chip, 0);
+}
+
+static void gp2ap002_update_data(struct gp2ap002_chip *chip)
+{
+	gp2ap002_get_vo(chip);
+	enable_irq(chip->client->irq);
+}
+
+static void gp2ap002_work(struct work_struct *work)
+{
+	struct gp2ap002_chip *chip = container_of(work,
+			struct gp2ap002_chip, work);
+
+	mutex_lock(&chip->lock);
+
+	gp2ap002_update_data(chip);
+
+	input_report_abs(chip->idev, ABS_DISTANCE, chip->vo);
+	input_sync(chip->idev);
+
+	mutex_unlock(&chip->lock);
+}
+
+static irqreturn_t gp2ap002_irq(int irq, void *data)
+{
+	struct gp2ap002_chip *chip = data;
+
+	if (!work_pending(&chip->work)) {
+		disable_irq_nosync(irq);
+		schedule_work(&chip->work);
+	} else {
+		dev_err(&chip->client->dev, "work pending\n");
+	}
+
+	return IRQ_HANDLED;
+}
+
+#define GP2AP002_OUTPUT(name, field)					\
+static ssize_t gp2ap002_show_##name(struct device *dev,			\
+		struct device_attribute *attr, char *buf)		\
+{									\
+	struct gp2ap002_chip *chip = dev_get_drvdata(dev);		\
+	gp2ap002_get_##name(chip);					\
+	return sprintf(buf, "%d\n", chip->field);			\
+}									\
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, gp2ap002_show_##name, NULL, 0);
+
+#define GP2AP002_INPUT(name, field)					\
+static ssize_t gp2ap002_store_##name(struct device *dev,		\
+	struct device_attribute *attr, const char *buf, size_t count)	\
+{									\
+	struct gp2ap002_chip *chip = dev_get_drvdata(dev);		\
+	if (!count)							\
+		return -EINVAL;						\
+	gp2ap002_set_##name(chip, buf);					\
+	return count;							\
+}									\
+static ssize_t gp2ap002_show_##name(struct device *dev,			\
+		struct device_attribute *attr, char *buf)		\
+{									\
+	struct gp2ap002_chip *chip = dev_get_drvdata(dev);		\
+	return sprintf(buf, "%d\n", chip->field);			\
+}									\
+static SENSOR_DEVICE_ATTR(name, S_IRUGO | S_IWUSR,			\
+		gp2ap002_show_##name, gp2ap002_store_##name, 0);
+
+GP2AP002_OUTPUT(vo, vo);
+GP2AP002_INPUT(enable, enable);
+GP2AP002_OUTPUT(adc, adc);
+GP2AP002_OUTPUT(level, level);
+
+static struct attribute *proximity_attributes[] = {
+	&sensor_dev_attr_vo.dev_attr.attr,
+	&sensor_dev_attr_enable.dev_attr.attr,
+	NULL
+};
+
+static struct attribute *ambient_attributes[] = {
+	&sensor_dev_attr_adc.dev_attr.attr,
+	&sensor_dev_attr_level.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group proximity_group = {
+	.name = "proximity",
+	.attrs = proximity_attributes,
+};
+
+static const struct attribute_group ambient_group = {
+	.name = "ambient",
+	.attrs = ambient_attributes,
+};
+
+static void gp2ap002_unregister_input_device(struct gp2ap002_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+
+	if (client->irq > 0)
+		free_irq(client->irq, chip);
+
+	input_unregister_device(chip->idev);
+	input_free_device(chip->idev);
+}
+
+static int gp2ap002_register_input_device(struct gp2ap002_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	struct input_dev *idev;
+	int ret;
+
+	idev = chip->idev = input_allocate_device();
+	if (!idev) {
+		dev_err(&client->dev, "allocating input device is failed\n");
+		ret = -ENOMEM;
+		goto error_alloc;
+	}
+
+	idev->name = "GP2AP002 OpticalSensor";
+	idev->id.bustype = BUS_I2C;
+	idev->dev.parent = &client->dev;
+	idev->evbit[0] = BIT_MASK(EV_ABS);
+
+	input_set_abs_params(idev, ABS_DISTANCE, 0, 1, 0, 0);
+
+	input_set_drvdata(idev, chip);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&client->dev, "registering input device is failed\n");
+		goto error_reg;
+	}
+
+	if (client->irq > 0) {
+		unsigned long irq_flag = IRQF_DISABLED;
+
+		if (chip->mode == OPMOD_VCON_IRQ)
+			irq_flag |= IRQF_TRIGGER_FALLING;
+		else
+			irq_flag |= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
+
+		ret = request_irq(client->irq, gp2ap002_irq, irq_flag,
+				"GP2AP002 OpticalSensor", chip);
+		if (ret) {
+			dev_err(&client->dev, "can't get IRQ %d, ret %d\n",
+					client->irq, ret);
+			goto error_irq;
+		}
+	}
+
+	return 0;
+
+error_irq:
+	input_unregister_device(idev);
+error_reg:
+	input_free_device(idev);
+error_alloc:
+	return ret;
+}
+
+
+static int gp2ap002_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct gp2ap002_chip *chip;
+	struct gp2ap002_platform_data *pdata;
+	int ret;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	chip = kzalloc(sizeof(struct gp2ap002_chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	pdata = client->dev.platform_data;
+
+	chip->client = client;
+	i2c_set_clientdata(client, chip);
+
+	chip->dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(chip->dev)) {
+		dev_err(&client->dev,
+				"Registering to hwmon device is failed\n");
+		ret = PTR_ERR(chip->dev);
+		goto error_hwmon;
+	}
+
+	ret = sysfs_create_group(&client->dev.kobj, &proximity_group);
+	if (ret) {
+		dev_err(&client->dev,
+				"Creating proximity attribute group failed\n");
+		goto error_sysfs1;
+	}
+
+	ret = sysfs_create_group(&client->dev.kobj, &ambient_group);
+	if (ret) {
+		dev_err(&client->dev,
+				"Creating light attribute group failed\n");
+		goto error_sysfs2;
+	}
+
+	ret = gp2ap002_register_input_device(chip);
+	if (ret) {
+		dev_err(&client->dev, "Registering input device is failed\n");
+		goto error_input;
+	}
+
+	chip->power_enable	= pdata->power_enable;
+	chip->get_adc		= pdata->get_adc;
+	chip->vo_gpio		= pdata->vo_gpio;
+	chip->mode		= pdata->prox_mode;
+
+	INIT_WORK(&chip->work, gp2ap002_work);
+	mutex_init(&chip->lock);
+
+	if (chip->power_enable)
+		chip->power_enable(0);
+
+	gp2ap002_set_mode(chip, pdata->enable);
+
+	return 0;
+
+error_input:
+	sysfs_remove_group(&client->dev.kobj, &ambient_group);
+error_sysfs2:
+	sysfs_remove_group(&client->dev.kobj, &proximity_group);
+error_sysfs1:
+	hwmon_device_unregister(chip->dev);
+error_hwmon:
+	i2c_set_clientdata(client, NULL);
+	kfree(chip);
+	return ret;
+}
+
+static int __exit gp2ap002_remove(struct i2c_client *client)
+{
+	struct gp2ap002_chip *chip = i2c_get_clientdata(client);
+
+	gp2ap002_unregister_input_device(chip);
+	hwmon_device_unregister(chip->dev);
+
+	sysfs_remove_group(&client->dev.kobj, &proximity_group);
+	sysfs_remove_group(&client->dev.kobj, &ambient_group);
+
+	i2c_set_clientdata(client, NULL);
+
+	if (chip->power_enable)
+		chip->power_enable(0);
+
+	kfree(chip);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int gp2ap002_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct gp2ap002_chip *chip = i2c_get_clientdata(client);
+	gp2ap002_set_mode(chip, 0);
+
+	return 0;
+}
+
+static int gp2ap002_resume(struct i2c_client *client)
+{
+	struct gp2ap002_chip *chip = i2c_get_clientdata(client);
+	gp2ap002_set_mode(chip, 1);
+
+	return 0;
+}
+
+#else
+
+#define gp2ap002_suspend NULL
+#define gp2ap002_resume NULL
+
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id gp2ap002_id[] = {
+	{ "GP2AP002", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, gp2ap002_id);
+
+static struct i2c_driver gp2ap002_i2c_driver = {
+	.driver = {
+		.name	= "GP2AP002",
+	},
+	.probe		= gp2ap002_probe,
+	.remove		= __exit_p(gp2ap002_remove),
+	.suspend	= gp2ap002_suspend,
+	.resume		= gp2ap002_resume,
+	.id_table	= gp2ap002_id,
+};
+
+static int __init gp2ap002_init(void)
+{
+	return i2c_add_driver(&gp2ap002_i2c_driver);
+}
+module_init(gp2ap002_init);
+
+static void __exit gp2ap002_exit(void)
+{
+	i2c_del_driver(&gp2ap002_i2c_driver);
+}
+module_exit(gp2ap002_exit);
+
+MODULE_AUTHOR("Minkyu Kang <mk7.kang at samsung.com>");
+MODULE_DESCRIPTION("GP2AP002 Proximity/Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/gp2ap002.h b/include/linux/gp2ap002.h
new file mode 100644
index 0000000..b6a950b
--- /dev/null
+++ b/include/linux/gp2ap002.h
@@ -0,0 +1,21 @@
+/*
+ *  Copyright (C) 2009 Samsung Electronics
+ *  Minkyu Kang <mk7.kang at samsung.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.
+ */
+
+#ifndef __GP2AP002_H_
+#define __GP2AP002_H_
+
+struct gp2ap002_platform_data {
+	int (*get_adc)(void);
+	void (*power_enable)(int);
+	int vo_gpio;
+	int prox_mode;
+	int enable;
+};
+
+#endif
-- 
1.5.4.3



[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux