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 
als_sensing_range_show(struct device * dev,struct device_attribute * attr,char * buf)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 
als_lux_input_data_show(struct device * dev,struct device_attribute * attr,char * buf)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 
als_sensing_range_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)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 
als_set_power_state(struct i2c_client * client,int enable)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 
als_set_default_config(struct i2c_client * client)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 
isl29020_probe(struct i2c_client * client)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(&client->dev.kobj, &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, 0);
169 	pm_runtime_enable(&client->dev);
170 	return res;
171 }
172 
isl29020_remove(struct i2c_client * client)173 static void isl29020_remove(struct i2c_client *client)
174 {
175 	pm_runtime_disable(&client->dev);
176 	sysfs_remove_group(&client->dev.kobj, &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 
isl29020_runtime_suspend(struct device * dev)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, 0);
192 	return 0;
193 }
194 
isl29020_runtime_resume(struct device * dev)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, 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