Commit | Line | Data |
---|---|---|
873e65bc | 1 | // SPDX-License-Identifier: GPL-2.0-only |
22d96aa5 | 2 | /* |
3 | * apds9802als.c - apds9802 ALS Driver | |
4 | * | |
5 | * Copyright (C) 2009 Intel Corp | |
6 | * | |
7 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
8 | * | |
22d96aa5 | 9 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
22d96aa5 | 10 | */ |
11 | ||
12 | #include <linux/module.h> | |
22d96aa5 | 13 | #include <linux/slab.h> |
14 | #include <linux/i2c.h> | |
15 | #include <linux/err.h> | |
16 | #include <linux/delay.h> | |
17 | #include <linux/mutex.h> | |
18 | #include <linux/sysfs.h> | |
f0cfec11 | 19 | #include <linux/pm_runtime.h> |
22d96aa5 | 20 | |
21 | #define ALS_MIN_RANGE_VAL 1 | |
22 | #define ALS_MAX_RANGE_VAL 2 | |
23 | #define POWER_STA_ENABLE 1 | |
24 | #define POWER_STA_DISABLE 0 | |
22d96aa5 | 25 | |
26 | #define DRIVER_NAME "apds9802als" | |
27 | ||
28 | struct als_data { | |
22d96aa5 | 29 | struct mutex mutex; |
30 | }; | |
31 | ||
32 | static ssize_t als_sensing_range_show(struct device *dev, | |
33 | struct device_attribute *attr, char *buf) | |
34 | { | |
35 | struct i2c_client *client = to_i2c_client(dev); | |
36 | int val; | |
37 | ||
38 | val = i2c_smbus_read_byte_data(client, 0x81); | |
39 | if (val < 0) | |
40 | return val; | |
41 | if (val & 1) | |
42 | return sprintf(buf, "4095\n"); | |
43 | else | |
44 | return sprintf(buf, "65535\n"); | |
45 | } | |
46 | ||
f0cfec11 HL |
47 | static int als_wait_for_data_ready(struct device *dev) |
48 | { | |
49 | struct i2c_client *client = to_i2c_client(dev); | |
50 | int ret; | |
51 | int retry = 10; | |
52 | ||
53 | do { | |
54 | msleep(30); | |
55 | ret = i2c_smbus_read_byte_data(client, 0x86); | |
56 | } while (!(ret & 0x80) && retry--); | |
57 | ||
644a9d3b | 58 | if (retry < 0) { |
f0cfec11 HL |
59 | dev_warn(dev, "timeout waiting for data ready\n"); |
60 | return -ETIMEDOUT; | |
61 | } | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
22d96aa5 | 66 | static ssize_t als_lux0_input_data_show(struct device *dev, |
67 | struct device_attribute *attr, char *buf) | |
68 | { | |
69 | struct i2c_client *client = to_i2c_client(dev); | |
70 | struct als_data *data = i2c_get_clientdata(client); | |
f0cfec11 | 71 | int ret_val; |
22d96aa5 | 72 | int temp; |
73 | ||
74 | /* Protect against parallel reads */ | |
f0cfec11 | 75 | pm_runtime_get_sync(dev); |
22d96aa5 | 76 | mutex_lock(&data->mutex); |
f0cfec11 HL |
77 | |
78 | /* clear EOC interrupt status */ | |
79 | i2c_smbus_write_byte(client, 0x40); | |
80 | /* start measurement */ | |
81 | temp = i2c_smbus_read_byte_data(client, 0x81); | |
82 | i2c_smbus_write_byte_data(client, 0x81, temp | 0x08); | |
83 | ||
84 | ret_val = als_wait_for_data_ready(dev); | |
85 | if (ret_val < 0) | |
86 | goto failed; | |
87 | ||
88 | temp = i2c_smbus_read_byte_data(client, 0x8C); /* LSB data */ | |
22d96aa5 | 89 | if (temp < 0) { |
90 | ret_val = temp; | |
91 | goto failed; | |
92 | } | |
f0cfec11 | 93 | ret_val = i2c_smbus_read_byte_data(client, 0x8D); /* MSB data */ |
22d96aa5 | 94 | if (ret_val < 0) |
95 | goto failed; | |
f0cfec11 | 96 | |
22d96aa5 | 97 | mutex_unlock(&data->mutex); |
f0cfec11 HL |
98 | pm_runtime_put_sync(dev); |
99 | ||
100 | temp = (ret_val << 8) | temp; | |
101 | return sprintf(buf, "%d\n", temp); | |
22d96aa5 | 102 | failed: |
103 | mutex_unlock(&data->mutex); | |
f0cfec11 | 104 | pm_runtime_put_sync(dev); |
22d96aa5 | 105 | return ret_val; |
106 | } | |
107 | ||
108 | static ssize_t als_sensing_range_store(struct device *dev, | |
109 | struct device_attribute *attr, const char *buf, size_t count) | |
110 | { | |
111 | struct i2c_client *client = to_i2c_client(dev); | |
112 | struct als_data *data = i2c_get_clientdata(client); | |
1093736b | 113 | int ret_val; |
22d96aa5 | 114 | unsigned long val; |
115 | ||
f7b41276 JH |
116 | ret_val = kstrtoul(buf, 10, &val); |
117 | if (ret_val) | |
118 | return ret_val; | |
22d96aa5 | 119 | |
120 | if (val < 4096) | |
121 | val = 1; | |
122 | else if (val < 65536) | |
123 | val = 2; | |
124 | else | |
125 | return -ERANGE; | |
126 | ||
f0cfec11 HL |
127 | pm_runtime_get_sync(dev); |
128 | ||
22d96aa5 | 129 | /* Make sure nobody else reads/modifies/writes 0x81 while we |
130 | are active */ | |
22d96aa5 | 131 | mutex_lock(&data->mutex); |
132 | ||
133 | ret_val = i2c_smbus_read_byte_data(client, 0x81); | |
134 | if (ret_val < 0) | |
135 | goto fail; | |
136 | ||
137 | /* Reset the bits before setting them */ | |
138 | ret_val = ret_val & 0xFA; | |
139 | ||
f0cfec11 HL |
140 | if (val == 1) /* Setting detection range up to 4k LUX */ |
141 | ret_val = (ret_val | 0x01); | |
142 | else /* Setting detection range up to 64k LUX*/ | |
143 | ret_val = (ret_val | 0x00); | |
22d96aa5 | 144 | |
145 | ret_val = i2c_smbus_write_byte_data(client, 0x81, ret_val); | |
f0cfec11 | 146 | |
22d96aa5 | 147 | if (ret_val >= 0) { |
148 | /* All OK */ | |
149 | mutex_unlock(&data->mutex); | |
f0cfec11 | 150 | pm_runtime_put_sync(dev); |
22d96aa5 | 151 | return count; |
152 | } | |
153 | fail: | |
154 | mutex_unlock(&data->mutex); | |
f0cfec11 | 155 | pm_runtime_put_sync(dev); |
22d96aa5 | 156 | return ret_val; |
157 | } | |
158 | ||
22d96aa5 | 159 | static int als_set_power_state(struct i2c_client *client, bool on_off) |
160 | { | |
161 | int ret_val; | |
162 | struct als_data *data = i2c_get_clientdata(client); | |
163 | ||
164 | mutex_lock(&data->mutex); | |
165 | ret_val = i2c_smbus_read_byte_data(client, 0x80); | |
166 | if (ret_val < 0) | |
167 | goto fail; | |
168 | if (on_off) | |
169 | ret_val = ret_val | 0x01; | |
170 | else | |
171 | ret_val = ret_val & 0xFE; | |
172 | ret_val = i2c_smbus_write_byte_data(client, 0x80, ret_val); | |
173 | fail: | |
174 | mutex_unlock(&data->mutex); | |
175 | return ret_val; | |
176 | } | |
177 | ||
22d96aa5 | 178 | static DEVICE_ATTR(lux0_sensor_range, S_IRUGO | S_IWUSR, |
179 | als_sensing_range_show, als_sensing_range_store); | |
180 | static DEVICE_ATTR(lux0_input, S_IRUGO, als_lux0_input_data_show, NULL); | |
22d96aa5 | 181 | |
182 | static struct attribute *mid_att_als[] = { | |
183 | &dev_attr_lux0_sensor_range.attr, | |
184 | &dev_attr_lux0_input.attr, | |
22d96aa5 | 185 | NULL |
186 | }; | |
187 | ||
579e9a30 | 188 | static const struct attribute_group m_als_gr = { |
22d96aa5 | 189 | .name = "apds9802als", |
190 | .attrs = mid_att_als | |
191 | }; | |
192 | ||
193 | static int als_set_default_config(struct i2c_client *client) | |
194 | { | |
195 | int ret_val; | |
196 | /* Write the command and then switch on */ | |
197 | ret_val = i2c_smbus_write_byte_data(client, 0x80, 0x01); | |
198 | if (ret_val < 0) { | |
199 | dev_err(&client->dev, "failed default switch on write\n"); | |
200 | return ret_val; | |
201 | } | |
f0cfec11 HL |
202 | /* detection range: 1~64K Lux, maunal measurement */ |
203 | ret_val = i2c_smbus_write_byte_data(client, 0x81, 0x08); | |
22d96aa5 | 204 | if (ret_val < 0) |
205 | dev_err(&client->dev, "failed default LUX on write\n"); | |
f0cfec11 HL |
206 | |
207 | /* We always get 0 for the 1st measurement after system power on, | |
208 | * so make sure it is finished before user asks for data. | |
209 | */ | |
210 | als_wait_for_data_ready(&client->dev); | |
211 | ||
22d96aa5 | 212 | return ret_val; |
213 | } | |
214 | ||
f0cfec11 HL |
215 | static int apds9802als_probe(struct i2c_client *client, |
216 | const struct i2c_device_id *id) | |
22d96aa5 | 217 | { |
218 | int res; | |
219 | struct als_data *data; | |
220 | ||
221 | data = kzalloc(sizeof(struct als_data), GFP_KERNEL); | |
222 | if (data == NULL) { | |
223 | dev_err(&client->dev, "Memory allocation failed\n"); | |
224 | return -ENOMEM; | |
225 | } | |
226 | i2c_set_clientdata(client, data); | |
227 | res = sysfs_create_group(&client->dev.kobj, &m_als_gr); | |
228 | if (res) { | |
229 | dev_err(&client->dev, "device create file failed\n"); | |
230 | goto als_error1; | |
231 | } | |
f0cfec11 | 232 | dev_info(&client->dev, "ALS chip found\n"); |
22d96aa5 | 233 | als_set_default_config(client); |
22d96aa5 | 234 | mutex_init(&data->mutex); |
f0cfec11 | 235 | |
4e673599 | 236 | pm_runtime_set_active(&client->dev); |
f0cfec11 | 237 | pm_runtime_enable(&client->dev); |
f0cfec11 | 238 | |
22d96aa5 | 239 | return res; |
240 | als_error1: | |
22d96aa5 | 241 | kfree(data); |
242 | return res; | |
243 | } | |
244 | ||
486a5c28 | 245 | static int apds9802als_remove(struct i2c_client *client) |
22d96aa5 | 246 | { |
247 | struct als_data *data = i2c_get_clientdata(client); | |
f0cfec11 | 248 | |
4e673599 HL |
249 | pm_runtime_get_sync(&client->dev); |
250 | ||
f0cfec11 | 251 | als_set_power_state(client, false); |
22d96aa5 | 252 | sysfs_remove_group(&client->dev.kobj, &m_als_gr); |
4e673599 HL |
253 | |
254 | pm_runtime_disable(&client->dev); | |
255 | pm_runtime_set_suspended(&client->dev); | |
256 | pm_runtime_put_noidle(&client->dev); | |
257 | ||
22d96aa5 | 258 | kfree(data); |
259 | return 0; | |
260 | } | |
261 | ||
f0cfec11 | 262 | #ifdef CONFIG_PM |
22d96aa5 | 263 | |
1c9354b0 | 264 | static int apds9802als_suspend(struct device *dev) |
f0cfec11 HL |
265 | { |
266 | struct i2c_client *client = to_i2c_client(dev); | |
267 | ||
268 | als_set_power_state(client, false); | |
269 | return 0; | |
270 | } | |
271 | ||
1c9354b0 | 272 | static int apds9802als_resume(struct device *dev) |
f0cfec11 HL |
273 | { |
274 | struct i2c_client *client = to_i2c_client(dev); | |
275 | ||
276 | als_set_power_state(client, true); | |
277 | return 0; | |
278 | } | |
279 | ||
1c9354b0 LPC |
280 | static UNIVERSAL_DEV_PM_OPS(apds9802als_pm_ops, apds9802als_suspend, |
281 | apds9802als_resume, NULL); | |
f0cfec11 HL |
282 | |
283 | #define APDS9802ALS_PM_OPS (&apds9802als_pm_ops) | |
284 | ||
285 | #else /* CONFIG_PM */ | |
f0cfec11 HL |
286 | #define APDS9802ALS_PM_OPS NULL |
287 | #endif /* CONFIG_PM */ | |
288 | ||
006dbb38 | 289 | static const struct i2c_device_id apds9802als_id[] = { |
22d96aa5 | 290 | { DRIVER_NAME, 0 }, |
291 | { } | |
292 | }; | |
293 | ||
294 | MODULE_DEVICE_TABLE(i2c, apds9802als_id); | |
295 | ||
296 | static struct i2c_driver apds9802als_driver = { | |
297 | .driver = { | |
f0cfec11 HL |
298 | .name = DRIVER_NAME, |
299 | .pm = APDS9802ALS_PM_OPS, | |
22d96aa5 | 300 | }, |
301 | .probe = apds9802als_probe, | |
2d6bed9c | 302 | .remove = apds9802als_remove, |
22d96aa5 | 303 | .id_table = apds9802als_id, |
304 | }; | |
305 | ||
a64fe2ed | 306 | module_i2c_driver(apds9802als_driver); |
22d96aa5 | 307 | |
308 | MODULE_AUTHOR("Anantha Narayanan <Anantha.Narayanan@intel.com"); | |
309 | MODULE_DESCRIPTION("Avago apds9802als ALS Driver"); | |
310 | MODULE_LICENSE("GPL v2"); |