hwmon: lis3: pm_runtime support
[deliverable/linux.git] / drivers / hwmon / lis3lv02d.c
index fc591ae53107da8481a2ab5e02f53117ac6e2471..383a84938a981bd4663a5770d3f54624bcfa2b7e 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/freezer.h>
 #include <linux/uaccess.h>
 #include <linux/miscdevice.h>
+#include <linux/pm_runtime.h>
 #include <asm/atomic.h>
 #include "lis3lv02d.h"
 
@@ -43,6 +44,9 @@
 #define MDPS_POLL_INTERVAL 50
 #define MDPS_POLL_MIN     0
 #define MDPS_POLL_MAX     2000
+
+#define LIS3_SYSFS_POWERDOWN_DELAY 5000 /* In milliseconds */
+
 /*
  * The sensor can also generate interrupts (DRDY) but it's pretty pointless
  * because they are generated even if the data do not change. So it's better
@@ -75,6 +79,30 @@ struct lis3lv02d lis3_dev = {
 
 EXPORT_SYMBOL_GPL(lis3_dev);
 
+/* just like param_set_int() but does sanity-check so that it won't point
+ * over the axis array size
+ */
+static int param_set_axis(const char *val, const struct kernel_param *kp)
+{
+       int ret = param_set_int(val, kp);
+       if (!ret) {
+               int val = *(int *)kp->arg;
+               if (val < 0)
+                       val = -val;
+               if (!val || val > 3)
+                       return -EINVAL;
+       }
+       return ret;
+}
+
+static struct kernel_param_ops param_ops_axis = {
+       .set = param_set_axis,
+       .get = param_get_int,
+};
+
+module_param_array_named(axes, lis3_dev.ac.as_array, axis, NULL, 0644);
+MODULE_PARM_DESC(axes, "Axis-mapping for x,y,z directions");
+
 static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg)
 {
        s8 lo;
@@ -138,6 +166,7 @@ static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z)
 /* conversion btw sampling rate and the register values */
 static int lis3_12_rates[4] = {40, 160, 640, 2560};
 static int lis3_8_rates[2] = {100, 400};
+static int lis3_3dc_rates[16] = {0, 1, 10, 25, 50, 100, 200, 400, 1600, 5000};
 
 /* ODR is Output Data Rate */
 static int lis3lv02d_get_odr(void)
@@ -156,6 +185,9 @@ static int lis3lv02d_set_odr(int rate)
        u8 ctrl;
        int i, len, shift;
 
+       if (!rate)
+               return -EINVAL;
+
        lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl);
        ctrl &= ~lis3_dev.odr_mask;
        len = 1 << hweight_long(lis3_dev.odr_mask); /* # of possible values */
@@ -172,19 +204,25 @@ static int lis3lv02d_set_odr(int rate)
 
 static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3])
 {
-       u8 reg;
+       u8 ctlreg, reg;
        s16 x, y, z;
        u8 selftest;
        int ret;
 
        mutex_lock(&lis3->mutex);
-       if (lis3_dev.whoami == WAI_12B)
-               selftest = CTRL1_ST;
-       else
-               selftest = CTRL1_STP;
+       if (lis3_dev.whoami == WAI_3DC) {
+               ctlreg = CTRL_REG4;
+               selftest = CTRL4_ST0;
+       } else {
+               ctlreg = CTRL_REG1;
+               if (lis3_dev.whoami == WAI_12B)
+                       selftest = CTRL1_ST;
+               else
+                       selftest = CTRL1_STP;
+       }
 
-       lis3->read(lis3, CTRL_REG1, &reg);
-       lis3->write(lis3, CTRL_REG1, (reg | selftest));
+       lis3->read(lis3, ctlreg, &reg);
+       lis3->write(lis3, ctlreg, (reg | selftest));
        msleep(lis3->pwron_delay / lis3lv02d_get_odr());
 
        /* Read directly to avoid axis remap */
@@ -193,7 +231,7 @@ static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3])
        z = lis3->read_data(lis3, OUTZ);
 
        /* back to normal settings */
-       lis3->write(lis3, CTRL_REG1, reg);
+       lis3->write(lis3, ctlreg, reg);
        msleep(lis3->pwron_delay / lis3lv02d_get_odr());
 
        results[0] = x - lis3->read_data(lis3, OUTX);
@@ -262,6 +300,18 @@ static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev)
        mutex_unlock(&lis3_dev.mutex);
 }
 
+static void lis3lv02d_joystick_open(struct input_polled_dev *pidev)
+{
+       if (lis3_dev.pm_dev)
+               pm_runtime_get_sync(lis3_dev.pm_dev);
+}
+
+static void lis3lv02d_joystick_close(struct input_polled_dev *pidev)
+{
+       if (lis3_dev.pm_dev)
+               pm_runtime_put(lis3_dev.pm_dev);
+}
+
 static irqreturn_t lis302dl_interrupt(int irq, void *dummy)
 {
        if (!test_bit(0, &lis3_dev.misc_opened))
@@ -356,6 +406,9 @@ static int lis3lv02d_misc_open(struct inode *inode, struct file *file)
        if (test_and_set_bit(0, &lis3_dev.misc_opened))
                return -EBUSY; /* already open */
 
+       if (lis3_dev.pm_dev)
+               pm_runtime_get_sync(lis3_dev.pm_dev);
+
        atomic_set(&lis3_dev.count, 0);
        return 0;
 }
