All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Émeric Vigier" <emeric.vigier-4ysUXcep3aM1wj+D4I0NRVaTQe2KTcn/@public.gmane.org>
To: Wolfram Sang <wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Subject: Re: how to gracefully unload an i2c driver if chip not detected?
Date: Sat, 20 Apr 2013 23:38:34 -0400 (EDT)	[thread overview]
Message-ID: <228256972.786837.1366515514850.JavaMail.root@mail> (raw)
In-Reply-To: <20130409092625.GD3624-z923LK4zBo2bacvFa/9K2g@public.gmane.org>


----- Mail original -----
> De: "Wolfram Sang" <wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
> À: "Émeric Vigier" <emeric.vigier-4ysUXcep3aM1wj+D4I0NRVaTQe2KTcn/@public.gmane.org>
> Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Envoyé: Mardi 9 Avril 2013 05:26:25
> Objet: Re: how to gracefully unload an i2c driver if chip not detected?
> 
> 
> > I recently changed the sensor on my board. Leading to kernel crash
> > when entering suspend. Thanks to "no_console_suspend" cmdline
> > argument, I found out that the suspend function of the "absent"
> > chip
> > gets called. It tries to take a mutex which has been freed in
> > probe's
> > "device not found" fallback code. Leading to kernel panic.
> 
> Can we have the full driver? The snipplet is not enough.

Here you go:

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/i2c.h>
#include <linux/i2c/vcnl4010.h>
#include <linux/gpio.h>

#define VCNL4010_DEBUG 1

#define DEVICE_NAME "vcnl4010"
#define DRIVER_NAME "vcnl4010"

#define VCNL4010_CMD_REG 0x80
#define VCNL4010_PROD_ID_VER 0x81
#define VCNL4010_PROX_RATE 0X82
#define VCNL4010_IR_LED_CURR 0x83
#define VCNL4010_AMB_LIGHT_PARAM 0x84
#define VCNL4010_MSB_AMB_LIGHT_RESULT 0x85
#define VCNL4010_LSB_AMB_LIGHT_RESULT 0x86
#define VCNL4010_MSB_PROX_MEASURE_RESULT 0x87
#define VCNL4010_LSB_PROX_MEASURE_RESULT 0x88
#define VCNL4010_INTERRUPT_CTRL 0x89
#define VCNL4010_MSB_LOW_THRESHOLD 0x8A
#define VCNL4010_LSB_LOW_THRESHOLD 0x8B
#define VCNL4010_MSB_HIGH_THRESHOLD 0x8C
#define VCNL4010_LSB_HIGH_THRESHOLD 0x8D
#define VCNL4010_INTERRUPT_STATUS 0x8E
#define VCNL4010_PROX_MODU_TIMING_ADJ 0x8F

typedef enum {eNear, eFar, eNone} vcnl4010InterruptState;
#define VCNL4010_INT_PROX_READY 0x08
#define VCNL4010_INT_ALS_READY  0x04
#define VCNL4010_INT_THRES_LOW  0x02
#define VCNL4010_INT_THRES_HI   0x01

#define VCNL4010_FAR_DISTANCE 10

struct vcnl4010_data
{
    struct vcnl4010_platform_data *pdata;
    struct i2c_client *client;
    struct input_dev *input_dev;
    struct delayed_work wq;
    struct mutex lock;
    uint32_t self_refresh_poll_rate;
    vcnl4010InterruptState last_interrupt_state;
    uint16_t prox_offset;
    uint16_t threshold_low;
    uint16_t threshold_hi;
};

static struct task_struct *proximity_intr;

//static uint32_t prox_debug;
//module_param(prox_debug, uint, 0664);
//module_param_named(vcnl4010_debug, prox_debug, uint, 0664);
//MODULE_PARM_DESC(vcnl4010_debug, "VCNL4010 Debug");

