aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/apds9802als.c
diff options
context:
space:
mode:
authoranantha <anantha.narayanan@intel.com>2010-10-26 17:22:41 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2010-10-26 19:52:15 -0400
commit22d96aa59cf120db3584e4c3365554cae77d2441 (patch)
treec43dd27c61139ee599bfe34ccaa7b14382d74b8a /drivers/misc/apds9802als.c
parent8a233f01b7d7dd587f85fa581274f168f1e88bb8 (diff)
drivers/misc/apds9802als.c: ALS drivers for the apds9802als
This adds support for the ADPS9802ALS sensor. Cleanup by Alan Cox - move mutexes to cover more things - report I/O errors back to user space - report range and values in LUX Signed-off-by: Anantha Narayanan <anantha.narayanan@intel.com> [The 4K and 64K in the hw spec actually means 4095 (12bit) and 65535 (16bit).] Signed-off-by: Hong Liu <hong.liu@intel.com> [Updated to match the ALS light API interface convention from Samu] Signed-off-by: Alan Cox <alan@linux.intel.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/misc/apds9802als.c')
-rw-r--r--drivers/misc/apds9802als.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/drivers/misc/apds9802als.c b/drivers/misc/apds9802als.c
new file mode 100644
index 000000000000..fbe49602f396
--- /dev/null
+++ b/drivers/misc/apds9802als.c
@@ -0,0 +1,308 @@
1/*
2 * apds9802als.c - apds9802 ALS Driver
3 *
4 * Copyright (C) 2009 Intel Corp
5 *
6 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; version 2 of the License.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 *
22 */
23
24#include <linux/module.h>
25#include <linux/init.h>
26#include <linux/slab.h>
27#include <linux/i2c.h>
28#include <linux/err.h>
29#include <linux/delay.h>
30#include <linux/mutex.h>
31#include <linux/sysfs.h>
32#include <linux/hwmon-sysfs.h>
33
34#define ALS_MIN_RANGE_VAL 1
35#define ALS_MAX_RANGE_VAL 2
36#define POWER_STA_ENABLE 1
37#define POWER_STA_DISABLE 0
38#define APDS9802ALS_I2C_ADDR 0x29
39
40#define DRIVER_NAME "apds9802als"
41
42struct als_data {
43 struct device *hwmon_dev;
44 bool needresume;
45 struct mutex mutex;
46};
47
48static ssize_t als_sensing_range_show(struct device *dev,
49 struct device_attribute *attr, char *buf)
50{
51 struct i2c_client *client = to_i2c_client(dev);
52 int val;
53
54 val = i2c_smbus_read_byte_data(client, 0x81);
55 if (val < 0)
56 return val;
57 if (val & 1)
58 return sprintf(buf, "4095\n");
59 else
60 return sprintf(buf, "65535\n");
61}
62
63static ssize_t als_lux0_input_data_show(struct device *dev,
64 struct device_attribute *attr, char *buf)
65{
66 struct i2c_client *client = to_i2c_client(dev);
67 struct als_data *data = i2c_get_clientdata(client);
68 unsigned int ret_val;
69 int temp;
70
71 /* Protect against parallel reads */
72 mutex_lock(&data->mutex);
73 temp = i2c_smbus_read_byte_data(client, 0x8C);/*LSB data*/
74 if (temp < 0) {
75 ret_val = temp;
76 goto failed;
77 }
78 ret_val = i2c_smbus_read_byte_data(client, 0x8D);/*MSB data*/
79 if (ret_val < 0)
80 goto failed;
81 mutex_unlock(&data->mutex);
82 ret_val = (ret_val << 8) | temp;
83 return sprintf(buf, "%d\n", ret_val);
84failed:
85 mutex_unlock(&data->mutex);
86 return ret_val;
87}
88
89static ssize_t als_sensing_range_store(struct device *dev,
90 struct device_attribute *attr, const char *buf, size_t count)
91{
92 struct i2c_client *client = to_i2c_client(dev);
93 struct als_data *data = i2c_get_clientdata(client);
94 unsigned int ret_val;
95 unsigned long val;
96
97 if (strict_strtoul(buf, 10, &val))
98 return -EINVAL;
99
100 if (val < 4096)
101 val = 1;
102 else if (val < 65536)
103 val = 2;
104 else
105 return -ERANGE;
106
107 /* Make sure nobody else reads/modifies/writes 0x81 while we
108 are active */
109
110 mutex_lock(&data->mutex);
111
112 ret_val = i2c_smbus_read_byte_data(client, 0x81);
113 if (ret_val < 0)
114 goto fail;
115
116 /* Reset the bits before setting them */
117 ret_val = ret_val & 0xFA;
118
119 if (val == 1) /* Setting the continous measurement up to 4k LUX */
120 ret_val = (ret_val | 0x05);
121 else /* Setting the continous measurement up to 64k LUX*/
122 ret_val = (ret_val | 0x04);
123
124 ret_val = i2c_smbus_write_byte_data(client, 0x81, ret_val);
125 if (ret_val >= 0) {
126 /* All OK */
127 mutex_unlock(&data->mutex);
128 return count;
129 }
130fail:
131 mutex_unlock(&data->mutex);
132 return ret_val;
133}
134
135static ssize_t als_power_status_show(struct device *dev,
136 struct device_attribute *attr, char *buf)
137{
138 struct i2c_client *client = to_i2c_client(dev);
139 int ret_val;
140 ret_val = i2c_smbus_read_byte_data(client, 0x80);
141 if (ret_val < 0)
142 return ret_val;
143 ret_val = ret_val & 0x01;
144 return sprintf(buf, "%d\n", ret_val);
145}
146
147static int als_set_power_state(struct i2c_client *client, bool on_off)
148{
149 int ret_val;
150 struct als_data *data = i2c_get_clientdata(client);
151
152 mutex_lock(&data->mutex);
153 ret_val = i2c_smbus_read_byte_data(client, 0x80);
154 if (ret_val < 0)
155 goto fail;
156 if (on_off)
157 ret_val = ret_val | 0x01;
158 else
159 ret_val = ret_val & 0xFE;
160 ret_val = i2c_smbus_write_byte_data(client, 0x80, ret_val);
161fail:
162 mutex_unlock(&data->mutex);
163 return ret_val;
164}
165
166static ssize_t als_power_status_store(struct device *dev,
167 struct device_attribute *attr, const char *buf, size_t count)
168{
169 struct i2c_client *client = to_i2c_client(dev);
170 struct als_data *data = i2c_get_clientdata(client);
171 unsigned long val;
172 int ret_val;
173
174 if (strict_strtoul(buf, 10, &val))
175 return -EINVAL;
176 if (val == POWER_STA_ENABLE) {
177 ret_val = als_set_power_state(client, true);
178 data->needresume = true;
179 } else if (val == POWER_STA_DISABLE) {
180 ret_val = als_set_power_state(client, false);
181 data->needresume = false;
182 } else
183 return -EINVAL;
184 if (ret_val < 0)
185 return ret_val;
186 return count;
187}
188
189static DEVICE_ATTR(lux0_sensor_range, S_IRUGO | S_IWUSR,
190 als_sensing_range_show, als_sensing_range_store);
191static DEVICE_ATTR(lux0_input, S_IRUGO, als_lux0_input_data_show, NULL);
192static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR,
193 als_power_status_show, als_power_status_store);
194
195static struct attribute *mid_att_als[] = {
196 &dev_attr_lux0_sensor_range.attr,
197 &dev_attr_lux0_input.attr,
198 &dev_attr_power_state.attr,
199 NULL
200};
201
202static struct attribute_group m_als_gr = {
203 .name = "apds9802als",
204 .attrs = mid_att_als
205};
206
207static int als_set_default_config(struct i2c_client *client)
208{
209 int ret_val;
210 /* Write the command and then switch on */
211 ret_val = i2c_smbus_write_byte_data(client, 0x80, 0x01);
212 if (ret_val < 0) {
213 dev_err(&client->dev, "failed default switch on write\n");
214 return ret_val;
215 }
216 /* Continous from 1Lux to 64k Lux */
217 ret_val = i2c_smbus_write_byte_data(client, 0x81, 0x04);
218 if (ret_val < 0)
219 dev_err(&client->dev, "failed default LUX on write\n");
220 return ret_val;
221}
222
223static int apds9802als_probe(struct i2c_client *client,
224 const struct i2c_device_id *id)
225{
226 int res;
227 struct als_data *data;
228
229 data = kzalloc(sizeof(struct als_data), GFP_KERNEL);
230 if (data == NULL) {
231 dev_err(&client->dev, "Memory allocation failed\n");
232 return -ENOMEM;
233 }
234 i2c_set_clientdata(client, data);
235 res = sysfs_create_group(&client->dev.kobj, &m_als_gr);
236 if (res) {
237 dev_err(&client->dev, "device create file failed\n");
238 goto als_error1;
239 }
240 dev_info(&client->dev,
241 "%s apds9802als: ALS chip found\n", client->name);
242 als_set_default_config(client);
243 data->needresume = true;
244 mutex_init(&data->mutex);
245 return res;
246als_error1:
247 i2c_set_clientdata(client, NULL);
248 kfree(data);
249 return res;
250}
251
252static int apds9802als_remove(struct i2c_client *client)
253{
254 struct als_data *data = i2c_get_clientdata(client);
255 sysfs_remove_group(&client->dev.kobj, &m_als_gr);
256 kfree(data);
257 return 0;
258}
259
260static int apds9802als_suspend(struct i2c_client *client, pm_message_t mesg)
261{
262 als_set_power_state(client, false);
263 return 0;
264}
265
266static int apds9802als_resume(struct i2c_client *client)
267{
268 struct als_data *data = i2c_get_clientdata(client);
269
270 if (data->needresume == true)
271 als_set_power_state(client, true);
272 return 0;
273}
274
275static struct i2c_device_id apds9802als_id[] = {
276 { DRIVER_NAME, 0 },
277 { }
278};
279
280MODULE_DEVICE_TABLE(i2c, apds9802als_id);
281
282static struct i2c_driver apds9802als_driver = {
283 .driver = {
284 .name = DRIVER_NAME,
285 .owner = THIS_MODULE,
286 },
287 .probe = apds9802als_probe,
288 .remove = apds9802als_remove,
289 .suspend = apds9802als_suspend,
290 .resume = apds9802als_resume,
291 .id_table = apds9802als_id,
292};
293
294static int __init sensor_apds9802als_init(void)
295{
296 return i2c_add_driver(&apds9802als_driver);
297}
298
299static void __exit sensor_apds9802als_exit(void)
300{
301 i2c_del_driver(&apds9802als_driver);
302}
303module_init(sensor_apds9802als_init);
304module_exit(sensor_apds9802als_exit);
305
306MODULE_AUTHOR("Anantha Narayanan <Anantha.Narayanan@intel.com");
307MODULE_DESCRIPTION("Avago apds9802als ALS Driver");
308MODULE_LICENSE("GPL v2");