Blame view

drivers/misc/apds9802als.c 8.28 KB
22d96aa59   anantha   drivers/misc/apds...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  /*
   * apds9802als.c - apds9802  ALS Driver
   *
   * Copyright (C) 2009 Intel Corp
   *
   *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; version 2 of the License.
   *
   * 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/module.h>
  #include <linux/init.h>
  #include <linux/slab.h>
  #include <linux/i2c.h>
  #include <linux/err.h>
  #include <linux/delay.h>
  #include <linux/mutex.h>
  #include <linux/sysfs.h>
f0cfec111   Hong Liu   drivers/misc/apds...
32
  #include <linux/pm_runtime.h>
22d96aa59   anantha   drivers/misc/apds...
33
34
35
36
37
  
  #define ALS_MIN_RANGE_VAL 1
  #define ALS_MAX_RANGE_VAL 2
  #define POWER_STA_ENABLE 1
  #define POWER_STA_DISABLE 0
22d96aa59   anantha   drivers/misc/apds...
38
39
40
41
  
  #define DRIVER_NAME "apds9802als"
  
  struct als_data {
22d96aa59   anantha   drivers/misc/apds...
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
  	struct mutex mutex;
  };
  
  static ssize_t als_sensing_range_show(struct device *dev,
  			struct device_attribute *attr,  char *buf)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  	int  val;
  
  	val = i2c_smbus_read_byte_data(client, 0x81);
  	if (val < 0)
  		return val;
  	if (val & 1)
  		return sprintf(buf, "4095
  ");
  	else
  		return sprintf(buf, "65535
  ");
  }
f0cfec111   Hong Liu   drivers/misc/apds...
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
  static int als_wait_for_data_ready(struct device *dev)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  	int ret;
  	int retry = 10;
  
  	do {
  		msleep(30);
  		ret = i2c_smbus_read_byte_data(client, 0x86);
  	} while (!(ret & 0x80) && retry--);
  
  	if (!retry) {
  		dev_warn(dev, "timeout waiting for data ready
  ");
  		return -ETIMEDOUT;
  	}
  
  	return 0;
  }
22d96aa59   anantha   drivers/misc/apds...
80
81
82
83
84
  static ssize_t als_lux0_input_data_show(struct device *dev,
  			struct device_attribute *attr, char *buf)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  	struct als_data *data = i2c_get_clientdata(client);
f0cfec111   Hong Liu   drivers/misc/apds...
85
  	int ret_val;
22d96aa59   anantha   drivers/misc/apds...
86
87
88
  	int temp;
  
  	/* Protect against parallel reads */
f0cfec111   Hong Liu   drivers/misc/apds...
89
  	pm_runtime_get_sync(dev);
22d96aa59   anantha   drivers/misc/apds...
90
  	mutex_lock(&data->mutex);
f0cfec111   Hong Liu   drivers/misc/apds...
91
92
93
94
95
96
97
98
99
100
101
102
  
  	/* clear EOC interrupt status */
  	i2c_smbus_write_byte(client, 0x40);
  	/* start measurement */
  	temp = i2c_smbus_read_byte_data(client, 0x81);
  	i2c_smbus_write_byte_data(client, 0x81, temp | 0x08);
  
  	ret_val = als_wait_for_data_ready(dev);
  	if (ret_val < 0)
  		goto failed;
  
  	temp = i2c_smbus_read_byte_data(client, 0x8C); /* LSB data */
22d96aa59   anantha   drivers/misc/apds...
103
104
105
106
  	if (temp < 0) {
  		ret_val = temp;
  		goto failed;
  	}
f0cfec111   Hong Liu   drivers/misc/apds...
107
  	ret_val = i2c_smbus_read_byte_data(client, 0x8D); /* MSB data */
22d96aa59   anantha   drivers/misc/apds...
108
109
  	if (ret_val < 0)
  		goto failed;