#ifdef VCNL4010_DEBUG
struct vcnl4010_reg
{
    const char *name;
    uint8_t reg;
    int writeable;
} vcnl4010_regs[] = {
    { "CMD_REG",                VCNL4010_CMD_REG, 0 },
    { "VERSION",                VCNL4010_PROD_ID_VER, 0 },
    { "VCNL4010_PROX_RATE",            VCNL4010_PROX_RATE, 0 },
    { "IR_LED_CURR",            VCNL4010_IR_LED_CURR, 0 },
    { "AMB_LIGHT_PARAM",            VCNL4010_AMB_LIGHT_PARAM, 0 },
    { "MSB_AMB_LIGHT_RESULT",        VCNL4010_MSB_AMB_LIGHT_RESULT, 0 },
    { "LSB_AMB_LIGHT_RESULT",        VCNL4010_LSB_AMB_LIGHT_RESULT, 0 },
    { "MSB_PROX_MEASURE_RESULT",        VCNL4010_MSB_PROX_MEASURE_RESULT, 0 },
    { "LSB_PROX_MEASURE_RESULT",        VCNL4010_LSB_PROX_MEASURE_RESULT, 0 },
    { "VCNL4010_INTERRUPT_CTRL",        VCNL4010_INTERRUPT_CTRL, 0 },
    { "VCNL4010_MSB_LOW_THRESHOLD",        VCNL4010_MSB_LOW_THRESHOLD, 0 },
    { "VCNL4010_LSB_LOW_THRESHOLD",        VCNL4010_LSB_LOW_THRESHOLD, 0 },
    { "VCNL4010_MSB_HIGH_THRESHOLD",    VCNL4010_MSB_HIGH_THRESHOLD, 0 },
    { "VCNL4010_LSB_HIGH_THRESHOLD",    VCNL4010_LSB_HIGH_THRESHOLD, 0 },
    { "VCNL4010_INTERRUPT_STATUS",        VCNL4010_INTERRUPT_STATUS, 0 },
    { "VCNL4010_PROX_MODU_TIMING_ADJ",    VCNL4010_PROX_MODU_TIMING_ADJ, 0 },
};
#endif

static int vcnl4010_write(struct vcnl4010_data *data, u8 reg, u8 val)
{
    int ret = 0;

    ret = i2c_smbus_write_byte_data(data->client, reg, val);
    if (ret < 0)
        dev_err(&data->client->dev,
            "i2c_smbus_write_byte_data failed\n");
    return ret;
}

static int vcnl4010_read_transfer(struct vcnl4010_data *data,
        unsigned short data_addr, char *data_buf, int count)
{
    int ret;
    int counter = 5;
    char *data_buffer = data_buf;

    struct i2c_msg msgs[] = {
        {
        .addr = data->client->addr,
        .flags = data->client->flags,
        .len = 1,
        .buf = data_buffer,
        },
        {
        .addr = data->client->addr,
        .flags = (data->client->flags) | I2C_M_RD,
        .len = count,
        .buf = data_buffer,
        },
    };

    data_buffer[0] = data_addr;
    msgs->buf = data_buffer;

    do {
        ret = i2c_transfer(data->client->adapter, msgs, 2);
        if (ret != 2)
        {
            dev_err(&data->client->dev,
                "i2c_transfer failed\n");
            counter--;
            msleep(1);
        }
        else
        {
            return 0;
        }
    } while (counter >= 0);

    return -1;
}

static void vcnl4010_enter_self_timed_mode(struct vcnl4010_data *data)
{
    int ret = 0;

    pr_info("vcnl4010: Enter self refresh mode");

    mutex_lock(&data->lock);
    ret = ret || vcnl4010_write(data, VCNL4010_CMD_REG,              0x00);
    ret = ret || vcnl4010_write(data, VCNL4010_IR_LED_CURR,            20);
    ret = ret || vcnl4010_write(data, VCNL4010_INTERRUPT_CTRL,       0x62);
    ret = ret || vcnl4010_write(data, VCNL4010_PROX_MODU_TIMING_ADJ, 0x01);
    ret = ret || vcnl4010_write(data, VCNL4010_PROX_RATE,            0x04);
    ret = ret || vcnl4010_write(data, VCNL4010_CMD_REG,              0x03);
    mutex_unlock(&data->lock);

    if(ret != 0)
        pr_err("vcnl4010: Failed to enter self refresh mode");
}

