misc: Initial MAX9635 ALS driver
authorDan Murphy <wldm10@motorola.com>
Wed, 26 May 2010 19:56:47 +0000 (14:56 -0500)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:32:59 +0000 (16:32 -0700)
The MAXIM 9635 IC is used for ALS sensing.  This will report ambient
light data in LUX.

Change-Id: I93728eb804d748145fb17524cfd328c923f33eb3
Signed-off-by: Dan Murphy <wldm10@motorola.com>
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/max9635.c [new file with mode: 0644]
include/linux/max9635.h [new file with mode: 0644]

index 0a90848940887725af65ef4b810565c306465184..0b5de4bb2fad87931c5ad8a3b5f5b3a4acee17d8 100644 (file)
@@ -348,6 +348,14 @@ config SENSORS_KXTF9
         Say yes here if you wish to include the Kionix
         KXTF9 accelerometer driver.
 
+config SENSORS_MAX9635
+       tristate "Maxim Ambient Light Sensor"
+       default n
+       depends on I2C
+       help
+        Say yes here if you wish to include the Maxim
+        MAX9635 ambient light sensor driver.
+
 config EP93XX_PWM
        tristate "EP93xx PWM support"
        depends on ARCH_EP93XX
index a6b987293abae582a9149c044aaa18d37baa8aac..9b68f3d5581131f9baf215b3ebb9ef25ff02ffc1 100644 (file)
@@ -42,3 +42,4 @@ obj-$(CONFIG_WL127X_RFKILL)   += wl127x-rfkill.o
 obj-$(CONFIG_APANIC)           += apanic.o
 obj-$(CONFIG_SENSORS_AK8975)   += akm8975.o
 obj-$(CONFIG_SENSORS_KXTF9)    += kxtf9.o