f0cfec111   Hong Liu   drivers/misc/apds...
110

22d96aa59   anantha   drivers/misc/apds...
111
  	mutex_unlock(&data->mutex);
f0cfec111   Hong Liu   drivers/misc/apds...
112
113
114
115
116
  	pm_runtime_put_sync(dev);
  
  	temp = (ret_val << 8) | temp;
  	return sprintf(buf, "%d
  ", temp);
22d96aa59   anantha   drivers/misc/apds...
117
118
  failed:
  	mutex_unlock(&data->mutex);
f0cfec111   Hong Liu   drivers/misc/apds...
119
  	pm_runtime_put_sync(dev);
22d96aa59   anantha   drivers/misc/apds...
120
121
122
123
124
125
126
127
  	return ret_val;
  }
  
  static ssize_t als_sensing_range_store(struct device *dev,
  		struct device_attribute *attr, const  char *buf, size_t count)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  	struct als_data *data = i2c_get_clientdata(client);
1093736b3   Vasiliy Kulikov   drivers/misc/apds...
128
  	int ret_val;
22d96aa59   anantha   drivers/misc/apds...
129
130
131
132
133
134
135
136
137
138
139
  	unsigned long val;
  
  	if (strict_strtoul(buf, 10, &val))
  		return -EINVAL;
  
  	if (val < 4096)
  		val = 1;
  	else if (val < 65536)
  		val = 2;
  	else
  		return -ERANGE;
f0cfec111   Hong Liu   drivers/misc/apds...
140
  	pm_runtime_get_sync(dev);
22d96aa59   anantha   drivers/misc/apds...
141
142
  	/* Make sure nobody else reads/modifies/writes 0x81 while we
  	   are active */
22d96aa59   anantha   drivers/misc/apds...
143
144
145
146
147
148
149
150
  	mutex_lock(&data->mutex);
  
  	ret_val = i2c_smbus_read_byte_data(client, 0x81);
  	if (ret_val < 0)
  		goto fail;
  
  	/* Reset the bits before setting them */
  	ret_val = ret_val & 0xFA;
f0cfec111   Hong Liu   drivers/misc/apds...
151
152
153
154
  	if (val == 1) /* Setting detection range up to 4k LUX */
  		ret_val = (ret_val | 0x01);
  	else /* Setting detection range up to 64k LUX*/
  		ret_val = (ret_val | 0x00);
22d96aa59   anantha   drivers/misc/apds...
155
156
  
  	ret_val = i2c_smbus_write_byte_data(client, 0x81, ret_val);
f0cfec111   Hong Liu   drivers/misc/apds...
157

22d96aa59   anantha   drivers/misc/apds...
158
159
160
  	if (ret_val >= 0) {
  		/* All OK */
  		mutex_unlock(&data->mutex);
f0cfec111   Hong Liu   drivers/misc/apds...
161
  		pm_runtime_put_sync(dev);
22d96aa59   anantha   drivers/misc/apds...
162
163
164
165
  		return count;
  	}
  fail:
  	mutex_unlock(&data->mutex);
f0cfec111   Hong Liu   drivers/misc/apds...
166
  	pm_runtime_put_sync(dev);
22d96aa59   anantha   drivers/misc/apds...
167
168
  	return ret_val;
  }
22d96aa59   anantha   drivers/misc/apds...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  static int als_set_power_state(struct i2c_client *client, bool on_off)
  {
  	int ret_val;
  	struct als_data *data = i2c_get_clientdata(client);
  
  	mutex_lock(&data->mutex);
  	ret_val = i2c_smbus_read_byte_data(client, 0x80);
  	if (ret_val < 0)
  		goto fail;
  	if (on_off)
  		ret_val = ret_val | 0x01;
  	else
  		ret_val = ret_val & 0xFE;
  	ret_val = i2c_smbus_write_byte_data(client, 0x80, ret_val);
  fail:
  	mutex_unlock(&data->mutex);
  	return ret_val;
  }