static uint32_t vcnl4010_poll_prox_value(struct vcnl4010_data *data)
{
    uint8_t reg_val, timeout;
    uint32_t data_val = 0;

    //device refreshs itself 31 times per secs, if value not ready, wait a little bit and try again
    for(timeout = 0; timeout < 10; timeout++)
    {
        mutex_lock(&data->lock);
        vcnl4010_read_transfer(data, VCNL4010_CMD_REG, &reg_val, 1);
        if(reg_val & 0x20)
        {
            vcnl4010_read_transfer(data, VCNL4010_MSB_PROX_MEASURE_RESULT, &reg_val, 1);
            data_val = reg_val << 8;

            vcnl4010_read_transfer(data, VCNL4010_LSB_PROX_MEASURE_RESULT, &reg_val, 1);
            data_val |= reg_val;
            mutex_unlock(&data->lock);
            break;
        }
        mutex_unlock(&data->lock);
        msleep_interruptible(8);
    }

    if(timeout == 10)
        pr_err("vcnl4010: get prox value failed");

    return data_val;
}

static vcnl4010InterruptState vcnl4010_poll_interrupt_value(struct vcnl4010_data *data)
{
    vcnl4010InterruptState retVal = eNone;
    uint8_t reg_val = 0;

    mutex_lock(&data->lock);
    if(vcnl4010_read_transfer(data, VCNL4010_INTERRUPT_STATUS, &reg_val, 1) == 0)
    {
        if(reg_val & VCNL4010_INT_THRES_HI)
            retVal = eNear;
        if(reg_val & VCNL4010_INT_THRES_LOW)
            retVal = eFar;

        if((reg_val & (VCNL4010_INT_THRES_HI | VCNL4010_INT_THRES_LOW)) != 0)
        {
            vcnl4010_write(data, VCNL4010_INTERRUPT_STATUS, VCNL4010_INT_THRES_HI || VCNL4010_INT_THRES_LOW);
        }
    }

    mutex_unlock(&data->lock);
    return retVal;
}

static void vcnl4010_set_int_threshold(struct vcnl4010_data *data, vcnl4010InterruptState type)
{
    uint16_t threshold_hi;
    uint16_t threshold_low;

    if(type == eNear)
    {
        threshold_hi = data->prox_offset + data->threshold_hi;
        threshold_low = 0;
    }
    else
    {
        threshold_hi = 0xffff;
        threshold_low = data->prox_offset + data->threshold_low;
    }

    mutex_lock(&data->lock);
    vcnl4010_write(data, VCNL4010_LSB_LOW_THRESHOLD, threshold_low & 0xff);
    vcnl4010_write(data, VCNL4010_MSB_LOW_THRESHOLD, (threshold_low >> 8) & 0xff);
    vcnl4010_write(data, VCNL4010_LSB_HIGH_THRESHOLD, threshold_hi & 0xff);
    vcnl4010_write(data, VCNL4010_MSB_HIGH_THRESHOLD, (threshold_hi >> 8) & 0xff);
    mutex_unlock(&data->lock);
}

static int proximity_thread(void *data)
{
    struct vcnl4010_data *prox_data = data;

    while (!kthread_should_stop())
    {
        vcnl4010InterruptState lState = vcnl4010_poll_interrupt_value(prox_data);
        if(lState != eNone && lState != prox_data->last_interrupt_state)
        {
            prox_data->last_interrupt_state = lState;
            if(lState == eFar)
            {
                input_report_abs (prox_data->input_dev, ABS_DISTANCE, VCNL4010_FAR_DISTANCE);
                input_sync(prox_data->input_dev);
                vcnl4010_set_int_threshold(prox_data, eNear);
            }
            else
            {
                input_report_abs (prox_data->input_dev, ABS_DISTANCE, 0);
                input_sync(prox_data->input_dev);
                vcnl4010_set_int_threshold(prox_data, eFar);
            }
        }
        msleep_interruptible (prox_data->self_refresh_poll_rate); //250ms
    }
    return 0;
}

