* how to gracefully unload an i2c driver if chip not detected?
[not found] <931575758.126890.1363615027376.JavaMail.root@mail>
@ 2013-03-18 14:47 ` Émeric Vigier
2013-04-09 9:26 ` Wolfram Sang
0 siblings, 1 reply; 5+ messages in thread
From: Émeric Vigier @ 2013-03-18 14:47 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA
Hi,
I work on an omap platform running linux 3.0.21. The kernel image embeds two built-in i2c drivers for two different proximity sensors.
The hardware platform embeds only one sensor, so there is a detection mechanism of which chip is present in each driver (code below).
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.
I naively tried to add "i2c_del_driver(&vcnl4010_driver);" in the driver's probe function.
But it triggers a null ptr deref in i2c_do_del_adapter().
How can I gracefully unload this i2c driver if chip is not detected, while keeping this driver built-in in the kernel?
What is the recommended method to do just that?
Thanks,
Emeric
---------
static struct i2c_driver vcnl4010_driver;
static int __devinit vcnl4010_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
[...]
if(vcnl4010_read_transfer(data, VCNL4010_PROD_ID_VER, ®_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;
}
[...]
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:
/* i2c_del_driver(&vcnl4010_driver); triggers NULL ptr deref in i2c_do_del_adapter() */
return ret;
}
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); /* panic kernel if chip is not present */
vcnl4010_write(data, VCNL4010_CMD_REG, 0x00);
vcnl4010_write(data, VCNL4010_IR_LED_CURR, 0);
mutex_unlock(&data->lock);
return 0;
}
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
},
};
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: how to gracefully unload an i2c driver if chip not detected?
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>
0 siblings, 1 reply; 5+ messages in thread
From: Wolfram Sang @ 2013-04-09 9:26 UTC (permalink / raw)
To: Émeric Vigier; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA
> 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.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: how to gracefully unload an i2c driver if chip not detected?
[not found] ` <20130409092625.GD3624-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
@ 2013-04-21 3:38 ` Émeric Vigier
2013-04-21 15:48 ` Wolfram Sang
0 siblings, 1 reply; 5+ messages in thread
From: Émeric Vigier @ 2013-04-21 3:38 UTC (permalink / raw)
To: Wolfram Sang; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA
----- 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, ®_val, 1);
if(reg_val & 0x20)
{
vcnl4010_read_transfer(data, VCNL4010_MSB_PROX_MEASURE_RESULT, ®_val, 1);
data_val = reg_val << 8;
vcnl4010_read_transfer(data, VCNL4010_LSB_PROX_MEASURE_RESULT, ®_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, ®_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, ®_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");
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: how to gracefully unload an i2c driver if chip not detected?
2013-04-21 3:38 ` Émeric Vigier
@ 2013-04-21 15:48 ` Wolfram Sang
[not found] ` <20130421154832.GA9593-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
0 siblings, 1 reply; 5+ messages in thread
From: Wolfram Sang @ 2013-04-21 15:48 UTC (permalink / raw)
To: Émeric Vigier; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA
Hi,
thanks, that helped, I think:
> //test for device presence
> if(vcnl4010_read_transfer(data, VCNL4010_PROD_ID_VER, ®_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;
> }
You should set 'ret = -Esomething' here. Currently, you return 0 which
means success, so the driver core thinks it may call the suspend
function.
Regards,
Wolfram
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: how to gracefully unload an i2c driver if chip not detected?
[not found] ` <20130421154832.GA9593-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
@ 2013-04-22 11:18 ` Émeric Vigier
0 siblings, 0 replies; 5+ messages in thread
From: Émeric Vigier @ 2013-04-22 11:18 UTC (permalink / raw)
To: Wolfram Sang; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA
Thanks a lot Wolfram!
Emeric
----- Mail original -----
> Hi,
>
> thanks, that helped, I think:
>
> > //test for device presence
> > if(vcnl4010_read_transfer(data, VCNL4010_PROD_ID_VER, ®_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;
> > }
>
> You should set 'ret = -Esomething' here. Currently, you return 0
> which
> means success, so the driver core thinks it may call the suspend
> function.
>
> Regards,
>
> Wolfram
>
>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2013-04-22 11:18 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[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
2013-04-21 15:48 ` Wolfram Sang
[not found] ` <20130421154832.GA9593-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
2013-04-22 11:18 ` Émeric Vigier
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.