22d96aa59   anantha   drivers/misc/apds...
187
188
189
  static DEVICE_ATTR(lux0_sensor_range, S_IRUGO | S_IWUSR,
  	als_sensing_range_show, als_sensing_range_store);
  static DEVICE_ATTR(lux0_input, S_IRUGO, als_lux0_input_data_show, NULL);
22d96aa59   anantha   drivers/misc/apds...
190
191
192
193
  
  static struct attribute *mid_att_als[] = {
  	&dev_attr_lux0_sensor_range.attr,
  	&dev_attr_lux0_input.attr,
22d96aa59   anantha   drivers/misc/apds...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
  	NULL
  };
  
  static struct attribute_group m_als_gr = {
  	.name = "apds9802als",
  	.attrs = mid_att_als
  };
  
  static int als_set_default_config(struct i2c_client *client)
  {
  	int ret_val;
  	/* Write the command and then switch on */
  	ret_val = i2c_smbus_write_byte_data(client, 0x80, 0x01);
  	if (ret_val < 0) {
  		dev_err(&client->dev, "failed default switch on write
  ");
  		return ret_val;
  	}
f0cfec111   Hong Liu   drivers/misc/apds...
212
213
  	/* detection range: 1~64K Lux, maunal measurement */
  	ret_val = i2c_smbus_write_byte_data(client, 0x81, 0x08);
22d96aa59   anantha   drivers/misc/apds...
214
215
216
  	if (ret_val < 0)
  		dev_err(&client->dev, "failed default LUX on write
  ");
f0cfec111   Hong Liu   drivers/misc/apds...
217
218
219
220
221
  
  	/*  We always get 0 for the 1st measurement after system power on,
  	 *  so make sure it is finished before user asks for data.
  	 */
  	als_wait_for_data_ready(&client->dev);
22d96aa59   anantha   drivers/misc/apds...
222
223
  	return ret_val;
  }
f0cfec111   Hong Liu   drivers/misc/apds...
224
225
  static int apds9802als_probe(struct i2c_client *client,
  			     const struct i2c_device_id *id)
22d96aa59   anantha   drivers/misc/apds...
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
  {
  	int res;
  	struct als_data *data;
  
  	data = kzalloc(sizeof(struct als_data), GFP_KERNEL);
  	if (data == NULL) {
  		dev_err(&client->dev, "Memory allocation failed
  ");
  		return -ENOMEM;
  	}
  	i2c_set_clientdata(client, data);
  	res = sysfs_create_group(&client->dev.kobj, &m_als_gr);
  	if (res) {
  		dev_err(&client->dev, "device create file failed
  ");
  		goto als_error1;
  	}
f0cfec111   Hong Liu   drivers/misc/apds...
243
244
  	dev_info(&client->dev, "ALS chip found
  ");
22d96aa59   anantha   drivers/misc/apds...
245
  	als_set_default_config(client);
22d96aa59   anantha   drivers/misc/apds...
246
  	mutex_init(&data->mutex);
f0cfec111   Hong Liu   drivers/misc/apds...
247

4e6735992   Hong Liu   drivers/misc/apds...
248
  	pm_runtime_set_active(&client->dev);
f0cfec111   Hong Liu   drivers/misc/apds...
249
  	pm_runtime_enable(&client->dev);
f0cfec111   Hong Liu   drivers/misc/apds...
250

22d96aa59   anantha   drivers/misc/apds...
251
252
  	return res;
  als_error1:
22d96aa59   anantha   drivers/misc/apds...
253
254
255
  	kfree(data);
  	return res;
  }
4e6735992   Hong Liu   drivers/misc/apds...
256
  static int __devexit apds9802als_remove(struct i2c_client *client)
22d96aa59   anantha   drivers/misc/apds...
257
258
  {
  	struct als_data *data = i2c_get_clientdata(client);
f0cfec111   Hong Liu   drivers/misc/apds...
259

4e6735992   Hong Liu   drivers/misc/apds...
260
  	pm_runtime_get_sync(&client->dev);
f0cfec111   Hong Liu   drivers/misc/apds...
261
  	als_set_power_state(client, false);
22d96aa59   anantha   drivers/misc/apds...
262
  	sysfs_remove_group(&client->dev.kobj, &m_als_gr);
4e6735992   Hong Liu   drivers/misc/apds...
263
264
265
266
  
  	pm_runtime_disable(&client->dev);
  	pm_runtime_set_suspended(&client->dev);
  	pm_runtime_put_noidle(&client->dev);
22d96aa59   anantha   drivers/misc/apds...
267
268
269
  	kfree(data);
  	return 0;
  }
f0cfec111   Hong Liu   drivers/misc/apds...
270
  #ifdef CONFIG_PM
22d96aa59   anantha   drivers/misc/apds...
271
272
273
274
275
276
277
278
  static int apds9802als_suspend(struct i2c_client *client, pm_message_t mesg)
  {
  	als_set_power_state(client, false);
  	return 0;
  }
  
  static int apds9802als_resume(struct i2c_client *client)
  {
f0cfec111   Hong Liu   drivers/misc/apds...
279
  	als_set_default_config(client);
22d96aa59   anantha   drivers/misc/apds...
280
281
  	return 0;
  }
f0cfec111   Hong Liu   drivers/misc/apds...
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
  static int apds9802als_runtime_suspend(struct device *dev)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  
  	als_set_power_state(client, false);
  	return 0;
  }
  
  static int apds9802als_runtime_resume(struct device *dev)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  
  	als_set_power_state(client, true);
  	return 0;
  }
  
  static const struct dev_pm_ops apds9802als_pm_ops = {
  	.runtime_suspend = apds9802als_runtime_suspend,
  	.runtime_resume = apds9802als_runtime_resume,
  };
  
  #define APDS9802ALS_PM_OPS (&apds9802als_pm_ops)
  
  #else	/* CONFIG_PM */
  #define apds9802als_suspend NULL
  #define apds9802als_resume NULL
  #define APDS9802ALS_PM_OPS NULL
  #endif	/* CONFIG_PM */
22d96aa59   anantha   drivers/misc/apds...
310
311
312
313
314
315
316
317
318
  static struct i2c_device_id apds9802als_id[] = {
  	{ DRIVER_NAME, 0 },
  	{ }
  };
  
  MODULE_DEVICE_TABLE(i2c, apds9802als_id);
  
  static struct i2c_driver apds9802als_driver = {
  	.driver = {
f0cfec111   Hong Liu   drivers/misc/apds...
319
320
  		.name = DRIVER_NAME,
  		.pm = APDS9802ALS_PM_OPS,
22d96aa59   anantha   drivers/misc/apds...
321
322
  	},
  	.probe = apds9802als_probe,
4e6735992   Hong Liu   drivers/misc/apds...
323
  	.remove = __devexit_p(apds9802als_remove),
22d96aa59   anantha   drivers/misc/apds...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
  	.suspend = apds9802als_suspend,
  	.resume = apds9802als_resume,
  	.id_table = apds9802als_id,
  };
  
  static int __init sensor_apds9802als_init(void)
  {
  	return i2c_add_driver(&apds9802als_driver);
  }
  
  static void  __exit sensor_apds9802als_exit(void)
  {
  	i2c_del_driver(&apds9802als_driver);
  }
  module_init(sensor_apds9802als_init);
  module_exit(sensor_apds9802als_exit);
  
  MODULE_AUTHOR("Anantha Narayanan <Anantha.Narayanan@intel.com");
  MODULE_DESCRIPTION("Avago apds9802als ALS Driver");
  MODULE_LICENSE("GPL v2");