static ssize_t vcnl4010_get_raw_prox(struct device *dev, struct device_attribute *attr, char *buf)
{
    uint32_t data_val;
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);

    data_val = vcnl4010_poll_prox_value(data);

    return sprintf(buf, "%d\n", data_val);
}

static ssize_t vcnl4010_get_offset(struct device *dev,
                  struct device_attribute *attr, char *buf)
{
    unsigned n;
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    n = scnprintf(buf, PAGE_SIZE, "%d", data->prox_offset);

    return n;
}

static ssize_t vcnl4010_set_offset(struct device *dev,
                   struct device_attribute *attr,
                   const char *buf, size_t count)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);

    int value;
    if (sscanf(buf, "%d", &value) != 1) {
        pr_err("%s:unable to parse input\n", __func__);
        return -1;
    }
    if(value > 0xffff || value < 0)
    {
        pr_err("%s:invalid offset value\n", __func__);
        return -1;
    }

    data->prox_offset = value;

    if(data->last_interrupt_state == eFar)
        vcnl4010_set_int_threshold(data, eNear);
    else
        vcnl4010_set_int_threshold(data, eFar);

    return count;
}

static ssize_t vcnl4010_get_threshold_hi(struct device *dev,
                  struct device_attribute *attr, char *buf)
{
    unsigned n;
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    n = scnprintf(buf, PAGE_SIZE, "%d", data->threshold_hi);

    return n;
}

static ssize_t vcnl4010_set_threshold_hi(struct device *dev,
                   struct device_attribute *attr,
                   const char *buf, size_t count)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);

    int value;
    if (sscanf(buf, "%d", &value) != 1) {
        pr_err("%s:unable to parse input\n", __func__);
        return -1;
    }
    if(value > 0xffff || value < 0)
    {
        pr_err("%s:invalid threshold value\n", __func__);
        return -1;
    }

    data->threshold_hi = value;

    if(data->last_interrupt_state == eFar)
        vcnl4010_set_int_threshold(data, eNear);
    else
        vcnl4010_set_int_threshold(data, eFar);

    return count;
}

static ssize_t vcnl4010_get_threshold_low(struct device *dev,
                  struct device_attribute *attr, char *buf)
{
    unsigned n;
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    n = scnprintf(buf, PAGE_SIZE, "%d", data->threshold_low);

    return n;
}

static ssize_t vcnl4010_set_threshold_low(struct device *dev,
                   struct device_attribute *attr,
                   const char *buf, size_t count)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);

    int value;
    if (sscanf(buf, "%d", &value) != 1) {
        pr_err("%s:unable to parse input\n", __func__);
        return -1;
    }
    if(value > 0xffff || value < 0)
    {
        pr_err("%s:invalid threshold value\n", __func__);
        return -1;
    }

    data->threshold_low = value;

    if(data->last_interrupt_state == eFar)
        vcnl4010_set_int_threshold(data, eNear);
    else
        vcnl4010_set_int_threshold(data, eFar);

    return count;
}

#ifdef VCNL4010_DEBUG
static ssize_t vcnl4010_registers_show(struct device *dev,
                  struct device_attribute *attr, char *buf)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    unsigned i, n, reg_count;
    uint8_t value;

    mutex_lock(&data->lock);
    reg_count = sizeof(vcnl4010_regs) / sizeof(vcnl4010_regs[0]);
    for (i = 0, n = 0; i < reg_count; i++)
    {
        vcnl4010_read_transfer(data, vcnl4010_regs[i].reg, &value, 1);
        n += scnprintf(buf + n, PAGE_SIZE - n, "%-20s = 0x%02X\n",
                   vcnl4010_regs[i].name,
                   value);
    }
    mutex_unlock(&data->lock);

    return n;
}