+obj-$(CONFIG_SENSORS_MAX9635)  += max9635.o
diff --git a/drivers/misc/max9635.c b/drivers/misc/max9635.c
new file mode 100644 (file)
index 0000000..df943c1
--- /dev/null
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#include <linux/delay.h>
+#include <linux/earlysuspend.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/leds.h>
+#include <linux/max9635.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+#define DEBUG  1
+
+#define MAX9635_ALLOWED_R_BYTES 1
+#define MAX9635_ALLOWED_W_BYTES 2
+#define MAX9635_MAX_RW_RETRIES 5
+#define MAX9635_I2C_RETRY_DELAY 10
+#define AUTO_INCREMENT          0x0
+
+#define MAX9635_INT_STATUS     0x00
+#define MAX9635_INT_EN         0x01
+#define MAX9635_CONFIGURE      0x02
+#define MAX9635_ALS_DATA_H     0x03
+#define MAX9635_ALS_DATA_L     0x04
+#define MAX9635_ALS_THRESH_H   0x05
+#define MAX9635_ALS_THRESH_L   0x06
+#define MAX9635_THRESH_TIMER   0x07
+
+struct max9635_zone_conv {
+       int lower_threshold;
+       int upper_threshold;
+};
+
+struct max9635_data {
+       struct input_dev *idev;
+       struct i2c_client *client;
+       struct delayed_work working_queue;
+       struct max9635_platform_data *als_pdata;
+       struct early_suspend early_suspend;
+       struct max9635_zone_conv max9635_zone_info[255];
+       atomic_t enabled;
+       int irq;
+};
+
+struct max9635_data *max9635_misc_data;
+
+#ifdef DEBUG
+struct max9635_reg {
+       const char *name;
+       uint8_t reg;
+} max9635_regs[] = {
+       {"INT_STATUS",          MAX9635_INT_STATUS},
+       {"INT_ENABLE",          MAX9635_INT_EN},
+       {"CONFIG",              MAX9635_CONFIGURE},
+       {"ALS_DATA_HIGH",       MAX9635_ALS_DATA_H},
+       {"ALS_DATA_LOW",        MAX9635_ALS_DATA_L},
+       {"ALS_THRESH_H",        MAX9635_ALS_THRESH_H},
+       {"ALS_THRESH_L",        MAX9635_ALS_THRESH_L},
+       {"ALS_THRESH_TIMER",    MAX9635_THRESH_TIMER},
+};
+#endif
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void max9635_early_suspend(struct early_suspend *handler);
+static void max9635_late_resume(struct early_suspend *handler);
+#endif
+
+static uint32_t max9635_debug = 0xff;
+module_param_named(als_debug, max9635_debug, uint, 0664);
+
+static int max9635_read_reg(struct max9635_data *als_data, u8 * buf, int len)
+{
+       int err;
+       int tries = 0;
+       struct i2c_msg msgs[] = {
+               {
+                .addr = als_data->client->addr,
+                .flags = als_data->client->flags & I2C_M_TEN,
+                .len = 1,
+                .buf = buf,
+                },
+               {
+                .addr = als_data->client->addr,
+                .flags = (als_data->client->flags & I2C_M_TEN) | I2C_M_RD,
+                .len = len,
+                .buf = buf,
+                },
+       };
+
+       do {
+               err = i2c_transfer(als_data->client->adapter, msgs, 2);
+               if (err != 2)
+                       msleep_interruptible(MAX9635_I2C_RETRY_DELAY);
+       } while ((err != 2) && (++tries < MAX9635_MAX_RW_RETRIES));
+
+       if (err != 2) {
+               pr_err("%s:read transfer error\n", __func__);
+               err = -EIO;
+       } else {
+               err = 0;
+       }
+
+       return err;
+}
+
+static int max9635_write_reg(struct max9635_data *als_data, u8 * buf, int len)
+{
+       int err;
+       int tries = 0;
+       struct i2c_msg msgs[] = {
+               {
+                .addr = als_data->client->addr,
+                .flags = als_data->client->flags & I2C_M_TEN,
+                .len = len + 1,
+                .buf = buf,
+                },
+       };
+
+       do {
+               err = i2c_transfer(als_data->client->adapter, msgs, 1);
+               if (err != 1)
+                       msleep_interruptible(MAX9635_I2C_RETRY_DELAY);
+       } while ((err != 1) && (++tries < MAX9635_MAX_RW_RETRIES));
+
+       if (err != 1) {
+               pr_err("%s:write transfer error\n", __func__);
+               err = -EIO;
+       } else {
+               err = 0;
+       }
+
+       return err;
+}
+
+static int max9635_init_registers(struct max9635_data *als_data)
+{
+       u8 buf[2];
+
+       buf[0] = (AUTO_INCREMENT | MAX9635_CONFIGURE);
+       buf[1] = als_data->als_pdata->configure;
+       if (max9635_write_reg(als_data, buf, 1))
+               goto init_failed;
+
+       buf[0] = (AUTO_INCREMENT | MAX9635_ALS_THRESH_H);
+       buf[1] = als_data->als_pdata->def_high_threshold;
+       if (max9635_write_reg(als_data, buf, 1))
+               goto init_failed;
+
+       buf[0] = (AUTO_INCREMENT | MAX9635_ALS_THRESH_L);
+       buf[1] = als_data->als_pdata->def_low_threshold;
+       if (max9635_write_reg(als_data, buf, 1))
+               goto init_failed;
+
+       buf[0] = (AUTO_INCREMENT | MAX9635_THRESH_TIMER);
+       buf[1] = als_data->als_pdata->threshold_timer;
+       if (max9635_write_reg(als_data, buf, 1))
+               goto init_failed;
+
+       buf[0] = (AUTO_INCREMENT | MAX9635_INT_EN);
+       buf[1] = 0x01;
+       if (max9635_write_reg(als_data, buf, 1))
+               goto init_failed;
+
+       return 0;
+
+init_failed:
+       pr_err("%s:Register 0x%d initialization failed\n", __func__, buf[0]);
+       return -EINVAL;
+}
+
+static irqreturn_t max9635_irq_handler(int irq, void *dev)
+{
+       struct max9635_data *als_data = dev;
+
+       disable_irq_nosync(als_data->client->irq);
+       schedule_delayed_work(&als_data->working_queue, 0);
+
+
+       return IRQ_HANDLED;
+}
+
+static int max9635_read_adj_als(struct max9635_data *als_data)
+{
+       int ret;
+       int i;
+       int lux = 0;
+       u8 buf[2] = { MAX9635_ALS_DATA_H, 0 };
+       u8 exponent;
+       u16 mantissa;
+
+       ret = max9635_read_reg(als_data, buf, 2);
+       if (ret != 0) {
+               pr_err("%s:Unable to read interrupt register: %d\n",
+                      __func__, ret);
+               return -1;
+       }
+
+       exponent = (buf[0] & 0xf0) >> 4;
+       mantissa = ((buf[0] & 0x0f) << 8) + buf[1];
+       /* lux = 2^exponent * mantissa / 32 */
+       lux = (mantissa << exponent) >> 5;
+       /* TO DO: Need to include lens loss coeffcient to achieve closer to
+          absolute lux value
+          if (als_data->als_pdata->lens_percent_t)
+          lux = ((10000 / als_data->als_pdata->lens_percent_t)*
+          (als_read_data)) / 100; */
+
+       for (i = 0; i < als_data->als_pdata->num_of_zones; i++) {
+               if ((lux <= als_data->max9635_zone_info[i].upper_threshold)
+                       && (lux >= als_data->max9635_zone_info[i].lower_threshold)) {
+                       if (max9635_debug & 1)
+                               pr_info("%s:Setting next window to %i\n",
+                                       __func__, i);
+
+                       buf[0] = (AUTO_INCREMENT | MAX9635_ALS_THRESH_L);
+                       buf[1] =
+                           als_data->als_pdata->als_lux_table[i].als_lower_threshold;
+                       ret = max9635_write_reg(als_data, buf, 1);
+                       if (ret != 0) {
+                               pr_err("%s:Unable to write reg: %d\n",
+                                      __func__, buf[0]);
+                               return -1;
+                       }
+                       buf[0] = (AUTO_INCREMENT | MAX9635_ALS_THRESH_H);
+                       buf[1] =
+                           als_data->als_pdata->als_lux_table[i].als_higher_threshold;
+                       ret = max9635_write_reg(als_data, buf, 1);
+                       if (ret != 0) {
+                               pr_err("%s:Unable to write reg: %d\n",
+                                      __func__, buf[0]);
+                               return -1;
+                       }
+               }
+       }
+       if (max9635_debug & 1)
+               pr_info("%s:Reporting LUX %d\n", __func__, lux);
+       return lux;
+}
+
+/* TO DO: Do we need to read the interrupt to clear the bit?
+Spec indicates that a read needs to be done to confirm it was this
+IC but does not indicate whether it is mandatory */
+static int max9635_report_input(struct max9635_data *als_data)
+{
+       int ret = 0;
+       int lux_val;
+       u8 buf[2] = { MAX9635_INT_STATUS, 0x00 };
+
+       lux_val = max9635_read_adj_als(als_data);
+       if (lux_val >= 0) {
+               input_event(als_data->idev, EV_LED, LED_MISC, lux_val);
+               input_sync(als_data->idev);
+       }
+
+       /* Clear the interrupt status register */
+       ret = max9635_read_reg(als_data, buf, 1);
+       if (ret != 0) {
+               pr_err("%s:Unable to read interrupt register: %d\n",
+                      __func__, ret);
+               return -1;
+       }
+       enable_irq(als_data->client->irq);
+       return ret;
+}
+
+static int max9635_device_power(struct max9635_data *als_data, u8 state)
+{
+       int err;
+       u8 buf[2] = { (AUTO_INCREMENT | MAX9635_INT_EN) };
+
+       buf[1] = state;
+       err = max9635_write_reg(als_data, buf, 1);
+       if (err)
+               pr_err("%s:Unable to turn off prox: %d\n", __func__, err);
+
+       return err;
+}
+
+static int max9635_enable(struct max9635_data *als_data)
+{
+       int err;
+
+       if (!atomic_cmpxchg(&als_data->enabled, 0, 1)) {
+               err = max9635_device_power(als_data, 0x01);
+               if (err) {
+                       atomic_set(&als_data->enabled, 0);
+                       return err;
+               }
+       }
+       return 0;
+}
+
+static int max9635_disable(struct max9635_data *als_data)
+{
+       if (atomic_cmpxchg(&als_data->enabled, 1, 0))
+               max9635_device_power(als_data, 0x00);
+       cancel_delayed_work_sync(&als_data->working_queue);
+
+       return 0;
+}
+
+static int max9635_misc_open(struct inode *inode, struct file *file)
+{
+       int err;
+       err = nonseekable_open(inode, file);
+       if (err < 0)
+               return err;
+
+       file->private_data = max9635_misc_data;
+
+       return 0;
+}
+
+static int max9635_misc_ioctl(struct inode *inode, struct file *file,
+                             unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       u8 enable;
+       struct max9635_data *als_data = file->private_data;
+
+       switch (cmd) {
+       case MAX9635_IOCTL_SET_ENABLE:
+               if (copy_from_user(&enable, argp, 1))
+                       return -EFAULT;
+               if (enable > 1)
+                       return -EINVAL;
+
+               if (enable != 0)
+                       max9635_enable(als_data);
+               else
+                       max9635_disable(als_data);
+
+               break;
+
+       case MAX9635_IOCTL_GET_ENABLE:
+               enable = atomic_read(&als_data->enabled);
+               if (copy_to_user(argp, &enable, 1))
+                       return -EINVAL;
+
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct file_operations max9635_misc_fops = {
+       .owner = THIS_MODULE,
+       .open = max9635_misc_open,
+       .ioctl = max9635_misc_ioctl,
+};
+
+static struct miscdevice max9635_misc_device = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = FOPS_MAX9635_NAME,
+       .fops = &max9635_misc_fops,
+};
+#ifdef DEBUG
+static ssize_t max9635_registers_show(struct device *dev,
+                                     struct device_attribute *attr, char *buf)
+{
+       struct i2c_client *client = container_of(dev, struct i2c_client,
+                                                dev);
+       struct max9635_data *als_data = i2c_get_clientdata(client);
+       unsigned i, n, reg_count;
+       u8 als_reg[2];
+
+       reg_count = sizeof(max9635_regs) / sizeof(max9635_regs[0]);
+       for (i = 0, n = 0; i < reg_count; i++) {
+               als_reg[0] = (AUTO_INCREMENT | max9635_regs[i].reg);
+               max9635_read_reg(als_data, als_reg, 1);
+               n += scnprintf(buf + n, PAGE_SIZE - n,
+                              "%-20s = 0x%02X\n",
+                              max9635_regs[i].name, als_reg[0]);
+       }
+
+       return n;
+}
+
+static ssize_t max9635_registers_store(struct device *dev,
+                                      struct device_attribute *attr,
+                                      const char *buf, size_t count)
+{
+       struct i2c_client *client = container_of(dev, struct i2c_client,
+                                                dev);
+       struct max9635_data *als_data = i2c_get_clientdata(client);
+       unsigned i, reg_count, value;
+       int error;
+       u8 als_reg[2];
+       char name[30];
+
+       if (count >= 30) {
+               pr_err("%s:input too long\n", __func__);
+               return -1;
+       }
+
+       if (sscanf(buf, "%s %x", name, &value) != 2) {
+               pr_err("%s:unable to parse input\n", __func__);
+               return -1;
+       }
+
+       reg_count = sizeof(max9635_regs) / sizeof(max9635_regs[0]);
+       for (i = 0; i < reg_count; i++) {
+               if (!strcmp(name, max9635_regs[i].name)) {
+                       als_reg[0] = (AUTO_INCREMENT | max9635_regs[i].reg);
+                       als_reg[1] = value;
+                       error = max9635_write_reg(als_data, als_reg, 1);
+                       if (error) {
+                               pr_err("%s:Failed to write register %s\n",
+                                      __func__, name);
+                               return -1;
+                       }
+                       return count;
+               }
+       }
+       if (!strcmp("Go", name)) {
+               max9635_enable(als_data);
+               return 0;
+       }
+       if (!strcmp("Stop", name)) {
+               max9635_disable(als_data);
+               return 0;
+       }
+       pr_err("%s:no such register %s\n", __func__, name);
+       return -1;
+}
+
+static DEVICE_ATTR(registers, 0644, max9635_registers_show,
+                  max9635_registers_store);
+#endif
+
+static void max9635_work_queue(struct work_struct *work)
+{
+       struct max9635_data *als_data = container_of((struct delayed_work *)work,
+               struct max9635_data, working_queue);
+
+       max9635_report_input(als_data);
+}
+
+static void max9635_convert_zones(struct max9635_data *als_data)
+{
+       int i = 0;
+       int lux = 0;
+       u8 exponent;
+       u8 mantissa;
+
+       /* Convert the byte to a lux value based
+       on the equation in the data sheet */
+       for (i = 0; i < als_data->als_pdata->num_of_zones; i++) {
+               exponent =
+                   (als_data->als_pdata->als_lux_table[i].als_lower_threshold & 0xf0) >> 4;
+               mantissa =
+                   ((als_data->als_pdata->als_lux_table[i].als_lower_threshold & 0x0f) << 4);
+               /* lux = 2^exponent * mantissa / 32 */
+               lux = (mantissa << exponent) >> 5;
+               als_data->max9635_zone_info[i].lower_threshold = lux;
+
+               exponent =
+                   (als_data->als_pdata->als_lux_table[i].
+                    als_higher_threshold & 0xf0) >> 4;
+               mantissa =
+                   ((als_data->als_pdata->als_lux_table[i].als_higher_threshold & 0x0f) << 4) | 0x0f;
+               /* lux = 2^exponent * mantissa / 32 */
+               lux = (mantissa << exponent) >> 5;
+               als_data->max9635_zone_info[i].upper_threshold = lux;
+
+               pr_info("%s:Element %i Upper %d Lower %d\n", __func__, i,
+                      als_data->max9635_zone_info[i].upper_threshold,
+                      als_data->max9635_zone_info[i].lower_threshold);
+       }
+       return;
+}
+
+static int max9635_probe(struct i2c_client *client,
+                        const struct i2c_device_id *id)
+{
+       struct max9635_platform_data *pdata = client->dev.platform_data;
+       struct max9635_data *als_data;
+       int error = 0;
+
+       if (pdata == NULL) {
+               pr_err("%s: platform data required\n", __func__);
+               return -ENODEV;
+       } else if (!client->irq) {
+               pr_err("%s: polling mode currently not supported\n", __func__);
+               return -ENODEV;
+       }
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               pr_err("%s:I2C_FUNC_I2C not supported\n", __func__);
+               return -ENODEV;
+       }
+
+       als_data = kzalloc(sizeof(struct max9635_data), GFP_KERNEL);
+       if (als_data == NULL) {
+               error = -ENOMEM;
+               goto err_alloc_data_failed;
+       }
+
+       als_data->client = client;
+       als_data->als_pdata = pdata;
+
+       als_data->idev = input_allocate_device();
+       if (!als_data->idev) {
+               error = -ENOMEM;
+               pr_err("%s: input device allocate failed: %d\n", __func__,
+                      error);
+               goto error_input_allocate_failed;
+       }
+
+       als_data->idev->name = "als";
+       input_set_capability(als_data->idev, EV_MSC, MSC_RAW);
+       input_set_capability(als_data->idev, EV_LED, LED_MISC);
+
+       max9635_convert_zones(als_data);
+
+       error = misc_register(&max9635_misc_device);
+       if (error < 0) {
+               pr_err("%s: max9635 register failed\n", __func__);
+               goto error_misc_register_failed;
+       }
+
+       atomic_set(&als_data->enabled, 0);
+
+       INIT_DELAYED_WORK(&als_data->working_queue, max9635_work_queue);
+
+       error = input_register_device(als_data->idev);
+       if (error) {
+               pr_err("%s: input device register failed:%d\n", __func__,
+                      error);
+               goto error_input_register_failed;
+       }
+
+       error = max9635_init_registers(als_data);
+       if (error < 0) {
+               pr_err("%s: Register Initialization failed: %d\n",
+                      __func__, error);
+               error = -ENODEV;
+               goto err_reg_init_failed;
+       }
+
+       error = request_irq(als_data->client->irq, max9635_irq_handler,
+                           IRQF_TRIGGER_FALLING, MAX9635_NAME, als_data);
+       if (error != 0) {
+               pr_err("%s: irq request failed: %d\n", __func__, error);
+               error = -ENODEV;
+               goto err_req_irq_failed;
+       }
+
+       i2c_set_clientdata(client, als_data);
+
+#ifdef DEBUG
+       error = device_create_file(&als_data->client->dev, &dev_attr_registers);
+       if (error < 0) {
+               pr_err("%s:File device creation failed: %d\n", __func__, error);
+               error = -ENODEV;
+               goto err_create_registers_file_failed;
+       }
+#endif
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+       als_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+       als_data->early_suspend.suspend = max9635_early_suspend;
+       als_data->early_suspend.resume = max9635_late_resume;
+       register_early_suspend(&als_data->early_suspend);
+#endif
+       disable_irq_nosync(als_data->client->irq);
+       schedule_delayed_work(&als_data->working_queue, 0);
+
+       return 0;
+
+#ifdef DEBUG
+err_create_registers_file_failed:
+       free_irq(als_data->client->irq, als_data);
+#endif
+err_req_irq_failed:
+err_reg_init_failed:
+       input_unregister_device(als_data->idev);
+error_input_register_failed:
+error_misc_register_failed:
+       input_free_device(als_data->idev);
+error_input_allocate_failed:
+       kfree(als_data);
+err_alloc_data_failed:
+       return error;
+}
+
+static int max9635_remove(struct i2c_client *client)
+{
+       struct max9635_data *als_data = i2c_get_clientdata(client);
+#ifdef DEBUG
+       device_remove_file(&als_data->client->dev, &dev_attr_registers);
+#endif
+       free_irq(als_data->client->irq, als_data);
+       input_unregister_device(als_data->idev);
+       input_free_device(als_data->idev);
+       misc_deregister(&max9635_misc_device);
+       kfree(als_data);
+       return 0;
+}
+
+static int max9635_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+       struct max9635_data *als_data = i2c_get_clientdata(client);
+
+       if (max9635_debug)
+               pr_info("%s: Suspending\n", __func__);
+
+       cancel_delayed_work_sync(&als_data->working_queue);
+
+       if (atomic_read(&als_data->enabled) == 1)
+               max9635_disable(als_data);
+
+       return 0;
+}
+
+static int max9635_resume(struct i2c_client *client)
+{
+       struct max9635_data *als_data = i2c_get_clientdata(client);
+
+       if (max9635_debug)
+               pr_info("%s: Resuming\n", __func__);
+
+       if (atomic_read(&als_data->enabled) == 0)
+               max9635_enable(als_data);
+
+       /* Allow the ALS sensor to read the zone */
+       schedule_delayed_work(&als_data->working_queue,
+               msecs_to_jiffies(100));
+
+       return 0;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void max9635_early_suspend(struct early_suspend *handler)
+{
+       struct max9635_data *als_data;
+
+       als_data = container_of(handler, struct max9635_data, early_suspend);
+       max9635_suspend(als_data->client, PMSG_SUSPEND);
+}
+
+static void max9635_late_resume(struct early_suspend *handler)
+{
+       struct max9635_data *als_data;
+
+       als_data = container_of(handler, struct max9635_data, early_suspend);
+       max9635_resume(als_data->client);
+}
+#endif
+
+static const struct i2c_device_id max9635_id[] = {
+       {MAX9635_NAME, 0},
+       {}
+};
+
+static struct i2c_driver max9635_i2c_driver = {
+       .probe = max9635_probe,
+       .remove = max9635_remove,
+#ifndef CONFIG_HAS_EARLYSUSPEND
+       .suspend = max9635_suspend,
+       .resume = max9635_resume,
+#endif
+       .id_table = max9635_id,
+       .driver = {
+          .name = MAX9635_NAME,
+          .owner = THIS_MODULE,
+       },
+};
+
+static int __init max9635_init(void)
+{
+       return i2c_add_driver(&max9635_i2c_driver);
+}
+
+static void __exit max9635_exit(void)
+{
+       i2c_del_driver(&max9635_i2c_driver);
+}
+
+module_init(max9635_init);
+module_exit(max9635_exit);
+
+MODULE_DESCRIPTION("ALS driver for Maxim 9635");
+MODULE_AUTHOR("Dan Murphy <D.Murphy@motorola.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/max9635.h b/include/linux/max9635.h
new file mode 100644 (file)
index 0000000..f49bfc4
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#ifndef _LINUX_MAX9635_H__
+#define _LINUX_MAX9635_H__
+
+#define MAX9635_NAME "MAX9635_als"
+#define FOPS_MAX9635_NAME "MAX9635"
+
+
+#ifdef __KERNEL__
+
+struct max9635_als_zone_data {
+       int  als_lower_threshold;
+       int  als_higher_threshold;
+};
+
+struct max9635_platform_data {
+       u8      configure;
+       u8      threshold_timer;
+       u8      def_low_threshold;
+       u8      def_high_threshold;
+       u32 lens_percent_t;
+       struct max9635_als_zone_data *als_lux_table;
+       u8  num_of_zones;
+       int (*power_on)(void);
+       int (*power_off)(void);
+};
+
+#endif /* __KERNEL__ */
+
+#define MAX9635_IO                     0xA3
+
+#define MAX9635_IOCTL_GET_ENABLE       _IOR(MAX9635_IO, 0x00, char)
+#define MAX9635_IOCTL_SET_ENABLE       _IOW(MAX9635_IO, 0x01, char)
+
+#endif /* _LINUX_MAX9635_H__ */