2009-06-26 08:39:18

by Minkyu Kang

[permalink] [raw]
Subject: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

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

Signed-off-by: Minkyu Kang <[email protected]>
Signed-off-by: Suchang Woo <[email protected]>
---
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 <[email protected]>
+ * Suchang Woo <[email protected]>
+ * Kyungmin Park <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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


2009-06-26 09:19:34

by Jaswinder Singh Rajput

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

On Fri, 2009-06-26 at 17:39 +0900, Minkyu Kang wrote:
> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
> This patch adds support the GP2A002 sensor
>
> Signed-off-by: Minkyu Kang <[email protected]>
> Signed-off-by: Suchang Woo <[email protected]>
> ---
> 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/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 <[email protected]>
> + *
> + * 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

I am curious what is the use of include/linux/gp2ap002.h, it only have
gp2ap002_platform_data structure which you can keep in your source file
the only file which is using it.

Thanks,
--
JSR

2009-06-26 09:31:20

by Jean Delvare

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

On Fri, 26 Jun 2009 17:39:02 +0900, Minkyu Kang wrote:
> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
> This patch adds support the GP2A002 sensor
>
> Signed-off-by: Minkyu Kang <[email protected]>
> Signed-off-by: Suchang Woo <[email protected]>
> ---
> 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.
> +

This doesn't have anything to do with hardware monitoring, so this
doesn't belong to drivers/hwmon.

--
Jean Delvare

2009-06-26 09:51:33

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

On Fri, Jun 26, 2009 at 02:46:00PM +0530, Jaswinder Singh Rajput wrote:

> I am curious what is the use of include/linux/gp2ap002.h, it only have
> gp2ap002_platform_data structure which you can keep in your source file
> the only file which is using it.

Platform data is data which is passed in from the board-specific code
for the system (which will live under arch) to configure the driver.

2009-06-26 10:22:32

by Trilok Soni

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

Hi Minkyu,

Adding linux-input ML, as driver is using linux input subsystem.

On Fri, Jun 26, 2009 at 2:09 PM, Minkyu Kang<[email protected]> wrote:
> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
> This patch adds support the GP2A002 sensor
>
> Signed-off-by: Minkyu Kang <[email protected]>
> Signed-off-by: Suchang Woo <[email protected]>
> ---
> ?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 <[email protected]>
> + * ?Suchang Woo <[email protected]>
> + * ?Kyungmin Park <[email protected]>
> + *
> + * 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);

Please see comment below.

> +}
> +
> +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";

maybe idev->phys also good to add.

> + ? ? ? 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);

Please put idev=NULL after input_unregister_device call, as idev is
ref-counted and input_free_device is not required after
input_unregister_device.

> +error_alloc:
> + ? ? ? return ret;
> +}
> +
> +
> +static int gp2ap002_probe(struct i2c_client *client,
> + ? ? ? ? ? ? ? ? ? ? ? ? const struct i2c_device_id *id)
> +{


__devinit.

> + ? ? ? 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);
> +

cancel_work_sync ?

> + ? ? ? 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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
> --
> To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
> the body of a message to [email protected]
> More majordomo info at ?http://vger.kernel.org/majordomo-info.html
>



--
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni

2009-06-28 23:49:20

by Minkyu Kang

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

Hi Trilok,

2009/6/26 Trilok Soni <[email protected]>:
> Hi Minkyu,
>
> Adding linux-input ML, as driver is using linux input subsystem.
>
> On Fri, Jun 26, 2009 at 2:09 PM, Minkyu Kang<[email protected]> wrote:
>> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
>> This patch adds support the GP2A002 sensor
>>
>> Signed-off-by: Minkyu Kang <[email protected]>
>> Signed-off-by: Suchang Woo <[email protected]>
>> ---
>> ?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
>>

Thanks for review and I'll make new version as you said : )

--
from. prom.
http://www.promsoft.net

2009-06-29 10:47:33

by Minkyu Kang

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

Hi Jean,

