1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * isl29020.c - Intersil ALS Driver |
4 | * |
5 | * Copyright (C) 2008 Intel Corp |
6 | * |
7 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
8 | * |
9 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
10 | * |
11 | * Data sheet at: http://www.intersil.com/data/fn/fn6505.pdf |
12 | */ |
13 | |
14 | #include <linux/module.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/i2c.h> |
17 | #include <linux/err.h> |
18 | #include <linux/delay.h> |
19 | #include <linux/sysfs.h> |
20 | #include <linux/pm_runtime.h> |
21 | |
22 | static DEFINE_MUTEX(mutex); |
23 | |
24 | static ssize_t als_sensing_range_show(struct device *dev, |
25 | struct device_attribute *attr, char *buf) |
26 | { |
27 | struct i2c_client *client = to_i2c_client(dev); |
28 | int val; |
29 | |
30 | val = i2c_smbus_read_byte_data(client, command: 0x00); |
31 | |
32 | if (val < 0) |
33 | return val; |
34 | return sprintf(buf, fmt: "%d000\n" , 1 << (2 * (val & 3))); |
35 | |
36 | } |
37 | |
38 | static ssize_t als_lux_input_data_show(struct device *dev, |
39 | struct device_attribute *attr, char *buf) |
40 | { |
41 | struct i2c_client *client = to_i2c_client(dev); |
42 | int ret_val, val; |
43 | unsigned long int lux; |
44 | int temp; |
45 | |
46 | pm_runtime_get_sync(dev); |
47 | msleep(msecs: 100); |
48 | |
49 | mutex_lock(&mutex); |
50 | temp = i2c_smbus_read_byte_data(client, command: 0x02); /* MSB data */ |
51 | if (temp < 0) { |
52 | pm_runtime_put_sync(dev); |
53 | mutex_unlock(lock: &mutex); |
54 | return temp; |
55 | } |
56 | |
57 | ret_val = i2c_smbus_read_byte_data(client, command: 0x01); /* LSB data */ |
58 | mutex_unlock(lock: &mutex); |
59 | |
60 | if (ret_val < 0) { |
61 | pm_runtime_put_sync(dev); |
62 | return ret_val; |
63 | } |
64 | |
65 | ret_val |= temp << 8; |
66 | val = i2c_smbus_read_byte_data(client, command: 0x00); |
67 | pm_runtime_put_sync(dev); |
68 | if (val < 0) |
69 | return val; |
70 | lux = ((((1 << (2 * (val & 3))))*1000) * ret_val) / 65536; |
71 | return sprintf(buf, fmt: "%ld\n" , lux); |
72 | } |
73 | |
74 | static ssize_t als_sensing_range_store(struct device *dev, |
75 | struct device_attribute *attr, const char *buf, size_t count) |
76 | { |
77 | struct i2c_client *client = to_i2c_client(dev); |
78 | int ret_val; |
79 | unsigned long val; |
80 | |
81 | ret_val = kstrtoul(s: buf, base: 10, res: &val); |
82 | if (ret_val) |
83 | return ret_val; |
84 | |
85 | if (val < 1 || val > 64000) |
86 | return -EINVAL; |
87 | |
88 | /* Pick the smallest sensor range that will meet our requirements */ |
89 | if (val <= 1000) |
90 | val = 1; |
91 | else if (val <= 4000) |
92 | val = 2; |
93 | else if (val <= 16000) |
94 | val = 3; |
95 | else |
96 | val = 4; |
97 | |
98 | ret_val = i2c_smbus_read_byte_data(client, command: 0x00); |
99 | if (ret_val < 0) |
100 | return ret_val; |
101 | |
102 | ret_val &= 0xFC; /*reset the bit before setting them */ |
103 | ret_val |= val - 1; |
104 | ret_val = i2c_smbus_write_byte_data(client, command: 0x00, value: ret_val); |
105 | |
106 | if (ret_val < 0) |
107 | return ret_val; |
108 | return count; |
109 | } |
110 | |
111 | static void als_set_power_state(struct i2c_client *client, int enable) |
112 | { |
113 | int ret_val; |
114 | |
115 | ret_val = i2c_smbus_read_byte_data(client, command: 0x00); |
116 | if (ret_val < 0) |
117 | return; |
118 | |
119 | if (enable) |
120 | ret_val |= 0x80; |
121 | else |
122 | ret_val &= 0x7F; |
123 | |
124 | i2c_smbus_write_byte_data(client, command: 0x00, value: ret_val); |
125 | } |
126 | |
127 | static DEVICE_ATTR(lux0_sensor_range, S_IRUGO | S_IWUSR, |
128 | als_sensing_range_show, als_sensing_range_store); |
129 | static DEVICE_ATTR(lux0_input, S_IRUGO, als_lux_input_data_show, NULL); |
130 | |
131 | static struct attribute *mid_att_als[] = { |
132 | &dev_attr_lux0_sensor_range.attr, |
133 | &dev_attr_lux0_input.attr, |
134 | NULL |
135 | }; |
136 | |
137 | static const struct attribute_group m_als_gr = { |
138 | .name = "isl29020" , |
139 | .attrs = mid_att_als |
140 | }; |
141 | |
142 | static int als_set_default_config(struct i2c_client *client) |
143 | { |
144 | int retval; |
145 | |
146 | retval = i2c_smbus_write_byte_data(client, command: 0x00, value: 0xc0); |
147 | if (retval < 0) { |
148 | dev_err(&client->dev, "default write failed." ); |
149 | return retval; |
150 | } |
151 | return 0; |
152 | } |
153 | |
154 | static int isl29020_probe(struct i2c_client *client) |
155 | { |
156 | int res; |
157 | |
158 | res = als_set_default_config(client); |
159 | if (res < 0) |
160 | return res; |
161 | |
162 | res = sysfs_create_group(kobj: &client->dev.kobj, grp: &m_als_gr); |
163 | if (res) { |
164 | dev_err(&client->dev, "isl29020: device create file failed\n" ); |
165 | return res; |
166 | } |
167 | dev_info(&client->dev, "%s isl29020: ALS chip found\n" , client->name); |
168 | als_set_power_state(client, enable: 0); |
169 | pm_runtime_enable(dev: &client->dev); |
170 | return res; |
171 | } |
172 | |
173 | static void isl29020_remove(struct i2c_client *client) |
174 | { |
175 | pm_runtime_disable(dev: &client->dev); |
176 | sysfs_remove_group(kobj: &client->dev.kobj, grp: &m_als_gr); |
177 | } |
178 | |
179 | static const struct i2c_device_id isl29020_id[] = { |
180 | { "isl29020" , 0 }, |
181 | { } |
182 | }; |
183 | |
184 | MODULE_DEVICE_TABLE(i2c, isl29020_id); |
185 | |
186 | #ifdef CONFIG_PM |
187 | |
188 | static int isl29020_runtime_suspend(struct device *dev) |
189 | { |
190 | struct i2c_client *client = to_i2c_client(dev); |
191 | als_set_power_state(client, enable: 0); |
192 | return 0; |
193 | } |
194 | |
195 | static int isl29020_runtime_resume(struct device *dev) |
196 | { |
197 | struct i2c_client *client = to_i2c_client(dev); |
198 | als_set_power_state(client, enable: 1); |
199 | return 0; |
200 | } |
201 | |
202 | static const struct dev_pm_ops isl29020_pm_ops = { |
203 | .runtime_suspend = isl29020_runtime_suspend, |
204 | .runtime_resume = isl29020_runtime_resume, |
205 | }; |
206 | |
207 | #define ISL29020_PM_OPS (&isl29020_pm_ops) |
208 | #else /* CONFIG_PM */ |
209 | #define ISL29020_PM_OPS NULL |
210 | #endif /* CONFIG_PM */ |
211 | |
212 | static struct i2c_driver isl29020_driver = { |
213 | .driver = { |
214 | .name = "isl29020" , |
215 | .pm = ISL29020_PM_OPS, |
216 | }, |
217 | .probe = isl29020_probe, |
218 | .remove = isl29020_remove, |
219 | .id_table = isl29020_id, |
220 | }; |
221 | |
222 | module_i2c_driver(isl29020_driver); |
223 | |
224 | MODULE_AUTHOR("Kalhan Trisal <kalhan.trisal@intel.com>" ); |
225 | MODULE_DESCRIPTION("Intersil isl29020 ALS Driver" ); |
226 | MODULE_LICENSE("GPL v2" ); |
227 | |