xref: /linux/drivers/misc/isl29020.c (revision c6fbb759)
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, 0x00);
31 
32 	if (val < 0)
33 		return val;
34 	return sprintf(buf, "%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(100);
48 
49 	mutex_lock(&mutex);
50 	temp = i2c_smbus_read_byte_data(client, 0x02); /* MSB data */
51 	if (temp < 0) {
52 		pm_runtime_put_sync(dev);
53 		mutex_unlock(&mutex);
54 		return temp;
55 	}
56 
57 	ret_val = i2c_smbus_read_byte_data(client, 0x01); /* LSB data */
58 	mutex_unlock(&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, 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, "%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(buf, 10, &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, 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, 0x00, 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, 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, 0x00, 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, 0x00, 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 					const struct i2c_device_id *id)
156 {
157 	int res;
158 
159 	res = als_set_default_config(client);
160 	if (res <  0)
161 		return res;
162 
163 	res = sysfs_create_group(&client->dev.kobj, &m_als_gr);
164 	if (res) {
165 		dev_err(&client->dev, "isl29020: device create file failed\n");
166 		return res;
167 	}
168 	dev_info(&client->dev, "%s isl29020: ALS chip found\n", client->name);
169 	als_set_power_state(client, 0);
170 	pm_runtime_enable(&client->dev);
171 	return res;
172 }
173 
174 static void isl29020_remove(struct i2c_client *client)
175 {
176 	pm_runtime_disable(&client->dev);
177 	sysfs_remove_group(&client->dev.kobj, &m_als_gr);
178 }
179 
180 static const struct i2c_device_id isl29020_id[] = {
181 	{ "isl29020", 0 },
182 	{ }
183 };
184 
185 MODULE_DEVICE_TABLE(i2c, isl29020_id);
186 
187 #ifdef CONFIG_PM
188 
189 static int isl29020_runtime_suspend(struct device *dev)
190 {
191 	struct i2c_client *client = to_i2c_client(dev);
192 	als_set_power_state(client, 0);
193 	return 0;
194 }
195 
196 static int isl29020_runtime_resume(struct device *dev)
197 {
198 	struct i2c_client *client = to_i2c_client(dev);
199 	als_set_power_state(client, 1);
200 	return 0;
201 }
202 
203 static const struct dev_pm_ops isl29020_pm_ops = {
204 	.runtime_suspend = isl29020_runtime_suspend,
205 	.runtime_resume = isl29020_runtime_resume,
206 };
207 
208 #define ISL29020_PM_OPS (&isl29020_pm_ops)
209 #else	/* CONFIG_PM */
210 #define ISL29020_PM_OPS NULL
211 #endif	/* CONFIG_PM */
212 
213 static struct i2c_driver isl29020_driver = {
214 	.driver = {
215 		.name = "isl29020",
216 		.pm = ISL29020_PM_OPS,
217 	},
218 	.probe = isl29020_probe,
219 	.remove = isl29020_remove,
220 	.id_table = isl29020_id,
221 };
222 
223 module_i2c_driver(isl29020_driver);
224 
225 MODULE_AUTHOR("Kalhan Trisal <kalhan.trisal@intel.com>");
226 MODULE_DESCRIPTION("Intersil isl29020 ALS Driver");
227 MODULE_LICENSE("GPL v2");
228