1873e65bcSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
22e85c4ddSKalhan Trisal /*
32e85c4ddSKalhan Trisal * isl29020.c - Intersil ALS Driver
42e85c4ddSKalhan Trisal *
52e85c4ddSKalhan Trisal * Copyright (C) 2008 Intel Corp
62e85c4ddSKalhan Trisal *
72e85c4ddSKalhan Trisal * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82e85c4ddSKalhan Trisal *
92e85c4ddSKalhan Trisal * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102e85c4ddSKalhan Trisal *
112e85c4ddSKalhan Trisal * Data sheet at: http://www.intersil.com/data/fn/fn6505.pdf
122e85c4ddSKalhan Trisal */
132e85c4ddSKalhan Trisal
142e85c4ddSKalhan Trisal #include <linux/module.h>
152e85c4ddSKalhan Trisal #include <linux/slab.h>
162e85c4ddSKalhan Trisal #include <linux/i2c.h>
172e85c4ddSKalhan Trisal #include <linux/err.h>
182e85c4ddSKalhan Trisal #include <linux/delay.h>
192e85c4ddSKalhan Trisal #include <linux/sysfs.h>
202e85c4ddSKalhan Trisal #include <linux/pm_runtime.h>
212e85c4ddSKalhan Trisal
222e85c4ddSKalhan Trisal static DEFINE_MUTEX(mutex);
232e85c4ddSKalhan Trisal
als_sensing_range_show(struct device * dev,struct device_attribute * attr,char * buf)242e85c4ddSKalhan Trisal static ssize_t als_sensing_range_show(struct device *dev,
252e85c4ddSKalhan Trisal struct device_attribute *attr, char *buf)
262e85c4ddSKalhan Trisal {
272e85c4ddSKalhan Trisal struct i2c_client *client = to_i2c_client(dev);
282e85c4ddSKalhan Trisal int val;
292e85c4ddSKalhan Trisal
302e85c4ddSKalhan Trisal val = i2c_smbus_read_byte_data(client, 0x00);
312e85c4ddSKalhan Trisal
322e85c4ddSKalhan Trisal if (val < 0)
332e85c4ddSKalhan Trisal return val;
342e85c4ddSKalhan Trisal return sprintf(buf, "%d000\n", 1 << (2 * (val & 3)));
352e85c4ddSKalhan Trisal
362e85c4ddSKalhan Trisal }
372e85c4ddSKalhan Trisal
als_lux_input_data_show(struct device * dev,struct device_attribute * attr,char * buf)382e85c4ddSKalhan Trisal static ssize_t als_lux_input_data_show(struct device *dev,
392e85c4ddSKalhan Trisal struct device_attribute *attr, char *buf)
402e85c4ddSKalhan Trisal {
412e85c4ddSKalhan Trisal struct i2c_client *client = to_i2c_client(dev);
422e85c4ddSKalhan Trisal int ret_val, val;
432e85c4ddSKalhan Trisal unsigned long int lux;
442e85c4ddSKalhan Trisal int temp;
452e85c4ddSKalhan Trisal
462e85c4ddSKalhan Trisal pm_runtime_get_sync(dev);
472e85c4ddSKalhan Trisal msleep(100);
482e85c4ddSKalhan Trisal
492e85c4ddSKalhan Trisal mutex_lock(&mutex);
502e85c4ddSKalhan Trisal temp = i2c_smbus_read_byte_data(client, 0x02); /* MSB data */
512e85c4ddSKalhan Trisal if (temp < 0) {
522e85c4ddSKalhan Trisal pm_runtime_put_sync(dev);
532e85c4ddSKalhan Trisal mutex_unlock(&mutex);
542e85c4ddSKalhan Trisal return temp;
552e85c4ddSKalhan Trisal }
562e85c4ddSKalhan Trisal
572e85c4ddSKalhan Trisal ret_val = i2c_smbus_read_byte_data(client, 0x01); /* LSB data */
582e85c4ddSKalhan Trisal mutex_unlock(&mutex);
592e85c4ddSKalhan Trisal
602e85c4ddSKalhan Trisal if (ret_val < 0) {
612e85c4ddSKalhan Trisal pm_runtime_put_sync(dev);
622e85c4ddSKalhan Trisal return ret_val;
632e85c4ddSKalhan Trisal }
642e85c4ddSKalhan Trisal
652e85c4ddSKalhan Trisal ret_val |= temp << 8;
662e85c4ddSKalhan Trisal val = i2c_smbus_read_byte_data(client, 0x00);
672e85c4ddSKalhan Trisal pm_runtime_put_sync(dev);
682e85c4ddSKalhan Trisal if (val < 0)
692e85c4ddSKalhan Trisal return val;
702e85c4ddSKalhan Trisal lux = ((((1 << (2 * (val & 3))))*1000) * ret_val) / 65536;
712e85c4ddSKalhan Trisal return sprintf(buf, "%ld\n", lux);
722e85c4ddSKalhan Trisal }
732e85c4ddSKalhan Trisal
als_sensing_range_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)742e85c4ddSKalhan Trisal static ssize_t als_sensing_range_store(struct device *dev,
752e85c4ddSKalhan Trisal struct device_attribute *attr, const char *buf, size_t count)
762e85c4ddSKalhan Trisal {
772e85c4ddSKalhan Trisal struct i2c_client *client = to_i2c_client(dev);
7890482e45SDan Carpenter int ret_val;
792e85c4ddSKalhan Trisal unsigned long val;
802e85c4ddSKalhan Trisal
81f7b41276SJingoo Han ret_val = kstrtoul(buf, 10, &val);
82f7b41276SJingoo Han if (ret_val)
83f7b41276SJingoo Han return ret_val;
84f7b41276SJingoo Han
852e85c4ddSKalhan Trisal if (val < 1 || val > 64000)
862e85c4ddSKalhan Trisal return -EINVAL;
872e85c4ddSKalhan Trisal
882e85c4ddSKalhan Trisal /* Pick the smallest sensor range that will meet our requirements */
892e85c4ddSKalhan Trisal if (val <= 1000)
902e85c4ddSKalhan Trisal val = 1;
912e85c4ddSKalhan Trisal else if (val <= 4000)
922e85c4ddSKalhan Trisal val = 2;
932e85c4ddSKalhan Trisal else if (val <= 16000)
942e85c4ddSKalhan Trisal val = 3;
952e85c4ddSKalhan Trisal else
962e85c4ddSKalhan Trisal val = 4;
972e85c4ddSKalhan Trisal
982e85c4ddSKalhan Trisal ret_val = i2c_smbus_read_byte_data(client, 0x00);
9990482e45SDan Carpenter if (ret_val < 0)
10090482e45SDan Carpenter return ret_val;
1012e85c4ddSKalhan Trisal
1022e85c4ddSKalhan Trisal ret_val &= 0xFC; /*reset the bit before setting them */
1032e85c4ddSKalhan Trisal ret_val |= val - 1;
1042e85c4ddSKalhan Trisal ret_val = i2c_smbus_write_byte_data(client, 0x00, ret_val);
1052e85c4ddSKalhan Trisal
1062e85c4ddSKalhan Trisal if (ret_val < 0)
1072e85c4ddSKalhan Trisal return ret_val;
1082e85c4ddSKalhan Trisal return count;
1092e85c4ddSKalhan Trisal }
1102e85c4ddSKalhan Trisal
als_set_power_state(struct i2c_client * client,int enable)1112e85c4ddSKalhan Trisal static void als_set_power_state(struct i2c_client *client, int enable)
1122e85c4ddSKalhan Trisal {
1132e85c4ddSKalhan Trisal int ret_val;
1142e85c4ddSKalhan Trisal
1152e85c4ddSKalhan Trisal ret_val = i2c_smbus_read_byte_data(client, 0x00);
1162e85c4ddSKalhan Trisal if (ret_val < 0)
1172e85c4ddSKalhan Trisal return;
1182e85c4ddSKalhan Trisal
1192e85c4ddSKalhan Trisal if (enable)
1202e85c4ddSKalhan Trisal ret_val |= 0x80;
1212e85c4ddSKalhan Trisal else
1222e85c4ddSKalhan Trisal ret_val &= 0x7F;
1232e85c4ddSKalhan Trisal
1242e85c4ddSKalhan Trisal i2c_smbus_write_byte_data(client, 0x00, ret_val);
1252e85c4ddSKalhan Trisal }
1262e85c4ddSKalhan Trisal
1272e85c4ddSKalhan Trisal static DEVICE_ATTR(lux0_sensor_range, S_IRUGO | S_IWUSR,
1282e85c4ddSKalhan Trisal als_sensing_range_show, als_sensing_range_store);
1292e85c4ddSKalhan Trisal static DEVICE_ATTR(lux0_input, S_IRUGO, als_lux_input_data_show, NULL);
1302e85c4ddSKalhan Trisal
1312e85c4ddSKalhan Trisal static struct attribute *mid_att_als[] = {
1322e85c4ddSKalhan Trisal &dev_attr_lux0_sensor_range.attr,
1332e85c4ddSKalhan Trisal &dev_attr_lux0_input.attr,
1342e85c4ddSKalhan Trisal NULL
1352e85c4ddSKalhan Trisal };
1362e85c4ddSKalhan Trisal
13764e6d2c1SArvind Yadav static const struct attribute_group m_als_gr = {
1382e85c4ddSKalhan Trisal .name = "isl29020",
1392e85c4ddSKalhan Trisal .attrs = mid_att_als
1402e85c4ddSKalhan Trisal };
1412e85c4ddSKalhan Trisal
als_set_default_config(struct i2c_client * client)1422e85c4ddSKalhan Trisal static int als_set_default_config(struct i2c_client *client)
1432e85c4ddSKalhan Trisal {
1442e85c4ddSKalhan Trisal int retval;
1452e85c4ddSKalhan Trisal
1462e85c4ddSKalhan Trisal retval = i2c_smbus_write_byte_data(client, 0x00, 0xc0);
1472e85c4ddSKalhan Trisal if (retval < 0) {
1482e85c4ddSKalhan Trisal dev_err(&client->dev, "default write failed.");
1492e85c4ddSKalhan Trisal return retval;
1502e85c4ddSKalhan Trisal }
1512a5ac6f7SJesper Juhl return 0;
1522e85c4ddSKalhan Trisal }
1532e85c4ddSKalhan Trisal
isl29020_probe(struct i2c_client * client)154327e1ad1SUwe Kleine-König static int isl29020_probe(struct i2c_client *client)
1552e85c4ddSKalhan Trisal {
1562e85c4ddSKalhan Trisal int res;
1572e85c4ddSKalhan Trisal
1582e85c4ddSKalhan Trisal res = als_set_default_config(client);
1592e85c4ddSKalhan Trisal if (res < 0)
1602e85c4ddSKalhan Trisal return res;
1612e85c4ddSKalhan Trisal
1622e85c4ddSKalhan Trisal res = sysfs_create_group(&client->dev.kobj, &m_als_gr);
1632e85c4ddSKalhan Trisal if (res) {
1642e85c4ddSKalhan Trisal dev_err(&client->dev, "isl29020: device create file failed\n");
1652e85c4ddSKalhan Trisal return res;
1662e85c4ddSKalhan Trisal }
1672e85c4ddSKalhan Trisal dev_info(&client->dev, "%s isl29020: ALS chip found\n", client->name);
1682e85c4ddSKalhan Trisal als_set_power_state(client, 0);
1692e85c4ddSKalhan Trisal pm_runtime_enable(&client->dev);
1702e85c4ddSKalhan Trisal return res;
1712e85c4ddSKalhan Trisal }
1722e85c4ddSKalhan Trisal
isl29020_remove(struct i2c_client * client)173ed5c2f5fSUwe Kleine-König static void isl29020_remove(struct i2c_client *client)
1742e85c4ddSKalhan Trisal {
175efb5bea6SChuhong Yuan pm_runtime_disable(&client->dev);
1762e85c4ddSKalhan Trisal sysfs_remove_group(&client->dev.kobj, &m_als_gr);
1772e85c4ddSKalhan Trisal }
1782e85c4ddSKalhan Trisal
17907e4049aSArvind Yadav static const struct i2c_device_id isl29020_id[] = {
1802e85c4ddSKalhan Trisal { "isl29020", 0 },
1812e85c4ddSKalhan Trisal { }
1822e85c4ddSKalhan Trisal };
1832e85c4ddSKalhan Trisal
1842e85c4ddSKalhan Trisal MODULE_DEVICE_TABLE(i2c, isl29020_id);
1852e85c4ddSKalhan Trisal
1862e85c4ddSKalhan Trisal #ifdef CONFIG_PM
1872e85c4ddSKalhan Trisal
isl29020_runtime_suspend(struct device * dev)1882e85c4ddSKalhan Trisal static int isl29020_runtime_suspend(struct device *dev)
1892e85c4ddSKalhan Trisal {
1902e85c4ddSKalhan Trisal struct i2c_client *client = to_i2c_client(dev);
1912e85c4ddSKalhan Trisal als_set_power_state(client, 0);
1922e85c4ddSKalhan Trisal return 0;
1932e85c4ddSKalhan Trisal }
1942e85c4ddSKalhan Trisal
isl29020_runtime_resume(struct device * dev)1952e85c4ddSKalhan Trisal static int isl29020_runtime_resume(struct device *dev)
1962e85c4ddSKalhan Trisal {
1972e85c4ddSKalhan Trisal struct i2c_client *client = to_i2c_client(dev);
1982e85c4ddSKalhan Trisal als_set_power_state(client, 1);
1992e85c4ddSKalhan Trisal return 0;
2002e85c4ddSKalhan Trisal }
2012e85c4ddSKalhan Trisal
2022e85c4ddSKalhan Trisal static const struct dev_pm_ops isl29020_pm_ops = {
2032e85c4ddSKalhan Trisal .runtime_suspend = isl29020_runtime_suspend,
2042e85c4ddSKalhan Trisal .runtime_resume = isl29020_runtime_resume,
2052e85c4ddSKalhan Trisal };
2062e85c4ddSKalhan Trisal
2072e85c4ddSKalhan Trisal #define ISL29020_PM_OPS (&isl29020_pm_ops)
2082e85c4ddSKalhan Trisal #else /* CONFIG_PM */
2092e85c4ddSKalhan Trisal #define ISL29020_PM_OPS NULL
2102e85c4ddSKalhan Trisal #endif /* CONFIG_PM */
2112e85c4ddSKalhan Trisal
2122e85c4ddSKalhan Trisal static struct i2c_driver isl29020_driver = {
2132e85c4ddSKalhan Trisal .driver = {
2142e85c4ddSKalhan Trisal .name = "isl29020",
2152e85c4ddSKalhan Trisal .pm = ISL29020_PM_OPS,
2162e85c4ddSKalhan Trisal },
217*f050bb8fSUwe Kleine-König .probe = isl29020_probe,
2182e85c4ddSKalhan Trisal .remove = isl29020_remove,
2192e85c4ddSKalhan Trisal .id_table = isl29020_id,
2202e85c4ddSKalhan Trisal };
2212e85c4ddSKalhan Trisal
222a64fe2edSAxel Lin module_i2c_driver(isl29020_driver);
2232e85c4ddSKalhan Trisal
224b38eeaaeSAxel Lin MODULE_AUTHOR("Kalhan Trisal <kalhan.trisal@intel.com>");
2252e85c4ddSKalhan Trisal MODULE_DESCRIPTION("Intersil isl29020 ALS Driver");
2262e85c4ddSKalhan Trisal MODULE_LICENSE("GPL v2");
227