2009/6/26 Jean Delvare <[email protected]>:
> On Fri, 26 Jun 2009 17:39:02 +0900, Minkyu Kang wrote:
>> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
>> This patch adds support the GP2A002 sensor
>>
>> Signed-off-by: Minkyu Kang <[email protected]>
>> Signed-off-by: Suchang Woo <[email protected]>
>> ---
>> ?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.
>> +
>
> This doesn't have anything to do with hardware monitoring, so this
> doesn't belong to drivers/hwmon.
>

Can you please explain about belong to hardware monitoring?
did i missing something?

many thanks :)

--
from. prom.
http://www.promsoft.net

2009-06-29 12:54:40

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

On Fri 2009-06-26 17:39:02, Minkyu Kang wrote:
> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
> This patch adds support the GP2A002 sensor
>
> Signed-off-by: Minkyu Kang <[email protected]>
> Signed-off-by: Suchang Woo <[email protected]>

It would be nice to document what it does and what interface to
userland it provides...

--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

2009-06-29 16:47:18

by Jean Delvare

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

On Mon, 29 Jun 2009 19:47:26 +0900, Minkyu Kang wrote:
> Hi Jean,
>
> 2009/6/26 Jean Delvare <[email protected]>:
> > On Fri, 26 Jun 2009 17:39:02 +0900, Minkyu Kang wrote:
> >> The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
> >> This patch adds support the GP2A002 sensor
> >>
> >> Signed-off-by: Minkyu Kang <[email protected]>
> >> Signed-off-by: Suchang Woo <[email protected]>
> >> ---
> >> ?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.
> >> +
> >
> > This doesn't have anything to do with hardware monitoring, so this
> > doesn't belong to drivers/hwmon.
> >
>
> Can you please explain about belong to hardware monitoring?
> did i missing something?

I'm not sure how I can explain it better. Your driver is for a device
which is no a hardware monitoring chip, so this driver should not be
located in the drivers/hwmon directory.

--
Jean Delvare

2009-06-30 02:32:48

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [PATCH] hwmon: add GP2A002 proximity/ambient sensor

On Fri, Jun 26, 2009 at 03:52:21PM +0530, Trilok Soni wrote:
> Hi Minkyu,
>
> Adding linux-input ML, as driver is using linux input subsystem.
>
> On Fri, Jun 26, 2009 at 2:09 PM, Minkyu Kang<[email protected]> wrote:
> > The GP2A002 is a I2C interfaced Sensor for Proximity/Ambient
> > This patch adds support the GP2A002 sensor
> >
> > Signed-off-by: Minkyu Kang <[email protected]>
> > Signed-off-by: Suchang Woo <[email protected]>
> > ---
> > ?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 <[email protected]>
> > + * ?Suchang Woo <[email protected]>
> > + * ?Kyungmin Park <[email protected]>
> > + *
> > + * 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)
> > +{

simple_strtoul() here please.

> > + ? ? ? 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);

What exactly do we protect here? I don't see any concurrency...

> > +
> > + ? ? ? 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);
> > +

I don't see the work being shut off here. That means that it can still
try to enable freed irq or even cause oops if kevend is backed up...

Overall this driver could benefit from threaded IRQ facility I think.

> > + ? ? ? input_unregister_device(chip->idev);
> > + ? ? ? input_free_device(chip->idev);
>
> Please see comment below.
>
> > +}
> > +
> > +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";
>
> maybe idev->phys also good to add.
>
> > + ? ? ? 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);
>
> Please put idev=NULL after input_unregister_device call, as idev is
> ref-counted and input_free_device is not required after
> input_unregister_device.
>
> > +error_alloc:
> > + ? ? ? return ret;
> > +}
> > +
> > +
> > +static int gp2ap002_probe(struct i2c_client *client,
> > + ? ? ? ? ? ? ? ? ? ? ? ? const struct i2c_device_id *id)
> > +{
>
>
> __devinit.
>
> > + ? ? ? 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);

We have already registered IRQ but only initializing work here, this
makes me uneasy... Are we certain that chip is disabled and will not
raise IRQ here?

> > + ? ? ? 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)

__devexit.

> > +{
> > + ? ? ? 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);
> > +
>
> cancel_work_sync ?
>
> > + ? ? ? 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),

__devexit_p()

> > + ? ? ? .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 <[email protected]>");
> > +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 <[email protected]>
> > + *
> > + * 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
> > --
--
Dmitry