static ssize_t vcnl4010_registers_store(struct device *dev,
                   struct device_attribute *attr,
                   const char *buf, size_t count)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    unsigned i, reg_count, value;
    int error = 0;
    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;
    }

    mutex_lock(&data->lock);
    reg_count = sizeof(vcnl4010_regs) / sizeof(vcnl4010_regs[0]);
    for (i = 0; i < reg_count; i++) {
        if (!strcmp(name, vcnl4010_regs[i].name)) {
            if (vcnl4010_regs[i].writeable) {
                error = vcnl4010_write(data,
                    vcnl4010_regs[i].reg,
                    value);
                if (error) {
                    pr_err("%s:Failed to write %s\n",
                        __func__, name);
                    mutex_unlock(&data->lock);
                    return -1;
                }
            } else {
                pr_err("%s:Register %s is not writeable\n",
                        __func__, name);
                mutex_unlock(&data->lock);
                return -1;
            }
            mutex_unlock(&data->lock);
            return count;
        }
    }
    mutex_unlock(&data->lock);

    pr_err("%s:no such register %s\n", __func__, name);
    return -1;
}

static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO,
        vcnl4010_registers_show, vcnl4010_registers_store);
#endif


//only root
//#define FILE_PERMS S_IWUSR | S_IRUGO
//all users
#define FILE_PERMS S_IWUGO | S_IRUGO

static DEVICE_ATTR(prox_value, FILE_PERMS, vcnl4010_get_raw_prox, NULL);
static DEVICE_ATTR(prox_offset, FILE_PERMS, vcnl4010_get_offset, vcnl4010_set_offset);
static DEVICE_ATTR(threshold_low, FILE_PERMS, vcnl4010_get_threshold_low, vcnl4010_set_threshold_low);
static DEVICE_ATTR(threshold_hi, FILE_PERMS, vcnl4010_get_threshold_hi, vcnl4010_set_threshold_hi);

static struct attribute *vcnl4010_attrs[] =
{
    &dev_attr_prox_value.attr,
    &dev_attr_prox_offset.attr,
    &dev_attr_threshold_low.attr,
    &dev_attr_threshold_hi.attr,
#ifdef VCNL4010_DEBUG
    &dev_attr_registers.attr,
#endif
    NULL
};

static const struct attribute_group vcnl4010_attr_group = {
    .attrs = vcnl4010_attrs,
};

static int __devinit vcnl4010_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct vcnl4010_platform_data *pdata = client->dev.platform_data;
    struct vcnl4010_data *data;
    int ret = 0;
    uint8_t reg_val = 0;

    pr_info("%s: Enter\n", __func__);

    if (pdata == NULL) {
        pr_err("%s: Platform data not found\n", __func__);
        return -ENODEV;
    }

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        pr_err("%s: need I2C_FUNC_I2C\n", __func__);
        return -ENODEV;
    }

    /* alloc memory for data structure */
    data = kzalloc(sizeof(struct vcnl4010_data), GFP_KERNEL);
    if (!data) {
        ret = -ENOMEM;
        goto error;
    }

    mutex_init(&data->lock);

    data->input_dev = input_allocate_device();

    if (!data->input_dev)
    {
        ret = -ENOMEM;
        printk("%s: input device allocate failed: %d\n", __func__, ret);
        goto dev_allocate_error;
    }

    data->input_dev->name = "proximity";
    set_bit(EV_ABS, data->input_dev->evbit);
    set_bit(ABS_DISTANCE, data->input_dev->absbit);
    data->input_dev->phys = "proximity/input";

    input_set_abs_params( data->input_dev, ABS_DISTANCE, 0, VCNL4010_FAR_DISTANCE, 0, 0 );

    ret = input_register_device(data->input_dev);
    if (ret) {
        printk("%s: input device register failed:%d\n", __func__, ret);
        goto dev_register_error;
    }

    data->pdata = pdata;
    data->client = client;
    data->self_refresh_poll_rate = pdata->def_poll_rate;
    data->last_interrupt_state = eFar;  //Start as far
    data->prox_offset = 17000;  //todo set correct default value
    data->threshold_low = 105;
    data->threshold_hi =  110;

    i2c_set_clientdata(client, data);

    //test for device presence
    if(vcnl4010_read_transfer(data, VCNL4010_PROD_ID_VER, &reg_val, 1) !=0 )
    {
        pr_err("vcnl4010: Device not found!");
        goto nochip;
    }

    if(reg_val != 0x21)
    {
        pr_err("vcnl4010: Found device isn't a vcnl4010, is a vcnl4000 installed?");
        goto badchip;
    }

    pr_info("vcnl4010: Found device!");

    vcnl4010_set_int_threshold(data, eNear);  //Generate an interrupt when become near
    vcnl4010_enter_self_timed_mode(data);

    input_report_abs (data->input_dev, ABS_DISTANCE, VCNL4010_FAR_DISTANCE);
    input_sync(data->input_dev);

    ret = sysfs_create_group(&client->dev.kobj, &vcnl4010_attr_group);
    if (ret)
        goto sysfs_error;

    /* start the kthread */
    proximity_intr = kthread_run(proximity_thread, data, "proximity_thread");

    return 0;