@@ -364,6 +417,8 @@ static int lis3lv02d_misc_release(struct inode *inode, struct file *file)
 {
        fasync_helper(-1, file, 0, &lis3_dev.async_queue);
        clear_bit(0, &lis3_dev.misc_opened); /* release the device */
+       if (lis3_dev.pm_dev)
+               pm_runtime_put(lis3_dev.pm_dev);
        return 0;
 }
 
@@ -460,6 +515,8 @@ int lis3lv02d_joystick_enable(void)
                return -ENOMEM;
 
        lis3_dev.idev->poll = lis3lv02d_joystick_poll;
+       lis3_dev.idev->open = lis3lv02d_joystick_open;
+       lis3_dev.idev->close = lis3lv02d_joystick_close;
        lis3_dev.idev->poll_interval = MDPS_POLL_INTERVAL;
        lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN;
        lis3_dev.idev->poll_interval_max = MDPS_POLL_MAX;
@@ -512,12 +569,30 @@ void lis3lv02d_joystick_disable(void)
 EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable);
 
 /* Sysfs stuff */
+static void lis3lv02d_sysfs_poweron(struct lis3lv02d *lis3)
+{
+       /*
+        * SYSFS functions are fast visitors so put-call
+        * immediately after the get-call. However, keep
+        * chip running for a while and schedule delayed
+        * suspend. This way periodic sysfs calls doesn't
+        * suffer from relatively long power up time.
+        */
+
+       if (lis3->pm_dev) {
+               pm_runtime_get_sync(lis3->pm_dev);
+               pm_runtime_put_noidle(lis3->pm_dev);
+               pm_schedule_suspend(lis3->pm_dev, LIS3_SYSFS_POWERDOWN_DELAY);
+       }
+}
+
 static ssize_t lis3lv02d_selftest_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
 {
        int result;
        s16 values[3];
 
+       lis3lv02d_sysfs_poweron(&lis3_dev);
        result = lis3lv02d_selftest(&lis3_dev, values);
        return sprintf(buf, "%s %d %d %d\n", result == 0 ? "OK" : "FAIL",
                values[0], values[1], values[2]);
@@ -528,6 +603,7 @@ static ssize_t lis3lv02d_position_show(struct device *dev,
 {
        int x, y, z;
 
+       lis3lv02d_sysfs_poweron(&lis3_dev);
        mutex_lock(&lis3_dev.mutex);
        lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z);
        mutex_unlock(&lis3_dev.mutex);
@@ -537,6 +613,7 @@ static ssize_t lis3lv02d_position_show(struct device *dev,
 static ssize_t lis3lv02d_rate_show(struct device *dev,
                        struct device_attribute *attr, char *buf)
 {
+       lis3lv02d_sysfs_poweron(&lis3_dev);
        return sprintf(buf, "%d\n", lis3lv02d_get_odr());
 }
 
@@ -549,6 +626,7 @@ static ssize_t lis3lv02d_rate_set(struct device *dev,
        if (strict_strtoul(buf, 0, &rate))
                return -EINVAL;
 
+       lis3lv02d_sysfs_poweron(&lis3_dev);
        if (lis3lv02d_set_odr(rate))
                return -EINVAL;
 
@@ -585,6 +663,17 @@ int lis3lv02d_remove_fs(struct lis3lv02d *lis3)
 {
        sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group);
        platform_device_unregister(lis3->pdev);
+       if (lis3->pm_dev) {
+               /* Barrier after the sysfs remove */
+               pm_runtime_barrier(lis3->pm_dev);
+
+               /* SYSFS may have left chip running. Turn off if necessary */
+               if (!pm_runtime_suspended(lis3->pm_dev))
+                       lis3lv02d_poweroff(&lis3_dev);
+
+               pm_runtime_disable(lis3->pm_dev);
+               pm_runtime_set_suspended(lis3->pm_dev);
+       }
        return 0;
 }
 EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs);
@@ -674,6 +763,15 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
                dev->odr_mask = CTRL1_DR;
                dev->scale = LIS3_SENSITIVITY_8B;
                break;
+       case WAI_3DC:
+               printk(KERN_INFO DRIVER_NAME ": 8 bits 3DC sensor found\n");
+               dev->read_data = lis3lv02d_read_8;
+               dev->mdps_max_val = 128;
+               dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B;
+               dev->odrs = lis3_3dc_rates;
+               dev->odr_mask = CTRL1_ODR0|CTRL1_ODR1|CTRL1_ODR2|CTRL1_ODR3;
+               dev->scale = LIS3_SENSITIVITY_8B;
+               break;
        default:
                printk(KERN_ERR DRIVER_NAME
                        ": unknown sensor type 0x%X\n", dev->whoami);
@@ -685,6 +783,11 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
        lis3lv02d_add_fs(dev);
        lis3lv02d_poweron(dev);
 
+       if (dev->pm_dev) {
+               pm_runtime_set_active(dev->pm_dev);
+               pm_runtime_enable(dev->pm_dev);
+       }
+
        if (lis3lv02d_joystick_enable())
                printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n");
 
This page took 0.028513 seconds and 5 git commands to generate.