sysfs_error:
badchip:
nochip:
    input_unregister_device(data->input_dev);
dev_register_error:
    input_free_device(data->input_dev);
dev_allocate_error:
    mutex_destroy(&data->lock);
    kfree(data);
error:
    return ret;
}

static int __devexit vcnl4010_driver_remove(struct i2c_client *client)
{
    struct vcnl4010_data *data = i2c_get_clientdata(client);
    int ret = 0;

    sysfs_remove_group(&client->dev.kobj, &vcnl4010_attr_group);

    i2c_set_clientdata(client, NULL);

    input_free_device(data->input_dev);
    mutex_destroy(&data->lock);
    kfree(data);

    return ret;
}


#ifdef CONFIG_PM
static int vcnl4010_driver_suspend(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    mutex_lock(&data->lock);
    vcnl4010_write(data, VCNL4010_CMD_REG,              0x00);
    vcnl4010_write(data, VCNL4010_IR_LED_CURR,             0);
    mutex_unlock(&data->lock);

    return 0;
}

static int vcnl4010_driver_resume(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct vcnl4010_data *data = platform_get_drvdata(pdev);
    vcnl4010_enter_self_timed_mode(data);
    if(data->last_interrupt_state == eFar)
        vcnl4010_set_int_threshold(data, eNear);
    else
        vcnl4010_set_int_threshold(data, eFar);

    return 0;
}

static const struct dev_pm_ops vcnl4010_pm_ops = {
    .suspend = vcnl4010_driver_suspend,
    .resume = vcnl4010_driver_resume,
};
#endif

static const struct i2c_device_id vcnl4010_idtable[] = {
    { DRIVER_NAME, 0 },
    { },
};

MODULE_DEVICE_TABLE(i2c, vcnl4010_idtable);

static struct i2c_driver vcnl4010_driver = {
    .probe        = vcnl4010_driver_probe,
    .remove        = vcnl4010_driver_remove,
    .id_table    = vcnl4010_idtable,
    .driver = {
        .name = DRIVER_NAME,
#ifdef CONFIG_PM
        .pm = &vcnl4010_pm_ops,
#endif
    },
};

static int __init vcnl4010_driver_init(void)
{
    return i2c_add_driver(&vcnl4010_driver);
}

static void __exit vcnl4010_driver_exit(void)
{
    i2c_del_driver(&vcnl4010_driver);
}

module_init(vcnl4010_driver_init);
module_exit(vcnl4010_driver_exit);

MODULE_DESCRIPTION("VCNL4010 Proximity Driver");
MODULE_LICENSE("GPL");

  parent reply	other threads:[~2013-04-21  3:38 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <931575758.126890.1363615027376.JavaMail.root@mail>
2013-03-18 14:47 ` how to gracefully unload an i2c driver if chip not detected? Émeric Vigier
2013-04-09  9:26   ` Wolfram Sang
     [not found]     ` <20130409092625.GD3624-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
2013-04-21  3:38       ` Émeric Vigier [this message]
2013-04-21 15:48         ` Wolfram Sang
     [not found]           ` <20130421154832.GA9593-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
2013-04-22 11:18             ` Émeric Vigier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=228256972.786837.1366515514850.JavaMail.root@mail \
    --to=emeric.vigier-4ysuxcep3am1wj+d4i0nrvatqe2ktcn/@public.gmane.org \
    --cc=linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.